neo4j 在自定义URL转换器中利用Flask应用程序上下文

xytpbqjk  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(131)

我遇到了与Passing application context to custom converter using the Application Factory pattern类似的问题,我使用自定义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解析过程中创建临时应用上下文,然后立即丢弃,从性能Angular 来看,这似乎不是最佳的。这是正确的方法吗?
这种配置的另一个问题是,当转换器和应用程序驻留在不同的文件中时,需要认识到潜在的循环引用,因为NodeConverter需要app,而app注册NodeConverter

blpfk2vs

blpfk2vs1#

在自定义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属性更改为当前应用程序的属性,而不是全局变量。这是避免循环导入和命名空间冲突的一个很好的做法。您可以通过在MyGraphDatabase类的init_app方法中设置db属性来做到这一点:

def init_app(self, app):
    app.db = self
    ...


这样,您就可以从当前应用程序上下文访问数据库驱动程序,而无需从另一个模块导入db对象。
在自定义URL转换器中利用Flask应用程序上下文的另一种可能方法是使用g对象,这是一个全局命名空间,用于在单个请求上下文中存储数据。您可以将数据库驱动程序存储在before_request处理程序中的g对象中,然后从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对象,因为它可能会影响转换器的行为。

相关问题