干杯们,
重构我的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
时如何访问连接对象以创建游标?
答案 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
的前两个参数是min
和max
连接。在这种情况下,这就是在1
和20
之间到数据库服务器的连接数。
接下来定义一个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
模块加载的)