diff --git a/introspection/__init__.py b/introspection/__init__.py index b25438e..ba57079 100644 --- a/introspection/__init__.py +++ b/introspection/__init__.py @@ -2,7 +2,7 @@ New and improved introspection functions """ -__version__ = "1.7.12" +__version__ = "1.7.13" from .parameter import * from .signature_ import * diff --git a/introspection/classes.py b/introspection/classes.py index 648e54a..030a8c3 100644 --- a/introspection/classes.py +++ b/introspection/classes.py @@ -336,11 +336,29 @@ def add_method_to_class( setattr(cls, method_name, method) +@overload def wrap_method( - method: Callable[..., Any], + cls: type, + method: Callable[..., object], + name: Optional[str] = None, + method_type: Union[None, Type[staticmethod], Type[classmethod]] = auto, # type: ignore +) -> None: ... + + +@overload # Deprecated signature +def wrap_method( + method: Callable[..., object], cls: type, name: Optional[str] = None, method_type: Union[None, Type[staticmethod], Type[classmethod]] = auto, # type: ignore +) -> None: ... + + +def wrap_method( # type: ignore[wtf] + cls: Union[type, Callable[..., object]], + method: Union[type, Callable[..., object]], + name: Optional[str] = None, + method_type: Union[None, Type[staticmethod], Type[classmethod]] = auto, # type: ignore ) -> None: r""" Adds ``method`` to ``cls``\ 's namespace under the given ``name``, wrapping the existing method @@ -443,12 +461,19 @@ def __new__(original_new, cls, *args, **kwargs): Child(5) # works! .. versionadded:: 1.3 + .. versionchanged:: 1.7.13 + Swapped the first two parameters. Passing the function as the first argument is still + possible, but deprecated. :param method: The method to add to the class :param cls: The class to which to add the method :param name: The name under which the method is registered in the class namespace :param method_type: One of :class:`staticmethod`, :class:`classmethod`, or ``None`` (or omitted) """ + # Deprecated call signature: Swap the first two arguments + if not isinstance(cls, type): + cls, method = method, cls + if not isinstance(cls, type): raise InvalidArgumentType("cls", cls, type) diff --git a/introspection/errors.py b/introspection/errors.py index 45c80ce..debb35e 100644 --- a/introspection/errors.py +++ b/introspection/errors.py @@ -203,7 +203,7 @@ def __str__(self) -> str: class CannotResolveForwardref(Error, ValueError): forward_ref: ForwardReference - context: ForwardRefContext + context: Optional[ForwardRefContext] def __str__(self) -> str: return f"Cannot resolve forward reference {self.forward_ref!r} in context {self.context!r}" diff --git a/introspection/parameter.py b/introspection/parameter.py index eda873a..33eb075 100644 --- a/introspection/parameter.py +++ b/introspection/parameter.py @@ -3,6 +3,7 @@ from typing_extensions import Self from ._utils import PARAM_EMPTY +from .types import ForwardRefContext __all__ = ["Parameter"] @@ -20,7 +21,7 @@ class Parameter(inspect.Parameter): This class adds a new special value for the ``default`` attribute: :attr:`Parameter.missing`. """ - __slots__ = () + __slots__ = ("forward_ref_context",) #: A special class-level marker that can be used to specify #: that the parameter is optional, but doesn't have a (known) @@ -46,6 +47,7 @@ def __init__( kind: inspect._ParameterKind = inspect.Parameter.POSITIONAL_OR_KEYWORD, default: Any = PARAM_EMPTY, annotation: Any = PARAM_EMPTY, + forward_ref_context: Optional[ForwardRefContext] = None, ): """ :param name: The parameter's name @@ -61,6 +63,8 @@ def __init__( super().__init__(name, kind, default=default, annotation=annotation) + self.forward_ref_context = forward_ref_context + @classmethod def from_parameter(cls, parameter: inspect.Parameter) -> Self: """ diff --git a/introspection/signature_.py b/introspection/signature_.py index 48067b5..c0adca0 100644 --- a/introspection/signature_.py +++ b/introspection/signature_.py @@ -16,7 +16,7 @@ from .misc import unwrap, static_mro, static_vars, extract_functions from ._utils import SIG_EMPTY from .errors import * -from .types import P, TypeAnnotation +from .types import P, TypeAnnotation, ForwardRefContext __all__ = ["Signature"] @@ -39,13 +39,13 @@ class Signature(inspect.Signature): __slots__ = ("forward_ref_context",) parameters: types.MappingProxyType[str, Parameter] # type: ignore - forward_ref_context: Optional[str] + forward_ref_context: Optional[ForwardRefContext] def __init__( self, parameters: Union[Iterable[Parameter], Mapping[str, Parameter], None] = None, return_annotation: Any = SIG_EMPTY, - forward_ref_context: Optional[str] = None, + forward_ref_context: Optional[ForwardRefContext] = None, validate_parameters: bool = True, ): """ @@ -144,7 +144,7 @@ def from_callable( callable_ = cast(Callable[P, Any], _find_constructor_function(callable_)) ignore_first_parameter = False - + if inspect.ismethod(callable_): callable_ = callable_.__func__ # type: ignore ignore_first_parameter = True @@ -153,7 +153,7 @@ def from_callable( callable_ = unwrap(callable_, lambda func: hasattr(func, "__signature__")) # type: ignore if not callable(callable_): - raise InvalidArgumentType("callable_", callable_, typing.Callable) + raise InvalidArgumentType("callable_", callable_, typing.Callable) # type: ignore try: sig = inspect.signature(callable_, follow_wrapped=False) diff --git a/introspection/types.py b/introspection/types.py index 4d95d4f..100e935 100644 --- a/introspection/types.py +++ b/introspection/types.py @@ -38,7 +38,7 @@ ForwardReference = typing.Union[str, typing.ForwardRef] TypeAnnotation = typing.Union[Type_, ForwardReference] ForwardRefContext = typing.Union[ - None, type, types.FunctionType, types.ModuleType, str, typing.Mapping[str, object] + type, types.FunctionType, types.ModuleType, str, typing.Mapping[str, object] ] @@ -58,14 +58,11 @@ class Slot(typing.Protocol[T]): # type: ignore[variance] - def __get__(self, instance: T, owner: typing.Optional[typing.Type[T]]) -> object: - ... + def __get__(self, instance: T, owner: typing.Optional[typing.Type[T]]) -> object: ... - def __set__(self, instance: T, value: object) -> None: - ... + def __set__(self, instance: T, value: object) -> None: ... - def __delete__(self, instance: T) -> None: - ... + def __delete__(self, instance: T) -> None: ... class ObjectWithQualname(typing.Protocol): diff --git a/introspection/typing/_utils.py b/introspection/typing/_utils.py index c34c31f..2440416 100644 --- a/introspection/typing/_utils.py +++ b/introspection/typing/_utils.py @@ -36,7 +36,7 @@ def resolve_names_in_all_typing_modules( @dataclasses.dataclass class TypeCheckingConfig: - forward_ref_context: ForwardRefContext + forward_ref_context: typing.Optional[ForwardRefContext] treat_name_errors_as_imports: bool def resolve_at_least_1_level_of_forward_refs(self, annotation: TypeAnnotation) -> Type_: @@ -48,7 +48,9 @@ def resolve_at_least_1_level_of_forward_refs(self, annotation: TypeAnnotation) - def resolve_at_least_1_level_of_forward_refs( - annotation: TypeAnnotation, context: ForwardRefContext, treat_name_errors_as_imports: bool + annotation: TypeAnnotation, + context: typing.Optional[ForwardRefContext], + treat_name_errors_as_imports: bool, ) -> Type_: # Given a forward reference as input, this function resolves the outermost type, but may leave # subtypes unevaluated. If the input isn't a forward reference, it is returned as-is. diff --git a/introspection/typing/instance_check.py b/introspection/typing/instance_check.py index cb0fbd7..0a190f2 100644 --- a/introspection/typing/instance_check.py +++ b/introspection/typing/instance_check.py @@ -19,7 +19,6 @@ ) from .subtype_check import _is_subtype from .type_compat import to_python -from ..errors import CannotResolveForwardref from ..parameter import Parameter from ..signature_ import Signature from .._utils import eval_or_discard @@ -35,7 +34,7 @@ def is_instance( obj: object, type_: Union[Type[T], TypeAnnotation], *, - forward_ref_context: ForwardRefContext = None, + forward_ref_context: Optional[ForwardRefContext] = None, treat_name_errors_as_imports: bool = False, ) -> typing_extensions.TypeGuard[T]: """ @@ -180,7 +179,8 @@ def _test_callable_subtypes( ) -> bool: signature = Signature.from_callable(obj) new_config = TypeCheckingConfig( - signature.forward_ref_context, config.treat_name_errors_as_imports + signature.forward_ref_context, # type: ignore[wtf] + config.treat_name_errors_as_imports, ) if signature.return_annotation is not Signature.empty: diff --git a/introspection/typing/introspection.py b/introspection/typing/introspection.py index 8a60a15..9ec7914 100644 --- a/introspection/typing/introspection.py +++ b/introspection/typing/introspection.py @@ -61,18 +61,15 @@ def _resolve_dotted_name(name: str) -> object: @overload -def _resolve_dotted_names(names: Dict[str, T]) -> Dict[object, T]: - ... +def _resolve_dotted_names(names: Dict[str, T]) -> Dict[object, T]: ... @overload -def _resolve_dotted_names(names: Tuple[str, ...]) -> Tuple[object, ...]: - ... +def _resolve_dotted_names(names: Tuple[str, ...]) -> Tuple[object, ...]: ... @overload -def _resolve_dotted_names(names: Iterable[str]) -> Iterable[object]: - ... +def _resolve_dotted_names(names: Iterable[str]) -> Iterable[object]: ... def _resolve_dotted_names( @@ -294,7 +291,7 @@ def _get_type_parameters(type_): if isinstance(type_, types.UnionType): return type_.__parameters__ # type: ignore - if safe_is_subclass(type_, Generic): + if safe_is_subclass(type_, Generic): # type: ignore[wtf] # Classes that inherit from Generic directly (like # ``class Protocol(Generic):``) and Generic itself don't # have __orig_bases__, while classes that have type @@ -421,7 +418,7 @@ def _get_name(cls): if sys.version_info >= (3, 9): def _is_generic_base_class(cls): - if safe_is_subclass(cls, Generic): + if safe_is_subclass(cls, Generic): # type: ignore[wtf] return bool(cls.__parameters__) # type: ignore return False @@ -818,7 +815,7 @@ def get_generic_base_class(type_: Type_) -> Type_: if len(args) == 1: base = Optional - return base + return base # type: ignore[wtf] def get_type_arguments(type_: Type_) -> Tuple[object, ...]: @@ -1070,7 +1067,7 @@ class MySequence(Sequence): arg = args[i] except IndexError: if assume_any: - return Any + return Any # type: ignore raise TypeVarNotSet(type_var, base_type, type_) # type: ignore @@ -1150,11 +1147,11 @@ def get_parent_types(type_: Type_) -> Tuple[Type_, ...]: try: orig_bases = type_.__orig_bases__ # type: ignore except AttributeError: - return type_.__bases__ + return type_.__bases__ # type: ignore[wtf] parent_types = [] - for base, orig_base in zip(type_.__bases__, orig_bases): + for base, orig_base in zip(type_.__bases__, orig_bases): # type: ignore[wtf] # Non-generic types show up as-is in both tuples if base is orig_base: parent_types.append(base) diff --git a/introspection/typing/misc.py b/introspection/typing/misc.py index ddf0797..c3dba12 100644 --- a/introspection/typing/misc.py +++ b/introspection/typing/misc.py @@ -43,7 +43,7 @@ def is_forward_ref( @overload def resolve_forward_refs( annotation: TypeAnnotation, - context: ForwardRefContext = None, + context: Optional[ForwardRefContext] = None, *, mode: Literal["eval", "getattr", "ast"] = "eval", strict: bool = True, @@ -64,7 +64,7 @@ def resolve_forward_refs( def resolve_forward_refs( # type: ignore annotation: TypeAnnotation, - context: ForwardRefContext = None, + context: Optional[ForwardRefContext] = None, eval_: Optional[bool] = None, strict: bool = True, *, @@ -458,7 +458,7 @@ def process_nested(prefix: str, elems: Iterable[TypeAnnotation]): if base in (typing.Callable, collections.abc.Callable): param_types, return_type = subtypes - prefix = recurse(base) + prefix = recurse(base) # type: ignore[wtf] return_str = recurse(return_type) # type: ignore if isinstance(param_types, list): @@ -471,7 +471,7 @@ def process_nested(prefix: str, elems: Iterable[TypeAnnotation]): if base in LITERAL_TYPES: literals = ", ".join(repr(value) for value in subtypes) - prefix = recurse(base) + prefix = recurse(base) # type: ignore[wtf] return f"{prefix}[{literals}]" if base is typing_extensions.Annotated: @@ -479,10 +479,10 @@ def process_nested(prefix: str, elems: Iterable[TypeAnnotation]): sub_strs = [recurse(sub_type)] # type: ignore sub_strs.extend(repr(ann) for ann in annotations) - prefix = recurse(base) + prefix = recurse(base) # type: ignore[wtf] return f'{prefix}[{", ".join(sub_strs)}]' - prefix = recurse(base) + prefix = recurse(base) # type: ignore[wtf] return process_nested(prefix, subtypes) # type: ignore if isinstance(annotation, (typing.TypeVar, typing_extensions.ParamSpec)): diff --git a/introspection/typing/subtype_check.py b/introspection/typing/subtype_check.py index 16c8d27..d0be5eb 100644 --- a/introspection/typing/subtype_check.py +++ b/introspection/typing/subtype_check.py @@ -1,5 +1,5 @@ import types -import typing +from typing import Any, Callable, List, Mapping, Optional, Tuple, Type, TypeVar from typing_extensions import TypeGuard from ._utils import ( @@ -15,16 +15,16 @@ __all__ = ["is_subtype"] -Type_Variable = typing.TypeVar("Type_Variable", bound=Type_) +Type_Variable = TypeVar("Type_Variable", bound=Type_) def is_subtype( subtype: TypeAnnotation, supertype: Type_Variable, *, - forward_ref_context: ForwardRefContext = None, + forward_ref_context: Optional[ForwardRefContext] = None, treat_name_errors_as_imports: bool = False, -) -> TypeGuard[typing.Type[Type_Variable]]: +) -> TypeGuard[Type[Type_Variable]]: """ Returns whether ``subtype`` is a subtype of ``supertype``. Unlike the builtin ``issubclass``, this function supports generics. @@ -49,10 +49,10 @@ def _is_subtype( subtype = config.resolve_at_least_1_level_of_forward_refs(subtype) supertype = config.resolve_at_least_1_level_of_forward_refs(supertype) # type: ignore - if subtype is typing.Any: + if subtype is Any: return True - if supertype in (typing.Any, object): + if supertype in (Any, object): return True if not is_parameterized_generic(supertype): @@ -93,7 +93,7 @@ def _unparameterized_supertype_check(subtype: Type_, supertype: Type_) -> bool: def _test_union_subtypes(config: TypeCheckingConfig, subtype: Type_, union_types: tuple) -> bool: - errors: typing.List[Exception] = [] + errors: List[Exception] = [] for union_type in union_types: try: @@ -122,13 +122,13 @@ def _test_optional_subtypes( return _test_union_subtypes(config, subtype, optional_type) -TYPE_ARGS_TESTS: typing.Mapping[ - Type_, typing.Callable[[TypeCheckingConfig, Type_, typing.Tuple[object, ...]], bool] -] = resolve_names_in_all_typing_modules( - { - "Union": _test_union_subtypes, - "Optional": _test_optional_subtypes, - } +TYPE_ARGS_TESTS: Mapping[Type_, Callable[[TypeCheckingConfig, Type_, Tuple[object, ...]], bool]] = ( + resolve_names_in_all_typing_modules( + { + "Union": _test_union_subtypes, + "Optional": _test_optional_subtypes, + } + ) ) if hasattr(types, "UnionType"): diff --git a/introspection/typing/type_compat.py b/introspection/typing/type_compat.py index 6833a79..42f1f1c 100644 --- a/introspection/typing/type_compat.py +++ b/introspection/typing/type_compat.py @@ -66,13 +66,11 @@ @typing.overload -def to_python(type_: Type_, strict: typing.Literal[True]) -> type: - ... +def to_python(type_: Type_, strict: typing.Literal[True]) -> type: ... @typing.overload -def to_python(type_: Type_, strict: bool = False) -> Type_: - ... +def to_python(type_: Type_, strict: bool = False) -> Type_: ... def to_python(type_: Type_, strict: bool = False) -> Type_: @@ -147,7 +145,7 @@ def to_python(type_: Type_, strict: bool = False) -> Type_: ..., typing.Any, ): - return collections.abc.Callable + return collections.abc.Callable # type: ignore[wtf] # At this point we know that the type arguments aren't redundant, so if # python doesn't have a generic equivalent of the base type, then we can't diff --git a/introspection/typing/type_info.py b/introspection/typing/type_info.py index 4ffe038..dadb7f2 100644 --- a/introspection/typing/type_info.py +++ b/introspection/typing/type_info.py @@ -1,7 +1,7 @@ from __future__ import annotations -import typing -import typing_extensions +from typing import Optional, List, Tuple +from typing_extensions import Annotated from ._utils import resolve_at_least_1_level_of_forward_refs from .introspection import ( @@ -10,7 +10,6 @@ get_generic_base_class, get_type_parameters, ) -from .misc import annotation_to_string from .type_compat import to_python from ..errors import NotAGeneric from ..types import Type_, TypeAnnotation, ForwardRefContext, TypeParameter @@ -25,7 +24,7 @@ def __init__( self, type_: TypeAnnotation, *, - forward_ref_context: ForwardRefContext = None, + forward_ref_context: Optional[ForwardRefContext] = None, treat_name_errors_as_imports: bool = False, ): self.raw = type_ @@ -34,14 +33,14 @@ def __init__( type_, forward_ref_context, treat_name_errors_as_imports ) - annotations: typing.List[object] = [] + annotations: List[object] = [] args = None while is_parameterized_generic(resolved_type): args = get_type_arguments(resolved_type) resolved_type = get_generic_base_class(resolved_type) - if resolved_type is typing_extensions.Annotated: + if resolved_type is Annotated: annotations += args[1:] resolved_type = resolve_at_least_1_level_of_forward_refs( args[0], # type: ignore @@ -56,14 +55,14 @@ def __init__( self._context = forward_ref_context @cached_property - def parameters(self) -> typing.Optional[typing.Tuple[TypeParameter, ...]]: + def parameters(self) -> Optional[Tuple[TypeParameter, ...]]: try: return get_type_parameters(self.type) except NotAGeneric: return None @property - def arguments(self) -> typing.Optional[typing.Tuple[object, ...]]: + def arguments(self) -> Optional[Tuple[object, ...]]: return self._arguments @property