Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

Hi, is it possible to change env_prefix at runtime? I've tried some hacks which didn't work (playing with inheritance, overriding) but noting worked. Something like this would be nice:

import os
from typing import Dict, Tuple

from pydantic import BaseSettings, Extra
from pydantic.env_settings import SettingsSourceCallable


class SettingsConfigBase:
    env_file = os.environ.get("dotenv_config_path", ".env")
    env_prefix = ""  # This should be overriden by deriving classes
    case_sensitive = False
    extra = Extra.ignore

    @classmethod
    def customise_sources(
        cls,
        init_settings: SettingsSourceCallable,
        env_settings: SettingsSourceCallable,
        file_secret_settings: SettingsSourceCallable,
    ) -> Tuple[SettingsSourceCallable, ...]:
        # Prioritize environment settings over default config
        return env_settings, init_settings, file_secret_settings


class Settings(BaseSettings):
    foo: str = "foo_default"

    class Config(SettingsConfigBase):
        env_prefix = 'APREFIX_'


def main():
    # Something like this?
    settings = Settings(_env_prefix='BPREFIX_')
    print(settings.foo)


if __name__ == '__main__':
    main()
You must be logged in to vote

Replies: 6 comments · 5 replies

Comment options

I'll shamelessly self-bump this. Why I think being able to change env_prefix at runtime would be useful - this would allow to configure multiple instances of the same object differently.

You must be logged in to vote
0 replies
Comment options

I would like to put some effort into implementing this myself, if this has some potential to be included in a future versions of pydantic.
Looking through code, it does not look simple because Config model seems to be used when constructing the model by metaclass. Making env_prefix as a part EnvSettingsSource may be easier, if all edge cases are handled.

Any thoughts on this?

You must be logged in to vote
0 replies
Comment options

I'd love/need that as well. I tried everything but nothing I came up with worked?

`
Settings.Config.env_prefix = 'bla_' # no

class Config:
env_prefix = 'bla_'

Setting.Config = Config
`

You must be logged in to vote
0 replies
Comment options

@jesusch @Null665 @whell

If anyone is also interested in this, I've found out that we need to do two things in order for it to work:

  1. Override the env_prefix property of the class' __config__
  2. Ask the __config__ instance to prepare fields again

I've put together a hack that works by doing something like this (yes, I know it is fugly):

import pydantic

class MySettings(pydantic.BaseSettings):
    param_a: bool


runtime_env_prefix = "something_set_at_runtime"

# 1. override the original env_prefix
MySettings.__config__.env_prefix = runtime_env_prefix

# 2. prepare fields again with the new env_prefix in place
for field in MySettings.__fields__.values():
    MySettings.__config__.prepare_field(field)

# now we can create our settings and it will look for env variables with the runtime env prefix
settings = MySettings()
You must be logged in to vote
4 replies
@bgloss
Comment options

Tried to use it to access different sets of Postgres credentials from an .env-file, e.g., for admin and the db-owner -> No success

settings_admin = MySettings(_env_file=".env", _env_prefix="POSTGRES_ADMIN_")
settings_db_owner = MySettings(_env_file=".env", _env_prefix="POSTGRES_DB_OWNER_")
@dmontagu
Comment options

@ricardogsilva's solution should work if you use Config.env_prefix rather than the _env_prefix key. For what it's worth, I think we'll be more open to fixing these sorts of things once pydantic v2 is released.

@dmontagu
Comment options

To be clear, that would look something like this:

MySettings.__config__.env_prefix = "POSTGRES_ADMIN_"
for field in MySettings.__fields__.values():
    MySettings.__config__.prepare_field(field)
settings_admin = MySettings(_env_file=".env")

MySettings.__config__.env_prefix = "POSTGRES_DB_OWNER_"
for field in MySettings.__fields__.values():
    MySettings.__config__.prepare_field(field)
settings_db_owner = MySettings(_env_file=".env", )

I'll note that I have not tested this.

@bgloss
Comment options

Whoooo ... :-)
For the sake of simplicity of my code, I'll wait for v2 and use it in my next project.
This "configuration before init" is quite a thing!

Comment options

