Question

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?

Was it helpful?

Solution

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()

OTHER TIPS

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__)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top