Подтвердить что ты не робот

Как я могу подключить 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?

4b9b3361

Ответ 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 связанную часть, где она должна быть.