8000 Add eval_type_backport to handle union operator and builtin generic subscripting in older Pythons by alexmojaki · Pull Request #8209 · pydantic/pydantic · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
75986a9
Add eval_type_backport to handle union operator in older Pythons
alexmojaki Oct 29, 2023
ca60caa
Use modified get_type_hints in test_config
alexmojaki Oct 29, 2023
a163ea3
unskip a couple more tests in older pythons
alexmojaki Oct 29, 2023
8033556
Use pipe operator in a bunch of tests
alexmojaki Oct 29, 2023
0495f0f
various misc tidying up: use default localns=None, handle None values…
alexmojaki Nov 4, 2023
780b46a
explain asserts
alexmojaki Nov 4, 2023
4c536b6
type hints
alexmojaki Nov 4, 2023
d7f874b
inline node_to_ref
alexmojaki Nov 4, 2023
09d2e93
is_unsupported_types_for_union_error
alexmojaki Nov 4, 2023
d7b1462
tidying
alexmojaki Nov 4, 2023
c65ae3d
remove more type: ignore comments
alexmojaki Nov 4, 2023
3aa88da
docstrings and tidying
alexmojaki Nov 4, 2023
ca09a03
fix and tighten test_is_union
alexmojaki Nov 4, 2023
4890fc8
Merge branch 'main' of github.com:pydantic/pydantic into eval_type_ba…
alexmojaki Nov 22, 2023
c2d9d22
Use `eval_type_backport` package
alexmojaki Nov 22, 2023
40a41fb
Add test dependency
alexmojaki Nov 22, 2023
7da17e0
Merge branch 'main' of github.com:pydantic/pydantic into eval_type_ba…
alexmojaki Dec 16, 2023
a998d14
upgrade eval_type_backport
alexmojaki Dec 16, 2023
4f465a3
fix pdm.lock
alexmojaki Dec 16, 2023
71ca7cf
upgrade eval_type_backport to handle fussy typing._type_check
alexmojaki Dec 16, 2023
f060876
update is_backport_fixable_error and move down, update eval_type_back…
alexmojaki Dec 16, 2023
2b42d65
raise helpful error if eval_type_backport isn't installed. ensure tes…
alexmojaki Dec 17, 2023
dcbdd28
Restore skip, add another test for combination of backport and Pydant…
alexmojaki Dec 17, 2023
959c755
Test that eval_type_backport is being called in the right places
alexmojaki Dec 17, 2023
71c912e
test calling backport from get_type_hints
alexmojaki Dec 17, 2023
9847058
upgrade eval_type_backport to handle working union operator
alexmojaki Dec 17, 2023
6beab5b
unskip tests that can now pass in 3.8
alexmojaki Dec 17, 2023
b79692b
revert scattered test changes
alexmojaki Dec 17, 2023
6bc0ee4
unskip more tests
alexmojaki Dec 17, 2023
f423659
upgrade eval_type_backport to copy ForwardRef attributes, allowing un…
alexmojaki Dec 17, 2023
d3d5584
Merge branch 'main' of github.com:pydantic/pydantic into eval_type_ba…
alexmojaki Jan 15, 2024
aa21092
revert moving part of pyproject.toml
alexmojaki Jan 15, 2024
8da4294
Refine and test error raised when eval_type_backport isn't installed
alexmojaki Jan 16, 2024
60aa70f
use a type annotation that's unsupported in 3.9, not just 3.8
alexmojaki Jan 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
unskip more tests
  • Loading branch information
