From 5b2355deff56fc754665a0888026eca0b53da0de Mon Sep 17 00:00:00 2001 From: pigletfly Date: Thu, 28 Dec 2017 16:31:22 +0800 Subject: [PATCH 1/3] add SQLALCHEMY_POOL_PRE_PING config --- docs/config.rst | 9 +++++++++ flask_sqlalchemy/__init__.py | 2 ++ 2 files changed, 11 insertions(+) diff --git a/docs/config.rst b/docs/config.rst index 7a824eb6..e55f551d 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -51,6 +51,15 @@ A list of configuration keys currently understood by the extension: different default timeout value. For more information about timeouts see :ref:`timeouts`. +``SQLALCHEMY_POOL_PRE_PING`` Native "pessimistic disconnection" handling + to the Pool object. This emits a simple + statement, typically "SELECT 1", + to test the connection for liveness. + If the existing connection is no longer able + to respond to commands, the connection is + transparently recycled, and all other + connections made prior to the current + timestamp are invalidated. ``SQLALCHEMY_MAX_OVERFLOW`` Controls the number of connections that can be created after the pool reached its maximum size. When those additional diff --git a/flask_sqlalchemy/__init__.py b/flask_sqlalchemy/__init__.py index 520afa00..7fe2c0fc 100644 --- a/flask_sqlalchemy/__init__.py +++ b/flask_sqlalchemy/__init__.py @@ -783,6 +783,7 @@ def init_app(self, app): app.config.setdefault('SQLALCHEMY_POOL_SIZE', None) app.config.setdefault('SQLALCHEMY_POOL_TIMEOUT', None) app.config.setdefault('SQLALCHEMY_POOL_RECYCLE', None) + app.config.setdefault('SQLALCHEMY_POOL_PRE_PING', False) app.config.setdefault('SQLALCHEMY_MAX_OVERFLOW', None) app.config.setdefault('SQLALCHEMY_COMMIT_ON_TEARDOWN', False) track_modifications = app.config.setdefault( @@ -815,6 +816,7 @@ def _setdefault(optionkey, configkey): _setdefault('pool_size', 'SQLALCHEMY_POOL_SIZE') _setdefault('pool_timeout', 'SQLALCHEMY_POOL_TIMEOUT') _setdefault('pool_recycle', 'SQLALCHEMY_POOL_RECYCLE') + _setdefault('pool_pre_ping', 'SQLALCHEMY_POOL_PRE_PING') _setdefault('max_overflow', 'SQLALCHEMY_MAX_OVERFLOW') def apply_driver_hacks(self, app, info, options): From e1edeab97ebe59100e6f70acfad014e351496a72 Mon Sep 17 00:00:00 2001 From: pigletfly Date: Thu, 22 Feb 2018 09:29:28 +0800 Subject: [PATCH 2/3] sqlalchemy support pre-ping from version 1.2.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 91c87d5a..cfd67da7 100755 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ platforms='any', install_requires=[ 'Flask>=0.10', - 'SQLAlchemy>=0.8.0' + 'SQLAlchemy>=1.2.0' ], test_suite='test_sqlalchemy.suite', classifiers=[ From d0e67ecca719fe616131141fadd3628b33c71c86 Mon Sep 17 00:00:00 2001 From: Alexandr Date: Wed, 11 Apr 2018 12:40:50 -0400 Subject: [PATCH 3/3] #222 ported to current code --- flask_sqlalchemy/__init__.py | 19 ++++++++++++++----- flask_sqlalchemy/model.py | 7 ++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/flask_sqlalchemy/__init__.py b/flask_sqlalchemy/__init__.py index a2b55637..8ae7ac37 100644 --- a/flask_sqlalchemy/__init__.py +++ b/flask_sqlalchemy/__init__.py @@ -25,6 +25,7 @@ from sqlalchemy import event, inspect, orm from sqlalchemy.engine.url import make_url from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base +from sqlalchemy.schema import MetaData from sqlalchemy.orm.exc import UnmappedClassError from sqlalchemy.orm.session import Session as SessionBase @@ -32,7 +33,7 @@ from ._compat import itervalues, string_types, to_str, xrange from .model import DefaultMeta -__version__ = '2.3.2' +__version__ = '2.3.3-dev' # the best timer function for the platform if sys.platform == 'win32': @@ -47,10 +48,10 @@ def _make_table(db): def _make_table(*args, **kwargs): - if len(args) > 1 and isinstance(args[1], db.Column): - args = (args[0], db.metadata) + args[1:] info = kwargs.pop('info', None) or {} info.setdefault('bind_key', None) + if len(args) > 1 and isinstance(args[1], db.Column): + args = (args[0], db.get_metadata(bind=info['bind_key'])) + args[1:] kwargs['info'] = info return sqlalchemy.Table(*args, **kwargs) return _make_table @@ -678,6 +679,7 @@ def __init__(self, app=None, use_native_unicode=True, session_options=None, self.use_native_unicode = use_native_unicode self.Query = query_class self.session = self.create_scoped_session(session_options) + model_class._metadata = {} self.Model = self.make_declarative_base(model_class, metadata) self._engine_lock = Lock() self.app = app @@ -692,6 +694,13 @@ def metadata(self): return self.Model.metadata + def get_metadata(self, bind=None): + if not bind: + return self.metadata + if bind not in self.Model._metadata: + self.Model._metadata[bind] = MetaData() + return self.Model._metadata.get(bind) + def create_scoped_session(self, options=None): """Create a :class:`~sqlalchemy.orm.scoping.scoped_session` on the factory from :meth:`create_session`. @@ -921,7 +930,7 @@ def get_app(self, reference_app=None): def get_tables_for_bind(self, bind=None): """Returns a list of all tables relevant for a bind.""" result = [] - for table in itervalues(self.Model.metadata.tables): + for table in itervalues(self.get_metadata(bind=bind).tables): if table.info.get('bind_key') == bind: result.append(table) return result @@ -955,7 +964,7 @@ def _execute_for_all_tables(self, app, bind, operation, skip_tables=False): if not skip_tables: tables = self.get_tables_for_bind(bind) extra['tables'] = tables - op = getattr(self.Model.metadata, operation) + op = getattr(self.get_metadata(bind=bind), operation) op(bind=self.get_engine(app, bind), **extra) def create_all(self, bind='__all__', app=None): diff --git a/flask_sqlalchemy/model.py b/flask_sqlalchemy/model.py index 9c55db2a..b0d745e1 100644 --- a/flask_sqlalchemy/model.py +++ b/flask_sqlalchemy/model.py @@ -3,7 +3,7 @@ import sqlalchemy as sa from sqlalchemy import inspect from sqlalchemy.ext.declarative import DeclarativeMeta, declared_attr -from sqlalchemy.schema import _get_table_key +from sqlalchemy.schema import _get_table_key, MetaData from ._compat import to_str @@ -118,6 +118,11 @@ def __init__(cls, name, bases, d): or getattr(cls, '__bind_key__', None) ) + if bind_key: + if bind_key not in cls._metadata: + cls._metadata[bind_key] = MetaData() + cls.metadata = cls._metadata[bind_key] + super(BindMetaMixin, cls).__init__(name, bases, d) if bind_key is not None and hasattr(cls, '__table__'):