From f5284a5b655315f960e39e034b3a0763d619c74c Mon Sep 17 00:00:00 2001 From: Ryan Soklaski Date: Fri, 30 Jun 2023 10:49:03 -0400 Subject: [PATCH 1/2] update builds signature inspection to be consistent with python 3.11.4 compat-breaking change to inspect.signature --- .../structured_configs/_implementations.py | 53 ++++++++++--------- tests/test_signature_parsing.py | 12 ++--- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/hydra_zen/structured_configs/_implementations.py b/src/hydra_zen/structured_configs/_implementations.py index b189c9aeb..7fdbeabba 100644 --- a/src/hydra_zen/structured_configs/_implementations.py +++ b/src/hydra_zen/structured_configs/_implementations.py @@ -804,6 +804,26 @@ def sanitized_field( return _utils.field(default=value, init=init) +def _get_sig_obj(target): + if not inspect.isclass(target): + return target + + # This implements the same method prioritization as + # `inspect.signature` for Python >= 3.9.1 + if "__new__" in target.__dict__: + return target.__new__ + if "__init__" in target.__dict__: + return target.__init__ + + if len(target.__mro__) > 2: + for parent in target.__mro__[1:-1]: + if "__new__" in parent.__dict__: + return target.__new__ + elif "__init__" in parent.__dict__: + return target.__init__ + return getattr(target, "__init__", target) + + # partial=False, pop-sig=True; no *args, **kwargs, nor builds_bases @overload def builds( @@ -1854,6 +1874,8 @@ def builds(target, populate_full_signature=False, **kw): ) ) + _sig_target = _get_sig_obj(target) + try: # We want to rely on `inspect.signature` logic for raising # against an uninspectable sig, before we start inspecting @@ -1884,14 +1906,9 @@ def builds(target, populate_full_signature=False, **kw): # This looks specifically for the scenario that the target # has inherited from a parent that implements __new__ and # the target implements only __init__. - if ( - inspect.isclass(target) - and len(target.__mro__) > 2 - and "__init__" in target.__dict__ - and "__new__" not in target.__dict__ - and any("__new__" in parent.__dict__ for parent in target.__mro__[1:-1]) - ): - _params = tuple(inspect.signature(target.__init__).parameters.items()) + + if _sig_target is not target: + _params = tuple(inspect.signature(_sig_target).parameters.items()) if ( _params and _params[0][1].kind is not _VAR_POSITIONAL @@ -1940,25 +1957,9 @@ def builds(target, populate_full_signature=False, **kw): # `get_type_hints` properly resolves forward references, whereas annotations from # `inspect.signature` do not try: - if inspect.isclass(target): - # This implements the same method prioritization as - # `inspect.signature` for Python >= 3.9.1 - if "__new__" in target.__dict__: - _annotation_target = target.__new__ - elif "__init__" in target.__dict__: - _annotation_target = target.__init__ - elif len(target.__mro__) > 2 and any( - "__new__" in parent.__dict__ for parent in target.__mro__[1:-1] - ): - _annotation_target = target.__new__ - else: - _annotation_target = target.__init__ - else: - _annotation_target = target - - type_hints = get_type_hints(_annotation_target) + type_hints = get_type_hints(_sig_target) - del _annotation_target + del _sig_target # We don't need to pop self/class because we only make on-demand # requests from `type_hints` diff --git a/tests/test_signature_parsing.py b/tests/test_signature_parsing.py index fc553aaf6..22055b0af 100644 --- a/tests/test_signature_parsing.py +++ b/tests/test_signature_parsing.py @@ -510,32 +510,32 @@ def test_populate_annotated_enum_regression(): class A: # Manually verified using `inspect.signature` after # the fix of https://bugs.python.org/issue40897 - py_310_sig = (("a", int),) + expected_sig = (("a", int),) def __new__(cls, a: int): return object.__new__(cls) class B(A): - py_310_sig = (("b", float),) + expected_sig = (("b", float),) def __init__(self, b: float): pass class C(A): - py_310_sig = (("c", str),) + expected_sig = (("c", str),) def __new__(cls, c: str): return object.__new__(cls) class D(A): - py_310_sig = (("a", int),) + expected_sig = (("a", int),) class E(B): - py_310_sig = (("a", int),) + expected_sig = (("b", float),) @pytest.mark.parametrize("Obj", [A, B, C, D, E]) @@ -548,7 +548,7 @@ def test_parse_sig_with_new_vs_init(Obj): (p.name, p.annotation) for p in inspect.signature(Conf).parameters.values() ) - assert sig_via_builds == Obj.py_310_sig + assert sig_via_builds == Obj.expected_sig def test_Counter(): From 19f222c05626c2241820b1eade1209c45da81bd5 Mon Sep 17 00:00:00 2001 From: Ryan Soklaski Date: Fri, 30 Jun 2023 11:02:11 -0400 Subject: [PATCH 2/2] update changelog --- docs/source/changes.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/changes.rst b/docs/source/changes.rst index 49140b090..167efe235 100644 --- a/docs/source/changes.rst +++ b/docs/source/changes.rst @@ -51,7 +51,7 @@ hydra-zen now uses the `trusted publishers