Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d3442ff

Browse files
committedDec 12, 2024
chore(internal): add support for TypeAliasType (#1942)
1 parent 190d1a8 commit d3442ff

File tree

7 files changed

+75
-22
lines changed

7 files changed

+75
-22
lines changed
 

‎src/openai/_legacy_response.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import pydantic
2525

2626
from ._types import NoneType
27-
from ._utils import is_given, extract_type_arg, is_annotated_type
27+
from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type
2828
from ._models import BaseModel, is_basemodel, add_request_id
2929
from ._constants import RAW_RESPONSE_HEADER
3030
from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
@@ -195,9 +195,15 @@ def elapsed(self) -> datetime.timedelta:
195195
return self.http_response.elapsed
196196

197197
def _parse(self, *, to: type[_T] | None = None) -> R | _T:
198+
cast_to = to if to is not None else self._cast_to
199+
200+
# unwrap `TypeAlias('Name', T)` -> `T`
201+
if is_type_alias_type(cast_to):
202+
cast_to = cast_to.__value__ # type: ignore[unreachable]
203+
198204
# unwrap `Annotated[T, ...]` -> `T`
199-
if to and is_annotated_type(to):
200-
to = extract_type_arg(to, 0)
205+
if cast_to and is_annotated_type(cast_to):
206+
cast_to = extract_type_arg(cast_to, 0)
201207

202208
if self._stream:
203209
if to:
@@ -233,18 +239,12 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
233239
return cast(
234240
R,
235241
stream_cls(
236-
cast_to=self._cast_to,
242+
cast_to=cast_to,
237243
response=self.http_response,
238244
client=cast(Any, self._client),
239245
),
240246
)
241247

242-
cast_to = to if to is not None else self._cast_to
243-
244-
# unwrap `Annotated[T, ...]` -> `T`
245-
if is_annotated_type(cast_to):
246-
cast_to = extract_type_arg(cast_to, 0)
247-
248248
if cast_to is NoneType:
249249
return cast(R, None)
250250

‎src/openai/_models.py

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
strip_not_given,
4848
extract_type_arg,
4949
is_annotated_type,
50+
is_type_alias_type,
5051
strip_annotated_type,
5152
)
5253
from ._compat import (
@@ -453,6 +454,8 @@ def construct_type(*, value: object, type_: object) -> object:
453454
# we allow `object` as the input type because otherwise, passing things like
454455
# `Literal['value']` will be reported as a type error by type checkers
455456
type_ = cast("type[object]", type_)
457+
if is_type_alias_type(type_):
458+
type_ = type_.__value__ # type: ignore[unreachable]
456459

457460
# unwrap `Annotated[T, ...]` -> `T`
458461
if is_annotated_type(type_):

‎src/openai/_response.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import pydantic
2626

2727
from ._types import NoneType
28-
from ._utils import is_given, extract_type_arg, is_annotated_type, extract_type_var_from_base
28+
from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base
2929
from ._models import BaseModel, is_basemodel, add_request_id
3030
from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
3131
from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
@@ -126,9 +126,15 @@ def __repr__(self) -> str:
126126
)
127127

128128
def _parse(self, *, to: type[_T] | None = None) -> R | _T:
129+
cast_to = to if to is not None else self._cast_to
130+
131+
# unwrap `TypeAlias('Name', T)` -> `T`
132+
if is_type_alias_type(cast_to):
133+
cast_to = cast_to.__value__ # type: ignore[unreachable]
134+
129135
# unwrap `Annotated[T, ...]` -> `T`
130-
if to and is_annotated_type(to):
131-
to = extract_type_arg(to, 0)
136+
if cast_to and is_annotated_type(cast_to):
137+
cast_to = extract_type_arg(cast_to, 0)
132138

133139
if self._is_sse_stream:
134140
if to:
@@ -164,18 +170,12 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
164170
return cast(
165171
R,
166172
stream_cls(
167-
cast_to=self._cast_to,
173+
cast_to=cast_to,
168174
response=self.http_response,
169175
client=cast(Any, self._client),
170176
),
171177
)
172178

