diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14426a9921..2c865edb1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: DEPS: yes - name: uninstall deps - run: pip uninstall -y cython email-validator typing-extensions devtools python-dotenv + run: pip uninstall -y cython email-validator devtools python-dotenv - name: test compiled without deps run: make test diff --git a/changes/2368-samuelcolvin.md b/changes/2368-samuelcolvin.md new file mode 100644 index 0000000000..c71748f2bb --- /dev/null +++ b/changes/2368-samuelcolvin.md @@ -0,0 +1 @@ +Making `typing-extensions` a required dependency. diff --git a/docs/examples/annotated_types_typed_dict.py b/docs/examples/annotated_types_typed_dict.py index 382ba65f36..bbfbc877b2 100644 --- a/docs/examples/annotated_types_typed_dict.py +++ b/docs/examples/annotated_types_typed_dict.py @@ -1,4 +1,4 @@ -from typing import TypedDict +from typing_extensions import TypedDict from pydantic import BaseModel, Extra, ValidationError diff --git a/docs/examples/models_from_typeddict.py b/docs/examples/models_from_typeddict.py index 54136274e9..2454db70fb 100644 --- a/docs/examples/models_from_typeddict.py +++ b/docs/examples/models_from_typeddict.py @@ -1,4 +1,4 @@ -from typing import TypedDict +from typing_extensions import TypedDict from pydantic import ValidationError, create_model_from_typeddict diff --git a/docs/examples/schema_annotated.py b/docs/examples/schema_annotated.py index ab29ad38a9..12f1ab5f12 100644 --- a/docs/examples/schema_annotated.py +++ b/docs/examples/schema_annotated.py @@ -1,11 +1,7 @@ from uuid import uuid4 -try: - from typing import Annotated -except ImportError: - from typing_extensions import Annotated - from pydantic import BaseModel, Field +from typing_extensions import Annotated class Foo(BaseModel): diff --git a/docs/install.md b/docs/install.md index b9bcb4d005..9cbd9e2850 100644 --- a/docs/install.md +++ b/docs/install.md @@ -4,7 +4,9 @@ Installation is as simple as: pip install pydantic ``` -*pydantic* has no required dependencies except python 3.6, 3.7, 3.8, or 3.9 (and the dataclasses package for python 3.6). +*pydantic* has no required dependencies except python 3.6, 3.7, 3.8, or 3.9, +[`typing-extensions`](https://pypi.org/project/typing-extensions/), and the +[`dataclasses`](https://pypi.org/project/dataclasses/) backport package for python 3.6. If you've got python 3.6+ and `pip` installed, you're good to go. Pydantic is also available on [conda](https://www.anaconda.com) under the [conda-forge](https://conda-forge.org) @@ -30,7 +32,6 @@ print('compiled:', pydantic.compiled) *pydantic* has three optional dependencies: * If you require email validation you can add [email-validator](https://github.com/JoshData/python-email-validator) -* use of `Literal` prior to python 3.8 relies on [typing-extensions](https://pypi.org/project/typing-extensions/) * [dotenv file support](usage/settings.md#dotenv-env-support) with `Settings` requires [python-dotenv](https://pypi.org/project/python-dotenv) @@ -38,18 +39,16 @@ To install these along with *pydantic*: ```bash pip install pydantic[email] # or -pip install pydantic[typing_extensions] -# or pip install pydantic[dotenv] # or just -pip install pydantic[email,typing_extensions,dotenv] +pip install pydantic[email,dotenv] ``` -Of course, you can also install these requirements manually with `pip install email-validator` and/or `pip install typing_extensions`. +Of course, you can also install these requirements manually with `pip install email-validator` and/or `pip install`. And if you prefer to install *pydantic* directly from the repository: ```bash pip install git+git://github.com/samuelcolvin/pydantic@master#egg=pydantic # or with extras -pip install git+git://github.com/samuelcolvin/pydantic@master#egg=pydantic[email,typing_extensions] +pip install git+git://github.com/samuelcolvin/pydantic@master#egg=pydantic[email,dotenv] ``` diff --git a/docs/requirements.txt b/docs/requirements.txt index b1b8cb912d..87e939b08a 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -7,6 +7,5 @@ mkdocs-exclude==1.0.2 mkdocs-material==6.2.8 markdown-include==0.6.0 sqlalchemy -typing-extensions==3.7.4.3 orjson ujson diff --git a/pydantic/annotated_types.py b/pydantic/annotated_types.py index a29da28c0a..bffcdc620c 100644 --- a/pydantic/annotated_types.py +++ b/pydantic/annotated_types.py @@ -16,29 +16,21 @@ def create_model_from_typeddict(typeddict_cls: Type['TypedDict'], **kwargs: Any) """ Create a `BaseModel` based on the fields of a `TypedDict`. Since `typing.TypedDict` in Python 3.8 does not store runtime information about optional keys, - we warn the user if that's the case (see https://bugs.python.org/issue38834). + we raise an error if this happens (see https://bugs.python.org/issue38834). """ field_definitions: Dict[str, Any] # Best case scenario: with python 3.9+ or when `TypedDict` is imported from `typing_extensions` - if hasattr(typeddict_cls, '__required_keys__'): - field_definitions = { - field_name: (field_type, Required if field_name in typeddict_cls.__required_keys__ else None) - for field_name, field_type in typeddict_cls.__annotations__.items() - } - else: - import warnings - - warnings.warn( - 'You should use `typing_extensions.TypedDict` instead of `typing.TypedDict` for better support! ' - 'Without it, there is no way to differentiate required and optional fields when subclassed. ' - 'Fields will therefore be considered all required or all optional depending on class totality.', - UserWarning, + if not hasattr(typeddict_cls, '__required_keys__'): + raise TypeError( + 'You should use `typing_extensions.TypedDict` instead of `typing.TypedDict`. ' + 'Without it, there is no way to differentiate required and optional fields when subclassed.' ) - default_value = Required if typeddict_cls.__total__ else None - field_definitions = { - field_name: (field_type, default_value) for field_name, field_type in typeddict_cls.__annotations__.items() - } + + field_definitions = { + field_name: (field_type, Required if field_name in typeddict_cls.__required_keys__ else None) + for field_name, field_type in typeddict_cls.__annotations__.items() + } return create_model(typeddict_cls.__name__, **kwargs, **field_definitions) diff --git a/pydantic/fields.py b/pydantic/fields.py index 127f9cb1a8..a59c598f47 100644 --- a/pydantic/fields.py +++ b/pydantic/fields.py @@ -22,6 +22,8 @@ Union, ) +from typing_extensions import Annotated + from . import errors as errors_ from .class_validators import Validator, make_generic_validator, prep_validators from .error_wrappers import ErrorWrapper @@ -29,7 +31,6 @@ from .types import Json, JsonWrapper from .typing import ( NONE_TYPES, - Annotated, Callable, ForwardRef, NoArgAnyCallable, diff --git a/pydantic/schema.py b/pydantic/schema.py index 2b68b89e54..582674755e 100644 --- a/pydantic/schema.py +++ b/pydantic/schema.py @@ -26,6 +26,8 @@ ) from uuid import UUID +from typing_extensions import Annotated, Literal + from .fields import ( SHAPE_FROZENSET, SHAPE_GENERIC, @@ -61,9 +63,7 @@ ) from .typing import ( NONE_TYPES, - Annotated, ForwardRef, - Literal, get_args, get_origin, is_callable_type, diff --git a/pydantic/typing.py b/pydantic/typing.py index 36cd1fb1d6..ff0cb7ee26 100644 --- a/pydantic/typing.py +++ b/pydantic/typing.py @@ -20,6 +20,8 @@ cast, ) +from typing_extensions import Annotated, Literal + try: from typing import _TypingBase as typing_base # type: ignore except ImportError: @@ -81,87 +83,21 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: NoArgAnyCallable = TypingCallable[[], Any] -if sys.version_info >= (3, 9): - from typing import Annotated -else: - if TYPE_CHECKING: - from typing_extensions import Annotated - else: # due to different mypy warnings raised during CI for python 3.7 and 3.8 - try: - from typing_extensions import Annotated - except ImportError: - # Create mock Annotated values distinct from `None`, which is a valid `get_origin` - # return value. - class _FalseMeta(type): - # Allow short circuiting with "Annotated[...] if Annotated else None". - def __bool__(cls): - return False - - # Give a nice suggestion for unguarded use - def __getitem__(cls, key): - raise RuntimeError( - 'Annotated is not supported in this python version, please `pip install typing-extensions`.' - ) - - class Annotated(metaclass=_FalseMeta): - pass - - # Annotated[...] is implemented by returning an instance of one of these classes, depending on # python/typing_extensions version. -AnnotatedTypeNames = ('AnnotatedMeta', '_AnnotatedAlias') +AnnotatedTypeNames = {'AnnotatedMeta', '_AnnotatedAlias'} -if sys.version_info < (3, 8): # noqa: C901 - if TYPE_CHECKING: - from typing_extensions import Literal - else: # due to different mypy warnings raised during CI for python 3.7 and 3.8 - try: - from typing_extensions import Literal - except ImportError: - Literal = None - - if sys.version_info < (3, 7): - - def get_args(t: Type[Any]) -> Tuple[Any, ...]: - """Simplest get_args compatibility layer possible. - - The Python 3.6 typing module does not have `_GenericAlias` so - this won't work for everything. In particular this will not - support the `generics` module (we don't support generic models in - python 3.6). - - """ - if Annotated and type(t).__name__ in AnnotatedTypeNames: - return t.__args__ + t.__metadata__ - return getattr(t, '__args__', ()) - - else: - from typing import _GenericAlias - - def get_args(t: Type[Any]) -> Tuple[Any, ...]: - """Compatibility version of get_args for python 3.7. - - Mostly compatible with the python 3.8 `typing` module version - and able to handle almost all use cases. - """ - if Annotated and type(t).__name__ in AnnotatedTypeNames: - return t.__args__ + t.__metadata__ - if isinstance(t, _GenericAlias): - res = t.__args__ - if t.__origin__ is Callable and res and res[0] is not Ellipsis: - res = (list(res[:-1]), res[-1]) - return res - return getattr(t, '__args__', ()) +if sys.version_info < (3, 8): def get_origin(t: Type[Any]) -> Optional[Type[Any]]: - if Annotated and type(t).__name__ in AnnotatedTypeNames: + if type(t).__name__ in AnnotatedTypeNames: return cast(Type[Any], Annotated) # mypy complains about _SpecialForm in py3.6 return getattr(t, '__origin__', None) else: - from typing import Literal, get_args as typing_get_args, get_origin as typing_get_origin + from typing import get_origin as _typing_get_origin def get_origin(tp: Type[Any]) -> Type[Any]: """ @@ -170,11 +106,50 @@ def get_origin(tp: Type[Any]) -> Type[Any]: It should be useless once https://github.com/cython/cython/issues/3537 is solved and https://github.com/samuelcolvin/pydantic/pull/1753 is merged. """ - if Annotated and type(tp).__name__ in AnnotatedTypeNames: + if type(tp).__name__ in AnnotatedTypeNames: return cast(Type[Any], Annotated) # mypy complains about _SpecialForm - return typing_get_origin(tp) or getattr(tp, '__origin__', None) + return _typing_get_origin(tp) or getattr(tp, '__origin__', None) + + +if sys.version_info < (3, 7): # noqa: C901 (ignore complexity) + + def get_args(t: Type[Any]) -> Tuple[Any, ...]: + """Simplest get_args compatibility layer possible. + + The Python 3.6 typing module does not have `_GenericAlias` so + this won't work for everything. In particular this will not + support the `generics` module (we don't support generic models in + python 3.6). + + """ + if type(t).__name__ in AnnotatedTypeNames: + return t.__args__ + t.__metadata__ + return getattr(t, '__args__', ()) + + +elif sys.version_info < (3, 8): # noqa: C901 + from typing import _GenericAlias + + def get_args(t: Type[Any]) -> Tuple[Any, ...]: + """Compatibility version of get_args for python 3.7. + + Mostly compatible with the python 3.8 `typing` module version + and able to handle almost all use cases. + """ + if type(t).__name__ in AnnotatedTypeNames: + return t.__args__ + t.__metadata__ + if isinstance(t, _GenericAlias): + res = t.__args__ + if t.__origin__ is Callable and res and res[0] is not Ellipsis: + res = (list(res[:-1]), res[-1]) + return res + return getattr(t, '__args__', ()) + + +else: + from typing import get_args as _typing_get_args - def generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]: + def _generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]: """ In python 3.9, `typing.Dict`, `typing.List`, ... do have an empty `__args__` by default (instead of the generic ~T for example). @@ -196,10 +171,10 @@ def get_args(tp: Type[Any]) -> Tuple[Any, ...]: get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) get_args(Callable[[], T][int]) == ([], int) """ - if Annotated and type(tp).__name__ in AnnotatedTypeNames: + if type(tp).__name__ in AnnotatedTypeNames: return tp.__args__ + tp.__metadata__ # the fallback is needed for the same reasons as `get_origin` (see above) - return typing_get_args(tp) or getattr(tp, '__args__', ()) or generic_get_args(tp) + return _typing_get_args(tp) or getattr(tp, '__args__', ()) or _generic_get_args(tp) if TYPE_CHECKING: @@ -220,7 +195,6 @@ def get_args(tp: Type[Any]) -> Tuple[Any, ...]: __all__ = ( 'ForwardRef', 'Callable', - 'Annotated', 'AnyCallable', 'NoArgAnyCallable', 'NoneType', @@ -230,7 +204,6 @@ def get_args(tp: Type[Any]) -> Tuple[Any, ...]: 'is_callable_type', 'is_literal_type', 'literal_values', - 'Literal', 'is_namedtuple', 'is_typeddict', 'is_new_type', @@ -255,9 +228,7 @@ def get_args(tp: Type[Any]) -> Tuple[Any, ...]: NoneType = None.__class__ -NONE_TYPES: Set[Any] = {None, NoneType} -if Literal: - NONE_TYPES.add(Literal[None]) +NONE_TYPES: Set[Any] = {None, NoneType, Literal[None]} def display_as_type(v: Type[Any]) -> str: diff --git a/pydantic/validators.py b/pydantic/validators.py index f4e21bbb2e..57a1a23fcc 100644 --- a/pydantic/validators.py +++ b/pydantic/validators.py @@ -25,13 +25,14 @@ ) from uuid import UUID +from typing_extensions import Literal + from . import errors from .datetime_parse import parse_date, parse_datetime, parse_duration, parse_time from .typing import ( NONE_TYPES, AnyCallable, ForwardRef, - Literal, all_literal_values, display_as_type, get_class, diff --git a/requirements.txt b/requirements.txt index 94aa76af52..e734ca2b04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,5 @@ Cython==0.29.21;sys_platform!='win32' devtools==0.6.1 email-validator==1.1.2 dataclasses==0.6; python_version < '3.7' -typing-extensions==3.7.4.3; python_version < '3.9' +typing-extensions==3.7.4.3 python-dotenv==0.15.0 diff --git a/setup.py b/setup.py index 96c735324e..52baae2789 100644 --- a/setup.py +++ b/setup.py @@ -127,11 +127,11 @@ def extra(self): python_requires='>=3.6.1', zip_safe=False, # https://mypy.readthedocs.io/en/latest/installed_packages.html install_requires=[ - 'dataclasses>=0.6;python_version<"3.7"' + 'dataclasses>=0.6;python_version<"3.7"', + 'typing-extensions>=3.7.4.3' ], extras_require={ 'email': ['email-validator>=1.0.3'], - 'typing_extensions': ['typing-extensions>=3.7.4'], 'dotenv': ['python-dotenv>=0.10.4'], }, ext_modules=ext_modules, diff --git a/tests/mypy/test_mypy.py b/tests/mypy/test_mypy.py index a4b9fbef53..a81cfe0f86 100644 --- a/tests/mypy/test_mypy.py +++ b/tests/mypy/test_mypy.py @@ -11,9 +11,9 @@ mypy_api = None try: - import typing_extensions + import dotenv except ImportError: - typing_extensions = None + dotenv = None # This ensures mypy can find the test files, no matter where tests are run from: os.chdir(Path(__file__).parent.parent.parent) @@ -33,7 +33,7 @@ executable_modules = list({fname[:-3] for _, fname, out_fname in cases if out_fname is None}) -@pytest.mark.skipif(not (typing_extensions and mypy_api), reason='typing_extensions or mypy are not installed') +@pytest.mark.skipif(not (dotenv and mypy_api), reason='dotenv or mypy are not installed') @pytest.mark.parametrize('config_filename,python_filename,output_filename', cases) def test_mypy_results(config_filename, python_filename, output_filename): full_config_filename = f'tests/mypy/configs/{config_filename}' diff --git a/tests/test_annotated.py b/tests/test_annotated.py index 4aff92c77f..0e6321fd96 100644 --- a/tests/test_annotated.py +++ b/tests/test_annotated.py @@ -7,8 +7,9 @@ from pydantic.fields import Undefined from pydantic.typing import Annotated +pytestmark = pytest.mark.skipif(not Annotated, reason='typing_extensions not installed') + -@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed') @pytest.mark.parametrize( ['hint_fn', 'value'], [ @@ -55,7 +56,6 @@ class M(BaseModel): assert get_type_hints(M)['x'] == hint -@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed') @pytest.mark.parametrize( ['hint_fn', 'value', 'subclass_ctx'], [ @@ -93,7 +93,6 @@ class M(BaseModel): x: hint = value -@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed') @pytest.mark.parametrize( ['hint_fn', 'value', 'empty_init_ctx'], [ @@ -121,7 +120,6 @@ class M(BaseModel): assert M().x == 5 -@pytest.mark.skipif(not Annotated, reason='typing_extensions not installed') def test_field_reuse(): field = Field(description='Long description') diff --git a/tests/test_annotated_types.py b/tests/test_annotated_types.py index 8c3fc087be..b4b84bac8c 100644 --- a/tests/test_annotated_types.py +++ b/tests/test_annotated_types.py @@ -8,25 +8,20 @@ from collections import namedtuple from typing import List, NamedTuple, Tuple +import pytest +from typing_extensions import TypedDict + +from pydantic import BaseModel, ValidationError + if sys.version_info < (3, 9): try: from typing import TypedDict as LegacyTypedDict except ImportError: LegacyTypedDict = None - try: - from typing_extensions import TypedDict - except ImportError: - TypedDict = None else: - from typing import TypedDict - LegacyTypedDict = None -import pytest - -from pydantic import BaseModel, ValidationError - def test_namedtuple(): Position = namedtuple('Pos', 'x y') @@ -128,7 +123,6 @@ class Model(BaseModel): ] -@pytest.mark.skipif(not TypedDict, reason='typing_extensions not installed') def test_typeddict(): class TD(TypedDict): a: int @@ -153,7 +147,6 @@ class Model(BaseModel): ] -@pytest.mark.skipif(not TypedDict, reason='typing_extensions not installed') def test_typeddict_non_total(): class FullMovie(TypedDict, total=True): name: str @@ -183,7 +176,6 @@ class Model(BaseModel): assert m.movie == {'year': 2002} -@pytest.mark.skipif(not TypedDict, reason='typing_extensions not installed') def test_partial_new_typeddict(): class OptionalUser(TypedDict, total=False): name: str @@ -198,7 +190,7 @@ class Model(BaseModel): assert m.user == {'id': 1} -@pytest.mark.skipif(not LegacyTypedDict, reason='python 3.9+ is used or typing_extensions is installed') +@pytest.mark.skipif(not LegacyTypedDict, reason='python 3.9+ is used, no legacy TypedDict') def test_partial_legacy_typeddict(): class OptionalUser(LegacyTypedDict, total=False): name: str @@ -206,26 +198,12 @@ class OptionalUser(LegacyTypedDict, total=False): class User(OptionalUser): id: int - with pytest.warns( - UserWarning, - match='You should use `typing_extensions.TypedDict` instead of `typing.TypedDict` for better support!', - ): + with pytest.raises(TypeError, match='^You should use `typing_extensions.TypedDict` instead of `typing.TypedDict`'): class Model(BaseModel): user: User - with pytest.raises(ValidationError) as exc_info: - Model(user={'id': 1}) - assert exc_info.value.errors() == [ - { - 'loc': ('user', 'name'), - 'msg': 'field required', - 'type': 'value_error.missing', - } - ] - -@pytest.mark.skipif(not TypedDict, reason='typing_extensions not installed') def test_typeddict_extra(): class User(TypedDict): name: str @@ -244,7 +222,6 @@ class Config: ] -@pytest.mark.skipif(not TypedDict, reason='typing_extensions not installed') def test_typeddict_schema(): class Data(BaseModel): a: int diff --git a/tests/test_errors.py b/tests/test_errors.py index fd3098f726..0f901b4edd 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -4,11 +4,11 @@ from uuid import UUID, uuid4 import pytest +from typing_extensions import Literal from pydantic import UUID1, BaseConfig, BaseModel, PydanticTypeError, ValidationError, conint, errors, validator from pydantic.error_wrappers import flatten_errors, get_exc_type from pydantic.errors import StrRegexError -from pydantic.typing import Literal def test_pydantic_error(): @@ -35,7 +35,6 @@ def test_pydantic_error_pickable(): assert error.pattern == 'pika' -@pytest.mark.skipif(not Literal, reason='typing_extensions not installed') def test_interval_validation_error(): class Foo(BaseModel): model_type: Literal['foo'] diff --git a/tests/test_forward_ref.py b/tests/test_forward_ref.py index ffcac4ec5f..95595fcbfe 100644 --- a/tests/test_forward_ref.py +++ b/tests/test_forward_ref.py @@ -4,7 +4,6 @@ import pytest from pydantic import BaseModel, ConfigError, ValidationError -from pydantic.typing import Literal skip_pre_37 = pytest.mark.skipif(sys.version_info < (3, 7), reason='testing >= 3.7 behaviour only') @@ -482,7 +481,6 @@ def module(): @skip_pre_37 -@pytest.mark.skipif(not Literal, reason='typing_extensions not installed') def test_resolve_forward_ref_dataclass(create_module): module = create_module( # language=Python diff --git a/tests/test_main.py b/tests/test_main.py index 7046a391d4..c6109f8427 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -703,7 +703,6 @@ class Config: assert m.foo == 'foo' -@pytest.mark.skipif(not Literal, reason='typing_extensions not installed') def test_literal_enum_values(): FooEnum = Enum('FooEnum', {'foo': 'foo_value', 'bar': 'bar_value'}) diff --git a/tests/test_schema.py b/tests/test_schema.py index f7d5d661d1..7408f7fc4c 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -25,6 +25,7 @@ from uuid import UUID import pytest +from typing_extensions import Literal from pydantic import BaseModel, Extra, Field, ValidationError, conlist, conset, validator from pydantic.color import Color @@ -74,7 +75,6 @@ conint, constr, ) -from pydantic.typing import Literal try: import email_validator @@ -1754,7 +1754,6 @@ class Model(BaseModel): } -@pytest.mark.skipif(not Literal, reason='typing_extensions not installed and python version < 3.8') def test_literal_schema(): class Model(BaseModel): a: Literal[1] diff --git a/tests/test_types.py b/tests/test_types.py index 66bb6b8627..4b6ef72409 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -26,6 +26,7 @@ from uuid import UUID import pytest +from typing_extensions import Literal from pydantic import ( UUID1, @@ -69,7 +70,7 @@ errors, validator, ) -from pydantic.typing import Literal, NoneType +from pydantic.typing import NoneType try: import email_validator @@ -2508,7 +2509,6 @@ class Model(BaseModel): ] -@pytest.mark.skipif(not Literal, reason='typing_extensions not installed') def test_literal_single(): class Model(BaseModel): a: Literal['a'] @@ -2526,7 +2526,6 @@ class Model(BaseModel): ] -@pytest.mark.skipif(not Literal, reason='typing_extensions not installed') def test_literal_multiple(): class Model(BaseModel): a_or_b: Literal['a', 'b'] @@ -2723,9 +2722,7 @@ class Model(BaseModel): assert Model(v=deque((1, 2, 3))).json() == '{"v": [1, 2, 3]}' -none_value_type_cases = (None, type(None), NoneType) -if Literal: - none_value_type_cases += (Literal[None],) +none_value_type_cases = None, type(None), NoneType, Literal[None] @pytest.mark.parametrize('value_type', none_value_type_cases) diff --git a/tests/test_utils.py b/tests/test_utils.py index f7cd163607..dd45ca1483 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -9,15 +9,14 @@ from typing import Callable, Dict, List, NewType, Tuple, TypeVar, Union import pytest +from typing_extensions import Annotated, Literal from pydantic import VERSION, BaseModel, ConstrainedList, conlist from pydantic.color import Color from pydantic.dataclasses import dataclass from pydantic.fields import Undefined from pydantic.typing import ( - Annotated, ForwardRef, - Literal, all_literal_values, display_as_type, get_args, @@ -366,7 +365,6 @@ class Foo: assert f.attr == 'not foo' -@pytest.mark.skipif(not Literal, reason='typing_extensions not installed') def test_all_literal_values(): L1 = Literal['1'] assert all_literal_values(L1) == ('1',) diff --git a/tests/test_validators.py b/tests/test_validators.py index d97973a089..c863eda6c4 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -5,10 +5,10 @@ from typing import Dict, List, Optional, Tuple import pytest +from typing_extensions import Literal from pydantic import BaseModel, ConfigError, Extra, Field, ValidationError, errors, validator from pydantic.class_validators import make_generic_validator, root_validator -from pydantic.typing import Literal def test_simple(): @@ -1142,7 +1142,6 @@ def check_foo(cls, value): assert validator_calls == 2 -@pytest.mark.skipif(not Literal, reason='typing_extensions not installed') def test_literal_validator(): class Model(BaseModel): a: Literal['foo'] @@ -1161,7 +1160,6 @@ class Model(BaseModel): ] -@pytest.mark.skipif(not Literal, reason='typing_extensions not installed') def test_literal_validator_str_enum(): class Bar(str, Enum): FIZ = 'fiz' @@ -1183,7 +1181,6 @@ class Foo(BaseModel): assert my_foo.fizfuz is Bar.FUZ -@pytest.mark.skipif(not Literal, reason='typing_extensions not installed') def test_nested_literal_validator(): L1 = Literal['foo'] L2 = Literal['bar']