[Feature]: Support for Pydantic v2
Problem
I'm encountering a dependency conflict due to incompatible versions of pydantic between dstack and another package my project relies on. Specifically:
- My project (
aanaversion 0.2.2.2) requirespydantic >=2.0. dstack(version 0.18.17) depends onpydantic >=1.10.10, <2.0.0.
As a result, I'm unable to resolve dependencies when adding dstack alongside other tools in my environment that are already using Pydantic v2.
The broader AI and software community are shifting towards Pydantic v2 for its improved performance and features, and many AI projects have either fully migrated to v2 or at least added support for it. This version conflict is limiting adoption and making it challenging to integrate dstack into modern AI/ML projects that have already upgraded to Pydantic v2.
Solution
No response
Workaround
No response
Would you like to help us implement this feature by sending a PR?
No
@movchan74, we have plans to migrate dstack to Pydantic v2. The migration is currently non-trivial since some of the dstack dependencies do not yet support Pydantic v2 (https://github.com/zmievsa/pydantic-duality for sure). We'll explore how it can be done.
You're using the dstack Python API, so you cannot install dstack into a separate venv? Is it the case?
It's great to hear that migrating to Pydantic v2 is on the roadmap! I understand that it’s non-trivial.
For now, this isn't a major blocker, as I can indeed set up a separate venv for dstack as you suggested. I'm still exploring how best to integrate dstack into our product, so I’ll work with this workaround in the meantime.
Thanks again for the quick response and for considering the upgrade!
Will add support to pydantic duality for pydantic 2 this week.
Added it in pydantic-duality==2.0.0
This issue is stale because it has been open for 30 days with no activity.
Once @r4victor is back, we can discuss how/when we move this forward
This issue is stale because it has been open for 30 days with no activity.
Any idea if this will be addressed within the next few weeks/months? For now we're likely to rely on the CLI instead of using the python API directly, which is not ideal. Thanks!
Any idea if this will be addressed within the next few weeks/months? For now we're likely to rely on the CLI instead of using the python API directly, which is not ideal. Thanks!
@tomzx We are 100% going to implement this. The plan is to do it by the end of Q2.
Pydantic v1 => Pydantic v2 migration guide: https://docs.pydantic.dev/latest/migration/
All Pydantic V1 features are still available in Pydantic v2 via pydantic.v1 imports. So we could migrate to Pydantic V2 initially just by changing imports. Unfortunately, this mode is not compatible with FastAPI, so we need to do full Pydantic V2 migration.
Main changes relevant for dstack codebase:
- Set
Optional[type] = None - Replace
__root__with RootModel (not sure if it works with pydantic_duality) - Replace deprecated functions/methods:
- parse_raw => model_validate_json
- parse_obj => model_validate
- .json() => model_dump_json
@validator=>@field_validator,@classmethod@root_validator=>@model_validator,@classmethod__get_validators__=>__get_pydantic_core_schema__or see Adding validation and serialization.
- (GenericModel) => (BaseModel, Generic[T])
- Drop or rename Field properties (const, regex, ...)
- Config => model_config
- schema_extra =>
ConfigDict(json_schema_extra=my_schema_extra)
- schema_extra =>
- Consider using
@computed_field
Replacing __root__ with RootModel is not obvious since RootModel is not compatible with pydantic-duality. In dstack, __root__ models are used to parse unions discriminated by type, e.g.:
class AWSCreds(CoreModel):
__root__: AnyAWSCreds = Field(..., discriminator="type")
Option 1. Use RootModel without pydantic-duality:
from pydantic import RootModel
class AWSCreds(RootModel):
root: AnyAWSCreds = Field(..., discriminator="type")
Works out-of-the-box but we would have to duplicate models if we need different extra settings (forbid/ignore) for parsing.
Option 2. Implement custom RootModel compatible with pydantic-duality. Something along the lines:
class CoreRootModel(CoreModel):
@model_validator(mode="before")
@classmethod
def populate_root(cls, values):
return {'root': values}
@model_serializer(mode="wrap")
def _serialize(self, handler, info):
data = handler(self)
if info.mode == "json":
return data["root"]
else:
return data
@classmethod
def model_modify_json_schema(cls, json_schema):
return json_schema["properties"]["root"]
class AWSCreds(CoreRootModel):
root: AnyAWSCreds = Field(..., discriminator="type")
Not sure what shortcomings of this implementation may be, so the usage of these models should be limited to top-level parsing and these models should not be included in other models (and there should be no need for that since unions are included directly).