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 Literal bug with typing-extension==4.6.0 #5826

Merged
merged 6 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/5826-hramezani.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a bug in `Literal` usage with `typing-extension==4.6.0`
13 changes: 8 additions & 5 deletions pydantic/typing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sys
import typing
from collections.abc import Callable
from os import PathLike
from typing import ( # type: ignore
Expand Down Expand Up @@ -91,6 +92,11 @@ def get_all_type_hints(obj: Any, globalns: Any = None, localns: Any = None) -> A
AnnotatedTypeNames = {'AnnotatedMeta', '_AnnotatedAlias'}


LITERAL_TYPES: Set[Any] = {Literal}
if hasattr(typing, 'Literal'):
LITERAL_TYPES.add(typing.Literal)


if sys.version_info < (3, 8):

def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
Expand Down Expand Up @@ -354,10 +360,7 @@ def is_none_type(type_: Any) -> bool:
else:

def is_none_type(type_: Any) -> bool:
for none_type in NONE_TYPES:
if type_ is none_type:
return True
return False
return type_ in NONE_TYPES
Copy link
Member

Choose a reason for hiding this comment

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

This looks weird enough that we might have done it for some strange reason, can we confirm this was just a code smell?

Copy link
Member Author

@hramezani hramezani May 23, 2023

Choose a reason for hiding this comment

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

The existing approach(checking with is) does not work in Python 3.9 and typing-extension==4.6.0. That's why I've changed it.

Note: the new approach is also Ok with typing-extension==4.5.0. So, it might be a wrong implementation before

Copy link
Member

Choose a reason for hiding this comment

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

thanks @hramezani.

Can we uprev the version we test with?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!



def display_as_type(v: Type[Any]) -> str:
Expand Down Expand Up @@ -415,7 +418,7 @@ def is_callable_type(type_: Type[Any]) -> bool:


def is_literal_type(type_: Type[Any]) -> bool:
return Literal is not None and get_origin(type_) is Literal
return Literal is not None and get_origin(type_) in LITERAL_TYPES


def literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
Expand Down
2 changes: 1 addition & 1 deletion tests/requirements-testing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pytest-cov==4.0.0
pytest-mock==3.10.0
pytest-sugar==0.9.6
# pin typing-extensions to minimum requirement - see #4885
typing-extensions==4.2.0
typing-extensions==4.6.0
# used in FastAPI tests, pin to avoid warnings in newer version
# that FastAPI needs to fix
Flask==2.2.3
19 changes: 19 additions & 0 deletions tests/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3440,3 +3440,22 @@ class Model(BaseModel):
'type': 'value_error.date.not_in_the_future',
}
]


def test_typing_extension_literal_field():
from typing_extensions import Literal

class Model(BaseModel):
foo: Literal['foo']

assert Model(foo='foo').foo == 'foo'


@pytest.mark.skipif(sys.version_info < (3, 8), reason='`typing.Literal` is available for python 3.8 and above.')
def test_typing_literal_field():
from typing import Literal

class Model(BaseModel):
foo: Literal['foo']

assert Model(foo='foo').foo == 'foo'
17 changes: 16 additions & 1 deletion tests/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing_extensions import Annotated # noqa: F401

from pydantic import Field # noqa: F401
from pydantic.typing import Literal, convert_generics, is_namedtuple, is_none_type, is_typeddict
from pydantic.typing import Literal, convert_generics, is_literal_type, is_namedtuple, is_none_type, is_typeddict

try:
from typing import TypedDict as typing_TypedDict
Expand Down Expand Up @@ -124,3 +124,18 @@ def test_convert_generics_pep604():
assert (
convert_generics(dict['Hero', list['Team']] | int) == dict[ForwardRef('Hero'), list[ForwardRef('Team')]] | int
)


def test_is_literal_with_typing_extension_literal():
from typing_extensions import Literal

assert is_literal_type(Literal) is False
assert is_literal_type(Literal['foo']) is True


@pytest.mark.skipif(sys.version_info < (3, 8), reason='`typing.Literal` is available for python 3.8 and above.')
def test_is_literal_with_typing_literal():
from typing import Literal

assert is_literal_type(Literal) is False
assert is_literal_type(Literal['foo']) is True