Smart environment loader with robust casting/normalization. Layers: decouple → dotenv → os.environ.
Project description
castenv
castenv is a smart environment loader with robust casting/normalization, that uses python-decouple (if installed), python-dotenv with a Fallback to os.environ
current_version = "v0.1.1"
- python-decouple (if installed)
- python-dotenv (if installed; auto-discovers
.env,.env.local,.env.{env},.env.{env}.localacross cwd+parents) - Fallback to os.environ
No per-call config — just castenv.get_env("KEY"), like os.environ.get but smarter.
Quick start
Install
pip install castenv
# Optional extras:
pip install "castenv[dotenv]"
pip install "castenv[decouple]"
Example
import castenv as env
# Optional one-time setup (else: auto cwd+parents):
# env.configure(search_dirs=[BASE_DIR], env_name="prod")
DATABASE_URL = env.get_env("DATABASE_URL", None)
DEBUG = env.env_bool("DEBUG", False)
DEBUG2 = env.get_env("DEBUG2", True)
TIMEOUT_SECS = env.get_env("TIMEOUT", "1m30s") # -> seconds float (90.0)
TIMEOUT_HOURS = env.get_env("HOURS", "2d1h") # -> seconds float (176400.0)
CACHE_BYTES = env.get_env("CACHE", "256MB") # -> bytes int (256000000)
ALLOWED = env.get_env(
"ALLOWED_HOSTS",
"localhost,127.0.0.1",
normalize_kwargs={"parse_lists": True}
)
PORT = env.get_env("PORT", 8080) # -> 8080 (int)
OPTS = env.get_env("OPTS", {"x": 1}) # -> {"x": 1} (dict)
SCALE = env.get_env("SCALE", "50%", normalize_kwargs={"percent_mode": "fraction"})
TAX = env.get_env("PORT", 15.5) # -> 15.5 (float)
print("DATABASE_URL:", DATABASE_URL)
print("DEBUG (bool) [env_bool]:", DEBUG)
print("DEBUG2 (bool) [get_env]:", DEBUG2)
print("TIMEOUT (secs):", TIMEOUT_SECS)
print("TIMEOUT_HOURS (secs):", TIMEOUT_HOURS)
print("CACHE (bytes):", CACHE_BYTES)
print("ALLOWED_HOSTS (list):", ALLOWED)
print("PORT (int):", PORT)
print("OPTS (dict):", OPTS)
print("SCALE (float) [0-1]:", SCALE)
print("TAX (float):", TAX)
API Reference
Main Functions
get_env(key, default=None, *, normalize_kwargs=None)
Retrieves and normalizes an environment variable with automatic type casting.
Parameters:
key(str): Environment variable namedefault(Any): Default value if key not found (default: None)normalize_kwargs(dict, optional): Additional normalization options (see below)
Returns: Normalized value based on content and settings
env_bool(key, default=None, **kwargs)
Retrieves environment variable as boolean.
Boolean values recognized:
True:"true","yes","y","on","1"False:"false","no","n","off","0"
env_int(key, default=None, **kwargs)
Retrieves environment variable as integer.
env_float(key, default=None, **kwargs)
Retrieves environment variable as float.
env_str(key, default=None, **kwargs)
Retrieves environment variable as string.
env_list(key, default=None, *, separators=(",",), **kwargs)
Retrieves environment variable as list with custom separators.
Parameters:
separators: Tuple of separator strings (default:(",",))
get_all(keys, defaults=None, *, normalize_kwargs=None)
Retrieves multiple environment variables at once.
Parameters:
keys: Iterable of key namesdefaults: Dict of default values per keynormalize_kwargs: Normalization options for all keys
Normalization & Casting
Automatic Type Detection
castenv automatically detects and converts values based on their content:
1. None/Null Values
Converts to Python None:
- Empty string:
"" - Null literals:
"null","none","nil","undefined"
env.get_env("MISSING", "") # -> None (if coerce_empty_to_none=True)
env.get_env("NULL_VAL", "null") # -> None
2. Boolean Conversion
Automatically detects boolean strings (case-insensitive):
True values: "true", "yes", "y", "on", "1"
False values: "false", "no", "n", "off", "0"
env.get_env("DEBUG", "true") # -> True
env.env_bool("ENABLED", "no") # -> False
3. Number Parsing
Integers:
- Decimal:
"42"→42 - Hexadecimal:
"0xFF"→255 - Binary:
"0b1010"→10 - Octal:
"0o10"→8
Floats:
- Standard:
"3.14"→3.14 - Scientific notation:
"1e-3"→0.001
env.get_env("HEX_COLOR", "0xFF00FF") # -> 16711935
env.get_env("RATIO", "1.5e-2") # -> 0.015
4. Duration Parsing
Converts human-readable durations to seconds (float):
Supported units:
ns- nanosecondsus,µs- microsecondsms- millisecondss- secondsm- minutesh- hoursd- daysw- weeks
Examples:
env.get_env("TIMEOUT", "1m30s") # -> 90.0
env.get_env("CACHE_TTL", "2d1h") # -> 176400.0
env.get_env("DELAY", "500ms") # -> 0.5
env.get_env("WEEK", "1w") # -> 604800.0
5. Byte Size Parsing
Converts size strings to bytes (int):
IEC Units (powers of 1024):
b,B- bytesk,kb,kib,KiB- kibibytesm,mb,mib,MiB- mebibytesg,gb,gib,GiB- gibibytest,tb,tib,TiB- tebibytes
SI Units (powers of 1000, uppercase trigger):
KB- kilobytes (1000)MB- megabytes (1000²)GB- gigabytes (1000³)TB- terabytes (1000⁴)
env.get_env("CACHE", "256MB") # -> 256000000 (SI: 256 * 1000²)
env.get_env("BUFFER", "1MiB") # -> 1048576 (IEC: 1024²)
env.get_env("DISK", "500gb") # -> 536870912000 (IEC by default)
6. Percentage Conversion
Handles percentage values with configurable output:
Modes (via percent_mode):
"none"- Returns as string (default):"50%"→"50%""number"- Returns numeric value:"50%"→50.0"fraction"- Returns decimal fraction:"50%"→0.5
env.get_env("TAX", "15%", normalize_kwargs={"percent_mode": "number"}) # -> 15.0
env.get_env("SCALE", "50%", normalize_kwargs={"percent_mode": "fraction"}) # -> 0.5
env.get_env("RAW", "25%") # -> "25%" (default)
7. JSON Parsing
Automatically parses JSON objects and arrays:
env.get_env("CONFIG", '{"key": "value", "count": 10}') # -> {'key': 'value', 'count': 10}
env.get_env("ITEMS", '["a", "b", "c"]') # -> ['a', 'b', 'c']
env.get_env("QUOTED", '"{\\"k\\": \\"v\\"}"') # -> {'k': 'v'}
8. List Parsing
Splits comma-separated values (or custom separators) into lists:
Default separator: ,
env.get_env("HOSTS", "localhost,127.0.0.1", normalize_kwargs={"parse_lists": True})
# -> ['localhost', '127.0.0.1']
env.env_list("PORTS", "8000;8001;8002", separators=(";",))
# -> ['8000', '8001', '8002']
# Recursive normalization on list items
env.get_env("MIXED", "true,42,3.14", normalize_kwargs={"parse_lists": True})
# -> [True, 42, 3.14]
9. Environment Variable Interpolation
Expands ${VAR} and $VAR references:
# With BASE_URL="https://api.example.com"
env.get_env("API_URL", "${BASE_URL}/v1") # -> "https://api.example.com/v1"
env.get_env("FULL_URL", "${BASE:-http://localhost}/api") # -> Uses default if BASE not set
env.get_env("PATH", "$HOME/config") # -> Expands $HOME
10. Path Expansion
Expands ~ to user's home directory:
env.get_env("LOG_FILE", "~/app/logs/app.log") # -> "/home/user/app/logs/app.log"
11. Quote Handling
Strips matching quotes and unescapes common sequences:
Escape sequences supported:
\\→\\"→"\'→'\n→ newline\r→ carriage return\t→ tab\b→ backspace\f→ form feed\0→ null byte
env.get_env("MSG", '"Hello\\nWorld"') # -> "Hello\nWorld" (with actual newline)
env.get_env("PATH", "'C:\\\\Users\\\\App'") # -> "C:\Users\App"
Normalization Parameters
The normalize_kwargs dictionary accepts the following parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
coerce_empty_to_none |
bool | True |
Convert empty strings to None |
coerce_null_strings |
bool | True |
Convert "null"/"none" to None |
parse_booleans |
bool | True |
Parse boolean strings |
parse_numbers |
bool | True |
Parse numeric strings |
parse_json |
bool | True |
Parse JSON objects/arrays |
parse_lists |
bool | True |
Parse comma-separated lists |
list_separators |
tuple | (",",) |
Separators for list parsing |
strip_quotes |
bool | True |
Remove matching quotes |
unescape_in_quotes |
bool | True |
Unescape sequences in quotes |
interpolate_env |
bool | True |
Expand ${VAR} references |
expand_user |
bool | True |
Expand ~ to home directory |
parse_duration |
bool | True |
Parse duration strings |
parse_bytesize |
bool | True |
Parse byte size strings |
percent_mode |
str | "none" |
Percentage mode: "none", "number", or "fraction" |
lowercase_strings |
bool | False |
Convert final strings to lowercase |
enum |
iterable | None |
Validate value is in allowed set |
Examples with Custom Parameters
# Disable list parsing
env.get_env("CSV", "a,b,c", normalize_kwargs={"parse_lists": False}) # -> "a,b,c"
# Custom list separator
env.get_env("ITEMS", "x|y|z", normalize_kwargs={"parse_lists": True, "list_separators": ("|",)}) # -> ['x', 'y', 'z']
# Enum validation
env.get_env("ENV", "dev", normalize_kwargs={"enum": ["dev", "staging", "prod"]}) # -> "dev"
# Raises ValueError if value not in enum
# Lowercase strings
env.get_env("NAME", "MyApp", normalize_kwargs={"lowercase_strings": True}) # -> "myapp"
# Disable specific parsers
env.get_env("RAW", "true", normalize_kwargs={"parse_booleans": False}) # -> "true" (string)
Configuration
Global Configuration
Configure castenv once at startup:
from pathlib import Path
import castenv as env
env.configure(
search_dirs=[Path("/app/config")], # Where to look for .env files
env_name="production", # Use .env.production files
filenames=[".env", ".env.local"], # Custom file names
stop_at_first_found_dir=True, # Stop at first dir with .env
prefer_os_over_dotenv=True, # OS env vars take precedence
use_decouple_if_available=True # Use python-decouple if installed
)
Temporary Configuration (Testing)
Use context manager for temporary overrides:
with env.using(search_dirs=[Path("tests/fixtures")], env_name="test"):
# Temporary configuration active here
value = env.get_env("TEST_VAR")
# Original configuration restored
Testing overrides
from pathlib import Path
import castenv as env
with env.using(search_dirs=[Path("tests/envs")], env_name="test"):
assert env.get_env("SOME_KEY") == "value"
Development
Virtual Environments
python -m venv venv
Mac/Linux
source venv/bin/activate
Windows
venv/scripts/activate
Install Requirements
pip install poetry
poetry install
Install Optional Requirements
poetry install --extras "decouple"
poetry install --extras "dotenv"
poetry install --extras "decouple dotenv"
Test
pytest
coverage run -m pytest
coverage report
coverage html
mypy --html-report mypy_report .
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --format=html --htmldir="flake8_report/basic" --exclude=venv
flake8 . --count --exit-zero --max-complexity=11 --max-line-length=127 --statistics --format=html --htmldir="flake8_report/complexity" --exclude=venv
BumpVer
With the CLI command bumpver, you can search for and update version strings in your project files. It has a flexible pattern syntax to support many version schemes (SemVer, CalVer or otherwise).
Run BumbVer with:
bumpver update --major --dry
bumpver update --major
bumpver update --minor --dry
bumpver update --minor
bumpver update --patch --dry
bumpver update --patch
Build
poetry build
Publish
poetry publish
License
Project details
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 castenv-0.1.1.tar.gz.
File metadata
- Download URL: castenv-0.1.1.tar.gz
- Upload date:
- Size: 19.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.12.2 Windows/11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b9a241bad840915bbefa8d1aa4a19cd3a4034050c95444c379f378cc172577f
|
|
| MD5 |
97ca5fef5e1fc288406ee80a5914aeb4
|
|
| BLAKE2b-256 |
d7b062d68c69ae1151a36253403bf6142a0c6904a9372af68fae8f1a8955952f
|
File details
Details for the file castenv-0.1.1-py3-none-any.whl.
File metadata
- Download URL: castenv-0.1.1-py3-none-any.whl
- Upload date:
- Size: 16.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.12.2 Windows/11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8df4b8191def521a1981771f02c4bf9ab3a49f143df68c51550287f55309dc98
|
|
| MD5 |
74f904e6ce6a88f64dfdeff72ef8f2a1
|
|
| BLAKE2b-256 |
7d2f6a1ea3381542419e609858ececfd7b9674067e71f94f51978b8fa9ec628e
|