summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-08-18 13:56:50 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-09-25 19:38:10 -0400
commit81d8394c0b5342cdc603cb2e07e12139c9506bf6 (patch)
tree5453f51ef80bb3b0b4705025070439fdccfea29c /lib/sqlalchemy
parenta8029f5a7e3e376ec57f1614ab0294b717d53c05 (diff)
downloadsqlalchemy-81d8394c0b5342cdc603cb2e07e12139c9506bf6.tar.gz
New ORM Query Guide featuring DML support
reviewers: these docs publish periodically at: https://docs.sqlalchemy.org/en/gerrit/4042/orm/queryguide/index.html See the "last generated" timestamp near the bottom of the page to ensure the latest version is up Change includes some other adjustments: * small typing fixes for end-user benefit * removal of a bunch of old examples for patterns that nobody uses or aren't really what we promote now * modernization of some examples, including inheritance Change-Id: I9929daab7797be9515f71c888b28af1209e789ff
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py1
-rw-r--r--lib/sqlalchemy/engine/cursor.py2
-rw-r--r--lib/sqlalchemy/orm/_orm_constructors.py31
-rw-r--r--lib/sqlalchemy/orm/attributes.py2
-rw-r--r--lib/sqlalchemy/orm/events.py31
-rw-r--r--lib/sqlalchemy/orm/interfaces.py5
-rw-r--r--lib/sqlalchemy/orm/mapper.py26
-rw-r--r--lib/sqlalchemy/orm/query.py13
-rw-r--r--lib/sqlalchemy/orm/relationships.py2
-rw-r--r--lib/sqlalchemy/orm/scoping.py170
-rw-r--r--lib/sqlalchemy/orm/session.py106
-rw-r--r--lib/sqlalchemy/orm/strategy_options.py85
-rw-r--r--lib/sqlalchemy/orm/util.py34
-rw-r--r--lib/sqlalchemy/sql/_dml_constructors.py4
-rw-r--r--lib/sqlalchemy/sql/base.py2
-rw-r--r--lib/sqlalchemy/sql/selectable.py44
-rw-r--r--lib/sqlalchemy/testing/requirements.py6
17 files changed, 233 insertions, 331 deletions
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index 5f468edbe..b4f407eb3 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -1937,6 +1937,7 @@ class SQLiteDialect(default.DefaultDialect):
insert_null_pk_still_autoincrements = True
insert_returning = True
update_returning = True
+ update_returning_multifrom = True
delete_returning = True
update_returning_multifrom = True
diff --git a/lib/sqlalchemy/engine/cursor.py b/lib/sqlalchemy/engine/cursor.py
index 07e782296..f22e89fbe 100644
--- a/lib/sqlalchemy/engine/cursor.py
+++ b/lib/sqlalchemy/engine/cursor.py
@@ -1821,7 +1821,7 @@ class CursorResult(Result[_T]):
.. seealso::
- :ref:`.CursorResult.splice_horizontally`
+ :meth:`.CursorResult.splice_horizontally`
"""
clone = self._generate()
diff --git a/lib/sqlalchemy/orm/_orm_constructors.py b/lib/sqlalchemy/orm/_orm_constructors.py
index 8dfec0fb1..0ea870277 100644
--- a/lib/sqlalchemy/orm/_orm_constructors.py
+++ b/lib/sqlalchemy/orm/_orm_constructors.py
@@ -199,13 +199,22 @@ def mapped_column(
.. seealso::
- :ref:`deferred`
+ :ref:`orm_queryguide_deferred_declarative`
:param deferred_group: Implies :paramref:`_orm.mapped_column.deferred`
to ``True``, and set the :paramref:`_orm.deferred.group` parameter.
+
+ .. seealso::
+
+ :ref:`orm_queryguide_deferred_group`
+
:param deferred_raiseload: Implies :paramref:`_orm.mapped_column.deferred`
to ``True``, and set the :paramref:`_orm.deferred.raiseload` parameter.
+ .. seealso::
+
+ :ref:`orm_queryguide_deferred_raiseload`
+
:param default: Passed directly to the
:paramref:`_schema.Column.default` parameter if the
:paramref:`_orm.mapped_column.insert_default` parameter is not present.
@@ -372,7 +381,7 @@ def column_property(
.. seealso::
- :ref:`deferred_raiseload`
+ :ref:`orm_queryguide_deferred_raiseload`
.. seealso::
@@ -1876,9 +1885,7 @@ def deferred(
.. seealso::
- :ref:`deferred`
-
- :ref:`deferred_raiseload`
+ :ref:`orm_queryguide_deferred_imperative`
"""
return ColumnProperty(
@@ -1910,22 +1917,12 @@ def query_expression(
:param default_expr: Optional SQL expression object that will be used in
all cases if not assigned later with :func:`_orm.with_expression`.
- E.g.::
-
- from sqlalchemy.sql import literal
-
- class C(Base):
- #...
- my_expr = query_expression(literal(1))
-
- .. versionadded:: 1.3.18
-
.. versionadded:: 1.2
.. seealso::
- :ref:`mapper_querytime_expression`
+ :ref:`orm_queryguide_with_expression` - background and usage examples
"""
prop = ColumnProperty(
@@ -2123,7 +2120,7 @@ def aliased(
def with_polymorphic(
base: Union[_O, Mapper[_O]],
- classes: Iterable[Type[Any]],
+ classes: Union[Literal["*"], Iterable[Type[Any]]],
selectable: Union[Literal[False, None], FromClause] = False,
flat: bool = False,
polymorphic_on: Optional[ColumnElement[Any]] = None,
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index db86d0810..119503014 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -393,7 +393,7 @@ class QueryableAttribute(
parententity=adapt_to_entity,
)
- def of_type(self, entity: _EntityType[_T]) -> QueryableAttribute[_T]:
+ def of_type(self, entity: _EntityType[Any]) -> QueryableAttribute[_T]:
return QueryableAttribute(
self.class_,
self.key,
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index 41348a0aa..73e2ee934 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -1876,10 +1876,16 @@ class SessionEvents(event.Events[Session]):
),
)
def after_bulk_update(self, update_context):
- """Execute after an ORM UPDATE against a WHERE expression has been
- invoked.
+ """Event for after the legacy :meth:`_orm.Query.update` method
+ has been called.
- This is called as a result of the :meth:`_query.Query.update` method.
+ .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_update` method
+ is a legacy event hook as of SQLAlchemy 2.0. The event
+ **does not participate** in :term:`2.0 style` invocations
+ using :func:`_dml.update` documented at
+ :ref:`orm_queryguide_update_delete_where`. For 2.0 style use,
+ the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept
+ these calls.
:param update_context: an "update context" object which contains
details about the update, including these attributes:
@@ -1916,10 +1922,16 @@ class SessionEvents(event.Events[Session]):
),
)
def after_bulk_delete(self, delete_context):
- """Execute after ORM DELETE against a WHERE expression has been
- invoked.
+ """Event for after the legacy :meth:`_orm.Query.delete` method
+ has been called.
- This is called as a result of the :meth:`_query.Query.delete` method.
+ .. legacy:: The :meth:`_orm.SessionEvents.after_bulk_delete` method
+ is a legacy event hook as of SQLAlchemy 2.0. The event
+ **does not participate** in :term:`2.0 style` invocations
+ using :func:`_dml.delete` documented at
+ :ref:`orm_queryguide_update_delete_where`. For 2.0 style use,
+ the :meth:`_orm.SessionEvents.do_orm_execute` hook will intercept
+ these calls.
:param delete_context: a "delete context" object which contains
details about the update, including these attributes:
@@ -2790,6 +2802,13 @@ class QueryEvents(event.Events):
"""Represent events within the construction of a :class:`_query.Query`
object.
+ .. legacy:: The :class:`_orm.QueryEvents` event methods are legacy
+ as of SQLAlchemy 2.0, and only apply to direct use of the
+ :class:`_orm.Query` object. They are not used for :term:`2.0 style`
+ statements. For events to intercept and modify 2.0 style ORM use,
+ use the :meth:`_orm.SessionEvents.do_orm_execute` hook.
+
+
The :class:`_orm.QueryEvents` hooks are now superseded by the
:meth:`_orm.SessionEvents.do_orm_execute` event hook.
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index 7d74eef2c..3c2903f30 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -763,7 +763,7 @@ class PropComparator(SQLORMOperations[_T]):
) -> ColumnElement[Any]:
...
- def of_type(self, class_: _EntityType[_T]) -> PropComparator[_T]:
+ def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T]:
r"""Redefine this object in terms of a polymorphic subclass,
:func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased`
construct.
@@ -781,7 +781,8 @@ class PropComparator(SQLORMOperations[_T]):
.. seealso::
- :ref:`queryguide_join_onclause` - in the :ref:`queryguide_toplevel`
+ :ref:`orm_queryguide_joining_relationships_aliased` - in the
+ :ref:`queryguide_toplevel`
:ref:`inheritance_of_type`
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index c9cf8f49b..98c0eba0c 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -473,17 +473,17 @@ class Mapper(
CASCADE for joined-table inheritance mappers
:param polymorphic_load: Specifies "polymorphic loading" behavior
- for a subclass in an inheritance hierarchy (joined and single
- table inheritance only). Valid values are:
+ for a subclass in an inheritance hierarchy (joined and single
+ table inheritance only). Valid values are:
- * "'inline'" - specifies this class should be part of the
- "with_polymorphic" mappers, e.g. its columns will be included
- in a SELECT query against the base.
+ * "'inline'" - specifies this class should be part of
+ the "with_polymorphic" mappers, e.g. its columns will be included
+ in a SELECT query against the base.
- * "'selectin'" - specifies that when instances of this class
- are loaded, an additional SELECT will be emitted to retrieve
- the columns specific to this subclass. The SELECT uses
- IN to fetch multiple subclasses at once.
+ * "'selectin'" - specifies that when instances of this class
+ are loaded, an additional SELECT will be emitted to retrieve
+ the columns specific to this subclass. The SELECT uses
+ IN to fetch multiple subclasses at once.
.. versionadded:: 1.2
@@ -667,10 +667,14 @@ class Mapper(
indicates a selectable that will be used to query for multiple
classes.
+ The :paramref:`_orm.Mapper.polymorphic_load` parameter may be
+ preferable over the use of :paramref:`_orm.Mapper.with_polymorphic`
+ in modern mappings to indicate a per-subclass technique of
+ indicating polymorphic loading styles.
+
.. seealso::
- :ref:`with_polymorphic` - discussion of polymorphic querying
- techniques.
+ :ref:`with_polymorphic_mapper_config`
"""
self.class_ = util.assert_arg_type(class_, type, "class_")
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 4d5a98fcf..61c446060 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -163,13 +163,10 @@ class Query(
"""ORM-level SQL construction object.
- :class:`_query.Query`
- is the source of all SELECT statements generated by the
- ORM, both those formulated by end-user query operations as well as by
- high level internal operations such as related collection loading. It
- features a generative interface whereby successive calls return a new
- :class:`_query.Query` object, a copy of the former with additional
- criteria and options associated with it.
+ .. legacy:: The ORM :class:`.Query` object is a legacy construct
+ as of SQLAlchemy 2.0. See the notes at the top of
+ :ref:`query_api_toplevel` for an overview, including links to migration
+ documentation.
:class:`_query.Query` objects are normally initially generated using the
:meth:`~.Session.query` method of :class:`.Session`, and in
@@ -1605,7 +1602,7 @@ class Query(
.. seealso::
- :ref:`deferred_options`
+ :ref:`loading_columns`
:ref:`relationship_loader_options`
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index 45b9b9bea..ecac07632 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -632,7 +632,7 @@ class Relationship(
else:
return pj
- def of_type(self, class_: _EntityType[_PT]) -> PropComparator[_PT]:
+ def of_type(self, class_: _EntityType[Any]) -> PropComparator[_PT]:
r"""Redefine this object in terms of a polymorphic subclass.
See :meth:`.PropComparator.of_type` for an example.
diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py
index c00508385..abe97653a 100644
--- a/lib/sqlalchemy/orm/scoping.py
+++ b/lib/sqlalchemy/orm/scoping.py
@@ -1124,44 +1124,19 @@ class scoped_session(Generic[_S]):
Proxied for the :class:`_orm.Session` class on
behalf of the :class:`_orm.scoping.scoped_session` class.
- The bulk save feature allows mapped objects to be used as the
- source of simple INSERT and UPDATE operations which can be more easily
- grouped together into higher performing "executemany"
- operations; the extraction of data from the objects is also performed
- using a lower-latency process that ignores whether or not attributes
- have actually been modified in the case of UPDATEs, and also ignores
- SQL expressions.
-
- The objects as given are not added to the session and no additional
- 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::
-
- The bulk save feature allows for a lower-latency INSERT/UPDATE
- of rows at the expense of most other unit-of-work features.
- Features such as object management, relationship handling,
- 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
- using these systems.**
+ .. legacy::
+
+ This method is a legacy feature as of the 2.0 series of
+ SQLAlchemy. For modern bulk INSERT and UPDATE, see
+ the sections :ref:`orm_queryguide_bulk_insert` and
+ :ref:`orm_queryguide_bulk_update`.
+
+ For general INSERT and UPDATE of existing ORM mapped objects,
+ prefer standard :term:`unit of work` data management patterns,
+ introduced in the :ref:`unified_tutorial` at
+ :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0
+ now uses :ref:`engine_insertmanyvalues` with modern dialects
+ which solves previous issues of bulk INSERT slowness.
:param objects: a sequence of mapped object instances. The mapped
objects are persisted as is, and are **not** associated with the
@@ -1203,11 +1178,9 @@ class scoped_session(Generic[_S]):
False, common types of objects are grouped into inserts
and updates, to allow for more batching opportunities.
- .. versionadded:: 1.3
-
.. seealso::
- :ref:`bulk_operations`
+ :doc:`queryguide/dml`
:meth:`.Session.bulk_insert_mappings`
@@ -1237,41 +1210,14 @@ class scoped_session(Generic[_S]):
Proxied for the :class:`_orm.Session` class on
behalf of the :class:`_orm.scoping.scoped_session` class.
- The bulk insert feature allows plain Python dictionaries to be used as
- the source of simple INSERT operations which can be more easily
- grouped together into higher performing "executemany"
- operations. Using dictionaries, there is no "history" or session
- state management features in use, reducing latency when inserting
- large numbers of simple rows.
-
- The values within the dictionaries as given are typically passed
- without modification into Core :meth:`_expression.Insert` constructs,
- after
- organizing the values within them across the tables to which
- the given mapper is mapped.
-
- .. versionadded:: 1.0.0
-
- .. warning::
-
- The bulk insert feature allows for a lower-latency INSERT
- of rows at the expense of most other unit-of-work features.
- Features such as object management, relationship handling,
- 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
- using these systems.**
+ .. legacy::
+
+ This method is a legacy feature as of the 2.0 series of
+ SQLAlchemy. For modern bulk INSERT and UPDATE, see
+ the sections :ref:`orm_queryguide_bulk_insert` and
+ :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
+ implementation details with this method and adds new features
+ as well.
:param mapper: a mapped class, or the actual :class:`_orm.Mapper`
object,
@@ -1284,19 +1230,18 @@ class scoped_session(Generic[_S]):
such as a joined-inheritance mapping, each dictionary must contain all
keys to be populated into all tables.
- :param return_defaults: when True, rows that are missing values which
- generate defaults, namely integer primary key defaults and sequences,
- will be inserted **one at a time**, so that the primary key value
- is available. In particular this will allow joined-inheritance
- and other multi-table mappings to insert correctly without the need
- to provide primary
- key values ahead of time; however,
- :paramref:`.Session.bulk_insert_mappings.return_defaults`
- **greatly reduces the performance gains** of the method overall.
- If the rows
- to be inserted only refer to a single table, then there is no
- reason this flag should be set as the returned default information
- is not used.
+ :param return_defaults: when True, the INSERT process will be altered
+ to ensure that newly generated primary key values will be fetched.
+ The rationale for this parameter is typically to enable
+ :ref:`Joined Table Inheritance <joined_inheritance>` mappings to
+ be bulk inserted.
+
+ .. note:: for backends that don't support RETURNING, the
+ :paramref:`_orm.Session.bulk_insert_mappings.return_defaults`
+ parameter can significantly decrease performance as INSERT
+ statements can no longer be batched. See
+ :ref:`engine_insertmanyvalues`
+ for background on which backends are affected.
:param render_nulls: When True, a value of ``None`` will result
in a NULL value being included in the INSERT statement, rather
@@ -1320,11 +1265,9 @@ class scoped_session(Generic[_S]):
to ensure that no server-side default functions need to be
invoked for the operation as a whole.
- .. versionadded:: 1.1
-
.. seealso::
- :ref:`bulk_operations`
+ :doc:`queryguide/dml`
:meth:`.Session.bulk_save_objects`
@@ -1350,35 +1293,14 @@ class scoped_session(Generic[_S]):
Proxied for the :class:`_orm.Session` class on
behalf of the :class:`_orm.scoping.scoped_session` class.
- The bulk update feature allows plain Python dictionaries to be used as
- the source of simple UPDATE operations which can be more easily
- grouped together into higher performing "executemany"
- operations. Using dictionaries, there is no "history" or session
- state management features in use, reducing latency when updating
- large numbers of simple rows.
+ .. legacy::
- .. versionadded:: 1.0.0
-
- .. warning::
-
- The bulk update feature allows for a lower-latency UPDATE
- of rows at the expense of most other unit-of-work features.
- Features such as object management, relationship handling,
- 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
- using these systems.**
+ This method is a legacy feature as of the 2.0 series of
+ SQLAlchemy. For modern bulk INSERT and UPDATE, see
+ the sections :ref:`orm_queryguide_bulk_insert` and
+ :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
+ implementation details with this method and adds new features
+ as well.
:param mapper: a mapped class, or the actual :class:`_orm.Mapper`
object,
@@ -1397,7 +1319,7 @@ class scoped_session(Generic[_S]):
.. seealso::
- :ref:`bulk_operations`
+ :doc:`queryguide/dml`
:meth:`.Session.bulk_insert_mappings`
@@ -1765,7 +1687,7 @@ class scoped_session(Generic[_S]):
def scalars(
self,
statement: TypedReturnsRows[Tuple[_T]],
- params: Optional[_CoreSingleExecuteParams] = None,
+ params: Optional[_CoreAnyExecuteParams] = None,
*,
execution_options: _ExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
@@ -1777,7 +1699,7 @@ class scoped_session(Generic[_S]):
def scalars(
self,
statement: Executable,
- params: Optional[_CoreSingleExecuteParams] = None,
+ params: Optional[_CoreAnyExecuteParams] = None,
*,
execution_options: _ExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
@@ -1788,7 +1710,7 @@ class scoped_session(Generic[_S]):
def scalars(
self,
statement: Executable,
- params: Optional[_CoreSingleExecuteParams] = None,
+ params: Optional[_CoreAnyExecuteParams] = None,
*,
execution_options: _ExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 64c013306..779860896 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -3964,36 +3964,19 @@ class Session(_SessionClassMethods, EventTarget):
) -> None:
"""Perform a bulk save of the given list of objects.
- The bulk save feature allows mapped objects to be used as the
- source of simple INSERT and UPDATE operations which can be more easily
- grouped together into higher performing "executemany"
- operations; the extraction of data from the objects is also performed
- using a lower-latency process that ignores whether or not attributes
- have actually been modified in the case of UPDATEs, and also ignores
- SQL expressions.
-
- The objects as given are not added to the session and no additional
- 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`.
+ .. legacy::
- .. warning::
-
- The bulk save feature allows for a lower-latency INSERT/UPDATE
- of rows at the expense of most other unit-of-work features.
- Features such as object management, relationship handling,
- and SQL clause support are **silently omitted** in favor of raw
- INSERT/UPDATES of records.
+ This method is a legacy feature as of the 2.0 series of
+ SQLAlchemy. For modern bulk INSERT and UPDATE, see
+ the sections :ref:`orm_queryguide_bulk_insert` and
+ :ref:`orm_queryguide_bulk_update`.
- **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
- using these systems.**
+ For general INSERT and UPDATE of existing ORM mapped objects,
+ prefer standard :term:`unit of work` data management patterns,
+ introduced in the :ref:`unified_tutorial` at
+ :ref:`tutorial_orm_data_manipulation`. SQLAlchemy 2.0
+ now uses :ref:`engine_insertmanyvalues` with modern dialects
+ which solves previous issues of bulk INSERT slowness.
:param objects: a sequence of mapped object instances. The mapped
objects are persisted as is, and are **not** associated with the
@@ -4035,11 +4018,9 @@ class Session(_SessionClassMethods, EventTarget):
False, common types of objects are grouped into inserts
and updates, to allow for more batching opportunities.
- .. versionadded:: 1.3
-
.. seealso::
- :ref:`bulk_operations`
+ :doc:`queryguide/dml`
:meth:`.Session.bulk_insert_mappings`
@@ -4088,31 +4069,14 @@ class Session(_SessionClassMethods, EventTarget):
) -> None:
"""Perform a bulk insert of the given list of mapping dictionaries.
- The bulk insert feature allows plain Python dictionaries to be used as
- the source of simple INSERT operations which can be more easily
- grouped together into higher performing "executemany"
- operations. Using dictionaries, there is no "history" or session
- state management features in use, reducing latency when inserting
- large numbers of simple rows.
-
- The values within the dictionaries as given are typically passed
- without modification into Core :meth:`_expression.Insert` constructs,
- after
- organizing the values within them across the tables to which
- the given mapper is mapped.
-
- .. warning::
-
- The bulk insert feature allows for a lower-latency INSERT
- of rows at the expense of most other unit-of-work features.
- Features such as object management, relationship handling,
- and SQL clause support are **silently omitted** in favor of raw
- INSERT of records.
+ .. legacy::
- **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
- using these systems.**
+ This method is a legacy feature as of the 2.0 series of
+ SQLAlchemy. For modern bulk INSERT and UPDATE, see
+ the sections :ref:`orm_queryguide_bulk_insert` and
+ :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
+ implementation details with this method and adds new features
+ as well.
:param mapper: a mapped class, or the actual :class:`_orm.Mapper`
object,
@@ -4162,7 +4126,7 @@ class Session(_SessionClassMethods, EventTarget):
.. seealso::
- :ref:`bulk_operations`
+ :doc:`queryguide/dml`
:meth:`.Session.bulk_save_objects`
@@ -4184,25 +4148,14 @@ class Session(_SessionClassMethods, EventTarget):
) -> None:
"""Perform a bulk update of the given list of mapping dictionaries.
- The bulk update feature allows plain Python dictionaries to be used as
- the source of simple UPDATE operations which can be more easily
- grouped together into higher performing "executemany"
- operations. Using dictionaries, there is no "history" or session
- state management features in use, reducing latency when updating
- large numbers of simple rows.
-
- .. warning::
-
- The bulk update feature allows for a lower-latency UPDATE
- of rows at the expense of most other unit-of-work features.
- Features such as object management, relationship handling,
- and SQL clause support are **silently omitted** in favor of raw
- UPDATES of records.
+ .. legacy::
- **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
- using these systems.**
+ This method is a legacy feature as of the 2.0 series of
+ SQLAlchemy. For modern bulk INSERT and UPDATE, see
+ the sections :ref:`orm_queryguide_bulk_insert` and
+ :ref:`orm_queryguide_bulk_update`. The 2.0 API shares
+ implementation details with this method and adds new features
+ as well.
:param mapper: a mapped class, or the actual :class:`_orm.Mapper`
object,
@@ -4221,7 +4174,7 @@ class Session(_SessionClassMethods, EventTarget):
.. seealso::
- :ref:`bulk_operations`
+ :doc:`queryguide/dml`
:meth:`.Session.bulk_insert_mappings`
@@ -4692,7 +4645,8 @@ def make_transient(instance: object) -> None:
* are normally :term:`lazy loaded` but are not currently loaded
- * are "deferred" via :ref:`deferred` and are not yet loaded
+ * are "deferred" (see :ref:`orm_queryguide_column_deferral`) and are
+ not yet loaded
* were not present in the query which loaded this object, such as that
which is common in joined table inheritance and other scenarios.
diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py
index 2748556fd..114030346 100644
--- a/lib/sqlalchemy/orm/strategy_options.py
+++ b/lib/sqlalchemy/orm/strategy_options.py
@@ -196,9 +196,18 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
Load(Address).load_only(Address.email_address)
)
- .. note:: This method will still load a :class:`_schema.Column` even
- if the column property is defined with ``deferred=True``
- for the :func:`.column_property` function.
+ :param \*attrs: Attributes to be loaded, all others will be deferred.
+
+ :param raiseload: raise :class:`.InvalidRequestError` rather than
+ lazy loading a value when a deferred attribute is accessed. Used
+ to prevent unwanted SQL from being emitted.
+
+ .. versionadded:: 2.0
+
+ .. seealso::
+
+ :ref:`orm_queryguide_column_deferral` - in the
+ :ref:`queryguide_toplevel`
:param \*attrs: Attributes to be loaded, all others will be deferred.
@@ -539,7 +548,7 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
:ref:`prevent_lazy_with_raiseload`
- :ref:`deferred_raiseload`
+ :ref:`orm_queryguide_deferred_raiseload`
"""
@@ -573,13 +582,9 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
.. seealso::
- :meth:`_orm.Load.options` - allows for complex hierarchical
- loader option structures with less verbosity than with individual
- :func:`.defaultload` directives.
-
- :ref:`relationship_loader_options`
+ :ref:`orm_queryguide_relationship_sub_options`
- :ref:`deferred_loading_w_multiple`
+ :meth:`_orm.Load.options`
"""
return self._set_relationship_strategy(attr, None)
@@ -633,11 +638,10 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
.. seealso::
- :ref:`deferred_raiseload`
-
- .. seealso::
+ :ref:`orm_queryguide_column_deferral` - in the
+ :ref:`queryguide_toplevel`
- :ref:`deferred`
+ :func:`_orm.load_only`
:func:`_orm.undefer`
@@ -677,7 +681,8 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
.. seealso::
- :ref:`deferred`
+ :ref:`orm_queryguide_column_deferral` - in the
+ :ref:`queryguide_toplevel`
:func:`_orm.defer`
@@ -708,7 +713,8 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
.. seealso::
- :ref:`deferred`
+ :ref:`orm_queryguide_column_deferral` - in the
+ :ref:`queryguide_toplevel`
:func:`_orm.defer`
@@ -743,15 +749,10 @@ class _AbstractLoad(traversals.GenerativeOnTraversal, LoaderOption):
:param expr: SQL expression to be applied to the attribute.
- .. note:: the target attribute is populated only if the target object
- is **not currently loaded** in the current :class:`_orm.Session`
- unless the :ref:`orm_queryguide_populate_existing` execution option
- is used. Please refer to :ref:`mapper_querytime_expression` for
- complete usage details.
-
.. seealso::
- :ref:`mapper_querytime_expression`
+ :ref:`orm_queryguide_with_expression` - background and usage
+ examples
"""
@@ -1013,39 +1014,13 @@ class Load(_AbstractLoad):
The :class:`_orm.Load` object is in most cases used implicitly behind the
scenes when one makes use of a query option like :func:`_orm.joinedload`,
- :func:`.defer`, or similar. However, the :class:`_orm.Load` object
- can also be used directly, and in some cases can be useful.
-
- To use :class:`_orm.Load` directly, instantiate it with the target mapped
- class as the argument. This style of usage is
- useful when dealing with a statement
- that has multiple entities::
-
- myopt = Load(MyClass).joinedload("widgets")
-
- The above ``myopt`` can now be used with :meth:`_sql.Select.options` or
- :meth:`_query.Query.options` where it
- will only take effect for the ``MyClass`` entity::
-
- stmt = select(MyClass, MyOtherClass).options(myopt)
-
- One case where :class:`_orm.Load`
- is useful as public API is when specifying
- "wildcard" options that only take effect for a certain class::
-
- stmt = select(Order).options(Load(Order).lazyload('*'))
-
- Above, all relationships on ``Order`` will be lazy-loaded, but other
- attributes on those descendant objects will load using their normal
- loader strategy.
+ :func:`_orm.defer`, or similar. It typically is not instantiated directly
+ except for in some very specific cases.
.. seealso::
- :ref:`deferred_options`
-
- :ref:`deferred_loading_w_multiple`
-
- :ref:`relationship_loader_options`
+ :ref:`orm_queryguide_relationship_per_entity_wildcard` - illustrates an
+ example where direct use of :class:`_orm.Load` may be useful
"""
@@ -1239,9 +1214,7 @@ class Load(_AbstractLoad):
:func:`.defaultload`
- :ref:`relationship_loader_options`
-
- :ref:`deferred_loading_w_multiple`
+ :ref:`orm_queryguide_relationship_sub_options`
"""
for opt in opts:
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index 9a5399af0..b92969f77 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -886,7 +886,7 @@ class AliasedInsp(
def _with_polymorphic_factory(
cls,
base: Union[_O, Mapper[_O]],
- classes: Iterable[_EntityType[Any]],
+ classes: Union[Literal["*"], Iterable[_EntityType[Any]]],
selectable: Union[Literal[False, None], FromClause] = False,
flat: bool = False,
polymorphic_on: Optional[ColumnElement[Any]] = None,
@@ -1379,8 +1379,6 @@ class Bundle(
allowing post-processing as well as custom return types, without
involving ORM identity-mapped classes.
- .. versionadded:: 0.9.0
-
.. seealso::
:ref:`bundles`
@@ -1538,11 +1536,35 @@ class Bundle(
) -> Callable[[Row[Any]], Any]:
"""Produce the "row processing" function for this :class:`.Bundle`.
- May be overridden by subclasses.
+ May be overridden by subclasses to provide custom behaviors when
+ results are fetched. The method is passed the statement object and a
+ set of "row processor" functions at query execution time; these
+ processor functions when given a result row will return the individual
+ attribute value, which can then be adapted into any kind of return data
+ structure.
+
+ The example below illustrates replacing the usual :class:`.Row`
+ return structure with a straight Python dictionary::
+
+ from sqlalchemy.orm import Bundle
+
+ class DictBundle(Bundle):
+ def create_row_processor(self, query, procs, labels):
+ 'Override create_row_processor to return values as
+ dictionaries'
+
+ def proc(row):
+ return dict(
+ zip(labels, (proc(row) for proc in procs))
+ )
+ return proc
- .. seealso::
+ A result from the above :class:`_orm.Bundle` will return dictionary
+ values::
- :ref:`bundles` - includes an example of subclassing.
+ bn = DictBundle('mybundle', MyClass.data1, MyClass.data2)
+ for row in session.execute(select(bn)).where(bn.c.data1 == 'd1'):
+ print(row.mybundle['data1'], row.mybundle['data2'])
"""
keyed_tuple = result_tuple(labels, [() for l in labels])
diff --git a/lib/sqlalchemy/sql/_dml_constructors.py b/lib/sqlalchemy/sql/_dml_constructors.py
index 293d225f9..889865649 100644
--- a/lib/sqlalchemy/sql/_dml_constructors.py
+++ b/lib/sqlalchemy/sql/_dml_constructors.py
@@ -57,9 +57,9 @@ def insert(table: _DMLTableArgument) -> Insert:
backends that support "returning", this turns off the "implicit
returning" feature for the statement.
- If both :paramref:`_expression.Insert.values` and compile-time bind
+ If both :paramref:`_expression.insert.values` and compile-time bind
parameters are present, the compile-time bind parameters override the
- information specified within :paramref:`_expression.Insert.values` on a
+ information specified within :paramref:`_expression.insert.values` on a
per-key basis.
The keys within :paramref:`_expression.Insert.values` can be either
diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py
index fbbf9f7f7..2a8479c6d 100644
--- a/lib/sqlalchemy/sql/base.py
+++ b/lib/sqlalchemy/sql/base.py
@@ -1068,7 +1068,7 @@ class Executable(roles.StatementRole):
.. seealso::
- :ref:`deferred_options` - refers to options specific to the usage
+ :ref:`loading_columns` - refers to options specific to the usage
of ORM queries
:ref:`relationship_loader_options` - refers to options specific
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index ff21b4584..8de93c2b4 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -5574,19 +5574,29 @@ class Select(
@_generative
def add_columns(
- self, *columns: _ColumnsClauseArgument[Any]
+ self, *entities: _ColumnsClauseArgument[Any]
) -> Select[Any]:
- """Return a new :func:`_expression.select` construct with
- the given column expressions added to its columns clause.
+ r"""Return a new :func:`_expression.select` construct with
+ the given entities appended to its columns clause.
E.g.::
my_select = my_select.add_columns(table.c.new_column)
- See the documentation for
- :meth:`_expression.Select.with_only_columns`
- for guidelines on adding /replacing the columns of a
- :class:`_expression.Select` object.
+ The original expressions in the columns clause remain in place.
+ To replace the original expressions with new ones, see the method
+ :meth:`_expression.Select.with_only_columns`.
+
+ :param \*entities: column, table, or other entity expressions to be
+ added to the columns clause
+
+ .. seealso::
+
+ :meth:`_expression.Select.with_only_columns` - replaces existing
+ expressions rather than appending.
+
+ :ref:`orm_queryguide_select_multiple_entities` - ORM-centric
+ example
"""
self._reset_memoizations()
@@ -5595,7 +5605,7 @@ class Select(
coercions.expect(
roles.ColumnsClauseRole, column, apply_propagate_attrs=self
)
- for column in columns
+ for column in entities
]
return self
@@ -5751,7 +5761,7 @@ class Select(
@overload
def with_only_columns(
self,
- *columns: _ColumnsClauseArgument[Any],
+ *entities: _ColumnsClauseArgument[Any],
maintain_column_froms: bool = False,
**__kw: Any,
) -> Select[Any]:
@@ -5760,16 +5770,16 @@ class Select(
@_generative
def with_only_columns(
self,
- *columns: _ColumnsClauseArgument[Any],
+ *entities: _ColumnsClauseArgument[Any],
maintain_column_froms: bool = False,
**__kw: Any,
) -> Select[Any]:
r"""Return a new :func:`_expression.select` construct with its columns
- clause replaced with the given columns.
+ clause replaced with the given entities.
By default, this method is exactly equivalent to as if the original
- :func:`_expression.select` had been called with the given columns
- clause. E.g. a statement::
+ :func:`_expression.select` had been called with the given entities.
+ E.g. a statement::
s = select(table1.c.a, table1.c.b)
s = s.with_only_columns(table1.c.b)
@@ -5803,11 +5813,7 @@ class Select(
s = select(table1.c.a, table2.c.b)
s = s.select_from(*s.columns_clause_froms).with_only_columns(table1.c.a)
- :param \*columns: column expressions to be used.
-
- .. versionchanged:: 1.4 the :meth:`_sql.Select.with_only_columns`
- method accepts the list of column expressions positionally;
- passing the expressions as a list is deprecated.
+ :param \*entities: column expressions to be used.
:param maintain_column_froms: boolean parameter that will ensure the
FROM list implied from the current columns clause will be transferred
@@ -5836,7 +5842,7 @@ class Select(
self._raw_columns = [
coercions.expect(roles.ColumnsClauseRole, c)
for c in coercions._expression_collection_was_a_list(
- "columns", "Select.with_only_columns", columns
+ "columns", "Select.with_only_columns", entities
)
]
return self
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index 3a0fc818d..1d6f1103d 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -1415,6 +1415,12 @@ class SuiteRequirements(Requirements):
)
@property
+ def python310(self):
+ return exclusions.only_if(
+ lambda: util.py310, "Python 3.10 or above required"
+ )
+
+ @property
def cpython(self):
return exclusions.only_if(
lambda: util.cpython, "cPython interpreter needed"