summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/engine
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/engine')
-rw-r--r--lib/sqlalchemy/engine/base.py1381
-rw-r--r--lib/sqlalchemy/engine/create.py33
-rw-r--r--lib/sqlalchemy/engine/default.py168
-rw-r--r--lib/sqlalchemy/engine/events.py22
-rw-r--r--lib/sqlalchemy/engine/interfaces.py18
-rw-r--r--lib/sqlalchemy/engine/util.py122
6 files changed, 487 insertions, 1257 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 5c1a159a6..41c5f4753 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -12,11 +12,10 @@ import sys
from .interfaces import Connectable
from .interfaces import ConnectionEventsTarget
from .interfaces import ExceptionContext
-from .util import _distill_params
from .util import _distill_params_20
+from .util import _distill_raw_params
from .util import TransactionalContext
from .. import exc
-from .. import inspection
from .. import log
from .. import util
from ..sql import compiler
@@ -28,15 +27,12 @@ from ..sql import util as sql_util
"""
_EMPTY_EXECUTION_OPTS = util.immutabledict()
+NO_OPTIONS = util.immutabledict()
class Connection(Connectable):
"""Provides high-level functionality for a wrapped DB-API connection.
- **This is the SQLAlchemy 1.x.x version** of the :class:`_engine.Connection`
- class. For the :term:`2.0 style` version, which features some API
- differences, see :class:`_future.Connection`.
-
The :class:`_engine.Connection` object is procured by calling
the :meth:`_engine.Engine.connect` method of the :class:`_engine.Engine`
object, and provides services for execution of SQL statements as well
@@ -59,7 +55,6 @@ class Connection(Connectable):
"""
- _is_future = False
_sqla_logger_namespace = "sqlalchemy.engine.Connection"
# used by sqlalchemy.engine.util.TransactionalContext
@@ -74,53 +69,46 @@ class Connection(Connectable):
self,
engine,
connection=None,
- _branch_from=None,
- _execution_options=None,
- _dispatch=None,
_has_events=None,
_allow_revalidate=True,
+ _allow_autobegin=True,
):
"""Construct a new Connection."""
self.engine = engine
- self.dialect = engine.dialect
- self.__branch_from = _branch_from
+ self.dialect = dialect = engine.dialect
- if _branch_from:
- # branching is always "from" the root connection
- assert _branch_from.__branch_from is None
- self._dbapi_connection = connection
- self._execution_options = _execution_options
- self._echo = _branch_from._echo
- self.dispatch = _dispatch
- self._has_events = _branch_from._has_events
+ if connection is None:
+ try:
+ self._dbapi_connection = engine.raw_connection()
+ except dialect.dbapi.Error as err:
+ Connection._handle_dbapi_exception_noconnection(
+ err, dialect, engine
+ )
+ raise
else:
- self._dbapi_connection = (
- connection
- if connection is not None
- else engine.raw_connection()
- )
-
- self._transaction = self._nested_transaction = None
- self.__savepoint_seq = 0
- self.__in_begin = False
-
- self.__can_reconnect = _allow_revalidate
- self._echo = self.engine._should_log_info()
+ self._dbapi_connection = connection
- if _has_events is None:
- # if _has_events is sent explicitly as False,
- # then don't join the dispatch of the engine; we don't
- # want to handle any of the engine's events in that case.
- self.dispatch = self.dispatch._join(engine.dispatch)
- self._has_events = _has_events or (
- _has_events is None and engine._has_events
- )
+ self._transaction = self._nested_transaction = None
+ self.__savepoint_seq = 0
+ self.__in_begin = False
+
+ self.__can_reconnect = _allow_revalidate
+ self._allow_autobegin = _allow_autobegin
+ self._echo = self.engine._should_log_info()
+
+ if _has_events is None:
+ # if _has_events is sent explicitly as False,
+ # then don't join the dispatch of the engine; we don't
+ # want to handle any of the engine's events in that case.
+ self.dispatch = self.dispatch._join(engine.dispatch)
+ self._has_events = _has_events or (
+ _has_events is None and engine._has_events
+ )
- assert not _execution_options
- self._execution_options = engine._execution_options
+ self._execution_options = engine._execution_options
if self._has_events or self.engine._has_events:
- self.dispatch.engine_connect(self, _branch_from is not None)
+ self.dispatch.engine_connect(self)
@util.memoized_property
def _message_formatter(self):
@@ -170,44 +158,6 @@ class Connection(Connectable):
else:
return name
- def _branch(self):
- """Return a new Connection which references this Connection's
- engine and connection; whose close() method does nothing.
-
- .. deprecated:: 1.4 the "branching" concept will be removed in
- SQLAlchemy 2.0 as well as the "Connection.connect()" method which
- is the only consumer for this.
-
- The Core uses this very sparingly, only in the case of
- custom SQL default functions that are to be INSERTed as the
- primary key of a row where we need to get the value back, so we have
- to invoke it distinctly - this is a very uncommon case.
-
- Userland code accesses _branch() when the connect()
- method is called. The branched connection
- acts as much as possible like the parent, except that it stays
- connected when a close() event occurs.
-
- """
- return self.engine._connection_cls(
- self.engine,
- self._dbapi_connection,
- _branch_from=self.__branch_from if self.__branch_from else self,
- _execution_options=self._execution_options,
- _has_events=self._has_events,
- _dispatch=self.dispatch,
- )
-
- def _generate_for_options(self):
- """define connection method chaining behavior for execution_options"""
-
- if self._is_future:
- return self
- else:
- c = self.__class__.__new__(self.__class__)
- c.__dict__ = self.__dict__.copy()
- return c
-
def __enter__(self):
return self
@@ -215,49 +165,37 @@ class Connection(Connectable):
self.close()
def execution_options(self, **opt):
- r""" Set non-SQL options for the connection which take effect
+ r"""Set non-SQL options for the connection which take effect
during execution.
- For a "future" style connection, this method returns this same
- :class:`_future.Connection` object with the new options added.
-
- For a legacy connection, this method returns a copy of this
- :class:`_engine.Connection` which references the same underlying DBAPI
- connection, but also defines the given execution options which will
- take effect for a call to
- :meth:`execute`. As the new :class:`_engine.Connection` references the
- same underlying resource, it's usually a good idea to ensure that
- the copies will be discarded immediately, which is implicit if used
- as in::
-
- result = connection.execution_options(stream_results=True).\
- execute(stmt)
-
- Note that any key/value can be passed to
- :meth:`_engine.Connection.execution_options`,
- and it will be stored in the
- ``_execution_options`` dictionary of the :class:`_engine.Connection`.
- It
- is suitable for usage by end-user schemes to communicate with
- event listeners, for example.
+ This method modifies this :class:`_engine.Connection` **in-place**;
+ the return value is the same :class:`_engine.Connection` object
+ upon which the method is called. Note that this is in contrast
+ to the behavior of the ``execution_options`` methods on other
+ objects such as :meth:`_engine.Engine.execution_options` and
+ :meth:`_sql.Executable.execution_options`. The rationale is that many
+ such execution options necessarily modify the state of the base
+ DBAPI connection in any case so there is no feasible means of
+ keeping the effect of such an option localized to a "sub" connection.
+
+ .. versionchanged:: 2.0 The :meth:`_engine.Connection.execution_options`
+ method, in constrast to other objects with this method, modifies
+ the connection in-place without creating copy of it.
+
+ As discussed elsewhere, the :meth:`_engine.Connection.execution_options`
+ method accepts any arbitrary parameters including user defined names.
+ All parameters given are consumable in a number of ways including
+ by using the :meth:`_engine.Connection.get_execution_options` method.
+ See the examples at :meth:`_sql.Executable.execution_options`
+ and :meth:`_engine.Engine.execution_options`.
The keywords that are currently recognized by SQLAlchemy itself
include all those listed under :meth:`.Executable.execution_options`,
as well as others that are specific to :class:`_engine.Connection`.
- :param autocommit: Available on: Connection, statement.
- When True, a COMMIT will be invoked after execution
- when executed in 'autocommit' mode, i.e. when an explicit
- transaction is not begun on the connection. Note that this
- is **library level, not DBAPI level autocommit**. The DBAPI
- connection will remain in a real transaction unless the
- "AUTOCOMMIT" isolation level is used.
-
- .. deprecated:: 1.4 The "autocommit" execution option is deprecated
- and will be removed in SQLAlchemy 2.0. See
- :ref:`migration_20_autocommit` for discussion.
+ :param compiled_cache: Available on: :class:`_engine.Connection`,
+ :class:`_engine.Engine`.
- :param compiled_cache: Available on: Connection.
A dictionary where :class:`.Compiled` objects
will be cached when the :class:`_engine.Connection`
compiles a clause
@@ -272,7 +210,7 @@ class Connection(Connectable):
specified here.
:param logging_token: Available on: :class:`_engine.Connection`,
- :class:`_engine.Engine`.
+ :class:`_engine.Engine`, :class:`_sql.Executable`.
Adds the specified string token surrounded by brackets in log
messages logged by the connection, i.e. the logging that's enabled
@@ -290,7 +228,8 @@ class Connection(Connectable):
:paramref:`_sa.create_engine.logging_name` - adds a name to the
name used by the Python logger object itself.
- :param isolation_level: Available on: :class:`_engine.Connection`.
+ :param isolation_level: Available on: :class:`_engine.Connection`,
+ :class:`_engine.Engine`.
Set the transaction isolation level for the lifespan of this
:class:`_engine.Connection` object.
@@ -301,52 +240,40 @@ class Connection(Connectable):
valid levels.
The isolation level option applies the isolation level by emitting
- statements on the DBAPI connection, and **necessarily affects the
- original Connection object overall**, not just the copy that is
- returned by the call to :meth:`_engine.Connection.execution_options`
- method. The isolation level will remain at the given setting until
- the DBAPI connection itself is returned to the connection pool, i.e.
- the :meth:`_engine.Connection.close` method on the original
- :class:`_engine.Connection` is called,
- where an event handler will emit
- additional statements on the DBAPI connection in order to revert the
- isolation level change.
-
- .. warning:: The ``isolation_level`` execution option should
- **not** be used when a transaction is already established, that
- is, the :meth:`_engine.Connection.begin`
- method or similar has been
- called. A database cannot change the isolation level on a
- transaction in progress, and different DBAPIs and/or
- SQLAlchemy dialects may implicitly roll back or commit
- the transaction, or not affect the connection at all.
+ statements on the DBAPI connection, and **necessarily affects the
+ original Connection object overall**. The isolation level will remain
+ at the given setting until explicitly changed, or when the DBAPI
+ connection itself is :term:`released` to the connection pool, i.e. the
+ :meth:`_engine.Connection.close` method is called, at which time an
+ event handler will emit additional statements on the DBAPI connection
+ in order to revert the isolation level change.
+
+ .. note:: The ``isolation_level`` execution option may only be
+ established before the :meth:`_engine.Connection.begin` method is
+ called, as well as before any SQL statements are emitted which
+ would otherwise trigger "autobegin", or directly after a call to
+ :meth:`_engine.Connection.commit` or
+ :meth:`_engine.Connection.rollback`. A database cannot change the
+ isolation level on a transaction in progress.
.. note:: The ``isolation_level`` execution option is implicitly
reset if the :class:`_engine.Connection` is invalidated, e.g. via
the :meth:`_engine.Connection.invalidate` method, or if a
- disconnection error occurs. The new connection produced after
- the invalidation will not have the isolation level re-applied
- to it automatically.
+ disconnection error occurs. The new connection produced after the
+ invalidation will **not** have the selected isolation level
+ re-applied to it automatically.
.. seealso::
- :paramref:`_sa.create_engine.isolation_level`
- - set per :class:`_engine.Engine` isolation level
+ :ref:`dbapi_autocommit`
:meth:`_engine.Connection.get_isolation_level`
- view current level
- :ref:`SQLite Transaction Isolation <sqlite_isolation_level>`
-
- :ref:`PostgreSQL Transaction Isolation <postgresql_isolation_level>`
-
- :ref:`MySQL Transaction Isolation <mysql_isolation_level>`
-
- :ref:`SQL Server Transaction Isolation <mssql_isolation_level>`
-
- :ref:`session_transaction_isolation` - for the ORM
+ :param no_parameters: Available on: :class:`_engine.Connection`,
+ :class:`_sql.Executable`.
- :param no_parameters: When ``True``, if the final parameter
+ When ``True``, if the final parameter
list or dictionary is totally empty, will invoke the
statement on the cursor as ``cursor.execute(statement)``,
not passing the parameter collection at all.
@@ -358,7 +285,9 @@ class Connection(Connectable):
or piped into a script that's later invoked by
command line tools.
- :param stream_results: Available on: Connection, statement.
+ :param stream_results: Available on: :class:`_engine.Connection`,
+ :class:`_sql.Executable`.
+
Indicate to the dialect that results should be
"streamed" and not pre-buffered, if possible. This is a limitation
of many DBAPIs. The flag is currently understood within a subset
@@ -369,7 +298,9 @@ class Connection(Connectable):
:ref:`engine_stream_results`
- :param schema_translate_map: Available on: Connection, Engine.
+ :param schema_translate_map: Available on: :class:`_engine.Connection`,
+ :class:`_engine.Engine`, :class:`_sql.Executable`.
+
A dictionary mapping schema names to schema names, that will be
applied to the :paramref:`_schema.Table.schema` element of each
:class:`_schema.Table`
@@ -391,14 +322,15 @@ class Connection(Connectable):
:meth:`_engine.Connection.get_execution_options`
+ :ref:`orm_queryguide_execution_options` - documentation on all
+ ORM-specific execution options
""" # noqa
- c = self._generate_for_options()
- c._execution_options = c._execution_options.union(opt)
+ self._execution_options = self._execution_options.union(opt)
if self._has_events or self.engine._has_events:
- self.dispatch.set_connection_execution_options(c, opt)
- self.dialect.set_connection_execution_options(c, opt)
- return c
+ self.dispatch.set_connection_execution_options(self, opt)
+ self.dialect.set_connection_execution_options(self, opt)
+ return self
def get_execution_options(self):
"""Get the non-SQL options which will take effect during execution.
@@ -415,9 +347,6 @@ class Connection(Connectable):
def closed(self):
"""Return True if this connection is closed."""
- # note this is independent for a "branched" connection vs.
- # the base
-
return self._dbapi_connection is None and not self.__can_reconnect
@property
@@ -433,9 +362,6 @@ class Connection(Connectable):
# "closed" does not need to be "invalid". So the state is now
# represented by the two facts alone.
- if self.__branch_from:
- return self.__branch_from.invalidated
-
return self._dbapi_connection is None and not self.closed
@property
@@ -535,39 +461,18 @@ class Connection(Connectable):
return self.dialect.default_isolation_level
def _invalid_transaction(self):
- if self.invalidated:
- raise exc.PendingRollbackError(
- "Can't reconnect until invalid %stransaction is rolled "
- "back."
- % (
- "savepoint "
- if self._nested_transaction is not None
- else ""
- ),
- code="8s2b",
- )
- else:
- assert not self._is_future
- raise exc.PendingRollbackError(
- "This connection is on an inactive %stransaction. "
- "Please rollback() fully before proceeding."
- % (
- "savepoint "
- if self._nested_transaction is not None
- else ""
- ),
- code="8s2a",
- )
+ raise exc.PendingRollbackError(
+ "Can't reconnect until invalid %stransaction is rolled "
+ "back. Please rollback() fully before proceeding"
+ % ("savepoint " if self._nested_transaction is not None else ""),
+ code="8s2b",
+ )
def _revalidate_connection(self):
- if self.__branch_from:
- return self.__branch_from._revalidate_connection()
if self.__can_reconnect and self.invalidated:
if self._transaction is not None:
self._invalid_transaction()
- self._dbapi_connection = self.engine.raw_connection(
- _connection=self
- )
+ self._dbapi_connection = self.engine.raw_connection()
return self._dbapi_connection
raise exc.ResourceClosedError("This Connection is closed")
@@ -591,24 +496,6 @@ class Connection(Connectable):
return self.connection.info
- @util.deprecated_20(":meth:`.Connection.connect`")
- def connect(
- self,
- ):
- """Returns a branched version of this :class:`_engine.Connection`.
-
- The :meth:`_engine.Connection.close` method on the returned
- :class:`_engine.Connection` can be called and this
- :class:`_engine.Connection` will remain open.
-
- This method provides usage symmetry with
- :meth:`_engine.Engine.connect`, including for usage
- with context managers.
-
- """
-
- return self._branch()
-
def invalidate(self, exception=None):
"""Invalidate the underlying DBAPI connection associated with
this :class:`_engine.Connection`.
@@ -655,9 +542,6 @@ class Connection(Connectable):
"""
- if self.__branch_from:
- return self.__branch_from.invalidate(exception=exception)
-
if self.invalidated:
return
@@ -697,50 +581,65 @@ class Connection(Connectable):
self._dbapi_connection.detach()
def _autobegin(self):
- self.begin()
+ if self._allow_autobegin:
+ self.begin()
def begin(self):
- """Begin a transaction and return a transaction handle.
+ """Begin a transaction prior to autobegin occurring.
- The returned object is an instance of :class:`.Transaction`.
- This object represents the "scope" of the transaction,
- which completes when either the :meth:`.Transaction.rollback`
- or :meth:`.Transaction.commit` method is called.
+ E.g.::
- .. tip::
+ with engine.connect() as conn:
+ with conn.begin() as trans:
+ conn.execute(table.insert(), {"username": "sandy"})
- The :meth:`_engine.Connection.begin` method is invoked when using
- the :meth:`_engine.Engine.begin` context manager method as well.
- All documentation that refers to behaviors specific to the
- :meth:`_engine.Connection.begin` method also apply to use of the
- :meth:`_engine.Engine.begin` method.
- Legacy use: nested calls to :meth:`.begin` on the same
- :class:`_engine.Connection` will return new :class:`.Transaction`
- objects that represent an emulated transaction within the scope of the
- enclosing transaction, that is::
+ The returned object is an instance of :class:`_engine.RootTransaction`.
+ This object represents the "scope" of the transaction,
+ which completes when either the :meth:`_engine.Transaction.rollback`
+ or :meth:`_engine.Transaction.commit` method is called; the object
+ also works as a context manager as illustrated above.
- trans = conn.begin() # outermost transaction
- trans2 = conn.begin() # "nested"
- trans2.commit() # does nothing
- trans.commit() # actually commits
+ The :meth:`_engine.Connection.begin` method begins a
+ transaction that normally will be begun in any case when the connection
+ is first used to execute a statement. The reason this method might be
+ used would be to invoke the :meth:`_events.ConnectionEvents.begin`
+ event at a specific time, or to organize code within the scope of a
+ connection checkout in terms of context managed blocks, such as::
- Calls to :meth:`.Transaction.commit` only have an effect
- when invoked via the outermost :class:`.Transaction` object, though the
- :meth:`.Transaction.rollback` method of any of the
- :class:`.Transaction` objects will roll back the
- transaction.
+ with engine.connect() as conn:
+ with conn.begin():
+ conn.execute(...)
+ conn.execute(...)
- .. tip::
+ with conn.begin():
+ conn.execute(...)
+ conn.execute(...)
- The above "nesting" behavior is a legacy behavior specific to
- :term:`1.x style` use and will be removed in SQLAlchemy 2.0. For
- notes on :term:`2.0 style` use, see
- :meth:`_future.Connection.begin`.
+ The above code is not fundamentally any different in its behavior than
+ the following code which does not use
+ :meth:`_engine.Connection.begin`; the below style is referred towards
+ as "commit as you go" style::
+ with engine.connect() as conn:
+ conn.execute(...)
+ conn.execute(...)
+ conn.commit()
+
+ conn.execute(...)
+ conn.execute(...)
+ conn.commit()
+
+ From a database point of view, the :meth:`_engine.Connection.begin`
+ method does not emit any SQL or change the state of the underlying
+ DBAPI connection in any way; the Python DBAPI does not have any
+ concept of explicit transaction begin.
.. seealso::
+ :ref:`tutorial_working_with_transactions` - in the
+ :ref:`unified_tutorial`
+
:meth:`_engine.Connection.begin_nested` - use a SAVEPOINT
:meth:`_engine.Connection.begin_twophase` -
@@ -750,11 +649,6 @@ class Connection(Connectable):
:class:`_engine.Engine`
"""
- if self._is_future:
- assert not self.__branch_from
- elif self.__branch_from:
- return self.__branch_from.begin()
-
if self.__in_begin:
# for dialects that emit SQL within the process of
# dialect.do_begin() or dialect.do_begin_twophase(), this
@@ -766,82 +660,86 @@ class Connection(Connectable):
self._transaction = RootTransaction(self)
return self._transaction
else:
- if self._is_future:
- raise exc.InvalidRequestError(
- "This connection has already initialized a SQLAlchemy "
- "Transaction() object via begin() or autobegin; can't "
- "call begin() here unless rollback() or commit() "
- "is called first."
- )
- else:
- return MarkerTransaction(self)
+ raise exc.InvalidRequestError(
+ "This connection has already initialized a SQLAlchemy "
+ "Transaction() object via begin() or autobegin; can't "
+ "call begin() here unless rollback() or commit() "
+ "is called first."
+ )
def begin_nested(self):
- """Begin a nested transaction (i.e. SAVEPOINT) and return a
- transaction handle, assuming an outer transaction is already
- established.
-
- Nested transactions require SAVEPOINT support in the
- underlying database. Any transaction in the hierarchy may
- ``commit`` and ``rollback``, however the outermost transaction
- still controls the overall ``commit`` or ``rollback`` of the
- transaction of a whole.
-
- The legacy form of :meth:`_engine.Connection.begin_nested` method has
- alternate behaviors based on whether or not the
- :meth:`_engine.Connection.begin` method was called previously. If
- :meth:`_engine.Connection.begin` was not called, then this method will
- behave the same as the :meth:`_engine.Connection.begin` method and
- return a :class:`.RootTransaction` object that begins and commits a
- real transaction - **no savepoint is invoked**. If
- :meth:`_engine.Connection.begin` **has** been called, and a
- :class:`.RootTransaction` is already established, then this method
- returns an instance of :class:`.NestedTransaction` which will invoke
- and manage the scope of a SAVEPOINT.
-
- .. tip::
-
- The above mentioned behavior of
- :meth:`_engine.Connection.begin_nested` is a legacy behavior
- specific to :term:`1.x style` use. In :term:`2.0 style` use, the
- :meth:`_future.Connection.begin_nested` method instead autobegins
- the outer transaction that can be committed using
- "commit-as-you-go" style; see
- :meth:`_future.Connection.begin_nested` for migration details.
-
- .. versionchanged:: 1.4.13 The behavior of
- :meth:`_engine.Connection.begin_nested`
- as returning a :class:`.RootTransaction` if
- :meth:`_engine.Connection.begin` were not called has been restored
- as was the case in 1.3.x versions; in previous 1.4.x versions, an
- outer transaction would be "autobegun" but would not be committed.
+ """Begin a nested transaction (i.e. SAVEPOINT) and return a transaction
+ handle that controls the scope of the SAVEPOINT.
+
+ E.g.::
+
+ with engine.begin() as connection:
+ with connection.begin_nested():
+ connection.execute(table.insert(), {"username": "sandy"})
+
+ The returned object is an instance of
+ :class:`_engine.NestedTransaction`, which includes transactional
+ methods :meth:`_engine.NestedTransaction.commit` and
+ :meth:`_engine.NestedTransaction.rollback`; for a nested transaction,
+ these methods correspond to the operations "RELEASE SAVEPOINT <name>"
+ and "ROLLBACK TO SAVEPOINT <name>". The name of the savepoint is local
+ to the :class:`_engine.NestedTransaction` object and is generated
+ automatically. Like any other :class:`_engine.Transaction`, the
+ :class:`_engine.NestedTransaction` may be used as a context manager as
+ illustrated above which will "release" or "rollback" corresponding to
+ if the operation within the block were successful or raised an
+ exception.
+
+ Nested transactions require SAVEPOINT support in the underlying
+ database, else the behavior is undefined. SAVEPOINT is commonly used to
+ run operations within a transaction that may fail, while continuing the
+ outer transaction. E.g.::
+
+ from sqlalchemy import exc
+
+ with engine.begin() as connection:
+ trans = connection.begin_nested()
+ try:
+ connection.execute(table.insert(), {"username": "sandy"})
+ trans.commit()
+ except exc.IntegrityError: # catch for duplicate username
+ trans.rollback() # rollback to savepoint
+
+ # outer transaction continues
+ connection.execute( ... )
+
+ If :meth:`_engine.Connection.begin_nested` is called without first
+ calling :meth:`_engine.Connection.begin` or
+ :meth:`_engine.Engine.begin`, the :class:`_engine.Connection` object
+ will "autobegin" the outer transaction first. This outer transaction
+ may be committed using "commit-as-you-go" style, e.g.::
+ with engine.connect() as connection: # begin() wasn't called
+
+ with connection.begin_nested(): will auto-"begin()" first
+ connection.execute( ... )
+ # savepoint is released
+
+ connection.execute( ... )
+
+ # explicitly commit outer transaction
+ connection.commit()
+
+ # can continue working with connection here
+
+ .. versionchanged:: 2.0
+
+ :meth:`_engine.Connection.begin_nested` will now participate
+ in the connection "autobegin" behavior that is new as of
+ 2.0 / "future" style connections in 1.4.
.. seealso::
:meth:`_engine.Connection.begin`
- :meth:`_engine.Connection.begin_twophase`
-
"""
- if self._is_future:
- assert not self.__branch_from
- elif self.__branch_from:
- return self.__branch_from.begin_nested()
-
if self._transaction is None:
- if not self._is_future:
- util.warn_deprecated_20(
- "Calling Connection.begin_nested() in 2.0 style use will "
- "return a NestedTransaction (SAVEPOINT) in all cases, "
- "that will not commit the outer transaction. For code "
- "that is cross-compatible between 1.x and 2.0 style use, "
- "ensure Connection.begin() is called before calling "
- "Connection.begin_nested()."
- )
- return self.begin()
- else:
- self._autobegin()
+ self._autobegin()
return NestedTransaction(self)
@@ -865,9 +763,6 @@ class Connection(Connectable):
"""
- if self.__branch_from:
- return self.__branch_from.begin_twophase(xid=xid)
-
if self._transaction is not None:
raise exc.InvalidRequestError(
"Cannot start a two phase transaction when a transaction "
@@ -877,6 +772,57 @@ class Connection(Connectable):
xid = self.engine.dialect.create_xid()
return TwoPhaseTransaction(self, xid)
+ def commit(self):
+ """Commit the transaction that is currently in progress.
+
+ This method commits the current transaction if one has been started.
+ If no transaction was started, the method has no effect, assuming
+ the connection is in a non-invalidated state.
+
+ A transaction is begun on a :class:`_engine.Connection` automatically
+ whenever a statement is first executed, or when the
+ :meth:`_engine.Connection.begin` method is called.
+
+ .. note:: The :meth:`_engine.Connection.commit` method only acts upon
+ the primary database transaction that is linked to the
+ :class:`_engine.Connection` object. It does not operate upon a
+ SAVEPOINT that would have been invoked from the
+ :meth:`_engine.Connection.begin_nested` method; for control of a
+ SAVEPOINT, call :meth:`_engine.NestedTransaction.commit` on the
+ :class:`_engine.NestedTransaction` that is returned by the
+ :meth:`_engine.Connection.begin_nested` method itself.
+
+
+ """
+ if self._transaction:
+ self._transaction.commit()
+
+ def rollback(self):
+ """Roll back the transaction that is currently in progress.
+
+ This method rolls back the current transaction if one has been started.
+ If no transaction was started, the method has no effect. If a
+ transaction was started and the connection is in an invalidated state,
+ the transaction is cleared using this method.
+
+ A transaction is begun on a :class:`_engine.Connection` automatically
+ whenever a statement is first executed, or when the
+ :meth:`_engine.Connection.begin` method is called.
+
+ .. note:: The :meth:`_engine.Connection.rollback` method only acts
+ upon the primary database transaction that is linked to the
+ :class:`_engine.Connection` object. It does not operate upon a
+ SAVEPOINT that would have been invoked from the
+ :meth:`_engine.Connection.begin_nested` method; for control of a
+ SAVEPOINT, call :meth:`_engine.NestedTransaction.rollback` on the
+ :class:`_engine.NestedTransaction` that is returned by the
+ :meth:`_engine.Connection.begin_nested` method itself.
+
+
+ """
+ if self._transaction:
+ self._transaction.rollback()
+
def recover_twophase(self):
return self.engine.dialect.do_recover_twophase(self)
@@ -888,16 +834,10 @@ class Connection(Connectable):
def in_transaction(self):
"""Return True if a transaction is in progress."""
- if self.__branch_from is not None:
- return self.__branch_from.in_transaction()
-
return self._transaction is not None and self._transaction.is_active
def in_nested_transaction(self):
"""Return True if a transaction is in progress."""
- if self.__branch_from is not None:
- return self.__branch_from.in_nested_transaction()
-
return (
self._nested_transaction is not None
and self._nested_transaction.is_active
@@ -916,9 +856,6 @@ class Connection(Connectable):
"""
- if self.__branch_from is not None:
- return self.__branch_from.get_transaction()
-
return self._transaction
def get_nested_transaction(self):
@@ -927,15 +864,9 @@ class Connection(Connectable):
.. versionadded:: 1.4
"""
- if self.__branch_from is not None:
-
- return self.__branch_from.get_nested_transaction()
-
return self._nested_transaction
def _begin_impl(self, transaction):
- assert not self.__branch_from
-
if self._echo:
self._log_info("BEGIN (implicit)")
@@ -952,8 +883,6 @@ class Connection(Connectable):
self.__in_begin = False
def _rollback_impl(self):
- assert not self.__branch_from
-
if self._has_events or self.engine._has_events:
self.dispatch.rollback(self)
@@ -971,21 +900,7 @@ class Connection(Connectable):
except BaseException as e:
self._handle_dbapi_exception(e, None, None, None, None)
- def _commit_impl(self, autocommit=False):
- assert not self.__branch_from
-
- # AUTOCOMMIT isolation-level is a dialect-specific concept, however
- # if a connection has this set as the isolation level, we can skip
- # the "autocommit" warning as the operation will do "autocommit"
- # in any case
- if autocommit and not self._is_autocommit():
- util.warn_deprecated_20(
- "The current statement is being autocommitted using "
- "implicit autocommit, which will be removed in "
- "SQLAlchemy 2.0. "
- "Use the .begin() method of Engine or Connection in order to "
- "use an explicit transaction for DML and DDL statements."
- )
+ def _commit_impl(self):
if self._has_events or self.engine._has_events:
self.dispatch.commit(self)
@@ -1004,8 +919,6 @@ class Connection(Connectable):
self._handle_dbapi_exception(e, None, None, None, None)
def _savepoint_impl(self, name=None):
- assert not self.__branch_from
-
if self._has_events or self.engine._has_events:
self.dispatch.savepoint(self, name)
@@ -1017,8 +930,6 @@ class Connection(Connectable):
return name
def _rollback_to_savepoint_impl(self, name):
- assert not self.__branch_from
-
if self._has_events or self.engine._has_events:
self.dispatch.rollback_savepoint(self, name, None)
@@ -1026,8 +937,6 @@ class Connection(Connectable):
self.engine.dialect.do_rollback_to_savepoint(self, name)
def _release_savepoint_impl(self, name):
- assert not self.__branch_from
-
if self._has_events or self.engine._has_events:
self.dispatch.release_savepoint(self, name, None)
@@ -1035,8 +944,6 @@ class Connection(Connectable):
self.engine.dialect.do_release_savepoint(self, name)
def _begin_twophase_impl(self, transaction):
- assert not self.__branch_from
-
if self._echo:
self._log_info("BEGIN TWOPHASE (implicit)")
if self._has_events or self.engine._has_events:
@@ -1052,8 +959,6 @@ class Connection(Connectable):
self.__in_begin = False
def _prepare_twophase_impl(self, xid):
- assert not self.__branch_from
-
if self._has_events or self.engine._has_events:
self.dispatch.prepare_twophase(self, xid)
@@ -1065,8 +970,6 @@ class Connection(Connectable):
self._handle_dbapi_exception(e, None, None, None, None)
def _rollback_twophase_impl(self, xid, is_prepared):
- assert not self.__branch_from
-
if self._has_events or self.engine._has_events:
self.dispatch.rollback_twophase(self, xid, is_prepared)
@@ -1080,8 +983,6 @@ class Connection(Connectable):
self._handle_dbapi_exception(e, None, None, None, None)
def _commit_twophase_impl(self, xid, is_prepared):
- assert not self.__branch_from
-
if self._has_events or self.engine._has_events:
self.dispatch.commit_twophase(self, xid, is_prepared)
@@ -1092,24 +993,6 @@ class Connection(Connectable):
except BaseException as e:
self._handle_dbapi_exception(e, None, None, None, None)
- def _autorollback(self):
- if self.__branch_from:
- self.__branch_from._autorollback()
-
- if not self.in_transaction():
- self._rollback_impl()
-
- def _warn_for_legacy_exec_format(self):
- util.warn_deprecated_20(
- "The connection.execute() method in "
- "SQLAlchemy 2.0 will accept parameters as a single "
- "dictionary or a "
- "single sequence of dictionaries only. "
- "Parameters passed as keyword arguments, tuples or positionally "
- "oriented dictionaries and/or tuples "
- "will no longer be accepted."
- )
-
def close(self):
"""Close this :class:`_engine.Connection`.
@@ -1124,24 +1007,15 @@ class Connection(Connectable):
of any :class:`.Transaction` object that may be
outstanding with regards to this :class:`_engine.Connection`.
+ This has the effect of also calling :meth:`_engine.Connection.rollback`
+ if any transaction is in place.
+
After :meth:`_engine.Connection.close` is called, the
:class:`_engine.Connection` is permanently in a closed state,
and will allow no further operations.
"""
- if self.__branch_from:
- assert not self._is_future
- util.warn_deprecated_20(
- "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."
- )
- self._dbapi_connection = None
- self.__can_reconnect = False
- return
-
if self._transaction:
self._transaction.close()
skip_reset = True
@@ -1165,16 +1039,20 @@ class Connection(Connectable):
self._dbapi_connection = None
self.__can_reconnect = False
- def scalar(self, object_, *multiparams, **params):
- """Executes and returns the first column of the first row.
+ def scalar(self, statement, parameters=None, execution_options=None):
+ r"""Executes a SQL statement construct and returns a scalar object.
- The underlying result/cursor is closed after execution.
+ This method is shorthand for invoking the
+ :meth:`_engine.Result.scalar` method after invoking the
+ :meth:`_engine.Connection.execute` method. Parameters are equivalent.
- """
+ :return: a scalar Python value representing the first column of the
+ first row returned.
- return self.execute(object_, *multiparams, **params).scalar()
+ """
+ return self.execute(statement, parameters, execution_options).scalar()
- def scalars(self, object_, *multiparams, **params):
+ def scalars(self, statement, parameters=None, execution_options=None):
"""Executes and returns a scalar result set, which yields scalar values
from the first column of each row.
@@ -1189,100 +1067,41 @@ class Connection(Connectable):
"""
- return self.execute(object_, *multiparams, **params).scalars()
+ return self.execute(statement, parameters, execution_options).scalars()
- def execute(self, statement, *multiparams, **params):
+ def execute(self, statement, parameters=None, execution_options=None):
r"""Executes a SQL statement construct and returns a
- :class:`_engine.CursorResult`.
-
- :param statement: The statement to be executed. May be
- one of:
-
- * a plain string (deprecated)
- * any :class:`_expression.ClauseElement` construct that is also
- a subclass of :class:`.Executable`, such as a
- :func:`_expression.select` construct
- * a :class:`.FunctionElement`, such as that generated
- by :data:`.func`, will be automatically wrapped in
- a SELECT statement, which is then executed.
- * a :class:`.DDLElement` object
- * a :class:`.DefaultGenerator` object
- * a :class:`.Compiled` object
-
- .. deprecated:: 2.0 passing a string to
- :meth:`_engine.Connection.execute` is
- deprecated and will be removed in version 2.0. Use the
- :func:`_expression.text` construct with
- :meth:`_engine.Connection.execute`, or the
- :meth:`_engine.Connection.exec_driver_sql`
- method to invoke a driver-level
- SQL string.
-
- :param \*multiparams/\**params: represent bound parameter
- values to be used in the execution. Typically,
- the format is either a collection of one or more
- dictionaries passed to \*multiparams::
-
- conn.execute(
- table.insert(),
- {"id":1, "value":"v1"},
- {"id":2, "value":"v2"}
- )
-
- ...or individual key/values interpreted by \**params::
-
- conn.execute(
- table.insert(), id=1, value="v1"
- )
-
- In the case that a plain SQL string is passed, and the underlying
- DBAPI accepts positional bind parameters, a collection of tuples
- or individual values in \*multiparams may be passed::
-
- conn.execute(
- "INSERT INTO table (id, value) VALUES (?, ?)",
- (1, "v1"), (2, "v2")
- )
-
- conn.execute(
- "INSERT INTO table (id, value) VALUES (?, ?)",
- 1, "v1"
- )
-
- Note above, the usage of a question mark "?" or other
- symbol is contingent upon the "paramstyle" accepted by the DBAPI
- in use, which may be any of "qmark", "named", "pyformat", "format",
- "numeric". See `pep-249
- <https://www.python.org/dev/peps/pep-0249/>`_ for details on
- paramstyle.
-
- To execute a textual SQL statement which uses bound parameters in a
- DBAPI-agnostic way, use the :func:`_expression.text` construct.
-
- .. deprecated:: 2.0 use of tuple or scalar positional parameters
- is deprecated. All params should be dicts or sequences of dicts.
- Use :meth:`.exec_driver_sql` to execute a plain string with
- tuple or scalar positional parameters.
+ :class:`_engine.Result`.
+
+ :param statement: The statement to be executed. This is always
+ an object that is in both the :class:`_expression.ClauseElement` and
+ :class:`_expression.Executable` hierarchies, including:
+
+ * :class:`_expression.Select`
+ * :class:`_expression.Insert`, :class:`_expression.Update`,
+ :class:`_expression.Delete`
+ * :class:`_expression.TextClause` and
+ :class:`_expression.TextualSelect`
+ * :class:`_schema.DDL` and objects which inherit from
+ :class:`_schema.DDLElement`
+
+ :param parameters: parameters which will be bound into the statement.
+ This may be either a dictionary of parameter names to values,
+ or a mutable sequence (e.g. a list) of dictionaries. When a
+ list of dictionaries is passed, the underlying statement execution
+ will make use of the DBAPI ``cursor.executemany()`` method.
+ When a single dictionary is passed, the DBAPI ``cursor.execute()``
+ method will be used.
+
+ :param execution_options: optional dictionary of execution options,
+ which will be associated with the statement execution. This
+ dictionary can provide a subset of the options that are accepted
+ by :meth:`_engine.Connection.execution_options`.
+
+ :return: a :class:`_engine.Result` object.
"""
-
- if isinstance(statement, util.string_types):
- util.warn_deprecated_20(
- "Passing a string to Connection.execute() is "
- "deprecated and will be removed in version 2.0. Use the "
- "text() construct, "
- "or the Connection.exec_driver_sql() method to invoke a "
- "driver-level SQL string."
- )
-
- return self._exec_driver_sql(
- statement,
- multiparams,
- params,
- _EMPTY_EXECUTION_OPTS,
- future=False,
- )
-
+ distilled_parameters = _distill_params_20(parameters)
try:
meth = statement._execute_on_connection
except AttributeError as err:
@@ -1290,22 +1109,21 @@ class Connection(Connectable):
exc.ObjectNotExecutableError(statement), replace_context=err
)
else:
- return meth(self, multiparams, params, _EMPTY_EXECUTION_OPTS)
+ return meth(
+ self,
+ distilled_parameters,
+ execution_options or NO_OPTIONS,
+ )
- def _execute_function(self, func, multiparams, params, execution_options):
+ def _execute_function(self, func, distilled_parameters, execution_options):
"""Execute a sql.FunctionElement object."""
return self._execute_clauseelement(
- func.select(), multiparams, params, execution_options
+ func.select(), distilled_parameters, execution_options
)
def _execute_default(
- self,
- default,
- multiparams,
- params,
- # migrate is calling this directly :(
- execution_options=_EMPTY_EXECUTION_OPTS,
+ self, default, distilled_parameters, execution_options
):
"""Execute a schema.ColumnDefault object."""
@@ -1313,12 +1131,14 @@ class Connection(Connectable):
execution_options
)
- distilled_parameters = _distill_params(self, multiparams, params)
-
+ # note for event handlers, the "distilled parameters" which is always
+ # a list of dicts is broken out into separate "multiparams" and
+ # "params" collections, which allows the handler to distinguish
+ # between an executemany and execute style set of parameters.
if self._has_events or self.engine._has_events:
(
default,
- distilled_params,
+ distilled_parameters,
event_multiparams,
event_params,
) = self._invoke_before_exec_event(
@@ -1353,19 +1173,17 @@ class Connection(Connectable):
return ret
- def _execute_ddl(self, ddl, multiparams, params, execution_options):
+ def _execute_ddl(self, ddl, distilled_parameters, execution_options):
"""Execute a schema.DDL object."""
execution_options = ddl._execution_options.merge_with(
self._execution_options, execution_options
)
- distilled_parameters = _distill_params(self, multiparams, params)
-
if self._has_events or self.engine._has_events:
(
ddl,
- distilled_params,
+ distilled_parameters,
event_multiparams,
event_params,
) = self._invoke_before_exec_event(
@@ -1432,7 +1250,7 @@ class Connection(Connectable):
return elem, distilled_params, event_multiparams, event_params
def _execute_clauseelement(
- self, elem, multiparams, params, execution_options
+ self, elem, distilled_parameters, execution_options
):
"""Execute a sql.ClauseElement object."""
@@ -1440,24 +1258,22 @@ class Connection(Connectable):
self._execution_options, execution_options
)
- distilled_params = _distill_params(self, multiparams, params)
-
has_events = self._has_events or self.engine._has_events
if has_events:
(
elem,
- distilled_params,
+ distilled_parameters,
event_multiparams,
event_params,
) = self._invoke_before_exec_event(
- elem, distilled_params, execution_options
+ elem, distilled_parameters, execution_options
)
- if distilled_params:
+ if distilled_parameters:
# ensure we don't retain a link to the view object for keys()
# which links to the values, which we don't want to cache
- keys = sorted(distilled_params[0])
- for_executemany = len(distilled_params) > 1
+ keys = sorted(distilled_parameters[0])
+ for_executemany = len(distilled_parameters) > 1
else:
keys = []
for_executemany = False
@@ -1484,10 +1300,10 @@ class Connection(Connectable):
dialect,
dialect.execution_ctx_cls._init_compiled,
compiled_sql,
- distilled_params,
+ distilled_parameters,
execution_options,
compiled_sql,
- distilled_params,
+ distilled_parameters,
elem,
extracted_params,
cache_hit=cache_hit,
@@ -1506,8 +1322,7 @@ class Connection(Connectable):
def _execute_compiled(
self,
compiled,
- multiparams,
- params,
+ distilled_parameters,
execution_options=_EMPTY_EXECUTION_OPTS,
):
"""Execute a sql.Compiled object.
@@ -1519,12 +1334,11 @@ class Connection(Connectable):
execution_options = compiled.execution_options.merge_with(
self._execution_options, execution_options
)
- distilled_parameters = _distill_params(self, multiparams, params)
if self._has_events or self.engine._has_events:
(
compiled,
- distilled_params,
+ distilled_parameters,
event_multiparams,
event_params,
) = self._invoke_before_exec_event(
@@ -1555,66 +1369,6 @@ class Connection(Connectable):
)
return ret
- def _exec_driver_sql(
- self, statement, multiparams, params, execution_options, future
- ):
-
- execution_options = self._execution_options.merge_with(
- execution_options
- )
-
- distilled_parameters = _distill_params(self, multiparams, params)
-
- if not future:
- if self._has_events or self.engine._has_events:
- (
- statement,
- distilled_params,
- event_multiparams,
- event_params,
- ) = self._invoke_before_exec_event(
- statement, distilled_parameters, execution_options
- )
-
- dialect = self.dialect
- ret = self._execute_context(
- dialect,
- dialect.execution_ctx_cls._init_statement,
- statement,
- distilled_parameters,
- execution_options,
- statement,
- distilled_parameters,
- )
-
- if not future:
- if self._has_events or self.engine._has_events:
- self.dispatch.after_execute(
- self,
- statement,
- event_multiparams,
- event_params,
- execution_options,
- ret,
- )
- return ret
-
- def _execute_20(
- self,
- statement,
- parameters=None,
- execution_options=_EMPTY_EXECUTION_OPTS,
- ):
- args_10style, kwargs_10style = _distill_params_20(parameters)
- try:
- meth = statement._execute_on_connection
- except AttributeError as err:
- util.raise_(
- exc.ObjectNotExecutableError(statement), replace_context=err
- )
- else:
- return meth(self, args_10style, kwargs_10style, execution_options)
-
def exec_driver_sql(
self, statement, parameters=None, execution_options=None
):
@@ -1666,16 +1420,25 @@ class Connection(Connectable):
"""
- args_10style, kwargs_10style = _distill_params_20(parameters)
+ distilled_parameters = _distill_raw_params(parameters)
- return self._exec_driver_sql(
+ execution_options = self._execution_options.merge_with(
+ execution_options
+ )
+
+ dialect = self.dialect
+ ret = self._execute_context(
+ dialect,
+ dialect.execution_ctx_cls._init_statement,
statement,
- args_10style,
- kwargs_10style,
+ distilled_parameters,
execution_options,
- future=True,
+ statement,
+ distilled_parameters,
)
+ return ret
+
def _execute_context(
self,
dialect,
@@ -1689,12 +1452,6 @@ class Connection(Connectable):
"""Create an :class:`.ExecutionContext` and execute, returning
a :class:`_engine.CursorResult`."""
- if self.__branch_from:
- # if this is a "branched" connection, do everything in terms
- # of the "root" connection, *except* for .close(), which is
- # the only feature that branching provides
- self = self.__branch_from
-
try:
conn = self._dbapi_connection
if conn is None:
@@ -1724,7 +1481,7 @@ class Connection(Connectable):
elif self._trans_context_manager:
TransactionalContext._trans_ctx_check(self)
- if self._is_future and self._transaction is None:
+ if self._transaction is None:
self._autobegin()
context.pre_exec()
@@ -1819,16 +1576,6 @@ class Connection(Connectable):
result = context._setup_result_proxy()
- if not self._is_future:
-
- if (
- # usually we're in a transaction so avoid relatively
- # expensive / legacy should_autocommit call
- self._transaction is None
- and context.should_autocommit
- ):
- self._commit_impl(autocommit=True)
-
except BaseException as e:
self._handle_dbapi_exception(
e, statement, parameters, cursor, context
@@ -2006,7 +1753,14 @@ class Connection(Connectable):
if cursor:
self._safe_close_cursor(cursor)
with util.safe_reraise(warn_only=True):
- self._autorollback()
+ # "autorollback" was mostly relevant in 1.x series.
+ # It's very unlikely to reach here, as the connection
+ # does autobegin so when we are here, we are usually
+ # in an explicit / semi-explicit transaction.
+ # however we have a test which manufactures this
+ # scenario in any case using an event handler.
+ if not self.in_transaction():
+ self._rollback_impl()
if newraise:
util.raise_(newraise, with_traceback=exc_info[2], from_=e)
@@ -2097,87 +1851,6 @@ class Connection(Connectable):
"""
visitorcallable(self.dialect, self, **kwargs).traverse_single(element)
- @util.deprecated(
- "1.4",
- "The :meth:`_engine.Connection.transaction` "
- "method is deprecated and will be "
- "removed in a future release. Use the :meth:`_engine.Engine.begin` "
- "context manager instead.",
- )
- def transaction(self, callable_, *args, **kwargs):
- r"""Execute the given function within a transaction boundary.
-
- The function is passed this :class:`_engine.Connection`
- as the first argument, followed by the given \*args and \**kwargs,
- e.g.::
-
- def do_something(conn, x, y):
- conn.execute(text("some statement"), {'x':x, 'y':y})
-
- conn.transaction(do_something, 5, 10)
-
- The operations inside the function are all invoked within the
- context of a single :class:`.Transaction`.
- Upon success, the transaction is committed. If an
- exception is raised, the transaction is rolled back
- before propagating the exception.
-
- .. note::
-
- The :meth:`.transaction` method is superseded by
- the usage of the Python ``with:`` statement, which can
- be used with :meth:`_engine.Connection.begin`::
-
- with conn.begin():
- conn.execute(text("some statement"), {'x':5, 'y':10})
-
- As well as with :meth:`_engine.Engine.begin`::
-
- with engine.begin() as conn:
- conn.execute(text("some statement"), {'x':5, 'y':10})
-
- .. seealso::
-
- :meth:`_engine.Engine.begin` - engine-level transactional
- context
-
- :meth:`_engine.Engine.transaction` - engine-level version of
- :meth:`_engine.Connection.transaction`
-
- """
-
- kwargs["_sa_skip_warning"] = True
- trans = self.begin()
- try:
- ret = self.run_callable(callable_, *args, **kwargs)
- trans.commit()
- return ret
- except:
- with util.safe_reraise():
- trans.rollback()
-
- @util.deprecated(
- "1.4",
- "The :meth:`_engine.Connection.run_callable` "
- "method is deprecated and will "
- "be removed in a future release. Invoke the callable function "
- "directly, passing the Connection.",
- )
- def run_callable(self, callable_, *args, **kwargs):
- r"""Given a callable object or function, execute it, passing
- a :class:`_engine.Connection` as the first argument.
-
- The given \*args and \**kwargs are passed subsequent
- to the :class:`_engine.Connection` argument.
-
- This function, along with :meth:`_engine.Engine.run_callable`,
- allows a function to be run with a :class:`_engine.Connection`
- or :class:`_engine.Engine` object without the need to know
- which one is being dealt with.
-
- """
- return callable_(self, *args, **kwargs)
-
class ExceptionContextImpl(ExceptionContext):
"""Implement the :class:`.ExceptionContext` interface."""
@@ -2250,23 +1923,6 @@ class Transaction(TransactionalContext):
def __init__(self, connection):
raise NotImplementedError()
- def _do_deactivate(self):
- """do whatever steps are necessary to set this transaction as
- "deactive", however leave this transaction object in place as far
- as the connection's state.
-
- for a "real" transaction this should roll back the transaction
- and ensure this transaction is no longer a reset agent.
-
- this is used for nesting of marker transactions where the marker
- can set the "real" transaction as rolled back, however it stays
- in place.
-
- for 2.0 we hope to remove this nesting feature.
-
- """
- raise NotImplementedError()
-
@property
def _deactivated_from_connection(self):
"""True if this transaction is totally deactivated from the connection
@@ -2357,70 +2013,6 @@ class Transaction(TransactionalContext):
return not self._deactivated_from_connection
-class MarkerTransaction(Transaction):
- """A 'marker' transaction that is used for nested begin() calls.
-
- .. deprecated:: 1.4 future connection for 2.0 won't support this pattern.
-
- """
-
- __slots__ = ("connection", "_is_active", "_transaction")
-
- def __init__(self, connection):
- assert connection._transaction is not None
- if not connection._transaction.is_active:
- raise exc.InvalidRequestError(
- "the current transaction on this connection is inactive. "
- "Please issue a rollback first."
- )
-
- assert not connection._is_future
- util.warn_deprecated_20(
- "Calling .begin() when a transaction is already begun, creating "
- "a 'sub' transaction, is deprecated "
- "and will be removed in 2.0. See the documentation section "
- "'Migrating from the nesting pattern' for background on how "
- "to migrate from this pattern."
- )
-
- self.connection = connection
-
- if connection._trans_context_manager:
- TransactionalContext._trans_ctx_check(connection)
-
- if connection._nested_transaction is not None:
- self._transaction = connection._nested_transaction
- else:
- self._transaction = connection._transaction
- self._is_active = True
-
- @property
- def _deactivated_from_connection(self):
- return not self.is_active
-
- @property
- def is_active(self):
- return self._is_active and self._transaction.is_active
-
- def _deactivate(self):
- self._is_active = False
-
- def _do_close(self):
- # does not actually roll back the root
- self._deactivate()
-
- def _do_rollback(self):
- # does roll back the root
- if self._is_active:
- try:
- self._transaction._do_deactivate()
- finally:
- self._deactivate()
-
- def _do_commit(self):
- self._deactivate()
-
-
class RootTransaction(Transaction):
"""Represent the "root" transaction on a :class:`_engine.Connection`.
@@ -2469,27 +2061,6 @@ class RootTransaction(Transaction):
def _deactivated_from_connection(self):
return self.connection._transaction is not self
- def _do_deactivate(self):
- # called from a MarkerTransaction to cancel this root transaction.
- # the transaction stays in place as connection._transaction, but
- # is no longer active and is no longer the reset agent for the
- # pooled connection. the connection won't support a new begin()
- # until this transaction is explicitly closed, rolled back,
- # or committed.
-
- assert self.connection._transaction is self
-
- if self.is_active:
- self._connection_rollback_impl()
-
- # handle case where a savepoint was created inside of a marker
- # transaction that refers to a root. nested has to be cancelled
- # also.
- if self.connection._nested_transaction:
- self.connection._nested_transaction._cancel()
-
- self._deactivate_from_connection()
-
def _connection_begin_impl(self):
self.connection._begin_impl(self)
@@ -2629,9 +2200,6 @@ class NestedTransaction(Transaction):
if deactivate_from_connection:
assert self.connection._nested_transaction is not self
- def _do_deactivate(self):
- self._close_impl(False, False)
-
def _do_close(self):
self._close_impl(True, False)
@@ -2824,45 +2392,52 @@ class Engine(ConnectionEventsTarget, log.Identified):
* The logging configuration and logging_name is copied from the parent
:class:`_engine.Engine`.
+ .. TODO: the below autocommit link will have a more specific ref
+ for the example in an upcoming commit
+
The intent of the :meth:`_engine.Engine.execution_options` method is
- to implement "sharding" schemes where multiple :class:`_engine.Engine`
+ to implement schemes where multiple :class:`_engine.Engine`
objects refer to the same connection pool, but are differentiated
- by options that would be consumed by a custom event::
+ by options that affect some execution-level behavior for each
+ engine. One such example is breaking into separate "reader" and
+ "writer" :class:`_engine.Engine` instances, where one
+ :class:`_engine.Engine`
+ has a lower :term:`isolation level` setting configured or is even
+ transaction-disabled using "autocommit". An example of this
+ configuration is at :ref:`dbapi_autocommit`.
+
+ Another example is one that
+ uses a custom option ``shard_id`` which is consumed by an event
+ to change the current schema on a database connection::
+
+ from sqlalchemy import event
+ from sqlalchemy.engine import Engine
primary_engine = create_engine("mysql://")
shard1 = primary_engine.execution_options(shard_id="shard1")
shard2 = primary_engine.execution_options(shard_id="shard2")
- Above, the ``shard1`` engine serves as a factory for
- :class:`_engine.Connection`
- objects that will contain the execution option
- ``shard_id=shard1``, and ``shard2`` will produce
- :class:`_engine.Connection`
- objects that contain the execution option ``shard_id=shard2``.
-
- An event handler can consume the above execution option to perform
- a schema switch or other operation, given a connection. Below
- we emit a MySQL ``use`` statement to switch databases, at the same
- time keeping track of which database we've established using the
- :attr:`_engine.Connection.info` dictionary,
- which gives us a persistent
- storage space that follows the DBAPI connection::
-
- from sqlalchemy import event
- from sqlalchemy.engine import Engine
-
- shards = {"default": "base", shard_1: "db1", "shard_2": "db2"}
+ shards = {"default": "base", "shard_1": "db1", "shard_2": "db2"}
@event.listens_for(Engine, "before_cursor_execute")
def _switch_shard(conn, cursor, stmt,
params, context, executemany):
- shard_id = conn._execution_options.get('shard_id', "default")
+ shard_id = conn.get_execution_options().get('shard_id', "default")
current_shard = conn.info.get("current_shard", None)
if current_shard != shard_id:
cursor.execute("use %s" % shards[shard_id])
conn.info["current_shard"] = shard_id
+ The above recipe illustrates two :class:`_engine.Engine` objects that
+ will each serve as factories for :class:`_engine.Connection` objects
+ that have pre-established "shard_id" execution options present. A
+ :meth:`_events.ConnectionEvents.before_cursor_execute` event handler
+ then interprets this execution option to emit a MySQL ``use`` statement
+ to switch databases before a statement execution, while at the same
+ time keeping track of which database we've established using the
+ :attr:`_engine.Connection.info` dictionary.
+
.. seealso::
:meth:`_engine.Connection.execution_options`
@@ -2876,7 +2451,7 @@ class Engine(ConnectionEventsTarget, log.Identified):
:meth:`_engine.Engine.get_execution_options`
- """
+ """ # noqa E501
return self._option_cls(self, opt)
def get_execution_options(self):
@@ -2938,12 +2513,6 @@ class Engine(ConnectionEventsTarget, log.Identified):
self.pool = self.pool.recreate()
self.dispatch.engine_disposed(self)
- def _execute_default(
- self, default, multiparams=(), params=util.EMPTY_DICT
- ):
- with self.connect() as conn:
- return conn._execute_default(default, multiparams, params)
-
@contextlib.contextmanager
def _optional_conn_ctx_manager(self, connection=None):
if connection is None:
@@ -2952,21 +2521,7 @@ class Engine(ConnectionEventsTarget, log.Identified):
else:
yield connection
- class _trans_ctx(object):
- def __init__(self, conn, transaction):
- self.conn = conn
- self.transaction = transaction
-
- def __enter__(self):
- self.transaction.__enter__()
- return self.conn
-
- def __exit__(self, type_, value, traceback):
- try:
- self.transaction.__exit__(type_, value, traceback)
- finally:
- self.conn.close()
-
+ @util.contextmanager
def begin(self):
"""Return a context manager delivering a :class:`_engine.Connection`
with a :class:`.Transaction` established.
@@ -2993,88 +2548,9 @@ class Engine(ConnectionEventsTarget, log.Identified):
for a particular :class:`_engine.Connection`.
"""
- conn = self.connect()
- try:
- trans = conn.begin()
- except:
- with util.safe_reraise():
- conn.close()
- return Engine._trans_ctx(conn, trans)
-
- @util.deprecated(
- "1.4",
- "The :meth:`_engine.Engine.transaction` "
- "method is deprecated and will be "
- "removed in a future release. Use the :meth:`_engine.Engine.begin` "
- "context "
- "manager instead.",
- )
- def transaction(self, callable_, *args, **kwargs):
- r"""Execute the given function within a transaction boundary.
-
- The function is passed a :class:`_engine.Connection` newly procured
- from :meth:`_engine.Engine.connect` as the first argument,
- followed by the given \*args and \**kwargs.
-
- e.g.::
-
- def do_something(conn, x, y):
- conn.execute(text("some statement"), {'x':x, 'y':y})
-
- engine.transaction(do_something, 5, 10)
-
- The operations inside the function are all invoked within the
- context of a single :class:`.Transaction`.
- Upon success, the transaction is committed. If an
- exception is raised, the transaction is rolled back
- before propagating the exception.
-
- .. note::
-
- The :meth:`.transaction` method is superseded by
- the usage of the Python ``with:`` statement, which can
- be used with :meth:`_engine.Engine.begin`::
-
- with engine.begin() as conn:
- conn.execute(text("some statement"), {'x':5, 'y':10})
-
- .. seealso::
-
- :meth:`_engine.Engine.begin` - engine-level transactional
- context
-
- :meth:`_engine.Connection.transaction`
- - connection-level version of
- :meth:`_engine.Engine.transaction`
-
- """
- kwargs["_sa_skip_warning"] = True
- with self.connect() as conn:
- return conn.transaction(callable_, *args, **kwargs)
-
- @util.deprecated(
- "1.4",
- "The :meth:`_engine.Engine.run_callable` "
- "method is deprecated and will be "
- "removed in a future release. Use the :meth:`_engine.Engine.begin` "
- "context manager instead.",
- )
- def run_callable(self, callable_, *args, **kwargs):
- r"""Given a callable object or function, execute it, passing
- a :class:`_engine.Connection` as the first argument.
-
- The given \*args and \**kwargs are passed subsequent
- to the :class:`_engine.Connection` argument.
-
- This function, along with :meth:`_engine.Connection.run_callable`,
- allows a function to be run with a :class:`_engine.Connection`
- or :class:`_engine.Engine` object without the need to know
- which one is being dealt with.
-
- """
- kwargs["_sa_skip_warning"] = True
with self.connect() as conn:
- return conn.run_callable(callable_, *args, **kwargs)
+ with conn.begin():
+ yield conn
def _run_ddl_visitor(self, visitorcallable, element, **kwargs):
with self.begin() as conn:
@@ -3083,76 +2559,29 @@ class Engine(ConnectionEventsTarget, log.Identified):
def connect(self):
"""Return a new :class:`_engine.Connection` object.
- The :class:`_engine.Connection` object is a facade that uses a DBAPI
- connection internally in order to communicate with the database. This
- connection is procured from the connection-holding :class:`_pool.Pool`
- referenced by this :class:`_engine.Engine`. When the
- :meth:`_engine.Connection.close` method of the
- :class:`_engine.Connection` object
- is called, the underlying DBAPI connection is then returned to the
- connection pool, where it may be used again in a subsequent call to
- :meth:`_engine.Engine.connect`.
+ The :class:`_engine.Connection` acts as a Python context manager, so
+ the typical use of this method looks like::
- """
-
- return self._connection_cls(self)
+ with engine.connect() as connection:
+ connection.execute(text("insert into table values ('foo')"))
+ connection.commit()
- @util.deprecated(
- "1.4",
- "The :meth:`_engine.Engine.table_names` "
- "method is deprecated and will be "
- "removed in a future release. Please refer to "
- ":meth:`_reflection.Inspector.get_table_names`.",
- )
- def table_names(self, schema=None, connection=None):
- """Return a list of all table names available in the database.
-
- :param schema: Optional, retrieve names from a non-default schema.
-
- :param connection: Optional, use a specified connection.
- """
- with self._optional_conn_ctx_manager(connection) as conn:
- insp = inspection.inspect(conn)
- return insp.get_table_names(schema)
-
- @util.deprecated(
- "1.4",
- "The :meth:`_engine.Engine.has_table` "
- "method is deprecated and will be "
- "removed in a future release. Please refer to "
- ":meth:`_reflection.Inspector.has_table`.",
- )
- def has_table(self, table_name, schema=None):
- """Return True if the given backend has a table of the given name.
+ Where above, after the block is completed, the connection is "closed"
+ and its underlying DBAPI resources are returned to the connection pool.
+ This also has the effect of rolling back any transaction that
+ was explicitly begun or was begun via autobegin, and will
+ emit the :meth:`_events.ConnectionEvents.rollback` event if one was
+ started and is still in progress.
.. seealso::
- :ref:`metadata_reflection_inspector` - detailed schema inspection
- using the :class:`_reflection.Inspector` interface.
-
- :class:`.quoted_name` - used to pass quoting information along
- with a schema identifier.
+ :meth:`_engine.Engine.begin`
"""
- with self._optional_conn_ctx_manager(None) as conn:
- insp = inspection.inspect(conn)
- return insp.has_table(table_name, schema=schema)
- def _wrap_pool_connect(self, fn, connection):
- dialect = self.dialect
- try:
- return fn()
- except dialect.dbapi.Error as e:
- if connection is None:
- Connection._handle_dbapi_exception_noconnection(
- e, dialect, self
- )
- else:
- util.raise_(
- sys.exc_info()[1], with_traceback=sys.exc_info()[2]
- )
+ return self._connection_cls(self)
- def raw_connection(self, _connection=None):
+ def raw_connection(self):
"""Return a "raw" DBAPI connection from the connection pool.
The returned object is a proxied version of the DBAPI
@@ -3174,7 +2603,7 @@ class Engine(ConnectionEventsTarget, log.Identified):
:ref:`dbapi_connections`
"""
- return self._wrap_pool_connect(self.pool.connect, _connection)
+ return self.pool.connect()
class OptionEngineMixin(object):
diff --git a/lib/sqlalchemy/engine/create.py b/lib/sqlalchemy/engine/create.py
index b9e111647..6e5a07098 100644
--- a/lib/sqlalchemy/engine/create.py
+++ b/lib/sqlalchemy/engine/create.py
@@ -230,11 +230,21 @@ def create_engine(url, **kwargs):
be applied to all connections. See
:meth:`~sqlalchemy.engine.Connection.execution_options`
- :param future: Use the 2.0 style :class:`_future.Engine` and
- :class:`_future.Connection` API.
+ :param future: Use the 2.0 style :class:`_engine.Engine` and
+ :class:`_engine.Connection` API.
+
+ As of SQLAlchemy 2.0, this parameter is present for backwards
+ compatibility only and must remain at its default value of ``True``.
+
+ The :paramref:`_sa.create_engine.future` parameter will be
+ deprecated in a subsequent 2.x release and eventually removed.
.. versionadded:: 1.4
+ .. versionchanged:: 2.0 All :class:`_engine.Engine` objects are
+ "future" style engines and there is no longer a ``future=False``
+ mode of operation.
+
.. seealso::
:ref:`migration_20_toplevel`
@@ -613,14 +623,13 @@ def create_engine(url, **kwargs):
pool._dialect = dialect
# create engine.
- if pop_kwarg("future", False):
- from sqlalchemy import future
-
- default_engine_class = future.Engine
- else:
- default_engine_class = base.Engine
+ if not pop_kwarg("future", True):
+ raise exc.ArgumentError(
+ "The 'future' parameter passed to "
+ "create_engine() may only be set to True."
+ )
- engineclass = kwargs.pop("_future_engine_class", default_engine_class)
+ engineclass = base.Engine
engine_args = {}
for k in util.get_cls_kwargs(engineclass):
@@ -630,7 +639,6 @@ def create_engine(url, **kwargs):
# internal flags used by the test suite for instrumenting / proxying
# engines with mocks etc.
_initialize = kwargs.pop("_initialize", True)
- _wrap_do_on_connect = kwargs.pop("_wrap_do_on_connect", None)
# all kwargs should be consumed
if kwargs:
@@ -652,8 +660,6 @@ def create_engine(url, **kwargs):
if _initialize:
do_on_connect = dialect.on_connect_url(u)
if do_on_connect:
- if _wrap_do_on_connect:
- do_on_connect = _wrap_do_on_connect(do_on_connect)
def on_connect(dbapi_connection, connection_record):
do_on_connect(dbapi_connection)
@@ -668,6 +674,9 @@ def create_engine(url, **kwargs):
# reconnecting will be a reentrant condition, so if the
# connection goes away, Connection is then closed
_allow_revalidate=False,
+ # dont trigger the autobegin sequence
+ # within the up front dialect checks
+ _allow_autobegin=False,
)
c._execution_options = util.EMPTY_DICT
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index b6dae6abc..9a59250e9 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -33,10 +33,6 @@ from ..sql import compiler
from ..sql import expression
from ..sql.elements import quoted_name
-AUTOCOMMIT_REGEXP = re.compile(
- r"\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER)", re.I | re.UNICODE
-)
-
# When we're handed literal SQL, ensure it's a SELECT query
SERVER_SIDE_CURSOR_RE = re.compile(r"\s*SELECT", re.I | re.UNICODE)
@@ -112,16 +108,10 @@ class DefaultDialect(interfaces.Dialect):
# *not* the FLOAT type however.
supports_native_decimal = False
- if util.py3k:
- supports_unicode_statements = True
- supports_unicode_binds = True
- returns_unicode_strings = sqltypes.String.RETURNS_UNICODE
- description_encoding = None
- else:
- supports_unicode_statements = False
- supports_unicode_binds = False
- returns_unicode_strings = sqltypes.String.RETURNS_UNKNOWN
- description_encoding = "use_encoding"
+ supports_unicode_statements = True
+ supports_unicode_binds = True
+ returns_unicode_strings = sqltypes.String.RETURNS_UNICODE
+ description_encoding = None
name = "default"
@@ -401,15 +391,6 @@ class DefaultDialect(interfaces.Dialect):
except NotImplementedError:
self.default_isolation_level = None
- if self.returns_unicode_strings is sqltypes.String.RETURNS_UNKNOWN:
- if util.py3k:
- raise exc.InvalidRequestError(
- "RETURNS_UNKNOWN is unsupported in Python 3"
- )
- self.returns_unicode_strings = self._check_unicode_returns(
- connection
- )
-
if (
self.description_encoding is not None
and self._check_unicode_description(connection)
@@ -463,60 +444,6 @@ class DefaultDialect(interfaces.Dialect):
"""
return self.get_isolation_level(dbapi_conn)
- def _check_unicode_returns(self, connection, additional_tests=None):
- cast_to = util.text_type
-
- if self.positional:
- parameters = self.execute_sequence_format()
- else:
- parameters = {}
-
- def check_unicode(test):
- statement = cast_to(expression.select(test).compile(dialect=self))
- try:
- cursor = connection.connection.cursor()
- connection._cursor_execute(cursor, statement, parameters)
- row = cursor.fetchone()
- cursor.close()
- except exc.DBAPIError as de:
- # note that _cursor_execute() will have closed the cursor
- # if an exception is thrown.
- util.warn(
- "Exception attempting to "
- "detect unicode returns: %r" % de
- )
- return False
- else:
- return isinstance(row[0], util.text_type)
-
- tests = [
- # detect plain VARCHAR
- expression.cast(
- expression.literal_column("'test plain returns'"),
- sqltypes.VARCHAR(60),
- ),
- # detect if there's an NVARCHAR type with different behavior
- # available
- expression.cast(
- expression.literal_column("'test unicode returns'"),
- sqltypes.Unicode(60),
- ),
- ]
-
- if additional_tests:
- tests += additional_tests
-
- results = {check_unicode(test) for test in tests}
-
- if results.issuperset([True, False]):
- return sqltypes.String.RETURNS_CONDITIONAL
- else:
- return (
- sqltypes.String.RETURNS_UNICODE
- if results == {True}
- else sqltypes.String.RETURNS_BYTES
- )
-
def _check_unicode_description(self, connection):
cast_to = util.text_type
@@ -580,11 +507,10 @@ class DefaultDialect(interfaces.Dialect):
)
@event.listens_for(engine, "engine_connect")
- def set_connection_characteristics(connection, branch):
- if not branch:
- self._set_connection_characteristics(
- connection, characteristics
- )
+ def set_connection_characteristics(connection):
+ self._set_connection_characteristics(
+ connection, characteristics
+ )
def set_connection_execution_options(self, connection, opts):
supported_names = set(self.connection_characteristics).intersection(
@@ -610,24 +536,13 @@ class DefaultDialect(interfaces.Dialect):
if obj.transactional
]
if trans_objs:
- if connection._is_future:
- raise exc.InvalidRequestError(
- "This connection has already initialized a SQLAlchemy "
- "Transaction() object via begin() or autobegin; "
- "%s may not be altered unless rollback() or commit() "
- "is called first."
- % (", ".join(name for name, obj in trans_objs))
- )
- else:
- util.warn(
- "Connection is already established with a "
- "Transaction; "
- "setting %s may implicitly rollback or "
- "commit "
- "the existing transaction, or have no effect until "
- "next transaction"
- % (", ".join(name for name, obj in trans_objs))
- )
+ raise exc.InvalidRequestError(
+ "This connection has already initialized a SQLAlchemy "
+ "Transaction() object via begin() or autobegin; "
+ "%s may not be altered unless rollback() or commit() "
+ "is called first."
+ % (", ".join(name for name, obj in trans_objs))
+ )
dbapi_connection = connection.connection.dbapi_connection
for name, characteristic, value in characteristic_values:
@@ -1175,21 +1090,6 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
def no_parameters(self):
return self.execution_options.get("no_parameters", False)
- @util.memoized_property
- def should_autocommit(self):
- autocommit = self.execution_options.get(
- "autocommit",
- not self.compiled
- and self.statement
- and expression.PARSE_AUTOCOMMIT
- or False,
- )
-
- if autocommit is expression.PARSE_AUTOCOMMIT:
- return self.should_autocommit_text(self.unicode_statement)
- else:
- return autocommit
-
def _execute_scalar(self, stmt, type_, parameters=None):
"""Execute a string statement on the current cursor, returning a
scalar result.
@@ -1232,16 +1132,9 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
return proc(r)
return r
- @property
+ @util.memoized_property
def connection(self):
- conn = self.root_connection
- if conn._is_future:
- return conn
- else:
- return conn._branch()
-
- def should_autocommit_text(self, statement):
- return AUTOCOMMIT_REGEXP.match(statement)
+ return self.root_connection
def _use_server_side_cursor(self):
if not self.dialect.supports_server_side_cursors:
@@ -1522,7 +1415,9 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
if self.isddl or self.is_text:
return
- inputsizes = self.compiled._get_set_input_sizes_lookup(
+ compiled = self.compiled
+
+ inputsizes = compiled._get_set_input_sizes_lookup(
include_types=self.include_set_input_sizes,
exclude_types=self.exclude_set_input_sizes,
)
@@ -1530,30 +1425,31 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
if inputsizes is None:
return
- if self.dialect._has_events:
+ dialect = self.dialect
+
+ if dialect._has_events:
inputsizes = dict(inputsizes)
- self.dialect.dispatch.do_setinputsizes(
+ dialect.dispatch.do_setinputsizes(
inputsizes, self.cursor, self.statement, self.parameters, self
)
- has_escaped_names = bool(self.compiled.escaped_bind_names)
+ has_escaped_names = bool(compiled.escaped_bind_names)
if has_escaped_names:
- escaped_bind_names = self.compiled.escaped_bind_names
+ escaped_bind_names = compiled.escaped_bind_names
- if self.dialect.positional:
+ if dialect.positional:
items = [
- (key, self.compiled.binds[key])
- for key in self.compiled.positiontup
+ (key, compiled.binds[key]) for key in compiled.positiontup
]
else:
items = [
(key, bindparam)
- for bindparam, key in self.compiled.bind_names.items()
+ for bindparam, key in compiled.bind_names.items()
]
generic_inputsizes = []
for key, bindparam in items:
- if bindparam in self.compiled.literal_execute_params:
+ if bindparam in compiled.literal_execute_params:
continue
if key in self._expanded_parameters:
@@ -1601,9 +1497,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
(escaped_name, dbtype, bindparam.type)
)
try:
- self.dialect.do_set_input_sizes(
- self.cursor, generic_inputsizes, self
- )
+ dialect.do_set_input_sizes(self.cursor, generic_inputsizes, self)
except BaseException as e:
self.root_connection._handle_dbapi_exception(
e, None, None, None, self
diff --git a/lib/sqlalchemy/engine/events.py b/lib/sqlalchemy/engine/events.py
index effebb4cb..cfb616aff 100644
--- a/lib/sqlalchemy/engine/events.py
+++ b/lib/sqlalchemy/engine/events.py
@@ -336,10 +336,7 @@ class ConnectionEvents(event.Events):
The hook is called while the cursor from the failed operation
(if any) is still open and accessible. Special cleanup operations
can be called on this cursor; SQLAlchemy will attempt to close
- this cursor subsequent to this hook being invoked. If the connection
- is in "autocommit" mode, the transaction also remains open within
- the scope of this hook; the rollback of the per-statement transaction
- also occurs after the hook is called.
+ this cursor subsequent to this hook being invoked.
.. note::
@@ -437,7 +434,10 @@ class ConnectionEvents(event.Events):
"""
- def engine_connect(self, conn, branch):
+ @event._legacy_signature(
+ "2.0", ["conn", "branch"], converter=lambda conn: (conn, False)
+ )
+ def engine_connect(self, conn):
"""Intercept the creation of a new :class:`_engine.Connection`.
This event is called typically as the direct result of calling
@@ -461,19 +461,9 @@ class ConnectionEvents(event.Events):
events within the lifespan
of a single :class:`_engine.Connection` object, if that
:class:`_engine.Connection`
- is invalidated and re-established. There can also be multiple
- :class:`_engine.Connection`
- objects generated for the same already-checked-out
- DBAPI connection, in the case that a "branch" of a
- :class:`_engine.Connection`
- is produced.
+ is invalidated and re-established.
:param conn: :class:`_engine.Connection` object.
- :param branch: if True, this is a "branch" of an existing
- :class:`_engine.Connection`. A branch is generated within the course
- of a statement execution to invoke supplemental statements, most
- typically to pre-execute a SELECT of a default value for the purposes
- of an INSERT statement.
.. seealso::
diff --git a/lib/sqlalchemy/engine/interfaces.py b/lib/sqlalchemy/engine/interfaces.py
index 3fd245e44..38d2c7a57 100644
--- a/lib/sqlalchemy/engine/interfaces.py
+++ b/lib/sqlalchemy/engine/interfaces.py
@@ -550,15 +550,6 @@ class Dialect(object):
that transactions are implicit. This hook is provided for those
DBAPIs that might need additional help in this area.
- Note that :meth:`.Dialect.do_begin` is not called unless a
- :class:`.Transaction` object is in use. The
- :meth:`.Dialect.do_autocommit`
- hook is provided for DBAPIs that need some extra commands emitted
- after a commit in order to enter the next transaction, when the
- SQLAlchemy :class:`_engine.Connection`
- is used in its default "autocommit"
- mode.
-
:param dbapi_connection: a DBAPI connection, typically
proxied within a :class:`.ConnectionFairy`.
@@ -1356,9 +1347,6 @@ class ExecutionContext(object):
isupdate
True if the statement is an UPDATE.
- should_autocommit
- True if the statement is a "committable" statement.
-
prefetch_cols
a list of Column objects for which a client-side default
was fired off. Applies to inserts and updates.
@@ -1507,12 +1495,6 @@ class ExecutionContext(object):
raise NotImplementedError()
- def should_autocommit_text(self, statement):
- """Parse the given textual statement and return True if it refers to
- a "committable" statement"""
-
- raise NotImplementedError()
-
def lastrow_has_defaults(self):
"""Return True if the last INSERT or UPDATE row contained
inlined or database-side defaults.
diff --git a/lib/sqlalchemy/engine/util.py b/lib/sqlalchemy/engine/util.py
index 8eb0f1820..36691504c 100644
--- a/lib/sqlalchemy/engine/util.py
+++ b/lib/sqlalchemy/engine/util.py
@@ -31,110 +31,36 @@ def connection_memoize(key):
_no_tuple = ()
-_no_kw = util.immutabledict()
-def _distill_params(connection, multiparams, params):
- r"""Given arguments from the calling form \*multiparams, \**params,
- return a list of bind parameter structures, usually a list of
- dictionaries.
-
- In the case of 'raw' execution which accepts positional parameters,
- it may be a list of tuples or lists.
-
- """
-
- if not multiparams:
- if params:
- connection._warn_for_legacy_exec_format()
- return [params]
- else:
- return []
- elif len(multiparams) == 1:
- zero = multiparams[0]
- if isinstance(zero, (list, tuple)):
- if (
- not zero
- or hasattr(zero[0], "__iter__")
- and not hasattr(zero[0], "strip")
- ):
- # execute(stmt, [{}, {}, {}, ...])
- # execute(stmt, [(), (), (), ...])
- return zero
- else:
- # this is used by exec_driver_sql only, so a deprecation
- # warning would already be coming from passing a plain
- # textual statement with positional parameters to
- # execute().
- # execute(stmt, ("value", "value"))
- return [zero]
- elif hasattr(zero, "keys"):
- # execute(stmt, {"key":"value"})
- return [zero]
- else:
- connection._warn_for_legacy_exec_format()
- # execute(stmt, "value")
- return [[zero]]
- else:
- connection._warn_for_legacy_exec_format()
- if hasattr(multiparams[0], "__iter__") and not hasattr(
- multiparams[0], "strip"
+def _distill_params_20(params):
+ if params is None:
+ return _no_tuple
+ elif isinstance(params, (list, tuple)):
+ # collections_abc.MutableSequence): # avoid abc.__instancecheck__
+ if params and not isinstance(
+ params[0], (collections_abc.Mapping, tuple)
):
- return multiparams
- else:
- return [multiparams]
-
-
-def _distill_cursor_params(connection, multiparams, params):
- """_distill_params without any warnings. more appropriate for
- "cursor" params that can include tuple arguments, lists of tuples,
- etc.
-
- """
+ raise exc.ArgumentError(
+ "List argument must consist only of tuples or dictionaries"
+ )
- if not multiparams:
- if params:
- return [params]
- else:
- return []
- elif len(multiparams) == 1:
- zero = multiparams[0]
- if isinstance(zero, (list, tuple)):
- if (
- not zero
- or hasattr(zero[0], "__iter__")
- and not hasattr(zero[0], "strip")
- ):
- # execute(stmt, [{}, {}, {}, ...])
- # execute(stmt, [(), (), (), ...])
- return zero
- else:
- # this is used by exec_driver_sql only, so a deprecation
- # warning would already be coming from passing a plain
- # textual statement with positional parameters to
- # execute().
- # execute(stmt, ("value", "value"))
-
- return [zero]
- elif hasattr(zero, "keys"):
- # execute(stmt, {"key":"value"})
- return [zero]
- else:
- # execute(stmt, "value")
- return [[zero]]
+ return params
+ elif isinstance(
+ params,
+ (dict, immutabledict),
+ # only do abc.__instancecheck__ for Mapping after we've checked
+ # for plain dictionaries and would otherwise raise
+ ) or isinstance(params, collections_abc.Mapping):
+ return [params]
else:
- if hasattr(multiparams[0], "__iter__") and not hasattr(
- multiparams[0], "strip"
- ):
- return multiparams
- else:
- return [multiparams]
+ raise exc.ArgumentError("mapping or sequence expected for parameters")
-def _distill_params_20(params):
+def _distill_raw_params(params):
if params is None:
- return _no_tuple, _no_kw
- elif isinstance(params, list):
+ return _no_tuple
+ elif isinstance(params, (list,)):
# collections_abc.MutableSequence): # avoid abc.__instancecheck__
if params and not isinstance(
params[0], (collections_abc.Mapping, tuple)
@@ -143,14 +69,14 @@ def _distill_params_20(params):
"List argument must consist only of tuples or dictionaries"
)
- return (params,), _no_kw
+ return params
elif isinstance(
params,
(tuple, dict, immutabledict),
# only do abc.__instancecheck__ for Mapping after we've checked
# for plain dictionaries and would otherwise raise
) or isinstance(params, collections_abc.Mapping):
- return (params,), _no_kw
+ return [params]
else:
raise exc.ArgumentError("mapping or sequence expected for parameters")