alexmojaki committed Dec 17, 2023
commit 6bc0ee45347bc168fa6629a7c8efd1170afc661f
4 changes: 2 additions & 2 deletions pydantic/_internal/_generate_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -989,7 +989,7 @@ def _common_field_schema( # C901
# Ensure that typevars get mapped to their concrete types:
types_namespace.update({k.__name__: v for k, v in self._typevars_map.items()})

evaluated = _typing_extra.eval_type_lenient(field_info.annotation, types_namespace, None)
evaluated = _typing_extra.eval_type_lenient(field_info.annotation, types_namespace)
if evaluated is not field_info.annotation and not has_instance_in_type(evaluated, PydanticRecursiveRef):
field_info.annotation = evaluated

Expand Down Expand Up @@ -1118,7 +1118,7 @@ def _type_alias_type_schema(
self._types_namespace = new_namespace
typevars_map = get_standard_typevars_map(obj)

annotation = _typing_extra.eval_type_lenient(annotation, self._types_namespace, None)
annotation = _typing_extra.eval_type_lenient(annotation, self._types_namespace)
annotation = replace_types(annotation, typevars_map)
schema = self.generate_schema(annotation)
assert schema['type'] != 'definitions'
Expand Down
2 changes: 1 addition & 1 deletion pydantic/_internal/_typing_extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def get_cls_type_hints_lenient(obj: Any, globalns: dict[str, Any] | None = None)
return hints


def eval_type_lenient(value: Any, globalns: dict[str, Any] | None, localns: dict[str, Any] | None) -> Any:
def eval_type_lenient(value: Any, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None) -> Any:
"""Behaves like typing._eval_type, except it won't raise an error if a forward reference can't be resolved."""
if value is None:
value = NoneType
Expand Down
2 changes: 1 addition & 1 deletion pydantic/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ def apply_typevars_map(self, typevars_map: dict[Any, Any] | None, types_namespac
pydantic._internal._generics.replace_types is used for replacing the typevars with
their concrete types.
"""
annotation = _typing_extra.eval_type_lenient(self.annotation, types_namespace, None)
annotation = _typing_extra.eval_type_lenient(self.annotation, types_namespace)
self.annotation = _generics.replace_types(annotation, typevars_map)

def __repr_args__(self) -> ReprArgs:
Expand Down
27 changes: 12 additions & 15 deletions tests/test_edge_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@ class Model(BaseModel):
(dict, frozenset, list, set, tuple, type),
],
)
@pytest.mark.skipif(sys.version_info < (3, 9), reason='PEP585 generics only supported for python 3.9 and above')
def test_pep585_generic_types(dict_cls, frozenset_cls, list_cls, set_cls, tuple_cls, type_cls):
class Type1:
pass
Expand All @@ -313,19 +312,19 @@ class Type2:

class Model(BaseModel, arbitrary_types_allowed=True):
a: dict_cls
a1: dict_cls[str, int]
a1: 'dict_cls[str, int]'
b: frozenset_cls
b1: frozenset_cls[int]
b1: 'frozenset_cls[int]'
c: list_cls
c1: list_cls[int]
c1: 'list_cls[int]'
d: set_cls
d1: set_cls[int]
d1: 'set_cls[int]'
e: tuple_cls
e1: tuple_cls[int]
e2: tuple_cls[int, ...]
e3: tuple_cls[()]
e1: 'tuple_cls[int]'
e2: 'tuple_cls[int, ...]'
e3: 'tuple_cls[()]'
f: type_cls
< 8000 /td> f1: type_cls[Type1]
f1: 'type_cls[Type1]'

default_model_kwargs = dict(
a={},
Expand Down Expand Up @@ -361,7 +360,7 @@ class Model(BaseModel, arbitrary_types_allowed=True):
assert m.f1 == Type1

with pytest.raises(ValidationError) as exc_info:
Model(**(default_model_kwargs | {'e3': (1,)}))
Model(**{**default_model_kwargs, 'e3': (1,)})
# insert_assert(exc_info.value.errors(include_url=False))
assert exc_info.value.errors(include_url=False) == [
{
Expand All @@ -373,10 +372,10 @@ class Model(BaseModel, arbitrary_types_allowed=True):
}
]

Model(**(default_model_kwargs | {'f': Type2}))
Model(**{**default_model_kwargs, 'f': Type2})

with pytest.raises(ValidationError) as exc_info:
Model(**(default_model_kwargs | {'f1': Type2}))
Model(**{**default_model_kwargs, 'f1': Type2})
# insert_assert(exc_info.value.errors(include_url=False))
assert exc_info.value.errors(include_url=False) == [
{
Expand Down Expand Up @@ -2382,10 +2381,9 @@ class Square(AbstractSquare):
Square(side=1.0)


@pytest.mark.skipif(sys.version_info < (3, 9), reason='cannot use list.__class_getitem__ before 3.9')
def test_generic_wrapped_forwardref():
class Operation(BaseModel):
callbacks: list['PathItem']
callbacks: 'list[PathItem]'

class PathItem(BaseModel):
pass
Expand Down Expand Up @@ -2483,7 +2481,6 @@ class C(BaseModel):
]


@pytest.mark.skipif(sys.version_info < (3, 9), reason='cannot parametrize types before 3.9')
@pytest.mark.parametrize(
('sequence_type', 'input_data', 'expected_error_type', 'expected_error_msg', 'expected_error_ctx'),
[
Expand Down
23 changes: 10 additions & 13 deletions tests/test_forward_ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,11 @@ def test_self_reference_json_schema_with_future_annotations(create_module):
# language=Python
"""
from __future__ import annotations
from typing import List
from pydantic import BaseModel

class Account(BaseModel):
name: str
subaccounts: List[Account] = []
subaccounts: list[Account] = []
"""
)
Account = module.Account
Expand Down Expand Up @@ -376,7 +375,6 @@ def test_circular_reference_json_schema_with_future_annotations(create_module):
# language=Python
"""
from __future__ import annotations
from typing import List
from pydantic import BaseModel

class Owner(BaseModel):
Expand All @@ -385,7 +383,7 @@ class Owner(BaseModel):
class Account(BaseModel):
name: str
owner: Owner
subaccounts: List[Account] = []
subaccounts: list[Account] = []

"""
)
Expand Down Expand Up @@ -440,28 +438,27 @@ def test_forward_ref_optional(create_module):
# language=Python
"""
from __future__ import annotations
from pydantic import BaseModel, Field, ConfigDict
from typing import List, Optional
from pydantic import BaseModel, Field


class Spec(BaseModel):
spec_fields: List[str] = Field(..., alias="fields")
filter: Optional[str] = None
sort: Optional[str]
spec_fields: list[str] = Field(..., alias="fields")
filter: str | None = None
sort: str | None


class PSpec(Spec):
g: Optional[GSpec] = None
g: GSpec | None = None


class GSpec(Spec):
p: Optional[PSpec]
p: PSpec | None

# PSpec.model_rebuild()

class Filter(BaseModel):
g: Optional[GSpec] = None
p: Optional[PSpec]
g: GSpec | None = None
p: PSpec | None
"""
)
Filter = module.Filter
Expand Down
4 changes: 2 additions & 2 deletions tests/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pydantic import Field # noqa: F401
from pydantic._internal._typing_extra import (
NoneType,
eval_type_backport,
eval_type_lenient,
get_function_type_hints,
is_classvar,
is_literal_type,
Expand Down Expand Up @@ -64,7 +64,7 @@ def test_is_none_type():
'union',
[
typing.Union[int, str],
eval_type_backport(typing.ForwardRef('int | str')),
eval_type_lenient('int | str'),
*([int | str] if sys.version_info >= (3, 10) else []),
],
)
Expand Down
4 changes: 0 additions & 4 deletions tests/test_validate_call.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import asyncio
import inspect
import re
import sys
from datetime import datetime, timezone
from functools import partial
from typing import Any, List, Tuple
Expand All @@ -13,8 +12,6 @@
from pydantic import Field, PydanticInvalidForJsonSchema, TypeAdapter, ValidationError, validate_call
from pydantic.main import BaseModel

skip_pre_39 = pytest.mark.skipif(sys.version_info < (3, 9), reason='testing >= 3.9 behaviour only')


def test_args():
@validate_call
Expand Down Expand Up @@ -372,7 +369,6 @@ def foo(self, a: int, b: int):
]


@skip_pre_39
def test_class_method():
class X:
@classmethod
Expand Down
5 changes: 0 additions & 5 deletions tests/test_validators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import contextlib
import re
import sys
from collections import deque
from dataclasses import dataclass
from datetime import date, datetime
Expand Down Expand Up @@ -1602,7 +1601,6 @@ def check_foo(cls, value):
assert validator_calls == 2


@pytest.mark.skipif(sys.version_info[:2] == (3, 8), reason='https://github.com/python/cpython/issues/103592')
def test_literal_validator():
class Model(BaseModel):
a: Literal['foo']
Expand Down Expand Up @@ -1643,9 +1641,6 @@ class Foo(BaseModel):
assert my_foo.fizfuz is Bar.FUZ


@pytest.mark.skipif(
sys.version_info[:2] == (3, 8), reason='https://github.com/python/cpython/issues/103592', strict=False
)
def test_nested_literal_validator():
L1 = Literal['foo']
L2 = Literal['bar']
Expand Down
0