Skip to content

feat: Add Support for complex list defined from env vars #376

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

Open
inean opened this issue Sep 2, 2024 · 6 comments
Open

feat: Add Support for complex list defined from env vars #376

inean opened this issue Sep 2, 2024 · 6 comments
Assignees
Labels
enhancement New feature or request

Comments

@inean
Copy link

inean commented Sep 2, 2024

Currently , only list of complex types can only be defined completely from env vars as Json values. The idea is to also allow
to define using nested delimiters, where index is explicitly defined in env vars:

class SubType(BaseSettings):
    v1 :str 

class Config(Settings):
   top: list[SubType]

then, something like this should work...

CONFIG__0__TOP="foo"
@inean
Copy link
Author

inean commented Sep 2, 2024

PR #377

@hramezani hramezani added enhancement New feature or request and removed unconfirmed labels Oct 1, 2024
@MarcBresson
Copy link

linked issue

@plpilew
Copy link

plpilew commented Apr 17, 2025

As a workaround for this problem I used dict with integer key instead of a list:

class SubType(BaseSettings):
    v1 :str 

class Config(Settings):
   top: dict[int, SubType]

Then I can use such environment variable to override the value:

CONFIG__TOP__0__V1="foo"

Still not perfect, but at least our Kubernetes developers can use same patterns for naming env variables regardless whether the pod contains python, dotnet or other stack (using index to override array items works in dotnet).

@MarcBresson
Copy link

@plpilew please see my comment that has the benefit of being a list.

@plpilew
Copy link

plpilew commented Apr 17, 2025

@plpilew please see my comment that has the benefit of being a list.

I tried this approach but could not use it for my scenario which is:

  1. My model (SubType in this case) has multiple fields.
  2. Configuration is read from .env file which sets all the fields.
  3. Then I want one of the fields to be updated to the value read from env variable leaving other fields intact.

In this case the built-in pydantic validation raised an error saying that other fields are required as well (I had only one env variable defined).

@MarcBresson
Copy link

What about :

from typing import TypeVar

from pydantic import BaseModel


T = TypeVar("T")


def env_var_dict_to_list(model: BaseModel, data: dict[str, T] | list[T]) -> list[T]:
    """If the data is a dictionary, convert its values to a list.

    It allows for the definition of lists through env var in pydantic
    settings.

    For instance, given the following BaseModel:
    ```python
    >>> from pydantic import BaseModel

    >>> class Model(BaseModel):
    >>>     slug: str
    >>>     weight: float
    ```

    You could define the following environment variables:

    ```
    models__0__slug=my-super-model
    models__0__weight=0.1
    models__1__slug=a-second-impressive-model
    models__1__weight=0.4
    ```

    This function will convert the dictionary given by pydantic-settings to a
    list of Model instances:
    ```python
    >>> from typing import Annotated
    >>> from pydantic import BeforeValidator
    >>> from pydantic_settings import BaseSettings
    >>> from your_module import env_var_dict_to_list
    >>> from your_model import Model

    >>> class Settings(BaseSettings):
    >>>     models: Annotated[
    >>>         list[Model],
    >>>         BeforeValidator(lambda v: env_var_dict_to_list(Model, v)),
    >>>     ]

    >>> Settings()
    Settings(
        models=[
            Model(slug='my-super-model', weight=0.1),
            Model(slug='a-second-impressive-model', weight=0.4)
        ]
    )
    ```
    ```
    """
    if isinstance(data, dict):
        return [model(**item) for item in data.values()]
    return data

and then, in your model:

from pydantic import BaseModel, BeforeValidator


class SubType(BaseModel):
    v1: str


class Config(BaseSettings):
    model_config = ConfigDict(env_nested_delimiter="__")

    sub_types: Annotated[list[Model, ...], BeforeValidator(lambda v: env_var_dict_to_list(SubType, v))]

I must admit that your solution is much (much) simpler than this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants