diff options
-rw-r--r-- | doc/build/changelog/unreleased_14/5131.rst | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/base.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/base.py | 17 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/default.py | 76 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/interfaces.py | 38 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/reflection.py | 233 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/schema.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/__init__.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/assertions.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/suite/test_reflection.py | 3 | ||||
-rw-r--r-- | lib/sqlalchemy/util/__init__.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/util/deprecations.py | 69 | ||||
-rw-r--r-- | test/dialect/mssql/test_reflection.py | 3 | ||||
-rw-r--r-- | test/dialect/postgresql/test_reflection.py | 9 | ||||
-rw-r--r-- | test/dialect/test_sqlite.py | 45 | ||||
-rw-r--r-- | test/engine/test_bind.py | 8 | ||||
-rw-r--r-- | test/engine/test_deprecations.py | 62 | ||||
-rw-r--r-- | test/engine/test_execute.py | 8 | ||||
-rw-r--r-- | test/engine/test_reflection.py | 148 | ||||
-rw-r--r-- | test/sql/test_defaults.py | 7 | ||||
-rw-r--r-- | test/sql/test_deprecations.py | 35 |
21 files changed, 510 insertions, 272 deletions
diff --git a/doc/build/changelog/unreleased_14/5131.rst b/doc/build/changelog/unreleased_14/5131.rst new file mode 100644 index 000000000..5205699d8 --- /dev/null +++ b/doc/build/changelog/unreleased_14/5131.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: usecase, engine + :tickets: 5131 + + The :meth:`.Connection.connect` method is deprecated as is the concept of + "connection branching", which copies a :class:`.Connection` into a new one + that has a no-op ".close()" method. This pattern is oriented around the + "connectionless execution" concept which is also being removed in 2.0. diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 45911d4c0..ee81fc020 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -2245,9 +2245,6 @@ class PGIdentifierPreparer(compiler.IdentifierPreparer): class PGInspector(reflection.Inspector): - def __init__(self, conn): - reflection.Inspector.__init__(self, conn) - def get_table_oid(self, table_name, schema=None): """Return the OID for the given table name.""" diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index 462e5f9ec..29df67dcb 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -102,6 +102,15 @@ class Connection(Connectable): self.__transaction = None self.__savepoint_seq = 0 self.should_close_with_result = close_with_result + if close_with_result: + util.warn_deprecated_20( + '"Connectionless" execution, which refers to running ' + "SQL commands using the Engine.execute() (or " + "some_statement.execute()) method without " + "calling .connect() or .begin() to get a Connection, is " + "deprecated and will be removed SQLAlchemy 2.0" + ) + self.__invalid = False self.__can_reconnect = True self._echo = self.engine._should_log_info() @@ -489,6 +498,7 @@ class Connection(Connectable): return self.connection.info + @util.deprecated_20(":meth:`.Connection.connect`") def connect(self, close_with_result=False): """Returns a branched version of this :class:`.Connection`. @@ -884,6 +894,12 @@ class Connection(Connectable): """ if self.__branch_from: + util.warn_deprecated( + "The .close() method on a so-called 'branched' connection is " + "deprecated as of 1.4, as are 'branched' connections overall, " + "and will be removed in a future release. If this is a " + "default-handling function, don't close the connection." + ) try: del self.__connection except AttributeError: @@ -2237,7 +2253,6 @@ class Engine(Connectable, log.Identified): resource to be returned to the connection pool. """ - connection = self.connect(close_with_result=True) return connection.execute(statement, *multiparams, **params) diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index d900a74b8..7d36345fd 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -810,12 +810,12 @@ class DefaultExecutionContext(interfaces.ExecutionContext): parameters = [] if compiled.positional: for compiled_params in self.compiled_parameters: - param = [] - for key in positiontup: - if key in processors: - param.append(processors[key](compiled_params[key])) - else: - param.append(compiled_params[key]) + param = [ + processors[key](compiled_params[key]) + if key in processors + else compiled_params[key] + for key in positiontup + ] parameters.append(dialect.execute_sequence_format(param)) else: encode = not dialect.supports_unicode_statements @@ -948,7 +948,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext): else: return autocommit - def _execute_scalar(self, stmt, type_): + def _execute_scalar(self, stmt, type_, parameters=None): """Execute a string statement on the current cursor, returning a scalar result. @@ -965,12 +965,13 @@ class DefaultExecutionContext(interfaces.ExecutionContext): ): stmt = self.dialect._encoder(stmt)[0] - if self.dialect.positional: - default_params = self.dialect.execute_sequence_format() - else: - default_params = {} + if not parameters: + if self.dialect.positional: + parameters = self.dialect.execute_sequence_format() + else: + parameters = {} - conn._cursor_execute(self.cursor, stmt, default_params, context=self) + conn._cursor_execute(self.cursor, stmt, parameters, context=self) r = self.cursor.fetchone()[0] if type_ is not None: # apply type post processors to the result @@ -1288,18 +1289,51 @@ class DefaultExecutionContext(interfaces.ExecutionContext): self.current_column = column return default.arg(self) elif default.is_clause_element: - # TODO: expensive branching here should be - # pulled into _exec_scalar() - conn = self.connection - if not default._arg_is_typed: - default_arg = expression.type_coerce(default.arg, type_) - else: - default_arg = default.arg - c = expression.select([default_arg]).compile(bind=conn) - return conn._execute_compiled(c, (), {}).scalar() + return self._exec_default_clause_element(column, default, type_) else: return default.arg + def _exec_default_clause_element(self, column, default, type_): + # execute a default that's a complete clause element. Here, we have + # to re-implement a miniature version of the compile->parameters-> + # cursor.execute() sequence, since we don't want to modify the state + # of the connection / result in progress or create new connection/ + # result objects etc. + # .. versionchanged:: 1.4 + + if not default._arg_is_typed: + default_arg = expression.type_coerce(default.arg, type_) + else: + default_arg = default.arg + compiled = expression.select([default_arg]).compile( + dialect=self.dialect + ) + compiled_params = compiled.construct_params() + processors = compiled._bind_processors + if compiled.positional: + positiontup = compiled.positiontup + parameters = self.dialect.execute_sequence_format( + [ + processors[key](compiled_params[key]) + if key in processors + else compiled_params[key] + for key in positiontup + ] + ) + else: + parameters = dict( + ( + key, + processors[key](compiled_params[key]) + if key in processors + else compiled_params[key], + ) + for key in compiled_params + ) + return self._execute_scalar( + util.text_type(compiled), type_, parameters=parameters + ) + current_parameters = None """A dictionary of parameters applied to the current row. diff --git a/lib/sqlalchemy/engine/interfaces.py b/lib/sqlalchemy/engine/interfaces.py index cffaa159b..237eb0f2f 100644 --- a/lib/sqlalchemy/engine/interfaces.py +++ b/lib/sqlalchemy/engine/interfaces.py @@ -1092,6 +1092,16 @@ class ExecutionContext(object): raise NotImplementedError() +@util.deprecated_20_cls( + ":class:`.Connectable`", + alternative=( + "The :class:`.Engine` will be the only Core " + "object that features a .connect() method, and the " + ":class:`.Connection` will be the only object that features " + "an .execute() method." + ), + constructor=None, +) class Connectable(object): """Interface for an object which supports execution of SQL constructs. @@ -1120,34 +1130,6 @@ class Connectable(object): """ - @util.deprecated( - "0.7", - "The :meth:`.Connectable.create` method is deprecated and will be " - "removed in a future release. Please use the ``.create()`` method " - "on specific schema objects to emit DDL sequences, including " - ":meth:`.Table.create`, :meth:`.Index.create`, and " - ":meth:`.MetaData.create_all`.", - ) - def create(self, entity, **kwargs): - """Emit CREATE statements for the given schema entity. - """ - - raise NotImplementedError() - - @util.deprecated( - "0.7", - "The :meth:`.Connectable.drop` method is deprecated and will be " - "removed in a future release. Please use the ``.drop()`` method " - "on specific schema objects to emit DDL sequences, including " - ":meth:`.Table.drop`, :meth:`.Index.drop`, and " - ":meth:`.MetaData.drop_all`.", - ) - def drop(self, entity, **kwargs): - """Emit DROP statements for the given schema entity. - """ - - raise NotImplementedError() - def execute(self, object_, *multiparams, **params): """Executes the given construct and returns a :class:`.ResultProxy`.""" raise NotImplementedError() diff --git a/lib/sqlalchemy/engine/reflection.py b/lib/sqlalchemy/engine/reflection.py index d113588bb..25538fddb 100644 --- a/lib/sqlalchemy/engine/reflection.py +++ b/lib/sqlalchemy/engine/reflection.py @@ -25,7 +25,11 @@ methods such as get_table_names, get_columns, etc. 'name' attribute.. """ +import contextlib + from .base import Connectable +from .base import Connection +from .base import Engine from .. import exc from .. import inspection from .. import sql @@ -64,24 +68,27 @@ class Inspector(object): fetched metadata. A :class:`.Inspector` object is usually created via the - :func:`.inspect` function:: + :func:`.inspect` function, which may be passed an :class:`.Engine` + or a :class:`.Connection`:: from sqlalchemy import inspect, create_engine engine = create_engine('...') insp = inspect(engine) - The inspection method above is equivalent to using the - :meth:`.Inspector.from_engine` method, i.e.:: - - engine = create_engine('...') - insp = Inspector.from_engine(engine) - - Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` may opt - to return an :class:`.Inspector` subclass that provides additional - methods specific to the dialect's target database. + Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` associated + with the engine may opt to return an :class:`.Inspector` subclass that + provides additional methods specific to the dialect's target database. """ + @util.deprecated( + "1.4", + "The __init__() method on :class:`.Inspector` is deprecated and " + "will be removed in a future release. Please use the " + ":func:`.sqlalchemy.inspect` " + "function on an :class:`.Engine` or :class:`.Connection` in order to " + "acquire an :class:`.Inspector`.", + ) def __init__(self, bind): """Initialize a new :class:`.Inspector`. @@ -94,23 +101,47 @@ class Inspector(object): :meth:`.Inspector.from_engine` """ - # this might not be a connection, it could be an engine. - self.bind = bind + return self._init_legacy(bind) + + @classmethod + def _construct(cls, init, bind): - # set the engine + if hasattr(bind.dialect, "inspector"): + cls = bind.dialect.inspector + + self = cls.__new__(cls) + init(self, bind) + return self + + def _init_legacy(self, bind): if hasattr(bind, "engine"): - self.engine = bind.engine + self._init_connection(bind) else: - self.engine = bind + self._init_engine(bind) - if self.engine is bind: - # if engine, ensure initialized - bind.connect().close() + def _init_engine(self, engine): + self.bind = self.engine = engine + engine.connect().close() + self._op_context_requires_connect = True + self.dialect = self.engine.dialect + self.info_cache = {} + def _init_connection(self, connection): + self.bind = connection + self.engine = connection.engine + self._op_context_requires_connect = False self.dialect = self.engine.dialect self.info_cache = {} @classmethod + @util.deprecated( + "1.4", + "The from_engine() method on :class:`.Inspector` is deprecated and " + "will be removed in a future release. Please use the " + ":func:`.sqlalchemy.inspect` " + "function on an :class:`.Engine` or :class:`.Connection` in order to " + "acquire an :class:`.Inspector`.", + ) def from_engine(cls, bind): """Construct a new dialect-specific Inspector object from the given engine or connection. @@ -129,13 +160,53 @@ class Inspector(object): See the example at :class:`.Inspector`. """ - if hasattr(bind.dialect, "inspector"): - return bind.dialect.inspector(bind) - return Inspector(bind) + return cls._construct(cls._init_legacy, bind) @inspection._inspects(Connectable) - def _insp(bind): - return Inspector.from_engine(bind) + def _connectable_insp(bind): + # this method should not be used unless some unusual case + # has subclassed "Connectable" + + return Inspector._construct(Inspector._init_legacy, bind) + + @inspection._inspects(Engine) + def _engine_insp(bind): + return Inspector._construct(Inspector._init_engine, bind) + + @inspection._inspects(Connection) + def _connection_insp(bind): + return Inspector._construct(Inspector._init_connection, bind) + + @contextlib.contextmanager + def _operation_context(self): + """Return a context that optimizes for multiple operations on a single + transaction. + + This essentially allows connect()/close() to be called if we detected + that we're against an :class:`.Engine` and not a :class:`.Connection`. + + """ + if self._op_context_requires_connect: + conn = self.bind.connect() + else: + conn = self.bind + try: + yield conn + finally: + if self._op_context_requires_connect: + conn.close() + + @contextlib.contextmanager + def _inspection_context(self): + """Return an :class:`.Inspector` from this one that will run all + operations on a single connection. + + """ + + with self._operation_context() as conn: + sub_insp = self._construct(self.__class__._init_connection, conn) + sub_insp.info_cache = self.info_cache + yield sub_insp @property def default_schema_name(self): @@ -153,9 +224,10 @@ class Inspector(object): """ if hasattr(self.dialect, "get_schema_names"): - return self.dialect.get_schema_names( - self.bind, info_cache=self.info_cache - ) + with self._operation_context() as conn: + return self.dialect.get_schema_names( + conn, info_cache=self.info_cache + ) return [] def get_table_names(self, schema=None): @@ -185,9 +257,10 @@ class Inspector(object): """ - return self.dialect.get_table_names( - self.bind, schema, info_cache=self.info_cache - ) + with self._operation_context() as conn: + return self.dialect.get_table_names( + conn, schema, info_cache=self.info_cache + ) def has_table(self, table_name, schema=None): """Return True if the backend has a table of the given name. @@ -196,7 +269,8 @@ class Inspector(object): """ # TODO: info_cache? - return self.dialect.has_table(self.bind, table_name, schema) + with self._operation_context() as conn: + return self.dialect.has_table(conn, table_name, schema) def get_sorted_table_and_fkc_names(self, schema=None): """Return dependency-sorted table and foreign key constraint names in @@ -222,12 +296,11 @@ class Inspector(object): with an already-given :class:`.MetaData`. """ - if hasattr(self.dialect, "get_table_names"): + + with self._operation_context() as conn: tnames = self.dialect.get_table_names( - self.bind, schema, info_cache=self.info_cache + conn, schema, info_cache=self.info_cache ) - else: - tnames = self.engine.table_names(schema) tuples = set() remaining_fkcs = set() @@ -263,9 +336,11 @@ class Inspector(object): .. versionadded:: 1.0.0 """ - return self.dialect.get_temp_table_names( - self.bind, info_cache=self.info_cache - ) + + with self._operation_context() as conn: + return self.dialect.get_temp_table_names( + conn, info_cache=self.info_cache + ) def get_temp_view_names(self): """return a list of temporary view names for the current bind. @@ -276,9 +351,10 @@ class Inspector(object): .. versionadded:: 1.0.0 """ - return self.dialect.get_temp_view_names( - self.bind, info_cache=self.info_cache - ) + with self._operation_context() as conn: + return self.dialect.get_temp_view_names( + conn, info_cache=self.info_cache + ) def get_table_options(self, table_name, schema=None, **kw): """Return a dictionary of options specified when the table of the @@ -295,9 +371,10 @@ class Inspector(object): """ if hasattr(self.dialect, "get_table_options"): - return self.dialect.get_table_options( - self.bind, table_name, schema, info_cache=self.info_cache, **kw - ) + with self._operation_context() as conn: + return self.dialect.get_table_options( + conn, table_name, schema, info_cache=self.info_cache, **kw + ) return {} def get_view_names(self, schema=None): @@ -308,9 +385,10 @@ class Inspector(object): """ - return self.dialect.get_view_names( - self.bind, schema, info_cache=self.info_cache - ) + with self._operation_context() as conn: + return self.dialect.get_view_names( + conn, schema, info_cache=self.info_cache + ) def get_view_definition(self, view_name, schema=None): """Return definition for `view_name`. @@ -320,9 +398,10 @@ class Inspector(object): """ - return self.dialect.get_view_definition( - self.bind, view_name, schema, info_cache=self.info_cache - ) + with self._operation_context() as conn: + return self.dialect.get_view_definition( + conn, view_name, schema, info_cache=self.info_cache + ) def get_columns(self, table_name, schema=None, **kw): """Return information about columns in `table_name`. @@ -354,9 +433,10 @@ class Inspector(object): """ - col_defs = self.dialect.get_columns( - self.bind, table_name, schema, info_cache=self.info_cache, **kw - ) + with self._operation_context() as conn: + col_defs = self.dialect.get_columns( + conn, table_name, schema, info_cache=self.info_cache, **kw + ) for col_def in col_defs: # make this easy and only return instances for coltype coltype = col_def["type"] @@ -377,9 +457,10 @@ class Inspector(object): primary key information as a list of column names. """ - return self.dialect.get_pk_constraint( - self.bind, table_name, schema, info_cache=self.info_cache, **kw - )["constrained_columns"] + with self._operation_context() as conn: + return self.dialect.get_pk_constraint( + conn, table_name, schema, info_cache=self.info_cache, **kw + )["constrained_columns"] def get_pk_constraint(self, table_name, schema=None, **kw): """Return information about primary key constraint on `table_name`. @@ -401,9 +482,10 @@ class Inspector(object): use :class:`.quoted_name`. """ - return self.dialect.get_pk_constraint( - self.bind, table_name, schema, info_cache=self.info_cache, **kw - ) + with self._operation_context() as conn: + return self.dialect.get_pk_constraint( + conn, table_name, schema, info_cache=self.info_cache, **kw + ) def get_foreign_keys(self, table_name, schema=None, **kw): """Return information about foreign_keys in `table_name`. @@ -436,9 +518,10 @@ class Inspector(object): """ - return self.dialect.get_foreign_keys( - self.bind, table_name, schema, info_cache=self.info_cache, **kw - ) + with self._operation_context() as conn: + return self.dialect.get_foreign_keys( + conn, table_name, schema, info_cache=self.info_cache, **kw + ) def get_indexes(self, table_name, schema=None, **kw): """Return information about indexes in `table_name`. @@ -476,9 +559,10 @@ class Inspector(object): """ - return self.dialect.get_indexes( - self.bind, table_name, schema, info_cache=self.info_cache, **kw - ) + with self._operation_context() as conn: + return self.dialect.get_indexes( + conn, table_name, schema, info_cache=self.info_cache, **kw + ) def get_unique_constraints(self, table_name, schema=None, **kw): """Return information about unique constraints in `table_name`. @@ -501,9 +585,10 @@ class Inspector(object): """ - return self.dialect.get_unique_constraints( - self.bind, table_name, schema, info_cache=self.info_cache, **kw - ) + with self._operation_context() as conn: + return self.dialect.get_unique_constraints( + conn, table_name, schema, info_cache=self.info_cache, **kw + ) def get_table_comment(self, table_name, schema=None, **kw): """Return information about the table comment for ``table_name``. @@ -521,9 +606,10 @@ class Inspector(object): """ - return self.dialect.get_table_comment( - self.bind, table_name, schema, info_cache=self.info_cache, **kw - ) + with self._operation_context() as conn: + return self.dialect.get_table_comment( + conn, table_name, schema, info_cache=self.info_cache, **kw + ) def get_check_constraints(self, table_name, schema=None, **kw): """Return information about check constraints in `table_name`. @@ -554,9 +640,10 @@ class Inspector(object): """ - return self.dialect.get_check_constraints( - self.bind, table_name, schema, info_cache=self.info_cache, **kw - ) + with self._operation_context() as conn: + return self.dialect.get_check_constraints( + conn, table_name, schema, info_cache=self.info_cache, **kw + ) def reflecttable( self, diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py index 9f66321fe..79a700ad8 100644 --- a/lib/sqlalchemy/sql/schema.py +++ b/lib/sqlalchemy/sql/schema.py @@ -4095,9 +4095,7 @@ class MetaData(SchemaItem): if bind is None: bind = _bind_or_error(self) - with bind.connect() as conn: - insp = inspection.inspect(conn) - + with inspection.inspect(bind)._inspection_context() as insp: reflect_opts = { "autoload_with": insp, "extend_existing": extend_existing, diff --git a/lib/sqlalchemy/testing/__init__.py b/lib/sqlalchemy/testing/__init__.py index ab1198da8..582901579 100644 --- a/lib/sqlalchemy/testing/__init__.py +++ b/lib/sqlalchemy/testing/__init__.py @@ -20,6 +20,7 @@ from .assertions import eq_ # noqa from .assertions import eq_ignore_whitespace # noqa from .assertions import eq_regex # noqa from .assertions import expect_deprecated # noqa +from .assertions import expect_deprecated_20 # noqa from .assertions import expect_warnings # noqa from .assertions import in_ # noqa from .assertions import is_ # noqa diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py index c74259bdf..d055ba86e 100644 --- a/lib/sqlalchemy/testing/assertions.py +++ b/lib/sqlalchemy/testing/assertions.py @@ -81,6 +81,10 @@ def expect_deprecated(*messages, **kw): return _expect_warnings(sa_exc.SADeprecationWarning, messages, **kw) +def expect_deprecated_20(*messages, **kw): + return _expect_warnings(sa_exc.RemovedIn20Warning, messages, **kw) + + def emits_warning_on(db, *messages): """Mark a test as emitting a warning on a specific dialect. diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py index f9ff46492..d375f0279 100644 --- a/lib/sqlalchemy/testing/suite/test_reflection.py +++ b/lib/sqlalchemy/testing/suite/test_reflection.py @@ -21,7 +21,6 @@ from ... import MetaData from ... import String from ... import testing from ... import types as sql_types -from ...engine.reflection import Inspector from ...schema import DDL from ...schema import Index from ...sql.elements import quoted_name @@ -661,7 +660,7 @@ class ComponentReflectionTest(fixtures.TablesTest): def test_deprecated_get_primary_keys(self): meta = self.metadata users = self.tables.users - insp = Inspector(meta.bind) + insp = inspect(meta.bind) assert_raises_message( sa_exc.SADeprecationWarning, r".*get_primary_keys\(\) method is deprecated", diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py index d2428bf75..434c5cb79 100644 --- a/lib/sqlalchemy/util/__init__.py +++ b/lib/sqlalchemy/util/__init__.py @@ -89,6 +89,7 @@ from .compat import with_metaclass # noqa from .compat import zip_longest # noqa from .deprecations import deprecated # noqa from .deprecations import deprecated_20 # noqa +from .deprecations import deprecated_20_cls # noqa from .deprecations import deprecated_cls # noqa from .deprecations import deprecated_params # noqa from .deprecations import inject_docstring_text # noqa diff --git a/lib/sqlalchemy/util/deprecations.py b/lib/sqlalchemy/util/deprecations.py index 0db2c72ae..b78a71b1b 100644 --- a/lib/sqlalchemy/util/deprecations.py +++ b/lib/sqlalchemy/util/deprecations.py @@ -23,7 +23,7 @@ def warn_deprecated(msg, stacklevel=3): def warn_deprecated_20(msg, stacklevel=3): - msg += "(Background on SQLAlchemy 2.0 at: http://sqlalche.me/e/b8d9)" + msg += " (Background on SQLAlchemy 2.0 at: http://sqlalche.me/e/b8d9)" warnings.warn(msg, exc.RemovedIn20Warning, stacklevel=stacklevel) @@ -43,6 +43,23 @@ def deprecated_cls(version, message, constructor="__init__"): return decorate +def deprecated_20_cls(clsname, alternative=None, constructor="__init__"): + message = ( + ".. deprecated:: 2.0 The %s class is considered legacy as of the " + "1.x series of SQLAlchemy and will be removed in 2.0." % clsname + ) + + if alternative: + message += " " + alternative + + def decorate(cls): + return _decorate_cls_with_warning( + cls, constructor, exc.RemovedIn20Warning, message, message + ) + + return decorate + + def deprecated( version, message=None, add_deprecation_to_docstring=True, warning=None ): @@ -83,15 +100,13 @@ def deprecated( def deprecated_20(api_name, alternative=None, **kw): message = ( - "The %s() function/method is considered legacy as of the " + "The %s function/method is considered legacy as of the " "1.x series of SQLAlchemy and will be removed in 2.0." % api_name ) if alternative: message += " " + alternative - message += " (Background on SQLAlchemy 2.0 at: http://sqlalche.me/e/b8d9)" - return deprecated( "2.0", message=message, warning=exc.RemovedIn20Warning, **kw ) @@ -194,25 +209,36 @@ def _decorate_cls_with_warning( ): doc = cls.__doc__ is not None and cls.__doc__ or "" if docstring_header is not None: - docstring_header %= dict(func=constructor) + if constructor is not None: + docstring_header %= dict(func=constructor) + + if issubclass(wtype, exc.RemovedIn20Warning): + docstring_header += ( + " (Background on SQLAlchemy 2.0 at: " + ":ref:`migration_20_toplevel`)" + ) doc = inject_docstring_text(doc, docstring_header, 1) if type(cls) is type: clsdict = dict(cls.__dict__) clsdict["__doc__"] = doc + clsdict.pop("__dict__", None) cls = type(cls.__name__, cls.__bases__, clsdict) - constructor_fn = clsdict[constructor] + if constructor is not None: + constructor_fn = clsdict[constructor] + else: cls.__doc__ = doc - constructor_fn = getattr(cls, constructor) - - setattr( - cls, - constructor, - _decorate_with_warning(constructor_fn, wtype, message, None), - ) - + if constructor is not None: + constructor_fn = getattr(cls, constructor) + + if constructor is not None: + setattr( + cls, + constructor, + _decorate_with_warning(constructor_fn, wtype, message, None), + ) return cls @@ -221,17 +247,30 @@ def _decorate_with_warning(func, wtype, message, docstring_header=None): message = _sanitize_restructured_text(message) + if issubclass(wtype, exc.RemovedIn20Warning): + doc_only = ( + " (Background on SQLAlchemy 2.0 at: " + ":ref:`migration_20_toplevel`)" + ) + warning_only = ( + " (Background on SQLAlchemy 2.0 at: http://sqlalche.me/e/b8d9)" + ) + else: + doc_only = warning_only = "" + @decorator def warned(fn, *args, **kwargs): skip_warning = kwargs.pop("_sa_skip_warning", False) if not skip_warning: - warnings.warn(message, wtype, stacklevel=3) + warnings.warn(message + warning_only, wtype, stacklevel=3) return fn(*args, **kwargs) doc = func.__doc__ is not None and func.__doc__ or "" if docstring_header is not None: docstring_header %= dict(func=func.__name__) + docstring_header += doc_only + doc = inject_docstring_text(doc, docstring_header, 1) decorated = warned(func) diff --git a/test/dialect/mssql/test_reflection.py b/test/dialect/mssql/test_reflection.py index 794588dce..120092e66 100644 --- a/test/dialect/mssql/test_reflection.py +++ b/test/dialect/mssql/test_reflection.py @@ -17,7 +17,6 @@ from sqlalchemy.databases import mssql from sqlalchemy.dialects.mssql import base from sqlalchemy.dialects.mssql.information_schema import CoerceUnicode from sqlalchemy.dialects.mssql.information_schema import tables -from sqlalchemy.engine.reflection import Inspector from sqlalchemy.testing import AssertsCompiledSQL from sqlalchemy.testing import ComparesTables from sqlalchemy.testing import eq_ @@ -415,7 +414,7 @@ class ReflectHugeViewTest(fixtures.TestBase): self.metadata.drop_all() def test_inspect_view_definition(self): - inspector = Inspector.from_engine(testing.db) + inspector = inspect(testing.db) view_def = inspector.get_view_definition("huge_named_view") eq_(view_def, self.view_str) diff --git a/test/dialect/postgresql/test_reflection.py b/test/dialect/postgresql/test_reflection.py index b410ca748..830a54eef 100644 --- a/test/dialect/postgresql/test_reflection.py +++ b/test/dialect/postgresql/test_reflection.py @@ -25,7 +25,6 @@ from sqlalchemy.dialects.postgresql import ExcludeConstraint from sqlalchemy.dialects.postgresql import INTEGER from sqlalchemy.dialects.postgresql import INTERVAL from sqlalchemy.dialects.postgresql import TSRANGE -from sqlalchemy.engine import reflection from sqlalchemy.sql.schema import CheckConstraint from sqlalchemy.testing import fixtures from sqlalchemy.testing import mock @@ -1199,7 +1198,7 @@ class ReflectionTest(fixtures.TestBase): metadata=self.metadata, ) enum_type.create(conn) - inspector = reflection.Inspector.from_engine(conn.engine) + inspector = inspect(conn) eq_( inspector.get_enums("test_schema"), [ @@ -1218,7 +1217,7 @@ class ReflectionTest(fixtures.TestBase): "cat", "dog", "rat", name="pet", metadata=self.metadata ) enum_type.create(testing.db) - inspector = reflection.Inspector.from_engine(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_enums(), [ @@ -1356,7 +1355,7 @@ class ReflectionTest(fixtures.TestBase): ) enum_type.create(testing.db) schema_enum_type.create(testing.db) - inspector = reflection.Inspector.from_engine(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_enums(), @@ -1392,7 +1391,7 @@ class ReflectionTest(fixtures.TestBase): def test_inspect_enum_empty(self): enum_type = postgresql.ENUM(name="empty", metadata=self.metadata) enum_type.create(testing.db) - inspector = reflection.Inspector.from_engine(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_enums(), diff --git a/test/dialect/test_sqlite.py b/test/dialect/test_sqlite.py index da349c1f8..7675b8aa6 100644 --- a/test/dialect/test_sqlite.py +++ b/test/dialect/test_sqlite.py @@ -35,7 +35,6 @@ from sqlalchemy import UniqueConstraint from sqlalchemy import util from sqlalchemy.dialects.sqlite import base as sqlite from sqlalchemy.dialects.sqlite import pysqlite as pysqlite_dialect -from sqlalchemy.engine.reflection import Inspector from sqlalchemy.engine.url import make_url from sqlalchemy.schema import CreateTable from sqlalchemy.schema import FetchedValue @@ -1963,7 +1962,7 @@ class ConstraintReflectionTest(fixtures.TestBase): def test_foreign_key_name_is_none(self): # and not "0" - inspector = Inspector(testing.db) + inspector = inspect(testing.db) fks = inspector.get_foreign_keys("b") eq_( fks, @@ -1988,7 +1987,7 @@ class ConstraintReflectionTest(fixtures.TestBase): ) def test_foreign_key_name_is_not_none(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) fks = inspector.get_foreign_keys("c") eq_( fks, @@ -2013,7 +2012,7 @@ class ConstraintReflectionTest(fixtures.TestBase): ) def test_foreign_key_implicit_parent(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) fks = inspector.get_foreign_keys("implicit_referrer") eq_( fks, @@ -2030,7 +2029,7 @@ class ConstraintReflectionTest(fixtures.TestBase): ) def test_foreign_key_composite_implicit_parent(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) fks = inspector.get_foreign_keys("implicit_referrer_comp") eq_( fks, @@ -2049,7 +2048,7 @@ class ConstraintReflectionTest(fixtures.TestBase): def test_foreign_key_implicit_missing_parent(self): # test when the FK refers to a non-existent table and column names # aren't given. only sqlite allows this case to exist - inspector = Inspector(testing.db) + inspector = inspect(testing.db) fks = inspector.get_foreign_keys("implicit_referrer_comp_fake") # the referred table doesn't exist but the operation does not fail eq_( @@ -2079,7 +2078,7 @@ class ConstraintReflectionTest(fixtures.TestBase): ) def test_unnamed_inline_foreign_key(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) fks = inspector.get_foreign_keys("e") eq_( fks, @@ -2096,7 +2095,7 @@ class ConstraintReflectionTest(fixtures.TestBase): ) def test_unnamed_inline_foreign_key_quoted(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) fks = inspector.get_foreign_keys("e1") eq_( fks, @@ -2127,7 +2126,7 @@ class ConstraintReflectionTest(fixtures.TestBase): ) def test_foreign_key_composite_broken_casing(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) fks = inspector.get_foreign_keys("j") eq_( fks, @@ -2158,7 +2157,7 @@ class ConstraintReflectionTest(fixtures.TestBase): ) def test_foreign_key_ondelete_onupdate(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) fks = inspector.get_foreign_keys("onud_test") eq_( fks, @@ -2221,7 +2220,7 @@ class ConstraintReflectionTest(fixtures.TestBase): ) def test_dont_reflect_autoindex(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_(inspector.get_indexes("o"), []) eq_( inspector.get_indexes("o", include_auto_indexes=True), @@ -2237,7 +2236,7 @@ class ConstraintReflectionTest(fixtures.TestBase): def test_create_index_with_schema(self): """Test creation of index with explicit schema""" - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_indexes("l", schema="main"), [ @@ -2250,35 +2249,35 @@ class ConstraintReflectionTest(fixtures.TestBase): ) def test_unique_constraint_named(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_unique_constraints("f"), [{"column_names": ["x"], "name": "foo_fx"}], ) def test_unique_constraint_named_broken_casing(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_unique_constraints("h"), [{"column_names": ["x"], "name": "foo_hx"}], ) def test_unique_constraint_named_broken_temp(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_unique_constraints("g"), [{"column_names": ["x"], "name": "foo_gx"}], ) def test_unique_constraint_unnamed_inline(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_unique_constraints("d"), [{"column_names": ["x"], "name": None}], ) def test_unique_constraint_unnamed_inline_quoted(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_unique_constraints("d1"), [{"column_names": ["some ( STUPID n,ame"], "name": None}], @@ -2293,42 +2292,42 @@ class ConstraintReflectionTest(fixtures.TestBase): ) def test_unique_constraint_unnamed_normal(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_unique_constraints("m"), [{"column_names": ["x"], "name": None}], ) def test_unique_constraint_unnamed_normal_temporary(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_unique_constraints("n"), [{"column_names": ["x"], "name": None}], ) def test_primary_key_constraint_named(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_pk_constraint("p"), {"constrained_columns": ["id"], "name": "pk_name"}, ) def test_primary_key_constraint_unnamed(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_pk_constraint("q"), {"constrained_columns": ["id"], "name": None}, ) def test_primary_key_constraint_no_pk(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_pk_constraint("d"), {"constrained_columns": [], "name": None}, ) def test_check_constraint(self): - inspector = Inspector(testing.db) + inspector = inspect(testing.db) eq_( inspector.get_check_constraints("cp"), [ diff --git a/test/engine/test_bind.py b/test/engine/test_bind.py index 39acfed2c..956874846 100644 --- a/test/engine/test_bind.py +++ b/test/engine/test_bind.py @@ -26,14 +26,6 @@ class BindTest(fixtures.TestBase): assert not conn.closed assert conn.closed - def test_bind_close_conn(self): - e = testing.db - conn = e.connect() - with conn.connect() as c2: - assert not c2.closed - assert not conn.closed - assert c2.closed - def test_create_drop_explicit(self): metadata = MetaData() table = Table("test_table", metadata, Column("foo", Integer)) diff --git a/test/engine/test_deprecations.py b/test/engine/test_deprecations.py index 6fbf1be5b..884838351 100644 --- a/test/engine/test_deprecations.py +++ b/test/engine/test_deprecations.py @@ -13,6 +13,7 @@ from sqlalchemy import String from sqlalchemy import testing from sqlalchemy import text from sqlalchemy import TypeDecorator +from sqlalchemy.engine import reflection from sqlalchemy.engine.base import Engine from sqlalchemy.engine.mock import MockConnection from sqlalchemy.testing import assert_raises @@ -20,6 +21,7 @@ from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import engines from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures +from sqlalchemy.testing import is_ from sqlalchemy.testing import is_false from sqlalchemy.testing import is_true from sqlalchemy.testing.mock import Mock @@ -31,6 +33,52 @@ class SomeException(Exception): pass +class ConnectionlessDeprecationTest(fixtures.TestBase): + """test various things associated with "connectionless" executions.""" + + def test_inspector_constructor_engine(self): + with testing.expect_deprecated( + r"The __init__\(\) method on Inspector is deprecated and will " + r"be removed in a future release." + ): + i1 = reflection.Inspector(testing.db) + + is_(i1.bind, testing.db) + + def test_inspector_constructor_connection(self): + with testing.db.connect() as conn: + with testing.expect_deprecated( + r"The __init__\(\) method on Inspector is deprecated and " + r"will be removed in a future release." + ): + i1 = reflection.Inspector(conn) + + is_(i1.bind, conn) + is_(i1.engine, testing.db) + + def test_inspector_from_engine(self): + with testing.expect_deprecated( + r"The from_engine\(\) method on Inspector is deprecated and will " + r"be removed in a future release." + ): + i1 = reflection.Inspector.from_engine(testing.db) + + is_(i1.bind, testing.db) + + def test_bind_close_conn(self): + e = testing.db + conn = e.connect() + + with testing.expect_deprecated( + r"The .close\(\) method on a so-called 'branched' " + "connection is deprecated" + ): + with conn.connect() as c2: + assert not c2.closed + assert not conn.closed + assert c2.closed + + class CreateEngineTest(fixtures.TestBase): def test_strategy_keyword_mock(self): def executor(x, y): @@ -512,3 +560,17 @@ class DeprecatedReflectionTest(fixtures.TablesTest): ): table_names = testing.db.table_names() is_true(set(table_names).issuperset(metadata.tables)) + + +class ExecutionOptionsTest(fixtures.TestBase): + def test_branched_connection_execution_options(self): + engine = engines.testing_engine("sqlite://") + + conn = engine.connect() + c2 = conn.execution_options(foo="bar") + + with testing.expect_deprecated_20( + r"The Connection.connect\(\) function/method is considered " + ): + c2_branch = c2.connect() + eq_(c2_branch._execution_options, {"foo": "bar"}) diff --git a/test/engine/test_execute.py b/test/engine/test_execute.py index ac9f034fe..5acd14177 100644 --- a/test/engine/test_execute.py +++ b/test/engine/test_execute.py @@ -1262,14 +1262,6 @@ class ExecutionOptionsTest(fixtures.TestBase): eq_(c1._execution_options, {"foo": "bar"}) eq_(c2._execution_options, {"foo": "bar", "bat": "hoho"}) - def test_branched_connection_execution_options(self): - engine = testing_engine("sqlite://") - - conn = engine.connect() - c2 = conn.execution_options(foo="bar") - c2_branch = c2.connect() - eq_(c2_branch._execution_options, {"foo": "bar"}) - def test_get_engine_execution_options(self): engine = testing_engine("sqlite://") engine.dialect = Mock() diff --git a/test/engine/test_reflection.py b/test/engine/test_reflection.py index c137488ec..301614061 100644 --- a/test/engine/test_reflection.py +++ b/test/engine/test_reflection.py @@ -34,9 +34,6 @@ from sqlalchemy.testing.schema import Table from sqlalchemy.util import ue -metadata, users = None, None - - class ReflectionTest(fixtures.TestBase, ComparesTables): __backend__ = True @@ -906,15 +903,15 @@ class ReflectionTest(fixtures.TestBase, ComparesTables): test_needs_fk=True, ) - meta.create_all() - meta2 = MetaData(testing.db) + meta.create_all(testing.db) + meta2 = MetaData() a2 = Table( "addresses", meta2, Column("user_id", sa.Integer, sa.ForeignKey("users.id")), - autoload=True, + autoload_with=testing.db, ) - u2 = Table("users", meta2, autoload=True) + u2 = Table("users", meta2, autoload_with=testing.db) s = sa.select([a2]).subquery() assert s.c.user_id is not None @@ -926,19 +923,19 @@ class ReflectionTest(fixtures.TestBase, ComparesTables): assert list(a2.c.user_id.foreign_keys)[0].parent is a2.c.user_id assert u2.join(a2).onclause.compare(u2.c.id == a2.c.user_id) - meta2 = MetaData(testing.db) + meta2 = MetaData() u2 = Table( "users", meta2, Column("id", sa.Integer, primary_key=True), - autoload=True, + autoload_with=testing.db, ) a2 = Table( "addresses", meta2, Column("id", sa.Integer, primary_key=True), Column("user_id", sa.Integer, sa.ForeignKey("users.id")), - autoload=True, + autoload_with=testing.db, ) s = sa.select([a2]).subquery() @@ -1012,29 +1009,28 @@ class ReflectionTest(fixtures.TestBase, ComparesTables): for attr in test_attrs: eq_(getattr(fk, attr), getattr(ref, attr)) + @testing.provide_metadata def test_pks_not_uniques(self): """test that primary key reflection not tripped up by unique indexes""" - testing.db.execute( - """ - CREATE TABLE book ( - id INTEGER NOT NULL, - title VARCHAR(100) NOT NULL, - series INTEGER, - series_id INTEGER, - UNIQUE(series, series_id), - PRIMARY KEY(id) - )""" - ) - try: - metadata = MetaData(bind=testing.db) - book = Table("book", metadata, autoload=True) - assert book.primary_key.contains_column(book.c.id) - assert not book.primary_key.contains_column(book.c.series) - assert len(book.primary_key) == 1 - finally: - testing.db.execute("drop table book") + with testing.db.begin() as conn: + conn.execute( + """ + CREATE TABLE book ( + id INTEGER NOT NULL, + title VARCHAR(100) NOT NULL, + series INTEGER, + series_id INTEGER, + UNIQUE(series, series_id), + PRIMARY KEY(id) + )""" + ) + + book = Table("book", self.metadata, autoload_with=testing.db) + assert book.primary_key.contains_column(book.c.id) + assert not book.primary_key.contains_column(book.c.series) + eq_(len(book.primary_key), 1) def test_fk_error(self): metadata = MetaData(testing.db) @@ -1054,30 +1050,28 @@ class ReflectionTest(fixtures.TestBase, ComparesTables): metadata.create_all, ) + @testing.provide_metadata def test_composite_pks(self): """test reflection of a composite primary key""" - testing.db.execute( - """ - CREATE TABLE book ( - id INTEGER NOT NULL, - isbn VARCHAR(50) NOT NULL, - title VARCHAR(100) NOT NULL, - series INTEGER NOT NULL, - series_id INTEGER NOT NULL, - UNIQUE(series, series_id), - PRIMARY KEY(id, isbn) - )""" - ) - try: - metadata = MetaData(bind=testing.db) - book = Table("book", metadata, autoload=True) - assert book.primary_key.contains_column(book.c.id) - assert book.primary_key.contains_column(book.c.isbn) - assert not book.primary_key.contains_column(book.c.series) - assert len(book.primary_key) == 2 - finally: - testing.db.execute("drop table book") + with testing.db.begin() as conn: + conn.execute( + """ + CREATE TABLE book ( + id INTEGER NOT NULL, + isbn VARCHAR(50) NOT NULL, + title VARCHAR(100) NOT NULL, + series INTEGER NOT NULL, + series_id INTEGER NOT NULL, + UNIQUE(series, series_id), + PRIMARY KEY(id, isbn) + )""" + ) + book = Table("book", self.metadata, autoload_with=testing.db) + assert book.primary_key.contains_column(book.c.id) + assert book.primary_key.contains_column(book.c.isbn) + assert not book.primary_key.contains_column(book.c.series) + eq_(len(book.primary_key), 2) @testing.exclude("mysql", "<", (4, 1, 1), "innodb funkiness") @testing.provide_metadata @@ -1747,16 +1741,17 @@ class SchemaTest(fixtures.TestBase): "dialect %s doesn't have a has_schema method" % testing.db.dialect.name ) - eq_( - testing.db.dialect.has_schema( - testing.db, testing.config.test_schema - ), - True, - ) - eq_( - testing.db.dialect.has_schema(testing.db, "sa_fake_schema_123"), - False, - ) + with testing.db.connect() as conn: + eq_( + testing.db.dialect.has_schema( + conn, testing.config.test_schema + ), + True, + ) + eq_( + testing.db.dialect.has_schema(conn, "sa_fake_schema_123"), + False, + ) @testing.requires.schemas @testing.requires.cross_schema_fk_reflection @@ -2018,24 +2013,29 @@ def createIndexes(con, schema=None): @testing.requires.views def _create_views(con, schema=None): - for table_name in ("users", "email_addresses"): - fullname = table_name - if schema: - fullname = "%s.%s" % (schema, table_name) - view_name = fullname + "_v" - query = "CREATE VIEW %s AS SELECT * FROM %s" % (view_name, fullname) - con.execute(sa.sql.text(query)) + with testing.db.connect() as conn: + for table_name in ("users", "email_addresses"): + fullname = table_name + if schema: + fullname = "%s.%s" % (schema, table_name) + view_name = fullname + "_v" + query = "CREATE VIEW %s AS SELECT * FROM %s" % ( + view_name, + fullname, + ) + conn.execute(sa.sql.text(query)) @testing.requires.views def _drop_views(con, schema=None): - for table_name in ("email_addresses", "users"): - fullname = table_name - if schema: - fullname = "%s.%s" % (schema, table_name) - view_name = fullname + "_v" - query = "DROP VIEW %s" % view_name - con.execute(sa.sql.text(query)) + with testing.db.connect() as conn: + for table_name in ("email_addresses", "users"): + fullname = table_name + if schema: + fullname = "%s.%s" % (schema, table_name) + view_name = fullname + "_v" + query = "DROP VIEW %s" % view_name + conn.execute(sa.sql.text(query)) class ReverseCasingReflectTest(fixtures.TestBase, AssertsCompiledSQL): diff --git a/test/sql/test_defaults.py b/test/sql/test_defaults.py index b31b070d8..2fded335b 100644 --- a/test/sql/test_defaults.py +++ b/test/sql/test_defaults.py @@ -163,12 +163,7 @@ class DefaultTest(fixtures.TestBase): def mydefault_using_connection(ctx): conn = ctx.connection - try: - return conn.execute(sa.select([sa.text("12")])).scalar() - finally: - # ensure a "close()" on this connection does nothing, - # since its a "branched" connection - conn.close() + return conn.execute(sa.select([sa.text("12")])).scalar() use_function_defaults = testing.against("postgresql", "mssql") is_oracle = testing.against("oracle") diff --git a/test/sql/test_deprecations.py b/test/sql/test_deprecations.py index b2b1f470b..b058cbe1b 100644 --- a/test/sql/test_deprecations.py +++ b/test/sql/test_deprecations.py @@ -1494,3 +1494,38 @@ class PositionalTextTest(fixtures.TablesTest): "Could not locate column in row for column 'text1.b'", lambda: row[text1.c.b], ) + + +class DefaultTest(fixtures.TestBase): + __backend__ = True + + @testing.provide_metadata + def test_close_on_branched(self): + metadata = self.metadata + + def mydefault_using_connection(ctx): + conn = ctx.connection + try: + return conn.execute(select([text("12")])).scalar() + finally: + # ensure a "close()" on this connection does nothing, + # since its a "branched" connection + conn.close() + + table = Table( + "foo", + metadata, + Column("x", Integer), + Column("y", Integer, default=mydefault_using_connection), + ) + + metadata.create_all(testing.db) + with testing.db.connect() as conn: + with testing.expect_deprecated( + r"The .close\(\) method on a so-called 'branched' " + r"connection is deprecated as of 1.4, as are " + r"'branched' connections overall" + ): + conn.execute(table.insert().values(x=5)) + + eq_(conn.execute(select([table])).first(), (5, 12)) |