From 281ddecd0892e3408c44b692f2cc6ca1784da67d Mon Sep 17 00:00:00 2001 From: Aran-Fey Date: Sun, 10 Mar 2024 15:44:40 +0100 Subject: [PATCH] add get_type_annotations --- introspection/__init__.py | 2 +- introspection/typing/__init__.py | 1 + introspection/typing/misc2.py | 28 ++++++++++++++++++++++++++++ introspection/typing/type_info.py | 19 +++++++++++-------- 4 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 introspection/typing/misc2.py diff --git a/introspection/__init__.py b/introspection/__init__.py index ede6e22..fa1c892 100644 --- a/introspection/__init__.py +++ b/introspection/__init__.py @@ -2,7 +2,7 @@ New and improved introspection functions """ -__version__ = "1.7.10" +__version__ = "1.7.11" from .parameter import * from .signature_ import * diff --git a/introspection/typing/__init__.py b/introspection/typing/__init__.py index 0d9e5b8..2620d8b 100644 --- a/introspection/typing/__init__.py +++ b/introspection/typing/__init__.py @@ -4,4 +4,5 @@ from .subtype_check import * from .type_compat import * from .misc import * +from .misc2 import * from .i_hate_circular_imports import * diff --git a/introspection/typing/misc2.py b/introspection/typing/misc2.py new file mode 100644 index 0000000..dfd919f --- /dev/null +++ b/introspection/typing/misc2.py @@ -0,0 +1,28 @@ +from typing import Dict, cast + +from .type_info import TypeInfo +from ..misc import static_mro, static_vars +from ..types import TypeAnnotation + + +__all__ = ["get_type_annotations"] + + +def get_type_annotations(cls: type) -> Dict[str, TypeInfo]: + """ + Similar to `typing.get_type_hints`, but returns the annotations as `TypeInfo` objects. + """ + result: Dict[str, TypeInfo] = {} + + for class_ in static_mro(cls): + cls_vars = static_vars(class_) + try: + annotations = cast(Dict[str, TypeAnnotation], cls_vars["__annotations__"]) + except KeyError: + continue + + for attr_name, annotation in annotations.items(): + if attr_name not in result: + result[attr_name] = TypeInfo(annotation) + + return result diff --git a/introspection/typing/type_info.py b/introspection/typing/type_info.py index 20d7a8d..4ffe038 100644 --- a/introspection/typing/type_info.py +++ b/introspection/typing/type_info.py @@ -10,6 +10,7 @@ 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 @@ -27,11 +28,13 @@ def __init__( forward_ref_context: ForwardRefContext = None, treat_name_errors_as_imports: bool = False, ): + self.raw = type_ + resolved_type: Type_ = resolve_at_least_1_level_of_forward_refs( type_, forward_ref_context, treat_name_errors_as_imports ) - self.annotations = [] + annotations: typing.List[object] = [] args = None while is_parameterized_generic(resolved_type): @@ -39,13 +42,15 @@ def __init__( resolved_type = get_generic_base_class(resolved_type) if resolved_type is typing_extensions.Annotated: - self.annotations += args[1:] + annotations += args[1:] resolved_type = resolve_at_least_1_level_of_forward_refs( args[0], # type: ignore forward_ref_context, treat_name_errors_as_imports, ) + args = None + self.annotations = tuple(annotations) self.type: Type_ = to_python(resolved_type, strict=False) self._arguments = args self._context = forward_ref_context @@ -57,12 +62,6 @@ def parameters(self) -> typing.Optional[typing.Tuple[TypeParameter, ...]]: except NotAGeneric: return None - # @cached_property - # def arguments(self) -> typing.Optional[typing.Tuple[object, ...]]: - # if self._arguments is None: - # return None - - # return tuple(Annotation(arg, context=self._context) for arg in self._arguments) @property def arguments(self) -> typing.Optional[typing.Tuple[object, ...]]: return self._arguments @@ -74,3 +73,7 @@ def is_generic(self) -> bool: @property def is_fully_parameterized_generic(self) -> bool: return self.parameters == () + + def __repr__(self) -> str: + cls_name = type(self).__qualname__ + return f"{cls_name}({self.raw})"