Psycopg2和Flask-将连接连接到before_request和teardown_appcontext

时间:2019-02-11 20:11:14

标签: flask psycopg2

干杯们, 重构我的Flask应用程序后,我陷入了将数据库连接与@app.before_request绑定并在@app.teardown_appcontext处将其关闭的困境。我正在使用普通的Psycopg2和应用程序工厂模式。

首先,我创建了一个调用应用程序工厂的函数,以便可以将@app用作suggested by Miguel Grinberg here

def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)

    --

    from shop.db import connect_and_close_db
    connect_and_close_db(app)

    --

    return app

然后我尝试了http://flask.pocoo.org/docs/1.0/appcontext/#storing-data上建议的这种模式:

def connect_and_close_db(app):

    @app.before_request
    def get_db_test():
        conn_string = "dbname=testdb user=testuser password=test host=localhost"
        if 'db' not in g:
            g.db = psycopg2.connect(conn_string)
        return g.db

    @app.teardown_appcontext
    def close_connection(exception):
        db = g.pop('db', None)

        if db is not None:
            db.close()

结果是:

TypeError: 'psycopg2.extensions.connection' object is not callable

任何人都知道发生了什么以及如何使它起作用?

此外,我想知道一旦将游标的创建绑定到before_request时如何访问连接对象以创建游标?

2 个答案:

答案 0 :(得分:0)

此解决方案可能还远远不够完美,并且不是真正的DRY。我欢迎发表评论或以此为基础的其他答案。

要实现对原始psycopg2的支持,您可能需要看一下connection pooler。还有good guide关于如何用Flask来实现这一点。

基本思想是首先创建您的连接池。您希望在flask应用程序初始化时建立此接口(这可以在python解释器中或通过gunicorn worker进行,其中可能有多个-在这种情况下,每个worker都有自己的连接池)。我选择将返回的池存储在配置中:

from flask import Flask, g, jsonify

import psycopg2
from psycopg2 import pool

app = Flask(__name__)

app.config['postgreSQL_pool'] = psycopg2.pool.SimpleConnectionPool(1, 20,
    user = "postgres",
    password = "very_secret",
    host = "127.0.0.1",
    port = "5432",
    database = "postgres")

请注意,SimpleConnectionPool的前两个参数是minmax连接。在这种情况下,这就是在120之间到数据库服务器的连接数。

接下来定义一个get_db函数:

def get_db():
    if 'db' not in g:
        g.db = app.config['postgreSQL_pool'].getconn()
    return g.db

此处使用的SimpleConnectionPool.getconn()方法只是从池中返回一个连接,我们将其分配给g.db并返回。这意味着当我们在代码中的任何地方调用get_db()时,它将返回相同的连接,或者如果不存在则创建一个连接。不需要before.context装饰器。

请定义您的拆卸功能:

@app.teardown_appcontext
def close_conn(e):
    db = g.pop('db', None)
    if db is not None:
        app.config['postgreSQL_pool'].putconn(db)

此操作在应用程序上下文被破坏时运行,并使用SimpleConnectionPool.putconn()断开连接。

最后定义一条路线:

@app.route('/')
def index():
    db = get_db()
    cursor = db.cursor()

    cursor.execute("select 1;")
    result = cursor.fetchall()
    print (result)

    cursor.close()
    return jsonify(result)

此代码对我有用,已在docker容器中针对postgres运行进行了测试。一些可能需要改进的地方:

  • 此视图不是非常干燥。也许您可以将其中一些移至get_db函数中,以便它返回一个游标。 (!!!)

  • 当python解释器退出时,您还应该找到通过app.config['postgreSQL_pool'].closeall

  • 关闭连接的方法
  • 尽管测试了某种监视池的方法会很好,这样您就可以监视负载下的池/数据库连接,并确保池程序的行为符合预期。

(!!!)在另一片土地上,sqlalchemy.scoped_session documentation用一些关于其“会话”如何与请求有关的理论来解释与此有关的更多事情。他们以一种可以调用Session.query('SELECT 1')的方式实现了该会话,如果会话尚不存在,它将创建会话。


编辑:以下是gist,其中包含您的应用程序工厂模式,并在注释中提供了示例用法。

答案 1 :(得分:0)

目前,我正在使用以下模式: (如果我提出更好的解决方案,我将最终编辑此答案)

这是我们使用数据库的主要脚本。它使用config中的两个函数:get_db()从池中获取连接,put_db()将连接返回到池中:

from config import get_db, put_db
from threading import Thread
from time import sleep

def select():
    db = get_db()
    sleep(1)
    cursor = db.cursor()
    # Print select result and db connection address in memory
    # To see if it gets connection from another addreess on second thread
    cursor.execute("SELECT 'It works %s'", (id(db),))
    print(cursor.fetchone())
    cursor.close()
    put_db(db)

Thread(target=select).start()
Thread(target=select).start()
print('Main thread')

这是config.py

import sys
import os
import psycopg2
from psycopg2 import pool
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())

def get_db(key=None):
    return getattr(get_db, 'pool').getconn(key)

def put_db(conn, key=None):
    getattr(get_db, 'pool').putconn(conn, key=key)

# So we here need to init connection pool in main thread in order everything to work
# Pool is initialized under function object get_db
try:
    setattr(get_db, 'pool', psycopg2.pool.ThreadedConnectionPool(1, 20, os.getenv("DB")))
    print(color.red('Initialized db'))
except psycopg2.OperationalError as e:
    print(e)
    sys.exit(0)

如果您很好奇,还有一个.env文件,其中包含DB env变量中的数据库连接字符串:

DB="dbname=postgres user=postgres password=1234 host=127.0.0.1 port=5433"

({.env文件是使用dotenv中的config.py模块加载的)