Как я могу подключить SQL-соединения Flask SQLAlchemy повторно?
Я не могу заставить приложение Flask закрыть или повторно использовать подключения к БД. Я использую PostgreSQL 9.1.3 и
Flask==0.8
Flask-SQLAlchemy==0.16
psycopg2==2.4.5
По мере того, как мой набор тестов запускает количество открытых соединений, он поднимается до 20 (настройка max_connections
в postgresql.conf
), затем я вижу:
OperationalError: (OperationalError) FATAL: sorry, too many clients already
None None
Я сократил код до того момента, когда он просто набрал create_all
и drop_all
(но не выпустил какой-либо sql, так как нет моделей).
Я вижу, что соединения проверяются и выводятся в журналах:
DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> checked out from pool
DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> being returned to pool
WARNING:root:impl <-------- That the test running
DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> checked out from pool
DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> being returned to pool
Для каждого тестового прогона адрес соединения ( "объект соединения в xyz" ) отличается. Я подозреваю, что это имеет какое-то отношение к проблеме, но я не уверен, как исследовать дальше.
В приведенном ниже коде воспроизводится проблема в новом venv:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from unittest import TestCase
import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('sqlalchemy.pool').setLevel(logging.DEBUG)
logging.getLogger('sqlalchemy.engine').setLevel(logging.DEBUG)
logging.getLogger('sqlalchemy.dialects').setLevel(logging.DEBUG)
logging.getLogger('sqlalchemy.orm').setLevel(logging.DEBUG)
db = SQLAlchemy()
def create_app(config=None):
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
return app
class AppTestCase(TestCase):
SQLALCHEMY_DATABASE_URI = "postgresql://localhost/cx_test"
TESTING = True
def create_app(self):
return create_app(self)
def setUp(self):
self.app = self.create_app()
self.client = self.app.test_client()
self._ctx = self.app.test_request_context()
self._ctx.push()
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
self._ctx.pop()
class TestModel(AppTestCase):
def impl(self):
logging.warn("impl")
pass
def test_01(self):
self.impl()
def test_02(self):
self.impl()
def test_03(self):
self.impl()
def test_04(self):
self.impl()
def test_05(self):
self.impl()
def test_06(self):
self.impl()
def test_07(self):
self.impl()
def test_08(self):
self.impl()
def test_09(self):
self.impl()
def test_10(self):
self.impl()
def test_11(self):
self.impl()
def test_12(self):
self.impl()
def test_13(self):
self.impl()
def test_14(self):
self.impl()
def test_15(self):
self.impl()
def test_16(self):
self.impl()
def test_17(self):
self.impl()
def test_18(self):
self.impl()
def test_19(self):
self.impl()
if __name__ == "__main__":
import unittest
unittest.main()
Это первый раз, когда я использовал фабрики приложений в колбе, и я скопировал этот код частично из Документов Flask-SQLAlchemy. Elseware в этих документах упоминается, что использование db в неправильном контексте приведет к утечке соединений - возможно, я неправильно делаю init?
Ответы
Ответ 1
После чтения документов SQLAlchemy и некоторых попыток с экземпляром db я наконец получил решение. Добавьте db.get_engine(self.app).dispose()
в tearDown()
, чтобы он выглядел так:
def tearDown(self):
db.session.remove()
db.drop_all()
db.get_engine(self.app).dispose()
self._ctx.pop()
Ответ 2
Поскольку вопросы были заданы примерно год назад, я считаю, что ОП должен решить свои проблемы. Но для тех, кто блуждал сюда (как и я), пытаясь понять, что происходит, вот мое лучшее объяснение:
Как сказал ван, проблема действительно в тестовом случае, вызывающем setUp
и tearDown
для каждого теста. Хотя соединение не совсем утечка из SQLAlchemy, но вместо этого из-за того, что каждый тест имеет свой собственный setUp
, создается несколько экземпляров приложения: каждое приложение будет иметь свой собственный пул соединений с базой данных, который, по-видимому, не используется повторно или рециркулировать, когда тест заканчивается.
Другими словами, соединение проверяется и возвращается в пул правильно, но соединение затем работает как незанятое соединение для будущих транзакций в той же app (точка объединения пулов).
В вышеприведенном тестовом примере создается около 20 пулов соединений (каждый из которых с незанятым соединением из-за create/drop_all) и занимает лимит подключения postgres.
Ответ 3
Вы знаете, что setUp and tearDown
вызывается до и после каждого test method
. Из вашего кода это похоже, что вам нужно, чтобы обеспечить пустую базу данных.
Однако есть также setUpClass and tearDownClass
, которые вызываются один раз для каждого тестового класса.
Я считаю, что вы можете разделить код, который у вас есть, и переместить связанную часть db-connection
на уровень Class
, сохраняя при этом test-method
связанную часть, где она должна быть.
Ответ 4
В последних версиях Flask-SQLAlchemy session.remove()
автоматически вызывается в app.after_request
.
Также см. параметр SQLALCHEMY_COMMIT_ON_TEARDOWN
здесь:
https://pythonhosted.org/Flask-SQLAlchemy/config.html?highlight=sqlalchemy_commit_on_teardown
Это также автоматически зафиксирует транзакцию.