8000 Unpacking annotated metadata from PEP 695 type aliases · Issue #11467 · pydantic/pydantic · GitHub
[go: up one dir, main page]

Skip to content
Unpacking annotated metadata from PEP 695 type aliases #11467
@Viicos

Description

@Viicos

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    changeSuggested alteration to Pydantic, not a new feature nor a bug

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0