I'm using the connexion framework for flask. I'd like to group several related functions into a class and I'm wondering whether methods of a class can be used for operationId instead of functions.
Something like
class Job:
def get():
"something"
def post():
"something"
in the describing yaml file:
paths:
/hello:
post:
operationId: myapp.api.Job.post
You can use the methods if they are available as staticmethods on the class:
class Job:
#staticmethod
def get():
"something"
#staticmethod
def post():
"something"
Related
The background
In python, if you were defining an Abstract Base Class which requires that its methods be overwritten, you'd do:
from abc import ABC, abstractmethod
class MyAbstractClass(ABC):
#abstractmethod
def my_method(self):
pass
The following code would then fail because it doesn't implement my_method.
class MyConcreteClass(MyAbstractClass):
pass
But what if I want to define the method requirements of a mixin class?
class MyMixin:
def my_mixin_method(self):
self.a_required_method()
The following code is then valid:
class MyBase:
def a_required_method(self):
pass
class MyFull(MyMixin, MyBase):
pass
The following code is also valid...
class MyDubious(MyMixin):
pass
But exposes an error at runtime:
MyFull().my_mixin_method() # Works
MyDubious().my_mixin_method() # Runtime error
The Question
Is there something like AbstractBaseClass which can be added to Mixin classes, to ensure that a derived class can't be instantiated unless it inherits correctly?
I'm thinking a nice API would look like:
from asc import ASC, requiredmethod
class MyRobustMixin(ASC):
#requiredmethod
def a_required_method(self):
pass
def my_mixin_method(self):
self.a_required_method()
I have classes like these
class Test:
def __str__(self):
return "Test"
class Test1(Test):
def __str__(self):
return "Test1"
class Test2(Test):
def __str__(self):
return "Test2"
class Runner:
pass
class Runner1(Runner):
def run(self):
print("I'm a method, doing this and that")
print(f"And I use {Test1()}")
class Runner2(Runner):
def func2(self):
print("I'm a method, doing this and that")
test = Test2()
print(f"And I use {test}")
and I would like to discover all Runner classes, which use Test instances, like this:
for klass, func, ref in get_all_references(Runner):
if isinstance(ref, Test):
print(f"{klass.__name}.{func.__name} uses Test!")
That is, I'm looking for the get_all_references method, which returns all referenced objects of any classes of type Runner (and their methods), which I can inspect for class type/inheritance.
The motivation behind this is to discover all places (class/method names) where instances of Test are used.
I think part of the problem is solved by static analyzers/doc creators/cross reference builders, but I couldn't find any which could be used to get this information via an API.
i think gc module has several useful functions in that matter, but it sounds like gc.get_referrers() is what you need.
I am currently in the process of writing a Flask application that routes endpoints to a variety of "Actions." These actions all implement a parent function called "run()"
In code:
import abc
class Action(object):
__metaclass__ = abc.ABCMeta
#classmethod
def authenticated(self):
print("bypassing action authentication")
return True
#classmethod
def authorized(self):
print("bypassing action authorization")
return True
#classmethod
#abc.abstractmethod
def execute(self):
raise NotImplementedError("must override execute!")
#classmethod
def response(self, executeResult):
return executeResult
#classmethod
def run(self):
result = ""
if self.authenticated() & self.authorized():
result = self.execute()
return self.response(result)
The intent is that all actually used actions are derived members of this Action class that bare-minimum implement an execute() function that differentiates them. Unfortunately, when I attempt to add routes for these
app.add_url_rule('/endone/', methods=['GET'], view_func=CoreActions.ActionOne.run)
app.add_url_rule('/endtwo/', methods=['GET'], view_func=CoreActions.ActionTwo.run)
I receive the following error:
AssertionError: View function mapping is overwriting an existing endpoint function: run
Does anyone know a possible solution to this issue? Thanks!
The common approach of generating view functions is to use Flask views. Subclass your Action class from flask.views.View, dispatch_request method is used instead of run:
import abc
from flask.views import View
class Action(View):
__metaclass__ = abc.ABCMeta
def authenticated(self):
print("bypassing action authentication")
return True
def authorized(self):
print("bypassing action authorization")
return True
#abc.abstractmethod
def execute(self):
raise NotImplementedError("must override execute!")
def response(self, executeResult):
return executeResult
def dispatch_request(self):
result = ""
if self.authenticated() & self.authorized():
result = self.execute()
return self.response(result)
And you can add routes using View.as_view() method which convert your class to view function:
app.add_url_rule(
'/endone/',
methods=['GET'],
view_func=CoreActions.ActionOne.as_view('endone')
)
In the Flask-RESTful example application posted here, the TODOS collection is a global variable.
After the Todo Resource is registered:
api.add_resource(Todo, '/todos/<string:todo_id>')
The Todo methods access the global TODOS variable when web requests are processed.
Instead, I want to instantiate the API within a class and pass a TODOS collection that is a class variable rather than a global variable.
When using Flask-RESTful, what is the proper way to allow methods in a Resource class to gain access to a variable provided by the calling class without using global variables?
Looks like I didn't understand you the first time, You can just use a classmethod to construct your API. Then add it as a resource
from flask import Flask
from flask.ext.restful import Api
class SomeApi(Resource):
def get(self):
return self.response
#classmethod
def make_api(cls, response):
cls.response = response
return cls
class KillerApp(object):
def __init__(self):
self.app = Flask()
app_api = Api(self.app)
MyApi = SomeAPI.make_api({"key": "value"})
app_api.add_resource(MyApi, "/api/path")
def run(self)
self.app.run()
KillerApp().run()
add_resource accepts two arguments, resource_class_args and resource_class_kwargs, used to pass arguments to the constructor. (source)
So you could have a Resource:
from flask_restful import Resource
class TodoNext(Resource):
def __init__(self, **kwargs):
# smart_engine is a black box dependency
self.smart_engine = kwargs['smart_engine']
def get(self):
return self.smart_engine.next_todo()
You can inject the required dependency into TodoNext like so:
smart_engine = SmartEngine()
api.add_resource(TodoNext, '/next',
resource_class_kwargs={ 'smart_engine': smart_engine })
based on #Greg answer I've added an initialization check in the init method:
creating and calling Todo Resource class for flask-restful api:
todo = Todo.create(InMemoryTodoRepository())
api.add_resource(todo, '/api/todos/<todo_id>')
The Todo Resource class:
from flask_restful import reqparse, abort, Resource
from server.ApiResources.DTOs.TodoDTO import TodoDTO
from server.Repositories.ITodoRepository import ITodoRepository
from server.Utils.Exceptions import InvalidInstantiationError
from server.Utils.GeneralUtils import member_exists
class Todo(Resource):
"""shows a single todo item and lets you delete a todo item
use the 'create' class method to instantiate the class
"""
def __init__(self):
if not member_exists(self, "todo_repository", of_type=ITodoRepository):
raise InvalidInstantiationError("Todo", "todo_repository", "ITodoRepository", "create")
self._parser = reqparse.RequestParser()
self._parser.add_argument('task', type=str)
#classmethod
def create(cls, todo_repository):
"""
:param todo_repository: an instance of ITodoRepository
:return: class object of Todo Resource
"""
cls.todo_repository = todo_repository
return cls
the member_exists helper methods:
def member_exists(obj, member, of_type):
member_value = getattr(obj, member, None)
if member_value is None:
return False
if not isinstance(member_value, of_type):
return False
return True
and the custom exception class:
class InvalidInstantiationError(Exception):
def __init__(self, origin_class_name, missing_argument_name, missing_argument_type, instantiation_method_to_use):
message = """Invalid instantiation for class '{class_name}':
missing instantiation argument '{arg}' of type '{arg_type}'.
Please use the '{method_name}' factory class method""" \
.format(class_name=origin_class_name,
arg=missing_argument_name,
arg_type=missing_argument_type,
method_name=instantiation_method_to_use)
# Call the base class constructor with the parameters it needs
super(InvalidInstantiationError, self).__init__(message)
Thus, trying to use the default constructor will end up in getting this exception:
server.Utils.Exceptions.InvalidInstantiationError: Invalid instantiation for class 'Todo':
missing instantiation argument 'todo_repository' of type 'ITodoRepository'.
Please use the 'create' factory class method
edit: this can be useful for using dependency injection with flask-restful api Resource classes (with or without IoC)
edit 2:
we can even go cleaner and add another help function (ready to import):
def must_have(obj, member, of_type, use_method):
if not member_exists(obj, member, of_type=of_type):
raise InvalidInstantiationError(obj.__class__.__name__,
member,
of_type.__name__,
use_method)
and then use it in the constructor like that:
from server.Utils.GeneralUtils import must_have
class Todo(Resource):
def __init__(self):
must_have(self,
member="todo_repository",
of_type=ITodoRepository,
use_method=Todo.create.__name__)
I'm trying to use decorators in order to manage the way users may or may not access resources within a web application (running on Google App Engine). Please note that I'm not allowing users to log in with their Google accounts, so setting specific access rights to specific routes within app.yaml is not an option.
I used the following resources :
- Bruce Eckel's guide to decorators
- SO : get-class-in-python-decorator2
- SO : python-decorators-and-inheritance
- SO : get-class-in-python-decorator
However I'm still a bit confused...
Here's my code ! In the following example, current_user is a #property method which belong to the RequestHandler class. It returns a User(db.model) object stored in the datastore, with a level IntProperty().
class FoobarController(RequestHandler):
# Access decorator
def requiredLevel(required_level):
def wrap(func):
def f(self, *args):
if self.current_user.level >= required_level:
func(self, *args)
else:
raise Exception('Insufficient level to access this resource')
return f
return wrap
#requiredLevel(100)
def get(self, someparameters):
#do stuff here...
#requiredLevel(200)
def post(self):
#do something else here...
However, my application uses different controllers for different kind of resources. In order to use the #requiredLevel decorator within all subclasses, I need to move it to the parent class (RequestHandler) :
class RequestHandler(webapp.RequestHandler):
#Access decorator
def requiredLevel(required_level):
#See code above
My idea is to access the decorator in all controller subclasses using the following code :
class FoobarController(RequestHandler):
#RequestHandler.requiredLevel(100)
def get(self):
#do stuff here...
I think I just reached the limit of my knowledge about decorators and class inheritance :). Any thoughts ?
Your original code, with two small tweaks, should also work. A class-based approach seems rather heavy-weight for such a simple decorator:
class RequestHandler(webapp.RequestHandler):
# The decorator is now a class method.
#classmethod # Note the 'klass' argument, similar to 'self' on an instance method
def requiredLevel(klass, required_level):
def wrap(func):
def f(self, *args):
if self.current_user.level >= required_level:
func(self, *args)
else:
raise Exception('Insufficient level to access this resource')
return f
return wrap
class FoobarController(RequestHandler):
#RequestHandler.requiredLevel(100)
def get(self, someparameters):
#do stuff here...
#RequestHandler.requiredLevel(200)
def post(self):
#do something else here...
Alternately, you could use a #staticmethod instead:
class RequestHandler(webapp.RequestHandler):
# The decorator is now a static method.
#staticmethod # No default argument required...
def requiredLevel(required_level):
The reason the original code didn't work is that requiredLevel was assumed to be an instance method, which isn't going to be available at class-declaration time (when you were decorating the other methods), nor will it be available from the class object (putting the decorator on your RequestHandler base class is an excellent idea, and the resulting decorator call is nicely self-documenting).
You might be interested to read the documentation about #classmethod and #staticmethod.
Also, a little bit of boilerplate I like to put in my decorators:
#staticmethod
def requiredLevel(required_level):
def wrap(func):
def f(self, *args):
if self.current_user.level >= required_level:
func(self, *args)
else:
raise Exception('Insufficient level to access this resource')
# This will maintain the function name and documentation of the wrapped function.
# Very helpful when debugging or checking the docs from the python shell:
wrap.__doc__ = f.__doc__
wrap.__name__ = f.__name__
return f
return wrap
After digging through StackOverflow, and carefully reading Bruce Eckel's guide to decorators, I think I found a possible solution.
It involves implementing the decorator as a class in the Parent class :
class RequestHandler(webapp.RequestHandler):
# Decorator class :
class requiredLevel(object):
def __init__(self, required_level):
self.required_level = required_level
def __call__(self, f):
def wrapped_f(*f_args):
if f_args[0].current_user.level >= self.required_level:
return f(*f_args)
else:
raise Exception('User has insufficient level to access this resource')
return wrapped_f
This does the work ! Using f_args[0] seems a bit dirty to me, I'll edit this answer if I find something prettier.
Then you can decorate methods in subclasses the following way :
FooController(RequestHandler):
#RequestHandler.requiredLevel(100)
def get(self, id):
# Do something here
#RequestHandler.requiredLevel(250)
def post(self)
# Do some stuff here
BarController(RequestHandler):
#RequestHandler.requiredLevel(500)
def get(self, id):
# Do something here
Feel free to comment or propose an enhancement.