Skip to content

Commit 42da61a

Browse files
chore(internal): add support for TypeAliasType (#404)
1 parent a2fe31a commit 42da61a

File tree

7 files changed

+66
-13
lines changed

7 files changed

+66
-13
lines changed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ authors = [
1010
dependencies = [
1111
"httpx>=0.23.0, <1",
1212
"pydantic>=1.9.0, <3",
13-
"typing-extensions>=4.7, <5",
13+
"typing-extensions>=4.10, <5",
1414
"anyio>=3.5.0, <5",
1515
"distro>=1.7.0, <2",
1616
"sniffio",

src/openlayer/_models.py

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
strip_not_given,
4747
extract_type_arg,
4848
is_annotated_type,
49+
is_type_alias_type,
4950
strip_annotated_type,
5051
)
5152
from ._compat import (
@@ -428,6 +429,8 @@ def construct_type(*, value: object, type_: object) -> object:
428429
# we allow `object` as the input type because otherwise, passing things like
429430
# `Literal['value']` will be reported as a type error by type checkers
430431
type_ = cast("type[object]", type_)
432+
if is_type_alias_type(type_):
433+
type_ = type_.__value__ # type: ignore[unreachable]
431434

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

src/openlayer/_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
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/openlayer/_utils/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
is_iterable_type as is_iterable_type,
4040
is_required_type as is_required_type,
4141
is_annotated_type as is_annotated_type,
42+
is_type_alias_type as is_type_alias_type,
4243
strip_annotated_type as strip_annotated_type,
4344
extract_type_var_from_base as extract_type_var_from_base,
4445
)

src/openlayer/_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
@@ -16,6 +16,7 @@
1616
is_union_type,
1717
extract_type_arg,
1818
is_annotated_type,
19+
is_type_alias_type,
1920
)
2021
from openlayer._compat import PYDANTIC_V2, field_outer_type, get_model_fields
2122
from openlayer._models import BaseModel
@@ -51,6 +52,9 @@ def assert_matches_type(
5152
path: list[str],
5253
allow_none: bool = False,
5354
) -> None:
55+
if is_type_alias_type(type_):
56+
type_ = type_.__value__
57+
5458
# unwrap `Annotated[T, ...]` -> `T`
5559
if is_annotated_type(type_):
5660
type_ = extract_type_arg(type_, 0)

0 commit comments

Comments
 (0)