Utilities to handle OS environment variables
Project description
os-env-injection
Utilities to handle OS environment variables
Why this library?
You have a function which requires several arguments that typically depend on the system on when the function is running
def f(url: str, user: str, password: str, subdomain: str):
...
You would like to have the possibility to have its values to be read from the OS env, so that, at will, you could invoke it as
f()
If you simply write
import os
def f(
url: str = os.environ["URL"],
user: str = os.environ["USER"],
password: str = os.environ["PASSWORD"],
subdomain: str = os.environ["SUBDOMAIN"],
):
...
then you will encounter a problem whenever the OS env variables are not set, because the defaults are evaluated at import time. This means in practice, that you will be forced to use them, instead of passing the values directly.
A workaround is
import os
def f(
url: str = os.environ.get("URL", None),
user: str = os.environ.get("USER", None),
password: str = os.environ.get("PASSWORD", None),
subdomain: str = os.environ.get("SUBDOMAIN", None),
):
...
In this way, there are still a couple of drawbacks:
- it is necessary to write code to check the values of each variable to raise an exception if values are missing (
None) - the default values will be evaluated when the function is first imported.
In case you are setting the OS env dynamically (e.g. by executing a shell script with
exports) you could end up in troubles.
The nice solution - Quickstart
First, install the library
pip install os-env-injection
From the previous example, say that all variables are required except for subdomain which can stay None.
You can leverage on this library in two ways, depending on your favorite style.
Imperative style
from os_env_injection import inject_var
def f(
url: str = "",
user: str = "",
password: str = "",
subdomain: str | None = None,
) -> None:
url = inject_var(var_value=url, os_env_key="OS_ENV_URL")
user = inject_var(var_value=user, os_env_key="OS_ENV_USER")
password = inject_var(var_value=password, os_env_key="OS_ENV_PASSWORD")
subdomain = inject_var(
var_value=subdomain, os_env_key="OS_ENV_SUBDOMAIN", is_required=False
)
...
Note: inject_var(var_value=url, os_env_key="OS_ENV_URL") is the same as inject_var(var_value=url, os_env_key="OS_ENV_URL", is_required=True).
Functional style
from os_env_injection import inject_os_env, Injection
@inject_os_env(
injections=[
Injection(var_name="url", os_env_key="OS_ENV_URL"),
Injection(var_name="user", os_env_key="OS_ENV_USER"),
Injection(var_name="password", os_env_key="OS_ENV_PASSWORD"),
Injection(
var_name="subdomain",
os_env_key="OS_ENV_SUBDOMAIN",
is_required=False,
),
]
)
def f(
url: str = "",
user: str = "",
password: str = "",
subdomain: str | None = None,
) -> None:
...
Note: Injection(var_name="url") is the same as Injection(var_name="url", os_env_key="url", is_required=True).
What will happen?
- If you explicitly pass a valid value when you call
f, it will be used. - If no value is passed (or the value matches the invalid sentinel, which defaults to
""orNone), it will try to read it from the OS environment variable specified inos_env_key. - If the value is still unset (or the environment variable itself is missing or set to the invalid sentinel), it will raise an exception if
is_requiredisTrue. - If
is_requiredisFalse, it will not raise an exception and simply keep the invalid sentinel (e.g.,None).
Why set a default value at all?
When you decorate a function with @inject_os_env and expect the OS environment to supply the values, you usually want to call the function without checking or passing those arguments yourself (e.g., just f()).
If your function signature doesn't provide default values (i.e., def f(url: str):), calling f() will cause Python to raise a TypeError at runtime for missing arguments, and type checkers (mypy, pyright, etc.) will also complain. Assigning a default value (like url: str = "") satisfies both Python and the type checker, allowing you to invoke f() cleanly while trusting the decorator to inject the real values.
Understanding Sentinel Values (value_to_consider_invalid)
By default, both None and the empty string "" are treated as "missing" values that trigger the fallback to the environment variable.
Why is this useful? Eliminating None-checks
When working with type checkers like mypy or pyright, using None as a default value forces you to make the type optional, which can clutter downstream code:
# Before: using `None` as the default value
@inject_os_env([Injection("url")])
def f(url: str | None = None):
# Type checkers will complain here because `url` might be None!
# Even though `inject_os_env` guarantees `url` will be populated from the OS var,
# the type signature `str | None` says otherwise.
processed_url = url.strip() # Error: Item "None" of "str | None" has no attribute "strip"
# You are forced to add redundant checks just to satisfy the type checker:
assert url is not None
processed_url = url.strip()
By using "" as the invalid sentinel, you can keep strict typing without the boilerplate:
# After: using `""` as the default invalid sentinel
@inject_os_env([Injection("url")])
def f(url: str = ""):
# No type checker complaints!
# `url` is guaranteed to be a string.
# If the env var is missing, the decorator raises an error before we even reach this point.
processed_url = url.strip()
Custom Sentinels
If your environment variable might legitimately be an empty string, or you want to use a different sentinel, you can explicitly set value_to_consider_invalid:
url = inject_var(
var_value=url,
os_env_key="OS_ENV_URL",
value_to_consider_invalid="__MISSING__"
)
# or functionally:
Injection(
var_name="url",
value_to_consider_invalid="__MISSING__"
)
Setup development environment (for contributors only)
-
Create a virtual environment and activate it
python -m venv venv source venv/bin/activate
-
Install the developer dependencies you will need
pip install -U pip wheel setuptools pip install -e .[dev]
-
Set black as pre-commit package (will automatically apply black before committing)
pre-commit install -
To run the tests
pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file os_env_injection-2.0.1.tar.gz.
File metadata
- Download URL: os_env_injection-2.0.1.tar.gz
- Upload date:
- Size: 12.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.20
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
549924e53ca8b3ea018994c83e82f2e4494444a72f160deecc290b69056c82a1
|
|
| MD5 |
158245cf238b00f3ad8bbfe66ecaa96c
|
|
| BLAKE2b-256 |
02bdbed69c700ab327fcfaa0a96639cc2251725c84d77dcbbe5226546494d8bd
|
File details
Details for the file os_env_injection-2.0.1-py3-none-any.whl.
File metadata
- Download URL: os_env_injection-2.0.1-py3-none-any.whl
- Upload date:
- Size: 5.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.20
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3d21f0edc5bf29f1318c2d2abea022c4a5c49af3c0996851ca2f18b3504ea48
|
|
| MD5 |
5ac4ac89cc0550a5b8df7feb37af7546
|
|
| BLAKE2b-256 |
f73c637054deed19166e923fab1b02b3769fb48e512cf85533da22167bfda4de
|