--- /dev/null
+.. change::
+ :tags: usecase, orm
+ :tickets: 6955
+
+ Added loader options to :meth:`_orm.Session.merge` and
+ :meth:`_asyncio.AsyncSession.merge`, which will apply the given loader
+ options to the ``get()`` used internally by merge, allowing eager loading
+ of relationships etc. to be applied when the merge process loads a new
+ object. Pull request courtesy Daniel Stone.
This is the async version of the :meth:`_orm.Session.refresh` method.
See that method for a complete description of all options.
+ .. seealso::
+
+ :meth:`_orm.Session.refresh` - main documentation for refresh
+
"""
return await greenlet_spawn(
):
"""Execute a statement and return a buffered
:class:`_engine.Result` object.
+
+ .. seealso::
+
+ :meth:`_orm.Session.execute` - main documentation for execute
+
"""
if execution_options:
bind_arguments=None,
**kw
):
- """Execute a statement and return a scalar result."""
+ """Execute a statement and return a scalar result.
+
+ .. seealso::
+
+ :meth:`_orm.Session.scalar` - main documentation for scalar
+
+ """
result = await self.execute(
statement,
"""Return an instance based on the given primary key identifier,
or ``None`` if not found.
+ .. seealso::
+
+ :meth:`_orm.Session.get` - main documentation for get
+
"""
return await greenlet_spawn(
As this operation may need to cascade along unloaded relationships,
it is awaitable to allow for those queries to take place.
+ .. seealso::
+
+ :meth:`_orm.Session.delete` - main documentation for delete
"""
return await greenlet_spawn(self.sync_session.delete, instance)
- async def merge(self, instance, load=True):
+ async def merge(self, instance, load=True, options=None):
"""Copy the state of a given instance into a corresponding instance
within this :class:`_asyncio.AsyncSession`.
+ .. seealso::
+
+ :meth:`_orm.Session.merge` - main documentation for merge
+
"""
return await greenlet_spawn(
- self.sync_session.merge, instance, load=load
+ self.sync_session.merge, instance, load=load, options=options
)
async def flush(self, objects=None):
.. seealso::
- :meth:`_orm.Session.flush`
+ :meth:`_orm.Session.flush` - main documentation for flush
"""
await greenlet_spawn(self.sync_session.flush, objects=objects)
r"""Return a :class:`_asyncio.AsyncConnection` object corresponding to
this :class:`.Session` object's transactional state.
+ This method may also be used to establish execution options for the
+ database connection used by the current transaction.
+
.. versionadded:: 1.4.24 Added **kw arguments which are passed through
to the underlying :meth:`_orm.Session.connection` method.
+ .. seealso::
+
+ :meth:`_orm.Session.connection` - main documentation for
+ "connection"
+
"""
sync_connection = await greenlet_spawn(
load_options=load_options,
)
- def merge(self, instance, load=True):
+ def merge(self, instance, load=True, options=None):
"""Copy the state of a given instance into a corresponding instance
within this :class:`.Session`.
produced as "clean", so it is only appropriate that the given objects
should be "clean" as well, else this suggests a mis-use of the
method.
+ :param options: optional sequence of loader options which will be
+ applied to the :meth:`_orm.Session.get` method when the merge
+ operation loads the existing version of the object from the database.
+
+ .. versionadded:: 1.4.24
.. seealso::
attributes.instance_state(instance),
attributes.instance_dict(instance),
load=load,
+ options=options,
_recursive=_recursive,
_resolve_conflict_map=_resolve_conflict_map,
)
state,
state_dict,
load=True,
+ options=None,
_recursive=None,
_resolve_conflict_map=None,
):
new_instance = True
elif key_is_persistent:
- merged = self.get(mapper.class_, key[1], identity_token=key[2])
+ merged = self.get(
+ mapper.class_,
+ key[1],
+ identity_token=key[2],
+ options=options,
+ )
if merged is None:
merged = mapper.class_manager.new_instance()
u3 = await async_session.get(User, 12)
is_(u3, None)
+ @async_test
+ async def test_get_loader_options(self, async_session):
+ User = self.classes.User
+
+ u = await async_session.get(
+ User, 7, options=[selectinload(User.addresses)]
+ )
+
+ eq_(u.name, "jack")
+ eq_(len(u.__dict__["addresses"]), 1)
+
@async_test
@testing.requires.independent_cursors
@testing.combinations(
is_(new_u_merged, u1)
eq_(u1.name, "new u1")
+ @async_test
+ async def test_merge_loader_options(self, async_session):
+ User = self.classes.User
+ Address = self.classes.Address
+
+ async with async_session.begin():
+ u1 = User(id=1, name="u1", addresses=[Address(email_address="e1")])
+
+ async_session.add(u1)
+
+ await async_session.close()
+
+ async with async_session.begin():
+ new_u1 = User(id=1, name="new u1")
+
+ new_u_merged = await async_session.merge(
+ new_u1, options=[selectinload(User.addresses)]
+ )
+
+ eq_(new_u_merged.name, "new u1")
+ eq_(len(new_u_merged.__dict__["addresses"]), 1)
+
@async_test
async def test_join_to_external_transaction(self, async_engine):
User = self.classes.User
from sqlalchemy.orm import foreign
from sqlalchemy.orm import mapper
from sqlalchemy.orm import relationship
+from sqlalchemy.orm import selectinload
from sqlalchemy.orm import Session
from sqlalchemy.orm import synonym
from sqlalchemy.orm.collections import attribute_mapped_collection
return canary
+ def test_loader_options(self):
+ User, Address, addresses, users = (
+ self.classes.User,
+ self.classes.Address,
+ self.tables.addresses,
+ self.tables.users,
+ )
+
+ mapper(
+ User,
+ users,
+ properties={"addresses": relationship(Address, backref="user")},
+ )
+ mapper(Address, addresses)
+
+ s = fixture_session()
+ u = User(
+ id=7,
+ name="fred",
+ addresses=[Address(id=1, email_address="jack@bean.com")],
+ )
+ s.add(u)
+ s.commit()
+ s.close()
+
+ u = User(id=7, name="fred")
+ u2 = s.merge(u, options=[selectinload(User.addresses)])
+
+ eq_(len(u2.__dict__["addresses"]), 1)
+
def test_transient_to_pending(self):
User, users = self.classes.User, self.tables.users