]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add ORMExecuteState mapper accessors
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 23 Dec 2020 15:43:51 +0000 (10:43 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 23 Dec 2020 15:43:51 +0000 (10:43 -0500)
Added :attr:`_orm.ORMExecuteState.bind_mapper` and
:attr:`_orm.ORMExecuteState.all_mappers` accessors to
:class:`_orm.ORMExecuteState` event object, so that handlers can respond to
the target mapper and/or mapped class or classes involved in an ORM
statement execution.

Change-Id: I2cfe3d422ce5df2559105d53a51135a583359bd9

doc/build/changelog/unreleased_14/orm_exec_mapper.rst [new file with mode: 0644]
lib/sqlalchemy/orm/session.py
test/orm/test_events.py

diff --git a/doc/build/changelog/unreleased_14/orm_exec_mapper.rst b/doc/build/changelog/unreleased_14/orm_exec_mapper.rst
new file mode 100644 (file)
index 0000000..709e79a
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: usecase, orm
+
+    Added :attr:`_orm.ORMExecuteState.bind_mapper` and
+    :attr:`_orm.ORMExecuteState.all_mappers` accessors to
+    :class:`_orm.ORMExecuteState` event object, so that handlers can respond to
+    the target mapper and/or mapped class or classes involved in an ORM
+    statement execution.
index 7b5fa2c733c7ba98852292eb29b0228e7fd9184a..1ec63fa40a0678c69a8ec8ce743b858397f49dad 100644 (file)
@@ -230,6 +230,71 @@ class ORMExecuteState(util.MemoizedSlots):
             _parent_execute_state=self,
         )
 
