我目前正在构建一个使用Application Factory模式的应用程序。在这个应用程序中,我有一个自定义URL转换器,它接受一个整数并返回一个带有该ID的SQLAlchemy模型实例(如果存在)。当我没有使用Application Factory模式时,这很好用,但是使用它时,我在访问使用转换器的任何路由时都会收到此错误:
RuntimeError: application not registered on db instance and no application bound to current context
我的应用程序结构如下所示:
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
有没有办法以某种方式将应用程序上下文传递给CustomConverter?我尝试用to_python
包装with current_app.app_context()
方法的内容,但所有这一切都将错误减少到RuntimeError: working outside of application context
。
以下是完整的追溯:
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
答案 0 :(得分:1)
我遇到了同样的问题。我不确定解决它的'正确'方法是什么,因为这似乎是一个相当明显的事情,应该只是工作,但我解决了它与适用于应用工厂模式的大多数问题的通用解决方法:将app对象保存在闭包中并从外部注入。以你的例子:
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
显然,这不是优雅或最佳:在URL解析期间创建临时应用程序上下文,然后立即丢弃。
编辑:主要问题:不适用于非平凡案例。返回的对象将不会连接到实时会话(临时应用程序上下文关闭时会清理会话)。修改和延迟加载将会中断。
答案 1 :(得分:0)
另一个解决方案很好,但是(如前所述)存在很多问题。一个更可靠的解决方案是采用其他方法并使用装饰器-
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
然后选择您的路线-
@main.route("<some_model>")
@swap_model
def get_some_model(some_model):
return some_model.name
您甚至可以通过在模型不存在时添加404错误来扩展它-
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