diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-08-18 13:56:50 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-09-25 19:38:10 -0400 |
| commit | 81d8394c0b5342cdc603cb2e07e12139c9506bf6 (patch) | |
| tree | 5453f51ef80bb3b0b4705025070439fdccfea29c /lib/sqlalchemy | |
| parent | a8029f5a7e3e376ec57f1614ab0294b717d53c05 (diff) | |
| download | sqlalchemy-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.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/cursor.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/_orm_constructors.py | 31 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/events.py | 31 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 5 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 26 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 13 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/relationships.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/scoping.py | 170 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/session.py | 106 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategy_options.py | 85 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 34 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/_dml_constructors.py | 4 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/base.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 44 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/requirements.py | 6 |
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" |
