问题
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