Skip to content

Adopt pydantic-settings with Pydantic V2 #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 24, 2023

Conversation

hramezani
Copy link
Member

@hramezani hramezani commented Apr 19, 2023

Here is the list of changes:

  • class Config is not valid anymore and has to be replaced with model_config. Example:
    class Settings(BaseSettings):
        apple: str

        class Config:
            env_prefix = 'foobar_'

has to be replaced with:

    class Settings(BaseSettings):
        apple: str

        model_config = ConfigDict(env_prefix='foobar_')
  • Model.dict() is not available anymore and Model.model_dump() has to be used.
  • Defining env for a field under class Config is not valid and validation_alias has to be used. Example:
    class Settings(BaseSettings):
        apple: str = ...

        class Config:
            fields = {'apple': {'env': 'BOOM'}}

has to be replaced with:

    class Settings(BaseSettings):
        apple: str = Field(None, validation_alias='BOOM')
  • Initializing settings class with field name whenever validation_alias is defined does not work anymore. Example:
class SettingsModel(BaseSettings):
    foobar: str = Field(validation_alias='foobar_env')

SettingsModel(foobar='test')  # Raises validation error
  • Defining env in Field is not valid anymore and validation_alias has to be used. Example:
    class Settings(BaseSettings):
        foobar: str = Field(..., env='foobar_env_name')

has to be replaced with:

    class Settings(BaseSettings):
        foobar: str = Field(validation_alias='foobar_env_name')
  • customise_sources is not part of class Config anymore and moved to BaseSettings class.

@codecov-commenter
Copy link

codecov-commenter commented Apr 19, 2023

Codecov Report

Patch coverage: 95.65% and project coverage change: -0.81 ⚠️

Comparison is base (0ecac22) 96.31% compared to head (857e057) 95.51%.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #17      +/-   ##
==========================================
- Coverage   96.31%   95.51%   -0.81%     
==========================================
  Files           4        5       +1     
  Lines         244      245       +1     
  Branches       60       59       -1     
==========================================
- Hits          235      234       -1     
- Misses          9       10       +1     
- Partials        0        1       +1     
Impacted Files Coverage Δ
pydantic_settings/utils.py 75.00% <75.00%> (ø)
pydantic_settings/sources.py 95.63% <97.33%> (+0.10%) ⬆️
pydantic_settings/main.py 100.00% <100.00%> (+1.75%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

Copy link
Contributor

@dmontagu dmontagu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

I did try to break some stuff related to the implementation of field_is_complex and the only thing I could get to not work was generic dataclasses, which had an easy fix.

I didn't look at the changes to the tests super closely, but from what I could tell they all seemed reasonable. (If you want specific feedback on any of that let me know.)

from pydantic.config import BaseConfig, Extra
from pydantic.fields import ModelField
from pydantic import ConfigDict, DirectoryPath, FilePath
from pydantic._internal._utils import deep_update
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we okay using pydantic internals here? I think it's fine since it's still our package, just want to make sure.

(If we do this, and I guess even if we don't, it might be a good idea to run the tests for pydantic_settings in the CI for pydantic.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we okay using pydantic internals here? I think it's fine since it's still our package, just want to make sure.

Yes, as we maintain Pydantic I think it's fine

(If we do this, and I guess even if we don't, it might be a good idea to run the tests for pydantic_settings in the CI for pydantic.)

Agree

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, like we do for fastapi.

@@ -18,7 +15,7 @@
SecretsSettingsSource,
)

env_file_sentinel = str(object())
env_file_sentinel: DotenvType = FilePath('')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What motivated this change? Just curious

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make mypy happy


# populated by the metaclass using the Config class defined above, annotated here to help IDEs only
__config__: ClassVar[Type[Config]]
model_config = ConfigDict( # type: ignore[typeddict-item]
Copy link
Contributor

@dmontagu dmontagu Apr 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make a subclass of ConfigDict that adds more items? I guess that probably doesn't work well.

Probably we'll want to make this work better with the pydantic type hints at some point (annoying that TypedDicts are generally treated as invariant..). But I guess that's outside the scope of a pydantic-settings issue and this approach seems good to me.

Maybe just ignore this comment lol.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes we should have a custom subclass, in theory it shouldn't break type hinting since we're not changing types, just adding to them, and pydantic will never know the dict has other keys.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SettingsConfigDict added

}


def path_type(p: Path) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor thing, I'd suggest renaming this to path_type_label though, to indicate that the returned value is purely meant for display purposes, not for equality checks or anything like that.

@hramezani hramezani force-pushed the pydantic_v2_integration branch from 5bdbff9 to 1eed3ad Compare April 20, 2023 08:58
@hramezani hramezani force-pushed the pydantic_v2_integration branch from 1eed3ad to 437093f Compare April 20, 2023 09:01
Copy link
Member

@samuelcolvin samuelcolvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise LGTM.

Sorry for the slow response.

from pydantic.config import BaseConfig, Extra
from pydantic.fields import ModelField
from pydantic import ConfigDict, DirectoryPath, FilePath
from pydantic._internal._utils import deep_update
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, like we do for fastapi.


# populated by the metaclass using the Config class defined above, annotated here to help IDEs only
__config__: ClassVar[Type[Config]]
model_config = ConfigDict( # type: ignore[typeddict-item]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes we should have a custom subclass, in theory it shouldn't break type hinting since we're not changing types, just adding to them, and pydantic will never know the dict has other keys.

env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> Tuple[PydanticBaseSettingsSource, ...]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not now, but we need to:

  1. add docstrings to this and other methods
  2. rename these methods to all use a consistent prefix, maybe settings_
  3. Implement Protected namespaces pydantic#4915 and use that to make settings_ a protected namespace.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created an issue for this #19


from pydantic.config import BaseConfig, Extra
from pydantic.fields import ModelField
from pydantic import ConfigDict, DirectoryPath, FilePath
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about useing DirectoryPath and FilePath here - I see it's useful semantically to communicate meaning, but it could also be confusing, since they're not enforced at runtime.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think on balance, it's better not to use these types, probably better to use Path.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DirectoryPath and FilePath replaced by Path

file_secret_settings: PydanticBaseSettingsSource,
) -> Tuple[PydanticBaseSettingsSource, ...]:
return init_settings, env_settings, dotenv_settings, file_secret_settings

def _build_values(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename, I guess to _settings_build_values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed

@hramezani
Copy link
Member Author

Thanks @samuelcolvin for the review. I've addressed all of them.

Copy link
Member

@samuelcolvin samuelcolvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otherwise LGTM.

@samuelcolvin samuelcolvin enabled auto-merge (squash) April 24, 2023 21:21
@samuelcolvin samuelcolvin merged commit 1144885 into pydantic:main Apr 24, 2023
@hramezani hramezani deleted the pydantic_v2_integration branch April 24, 2023 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants