pydantic / pydantic-settings

Settings management using pydantic
https://docs.pydantic.dev/latest/usage/pydantic_settings/
MIT License
502 stars 47 forks source link

Typing for multiple PathTypes (and support Traversable) for SettingsConfigDict json_file, toml_file, yaml_file, etc #299

Open DylanLukes opened 1 month ago

DylanLukes commented 1 month ago

There are issues with the typing for the file options for SettingsConfigDict:

  1. They are typed as PathType | None despite documentation indicating they accept lists (which indeed they do).

https://github.com/pydantic/pydantic-settings/blob/bcbfd17affa5a90663c9afc5a7dc67b281a728dc/pydantic_settings/main.py#L43-L46

The documentation indicates this at: https://docs.pydantic.dev/latest/concepts/pydantic_settings/#other-settings-source

  1. PathType could, but does not not include importlib.resources.abc.Traversable:

https://github.com/pydantic/pydantic-settings/blob/bcbfd17affa5a90663c9afc5a7dc67b281a728dc/pydantic_settings/sources.py#L87

I should note that if one provides a Traversable, it does currently work, indicating the subset of Path functionality used by pydantic-settings is within the Traversable subset of Path.

In the example below, I pass a Traversable, and I have verified in a test that it does indeed load the packaged resource file just fine.


Motivation

A somewhat useful pattern is to package a default configuration file inside a Python package. For example:

class FoobarSettings(BaseSettings):
    data_dir: Path

    model_config = SettingsConfigDict(
        env_prefix="foobar_",
        toml_file=[
            resources.files("foobar").joinpath("foobar.defaults.toml"),
            "foobar.toml"
        ],
    )

    @classmethod
    def settings_customise_sources(
            cls,
            settings_cls: type[BaseSettings],
            init_settings: PydanticBaseSettingsSource,
            env_settings: PydanticBaseSettingsSource,
            dotenv_settings: PydanticBaseSettingsSource,
            file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        default_sources = super().settings_customise_sources(
            settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings
        )
        return *default_sources, TomlConfigSettingsSource(settings_cls)

And then have in src/foobar/foobar.defaults.toml:

data_dir = ".foobar/data"

This currently works. However, it will not type-check.

DylanLukes commented 1 month ago

Currently I bypass this by simply using typing.cast to cast Traversable into Path. This is a bit brittle as there's no guarantee pydantic_settings won't eventually start using something Traversable doesn't support, but at least today it does indeed work.

hramezani commented 1 month ago

Thanks @DylanLukes for bringing this up.

Would you be interested in working on fixing and creating a PR?