Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Difference between TypeAdapter().json_schema and BaseModel.model_json_schema #9210

Open
1 task done
Mark90 opened this issue Apr 11, 2024 · 5 comments · May be fixed by #9636
Open
1 task done

Difference between TypeAdapter().json_schema and BaseModel.model_json_schema #9210

Mark90 opened this issue Apr 11, 2024 · 5 comments · May be fixed by #9636
Assignees
Labels
bug V2 Bug related to Pydantic V2 good first issue help wanted Pull Request welcome
Milestone

Comments

@Mark90
Copy link

Mark90 commented Apr 11, 2024

Initial Checks

  • I confirm that I'm using Pydantic V2

Description

When presented with an annotated type with multiple Field objects that set json_schema_extra, TypeAdapter seems to combine them whereas BaseModel does not.

I was used to the behavior of BaseModel of not combining anything, which I can understand because merging json schema's is non-trivial in the case of conflicting keys. I would expect TypeAdapter to follow the same rules.

Below code example outputs the following

BaseModel.model_json_schema
{'properties': {'v': {'key2': 'value2', 'title': 'V', 'type': 'string'}},  # only contains key2
 'required': ['v'],
 'title': 'Foo',
 'type': 'object'}

TypeAdapter().json_schema
{'key1': 'value1', 'key2': 'value2', 'type': 'string'}  # contains key1 and key2

Example Code

from pprint import pprint
from typing import Annotated

from pydantic import TypeAdapter, BaseModel, Field

MyStr = Annotated[
  str, 
  Field(json_schema_extra={'key1': 'value1'}), 
  Field(json_schema_extra={'key2': 'value2'})
]
class Foo(BaseModel):
    v: MyStr


print("BaseModel.model_json_schema")
pprint(Foo.model_json_schema())
print("TypeAdapter().json_schema")
pprint(TypeAdapter(MyStr).json_schema())

Python, Pydantic & OS Version

pydantic version: 2.6.4
        pydantic-core version: 2.16.3
          pydantic-core build: profile=release pgo=true
                 install path: /Users/mark/.pyenv/versions/3.12.1/envs/312pydantic26/lib/python3.12/site-packages/pydantic
               python version: 3.12.1 (main, Jan  2 2024, 17:23:05) [Clang 15.0.0 (clang-1500.0.40.1)]
                     platform: macOS-14.4.1-arm64-arm-64bit
             related packages: typing_extensions-4.11.0
                       commit: unknown
@Mark90 Mark90 added bug V2 Bug related to Pydantic V2 pending Awaiting a response / confirmation labels Apr 11, 2024
@sydney-runkle sydney-runkle added this to the v2.8.0 milestone Apr 11, 2024
@sydney-runkle sydney-runkle added good first issue help wanted Pull Request welcome bug V2 Bug related to Pydantic V2 and removed bug V2 Bug related to Pydantic V2 pending Awaiting a response / confirmation labels Apr 11, 2024
@sydney-runkle
Copy link
Member

@Mark90,

Thanks for reporting this. Definitely a bug - the schemas should match. I think we do actually want to merge the field infos like is done in the TypeAdapter case, but the answer should be somwhere in the fields.py or _fields.py.

Marking as a good first issue and adding to our next milestone!

@nix010
Copy link
Contributor

nix010 commented Apr 13, 2024

@sydney-runkle I can work on this one.

@nix010
Copy link
Contributor

nix010 commented Apr 16, 2024

@sydney-runkle While working on the issue I found something that I want to clarify.

from pprint import pprint
from typing import Annotated

from pydantic import TypeAdapter, BaseModel, Field


def _set_key2(s):
    s['key2'] = 'value2'


CustomField = Annotated[
    str,
    Field(json_schema_extra={'key1': 'value1'}),
    Field(json_schema_extra=_set_key2),
    Field(json_schema_extra={'key3': 'value3'}),
]


class Foo(BaseModel):
    field: CustomField


print("-1. BaseModel.model_json_schema")
pprint(Foo.model_json_schema())
print("-2. TypeAdapter().json_schema")
pprint(TypeAdapter(CustomField).json_schema())

Result

-1. BaseModel.model_json_schema
fields --  {'root': FieldInfo(annotation=~RootModelRootType, required=True)}
{'properties': {'field': {'key3': 'value3',
                          'title': 'Field',
                          'type': 'string'}},
 'required': ['field'],
 'title': 'Foo',
 'type': 'object'}
-2. TypeAdapter().json_schema
{'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'type': 'string'}

Note that key2 is set by a callable json_schema_extra

  • The 1. result should be the same as 2. (have all keys key1, key2, key3 ) right ?

@sydney-runkle
Copy link
Member

@nix010,

Great question. Seems the problem is even worse than I imagined 😂. I'd direct you to this issue to understand some more context behind modifying json schema when merging field info: #7686.

Even though I like the approach of the TypeAdapter more, I do think that changing this would be a breaking change. Let me chat with @dmontagu on Monday to see what he thinks the best next step is.

@sydney-runkle
Copy link
Member

Alright, let's go with the TypeAdapter approach. This should also fix #7686 that I've mentioned above.

We'll want to document this as a breaking change, and illustrate to users how they can delete keys via callable json schemas!

@nix010 nix010 linked a pull request Jun 12, 2024 that will close this issue
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V2 Bug related to Pydantic V2 good first issue help wanted Pull Request welcome
Projects
None yet
3 participants