Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit43bbdc0

Browse files
authored
Rebuild dataclass fields before schema generation (#11949)
This applying roughly the same logic from#11388 for dataclasses.Also fix third-party tests using uv.
1 parente1f9d15 commit43bbdc0

File tree

6 files changed

+135
-33
lines changed

6 files changed

+135
-33
lines changed

‎.github/workflows/third-party.yml

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
# - Make sure Pydantic is installed in editable mode (e.g. `uv pip install -e ./pydantic-latest`)
99
# so that the path appears in the `pip list` output (and so we can be assured Pydantic was properly
1010
# installed from the provided path).
11+
# - If using `uv run`, make use to use the `--no-sync`, because uv has the nasty habit of syncing the venv
12+
# on each `uv run` invocation, which will reinstall the locked `pydantic`/`pydantic-core` version.
1113
name:Third party tests
1214

1315
on:
@@ -61,14 +63,14 @@ jobs:
6163
-name:Install FastAPI dependencies
6264
run:|
6365
uv venv --python ${{ matrix.python-version }}
64-
uv pip install -r requirements-tests.txt
66+
uv pip install --no-progress -r requirements-tests.txt
6567
uv pip install -e ./pydantic-latest
6668
6769
-name:List installed dependencies
6870
run:uv pip list
6971

7072
-name:Run FastAPI tests
71-
run:PYTHONPATH=./docs_src uv run --no-project pytest tests
73+
run:PYTHONPATH=./docs_src uv run --no-project--no-syncpytest tests
7274

7375
test-sqlmodel:
7476
name:Test SQLModel (main branch) on Python ${{ matrix.python-version }}
@@ -101,14 +103,14 @@ jobs:
101103
-name:Install SQLModel dependencies
102104
run:|
103105
uv venv --python ${{ matrix.python-version }}
104-
uv pip install -r requirements-tests.txt
106+
uv pip install --no-progress -r requirements-tests.txt
105107
uv pip install -e ./pydantic-latest
106108
107109
-name:List installed dependencies
108110
run:uv pip list
109111

110112
-name:Run SQLModel tests
111-
run:uv run --no-project pytest tests
113+
run:uv run --no-project--no-syncpytest tests
112114

113115
test-beanie:
114116
name:Test Beanie (main branch) on Python ${{ matrix.python-version }}
@@ -232,7 +234,7 @@ jobs:
232234
-name:Install Pandera dependencies
233235
run:|
234236
pip install uv
235-
uv sync --extra pandas --extra fastapi --extra pandas --group dev --group testing --group docs
237+
uv sync --no-progress --extra pandas --extra fastapi --extra pandas --group dev --group testing --group docs
236238
uv pip uninstall --system pydantic pydantic-core
237239
uv pip install --system -e ./pydantic-latest
238240
@@ -242,7 +244,7 @@ jobs:
242244
-name:Run Pandera tests
243245
# Pandera's CI uses nox sessions which encapsulate the logic to install a specific Pydantic version.
244246
# Instead, manually run pytest (we run core, pandas and FastAPI tests):
245-
run:uv run pytest tests/base tests/pandas tests/fastapi
247+
run:uv run--no-syncpytest tests/base tests/pandas tests/fastapi
246248

247249
test-odmantic:
248250
name:Test ODMantic (main branch) on Python ${{ matrix.python-version }}
@@ -394,26 +396,26 @@ jobs:
394396
-name:🔧 uv install
395397
working-directory:./server
396398
run:|
397-
uv sync --dev
399+
uv sync --no-progress --dev
398400
uv pip uninstall pydantic
399401
uv pip install -e ./../pydantic-latest
400-
uv run task generate_dev_jwks
402+
uv run--no-synctask generate_dev_jwks
401403
402404
-name:List installed dependencies
403405
working-directory:./server
404406
run:uv pip list
405407

406408
-name:⚗️ alembic migrate
407409
working-directory:./server
408-
run:uv run task db_migrate
410+
run:uv run--no-synctask db_migrate
409411

410412
-name:⚗️ alembic check
411413
working-directory:./server
412-
run:uv run alembic check
414+
run:uv run--no-syncalembic check
413415

414416
-name:🐍 Run polar tests (pytest)
415417
working-directory:./server
416-
run:uv run pytest -n auto --no-cov
418+
run:uv run--no-syncpytest -n auto --no-cov
417419

418420
test-bentoml:
419421
name:Test BentoML (main branch) on Python ${{ matrix.python-version }}
@@ -486,7 +488,7 @@ jobs:
486488
with:
487489
path:pydantic-latest
488490

489-
-name:InstallUV
491+
-name:Installuv
490492
uses:astral-sh/setup-uv@v6
491493
with:
492494
python-version:${{ matrix.python-version }}
@@ -495,7 +497,7 @@ jobs:
495497
-name:Install Semantic Kernel dependencies
496498
working-directory:./python
497499
run:|
498-
uv sync --all-extras --dev --prerelease=if-necessary-or-explicit
500+
uv sync --no-progress --all-extras --dev --prerelease=if-necessary-or-explicit
499501
uv pip uninstall pydantic
500502
uv pip install -e ../pydantic-latest
501503
@@ -505,7 +507,7 @@ jobs:
505507

506508
-name:Run Semantic Kernel tests
507509
working-directory:./python
508-
run:uv run --frozen pytest ./tests/unit
510+
run:uv run --frozen--no-syncpytest ./tests/unit
509511

510512
test-langchain:
511513
name:Test LangChain (main branch) on Python ${{ matrix.python-version }}
@@ -531,14 +533,16 @@ jobs:
531533
with:
532534
path:pydantic-latest
533535

534-
-name:InstallUV
536+
-name:Installuv
535537
uses:astral-sh/setup-uv@v6
536538
with:
537539
python-version:${{ matrix.python-version }}
538540

539541
-name:Install LangChain dependencies
540542
run:|
541-
uv sync --group test
543+
uv sync --no-progress --group test
544+
uv sync --no-progress --directory ./libs/core --group test
545+
uv sync --no-progress --directory ./libs/langchain --group test
542546
uv pip uninstall pydantic
543547
uv pip install -e ./pydantic-latest
544548
@@ -547,11 +551,11 @@ jobs:
547551

548552
-name:Run LangChain core tests
549553
working-directory:./libs/core
550-
run:make test
554+
run:UV_NO_SYNC=1make test
551555

552556
-name:Run LangChain main tests
553557
working-directory:./libs/langchain
554-
run:make test
558+
run:UV_NO_SYNC=1make test
555559

556560
test-dify:
557561
name:Test Dify (main branch) on Python ${{ matrix.python-version }}
@@ -583,19 +587,19 @@ jobs:
583587

584588
-name:Install uv
585589
shell:bash
586-
run:pip install uv~=0.6.14
590+
run:pip install uv~=0.7.0
587591

588592
-name:Install Dify dependencies
589593
run:|
590-
uv sync --directory api --dev
594+
uv sync --no-progress --directory api --dev
591595
uv pip --directory api uninstall pydantic
592596
uv pip --directory api install -e ../pydantic-latest
593597
594598
-name:List installed dependencies
595599
run:uv pip --directory api list
596600

597601
-name:Run Dify unit tests
598-
run:uv run --project api bash dev/pytest/pytest_unit_tests.sh
602+
run:uv run --no-sync --project api bash dev/pytest/pytest_unit_tests.sh
599603

600604
test-cadwyn:
601605
name:Test Cadwyn (main branch) on Python ${{ matrix.python-version }}
@@ -628,14 +632,14 @@ jobs:
628632

629633
-name:Install Cadwyn dependencies
630634
run:|
631-
uv sync --dev --all-extras
635+
uv sync --no-progress --dev --all-extras
632636
uv pip install -e ./pydantic-latest
633637
634638
-name:List installed dependencies
635639
run:uv pip list
636640

637641
-name:Run Cadwyn tests
638-
run:uv run --no-project pytest tests docs_src
642+
run:uv run --no-project--no-syncpytest tests docs_src
639643

640644

641645
create-issue-on-failure:

‎pydantic/_internal/_dataclasses.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ class PydanticDataclass(StandardDataclass, typing.Protocol):
5454
__pydantic_serializer__:ClassVar[SchemaSerializer]
5555
__pydantic_validator__:ClassVar[SchemaValidator|PluggableSchemaValidator]
5656

57+
@classmethod
58+
def__pydantic_fields_complete__(self)->bool: ...
59+
5760
else:
5861
# See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915
5962
# and https://youtrack.jetbrains.com/issue/PY-51428

‎pydantic/_internal/_fields.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
from ..fieldsimportFieldInfo
3535
from ..mainimportBaseModel
36-
from ._dataclassesimportStandardDataclass
36+
from ._dataclassesimportPydanticDataclass,StandardDataclass
3737
from ._decoratorsimportDecoratorInfos
3838

3939

@@ -485,7 +485,7 @@ def collect_dataclass_fields(
485485
continue
486486

487487
globalns,localns=ns_resolver.types_namespace
488-
ann_type,_=_typing_extra.try_eval_type(dataclass_field.type,globalns,localns)
488+
ann_type,evaluated=_typing_extra.try_eval_type(dataclass_field.type,globalns,localns)
489489

490490
if_typing_extra.is_classvar_annotation(ann_type):
491491
continue
@@ -512,10 +512,16 @@ def collect_dataclass_fields(
512512
field_info=FieldInfo_.from_annotated_attribute(
513513
ann_type,dataclass_field.default,_source=AnnotationSource.DATACLASS
514514
)
515+
field_info._original_assignment=dataclass_field.default
515516
else:
516517
field_info=FieldInfo_.from_annotated_attribute(
517518
ann_type,dataclass_field,_source=AnnotationSource.DATACLASS
518519
)
520+
field_info._original_assignment=dataclass_field
521+
522+
ifnotevaluated:
523+
field_info._complete=False
524+
field_info._original_annotation=ann_type
519525

520526
fields[ann_name]=field_info
521527
update_field_from_config(config_wrapper,ann_name,field_info)
@@ -545,6 +551,52 @@ def collect_dataclass_fields(
545551
returnfields
546552

547553

554+
defrebuild_dataclass_fields(
555+
cls:type[PydanticDataclass],
556+
*,
557+
config_wrapper:ConfigWrapper,
558+
ns_resolver:NsResolver,
559+
typevars_map:Mapping[TypeVar,Any],
560+
)->dict[str,FieldInfo]:
561+
"""Rebuild the (already present) dataclass fields by trying to reevaluate annotations.
562+
563+
This function should be called whenever a dataclass with incomplete fields is encountered.
564+
565+
Raises:
566+
NameError: If one of the annotations failed to evaluate.
567+
568+
Note:
569+
This function *doesn't* mutate the dataclass fields in place, as it can be called during
570+
schema generation, where you don't want to mutate other dataclass's fields.
571+
"""
572+
FieldInfo_=import_cached_field_info()
573+
574+
rebuilt_fields:dict[str,FieldInfo]= {}
575+
withns_resolver.push(cls):
576+
forf_name,field_infoincls.__pydantic_fields__.items():
577+
iffield_info._complete:
578+
rebuilt_fields[f_name]=field_info
579+
else:
580+
existing_desc=field_info.description
581+
ann=_typing_extra.eval_type(
582+
field_info._original_annotation,
583+
*ns_resolver.types_namespace,
584+
)
585+
ann=_generics.replace_types(ann,typevars_map)
586+
new_field=FieldInfo_.from_annotated_attribute(
587+
ann,
588+
field_info._original_assignment,
589+
_source=AnnotationSource.DATACLASS,
590+
)
591+
592+
# The description might come from the docstring if `use_attribute_docstrings` was `True`:
593+
new_field.description=new_field.descriptionifnew_field.descriptionisnotNoneelseexisting_desc
594+
update_field_from_config(config_wrapper,f_name,new_field)
595+
rebuilt_fields[f_name]=new_field
596+
597+
returnrebuilt_fields
598+
599+
548600
defis_valid_field_name(name:str)->bool:
549601
returnnotname.startswith('_')
550602

‎pydantic/_internal/_generate_schema.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
from ._docs_extractionimportextract_docstrings_from_cls
8989
from ._fieldsimport (
9090
collect_dataclass_fields,
91+
rebuild_dataclass_fields,
9192
rebuild_model_fields,
9293
takes_validated_data_argument,
9394
update_field_from_config,
@@ -1779,14 +1780,27 @@ def _dataclass_schema(
17791780

17801781
withself._ns_resolver.push(dataclass),self._config_wrapper_stack.push(config):
17811782
ifis_pydantic_dataclass(dataclass):
1782-
# Copy the field info instances to avoid mutating the `FieldInfo` instances
1783-
# of the generic dataclass generic origin (e.g. `apply_typevars_map` below).
1784-
# Note that we don't apply `deepcopy` on `__pydantic_fields__` because we
1785-
# don't want to copy the `FieldInfo` attributes:
1786-
fields= {f_name:copy(field_info)forf_name,field_infoindataclass.__pydantic_fields__.items()}
1787-
iftypevars_map:
1788-
forfieldinfields.values():
1789-
field.apply_typevars_map(typevars_map,*self._types_namespace)
1783+
ifdataclass.__pydantic_fields_complete__():
1784+
# Copy the field info instances to avoid mutating the `FieldInfo` instances
1785+
# of the generic dataclass generic origin (e.g. `apply_typevars_map` below).
1786+
# Note that we don't apply `deepcopy` on `__pydantic_fields__` because we
1787+
# don't want to copy the `FieldInfo` attributes:
1788+
fields= {
1789+
f_name:copy(field_info)forf_name,field_infoindataclass.__pydantic_fields__.items()
1790+
}
1791+
iftypevars_map:
1792+
forfieldinfields.values():
1793+
field.apply_typevars_map(typevars_map,*self._types_namespace)
1794+
else:
1795+
try:
1796+
fields=rebuild_dataclass_fields(
1797+
dataclass,
1798+
config_wrapper=self._config_wrapper,
1799+
ns_resolver=self._ns_resolver,
1800+
typevars_map=typevars_mapor {},
1801+
)
1802+
exceptNameErrorase:
1803+
raisePydanticUndefinedAnnotation.from_name_error(e)frome
17901804
else:
17911805
fields=collect_dataclass_fields(
17921806
dataclass,

‎pydantic/dataclasses.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ def _dataclass_setstate(self: Any, state: list[Any]) -> None:
309309
original_cls.__firstlineno__=firstlineno
310310
cls.__firstlineno__=firstlineno
311311
cls.__qualname__=original_cls.__qualname__
312+
cls.__pydantic_fields_complete__=classmethod(_pydantic_fields_complete)
312313
cls.__pydantic_complete__=False# `complete_dataclass` will set it to `True` if successful.
313314
# TODO `parent_namespace` is currently None, but we could do the same thing as Pydantic models:
314315
# fetch the parent ns using `parent_frame_namespace` (if the dataclass was defined in a function),
@@ -319,6 +320,14 @@ def _dataclass_setstate(self: Any, state: list[Any]) -> None:
319320
returncreate_dataclassif_clsisNoneelsecreate_dataclass(_cls)
320321

321322

323+
def_pydantic_fields_complete(cls:type[PydanticDataclass])->bool:
324+
"""Return whether the fields where successfully collected (i.e. type hints were successfully resolves).
325+
326+
This is a private property, not meant to be used outside Pydantic.
327+
"""
328+
returnall(field_info._completeforfield_infoincls.__pydantic_fields__.values())
329+
330+
322331
__getattr__=getattr_migration(__name__)
323332

324333
ifsys.version_info< (3,11):

‎tests/test_dataclasses.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3129,3 +3129,23 @@ class A:
31293129
a:int
31303130

31313131
assert'a'inA.__pydantic_fields__# pyright: ignore[reportAttributeAccessIssue]
3132+
3133+
3134+
deftest_dataclass_fields_rebuilt_before_schema_generation()->None:
3135+
"""https://github.com/pydantic/pydantic/issues/11947"""
3136+
3137+
defupdate_schema(schema:dict[str,Any])->None:
3138+
schema['test']=schema['title']
3139+
3140+
@pydantic.dataclasses.dataclass
3141+
classA:
3142+
a:"""Annotated[
3143+
Forward,
3144+
Field(field_title_generator=lambda name, _: name, json_schema_extra=update_schema)
3145+
]"""=True
3146+
3147+
Forward=bool
3148+
3149+
ta=TypeAdapter(A)
3150+
3151+
assertta.json_schema()['properties']['a']['test']=='a'

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp