我遇到了与 使用应用程序工厂模式将应用程序上下文传递到自定义转换器类似的问题,其中我使用自定义 URL 转换器将 Neo4j 图形数据库 ID 转换为节点对象,即
import atexit
from flask import Flask
from neo4j.v1 import GraphDatabase
from werkzeug.routing import BaseConverter
class NodeConverter(BaseConverter):
def to_python(self, value):
with driver.session() as session:
cursor = session.run('MATCH (n {id: $id}) RETURN n', id=value)
return cursor.single().values()[0]
app = Flask(__name__)
app.url_map.converters['node'] = NodeConverter
driver = GraphDatabase.driver('bolt://localhost')
atexit.register(lambda driver=driver: driver.close())
@app.route('/<node:node>')
def test(node):
print node
if __name__ == '__main__':
app.run()
虽然这种方法利用单个数据库连接,但有几个主要缺点:i)数据库连接无法通过 Flask 配置进行配置,ii)如果数据库失败,Flask 应用程序也会失败。
为了解决这个问题,我根据 http://flask.pocoo.org/docs/0.12/extensiondev/ 创建了一个本地扩展,即
from flask import _app_ctx_stack, Flask
from neo4j.v1 import GraphDatabase
from werkzeug.routing import BaseConverter
class MyGraphDatabase(object):
def __init__(self, app=None):
self.app = app
if app is not None:
self.init_app(app)
def init_app(self, app):
@app.teardown_appcontext
def teardown(exception):
ctx = _app_ctx_stack.top
if hasattr(ctx, 'driver'):
ctx.driver.close()
@property
def driver(self):
ctx = _app_ctx_stack.top
if ctx is not None and not hasattr(ctx, 'driver'):
ctx.driver = GraphDatabase.driver(app.config['NEO4J_URI'])
return ctx.driver
class NodeConverter(BaseConverter):
def to_python(self, value):
with app.app_context():
with db.driver.session() as session:
cursor = session.run('MATCH (n {id: $id}) RETURN n', id=value)
return cursor.single().values()[0]
db = MyGraphDatabase()
app = Flask(__name__)
app.config.from_pyfile('app.cfg')
app.url_map.converters['node'] = NodeConverter
db.init_app(app)
@app.route('/<node:node>')
def test(node):
print node
if __name__ == '__main__':
app.run()
此问题是由于 URL 转换器位于应用程序上下文之外,我需要包含以下块,
with app.app_context():
...
在 URL 解析期间创建临时应用程序上下文,然后立即丢弃,从性能角度来看,这似乎不是最佳的。这是正确的方法吗?
此配置的另一个问题是,当转换器和应用程序驻留在不同文件中时,需要认识到潜在的循环引用,因为
NodeConverter
需要 app
并且 app
注册 NodeConverter
。
在自定义 URL 转换器中利用 Flask 应用程序上下文的一种可能方法是使用
current_app
代理对象而不是 app
实例。这样,您就可以访问当前应用程序上下文,而无需创建临时应用程序上下文。例如,您可以按如下方式修改您的 NodeConverter
类:
from flask import current_app
class NodeConverter(BaseConverter):
def to_python(self, value):
with current_app.db.driver.session() as session:
cursor = session.run('MATCH (n {id: $id}) RETURN n', id=value)
return cursor.single().values()[0]
请注意,我还将
db
属性更改为当前应用程序的属性,而不是全局变量。这是避免循环导入和命名空间冲突的好习惯。您可以通过在 db
类的 init_app
方法中设置 MyGraphDatabase
属性来完成此操作:
def init_app(self, app):
app.db = self
...
这样,您就可以从当前应用程序上下文访问数据库驱动程序,而无需从另一个模块导入
db
对象。
在自定义 URL 转换器中利用 Flask 应用程序上下文的另一种可能方法是使用
g
对象,它是一个全局命名空间,用于在单个请求上下文期间存储数据。您可以将数据库驱动程序存储在 g
处理程序中的 before_request
对象中,然后从 NodeConverter
类访问它。例如,你可以这样做:
from flask import g
@app.before_request
def before_request():
g.driver = db.driver
class NodeConverter(BaseConverter):
def to_python(self, value):
with g.driver.session() as session:
cursor = session.run('MATCH (n {id: $id}) RETURN n', id=value)
return cursor.single().values()[0]
这样,您可以避免为每个 URL 转换创建临时应用程序上下文,并为整个请求重用相同的数据库驱动程序。但是,您应该小心不要在其他地方修改
g
对象,因为它可能会影响转换器的行为。