Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(typing): always unwrap TypeAliasTypes #3982

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

provinzkraut
Copy link
Member

While fixing #3912 I noticed we only unwrap these partially. However, I am not sure if unwrapping them at all is proper, because of a particular edge case: They can be self-referential. This is used e.g. in pydantic.JsonType.

SelfReferential = TypeAliasType("Outer", Union[Annotated[int, "meta"], list["Outer"]])

This type can be understood by e.g. mypy, but is hard to parse for our typing system since we want to extract the metadata from the Annotated, but need to keep the outer TypeAliasType, so the self-referencing works in later stages (e.g. Pydantic).

I'm not entirely sure how we should best handle this, as our typing system has fundamentally different requirements than that of the 3rd party tools we rely on.

Divergences in how self-referential types are treated by these tools further complicates things.

For example, this is fine in Pydantic:

import pydantic

type Foo = int | list[Foo]

class SomeModel(pydantic.BaseModel):
    a: Foo

SomeModel.model_validate_json('{"a": 1}')

But msgspec raises a RecursionError:

import msgspec

type Foo = int | list[Foo]

class SomeStruct(msgspec.Struct):
    a: Foo

msgspec.json.decode('{"a": 1}', type=SomeStruct)

Not really sure yet how to proceed, I'll have to think about this a bit. I'd be happy about some input though @litestar-org/members

@provinzkraut provinzkraut requested review from a team as code owners February 1, 2025 12:50
@github-actions github-actions bot added area/private-api This PR involves changes to the privatized API size: small type/bug pr/internal labels Feb 1, 2025
Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't quite know on how to proceed. I think that probably documenting different corner cases is a must here. For example: how different serializer frameworks handle recursive aliases and when to use them. Maybe some recipies.

Thanks for raising this! I am really interested in this topic.

litestar/utils/typing.py Show resolved Hide resolved
tests/unit/test_typing.py Show resolved Hide resolved
@provinzkraut
Copy link
Member Author

I think this might be related: python/mypy#18252

Mypy also doesn't seem to handle these cyclic definitions

while stack:
ann = stack.pop()

if isinstance(ann, TypeAliasType):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a heads up: typing-extensions 4.13.0 (yet to be released) has a backported TypeAliasType version, meaning:

from typing import TypeAliasType
from typing_extensions import TypeAliasType as ExtensionsTypeAliasType

Test = ExtensionsTypeAliasType('Test', int)
isinstance(Test, TypeAliasType)
#> False

Also on Python 3.10 a bug leads to the following:

T = TypeVar('T')

Test = TypeAliasType('Test', list[T], type_params=(T,))
isinstance(Test[int], TypeAliasType)
#> True, but should be false as Test[int] is a `types.GenericAlias` instance`. In your case, your are going to loose the parametrization and `__value__` will be list[T]

This is how we do it in Pydantic.

Overall it seems that you may face similar issues in the future if typing-extensions starts exposing new versions of the typing.* constructs. One example is also in this function: you extract type qualifiers such as Required, and latter in the code base a Required in type_wrappers is performed. Currently, typing_extensions reexports typing.(Not)Required on 3.11, but that might change in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the heads up @Viicos! Seems like another indicator that what was discussed in beartype/beartype#479 (comment) would be quite useful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/private-api This PR involves changes to the privatized API pr/internal size: small type/bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants