Resolve annotations that may be string based into real objects. This is particularly important if a module defines "from __future__ import annotations", as everything inside of __annotations__ is a string. We want to at least have generic containers like ``Mapped``, ``Union``, ``List``,
(
cls: Type[Any],
annotation: _AnnotationScanType,
originating_module: str,
locals_: Mapping[str, Any],
*,
str_cleanup_fn: Optional[Callable[[str, str], str]] = None,
include_generic: bool = False,
_already_seen: Optional[Set[Any]] = None,
)
| 115 | |
| 116 | |
| 117 | def de_stringify_annotation( |
| 118 | cls: Type[Any], |
| 119 | annotation: _AnnotationScanType, |
| 120 | originating_module: str, |
| 121 | locals_: Mapping[str, Any], |
| 122 | *, |
| 123 | str_cleanup_fn: Optional[Callable[[str, str], str]] = None, |
| 124 | include_generic: bool = False, |
| 125 | _already_seen: Optional[Set[Any]] = None, |
| 126 | ) -> Type[Any]: |
| 127 | """Resolve annotations that may be string based into real objects. |
| 128 | |
| 129 | This is particularly important if a module defines "from __future__ import |
| 130 | annotations", as everything inside of __annotations__ is a string. We want |
| 131 | to at least have generic containers like ``Mapped``, ``Union``, ``List``, |
| 132 | etc. |
| 133 | |
| 134 | """ |
| 135 | # looked at typing.get_type_hints(), looked at pydantic. We need much |
| 136 | # less here, and we here try to not use any private typing internals |
| 137 | # or construct ForwardRef objects which is documented as something |
| 138 | # that should be avoided. |
| 139 | |
| 140 | original_annotation = annotation |
| 141 | |
| 142 | if is_fwd_ref(annotation): |
| 143 | annotation = annotation.__forward_arg__ |
| 144 | |
| 145 | if isinstance(annotation, str): |
| 146 | if str_cleanup_fn: |
| 147 | annotation = str_cleanup_fn(annotation, originating_module) |
| 148 | |
| 149 | annotation = eval_expression( |
| 150 | annotation, originating_module, locals_=locals_, in_class=cls |
| 151 | ) |
| 152 | |
| 153 | if ( |
| 154 | include_generic |
| 155 | and is_generic(annotation) |
| 156 | and not is_literal(annotation) |
| 157 | ): |
| 158 | if _already_seen is None: |
| 159 | _already_seen = set() |
| 160 | |
| 161 | if annotation in _already_seen: |
| 162 | # only occurs recursively. outermost return type |
| 163 | # will always be Type. |
| 164 | # the element here will be either ForwardRef or |
| 165 | # Optional[ForwardRef] |
| 166 | return original_annotation # type: ignore |
| 167 | else: |
| 168 | _already_seen.add(annotation) |
| 169 | |
| 170 | elements = tuple( |
| 171 | de_stringify_annotation( |
| 172 | cls, |
| 173 | elem, |
| 174 | originating_module, |