[Install issue]: type object 'CreateCollection' has no attribute 'model_validate' during Create Index
What happened?
After installed ChromaDB and run chroma run, it throws an error when I try to create a new index using the NodeJS client.
The reason I find out is because the version of pydantic is too old on my machine.
Mine was 1.10.12, and upgrading the library to the latest version (2.7.1) solves this issue.
pip install pydantic --upgrade
It seems the version in the requirement.txt is pydantic>=1.9 so it doesn't upgrade the library automatically during install, should the version number gets bumped?
Reference:
https://github.com/chroma-core/chroma/blob/b34f90ce41a82d54ca4d68e43009309cd8b43f89/requirements.txt#L18
The code that throws the error (model_validate does not exist on 1.10):
https://github.com/chroma-core/chroma/blob/b34f90ce41a82d54ca4d68e43009309cd8b43f89/chromadb/server/fastapi/init.py#L561
Versions
Chroma v0.5.0, Python 3.9.13, MacOS 14.4.1
Relevant log output
ERROR: [06-05-2024 22:51:40] type object 'CreateCollection' has no attribute 'model_validate'
Traceback (most recent call last):
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/anyio/streams/memory.py", line 81, in receive
return self.receive_nowait()
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/anyio/streams/memory.py", line 76, in receive_nowait
raise WouldBlock
anyio.WouldBlock
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/middleware/base.py", line 159, in call_next
message = await recv_stream.receive()
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/anyio/streams/memory.py", line 101, in receive
raise EndOfStream
anyio.EndOfStream
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/chromadb/server/fastapi/__init__.py", line 78, in catch_exceptions_middleware
return await call_next(request)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/middleware/base.py", line 165, in call_next
raise app_exc
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/middleware/base.py", line 151, in coro
await self.app(scope, receive_or_disconnect, send_no_error)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/middleware/base.py", line 191, in __call__
response = await self.dispatch_func(request, call_next)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/chromadb/server/fastapi/__init__.py", line 92, in check_http_version_middleware
return await call_next(request)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/middleware/base.py", line 165, in call_next
raise app_exc
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/middleware/base.py", line 151, in coro
await self.app(scope, receive_or_disconnect, send_no_error)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/middleware/exceptions.py", line 65, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/routing.py", line 756, in __call__
await self.middleware_stack(scope, receive, send)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/routing.py", line 776, in app
await route.handle(scope, receive, send)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/routing.py", line 297, in handle
await self.app(scope, receive, send)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/routing.py", line 77, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
raise exc
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
await app(scope, receive, sender)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/starlette/routing.py", line 72, in app
response = await func(request)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/fastapi/routing.py", line 278, in app
raw_response = await run_endpoint_function(
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/fastapi/routing.py", line 191, in run_endpoint_function
return await dependant.call(**values)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/chromadb/telemetry/opentelemetry/__init__.py", line 130, in wrapper
return await f(*args, **kwargs)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/chromadb/server/fastapi/__init__.py", line 630, in create_collection
await to_thread.run_sync(
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/anyio/to_thread.py", line 28, in run_sync
return await get_asynclib().run_sync_in_worker_thread(func, *args, cancellable=cancellable,
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/anyio/_backends/_asyncio.py", line 818, in run_sync_in_worker_thread
return await future
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/anyio/_backends/_asyncio.py", line 754, in run
result = context.run(func, *args)
File "/Users/user/opt/anaconda3/lib/python3.9/site-packages/chromadb/server/fastapi/__init__.py", line 606, in process_create_collection
create = CreateCollection.model_validate(orjson.loads(raw_body))
AttributeError: type object 'CreateCollection' has no attribute 'model_validate'
INFO: [06-05-2024 22:51:40] ::1:53570 - "POST /api/v1/collections?tenant=default_tenant&database=default_database HTTP/1.1" 500
This is a good point - @tazarov should we bump the version?
Pydantic 1.x also has similar function to so maybe we can implement an adapter that is version-aware function to keep backward compatibility.
We've added the orjson serialization in 0.5.0 and so far @zhou9110 is the first to notice which leads me to believe that not so many people are running pydantic <2.x. Maybe bumping the pydantic range >=2.0 and ditching 1.x as part of the 0.5.x versions can be an OK approach.
However if we go 2.x route we may want to also add pydantic-settings package and refactor the config (can be done in a separate PR).
looking at pydantic-settings package I can see that it basically requires pydantic > 2.7
https://github.com/pydantic/pydantic-settings/blob/6d25cee4bb7a6db592ca0da123c53f3d775cd1e1/pyproject.toml#L43
Using pedantic-settings as a package dependency, we're effectively forcing a pydantic>2.0 upgrade, which in @zhou9110's and other's cases might be a breaking change.
Another interesting aspect is our fastapi dep fastapi>=0.95.2, which translates:
https://github.com/tiangolo/fastapi/blob/c81e136d75f5ac4252df740b35551cf2afb4c7f1/pyproject.toml#L45
The above will cause some dependency resolution issues with older fastapi versions.
In the end, it will be a trade-off and a bit of a jerk move to force users to upgrade their deps which may come from other dependencies.
Bottom line:
pydantic>2.0 with pydantic-settings
pro: makes the code a bit cleaner as we'll remove all the pydantic 1.x compatibility logicpro: allows us to bumpfastapicon: we add an extra dependencypydantic-settingscon: forces users to upgrade topydantic>2.0con: there are possible paths that lead to unreconcilable dependency conflicts and breaking changes
support for both versions
pro: we don't have to addpydantic-settingsas direct dependency, but in case it is there we can use itpro: no breaking changes for userscon: add more boilerplate code to support both versions that need to be maintained and testedcon: our tests always run on2.xso it properly testing with older version requires additional CI with little gain, but it will help us avoid the above error
@HammadB, any strong opinion on which option to go for?
Given that Pydantic v2 was released in April of last year, I would vote for just bumping the version.
I think we should preserve backwards compat here. The history of this is we actually did support this https://github.com/chroma-core/chroma/pull/1174 but then regressed when we changed this.
I'm for keeping backward with 1.x. Pydantic seems to be committed to the 1.x release train (last release 1.10 was about a month ago). Also fastAPI seems to support 1.7+. Last but not least our surface area is pretty small so it is not a huge overhead to keep supporting 1.x.