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

Commit3a7fe26

Browse files
authored
Emit warning when field-specific metadata is used in invalid contexts (#12028)
1 parentd156ba0 commit3a7fe26

File tree

7 files changed

+186
-52
lines changed

7 files changed

+186
-52
lines changed

‎docs/concepts/types.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ By leveraging the new [`type` statement](https://typing.readthedocs.io/en/latest
288288

289289
=== "Python 3.9 and above"
290290

291-
```python
291+
```python {test="skip"}
292292
from typing import Annotated
293293

294294
from typing_extensions import TypeAliasType
@@ -304,7 +304,7 @@ By leveraging the new [`type` statement](https://typing.readthedocs.io/en/latest
304304

305305
=== "Python 3.12 and above (new syntax)"
306306

307-
```python {requires="3.12" upgrade="skip" lint="skip"}
307+
```python {requires="3.12" upgrade="skip" lint="skip" test="skip"}
308308
from typing import Annotated
309309

310310
from pydantic import BaseModel, Field

‎pydantic/_internal/_fields.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
fromtypingimportTYPE_CHECKING,Any,Callable,TypeVar
1313

1414
frompydantic_coreimportPydanticUndefined
15-
fromtyping_extensionsimportTypeIs,get_origin
16-
fromtyping_inspectionimporttyping_objects
15+
fromtyping_extensionsimportTypeIs
1716
fromtyping_inspection.introspectionimportAnnotationSource
1817

1918
frompydanticimportPydanticDeprecatedSince211
@@ -351,7 +350,6 @@ def collect_model_fields( # noqa: C901
351350
field_info=copy(parent_fields_lookup[ann_name])
352351

353352
else:# An assigned value is present (either the default value, or a `Field()` function)
354-
_warn_on_nested_alias_in_annotation(ann_type,ann_name)
355353
ifisinstance(assigned_value,FieldInfo_)andismethoddescriptor(assigned_value.default):
356354
# `assigned_value` was fetched using `getattr`, which triggers a call to `__get__`
357355
# for descriptors, so we do the same if the `= field(default=...)` form is used.
@@ -414,22 +412,6 @@ def collect_model_fields( # noqa: C901
414412
returnfields,class_vars
415413

416414

417-
def_warn_on_nested_alias_in_annotation(ann_type:type[Any],ann_name:str)->None:
418-
FieldInfo=import_cached_field_info()
419-
420-
args=getattr(ann_type,'__args__',None)
421-
ifargs:
422-
foranno_arginargs:
423-
iftyping_objects.is_annotated(get_origin(anno_arg)):
424-
foranno_type_argin_typing_extra.get_args(anno_arg):
425-
ifisinstance(anno_type_arg,FieldInfo)andanno_type_arg.aliasisnotNone:
426-
warnings.warn(
427-
f'`alias` specification on field "{ann_name}" must be set on outermost annotation to take effect.',
428-
UserWarning,
429-
)
430-
return
431-
432-
433415
defrebuild_model_fields(
434416
cls:type[BaseModel],
435417
*,

‎pydantic/_internal/_generate_schema.py

Lines changed: 103 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
overload,
3838
)
3939
fromuuidimportUUID
40-
fromwarningsimportwarn
4140
fromzoneinfoimportZoneInfo
4241

4342
importtyping_extensions
@@ -62,7 +61,7 @@
6261
from ..functional_validatorsimportAfterValidator,BeforeValidator,FieldValidatorModes,PlainValidator,WrapValidator
6362
from ..json_schemaimportJsonSchemaValue
6463
from ..versionimportversion_short
65-
from ..warningsimportPydanticArbitraryTypeWarning,PydanticDeprecatedSince20
64+
from ..warningsimportArbitraryTypeWarning,PydanticDeprecatedSince20,UnsupportedFieldAttributeWarning
6665
from .import_decorators,_discriminated_union,_known_annotated_metadata,_repr,_typing_extra
6766
from ._configimportConfigWrapper,ConfigWrapperStack
6867
from ._core_metadataimportCoreMetadata,update_core_metadata
@@ -164,6 +163,24 @@
164163
]
165164

166165
VALIDATE_CALL_SUPPORTED_TYPES=get_args(ValidateCallSupportedTypes)
166+
UNSUPPORTED_STANDALONE_FIELDINFO_ATTRIBUTES= [
167+
('alias',None),
168+
('validation_alias',None),
169+
('serialization_alias',None),
170+
# will be set if any alias is set, so disable it to avoid double warnings:
171+
# 'alias_priority',
172+
('default',PydanticUndefined),
173+
('default_factory',None),
174+
('exclude',None),
175+
('deprecated',None),
176+
('repr',True),
177+
('validate_default',None),
178+
('frozen',None),
179+
('init',None),
180+
('init_var',None),
181+
('kw_only',None),
182+
]
183+
"""`FieldInfo` attributes (and their default value) that can't be used outside of a model (e.g. in a type adapter or a PEP 695 type alias)."""
167184

168185
_mode_to_validator:dict[
169186
FieldValidatorModes,type[BeforeValidator|AfterValidator|PlainValidator|WrapValidator]
@@ -562,7 +579,15 @@ def _mapping_schema(self, tp: Any, keys_type: Any, values_type: Any) -> CoreSche
562579

563580
mapped_origin=MAPPING_ORIGIN_MAP[tp]
564581
keys_schema=self.generate_schema(keys_type)
565-
values_schema=self.generate_schema(values_type)
582+
withwarnings.catch_warnings():
583+
# We kind of abused `Field()` default factories to be able to specify
584+
# the `defaultdict`'s `default_factory`. As a consequence, we get warnings
585+
# as normally `FieldInfo.default_factory` is unsupported in the context where
586+
# `Field()` is used and our only solution is to ignore them (note that this might
587+
# wrongfully ignore valid warnings, e.g. if the `value_type` is a PEP 695 type alias
588+
# with unsupported metadata).
589+
warnings.simplefilter('ignore',category=UnsupportedFieldAttributeWarning)
590+
values_schema=self.generate_schema(values_type)
566591
dict_schema=core_schema.dict_schema(keys_schema,values_schema,strict=False)
567592

568593
ifmapped_originisdict:
@@ -614,12 +639,12 @@ def _fraction_schema(self) -> CoreSchema:
614639

615640
def_arbitrary_type_schema(self,tp:Any)->CoreSchema:
616641
ifnotisinstance(tp,type):
617-
warn(
642+
warnings.warn(
618643
f'{tp!r} is not a Python type (it may be an instance of an object),'
619644
' Pydantic will allow any object with no validation since we cannot even'
620645
' enforce that the input is an instance of the given type.'
621646
' To get rid of this error wrap the type with `pydantic.SkipValidation`.',
622-
PydanticArbitraryTypeWarning,
647+
ArbitraryTypeWarning,
623648
)
624649
returncore_schema.any_schema()
625650
returncore_schema.is_instance_schema(tp)
@@ -903,12 +928,12 @@ def _generate_schema_from_get_schema_method(self, obj: Any, source: Any) -> core
903928
frompydantic.v1importBaseModelasBaseModelV1
904929

905930
ifissubclass(obj,BaseModelV1):
906-
warn(
931+
warnings.warn(
907932
f'Mixing V1 models and V2 models (or constructs, like `TypeAdapter`) is not supported. Please upgrade `{obj.__name__}` to V2.',
908933
UserWarning,
909934
)
910935
else:
911-
warn(
936+
warnings.warn(
912937
'`__get_validators__` is deprecated and will be removed, use `__get_pydantic_core_schema__` instead.',
913938
PydanticDeprecatedSince20,
914939
)
@@ -1525,7 +1550,14 @@ def _generate_parameter_schema(
15251550
update_field_from_config(self._config_wrapper,name,field)
15261551

15271552
withself.field_name_stack.push(name):
1528-
schema=self._apply_annotations(field.annotation, [field])
1553+
schema=self._apply_annotations(
1554+
field.annotation,
1555+
[field],
1556+
# Because we pass `field` as metadata above (required for attributes relevant for
1557+
# JSON Scheme generation), we need to ignore the potential warnings about `FieldInfo`
1558+
# attributes that will not be used:
1559+
check_unsupported_field_info_attributes=False,
1560+
)
15291561

15301562
ifnotfield.is_required():
15311563
schema=wrap_default(field,schema)
@@ -1567,7 +1599,14 @@ def _generate_parameter_v3_schema(
15671599
update_field_from_config(self._config_wrapper,name,field)
15681600

15691601
withself.field_name_stack.push(name):
1570-
schema=self._apply_annotations(field.annotation, [field])
1602+
schema=self._apply_annotations(
1603+
field.annotation,
1604+
[field],
1605+
# Because we pass `field` as metadata above (required for attributes relevant for
1606+
# JSON Scheme generation), we need to ignore the potential warnings about `FieldInfo`
1607+
# attributes that will not be used:
1608+
check_unsupported_field_info_attributes=False,
1609+
)
15711610

15721611
ifnotfield.is_required():
15731612
schema=wrap_default(field,schema)
@@ -2120,6 +2159,7 @@ def _apply_annotations(
21202159
source_type:Any,
21212160
annotations:list[Any],
21222161
transform_inner_schema:Callable[[CoreSchema],CoreSchema]=lambdax:x,
2162+
check_unsupported_field_info_attributes:bool=True,
21232163
)->CoreSchema:
21242164
"""Apply arguments from `Annotated` or from `FieldInfo` to a schema.
21252165
@@ -2150,7 +2190,10 @@ def inner_handler(obj: Any) -> CoreSchema:
21502190
ifannotationisNone:
21512191
continue
21522192
get_inner_schema=self._get_wrapped_inner_schema(
2153-
get_inner_schema,annotation,pydantic_js_annotation_functions
2193+
get_inner_schema,
2194+
annotation,
2195+
pydantic_js_annotation_functions,
2196+
check_unsupported_field_info_attributes=check_unsupported_field_info_attributes,
21542197
)
21552198

21562199
schema=get_inner_schema(source_type)
@@ -2159,10 +2202,31 @@ def inner_handler(obj: Any) -> CoreSchema:
21592202
update_core_metadata(core_metadata,pydantic_js_annotation_functions=pydantic_js_annotation_functions)
21602203
return_add_custom_serialization_from_json_encoders(self._config_wrapper.json_encoders,source_type,schema)
21612204

2162-
def_apply_single_annotation(self,schema:core_schema.CoreSchema,metadata:Any)->core_schema.CoreSchema:
2205+
def_apply_single_annotation(
2206+
self,
2207+
schema:core_schema.CoreSchema,
2208+
metadata:Any,
2209+
check_unsupported_field_info_attributes:bool=True,
2210+
)->core_schema.CoreSchema:
21632211
FieldInfo=import_cached_field_info()
21642212

21652213
ifisinstance(metadata,FieldInfo):
2214+
if (
2215+
check_unsupported_field_info_attributes
2216+
# HACK: we don't want to emit the warning for `FieldInfo` subclasses, because FastAPI does weird manipulations
2217+
# with its subclasses and their annotations:
2218+
andtype(metadata)isFieldInfo
2219+
and (unsupported_attributes:=self._get_unsupported_field_info_attributes(metadata))
2220+
):
2221+
forattr,valueinunsupported_attributes:
2222+
warnings.warn(
2223+
f'The{attr!r} attribute with value{value!r} was provided to the `Field()` function, '
2224+
f'which has no effect in the context it was used.{attr!r} is field-specific metadata, '
2225+
'and can only be attached to a model field using `Annotated` metadata or by assignment. '
2226+
'This may have happened because an `Annotated` type alias using the `type` statement was '
2227+
'used, or if the `Field()` function was attached to a single member of a union type.',
2228+
category=UnsupportedFieldAttributeWarning,
2229+
)
21662230
forfield_metadatainmetadata.metadata:
21672231
schema=self._apply_single_annotation(schema,field_metadata)
21682232

@@ -2217,11 +2281,34 @@ def _apply_single_annotation_json_schema(
22172281
)
22182282
returnschema
22192283

2284+
def_get_unsupported_field_info_attributes(self,field_info:FieldInfo)->list[tuple[str,Any]]:
2285+
"""Get the list of unsupported `FieldInfo` attributes when not directly used in `Annotated` for field annotations."""
2286+
unused_metadata:list[tuple[str,Any]]= []
2287+
forunused_metadata_name,unset_valueinUNSUPPORTED_STANDALONE_FIELDINFO_ATTRIBUTES:
2288+
if (
2289+
(unused_metadata_value:=getattr(field_info,unused_metadata_name))isnotunset_value
2290+
# `default` and `default_factory` can still be used with a type adapter, so only include them
2291+
# if used with a model-like class:
2292+
and (
2293+
unused_metadata_namenotin ('default','default_factory')
2294+
orself.model_type_stack.get()isnotNone
2295+
)
2296+
):
2297+
# Setting `alias` will set `validation/serialization_alias` as well, so we want to avoid duplicate warnings:
2298+
if (
2299+
unused_metadata_namenotin ('validation_alias','serialization_alias')
2300+
or'alias'notinfield_info._attributes_set
2301+
):
2302+
unused_metadata.append((unused_metadata_name,unused_metadata_value))
2303+
2304+
returnunused_metadata
2305+
22202306
def_get_wrapped_inner_schema(
22212307
self,
22222308
get_inner_schema:GetCoreSchemaHandler,
22232309
annotation:Any,
22242310
pydantic_js_annotation_functions:list[GetJsonSchemaFunction],
2311+
check_unsupported_field_info_attributes:bool=False,
22252312
)->CallbackGetCoreSchemaHandler:
22262313
annotation_get_schema:GetCoreSchemaFunction|None=getattr(annotation,'__get_pydantic_core_schema__',None)
22272314

@@ -2230,7 +2317,11 @@ def new_handler(source: Any) -> core_schema.CoreSchema:
22302317
schema=annotation_get_schema(source,get_inner_schema)
22312318
else:
22322319
schema=get_inner_schema(source)
2233-
schema=self._apply_single_annotation(schema,annotation)
2320+
schema=self._apply_single_annotation(
2321+
schema,
2322+
annotation,
2323+
check_unsupported_field_info_attributes=check_unsupported_field_info_attributes,
2324+
)
22342325
schema=self._apply_single_annotation_json_schema(schema,annotation)
22352326

22362327
metadata_js_function=_extract_get_pydantic_json_schema(annotation)

‎pydantic/functional_validators.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from ._internalimport_decorators,_generics,_internal_dataclass
1616
from .annotated_handlersimportGetCoreSchemaHandler
1717
from .errorsimportPydanticUserError
18-
from .warningsimportPydanticArbitraryTypeWarning
18+
from .warningsimportArbitraryTypeWarning
1919

2020
ifsys.version_info< (3,11):
2121
fromtyping_extensionsimportProtocol
@@ -820,7 +820,7 @@ def __class_getitem__(cls, item: Any) -> Any:
820820
@classmethod
821821
def__get_pydantic_core_schema__(cls,source:Any,handler:GetCoreSchemaHandler)->core_schema.CoreSchema:
822822
withwarnings.catch_warnings():
823-
warnings.simplefilter('ignore',PydanticArbitraryTypeWarning)
823+
warnings.simplefilter('ignore',ArbitraryTypeWarning)
824824
original_schema=handler(source)
825825
metadata= {'pydantic_js_annotation_functions': [lambda_c,h:h(original_schema)]}
826826
returncore_schema.any_schema(

‎pydantic/warnings.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
'PydanticDeprecatedSince211',
1313
'PydanticDeprecationWarning',
1414
'PydanticExperimentalWarning',
15+
'ArbitraryTypeWarning',
16+
'UnsupportedFieldAttributeWarning',
1517
)
1618

1719

@@ -96,5 +98,13 @@ class PydanticExperimentalWarning(Warning):
9698
"""
9799

98100

99-
classPydanticArbitraryTypeWarning(UserWarning):
100-
"""Warning raised when Pydantic fails to generate a core schema for an arbitrary type."""
101+
classCoreSchemaGenerationWarning(UserWarning):
102+
"""A warning raised during core schema generation."""
103+
104+
105+
classArbitraryTypeWarning(CoreSchemaGenerationWarning):
106+
"""A warning raised when Pydantic fails to generate a core schema for an arbitrary type."""
107+
108+
109+
classUnsupportedFieldAttributeWarning(CoreSchemaGenerationWarning):
110+
"""A warning raised when a `Field()` attribute isn't supported in the context it is used."""

‎tests/test_annotated.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -251,18 +251,6 @@ class _(BaseModel):
251251
calls.clear()
252252

253253

254-
deftest_annotated_alias_at_low_level()->None:
255-
withpytest.warns(
256-
UserWarning,
257-
match=r'`alias` specification on field "low_level_alias_field" must be set on outermost annotation to take effect.',
258-
):
259-
260-
classModel(BaseModel):
261-
low_level_alias_field:Optional[Annotated[int,Field(alias='field_alias')]]=None
262-
263-
assertModel(field_alias=1).low_level_alias_fieldisNone
264-
265-
266254
deftest_get_pydantic_core_schema_source_type()->None:
267255
types:set[Any]=set()
268256

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp