Passing application context to custom converter using the Application Factory pattern

冷暖自知 提交于 2019-12-23 15:54:21

问题


I am currently building an application that uses the Application Factory pattern. In this application, I have a custom URL converter, that takes an integer and returns an SQLAlchemy model instance with that ID, if it exists. This works fine when I'm not using the Application Factory pattern, but with it, I get this error when accessing any route that uses the converter:

RuntimeError: application not registered on db instance and no application bound to current context

My application structure looks like this:

app/__init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import config

db = SQLAlchemy()

def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    db.init_app(app)

    from app.converters import CustomConverter
    app.url_map.converters["custom"] = CustomConverter

    from app.views.main import main
    app.register_blueprint(main)
    return app

app/converters.py

from werkzeug.routing import ValidationError, IntegerConverter
from app.models import SomeModel


class CustomConverter(IntegerConverter):
    """ Converts a valid SomeModel ID into a SomeModel object. """
    def to_python(self, value):
        some_model = SomeModel.query.get(value)
        if some_model is None:
            raise ValidationError()
        else:
            return some_model

app/views/main.py

from flask import Blueprint

main = Blueprint("main", __name__)

# This causes the aforementioned error.
@main.route("/<custom:some_model>")
def get_some_model(some_model):
    return some_model.name

Is there any way to somehow pass the application context to the CustomConverter? I have tried wrapping the contents of the to_python method with with current_app.app_context(), but all that does is reduce the error to RuntimeError: working outside of application context.

Here is the full traceback:

File "c:\Python34\lib\site-packages\flask\app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "c:\Python34\lib\site-packages\flask\app.py", line 1812, in wsgi_app
ctx = self.request_context(environ)
File "c:\Python34\lib\site-packages\flask\app.py", line 1773, in request_context
return RequestContext(self, environ)
File "c:\Python34\lib\site-packages\flask\ctx.py", line 247, in __init__
self.match_request()
File "c:\Python34\lib\site-packages\flask\ctx.py", line 286, in match_request
self.url_adapter.match(return_rule=True)
File "c:\Python34\lib\site-packages\werkzeug\routing.py", line 1440, in match
rv = rule.match(path)
File "c:\Python34\lib\site-packages\werkzeug\routing.py", line 715, in match
value = self._converters[name].to_python(value)
File "c:\Users\Encrylize\Desktop\Testing\Flask\app\converters.py", line 8, in to_python
some_model = SomeModel.query.get(value)
File "c:\Python34\lib\site-packages\flask_sqlalchemy\__init__.py", line 428, in __get__
return type.query_class(mapper, session=self.sa.session())
File "c:\Python34\lib\site-packages\sqlalchemy\orm\scoping.py", line 71, in __call__
return self.registry()
File "c:\Python34\lib\site-packages\sqlalchemy\util\_collections.py", line 988, in __call__
return self.registry.setdefault(key, self.createfunc())
File "c:\Python34\lib\site-packages\flask_sqlalchemy\__init__.py", line 136, in __init__
self.app = db.get_app()
File "c:\Python34\lib\site-packages\flask_sqlalchemy\__init__.py", line 809, in get_app
raise RuntimeError('application not registered on db '
RuntimeError: application not registered on db instance and no application bound to current context

回答1:


I just had the same problem. I'm not sure what the 'correct' way to solve it is, since this seems to be a rather obvious thing to do and should just work, but I solved it with the generic workaround that works for most problems with the application factory pattern: save the app object in a closure and inject it from outside. For your example:

def converters(app):
    class CustomConverter(IntegerConverter):
        """ Converts a valid SomeModel ID into a SomeModel object. """
        def to_python(self, value):
            with app.app_context():
                some_model = SomeModel.query.get(value)
                if some_model is None:
                    raise ValidationError()
                else:
                    return some_model
    return {"custom": CustomConverter}

def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    db.init_app(app)

    app.url_map.converters.update(converters(app))

    from app.views.main import main
    app.register_blueprint(main)
    return app

Obviously this is rather less then elegant or optimal: A temporary app context is created during URL parsing and then discarded immediately.

EDIT: Major Gotcha: This does not work for non-trivial cases. The object returned will not be connected to a live session (the session is cleaned up when the temporary app context is closed). Modification and lazy loading will break.




回答2:


The other solution is nice but (as it mentioned) presents a lot of problems. A more robust solution is to take a different approach and use a decorator-

def swap_model(func):
    @wraps(func)
    def decorated_function(*args, **kwargs):
        kwargs['some_model'] = SomeModel.query.filter(SomeModel.name == kwargs['some_model']).first()
        return func(*args, **kwargs)
    return decorated_function

Then for your route-

@main.route("<some_model>")
@swap_model
def get_some_model(some_model):
    return some_model.name

You can even expand that by adding 404 errors when the model isn't present-

def swap_model(func):
    @wraps(func)
    def decorated_function(*args, **kwargs):
        some_model = SomeModel.query.filter(SomeModel.name == kwargs['some_model']).first()
        if not some_model:
            abort(404)
        kwargs['some_model'] = some_model
        return func(*args, **kwargs)
    return decorated_function


来源:https://stackoverflow.com/questions/33856007/passing-application-context-to-custom-converter-using-the-application-factory-pa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!