]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Allow inspection registrations from module reloads
authorw-Jessamine <148705640+w-Jessamine@users.noreply.github.com>
Mon, 29 Jun 2026 10:30:16 +0000 (06:30 -0400)
committerMichael Bayer <mike_mp@zzzcomputing.com>
Thu, 9 Jul 2026 03:34:59 +0000 (03:34 +0000)
Allowed the inspection registry to replace an existing registration with a
reloaded callable from the same module and name. This avoids an assertion
failure for tooling that unloads and reloads SQLAlchemy modules while still
rejecting conflicting registrations. Pull request courtersy w-Jessamine.

Fixes: #10748
Closes: #13402
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13402
Pull-request-sha: e54815ef0092da05277efab79b90b77e44734cb9

Change-Id: If2565206d6b861993df8c361511551e204120b5e

doc/build/changelog/unreleased_21/10748.rst [new file with mode: 0644]
lib/sqlalchemy/inspection.py
test/base/test_inspect.py

diff --git a/doc/build/changelog/unreleased_21/10748.rst b/doc/build/changelog/unreleased_21/10748.rst
new file mode 100644 (file)
index 0000000..b27d8d4
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: bug, general
+    :tickets: 10748
+
+    Allowed the inspection registry to replace an existing registration with a
+    reloaded callable from the same module and name. This avoids an assertion
+    failure for tooling that unloads and reloads SQLAlchemy modules while still
+    rejecting conflicting registrations. Pull request courtersy w-Jessamine.
index 2758a527bd806b92f623404c3c85f7f10d7ee6d3..5187483896f401ce6938abda1bb5a5610c8a7ecc 100644 (file)
@@ -158,7 +158,20 @@ def _inspects(
     def decorate(fn_or_cls: _F) -> _F:
         for type_ in types:
             if type_ in _registrars:
-                raise AssertionError("Type %s is already registered" % type_)
+                existing = _registrars[type_]
+                existing_name = getattr(existing, "__name__", None)
+                existing_module = getattr(existing, "__module__", None)
+
+                # allow a new version of the same function or class, that is,
+                # a module reload; compare name/module attributes because
+                # classes may define custom __eq__ behavior.
+                if (
+                    existing_name != fn_or_cls.__name__
+                    or existing_module != fn_or_cls.__module__
+                ):
+                    raise AssertionError(
+                        "Type %s is already registered" % type_
+                    )
             _registrars[type_] = fn_or_cls
         return fn_or_cls
 
index fd433ce99aa16499b41b80459f8efd200218e647..cd90f9f308f5e5671091f313ac8d0e7ffa2b7f8a 100644 (file)
@@ -74,3 +74,36 @@ class TestInspection(fixtures.TestBase):
         SomeFoo()
         eq_(inspect(SomeFoo()), 1)
         eq_(inspect(SomeSubFoo()), 2)
+
+    def test_inspects_allows_reloaded_registrar(self):
+        """test #10748"""
+
+        class SomeFoo(TestFixture):
+            pass
+
+        @inspection._inspects(SomeFoo)
+        def insp_somefoo(subject):
+            return 1
+
+        reloaded_insp_somefoo = type(insp_somefoo)(
+            insp_somefoo.__code__,
+            insp_somefoo.__globals__,
+            insp_somefoo.__name__,
+            insp_somefoo.__defaults__,
+            insp_somefoo.__closure__,
+        )
+        reloaded_insp_somefoo.__kwdefaults__ = insp_somefoo.__kwdefaults__
+
+        def insp_other_somefoo(subject):
+            return 2
+
+        assert_raises_message(
+            AssertionError,
+            "Type .* is already registered",
+            inspection._inspects(SomeFoo),
+            insp_other_somefoo,
+        )
+
+        inspection._inspects(SomeFoo)(reloaded_insp_somefoo)
+        assert inspection._registrars[SomeFoo] is reloaded_insp_somefoo
+        eq_(inspect(SomeFoo()), 1)