-
-
Notifications
You must be signed in to change notification settings - Fork 61
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
Feature Request: Settings source - allow non-JSON complex fields #126
Comments
Thanks @dbendall for this issue 🙏 You only need to override the import json
import os
from typing import Any
from pydantic import AnyHttpUrl
from pydantic.fields import FieldInfo
from pydantic_settings import (
BaseSettings,
EnvSettingsSource,
PydanticBaseSettingsSource,
)
class ModifiedEnvSettingsSource(EnvSettingsSource):
def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool) -> Any:
try:
return json.loads(value)
except ValueError:
return value.split(',')
class Settings(BaseSettings):
list_of_urls: list[AnyHttpUrl]
@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, ...]:
return (init_settings, ModifiedEnvSettingsSource(settings_cls))
os.environ["LIST_OF_URLS"] = '["https://pydantic.dev","https://github.com"]'
settings_from_json = Settings() # OK
os.environ["LIST_OF_URLS"] = "https://pydantic.dev,https://github.com"
settings_from_csl = Settings() # SettingsError from json.decode.JSONDecodeError |
Thanks for checking this out, although this works for the simple example I provided, it starts to get more complicated when you add non-complex fields or if you use nested settings models. For example: class NestedSettings(BaseSettings):
another_list_of_urls: list[AnyHttpUrl]
class Settings(BaseSettings):
single_str: str
single_int: int
list_of_urls: list[AnyHttpUrl]
nested_model: NestedSettings
model_config = SettingsConfigDict(env_nested_delimiter="__")
os.environ["SINGLE_STR"] = "example"
os.environ["SINGLE_INT"] = "99"
os.environ["LIST_OF_URLS"] = '["https://pydantic.dev","https://github.com"]'
os.environ["NESTED_MODEL__ANOTHER_LIST_OF_URLS"] = '["https://pydantic.dev","https://github.com"]'
settings_from_json = Settings() # OK
os.environ["LIST_OF_URLS"] = "https://pydantic.dev,https://github.com"
os.environ["NESTED_MODEL__ANOTHER_LIST_OF_URLS"] = "https://pydantic.dev,https://github.com"
settings_from_csl = Settings() # SettingsError from json.decode.JSONDecodeError For our use-case it's only the complex model decoding that we would like to customise, we would like to retain all the existing environment variable parsing, extraction, and preparation where possible so that we are only extending the specific bit of functionality we need to change. Please let me know if I can provide any further examples and as I said, I'm happy to put together a PR to show how I think it can be done without making any changes to the existing interface. |
Thanks @dbendall for the explanation. |
Closed in 7ebb3bf |
Use case
When defining a settings model with complex fields, the default environment variable settings source class only decodes JSON encoded strings for that complex field. It would be great if there were an easy way to decode non-JSON encoded strings too (for example a comma separated list)
Current solution
One can subclass the
EnvSettingsSource
class to implement this functionality and apply it as a customisable settings source but this requires the developer to repeat theprepare_field_value
andexplode_env_vars
methods to replace only thejson.loads()
call resulting in a large amount of repeated code.Example code...
Proposed solution
Break out the
json.loads()
from these methods into a dedicateddecode_complex_value
method for easier sub-classing when implementing custom settings source.Example code...
Thanks for reading, I'd be happy to put together a PR for this if acceptable.
Selected Assignee: @hramezani
The text was updated successfully, but these errors were encountered: