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
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
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
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