-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
In 2.11, we recently fixed issue #6352 with #11109.
from typing import Annotated
from pydantic import Field, BaseModel
type SomeAlias = Annotated[int, Field(description='number')]
class Model(BaseModel):
foo: Annotated[SomeAlias, Field(title='abc')]
print(Model.model_json_schema()) # description gets dropped
In this specific example, the issue wasn't lying in the core schema, but rather in the JSON Schema generation (in fact, we had a proposed fix fixing things in the GenerateJsonSchema
class).
However, we took a different approach to fix this, by eagerly unpacking the type alias (unpacking = inspecting the __value__
property), to get the same behavior as if an old-type type alias (that is, a simple assignment) was used. In this example, at runtime, the annotation of bar
is flattened by the Annotated
form, meaning it is in fact Annotated[int, Field(description='number'), Field(title='abc')]
type SomeAlias = Annotated[int, Field(description='number')]
OldAlias: TypeAlias = Annotated[int, Field(description='number')]
class Model(BaseModel):
foo: Annotated[SomeAlias, Field(title='abc')] # In 2.11, this annotation will be processed to be equivalent to `bar`
bar: Annotated[OldAlias, Field(title='abc')]
The reason we took this approach is because some metadata attributes specific to the class field (and not the annotated type) -- default
, default_factory
, alias
-- need to be treated during field collection. With the following:
type SomeAlias = Annotated[int, Field(alias='bar')]
class Model(BaseModel):
foo: SomeAlias
If we don't unpack SomeAlias
when building fields, it will only be inspected during core schema generation. When we encounter the alias
attribute (in GenerateSchema._annotated_schema()
), it's currently not really possible to apply it to the FieldInfo
instance (nor this is a good idea I believe).
So this works well, but it breaks the assumption that PEP 695 type aliases are referenceable, and so the following does not work as expected:
type MyDict = Annotated[dict[str, object], Field(description='my dict')]
class Model(BaseModel):
d1: MyDict
d2: MyDict
# MyDict is not stored as a ref
And so the core schema (and the JSON Schema) ends up like:
{
│ 'type': 'model',
│ 'cls': <class '__main__.Model'>,
│ 'schema': {
│ │ 'type': 'model-fields',
│ │ 'fields': {
│ │ │ 'd1': {
│ │ │ │ 'type': 'model-field',
│ │ │ │ 'schema': {'type': 'dict', 'keys_schema': {'type': 'str'}, 'values_schema': {'type': 'any'}},
│ │ │ │ 'metadata': {'pydantic_js_updates': {'description': 'my dict'}}
│ │ │ },
│ │ │ 'd2': {
│ │ │ │ 'type': 'model-field',
│ │ │ │ 'schema': {'type': 'dict', 'keys_schema': {'type': 'str'}, 'values_schema': {'type': 'any'}},
│ │ │ │ 'metadata': {'pydantic_js_updates': {'description': 'my dict'}}
│ │ │ }
│ │ },
│ │ 'model_name': 'Model',
│ │ 'computed_fields': []
│ },
│ 'config': {'title': 'Model'},
│ 'ref': '__main__.Model:101685426333792',
│ 'metadata': {'<stripped>'}
}
I'm really not sure what is the best approach here. One solution could be to not unpack PEP 695 type aliases during fields collection, fix the JSON Schema generation, and forbid using any Field()
metadata that is relevant to the actual field definition. However, some users actually want to do so (#6352 (comment), #10219).