Pydantic 2 Regression - TypeError: unhashable type on json_schema_extra examples #11489
-
First Check
Commit to Help
Example Codefrom fastapi import FastAPI
from pydantic import BaseModel
from typing import List
class Toy(BaseModel):
name: str
class Cat(BaseModel):
toys: List[Toy]
model_config = {
"json_schema_extra": {
"examples": [
{
"toys": [Toy(name="a"), Toy(name="b")]
}
]
}
}
app = FastAPI()
@app.post("/cats")
def create_cat(cat: Cat):
print("cat was created")DescriptionI am aware of the discussion #10097. I did comment on it but since it is closed, I guess nobody gets notified. It seems the issue (#10321) has been fixed for Reproduce with Pydantic v2$ vim api.py # put Example Code in this file
$ python3 -m venv venv
$ source venv/bin/activate
(venv) $ pip install fastapi uvicorn
(venv) $ uvicorn api:app --reload
INFO: Will watch for changes in these directories: ['/tmp/sample']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [23192] using StatReload
INFO: Started server process [23194]
INFO: Waiting for application startup.
INFO: Application startup complete.Then go to: http://localhost:8000/docs INFO: 127.0.0.1:60640 - "GET /docs HTTP/1.1" 200 OK
INFO: 127.0.0.1:60640 - "GET /openapi.json HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/tmp/sample/venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 407, in run_asgi
result = await app( # type: ignore[func-returns-value]
File "/tmp/sample/venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 69, in __call__
return await self.app(scope, receive, send)
File "/tmp/sample/venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/applications.py", line 123, in __call__
await self.middleware_stack(scope, receive, send)
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 65, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/routing.py", line 756, in __call__
await self.middleware_stack(scope, receive, send)
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/routing.py", line 776, in app
await route.handle(scope, receive, send)
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/routing.py", line 297, in handle
await self.app(scope, receive, send)
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/routing.py", line 77, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/tmp/sample/venv/lib/python3.10/site-packages/starlette/routing.py", line 72, in app
response = await func(request)
File "/tmp/sample/venv/lib/python3.10/site-packages/fastapi/applications.py", line 1009, in openapi
return JSONResponse(self.openapi())
File "/tmp/sample/venv/lib/python3.10/site-packages/fastapi/applications.py", line 981, in openapi
self.openapi_schema = get_openapi(
File "/tmp/sample/venv/lib/python3.10/site-packages/fastapi/openapi/utils.py", line 475, in get_openapi
field_mapping, definitions = get_definitions(
File "/tmp/sample/venv/lib/python3.10/site-packages/fastapi/_compat.py", line 229, in get_definitions
field_mapping, definitions = schema_generator.generate_definitions(
File "/tmp/sample/venv/lib/python3.10/site-packages/pydantic/json_schema.py", line 378, in generate_definitions
definitions_remapping = self._build_definitions_remapping()
File "/tmp/sample/venv/lib/python3.10/site-packages/pydantic/json_schema.py", line 2188, in _build_definitions_remapping
return _DefinitionsRemapping.from_prioritized_choices(
File "/tmp/sample/venv/lib/python3.10/site-packages/pydantic/json_schema.py", line 181, in from_prioritized_choices
schemas_for_alternatives[defs_ref] = _deduplicate_schemas(schemas_for_alternatives[defs_ref])
File "/tmp/sample/venv/lib/python3.10/site-packages/pydantic/json_schema.py", line 2299, in _deduplicate_schemas
return list({_make_json_hashable(schema): schema for schema in schemas}.values())
File "/tmp/sample/venv/lib/python3.10/site-packages/pydantic/json_schema.py", line 2299, in <dictcomp>
return list({_make_json_hashable(schema): schema for schema in schemas}.values())
TypeError: unhashable type: 'Toy'Same working example with Pydantic v1# api.py
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
class Toy(BaseModel):
name: str
class Cat(BaseModel):
toys: List[Toy]
class Config:
schema_extra = {
"examples": [
{
"toys": [Toy(name="a"), Toy(name="b")]
}
]
}
app = FastAPI()
@app.post("/cats")
def create_cat(cat: Cat):
print("cat was created")Downgrade to Pydantic v1: (venv) $ pip uninstall pydantic
(venv) $ pip install pydantic==v1.10.15
(venv) $ python -c "import pydantic; print(pydantic.version.VERSION)"
1.10.15(venv) $ uvicorn api:app --reload
INFO: Will watch for changes in these directories: ['/tmp/sample']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [23707] using StatReload
INFO: Started server process [23709]
INFO: Waiting for application startup.
INFO: Application startup complete.Then go to: http://localhost:8000/docs INFO: 127.0.0.1:48500 - "GET /docs HTTP/1.1" 200 OK
INFO: 127.0.0.1:48500 - "GET /openapi.json HTTP/1.1" 200 OKWorkaround with Pydantic v2I have to use class Cat(BaseModel):
toys: List[Toy]
model_config = {
"json_schema_extra": {
"examples": [
{
"toys": [Toy(name="a").model_dump(), Toy(name="b").model_dump()] # here
}
]
}
}Operating SystemLinux Operating System DetailsNo response FastAPI Version0.110.2 Pydantic Version2.7.1 Python Version3.10.12 Additional ContextEdit: after more research, it appears to be an issue of Pydantic as per pydantic/pydantic#7625. @dmontagu did mention the fix doesn't affect
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
|
Any thoughts on this regression? |
Beta Was this translation helpful? Give feedback.
-
|
As you already mentioned this comes from Pydantic. Looking at their discussions I see that they are not going to solve this. |
Beta Was this translation helpful? Give feedback.
As you already mentioned this comes from Pydantic. Looking at their discussions I see that they are not going to solve this.
So, the only solution is to use
model_dump.