openai/openai-python
Publicmirrored from https://github.com/openai/openai-pythonAvailable
src/openai/_utils/_typing.py
151lines · modecode
| 1 | from __future__ import annotations |
| 2 | |
| 3 | import sys |
| 4 | import typing |
| 5 | import typing_extensions |
| 6 | from typing import Any, TypeVar, Iterable, cast |
| 7 | from collections import abc as _c_abc |
| 8 | from typing_extensions import ( |
| 9 | TypeIs, |
| 10 | Required, |
| 11 | Annotated, |
| 12 | get_args, |
| 13 | get_origin, |
| 14 | ) |
| 15 | |
| 16 | from ._utils import lru_cache |
| 17 | from .._types import InheritsGeneric |
| 18 | from .._compat import is_union as _is_union |
| 19 | |
| 20 | |
| 21 | def is_annotated_type(typ: type) -> bool: |
| 22 | return get_origin(typ) == Annotated |
| 23 | |
| 24 | |
| 25 | def is_list_type(typ: type) -> bool: |
| 26 | return (get_origin(typ) or typ) == list |
| 27 | |
| 28 | |
| 29 | def is_iterable_type(typ: type) -> bool: |
| 30 | """If the given type is `typing.Iterable[T]`""" |
| 31 | origin = get_origin(typ) or typ |
| 32 | return origin == Iterable or origin == _c_abc.Iterable |
| 33 | |
| 34 | |
| 35 | def is_union_type(typ: type) -> bool: |
| 36 | return _is_union(get_origin(typ)) |
| 37 | |
| 38 | |
| 39 | def is_required_type(typ: type) -> bool: |
| 40 | return get_origin(typ) == Required |
| 41 | |
| 42 | |
| 43 | def is_typevar(typ: type) -> bool: |
| 44 | # type ignore is required because type checkers |
| 45 | # think this expression will always return False |
| 46 | return type(typ) == TypeVar # type: ignore |
| 47 | |
| 48 | |
| 49 | _TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,) |
| 50 | if sys.version_info >= (3, 12): |
| 51 | _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) |
| 52 | |
| 53 | |
| 54 | def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: |
| 55 | """Return whether the provided argument is an instance of `TypeAliasType`. |
| 56 | |
| 57 | ```python |
| 58 | type Int = int |
| 59 | is_type_alias_type(Int) |
| 60 | # > True |
| 61 | Str = TypeAliasType("Str", str) |
| 62 | is_type_alias_type(Str) |
| 63 | # > True |
| 64 | ``` |
| 65 | """ |
| 66 | return isinstance(tp, _TYPE_ALIAS_TYPES) |
| 67 | |
| 68 | |
| 69 | # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] |
| 70 | @lru_cache(maxsize=8096) |
| 71 | def strip_annotated_type(typ: type) -> type: |
| 72 | if is_required_type(typ) or is_annotated_type(typ): |
| 73 | return strip_annotated_type(cast(type, get_args(typ)[0])) |
| 74 | |
| 75 | return typ |
| 76 | |
| 77 | |
| 78 | def extract_type_arg(typ: type, index: int) -> type: |
| 79 | args = get_args(typ) |
| 80 | try: |
| 81 | return cast(type, args[index]) |
| 82 | except IndexError as err: |
| 83 | raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err |
| 84 | |
| 85 | |
| 86 | def extract_type_var_from_base( |
| 87 | typ: type, |
| 88 | *, |
| 89 | generic_bases: tuple[type, ...], |
| 90 | index: int, |
| 91 | failure_message: str | None = None, |
| 92 | ) -> type: |
| 93 | """Given a type like `Foo[T]`, returns the generic type variable `T`. |
| 94 | |
| 95 | This also handles the case where a concrete subclass is given, e.g. |
| 96 | ```py |
| 97 | class MyResponse(Foo[bytes]): |
| 98 | ... |
| 99 | |
| 100 | extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes |
| 101 | ``` |
| 102 | |
| 103 | And where a generic subclass is given: |
| 104 | ```py |
| 105 | _T = TypeVar('_T') |
| 106 | class MyResponse(Foo[_T]): |
| 107 | ... |
| 108 | |
| 109 | extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes |
| 110 | ``` |
| 111 | """ |
| 112 | cls = cast(object, get_origin(typ) or typ) |
| 113 | if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains] |
| 114 | # we're given the class directly |
| 115 | return extract_type_arg(typ, index) |
| 116 | |
| 117 | # if a subclass is given |
| 118 | # --- |
| 119 | # this is needed as __orig_bases__ is not present in the typeshed stubs |
| 120 | # because it is intended to be for internal use only, however there does |
| 121 | # not seem to be a way to resolve generic TypeVars for inherited subclasses |
| 122 | # without using it. |
| 123 | if isinstance(cls, InheritsGeneric): |
| 124 | target_base_class: Any | None = None |
| 125 | for base in cls.__orig_bases__: |
| 126 | if base.__origin__ in generic_bases: |
| 127 | target_base_class = base |
| 128 | break |
| 129 | |
| 130 | if target_base_class is None: |
| 131 | raise RuntimeError( |
| 132 | "Could not find the generic base class;\n" |
| 133 | "This should never happen;\n" |
| 134 | f"Does {cls} inherit from one of {generic_bases} ?" |
| 135 | ) |
| 136 | |
| 137 | extracted = extract_type_arg(target_base_class, index) |
| 138 | if is_typevar(extracted): |
| 139 | # If the extracted type argument is itself a type variable |
| 140 | # then that means the subclass itself is generic, so we have |
| 141 | # to resolve the type argument from the class itself, not |
| 142 | # the base class. |
| 143 | # |
| 144 | # Note: if there is more than 1 type argument, the subclass could |
| 145 | # change the ordering of the type arguments, this is not currently |
| 146 | # supported. |
| 147 | return extract_type_arg(typ, index) |
| 148 | |
| 149 | return extracted |
| 150 | |
| 151 | raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}") |
| 152 | |