pydantic: BaseSettings with parametrized default values

Question:

It appears one cannot create an __init__ function on pydantic BaseSettings superclasses.

I am trying to achieve this (note; for demonstration, this code is not supported in pydantic):

class MySettings(BaseSettings):
    
    def __init__(self, foo: str):
        
        self.p1 = f"{foo}_param_name1"  # <- trying to achieve this 
        self.p2 = f"{foo}_param_name2"  # <- trying to achieve this

    redis_host: Optional[str] = "asdf"

settings = MySettings(foo="test")
assert settings.redis_host == "asdf"
assert settings.p1 == "test_param_name1" # this will raise a ValueError for p1 not existing
assert settings.p2 == "test_param_name2"

So far, the closest thing I’ve found is create_model https://docs.pydantic.dev/usage/models/#dynamic-model-creation
but this is not discussed in the context of BaseSettings

Is there a way to achieve a BaseSettings superclass where some values are set based on a parameter?

The following seems pretty nasty and would also greatly confuse code editors:

def retrofit(class_instance: BaseSettings, foo: str):
      setattr(class_instance, "p1", f"{foo}_param_name1")

This also works, but causes many linting errors and also confuses editors (seems not to know p1 is a parameter)

def default_settings(foo: str) -> Type[BaseSettings]:
    class DefaultSettings(BaseSettings):
        

        p1 = f"{foo}_param_name1"
        redis_host: Optional[str] = "asdf"


    return DefaultSettings


MyDefaultSettings: Type[BaseSettings] = default_settings("test")


class SuperSettings(MyDefaultSettings): # linting error that MyDefaultSettings is not a valid type
    """ """


conf = SuperSettings()
assert conf.p1 == "test_param_name1" # works at runtime, but linting error that p1 is not an attribute
Asked By: Tommy

||

Answers:

BaseSettings has own constructor __init__ and if you want to override it you should implement same behavior as original constructor +α.

In other case you may call constructor of base (super) class that will do his job. Correct inheritance is matter.

from pydantic import BaseSettings
from typing import Optional


class MySettings(BaseSettings):

    p1: Optional[str]
    p2: Optional[str]
    redis_host: Optional[str] = "asdf"

    def __init__(self, *args, foo: str = None, **kwargs):

        super().__init__(*args, **kwargs)
        if foo is not None:
            self.p1 = f"{foo}_param_name1"  # <- achieved
            self.p2 = f"{foo}_param_name2"  # <- achieved

settings = MySettings(foo="test")
assert settings.redis_host == "asdf"
assert settings.p1 == "test_param_name1" # no ValueError
assert settings.p2 == "test_param_name2"

No assertions in this code

Answered By: rzlvmp
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.