--- /dev/null
+.. change::
+ :tags: orm, usecase
+ :tickets: 11776
+
+ Added the utility method :meth:`_orm.Session.merge_all` and
+ :meth:`_orm.Session.delete_all` that operate on a collection
+ of instances.
"commit",
"connection",
"delete",
+ "delete_all",
"execute",
"expire",
"expire_all",
"is_modified",
"invalidate",
"merge",
+ "merge_all",
"refresh",
"rollback",
"scalar",
return await self._proxied.aclose()
- def add(self, instance: object, _warn: bool = True) -> None:
+ def add(self, instance: object, *, _warn: bool = True) -> None:
r"""Place an object into this :class:`_orm.Session`.
.. container:: class_bases
return await self._proxied.delete(instance)
+ async def delete_all(self, instances: Iterable[object]) -> None:
+ r"""Calls :meth:`.AsyncSession.delete` on multiple instances.
+
+ .. container:: class_bases
+
+ Proxied for the :class:`_asyncio.AsyncSession` class on
+ behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
+
+ .. seealso::
+
+ :meth:`_orm.Session.delete_all` - main documentation for delete_all
+
+
+ """ # noqa: E501
+
+ return await self._proxied.delete_all(instances)
+
@overload
async def execute(
self,
return await self._proxied.merge(instance, load=load, options=options)
+ async def merge_all(
+ self,
+ instances: Iterable[_O],
+ *,
+ load: bool = True,
+ options: Optional[Sequence[ORMOption]] = None,
+ ) -> Sequence[_O]:
+ r"""Calls :meth:`.AsyncSession.merge` on multiple instances.
+
+ .. container:: class_bases
+
+ Proxied for the :class:`_asyncio.AsyncSession` class on
+ behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
+
+ .. seealso::
+
+ :meth:`_orm.Session.merge_all` - main documentation for merge_all
+
+
+ """ # noqa: E501
+
+ return await self._proxied.merge_all(
+ instances, load=load, options=options
+ )
+
async def refresh(
self,
instance: object,
"""
await greenlet_spawn(self.sync_session.delete, instance)
+ async def delete_all(self, instances: Iterable[object]) -> None:
+ """Calls :meth:`.AsyncSession.delete` on multiple instances.
+
+ .. seealso::
+
+ :meth:`_orm.Session.delete_all` - main documentation for delete_all
+
+ """
+ await greenlet_spawn(self.sync_session.delete_all, instances)
+
async def merge(
self,
instance: _O,
self.sync_session.merge, instance, load=load, options=options
)
+ async def merge_all(
+ self,
+ instances: Iterable[_O],
+ *,
+ load: bool = True,
+ options: Optional[Sequence[ORMOption]] = None,
+ ) -> Sequence[_O]:
+ """Calls :meth:`.AsyncSession.merge` on multiple instances.
+
+ .. seealso::
+
+ :meth:`_orm.Session.merge_all` - main documentation for merge_all
+
+ """
+ return await greenlet_spawn(
+ self.sync_session.merge_all, instances, load=load, options=options
+ )
+
async def flush(self, objects: Optional[Sequence[Any]] = None) -> None:
"""Flush all the object changes to the database.
return self._proxied.__iter__()
- def add(self, instance: object, _warn: bool = True) -> None:
+ def add(self, instance: object, *, _warn: bool = True) -> None:
r"""Place an object into this :class:`_orm.Session`.
.. container:: class_bases
statement, legacy=False
)
- autoflush = session.autoflush
- try:
- session.autoflush = False
+ with session.no_autoflush:
mapped_entities = [
i
for i, e in enumerate(ctx._entities)
result.append(keyed_tuple(newrow))
return frozen_result.with_new_rows(result)
- finally:
- session.autoflush = autoflush
@util.became_legacy_20(
"commit",
"connection",
"delete",
+ "delete_all",
"execute",
"expire",
"expire_all",
"bulk_insert_mappings",
"bulk_update_mappings",
"merge",
+ "merge_all",
"query",
"refresh",
"rollback",
return self._proxied.__iter__()
- def add(self, instance: object, _warn: bool = True) -> None:
+ def add(self, instance: object, *, _warn: bool = True) -> None:
r"""Place an object into this :class:`_orm.Session`.
.. container:: class_bases
:ref:`session_deleting` - at :ref:`session_basics`
+ :meth:`.Session.delete_all` - multiple instance version
+
""" # noqa: E501
return self._proxied.delete(instance)
+ def delete_all(self, instances: Iterable[object]) -> None:
+ r"""Calls :meth:`.Session.delete` on multiple instances.
+
+ .. container:: class_bases
+
+ Proxied for the :class:`_orm.Session` class on
+ behalf of the :class:`_orm.scoping.scoped_session` class.
+
+ .. seealso::
+
+ :meth:`.Session.delete` - main documentation on delete
+
+ .. versionadded: 2.1
+
+
+ """ # noqa: E501
+
+ return self._proxied.delete_all(instances)
+
@overload
def execute(
self,
:func:`.make_transient_to_detached` - provides for an alternative
means of "merging" a single object into the :class:`.Session`
+ :meth:`.Session.merge_all` - multiple instance version
+
""" # noqa: E501
return self._proxied.merge(instance, load=load, options=options)
+ def merge_all(
+ self,
+ instances: Iterable[_O],
+ *,
+ load: bool = True,
+ options: Optional[Sequence[ORMOption]] = None,
+ ) -> Sequence[_O]:
+ r"""Calls :meth:`.Session.merge` on multiple instances.
+
+ .. container:: class_bases
+
+ Proxied for the :class:`_orm.Session` class on
+ behalf of the :class:`_orm.scoping.scoped_session` class.
+
+ .. seealso::
+
+ :meth:`.Session.merge` - main documentation on merge
+
+ .. versionadded: 2.1
+
+
+ """ # noqa: E501
+
+ return self._proxied.merge_all(instances, load=load, options=options)
+
@overload
def query(self, _entity: _EntityType[_O]) -> Query[_O]: ...
if persistent_to_deleted is not None:
persistent_to_deleted(self, state)
- def add(self, instance: object, _warn: bool = True) -> None:
+ def add(self, instance: object, *, _warn: bool = True) -> None:
"""Place an object into this :class:`_orm.Session`.
Objects that are in the :term:`transient` state when passed to the
:ref:`session_deleting` - at :ref:`session_basics`
+ :meth:`.Session.delete_all` - multiple instance version
+
"""
if self._warn_on_events:
self._flush_warning("Session.delete()")
- try:
- state = attributes.instance_state(instance)
- except exc.NO_STATE as err:
- raise exc.UnmappedInstanceError(instance) from err
+ self._delete_impl(object_state(instance), instance, head=True)
+
+ def delete_all(self, instances: Iterable[object]) -> None:
+ """Calls :meth:`.Session.delete` on multiple instances.
- self._delete_impl(state, instance, head=True)
+ .. seealso::
+
+ :meth:`.Session.delete` - main documentation on delete
+
+ .. versionadded: 2.1
+
+ """
+
+ if self._warn_on_events:
+ self._flush_warning("Session.delete_all()")
+
+ for instance in instances:
+ self._delete_impl(object_state(instance), instance, head=True)
def _delete_impl(
self, state: InstanceState[Any], obj: object, head: bool
:func:`.make_transient_to_detached` - provides for an alternative
means of "merging" a single object into the :class:`.Session`
+ :meth:`.Session.merge_all` - multiple instance version
+
"""
if self._warn_on_events:
self._flush_warning("Session.merge()")
- _recursive: Dict[InstanceState[Any], object] = {}
- _resolve_conflict_map: Dict[_IdentityKeyType[Any], object] = {}
-
if load:
# flush current contents if we expect to load data
self._autoflush()
- object_mapper(instance) # verify mapped
- autoflush = self.autoflush
- try:
- self.autoflush = False
+ with self.no_autoflush:
return self._merge(
- attributes.instance_state(instance),
+ object_state(instance),
attributes.instance_dict(instance),
load=load,
options=options,
- _recursive=_recursive,
- _resolve_conflict_map=_resolve_conflict_map,
+ _recursive={},
+ _resolve_conflict_map={},
)
- finally:
- self.autoflush = autoflush
+
+ def merge_all(
+ self,
+ instances: Iterable[_O],
+ *,
+ load: bool = True,
+ options: Optional[Sequence[ORMOption]] = None,
+ ) -> Sequence[_O]:
+ """Calls :meth:`.Session.merge` on multiple instances.
+
+ .. seealso::
+
+ :meth:`.Session.merge` - main documentation on merge
+
+ .. versionadded: 2.1
+
+ """
+
+ if self._warn_on_events:
+ self._flush_warning("Session.merge_all()")
+
+ if load:
+ # flush current contents if we expect to load data
+ self._autoflush()
+
+ return [
+ self._merge(
+ object_state(instance),
+ attributes.instance_dict(instance),
+ load=load,
+ options=options,
+ _recursive={},
+ _resolve_conflict_map={},
+ )
+ for instance in instances
+ ]
def _merge(
self,
eq_(sess.query(Address).one(), Address(id=1, email_address="c"))
+ def test_merge_all(self):
+ User, users = self.classes.User, self.tables.users
+
+ self.mapper_registry.map_imperatively(User, users)
+ sess = fixture_session()
+ load = self.load_tracker(User)
+
+ ua = User(id=42, name="bob")
+ ub = User(id=7, name="fred")
+ eq_(load.called, 0)
+ uam, ubm = sess.merge_all([ua, ub])
+ eq_(load.called, 2)
+ assert uam in sess
+ assert ubm in sess
+ eq_(uam, User(id=42, name="bob"))
+ eq_(ubm, User(id=7, name="fred"))
+ sess.flush()
+ sess.expunge_all()
+ eq_(
+ sess.query(User).order_by("id").all(),
+ [User(id=7, name="fred"), User(id=42, name="bob")],
+ )
+
class M2ONoUseGetLoadingTest(fixtures.MappedTest):
"""Merge a one-to-many. The many-to-one on the other side is set up
):
sess.get_one(User, 2)
+ def test_delete_all(self):
+ users, User = self.tables.users, self.classes.User
+ self.mapper_registry.map_imperatively(User, users)
+
+ sess = fixture_session()
+
+ sess.add_all([User(id=1, name="u1"), User(id=2, name="u2")])
+ sess.commit()
+ sess.close()
+
+ ua, ub = sess.scalars(select(User)).all()
+ eq_([ua in sess, ub in sess], [True, True])
+ sess.delete_all([ua, ub])
+ sess.flush()
+ eq_([ua in sess, ub in sess], [False, False])
+ eq_(sess.scalars(select(User)).all(), [])
+
class SessionStateTest(_fixtures.FixtureTest):
run_inserts = None
]:
raises_(name, user_arg)
- raises_("add_all", (user_arg,))
+ for name in ["add_all", "merge_all", "delete_all"]:
+ raises_(name, (user_arg,))
# flush will no-op without something in the unit of work
def _():
# TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_no_load
-test.aaa_profiling.test_orm.MergeTest.test_merge_no_load x86_64_linux_cpython_3.11_sqlite_pysqlite_dbapiunicode_cextensions 108,20
-test.aaa_profiling.test_orm.MergeTest.test_merge_no_load x86_64_linux_cpython_3.11_sqlite_pysqlite_dbapiunicode_nocextensions 108,20
-test.aaa_profiling.test_orm.MergeTest.test_merge_no_load x86_64_linux_cpython_3.12_sqlite_pysqlite_dbapiunicode_cextensions 108,20
-test.aaa_profiling.test_orm.MergeTest.test_merge_no_load x86_64_linux_cpython_3.12_sqlite_pysqlite_dbapiunicode_nocextensions 108,20
+test.aaa_profiling.test_orm.MergeTest.test_merge_no_load x86_64_linux_cpython_3.11_sqlite_pysqlite_dbapiunicode_cextensions 108,29
+test.aaa_profiling.test_orm.MergeTest.test_merge_no_load x86_64_linux_cpython_3.11_sqlite_pysqlite_dbapiunicode_nocextensions 108,29
+test.aaa_profiling.test_orm.MergeTest.test_merge_no_load x86_64_linux_cpython_3.12_sqlite_pysqlite_dbapiunicode_cextensions 108,29
+test.aaa_profiling.test_orm.MergeTest.test_merge_no_load x86_64_linux_cpython_3.12_sqlite_pysqlite_dbapiunicode_nocextensions 108,29
# TEST: test.aaa_profiling.test_orm.QueryTest.test_query_cols