+    @property
+    def bind_mapper(self):
+        """Return the :class:`_orm.Mapper` that is the primary "bind" mapper.
+
+        For an :class:`_orm.ORMExecuteState` object invoking an ORM
+        statement, that is, the :attr:`_orm.ORMExecuteState.is_orm_statement`
+        attribute is ``True``, this attribute will return the
+        :class:`_orm.Mapper` that is considered to be the "primary" mapper
+        of the statement.   The term "bind mapper" refers to the fact that
+        a :class:`_orm.Session` object may be "bound" to multiple
+        :class:`_engine.Engine` objects keyed to mapped classes, and the
+        "bind mapper" determines which of those :class:`_engine.Engine` objects
+        would be selected.
+
+        For a statement that is invoked against a single mapped class,
+        :attr:`_orm.ORMExecuteState.bind_mapper` is intended to be a reliable
+        way of getting this mapper.
+
+        .. versionadded:: 1.4.0b2
+
+        .. seealso::
+
+            :attr:`_orm.ORMExecuteState.all_mappers`
+
+
+        """
+        return self.bind_arguments.get("mapper", None)
+
+    @property
+    def all_mappers(self):
+        """Return a sequence of all :class:`_orm.Mapper` objects that are
+        involved at the top level of this statement.
+
+        By "top level" we mean those :class:`_orm.Mapper` objects that would
+        be represented in the result set rows for a :func:`_sql.select`
+        query, or for a :func:`_dml.update` or :func:`_dml.delete` query,
+        the mapper that is the main subject of the UPDATE or DELETE.
+
+        .. versionadded:: 1.4.0b2
+
+        .. seealso::
+
+            :attr:`_orm.ORMExecuteState.bind_mapper`
+
+
+
+        """
+        if not self.is_orm_statement:
+            return []
+        elif self.is_select:
+            result = []
+            seen = set()
+            for d in self.statement.column_descriptions:
+                ent = d["entity"]
+                if ent:
+                    insp = inspect(ent, raiseerr=False)
+                    if insp and insp.mapper and insp.mapper not in seen:
+                        seen.add(insp.mapper)
+                        result.append(insp.mapper)
+            return result
+        elif self.is_update or self.is_delete:
+            return [self.bind_mapper]
+        else:
+            return []
+
     @property
     def is_orm_statement(self):
         """return True if the operation is an ORM statement.
index a046ba34c7629972ba44ddb61d32d9b3ce3ec2bb..fb05f6601c817b66008eb7600c5d70f353e1f803 100644 (file)
@@ -2,6 +2,7 @@ import sqlalchemy as sa
 from sqlalchemy import delete
 from sqlalchemy import event
 from sqlalchemy import ForeignKey
+from sqlalchemy import inspect
 from sqlalchemy import Integer
 from sqlalchemy import literal_column
 from sqlalchemy import select
@@ -184,6 +185,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
                 )
 
             canary.options(
+                bind_mapper=ctx.bind_mapper,
+                all_mappers=ctx.all_mappers,
                 is_select=ctx.is_select,
                 is_update=ctx.is_update,
                 is_delete=ctx.is_delete,
@@ -197,6 +200,87 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
 
         return canary
 
+    def test_all_mappers_accessor_one(self):
+        User, Address = self.classes("User", "Address")
+
+        sess = Session(testing.db, future=True)
+
+        canary = self._flag_fixture(sess)
+
+        sess.execute(
+            select(User.id, Address.email_address, User.name)
+            .join(Address)
+            .filter_by(id=7)
+        )
+
+        eq_(
+            canary.mock_calls,
+            [
+                call.options(
+                    bind_mapper=inspect(User),
+                    all_mappers=[inspect(User), inspect(Address)],
+                    is_select=True,
+                    is_update=False,
+                    is_delete=False,
+                    is_orm_statement=True,
+                    is_relationship_load=False,
+                    is_column_load=False,
+                    lazy_loaded_from=None,
+                )
+            ],
+        )
+
+    def test_all_mappers_accessor_two(self):
+
+        sess = Session(testing.db, future=True)
+
+        canary = self._flag_fixture(sess)
+
+        sess.execute(select(self.tables.users).filter_by(id=7))
+
+        eq_(
+            canary.mock_calls,
+            [
+                call.options(
+                    bind_mapper=None,
+                    all_mappers=[],
+                    is_select=True,
+                    is_update=False,
+                    is_delete=False,
+                    is_orm_statement=False,
+                    is_relationship_load=False,
+                    is_column_load=False,
+                    lazy_loaded_from=None,
+                )
+            ],
+        )
+
+    def test_all_mappers_accessor_three(self):
+        User, Address = self.classes("User", "Address")
+
+        sess = Session(testing.db, future=True)
+
+        canary = self._flag_fixture(sess)
+
+        sess.execute(select(User).join(Address).filter_by(id=7))
+
+        eq_(
+            canary.mock_calls,
+            [
+                call.options(
+                    bind_mapper=inspect(User),
+                    all_mappers=[inspect(User)],  # Address not in results
+                    is_select=True,
+                    is_update=False,
+                    is_delete=False,
+                    is_orm_statement=True,
+                    is_relationship_load=False,
+                    is_column_load=False,
+                    lazy_loaded_from=None,
+                )
+            ],
+        )
+
     def test_select_flags(self):
         User, Address = self.classes("User", "Address")
 
@@ -214,6 +298,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
             canary.mock_calls,
             [
                 call.options(
+                    bind_mapper=inspect(User),
+                    all_mappers=[inspect(User)],
                     is_select=True,
                     is_update=False,
                     is_delete=False,
@@ -223,6 +309,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
                     lazy_loaded_from=None,
                 ),
                 call.options(
+                    bind_mapper=inspect(User),
+                    all_mappers=[inspect(User)],
                     is_select=True,
                     is_update=False,
                     is_delete=False,
@@ -249,6 +337,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
             canary.mock_calls,
             [
                 call.options(
+                    bind_mapper=inspect(User),
+                    all_mappers=[inspect(User)],
                     is_select=True,
                     is_update=False,
                     is_delete=False,
@@ -258,6 +348,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
                     lazy_loaded_from=None,
                 ),
                 call.options(
+                    bind_mapper=inspect(Address),
+                    all_mappers=[inspect(Address)],
                     is_select=True,
                     is_update=False,
                     is_delete=False,
@@ -286,6 +378,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
             canary.mock_calls,
             [
                 call.options(
+                    bind_mapper=inspect(User),
+                    all_mappers=[inspect(User)],
                     is_select=True,
                     is_update=False,
                     is_delete=False,
@@ -295,6 +389,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
                     lazy_loaded_from=None,
                 ),
                 call.options(
+                    bind_mapper=inspect(Address),
+                    all_mappers=[inspect(Address)],
                     is_select=True,
                     is_update=False,
                     is_delete=False,
@@ -323,6 +419,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
             canary.mock_calls,
             [
                 call.options(
+                    bind_mapper=inspect(User),
+                    all_mappers=[inspect(User)],
                     is_select=True,
                     is_update=False,
                     is_delete=False,
@@ -332,6 +430,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
                     lazy_loaded_from=None,
                 ),
                 call.options(
+                    bind_mapper=inspect(Address),
+                    all_mappers=[inspect(Address), inspect(User)],
                     is_select=True,
                     is_update=False,
                     is_delete=False,
@@ -357,6 +457,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
             canary.mock_calls,
             [
                 call.options(
+                    bind_mapper=inspect(User),
+                    all_mappers=[inspect(User)],
                     is_select=False,
                     is_update=False,
                     is_delete=True,
@@ -366,6 +468,8 @@ class ORMExecuteTest(_RemoveListeners, _fixtures.FixtureTest):
                     lazy_loaded_from=None,
                 ),
                 call.options(
+                    bind_mapper=inspect(User),
+                    all_mappers=[inspect(User)],
                     is_select=False,
                     is_update=True,
                     is_delete=False,