summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_14/5131.rst8
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py3
-rw-r--r--lib/sqlalchemy/engine/base.py17
-rw-r--r--lib/sqlalchemy/engine/default.py76
-rw-r--r--lib/sqlalchemy/engine/interfaces.py38
-rw-r--r--lib/sqlalchemy/engine/reflection.py233
-rw-r--r--lib/sqlalchemy/sql/schema.py4
-rw-r--r--lib/sqlalchemy/testing/__init__.py1
-rw-r--r--lib/sqlalchemy/testing/assertions.py4
-rw-r--r--lib/sqlalchemy/testing/suite/test_reflection.py3
-rw-r--r--lib/sqlalchemy/util/__init__.py1
-rw-r--r--lib/sqlalchemy/util/deprecations.py69
-rw-r--r--test/dialect/mssql/test_reflection.py3
-rw-r--r--test/dialect/postgresql/test_reflection.py9
-rw-r--r--test/dialect/test_sqlite.py45
-rw-r--r--test/engine/test_bind.py8
-rw-r--r--test/engine/test_deprecations.py62
-rw-r--r--test/engine/test_execute.py8
-rw-r--r--test/engine/test_reflection.py148
-rw-r--r--test/sql/test_defaults.py7
-rw-r--r--test/sql/test_deprecations.py35
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))