Repository navigation
[Feature]: Support for Pydantic v2 #1844
Description
Activity
@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!
Reacted by Victor Skvortsov, Andrey Cheptsov and Stanislav ZmievWill add support to pydantic duality for pydantic 2 this week.
Reacted by Andrey CheptsovAdded 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.v1imports. 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 - Migrate from orjson load/dump (json_loads/json_dumps are removed) to pyndatic-native load/dump). Still custom Response.render() to avoid FastAPI's jsonable_encoder and re-validation overhead.
- Set
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).
Hi all, any update on this? Thanks and keep up the great work!
Hi all, any update on this? Thanks and keep up the great work!
Actually, we’re planning to start working on it soon!
Reacted by Hugo PenedonesThe main dstack-specific question about pydantic V2 migration is how to preserve two modes of parsing the same models (extra="forbid"/extra="ignore") that's currently provided by pydantic-duality. Keeping pydantic-duality is undesirable long-term as it's not maintained and short-term as it complicates the migration (see RootModel support).
We may find pydantic-native way of support this. One proposal is to define
model_validator(mode="after")onCoreModelthat enforces extra="forbid"/extra="ignore" semantics depending on the validation context.Here's the idea:
from typing import Any, TypeVar, overload from pydantic import ( BaseModel, ConfigDict, TypeAdapter, ValidationError, ValidationInfo, model_validator, ) from pydantic_core import InitErrorDetails, PydanticCustomError MODEL_EXTRA_IGNORE_CONTEXT = {"ignore": True} T = TypeVar("T") class CoreModel(BaseModel): """A model that can be parsed with extra="forbid"/extra="ignore" semantics depending on context.""" model_config = ConfigDict(extra="allow") @model_validator(mode="after") def _handle_extra(self, info: ValidationInfo): extra = self.__pydantic_extra__ if not extra: return self if not (info.context or {}).get("ignore"): raise ValidationError.from_exception_data( type(self).__name__, [ InitErrorDetails( type=PydanticCustomError( "extra_forbidden", "Extra inputs are not permitted" ), loc=(key,), input=value, ) for key, value in extra.items() ], ) # Drop them: unknown fields must not round-trip into the DB or responses. object.__setattr__(self, "__pydantic_extra__", {}) return self @overload def validate_extra_ignore(tp: type[T], obj: Any) -> T: ... # Second overload for special forms such as Union, Optional, and Annotated, which cannot currently be typed. @overload def validate_extra_ignore(tp: Any, obj: Any) -> Any: ... def validate_extra_ignore(tp: Any, obj: Any) -> Any: """Parse arbitrary annotations with extra="ignore", e.g. list[Fleet].""" return TypeAdapter(tp).validate_python(obj, context=MODEL_EXTRA_IGNORE_CONTEXT)
It's now possible to specify
extradynamically since Pydantic 2.12:The validation methods (e.g. model_validate()) have an optional extra argument that will override the extra configuration value of the model for that validation call.
Problem
I'm encountering a dependency conflict due to incompatible versions of
pydanticbetween dstack and another package my project relies on. Specifically: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
dstackalongside 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