```{currentmodule} typed_settings ``` (guide-settings-from-env-vars)= # Environment Variables This pages explains how to load settings from environment variables. ## Basics Typed Settings loads environment variables that match `{PREFIX}{OPTION_NAME}`. {samp}`{PREFIX}` is an option for the {class}`~typed_settings.loaders.EnvLoader`. It should be UPPER_CASE and end with an `_`, but this is not enforced. The prefix can also be an empty string. If you use {func}`load()` (or {func}`default_loaders()`), {samp}`{PREFIX}` is derived from the *appname* argument. For example, {code}`"appname"` becomes {code}`"APPNAME_"`. You can override it with the *env_prefix* argument. You can also completely disable environment variable loading by setting *env_prefix* to {code}`None`. Values loaded from environment variables are strings. They are converted to the type specified in the settings class by the converter at the end of the settings loading process. The {func}`~typed_settings.converters.default_converter()` supports the most common types like booleans, dates, enums and paths. ```{danger} Never pass secrets via environment variables! See {ref}`secrets` for details. ``` ## Nested settings Settings classes can be nested but environment variables have a flat namespace. So Typed Settings builds a flat list of all options and uses the "dotted path" to an attribute (e.g., {code}`attrib.nested_attrib.nested_nested_attrib`) for mapping flat names to nested attributes. Here's an example: ```{code-block} python :caption: example.py import typed_settings as ts @ts.settings class Nested: attrib: int = 0 @ts.settings class Settings: nested: Nested = Nested() attrib: str = "" flag: bool = True print(ts.load(Settings, "myapp")) ``` ```{code-block} console $ export MYAPP_ATTRIB=spam $ export MYAPP_FLAG=0 $ export MYAPP_NESTED_ATTRIB=42 $ python example.py Settings(nested=Nested(attrib=42), attrib='spam', flag=False) ``` ### Delimiter for nested settings If the {code}`Settings` from above defined an attribute {code}`nested_attrib`}, this would lead to a conflict with the env-var name {code}`MYAPP_NESTED_ATTRIB`. To avoid this problem, you can define a *nested delimiter* when you load the settings: ```{code-block} python :caption: example.py :emphasize-lines: 18 import typed_settings as ts @ts.settings class Nested: attrib: str @ts.settings class Settings: nested: Nested nested_attrib: str settings = ts.load( Settings, "myapp", env_nested_delimiter="__", ) print(settings) ``` ```{code-block} console $ export MYAPP_NESTED_ATTRIB=spam $ export MYAPP_NESTED__ATTRIB=eggs $ python example.py Settings(nested=Nested(attrib='eggs'), nested_attrib='spam') ``` ## Overriding the var name for a single option Sometimes, you may want to read an option from another variable than Typed Settings would normally do. For example, your company's convention might be to use {code}`SSH_PRIVATE_KEY_FILE`, but your app would look for {code}`MYAPP_SSH_KEY_FILE`: ```{code-block} python import typed_settings as ts @ts.settings class Settings: ssh_key_file: str = "" print(ts.load(Settings, "myapp")) ``` In order to read from the desired env var, you can use {func}`os.getenv()` and assign its result as default for your option: ```{code-block} python :caption: example.py import os import typed_settings as ts @ts.settings class Settings: ssh_key_file: str = os.getenv("SSH_PRIVATE_KEY_FILE", "") print(ts.load(Settings, "myapp")) ``` ```{code-block} console $ export SSH_PRIVATE_KEY_FILE='/run/private/id_ed25519' $ python example.py Settings(ssh_key_file='/run/private/id_ed25519') ``` ## Dotenv Files There are two ways to read settings from a {file}`.env` file: 1. Use [python-dotenv] and call {func}`dotenv.load_dotenv()` prior to loading your settings with Typed Settings. This will update {data}`os.environ` which Typed Settings' {class}`~typed_settings.loaders.EnvLoader` will then read. 2. Add the {class}`~typed_settings.loaders.DotEnvLoader` to your loaders. That will also use [python-dotenv], but it will not modify {data}`os.environ`. If you want to use the {class}`~typed_settings.loaders.DotEnvLoader`, you need to add the dependency group `dotfiles` (e.g., `typed-settings[dotfiles]`) to your requirements. Here is an example for the {class}`typed_settings.loaders.DotEnvLoader` loader: ```{code-block} python :caption: example.py from pathlib import Path import typed_settings as ts @ts.settings class Settings: ssh_key_file: str = "" loaders = [ *ts.default_loaders("myapp"), ts.loaders.DotEnvLoader( # Last position => highest precedence prefix="MYAPP_", dotenv_path=Path(".env"), # Optional in most case (except this doctest :)) ), ] print(ts.load_settings(Settings, loaders)) ``` ```{code-block} console $ echo "MYAPP_SSH_KEY_FILE='/run/private/id_ed25519'" > ".env" $ python example.py Settings(ssh_key_file='/run/private/id_ed25519') ``` [python-dotenv]: https://github.com/theskumar/python-dotenv