Skip to content

Commit

Permalink
fix forward_ref_context not always being set
Browse files Browse the repository at this point in the history
  • Loading branch information
Aran-Fey committed Dec 21, 2024
1 parent 73d645d commit 8a97f9c
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 11 deletions.
2 changes: 1 addition & 1 deletion introspection/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
New and improved introspection functions
"""

__version__ = "1.9.6"
__version__ = "1.9.7"

from .parameter import *
from .signature_ import *
Expand Down
22 changes: 16 additions & 6 deletions introspection/parameter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import inspect
from typing import *
from typing_extensions import Self
import typing as t
import typing_extensions as te

from ._utils import PARAM_EMPTY
from .types import ForwardRefContext
Expand Down Expand Up @@ -45,9 +45,9 @@ def __init__(
self,
name: str,
kind: inspect._ParameterKind = inspect.Parameter.POSITIONAL_OR_KEYWORD,
default: Any = PARAM_EMPTY,
annotation: Any = PARAM_EMPTY,
forward_ref_context: Optional[ForwardRefContext] = None,
default: t.Any = PARAM_EMPTY,
annotation: t.Any = PARAM_EMPTY,
forward_ref_context: t.Optional[ForwardRefContext] = None,
):
"""
:param name: The parameter's name
Expand All @@ -66,7 +66,7 @@ def __init__(
self.forward_ref_context = forward_ref_context

@classmethod
def from_parameter(cls, parameter: inspect.Parameter) -> Self:
def from_parameter(cls, parameter: inspect.Parameter) -> te.Self:
"""
Creates a new :class:`Parameter` instance from an :class:`inspect.Parameter` instance.
Expand All @@ -75,12 +75,22 @@ def from_parameter(cls, parameter: inspect.Parameter) -> Self:
"""
if isinstance(parameter, cls):
return parameter
else:
return cls._from_inspect_parameter(parameter)

@classmethod
def _from_inspect_parameter(
cls,
parameter: inspect.Parameter,
*,
forward_ref_context: t.Optional[ForwardRefContext] = None,
) -> te.Self:
return cls(
parameter.name,
kind=parameter.kind,
default=parameter.default,
annotation=parameter.annotation,
forward_ref_context=forward_ref_context,
)

@property
Expand Down
25 changes: 22 additions & 3 deletions introspection/signature_.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,25 @@ def from_signature(
"""
if isinstance(signature, cls):
return signature
else:
return cls._from_inspect_signature(signature)

params = [Parameter.from_parameter(param) for param in signature.parameters.values()]
return cls(params, return_annotation=signature.return_annotation)
@classmethod
def _from_inspect_signature(
cls,
signature: inspect.Signature,
*,
forward_ref_context: t.Optional[ForwardRefContext] = None,
) -> te.Self:
params = [
Parameter._from_inspect_parameter(param, forward_ref_context=forward_ref_context)
for param in signature.parameters.values()
]
return cls(
params,
return_annotation=signature.return_annotation,
forward_ref_context=forward_ref_context,
)

@classmethod
def from_callable( # type: ignore[incompatible-override]
Expand Down Expand Up @@ -212,7 +228,10 @@ def recurse(callable_: t.Callable) -> te.Self:
except AttributeError:
pass
else:
return cls.from_signature(sig)
if isinstance(sig, cls):
return sig

return cls._from_inspect_signature(sig, forward_ref_context=callable_.__module__)

# Are we even supposed to unwrap? If not, abort
if not follow_wrapped:
Expand Down
2 changes: 1 addition & 1 deletion introspection/typing/type_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ def __init__(

self.annotations = tuple(annotations)
self.type: Type_ = to_python(resolved_type, strict=False)
self.forward_ref_context = forward_ref_context
self._arguments = args
self._context = forward_ref_context

@cached_property
def parameters(self) -> t.Optional[t.Tuple[TypeParameter, ...]]:
Expand Down
24 changes: 24 additions & 0 deletions tests/test_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def foo(a, b=3) -> str:
return ""

sig = Signature.from_callable(foo)
assert sig.forward_ref_context == __name__
assert sig.return_annotation is str
assert len(sig.parameters) == 2
assert list(sig.parameters) == ["a", "b"]
Expand All @@ -27,6 +28,7 @@ def foo(a, b=3) -> str:

def test_get_int_signature():
sig = Signature.from_callable(int)
assert sig.forward_ref_context in (None, "builtins")
assert sig.return_annotation is int
assert len(sig.parameters) == 2
assert list(sig.parameters) == ["x", "base"]
Expand All @@ -38,6 +40,7 @@ def test_get_int_signature():

def test_get_float_signature():
sig = Signature.from_callable(float)
assert sig.forward_ref_context in (None, "builtins")
assert sig.return_annotation is float
assert len(sig.parameters) == 1
assert list(sig.parameters) == ["x"]
Expand All @@ -47,6 +50,7 @@ def test_get_float_signature():

def test_get_bool_signature():
sig = Signature.from_callable(bool)
assert sig.forward_ref_context in (None, "builtins")
assert sig.return_annotation is bool
assert len(sig.parameters) == 1
assert list(sig.parameters) == ["x"]
Expand Down Expand Up @@ -82,6 +86,7 @@ class MyDataClass:

sig = Signature.from_callable(MyDataClass)
assert list(sig.parameters) == ["foo"]
assert sig.forward_ref_context == __name__


def test_get_abstract_class_signature():
Expand All @@ -95,6 +100,7 @@ def func(self):

sig = Signature.from_callable(MyAbstractClass)
assert list(sig.parameters) == ["foo"]
assert sig.forward_ref_context == __name__


def test_constructor_descriptors():
Expand Down Expand Up @@ -162,6 +168,20 @@ def foo(a=5) -> str:
assert s is sig


def test_cached_signature():
def foo(a=5) -> str:
return "bar"

# Create and cache an `inspect.Signature`
foo.__signature__ = inspect.signature(foo) # type: ignore

# Now, when we create a `Signature` object, make sure that metadata such as
# `forward_ref_context` is still available
sig = Signature.from_callable(foo)
assert sig.forward_ref_context == __name__
assert sig.parameters["a"].forward_ref_context == __name__


BUILTIN_CALLABLES = {
name: obj
for name, obj in vars(builtins).items()
Expand Down Expand Up @@ -289,6 +309,7 @@ def __init__(self, init):

sig = Signature.from_callable(Cls)
assert list(sig.parameters) == ["init"]
assert sig.forward_ref_context == __name__


def test_class_signature_with_no_constructor():
Expand All @@ -313,6 +334,7 @@ def __init__(self, init):

sig = Signature.from_callable(Cls)
assert list(sig.parameters) == ["meta"]
assert sig.forward_ref_context == __name__


def test_skip_metaclass_signature():
Expand All @@ -327,6 +349,7 @@ def __init__(self, foo):

sig = Signature.from_callable(Cls)
assert list(sig.parameters) == ["foo"]
assert sig.forward_ref_context == __name__


def test_mark_on_decorated_function():
Expand All @@ -350,6 +373,7 @@ def __init__(self, foo):

sig = Signature.from_callable(Cls)
assert list(sig.parameters) == ["foo"]
assert sig.forward_ref_context == __name__


def test_partial():
Expand Down

0 comments on commit 8a97f9c

Please sign in to comment.