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
Metadata
Release files for os-env-injection 2.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| os_env_injection-2.0.1.tar.gz | 12.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| os_env_injection-2.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 18.0 kB
Release files / os_env_injection-2.0.1.tar.gz
| Download URL | os_env_injection-2.0.1.tar.gz |
|---|---|
| Size | 12.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
549924e53ca8b3ea018994c83e82f2e4494444a72f160deecc290b69056c82a1
|
|
BLAKE2b-256 checksum How to use checksums |
02bdbed69c700ab327fcfaa0a96639cc2251725c84d77dcbbe5226546494d8bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.20
|
Release files / os_env_injection-2.0.1-py3-none-any.whl
| Download URL | os_env_injection-2.0.1-py3-none-any.whl |
|---|---|
| Size | 5.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e3d21f0edc5bf29f1318c2d2abea022c4a5c49af3c0996851ca2f18b3504ea48
|
|
BLAKE2b-256 checksum How to use checksums |
f73c637054deed19166e923fab1b02b3769fb48e512cf85533da22167bfda4de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.20
|