summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/cextension/immutabledict.c2
-rw-r--r--lib/sqlalchemy/cextension/resultproxy.c4
-rw-r--r--lib/sqlalchemy/dialects/mssql/base.py2
-rw-r--r--lib/sqlalchemy/dialects/oracle/cx_oracle.py2
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py10
-rw-r--r--lib/sqlalchemy/dialects/postgresql/psycopg2.py4
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py10
-rw-r--r--lib/sqlalchemy/dialects/sqlite/pysqlite.py6
-rw-r--r--lib/sqlalchemy/engine/base.py18
-rw-r--r--lib/sqlalchemy/engine/default.py3
-rw-r--r--lib/sqlalchemy/engine/events.py30
-rw-r--r--lib/sqlalchemy/engine/url.py37
-rw-r--r--lib/sqlalchemy/ext/asyncio/base.py5
-rw-r--r--lib/sqlalchemy/ext/asyncio/engine.py64
-rw-r--r--lib/sqlalchemy/ext/asyncio/session.py190
-rw-r--r--lib/sqlalchemy/ext/mypy/decl_class.py3
-rw-r--r--lib/sqlalchemy/ext/mypy/infer.py29
-rw-r--r--lib/sqlalchemy/ext/mypy/names.py5
-rw-r--r--lib/sqlalchemy/ext/mypy/util.py4
-rw-r--r--lib/sqlalchemy/orm/context.py21
-rw-r--r--lib/sqlalchemy/orm/decl_api.py4
-rw-r--r--lib/sqlalchemy/orm/path_registry.py2
-rw-r--r--lib/sqlalchemy/orm/persistence.py2
-rw-r--r--lib/sqlalchemy/orm/relationships.py2
-rw-r--r--lib/sqlalchemy/orm/session.py90
-rw-r--r--lib/sqlalchemy/orm/strategies.py10
-rw-r--r--lib/sqlalchemy/sql/compiler.py2
-rw-r--r--lib/sqlalchemy/sql/ddl.py2
-rw-r--r--lib/sqlalchemy/sql/lambdas.py2
-rw-r--r--lib/sqlalchemy/sql/selectable.py2
-rw-r--r--lib/sqlalchemy/testing/requirements.py8
-rw-r--r--lib/sqlalchemy/testing/warnings.py7
-rw-r--r--lib/sqlalchemy/util/compat.py3
33 files changed, 492 insertions, 93 deletions
diff --git a/lib/sqlalchemy/cextension/immutabledict.c b/lib/sqlalchemy/cextension/immutabledict.c
index 0b1003d63..1188dcd2b 100644
--- a/lib/sqlalchemy/cextension/immutabledict.c
+++ b/lib/sqlalchemy/cextension/immutabledict.c
@@ -173,7 +173,7 @@ ImmutableDict_union(PyObject *self, PyObject *args, PyObject *kw)
}
if (!PyDict_CheckExact(arg_dict)) {
- // if we didnt get a dict, and got lists of tuples or
+ // if we didn't get a dict, and got lists of tuples or
// keyword args, make a dict
arg_dict = PyObject_Call((PyObject *) &PyDict_Type, args, kw);
if (arg_dict == NULL) {
diff --git a/lib/sqlalchemy/cextension/resultproxy.c b/lib/sqlalchemy/cextension/resultproxy.c
index dc828698c..2de672f22 100644
--- a/lib/sqlalchemy/cextension/resultproxy.c
+++ b/lib/sqlalchemy/cextension/resultproxy.c
@@ -442,7 +442,7 @@ BaseRow_subscript_impl(BaseRow *self, PyObject *key, int asmapping)
// support negative indexes. We can also call PySequence_GetItem,
// but here we can stay with the simpler tuple protocol
- // rather than the seqeunce protocol which has to check for
+ // rather than the sequence protocol which has to check for
// __getitem__ methods etc.
if (index < 0)
index += (long)BaseRow_length(self);
@@ -467,7 +467,7 @@ BaseRow_subscript_impl(BaseRow *self, PyObject *key, int asmapping)
// support negative indexes. We can also call PySequence_GetItem,
// but here we can stay with the simpler tuple protocol
- // rather than the seqeunce protocol which has to check for
+ // rather than the sequence protocol which has to check for
// __getitem__ methods etc.
if (index < 0)
index += (long)BaseRow_length(self);
diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py
index 8607edeca..7946633eb 100644
--- a/lib/sqlalchemy/dialects/mssql/base.py
+++ b/lib/sqlalchemy/dialects/mssql/base.py
@@ -514,7 +514,7 @@ or embedded dots, use two sets of brackets::
.. versionchanged:: 1.2 the SQL Server dialect now treats brackets as
- identifier delimeters splitting the schema into separate database
+ identifier delimiters splitting the schema into separate database
and owner tokens, to allow dots within either name itself.
.. _legacy_schema_rendering:
diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
index 219ba82e4..aab2018bf 100644
--- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py
+++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py
@@ -108,7 +108,7 @@ Any cx_Oracle parameter value and/or constant may be passed, such as::
)
Note that the default value for ``encoding`` and ``nencoding`` was changed to
-"UTF-8" in cx_Oracle 8.0 so these parameters can be ommitted when using that
+"UTF-8" in cx_Oracle 8.0 so these parameters can be omitted when using that
version, or later.
Options consumed by the SQLAlchemy cx_Oracle dialect outside of the driver
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index d8e4d5d20..f33542ee8 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -1868,6 +1868,14 @@ class ENUM(sqltypes.NativeForEmulated, sqltypes.Enum):
be used to emit SQL to a target bind.
"""
+ native_enum = kw.pop("native_enum", None)
+ if native_enum is False:
+ util.warn(
+ "the native_enum flag does not apply to the "
+ "sqlalchemy.dialects.postgresql.ENUM datatype; this type "
+ "always refers to ENUM. Use sqlalchemy.types.Enum for "
+ "non-native enum."
+ )
self.create_type = kw.pop("create_type", True)
super(ENUM, self).__init__(*enums, **kw)
@@ -3425,7 +3433,7 @@ class PGDialect(default.DefaultDialect):
return bool(cursor.scalar())
def _get_server_version_info(self, connection):
- v = connection.exec_driver_sql("select version()").scalar()
+ v = connection.exec_driver_sql("select pg_catalog.version()").scalar()
m = re.match(
r".*(?:PostgreSQL|EnterpriseDB) "
r"(\d+)\.?(\d+)?(?:\.(\d+))?(?:\.\d+)?(?:devel|beta)?",
diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
index e28c01f11..c80198825 100644
--- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py
+++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py
@@ -50,7 +50,7 @@ may be passed to :func:`_sa.create_engine()`, and include the following:
* ``executemany_mode``, ``executemany_batch_page_size``,
``executemany_values_page_size``: Allows use of psycopg2
- extensions for optimizing "executemany"-stye queries. See the referenced
+ extensions for optimizing "executemany"-style queries. See the referenced
section below for details.
.. seealso::
@@ -1037,7 +1037,7 @@ class PGDialect_psycopg2(PGDialect):
"connection not open",
"could not receive data from server",
"could not send data to server",
- # psycopg2 client errors, psycopg2/conenction.h,
+ # psycopg2 client errors, psycopg2/connection.h,
# psycopg2/cursor.h
"connection already closed",
"cursor already closed",
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index dc5ebc3f0..c4a6bf8e9 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -1915,7 +1915,9 @@ class SQLiteDialect(default.DefaultDialect):
14,
)
- _isolation_lookup = {"READ UNCOMMITTED": 1, "SERIALIZABLE": 0}
+ _isolation_lookup = util.immutabledict(
+ {"READ UNCOMMITTED": 1, "SERIALIZABLE": 0}
+ )
def set_isolation_level(self, connection, level):
try:
@@ -1925,7 +1927,11 @@ class SQLiteDialect(default.DefaultDialect):
exc.ArgumentError(
"Invalid value '%s' for isolation_level. "
"Valid isolation levels for %s are %s"
- % (level, self.name, ", ".join(self._isolation_lookup))
+ % (
+ level,
+ self.name,
+ ", ".join(self._isolation_lookup),
+ )
),
replace_context=err,
)
diff --git a/lib/sqlalchemy/dialects/sqlite/pysqlite.py b/lib/sqlalchemy/dialects/sqlite/pysqlite.py
index 96a5351da..0f96e8830 100644
--- a/lib/sqlalchemy/dialects/sqlite/pysqlite.py
+++ b/lib/sqlalchemy/dialects/sqlite/pysqlite.py
@@ -492,6 +492,12 @@ class SQLiteDialect_pysqlite(SQLiteDialect):
def _get_server_version_info(self, connection):
return self.dbapi.sqlite_version_info
+ _isolation_lookup = SQLiteDialect._isolation_lookup.union(
+ {
+ "AUTOCOMMIT": None,
+ }
+ )
+
def set_isolation_level(self, connection, level):
if hasattr(connection, "connection"):
dbapi_connection = connection.connection
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index a316f904f..25ced0343 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -1157,10 +1157,28 @@ class Connection(Connectable):
"""Executes and returns the first column of the first row.
The underlying result/cursor is closed after execution.
+
"""
return self.execute(object_, *multiparams, **params).scalar()
+ def scalars(self, object_, *multiparams, **params):
+ """Executes and returns a scalar result set, which yields scalar values
+ from the first column of each row.
+
+ This method is equivalent to calling :meth:`_engine.Connection.execute`
+ to receive a :class:`_result.Result` object, then invoking the
+ :meth:`_result.Result.scalars` method to produce a
+ :class:`_result.ScalarResult` instance.
+
+ :return: a :class:`_result.ScalarResult`
+
+ .. versionadded:: 1.4.24
+
+ """
+
+ return self.execute(object_, *multiparams, **params).scalars()
+
def execute(self, statement, *multiparams, **params):
r"""Executes a SQL statement construct and returns a
:class:`_engine.CursorResult`.
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index 8d6f40ff6..8bd8a121b 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -1835,8 +1835,9 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
# to avoid many calls of get_insert_default()/
# get_update_default()
for c in insert_prefetch:
- if c.default and c.default.is_scalar:
+ if c.default and not c.default.is_sequence and c.default.is_scalar:
scalar_defaults[c] = c.default.arg
+
for c in update_prefetch:
if c.onupdate and c.onupdate.is_scalar:
scalar_defaults[c] = c.onupdate.arg
diff --git a/lib/sqlalchemy/engine/events.py b/lib/sqlalchemy/engine/events.py
index f3775aed7..f091c7733 100644
--- a/lib/sqlalchemy/engine/events.py
+++ b/lib/sqlalchemy/engine/events.py
@@ -722,15 +722,27 @@ class DialectEvents(event.Events):
def do_connect(self, dialect, conn_rec, cargs, cparams):
"""Receive connection arguments before a connection is made.
- Return a DBAPI connection to halt further events from invoking;
- the returned connection will be used.
-
- Alternatively, the event can manipulate the cargs and/or cparams
- collections; cargs will always be a Python list that can be mutated
- in-place and cparams a Python dictionary. Return None to
- allow control to pass to the next event handler and ultimately
- to allow the dialect to connect normally, given the updated
- arguments.
+ This event is useful in that it allows the handler to manipulate the
+ cargs and/or cparams collections that control how the DBAPI
+ ``connect()`` function will be called. ``cargs`` will always be a
+ Python list that can be mutated in-place, and ``cparams`` a Python
+ dictionary that may also be mutated::
+
+ e = create_engine("postgresql+psycopg2://user@host/dbname")
+
+ @event.listens_for(e, 'do_connect')
+ def receive_do_connect(dialect, conn_rec, cargs, cparams):
+ cparams["password"] = "some_password"
+
+ The event hook may also be used to override the call to ``connect()``
+ entirely, by returning a non-``None`` DBAPI connection object::
+
+ e = create_engine("postgresql+psycopg2://user@host/dbname")
+
+ @event.listens_for(e, 'do_connect')
+ def receive_do_connect(dialect, conn_rec, cargs, cparams):
+ return psycopg2.connect(*cargs, **cparams)
+
.. versionadded:: 1.0.3
diff --git a/lib/sqlalchemy/engine/url.py b/lib/sqlalchemy/engine/url.py
index d72654c73..d91f06011 100644
--- a/lib/sqlalchemy/engine/url.py
+++ b/lib/sqlalchemy/engine/url.py
@@ -67,8 +67,13 @@ class URL(
* :attr:`_engine.URL.drivername`: database backend and driver name, such as
``postgresql+psycopg2``
* :attr:`_engine.URL.username`: username string
- * :attr:`_engine.URL.password`: password, which is normally a string but
- may also be any object that has a ``__str__()`` method.
+ * :attr:`_engine.URL.password`: password string, or object that includes
+ a ``__str__()`` method that produces a password.
+
+ .. note:: A password-producing object will be stringified only
+ **once** per :class:`_engine.Engine` object. For dynamic password
+ generation per connect, see :ref:`engines_dynamic_tokens`.
+
* :attr:`_engine.URL.host`: string hostname
* :attr:`_engine.URL.port`: integer port number
* :attr:`_engine.URL.database`: string database name
@@ -108,8 +113,13 @@ class URL(
correspond to a module in sqlalchemy/databases or a third party
plug-in.
:param username: The user name.
- :param password: database password. May be a string or an object that
- can be stringified with ``str()``.
+ :param password: database password. Is typically a string, but may
+ also be an object that can be stringified with ``str()``.
+
+ .. note:: A password-producing object will be stringified only
+ **once** per :class:`_engine.Engine` object. For dynamic password
+ generation per connect, see :ref:`engines_dynamic_tokens`.
+
:param host: The name of the host.
:param port: The port number.
:param database: The database name.
@@ -154,9 +164,6 @@ class URL(
@classmethod
def _assert_str(cls, v, paramname):
- if v is None:
- return v
-
if not isinstance(v, compat.string_types):
raise TypeError("%s must be a string" % paramname)
return v
@@ -655,7 +662,7 @@ class URL(
dialect_cls = entrypoint.get_dialect_cls(self)
return dialect_cls
- def translate_connect_args(self, names=[], **kw):
+ def translate_connect_args(self, names=None, **kw):
r"""Translate url attributes into a dictionary of connection arguments.
Returns attributes of this url (`host`, `database`, `username`,
@@ -669,6 +676,14 @@ class URL(
names, but correlates the name to the original positionally.
"""
+ if names is not None:
+ util.warn_deprecated(
+ "The `URL.translate_connect_args.name`s parameter is "
+ "deprecated. Please pass the "
+ "alternate names as kw arguments.",
+ "1.4",
+ )
+
translated = {}
attribute_names = ["host", "database", "username", "password", "port"]
for sname in attribute_names:
@@ -679,7 +694,11 @@ class URL(
else:
name = sname
if name is not None and getattr(self, sname, False):
- translated[name] = getattr(self, sname)
+ if sname == "password":
+ translated[name] = str(getattr(self, sname))
+ else:
+ translated[name] = getattr(self, sname)
+
return translated
diff --git a/lib/sqlalchemy/ext/asyncio/base.py b/lib/sqlalchemy/ext/asyncio/base.py
index 3f2c084f4..3f77f5500 100644
--- a/lib/sqlalchemy/ext/asyncio/base.py
+++ b/lib/sqlalchemy/ext/asyncio/base.py
@@ -8,6 +8,7 @@ from . import exc as async_exc
class ReversibleProxy:
# weakref.ref(async proxy object) -> weakref.ref(sync proxied object)
_proxy_objects = {}
+ __slots__ = ("__weakref__",)
def _assign_proxied(self, target):
if target is not None:
@@ -46,6 +47,8 @@ class ReversibleProxy:
class StartableContext(abc.ABC):
+ __slots__ = ()
+
@abc.abstractmethod
async def start(self, is_ctxmanager=False):
pass
@@ -68,6 +71,8 @@ class StartableContext(abc.ABC):
class ProxyComparable(ReversibleProxy):
+ __slots__ = ()
+
def __hash__(self):
return id(self)
diff --git a/lib/sqlalchemy/ext/asyncio/engine.py b/lib/sqlalchemy/ext/asyncio/engine.py
index f5c3bdca4..ab29438ed 100644
--- a/lib/sqlalchemy/ext/asyncio/engine.py
+++ b/lib/sqlalchemy/ext/asyncio/engine.py
@@ -9,6 +9,7 @@ from .base import ProxyComparable
from .base import StartableContext
from .result import AsyncResult
from ... import exc
+from ... import inspection
from ... import util
from ...engine import create_engine as _create_engine
from ...engine.base import NestedTransaction
@@ -80,6 +81,7 @@ class AsyncConnection(ProxyComparable, StartableContext, AsyncConnectable):
# create a new AsyncConnection that matches this one given only the
# "sync" elements.
__slots__ = (
+ "engine",
"sync_engine",
"sync_connection",
)
@@ -437,6 +439,47 @@ class AsyncConnection(ProxyComparable, StartableContext, AsyncConnectable):
result = await self.execute(statement, parameters, execution_options)
return result.scalar()
+ async def scalars(
+ self,
+ statement,
+ parameters=None,
+ execution_options=util.EMPTY_DICT,
+ ):
+ r"""Executes a SQL statement construct and returns a scalar objects.
+
+ This method is shorthand for invoking the
+ :meth:`_engine.Result.scalars` method after invoking the
+ :meth:`_future.Connection.execute` method. Parameters are equivalent.
+
+ :return: a :class:`_engine.ScalarResult` object.
+
+ .. versionadded:: 1.4.24
+
+ """
+ result = await self.execute(statement, parameters, execution_options)
+ return result.scalars()
+
+ async def stream_scalars(
+ self,
+ statement,
+ parameters=None,
+ execution_options=util.EMPTY_DICT,
+ ):
+ r"""Executes a SQL statement and returns a streaming scalar result
+ object.
+
+ This method is shorthand for invoking the
+ :meth:`_engine.AsyncResult.scalars` method after invoking the
+ :meth:`_future.Connection.stream` method. Parameters are equivalent.
+
+ :return: an :class:`_asyncio.AsyncScalarResult` object.
+
+ .. versionadded:: 1.4.24
+
+ """
+ result = await self.stream(statement, parameters, execution_options)
+ return result.scalars()
+
async def run_sync(self, fn, *arg, **kw):
"""Invoke the given sync callable passing self as the first argument.
@@ -709,3 +752,24 @@ def _get_sync_engine_or_connection(async_engine):
raise exc.ArgumentError(
"AsyncEngine expected, got %r" % async_engine
) from e
+
+
+@inspection._inspects(AsyncConnection)
+def _no_insp_for_async_conn_yet(subject):
+ raise exc.NoInspectionAvailable(
+ "Inspection on an AsyncConnection is currently not supported. "
+ "Please use ``run_sync`` to pass a callable where it's possible "
+ "to call ``inspect`` on the passed connection.",
+ code="xd3s",
+ )
+
+
+@inspection._inspects(AsyncEngine)
+def _no_insp_for_async_engine_xyet(subject):
+ raise exc.NoInspectionAvailable(
+ "Inspection on an AsyncEngine is currently not supported. "
+ "Please obtain a connection then use ``conn.run_sync`` to pass a "
+ "callable where it's possible to call ``inspect`` on the "
+ "passed connection.",
+ code="xd3s",
+ )
diff --git a/lib/sqlalchemy/ext/asyncio/session.py b/lib/sqlalchemy/ext/asyncio/session.py
index a10621eef..6e3ac5a90 100644
--- a/lib/sqlalchemy/ext/asyncio/session.py
+++ b/lib/sqlalchemy/ext/asyncio/session.py
@@ -14,6 +14,9 @@ from ...orm import Session
from ...orm import state as _instance_state
from ...util.concurrency import greenlet_spawn
+_EXECUTE_OPTIONS = util.immutabledict({"prebuffer_rows": True})
+_STREAM_OPTIONS = util.immutabledict({"stream_results": True})
+
@util.create_proxy_methods(
Session,
@@ -48,24 +51,41 @@ from ...util.concurrency import greenlet_spawn
class AsyncSession(ReversibleProxy):
"""Asyncio version of :class:`_orm.Session`.
+ The :class:`_asyncio.AsyncSession` is a proxy for a traditional
+ :class:`_orm.Session` instance.
.. versionadded:: 1.4
+ To use an :class:`_asyncio.AsyncSession` with custom :class:`_orm.Session`
+ implementations, see the
+ :paramref:`_asyncio.AsyncSession.sync_session_class` parameter.
+
+
"""
_is_asyncio = True
- __slots__ = (
- "binds",
- "bind",
- "sync_session",
- "_proxied",
- "_slots_dispatch",
- )
-
dispatch = None
- def __init__(self, bind=None, binds=None, **kw):
+ def __init__(self, bind=None, binds=None, sync_session_class=None, **kw):
+ r"""Construct a new :class:`_asyncio.AsyncSession`.
+
+ All parameters other than ``sync_session_class`` are passed to the
+ ``sync_session_class`` callable directly to instantiate a new
+ :class:`_orm.Session`. Refer to :meth:`_orm.Session.__init__` for
+ parameter documentation.
+
+ :param sync_session_class:
+ A :class:`_orm.Session` subclass or other callable which will be used
+ to construct the :class:`_orm.Session` which will be proxied. This
+ parameter may be used to provide custom :class:`_orm.Session`
+ subclasses. Defaults to the
+ :attr:`_asyncio.AsyncSession.sync_session_class` class-level
+ attribute.
+
+ .. versionadded:: 1.4.24
+
+ """
kw["future"] = True
if bind:
self.bind = bind
@@ -78,10 +98,30 @@ class AsyncSession(ReversibleProxy):
for key, b in binds.items()
}
+ if sync_session_class:
+ self.sync_session_class = sync_session_class
+
self.sync_session = self._proxied = self._assign_proxied(
- Session(bind=bind, binds=binds, **kw)
+ self.sync_session_class(bind=bind, binds=binds, **kw)
)
+ sync_session_class = Session
+ """The class or callable that provides the
+ underlying :class:`_orm.Session` instance for a particular
+ :class:`_asyncio.AsyncSession`.
+
+ At the class level, this attribute is the default value for the
+ :paramref:`_asyncio.AsyncSession.sync_session_class` parameter. Custom
+ subclasses of :class:`_asyncio.AsyncSession` can override this.
+
+ At the instance level, this attribute indicates the current class or
+ callable that was used to provide the :class:`_orm.Session` instance for
+ this :class:`_asyncio.AsyncSession` instance.
+
+ .. versionadded:: 1.4.24
+
+ """
+
async def refresh(
self, instance, attribute_names=None, with_for_update=None
):
@@ -93,6 +133,10 @@ class AsyncSession(ReversibleProxy):
This is the async version of the :meth:`_orm.Session.refresh` method.
See that method for a complete description of all options.
+ .. seealso::
+
+ :meth:`_orm.Session.refresh` - main documentation for refresh
+
"""
return await greenlet_spawn(
@@ -138,9 +182,20 @@ class AsyncSession(ReversibleProxy):
**kw
):
"""Execute a statement and return a buffered
- :class:`_engine.Result` object."""
+ :class:`_engine.Result` object.
+
+ .. seealso::
+
+ :meth:`_orm.Session.execute` - main documentation for execute
+
+ """
- execution_options = execution_options.union({"prebuffer_rows": True})
+ if execution_options:
+ execution_options = util.immutabledict(execution_options).union(
+ _EXECUTE_OPTIONS
+ )
+ else:
+ execution_options = _EXECUTE_OPTIONS
return await greenlet_spawn(
self.sync_session.execute,
@@ -159,7 +214,13 @@ class AsyncSession(ReversibleProxy):
bind_arguments=None,
**kw
):
- """Execute a statement and return a scalar result."""
+ """Execute a statement and return a scalar result.
+
+ .. seealso::
+
+ :meth:`_orm.Session.scalar` - main documentation for scalar
+
+ """
result = await self.execute(
statement,
@@ -170,6 +231,37 @@ class AsyncSession(ReversibleProxy):
)
return result.scalar()
+ async def scalars(
+ self,
+ statement,
+ params=None,
+ execution_options=util.EMPTY_DICT,
+ bind_arguments=None,
+ **kw
+ ):
+ """Execute a statement and return scalar results.
+
+ :return: a :class:`_result.ScalarResult` object
+
+ .. versionadded:: 1.4.24
+
+ .. seealso::
+
+ :meth:`_orm.Session.scalars` - main documentation for scalars
+
+ :meth:`_asyncio.AsyncSession.stream_scalars` - streaming version
+
+ """
+
+ result = await self.execute(
+ statement,
+ params=params,
+ execution_options=execution_options,
+ bind_arguments=bind_arguments,
+ **kw
+ )
+ return result.scalars()
+
async def get(
self,
entity,
@@ -182,6 +274,10 @@ class AsyncSession(ReversibleProxy):
"""Return an instance based on the given primary key identifier,
or ``None`` if not found.
+ .. seealso::
+
+ :meth:`_orm.Session.get` - main documentation for get
+
"""
return await greenlet_spawn(
@@ -205,7 +301,12 @@ class AsyncSession(ReversibleProxy):
"""Execute a statement and return a streaming
:class:`_asyncio.AsyncResult` object."""
- execution_options = execution_options.union({"stream_results": True})
+ if execution_options:
+ execution_options = util.immutabledict(execution_options).union(
+ _STREAM_OPTIONS
+ )
+ else:
+ execution_options = _STREAM_OPTIONS
result = await greenlet_spawn(
self.sync_session.execute,
@@ -217,6 +318,37 @@ class AsyncSession(ReversibleProxy):
)
return _result.AsyncResult(result)
+ async def stream_scalars(
+ self,
+ statement,
+ params=None,
+ execution_options=util.EMPTY_DICT,
+ bind_arguments=None,
+ **kw
+ ):
+ """Execute a statement and return a stream of scalar results.
+
+ :return: an :class:`_asyncio.AsyncScalarResult` object
+
+ .. versionadded:: 1.4.24
+
+ .. seealso::
+
+ :meth:`_orm.Session.scalars` - main documentation for scalars
+
+ :meth:`_asyncio.AsyncSession.scalars` - non streaming version
+
+ """
+
+ result = await self.stream(
+ statement,
+ params=params,
+ execution_options=execution_options,
+ bind_arguments=bind_arguments,
+ **kw
+ )
+ return result.scalars()
+
async def delete(self, instance):
"""Mark an instance as deleted.
@@ -225,17 +357,24 @@ class AsyncSession(ReversibleProxy):
As this operation may need to cascade along unloaded relationships,
it is awaitable to allow for those queries to take place.
+ .. seealso::
+
+ :meth:`_orm.Session.delete` - main documentation for delete
"""
return await greenlet_spawn(self.sync_session.delete, instance)
- async def merge(self, instance, load=True):
+ async def merge(self, instance, load=True, options=None):
"""Copy the state of a given instance into a corresponding instance
within this :class:`_asyncio.AsyncSession`.
+ .. seealso::
+
+ :meth:`_orm.Session.merge` - main documentation for merge
+
"""
return await greenlet_spawn(
- self.sync_session.merge, instance, load=load
+ self.sync_session.merge, instance, load=load, options=options
)
async def flush(self, objects=None):
@@ -243,7 +382,7 @@ class AsyncSession(ReversibleProxy):
.. seealso::
- :meth:`_orm.Session.flush`
+ :meth:`_orm.Session.flush` - main documentation for flush
"""
await greenlet_spawn(self.sync_session.flush, objects=objects)
@@ -279,13 +418,26 @@ class AsyncSession(ReversibleProxy):
else:
return None
- async def connection(self):
+ async def connection(self, **kw):
r"""Return a :class:`_asyncio.AsyncConnection` object corresponding to
this :class:`.Session` object's transactional state.
+ This method may also be used to establish execution options for the
+ database connection used by the current transaction.
+
+ .. versionadded:: 1.4.24 Added **kw arguments which are passed through
+ to the underlying :meth:`_orm.Session.connection` method.
+
+ .. seealso::
+
+ :meth:`_orm.Session.connection` - main documentation for
+ "connection"
+
"""
- sync_connection = await greenlet_spawn(self.sync_session.connection)
+ sync_connection = await greenlet_spawn(
+ self.sync_session.connection, **kw
+ )
return engine.AsyncConnection._retrieve_proxy_for_target(
sync_connection
)
diff --git a/lib/sqlalchemy/ext/mypy/decl_class.py b/lib/sqlalchemy/ext/mypy/decl_class.py
index 23c78aa51..b85ec0f69 100644
--- a/lib/sqlalchemy/ext/mypy/decl_class.py
+++ b/lib/sqlalchemy/ext/mypy/decl_class.py
@@ -61,6 +61,9 @@ def scan_declarative_assignments_and_apply_types(
List[util.SQLAlchemyAttribute]
] = util.get_mapped_attributes(info, api)
+ # used by assign.add_additional_orm_attributes among others
+ util.establish_as_sqlalchemy(info)
+
if mapped_attributes is not None:
# ensure that a class that's mapped is always picked up by
# its mapped() decorator or declarative metaclass before
diff --git a/lib/sqlalchemy/ext/mypy/infer.py b/lib/sqlalchemy/ext/mypy/infer.py
index 85a94bba6..52570f772 100644
--- a/lib/sqlalchemy/ext/mypy/infer.py
+++ b/lib/sqlalchemy/ext/mypy/infer.py
@@ -284,20 +284,35 @@ def _infer_type_from_decl_column_property(
"""
assert isinstance(stmt.rvalue, CallExpr)
- first_prop_arg = stmt.rvalue.args[0]
- if isinstance(first_prop_arg, CallExpr):
- type_id = names.type_id_for_callee(first_prop_arg.callee)
+ if stmt.rvalue.args:
+ first_prop_arg = stmt.rvalue.args[0]
+
+ if isinstance(first_prop_arg, CallExpr):
+ type_id = names.type_id_for_callee(first_prop_arg.callee)
+
+ # look for column_property() / deferred() etc with Column as first
+ # argument
+ if type_id is names.COLUMN:
+ return _infer_type_from_decl_column(
+ api,
+ stmt,
+ node,
+ left_hand_explicit_type,
+ right_hand_expression=first_prop_arg,
+ )
- # look for column_property() / deferred() etc with Column as first
- # argument
- if type_id is names.COLUMN:
+ if isinstance(stmt.rvalue, CallExpr):
+ type_id = names.type_id_for_callee(stmt.rvalue.callee)
+ # this is probably not strictly necessary as we have to use the left
+ # hand type for query expression in any case. any other no-arg
+ # column prop objects would go here also
+ if type_id is names.QUERY_EXPRESSION:
return _infer_type_from_decl_column(
api,
stmt,
node,
left_hand_explicit_type,
- right_hand_expression=first_prop_arg,
)
return infer_type_from_left_hand_type_only(
diff --git a/lib/sqlalchemy/ext/mypy/names.py b/lib/sqlalchemy/ext/mypy/names.py
index 22a79e29b..3dbfcc770 100644
--- a/lib/sqlalchemy/ext/mypy/names.py
+++ b/lib/sqlalchemy/ext/mypy/names.py
@@ -45,6 +45,7 @@ MAPPER_PROPERTY: int = util.symbol("MAPPER_PROPERTY") # type: ignore
AS_DECLARATIVE: int = util.symbol("AS_DECLARATIVE") # type: ignore
AS_DECLARATIVE_BASE: int = util.symbol("AS_DECLARATIVE_BASE") # type: ignore
DECLARATIVE_MIXIN: int = util.symbol("DECLARATIVE_MIXIN") # type: ignore
+QUERY_EXPRESSION: int = util.symbol("QUERY_EXPRESSION") # type: ignore
_lookup: Dict[str, Tuple[int, Set[str]]] = {
"Column": (
@@ -150,6 +151,10 @@ _lookup: Dict[str, Tuple[int, Set[str]]] = {
"sqlalchemy.orm.declarative_mixin",
},
),
+ "query_expression": (
+ QUERY_EXPRESSION,
+ {"sqlalchemy.orm.query_expression"},
+ ),
}
diff --git a/lib/sqlalchemy/ext/mypy/util.py b/lib/sqlalchemy/ext/mypy/util.py
index 614805d77..a3825f175 100644
--- a/lib/sqlalchemy/ext/mypy/util.py
+++ b/lib/sqlalchemy/ext/mypy/util.py
@@ -99,6 +99,10 @@ def _get_info_mro_metadata(info: TypeInfo, key: str) -> Optional[Any]:
return None
+def establish_as_sqlalchemy(info: TypeInfo) -> None:
+ info.metadata.setdefault("sqlalchemy", {})
+
+
def set_is_base(info: TypeInfo) -> None:
_set_info_metadata(info, "is_base", True)
diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py
index 9318bb163..85c736e12 100644
--- a/lib/sqlalchemy/orm/context.py
+++ b/lib/sqlalchemy/orm/context.py
@@ -187,6 +187,12 @@ class ORMCompileState(CompileState):
def __init__(self, *arg, **kw):
raise NotImplementedError()
+ def _append_dedupe_col_collection(self, obj, col_collection):
+ dedupe = self.dedupe_columns
+ if obj not in dedupe:
+ dedupe.add(obj)
+ col_collection.append(obj)
+
@classmethod
def _column_naming_convention(cls, label_style, legacy):
@@ -443,6 +449,7 @@ class ORMFromStatementCompileState(ORMCompileState):
self.primary_columns = []
self.secondary_columns = []
+ self.dedupe_columns = set()
self.create_eager_joins = []
self._fallback_from_clauses = []
@@ -645,6 +652,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
self.primary_columns = []
self.secondary_columns = []
+ self.dedupe_columns = set()
self.eager_joins = {}
self.extra_criteria_entities = {}
self.create_eager_joins = []
@@ -765,8 +773,6 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
# PART II
- self.dedupe_cols = True
-
self._for_update_arg = query._for_update_arg
for entity in self._entities:
@@ -1036,9 +1042,8 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
# put FOR UPDATE on the inner query, where MySQL will honor it,
# as well as if it has an OF so PostgreSQL can use it.
inner = self._select_statement(
- util.unique_list(self.primary_columns + order_by_col_expr)
- if self.dedupe_cols
- else (self.primary_columns + order_by_col_expr),
+ self.primary_columns
+ + [c for c in order_by_col_expr if c not in self.dedupe_columns],
self.from_clauses,
self._where_criteria,
self._having_criteria,
@@ -1116,9 +1121,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
self.primary_columns += to_add
statement = self._select_statement(
- util.unique_list(self.primary_columns + self.secondary_columns)
- if self.dedupe_cols
- else (self.primary_columns + self.secondary_columns),
+ self.primary_columns + self.secondary_columns,
tuple(self.from_clauses) + tuple(self.eager_joins.values()),
self._where_criteria,
self._having_criteria,
@@ -2822,6 +2825,7 @@ class _RawColumnEntity(_ColumnEntity):
# result due to the __eq__() method, so use deannotated
column = column._deannotate()
+ compile_state.dedupe_columns.add(column)
compile_state.primary_columns.append(column)
self._fetch_column = column
@@ -2949,6 +2953,7 @@ class _ORMColumnEntity(_ColumnEntity):
):
compile_state._fallback_from_clauses.append(ezero.selectable)
+ compile_state.dedupe_columns.add(column)
compile_state.primary_columns.append(column)
self._fetch_column = column
diff --git a/lib/sqlalchemy/orm/decl_api.py b/lib/sqlalchemy/orm/decl_api.py
index 23b89a543..94cda236d 100644
--- a/lib/sqlalchemy/orm/decl_api.py
+++ b/lib/sqlalchemy/orm/decl_api.py
@@ -126,7 +126,7 @@ class declared_attr(interfaces._MappedAttribute, property):
:class:`_orm.declared_attr` is typically applied as a decorator to a class
level method, turning the attribute into a scalar-like property that can be
invoked from the uninstantiated class. The Declarative mapping process
- looks for these :class:`_orm.declared_attr` callables as it scans classe,
+ looks for these :class:`_orm.declared_attr` callables as it scans classes,
and assumes any attribute marked with :class:`_orm.declared_attr` will be a
callable that will produce an object specific to the Declarative mapping or
table configuration.
@@ -761,6 +761,8 @@ class registry(object):
registry = mapper_registry
metadata = mapper_registry.metadata
+ __init__ = mapper_registry.constructor
+
The :meth:`_orm.registry.generate_base` method provides the
implementation for the :func:`_orm.declarative_base` function, which
creates the :class:`_orm.registry` and base class all at once.
diff --git a/lib/sqlalchemy/orm/path_registry.py b/lib/sqlalchemy/orm/path_registry.py
index d50a242ee..0327605d5 100644
--- a/lib/sqlalchemy/orm/path_registry.py
+++ b/lib/sqlalchemy/orm/path_registry.py
@@ -420,7 +420,7 @@ class AbstractEntityRegistry(PathRegistry):
)
# it seems to make sense that since these paths get mixed up
# with statements that are cached or not, we should make
- # sure the natural path is cachable across different occurrences
+ # sure the natural path is cacheable across different occurrences
# of equivalent AliasedClass objects. however, so far this
# does not seem to be needed for whatever reason.
# elif not parent.path and self.is_aliased_class:
diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py
index 4747d0bba..fd484b52b 100644
--- a/lib/sqlalchemy/orm/persistence.py
+++ b/lib/sqlalchemy/orm/persistence.py
@@ -1833,7 +1833,7 @@ class BulkUDCompileState(CompileState):
return (
statement,
util.immutabledict(execution_options).union(
- dict(_sa_orm_update_options=update_options)
+ {"_sa_orm_update_options": update_options}
),
)
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index b44a16102..e7999521a 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -3431,7 +3431,7 @@ class JoinCondition(object):
and pr.key not in self.prop._overlaps
and self.prop.key not in pr._overlaps
# note: the "__*" symbol is used internally by
- # SQLAlchemy as a general means of supressing the
+ # SQLAlchemy as a general means of suppressing the
# overlaps warning for some extension cases, however
# this is not currently
# a publicly supported symbol and may change at
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 37c94e231..f051d8df2 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -1309,6 +1309,7 @@ class Session(_SessionClassMethods):
"subtransactions are not implemented in future "
"Session objects."
)
+
if self._autobegin():
if not subtransactions and not nested and not _subtrans:
return self._transaction
@@ -1326,6 +1327,7 @@ class Session(_SessionClassMethods):
elif not self.autocommit:
# outermost transaction. must be a not nested and not
# a subtransaction
+
assert not nested and not _subtrans and not subtransactions
trans = SessionTransaction(self)
assert self._transaction is trans
@@ -1579,7 +1581,7 @@ class Session(_SessionClassMethods):
: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:`_future.Connection.execution_options`, and may also
+ by :meth:`_engine.Connection.execution_options`, and may also
provide additional options understood only in an ORM context.
:param bind_arguments: dictionary of additional arguments to determine
@@ -1722,6 +1724,35 @@ class Session(_SessionClassMethods):
**kw
).scalar()
+ def scalars(
+ self,
+ statement,
+ params=None,
+ execution_options=util.EMPTY_DICT,
+ bind_arguments=None,
+ **kw
+ ):
+ """Execute a statement and return the results as scalars.
+
+ Usage and parameters are the same as that of
+ :meth:`_orm.Session.execute`; the return result is a
+ :class:`_result.ScalarResult` filtering object which
+ will return single elements rather than :class:`_row.Row` objects.
+
+ :return: a :class:`_result.ScalarResult` object
+
+ .. versionadded:: 1.4.24
+
+ """
+
+ return self.execute(
+ statement,
+ params=params,
+ execution_options=execution_options,
+ bind_arguments=bind_arguments,
+ **kw
+ ).scalars()
+
def close(self):
"""Close out the transactional resources and ORM objects used by this
:class:`_orm.Session`.
@@ -2841,7 +2872,7 @@ class Session(_SessionClassMethods):
load_options=load_options,
)
- def merge(self, instance, load=True):
+ def merge(self, instance, load=True, options=None):
"""Copy the state of a given instance into a corresponding instance
within this :class:`.Session`.
@@ -2887,6 +2918,11 @@ class Session(_SessionClassMethods):
produced as "clean", so it is only appropriate that the given objects
should be "clean" as well, else this suggests a mis-use of the
method.
+ :param options: optional sequence of loader options which will be
+ applied to the :meth:`_orm.Session.get` method when the merge
+ operation loads the existing version of the object from the database.
+
+ .. versionadded:: 1.4.24
.. seealso::
@@ -2914,6 +2950,7 @@ class Session(_SessionClassMethods):
attributes.instance_state(instance),
attributes.instance_dict(instance),
load=load,
+ options=options,
_recursive=_recursive,
_resolve_conflict_map=_resolve_conflict_map,
)
@@ -2925,6 +2962,7 @@ class Session(_SessionClassMethods):
state,
state_dict,
load=True,
+ options=None,
_recursive=None,
_resolve_conflict_map=None,
):
@@ -2988,7 +3026,12 @@ class Session(_SessionClassMethods):
new_instance = True
elif key_is_persistent:
- merged = self.get(mapper.class_, key[1], identity_token=key[2])
+ merged = self.get(
+ mapper.class_,
+ key[1],
+ identity_token=key[2],
+ options=options,
+ )
if merged is None:
merged = mapper.class_manager.new_instance()
@@ -3453,11 +3496,14 @@ class Session(_SessionClassMethods):
SQL expressions.
The objects as given are not added to the session and no additional
- state is established on them, unless the ``return_defaults`` flag
- is also set, in which case primary key attributes and server-side
- default values will be populated.
-
- .. versionadded:: 1.0.0
+ state is established on them. If the
+ :paramref:`_orm.Session.bulk_save_objects.return_defaults` flag is set,
+ then server-generated primary key values will be assigned to the
+ returned objects, but **not server side defaults**; this is a
+ limitation in the implementation. If stateful objects are desired,
+ please use the standard :meth:`_orm.Session.add_all` approach or
+ as an alternative newer mass-insert features such as
+ :ref:`orm_dml_returning_objects`.
.. warning::
@@ -3467,6 +3513,14 @@ class Session(_SessionClassMethods):
and SQL clause support are **silently omitted** in favor of raw
INSERT/UPDATES of records.
+ Please note that newer versions of SQLAlchemy are **greatly
+ improving the efficiency** of the standard flush process. It is
+ **strongly recommended** to not use the bulk methods as they
+ represent a forking of SQLAlchemy's functionality and are slowly
+ being moved into legacy status. New features such as
+ :ref:`orm_dml_returning_objects` are both more efficient than
+ the "bulk" methods and provide more predictable functionality.
+
**Please read the list of caveats at**
:ref:`bulk_operations_caveats` **before using this method, and
fully test and confirm the functionality of all code developed
@@ -3498,7 +3552,9 @@ class Session(_SessionClassMethods):
and other multi-table mappings to insert correctly without the need
to provide primary key values ahead of time; however,
:paramref:`.Session.bulk_save_objects.return_defaults` **greatly
- reduces the performance gains** of the method overall.
+ reduces the performance gains** of the method overall. It is strongly
+ advised to please use the standard :meth:`_orm.Session.add_all`
+ approach.
:param update_changed_only: when True, UPDATE statements are rendered
based on those attributes in each state that have logged changes.
@@ -3568,6 +3624,14 @@ class Session(_SessionClassMethods):
and SQL clause support are **silently omitted** in favor of raw
INSERT of records.
+ Please note that newer versions of SQLAlchemy are **greatly
+ improving the efficiency** of the standard flush process. It is
+ **strongly recommended** to not use the bulk methods as they
+ represent a forking of SQLAlchemy's functionality and are slowly
+ being moved into legacy status. New features such as
+ :ref:`orm_dml_returning_objects` are both more efficient than
+ the "bulk" methods and provide more predictable functionality.
+
**Please read the list of caveats at**
:ref:`bulk_operations_caveats` **before using this method, and
fully test and confirm the functionality of all code developed
@@ -3661,6 +3725,14 @@ class Session(_SessionClassMethods):
and SQL clause support are **silently omitted** in favor of raw
UPDATES of records.
+ Please note that newer versions of SQLAlchemy are **greatly
+ improving the efficiency** of the standard flush process. It is
+ **strongly recommended** to not use the bulk methods as they
+ represent a forking of SQLAlchemy's functionality and are slowly
+ being moved into legacy status. New features such as
+ :ref:`orm_dml_returning_objects` are both more efficient than
+ the "bulk" methods and provide more predictable functionality.
+
**Please read the list of caveats at**
:ref:`bulk_operations_caveats` **before using this method, and
fully test and confirm the functionality of all code developed
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 069e5e667..4f361be2c 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -157,7 +157,7 @@ class UninstrumentedColumnLoader(LoaderStrategy):
for c in self.columns:
if adapter:
c = adapter.columns[c]
- column_collection.append(c)
+ compile_state._append_dedupe_col_collection(c, column_collection)
def create_row_processor(
self,
@@ -206,7 +206,7 @@ class ColumnLoader(LoaderStrategy):
else:
c = adapter.columns[c]
- column_collection.append(c)
+ compile_state._append_dedupe_col_collection(c, column_collection)
fetch = self.columns[0]
if adapter:
@@ -296,7 +296,7 @@ class ExpressionColumnLoader(ColumnLoader):
for c in columns:
if adapter:
c = adapter.columns[c]
- column_collection.append(c)
+ compile_state._append_dedupe_col_collection(c, column_collection)
fetch = columns[0]
if adapter:
@@ -2335,7 +2335,9 @@ class JoinedLoader(AbstractRelationshipLoader):
if localparent.persist_selectable.c.contains_column(col):
if adapter:
col = adapter.columns[col]
- compile_state.primary_columns.append(col)
+ compile_state._append_dedupe_col_collection(
+ col, compile_state.primary_columns
+ )
if self.parent_property.order_by:
compile_state.eager_order_by += tuple(
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 94a0b4201..9af82823a 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -610,7 +610,7 @@ class SQLCompiler(Compiled):
"""
_loose_column_name_matching = False
- """tell the result object that the SQL staement is textual, wants to match
+ """tell the result object that the SQL statement is textual, wants to match
up to Column objects, and may be using the ._tq_label in the SELECT rather
than the base name.
diff --git a/lib/sqlalchemy/sql/ddl.py b/lib/sqlalchemy/sql/ddl.py
index 233e79f7c..f8985548e 100644
--- a/lib/sqlalchemy/sql/ddl.py
+++ b/lib/sqlalchemy/sql/ddl.py
@@ -1081,7 +1081,7 @@ class SchemaDropper(DDLBase):
table,
drop_ok=False,
_is_metadata_operation=False,
- _ignore_sequences=[],
+ _ignore_sequences=(),
):
if not drop_ok and not self._can_drop_table(table):
return
diff --git a/lib/sqlalchemy/sql/lambdas.py b/lib/sqlalchemy/sql/lambdas.py
index 36e470ce7..03cd05f02 100644
--- a/lib/sqlalchemy/sql/lambdas.py
+++ b/lib/sqlalchemy/sql/lambdas.py
@@ -905,7 +905,7 @@ class AnalyzedCode(object):
util.raise_(
exc.InvalidRequestError(
"Closure variable named '%s' inside of lambda callable %s "
- "does not refer to a cachable SQL element, and also does not "
+ "does not refer to a cacheable SQL element, and also does not "
"appear to be serving as a SQL literal bound value based on "
"the default "
"SQL expression returned by the function. This variable "
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index e530beef2..c6d997649 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -809,7 +809,7 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
objects maintained by this :class:`_expression.FromClause`.
The :attr:`_sql.FromClause.c` attribute is an alias for the
- :attr:`_sql.FromClause.columns` atttribute.
+ :attr:`_sql.FromClause.columns` attribute.
:return: a :class:`.ColumnCollection`
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index f660556eb..40127addf 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -500,7 +500,7 @@ class SuiteRequirements(Requirements):
def foreign_key_constraint_name_reflection(self):
"""Target supports refleciton of FOREIGN KEY constraints and
will return the name of the constraint that was used in the
- "CONSTRANT <name> FOREIGN KEY" DDL.
+ "CONSTRAINT <name> FOREIGN KEY" DDL.
MySQL prior to version 8 and MariaDB prior to version 10.5
don't support this.
@@ -857,7 +857,7 @@ class SuiteRequirements(Requirements):
>>> testing.requirements.get_isolation_levels()
{
- "default": "READ_COMMITED",
+ "default": "READ_COMMITTED",
"supported": [
"SERIALIZABLE", "READ UNCOMMITTED",
"READ COMMITTED", "REPEATABLE READ",
@@ -1227,6 +1227,10 @@ class SuiteRequirements(Requirements):
return self.python36
@property
+ def insert_order_dicts(self):
+ return self.python37
+
+ @property
def python36(self):
return exclusions.skip_if(
lambda: sys.version_info < (3, 6),
diff --git a/lib/sqlalchemy/testing/warnings.py b/lib/sqlalchemy/testing/warnings.py
index 31834cebe..d22fb175e 100644
--- a/lib/sqlalchemy/testing/warnings.py
+++ b/lib/sqlalchemy/testing/warnings.py
@@ -66,13 +66,6 @@ def setup_filters():
# we are moving one at a time
for msg in [
#
- # DML
- #
- r"The update.preserve_parameter_order parameter will be removed in "
- "SQLAlchemy 2.0.",
- r"Passing dialect keyword arguments directly to the "
- "(?:Insert|Update|Delete) constructor",
- #
# ORM configuration
#
r"Calling the mapper\(\) function directly outside of a "
diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py
index 5d52f740f..5914e8681 100644
--- a/lib/sqlalchemy/util/compat.py
+++ b/lib/sqlalchemy/util/compat.py
@@ -392,6 +392,9 @@ if py3k:
"""
+ kwonlydefaults = kwonlydefaults or {}
+ annotations = annotations or {}
+
def formatargandannotation(arg):
result = formatarg(arg)
if arg in annotations: