--- /dev/null
+.. 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.
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
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)