Pydantic: BaseSettings Singleton option?

Created on 10 Jun 2019  路  5Comments  路  Source: samuelcolvin/pydantic

I've noticed a pattern come up in several of our projects and thought maybe (but I'm really not sure) pydantic could ship support for it.

# config.py
from functools import lru_cache
from pydantic import BaseSettings


class Config(BaseSettings):
    a: str
    b: str
    c: str

@lru_cache()
def read() -> Config:
    return Config()

```py

a.py

from . import config
conf = config.read

```py
# b.py
from . import config
conf = config.read

Would it be a good library addition if we could do something like:

# config.py
from functools import lru_cache
from pydantic import BaseSettings


class Config(BaseSettings):
    a: str
    b: str
    c: str

    class Config:
        singleton = True

```py

a.py

from . import Config
conf = Config()

```py
# b.py
from . import config
conf = Config()

I definitely appreciate that pydantic should not try to take care of application-level features like dependency injection and object caching. However, for the special case of BaseSettings this just seems like a practical feature. Thoughts?

Maybe there is already a more general solution here that is readily available that i missed, allowing e.g.:

# config.py
from pydantic import BaseSettings

@singleton # exists?
class Config(BaseSettings):
    a: str
    b: str
    c: str

But I haven't come across that yet. And if such a thing is from another third party lib. I still feel then that there is value in pydantic shipping an out of box solution, if its what users would want most of the time.

question

Most helpful comment

I completely agree settings should be a singleton, however I've never had any problem creating it as a singleton which is then used throughout the application.

This is probably because most applications I build are either based on aiohttp or use aiohttp's approach of a single "app" context.

See for example here. This approach also works well for testing where you generally want a very different settings instance which can easily be changed on a per-test basis without reloading modules.

My suspicion is that you've made design mistakes if you find yourself trying to initialise settings more than once. Probably best you fix the root mistake or take care of the workaround outside pydantic.

All 5 comments

I have personally dealt with this by just using a module-level instance.

My settings file looks like this:

# config.py
from pydantic import BaseSettings

class AppSettings(BaseSettings):
    ...

settings = AppSettings()

Then, when I want to access the settings object, I just do

from .config import settings

and it imports that specific instance.

As far as I can tell, the only difference between this and your current approach is that the module-variable approach instantiates the config on import, whereas your approach waits to instantiate it until the config.read() function is called for the first time. In my applications, 1) the config is used very early in the app lifecycle, and 2) I never expect the environment to change anyway, so this difference is irrelevant. But I could imagine an application requiring more of a lazy-loading approach.

Would this approach be undesirable for you?

I completely agree settings should be a singleton, however I've never had any problem creating it as a singleton which is then used throughout the application.

This is probably because most applications I build are either based on aiohttp or use aiohttp's approach of a single "app" context.

See for example here. This approach also works well for testing where you generally want a very different settings instance which can easily be changed on a per-test basis without reloading modules.

My suspicion is that you've made design mistakes if you find yourself trying to initialise settings more than once. Probably best you fix the root mistake or take care of the workaround outside pydantic.

Makes sense and thanks!

@dmontagu that doesn't work for my case because it would lead to the config being calculated before environment mocking takes place by pytest.

@samuelcolvin agree; There is a discussion on the fastapi project about similar but as far as I know still unresolved.

@jasonkuhrt makes sense, thanks for explaining

Was this page helpful?
0 / 5 - 0 ratings