173-
cast_to = to if to is not None else self._cast_to
174-
175-
# unwrap `Annotated[T, ...]` -> `T`
176-
if is_annotated_type(cast_to):
177-
cast_to = extract_type_arg(cast_to, 0)
178-
179179
if cast_to is NoneType:
180180
return cast(R, None)
181181

‎src/openai/_utils/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
is_iterable_type as is_iterable_type,
4141
is_required_type as is_required_type,
4242
is_annotated_type as is_annotated_type,
43+
is_type_alias_type as is_type_alias_type,
4344
strip_annotated_type as strip_annotated_type,
4445
extract_type_var_from_base as extract_type_var_from_base,
4546
)

‎src/openai/_utils/_typing.py

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
from __future__ import annotations
22

3+
import sys
4+
import typing
5+
import typing_extensions
36
from typing import Any, TypeVar, Iterable, cast
47
from collections import abc as _c_abc
5-
from typing_extensions import Required, Annotated, get_args, get_origin
8+
from typing_extensions import (
9+
TypeIs,
10+
Required,
11+
Annotated,
12+
get_args,
13+
get_origin,
14+
)
615

716
from .._types import InheritsGeneric
817
from .._compat import is_union as _is_union
@@ -36,6 +45,26 @@ def is_typevar(typ: type) -> bool:
3645
return type(typ) == TypeVar # type: ignore
3746

3847

48+
_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,)
49+
if sys.version_info >= (3, 12):
50+
_TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType)
51+
52+
53+
def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
54+
"""Return whether the provided argument is an instance of `TypeAliasType`.
55+
56+
```python
57+
type Int = int
58+
is_type_alias_type(Int)
59+
# > True
60+
Str = TypeAliasType("Str", str)
61+
is_type_alias_type(Str)
62+
# > True
63+
```
64+
"""
65+
return isinstance(tp, _TYPE_ALIAS_TYPES)
66+
67+
3968
# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
4069
def strip_annotated_type(typ: type) -> type:
4170
if is_required_type(typ) or is_annotated_type(typ):

‎tests/test_models.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
from typing import Any, Dict, List, Union, Optional, cast
33
from datetime import datetime, timezone
4-
from typing_extensions import Literal, Annotated
4+
from typing_extensions import Literal, Annotated, TypeAliasType
55

66
import pytest
77
import pydantic
@@ -828,3 +828,19 @@ class B(BaseModel):
828828
# if the discriminator details object stays the same between invocations then
829829
# we hit the cache
830830
assert UnionType.__discriminator__ is discriminator
831+
832+
833+
@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1")
834+
def test_type_alias_type() -> None:
835+
Alias = TypeAliasType("Alias", str)
836+
837+
class Model(BaseModel):
838+
alias: Alias
839+
union: Union[int, Alias]
840+
841+
m = construct_type(value={"alias": "foo", "union": "bar"}, type_=Model)
842+
assert isinstance(m, Model)
843+
assert isinstance(m.alias, str)
844+
assert m.alias == "foo"
845+
assert isinstance(m.union, str)
846+
assert m.union == "bar"

‎tests/utils.py

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
is_union_type,
2020
extract_type_arg,
2121
is_annotated_type,
22+
is_type_alias_type,
2223
)
2324
from openai._compat import PYDANTIC_V2, field_outer_type, get_model_fields
2425
from openai._models import BaseModel
@@ -58,6 +59,9 @@ def assert_matches_type(
5859
path: list[str],
5960
allow_none: bool = False,
6061
) -> None:
62+
if is_type_alias_type(type_):
63+
type_ = type_.__value__
64+
6165
# unwrap `Annotated[T, ...]` -> `T`
6266
if is_annotated_type(type_):
6367
type_ = extract_type_arg(type_, 0)

0 commit comments

Comments
 (0)
Please sign in to comment.