Running pydantic-settings 2.0.0 - I use inheritance to do this. For me it works better, as I don't need to worry about which .model_config changes I made in what order (changing this class variable is basically modifying global state).

import pydantic_settings

class FooLikeSettings(pydantic_settings.BaseSettings): 
   model_config = (
        pydantic_settings.SettingsConfigDict(
            env_prefix="foo_",
     )
     ... # model definition

class Bar(FooLikeSettings):
    pass

Bar.model_config["env_prefix"] = "bar_"    

Not sure if this is the right thing. Probably this should be discouraged in favor of creating nested models.

You must be logged in to vote
0 replies
Comment options

I also have a need for this behavior. In the application I'm working on, users will need to configure connections to external data sources, many of which may be various instances of the same type of connection. Think of rclone "remotes", rclone allows users to configure muliple named remotes pointing to various s3 accounts/instances, but all of them share the same underlying configuration (because they are all s3 connections).

Because these connections will necessarily handle secrets, I would like to use BaseSettings for parsing them from the environment/.env files/etc.

Having reviewed the above discussion and related threads, I've also concluded that subclassing is probably the most stable way to implement this feature, but because users will not be interacting with the code directly (they will be using either a CLI or a web app backed by Pydantic models), the subclassing (if that is indeed the best solution) will need to be dynamic.

Here's what I've come up with so far. Very interested to know what others think of this solution, perhaps particularly @dmontagu as IIUC you are the sole Pydantic core dev on this thread (apologies if I've misunderstood that).

I'm particularly concerned with making sure my solution is stable, as this will eventually be deployed to a large user base. Thank you so much in advance for taking a look, and any feedback!

# change-prefix.py

import sys
from typing import Type

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):

    key: str
    secret: str

    @classmethod
    def dynamically_subclass_with_prefix(cls: Type["Settings"], env_prefix: str) -> "Settings":
        """Dynamically subclass the Settings class, using the user-supplied `env_prefix`."""
        new_cls_name = env_prefix.title() + "Settings"
        config = {"env_prefix": f"{env_prefix}_", "case_sensitive": False}
        _cls = type(
            new_cls_name,
            (cls,),
            {"model_config": SettingsConfigDict(**config)},
        )
        return _cls()
    

if __name__ == "__main__":
    env_prefix = sys.argv[1]
    settings = Settings.dynamically_subclass_with_prefix(env_prefix)
    print(f"{settings = }")
    print(f"{settings.key = }")
    print(f"{settings.secret = }")
FIRST_KEY="abc" FIRST_SECRET="123" python3 change-prefix.py "FIRST"
settings = FirstSettings(key='abc', secret='123')
settings.key = 'abc'
settings.secret = '123'SECOND_KEY="def" SECOND_SECRET="456" python3 change-prefix.py "SECOND"   
settings = SecondSettings(key='def', secret='456')
settings.key = 'def'
settings.secret = '456'ANOTHER_KEY="ghi" ANOTHER_SECRET="789" python3 change-prefix.py "ANOTHER"
settings = AnotherSettings(key='ghi', secret='789')
settings.key = 'ghi'
settings.secret = '789'
You must be logged in to vote
1 reply
@mansenfranzen
Comment options

Exactly same idea as @cisaacstern, perhaps a bit more readable:

class Settings(BaseSettings):

    key: str
    secret: str

    @classmethod
    def from_env_vars(cls: Type[Settings], env_prefix: str, **kwargs) -> Settings:
        """Create Settings instance from environment variables. A single,
        global, static prefix is not meaningful because different applications may
        wish to use different prefixes. This method allows specifying a custom prefix.

        Settings can be provided via environment variables, passed directly as function 
        parameters or mixed and matched.

        You can also mix and match environment variables and function parameters.

        >>> settings = Settings.from_env_vars(
        >>>    env_prefix="MY_ENV_PREFIX",
        >>>    key="MY_KEY"  # Override via parameter
        >>> )

        """

        model_config = SettingsConfigDict(
            env_prefix=env_prefix,
            case_sensitive=False,
            extra="ignore"
        )

        class ConfiguredSettings(Settings):
            model_config = model_config

        return ConfiguredSettings(**kwargs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
9 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.