Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.2k
Refactor namespace logic for annotations evaluation#10530
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes from26 commits
d32107f
e5c4207
9ef1965
1da3698
f4f2ae8
bb7ffff
2835c7f
9b77d05
132c06c
b40468e
cee89ad
24338c9
6a86ac8
93bd021
7c3bf4f
dd7e44c
7ae989e
d2d549d
e182033
89199ba
127aefe
66c024e
2a371ca
8d57b83
7861286
08691ec
86fe4cc
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -51,7 +51,7 @@ from datetime import datetime | ||
from typing_extensions import Annotated | ||
from pydantic import BaseModel | ||
from pydantic.experimental.pipeline import validate_as | ||
class User(BaseModel): | ||
@@ -71,10 +71,6 @@ class User(BaseModel): | ||
), | ||
] | ||
friends: Annotated[list[User], validate_as(...).len(0, 100)] # (6)! | ||
Comment on lines -74 to -77 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. CC@adriangb I think this is broken by this PR but maybe worth breaking. I think it's possible to fix by fiddling with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I think@Viicos decided this was worth breaking. As long as there's clear documentation showing the alternative path forward I'm okay with that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Copy pasting what I added on Slack so that it's not lost: the TL;DR is using lambdas with references to other symbols in annotations opens the door to a lot of weird behaviors. This is the (simplified) test failing on the PR: fromtyping_extensionsimportAnnotatedfrompydanticimportBaseModelfrompydantic.experimental.pipelineimportvalidate_as_deferredclassUser(BaseModel):family:'Annotated[list[User], validate_as_deferred(lambda: list[User])]'# The `family` annotation is successfully evaluated, but at some point during# core schema generation, the pipeline API logic is triggered and when the lambda# gets called, we end up with:# NameError: name 'User' is not defined On On this PR, we cleaned up how globals and locals were mixed up before. This means that we now use the following as globals: And the issue comes from what could be considered as a limitation of deffunc():A=intworks=lambda:list[A]fails=eval('lambda: list[A]',globals(),locals())works()# list[int]fails()# NameError: A is not defined. The This limitation is documented in this open CPythonPR. | ||
bio: Annotated[ | ||
datetime, | ||
validate_as(int) | ||
@@ -89,8 +85,7 @@ class User(BaseModel): | ||
4. You can also use the lower level transform, constrain and predicate methods. | ||
5. Use the `|` or `&` operators to combine steps (like a logical OR or AND). | ||
6. Calling `validate_as(...)` with `Ellipsis`, `...` as the first positional argument implies `validate_as(<field type>)`. Use `validate_as(Any)` to accept any type. | ||
7. You can call `validate_as()` before or after other steps to do pre or post processing. | ||
### Mapping from `BeforeValidator`, `AfterValidator` and `WrapValidator` | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -19,20 +19,23 @@ | ||
from ..errors import PydanticUndefinedAnnotation | ||
from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator | ||
from ..warnings import PydanticDeprecatedSince20 | ||
from . import _config, _decorators | ||
from ._fields import collect_dataclass_fields | ||
from ._generate_schema import GenerateSchema | ||
from ._generics import get_standard_typevars_map | ||
from ._mock_val_ser import set_dataclass_mocks | ||
from ._namespace_utils import NsResolver | ||
from ._schema_generation_shared import CallbackGetCoreSchemaHandler | ||
from ._signature import generate_pydantic_signature | ||
if typing.TYPE_CHECKING: | ||
from dataclasses import Field | ||
from ..config import ConfigDict | ||
from ..fields import FieldInfo | ||
class StandardDataclass(typing.Protocol): | ||
__dataclass_fields__: ClassVar[dict[str,Field[Any]]] | ||
__dataclass_params__: ClassVar[Any] # in reality `dataclasses._DataclassParams` | ||
__post_init__: ClassVar[Callable[..., None]] | ||
@@ -68,18 +71,20 @@ class PydanticDataclass(StandardDataclass, typing.Protocol): | ||
def set_dataclass_fields( | ||
cls: type[StandardDataclass], | ||
ns_resolver: NsResolver | None = None, | ||
config_wrapper: _config.ConfigWrapper | None = None, | ||
) -> None: | ||
"""Collect and set `cls.__pydantic_fields__`. | ||
Args: | ||
cls: The class. | ||
ns_resolver: Namespace resolver to use when getting dataclass annotations. | ||
config_wrapper: The config wrapper instance, defaults to `None`. | ||
""" | ||
typevars_map = get_standard_typevars_map(cls) | ||
fields = collect_dataclass_fields( | ||
cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper | ||
) | ||
cls.__pydantic_fields__ = fields # type: ignore | ||
@@ -89,7 +94,7 @@ def complete_dataclass( | ||
config_wrapper: _config.ConfigWrapper, | ||
*, | ||
raise_errors: bool = True, | ||
ns_resolver: NsResolver | None = None, | ||
_force_build: bool = False, | ||
) -> bool: | ||
"""Finish building a pydantic dataclass. | ||
@@ -102,7 +107,8 @@ def complete_dataclass( | ||
cls: The class. | ||
config_wrapper: The config wrapper instance. | ||
raise_errors: Whether to raise errors, defaults to `True`. | ||
ns_resolver: The namespace resolver instance to use when collecting dataclass fields | ||
and during schema building. | ||
_force_build: Whether to force building the dataclass, no matter if | ||
[`defer_build`][pydantic.config.ConfigDict.defer_build] is set. | ||
@@ -135,16 +141,13 @@ def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) - | ||
'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning | ||
) | ||
sydney-runkle marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
set_dataclass_fields(cls, ns_resolver, config_wrapper=config_wrapper) | ||
typevars_map = get_standard_typevars_map(cls) | ||
gen_schema = GenerateSchema( | ||
config_wrapper, | ||
ns_resolver=ns_resolver, | ||
typevars_map=typevars_map, | ||
) | ||
# This needs to be called before we change the __init__ | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.