Skip to content

[Feature]: Support for Pydantic v2 #1844

Description

@movchan74

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 (aana version 0.2.2.2) requires pydantic >=2.0.
  • dstack (version 0.18.17) depends on pydantic >=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

Activity

  1. r4victor commented on Oct 16, 2024

    @r4victor
    Collaborator

    @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?

  2. movchan74 commented on Oct 16, 2024

    @movchan74
    Author

    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!

  3. zmievsa commented on Oct 25, 2024

    @zmievsa
    Contributor

    Will add support to pydantic duality for pydantic 2 this week.

  4. zmievsa commented on Oct 27, 2024

    @zmievsa
    Contributor

    Added it in pydantic-duality==2.0.0

  5. github-actions commented on Nov 27, 2024

    @github-actions

    This issue is stale because it has been open for 30 days with no activity.

  6. peterschmidt85 commented on Nov 27, 2024

    @peterschmidt85
    Contributor

    Once @r4victor is back, we can discuss how/when we move this forward

  7. github-actions commented on Dec 28, 2024

    @github-actions

    This issue is stale because it has been open for 30 days with no activity.

  8. tomzx commented on Apr 29, 2025

    @tomzx

    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!

  9. peterschmidt85 commented on Apr 30, 2025

    @peterschmidt85
    Contributor

    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.

  10. r4victor commented on May 28, 2025

    @r4victor
    Collaborator

    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:

    1. Set Optional[type] = None
    2. Replace __root__ with RootModel (not sure if it works with pydantic_duality)
    3. Replace deprecated functions/methods:
      1. parse_raw => model_validate_json
      2. parse_obj => model_validate
      3. .json() => model_dump_json
      4. @validator => @field_validator, @classmethod
      5. @root_validator => @model_validator, @classmethod
      6. __get_validators__ => __get_pydantic_core_schema__ or see Adding validation and serialization.
    4. (GenericModel) => (BaseModel, Generic[T])
    5. Drop or rename Field properties (const, regex, ...)
    6. Config => model_config
      1. schema_extra => ConfigDict(json_schema_extra=my_schema_extra)
    7. Consider using @computed_field
    8. 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.
  11. r4victor commented on May 28, 2025

    @r4victor
    Collaborator

    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).

  12. hpenedones commented on May 29, 2026

    @hpenedones

    Hi all, any update on this? Thanks and keep up the great work!

  13. peterschmidt85 commented on May 29, 2026

    @peterschmidt85
    Contributor

    @hpenedones

    Hi all, any update on this? Thanks and keep up the great work!

    Actually, we’re planning to start working on it soon!

  14. r4victor commented on Jul 28, 2026

    @r4victor
    Collaborator

    The 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") on CoreModel that 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)
    
    
  15. self-assigned this
    on Jul 29, 2026
  16. r4victor commented on Jul 29, 2026

    @r4victor
    Collaborator

    It's now possible to specify extra dynamically 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.

    https://pydantic.dev/articles/pydantic-v2-12-release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions