Twelve-Factor Python applications using Pydantic Settings
A look at Pydantic Settings and how it can help you reliably deploy applications across environments
Pydantic Settings
Pydantic Settings is a Python package closely related to the popular Pydantic package. It allows defining type-checked “settings” objects that can be automatically populated from environment variables or a dotenv (.env) file.
As a small example, consider the following Python code snippet:
from pydantic_settings import BaseSettings, SettingsConfigDict
class TestSettings(BaseSettings, frozen=True):
model_config = SettingsConfigDict(env_file=".env")
foo: str
bar: bool
baz: int
if __name__ == "__main__":
print(TestSettings(baz=1))
The test settings object initializes values that are not configured in the code with values sourced from the environment:
$ foo=bar BAR=true baz=5 python example.py
# foo='bar' bar=True baz=1
If settings remain unset, Pydantic refuses to create the settings object:
$ python example.py
# pydantic_core._pydantic_core.ValidationError: 2 validation errors for TestSettings
# foo
# Field required [type=missing, input_value={}, input_type=dict]
# For further information visit https://errors.pydantic.dev/2.3/v/missing
# bar
# Field required [type=missing, input_value={}, input_type=dict]
# For further information visit https://errors.pydantic.dev/2.3/v/missing
Pydantic additionally checks whether settings match their declared type:
$ foo=bar bar=baz python example.py
# pydantic_core._pydantic_core.ValidationError: 1 validation error for TestSettings
# bar
# Input should be a valid boolean, unable to interpret input [type=bool_parsing, input_value='baz', input_type=str]
# For further information visit https://errors.pydantic.dev/2.3/v/bool_parsing
If possible, Pydantic implicitly converts types; if you set “bar” to 1, Pydantic converts this to True. Settings can also be read automatically from a .env
file:
$ echo -e "foo=bar\nbar=1\nbaz=5" > .env && python example.py
# foo='bar' bar=True baz=1
Twelve-factor applications
Now that we know what Pydantic Settings is, let’s look at what we need it for.
It is almost always a good idea™️ to separate application configuration from core application logic. A good way to do this is by injecting configuration through the environment. A fancier way of saying this is that Pydantic Settings helps create applications that adhere to the twelve-factor methodology. The twelve-factor methodology is an influential set of best practices that maximize portability between execution environments, intending to facilitate your application’s deployment. Injecting configuration through environment variables is a big part of that.
Say you build or package your application, and then deploy it to a development or integration environment with certain settings. Injecting all configurations into it from the environment enables moving your application from your testing to your production environment exactly as it was built and tested.
Why Pydantic Settings?
Separating configuration values from your core application code is definitely a good idea. Whether you should use Pydantic Settings for this is more circumstantial. Pydantic Settings does offer several advantages over using python-dotenv directly and/or reading from environment variables or configuration files from all over your application:
- Validity. Pydantic Settings checks for the presence and types of your setting variables, allowing you to fail early in case of incorrect configuration.
- Error messages. Pydantic Settings presents clear validation errors that tell you exactly which settings are missing or wrong.
- Overriding. Environment settings can easily be overridden from within your code. This can be useful, e.g., to set settings to localized values when testing.
- Loose coupling. Although the twelve-factor methodology is very specific about using environment variables, I consider this an implementation detail — the important part is that you keep configuration and code separated. Environment variables cannot always be considered sufficiently secure and are resolved at your application’s startup time, so there may be situations where retrieving configuration in another way is more appropriate. For example, Google recommends against storing secret configuration values in environment variables. Creating settings objects at the fringes of your application instead of calling
os.env
everywhere directly decouples your application logic from how it retrieves configuration, making it easy to source settings from somewhere else, such as a database, a YAML file, or a secret vault. Although such functionality is not built into Pydantic Settings, loose coupling means you can implement it without changing most of your application. - Features. Pydantic (Settings) has many nice-to-have features not discussed in this introductory article. One example is that it allows cleanly defining custom validators. Another is its ability to mark strings as “secret”, which helps prevent them from ending up in logs. Check out the Pydantic and Pydantic Settings documentation to learn more!