Flexible configuration handler for Python projects.
Project description
PyFlexCfg
Flexible YAML configuration handler for Python projects. PyFlexCfg loads a directory tree of YAML files
into a single global Cfg object at import time, with support for environment-variable overrides,
encrypted secrets, custom path constructors, and automatic .env loading.
The handler is built on PyYAML for low dependency footprint and fast loading. YAML 1.2 features are not guaranteed to load properly.
Contents
- Installation
- Quick start
- Configuration root and project root
.envfiles- Environment-variable overrides
- Custom YAML constructors
- Handling secrets
- Runtime reload
- Logging
- Development
Installation
pip install pyflexcfg
Quick start
Given this layout:
project/
├── config/
│ ├── general.yaml
│ └── env/
│ ├── dev.yaml
│ └── prd.yaml
with each file containing data: test, you can use the configuration immediately after import:
from pyflexcfg import Cfg
print(Cfg.general.data)
print(Cfg.env.dev.data)
print(Cfg.env.prd.data)
Directory and file names must match the regex ^[a-z][a-z0-9_]{0,28}[a-z0-9]$ — names that don't match
are silently skipped during loading. Top-level value names inside YAML files become attribute names on
the loaded object, so they must also be valid Python identifiers if you want dot-notation access.
Configuration root and project root
PyFlexCfg distinguishes two paths:
- Config root — the directory it walks for YAML files. Defaults to
./configrelative to the current working directory. Override withPYFLEX_CFG_ROOT_PATH(absolute path). - Project root — the anchor used by the
!proj_rootYAML constructor. Resolved as follows:PYFLEX_PROJECT_ROOT_PATHenv var, if set — used verbatim.Path.cwd()whenPYFLEX_CFG_ROOT_PATHis not set (default layout:./configlives inside the project root, so cwd is the project root).- Otherwise (custom config root, no project-root env var) — unresolved. Loading a YAML that
uses
!proj_rootwill raiseRuntimeError. Configs that don't use the tag are unaffected.
In short: if you set PYFLEX_CFG_ROOT_PATH, you must also set PYFLEX_PROJECT_ROOT_PATH
whenever your config uses !proj_root. The default layout requires neither.
.env files
Any *.env file found directly inside the config root (non-recursive) is loaded via
python-dotenv before YAML parsing. This lets you set
both PYFLEX_CFG_KEY and any CFG__… overrides without touching the shell environment. .env
files are not parsed as YAML and not exposed as Cfg.<name> attributes.
Environment-variable overrides
Variables matching CFG__SECTION__KEY override Cfg.section.key. Cfg.update_from_env() is called
automatically at import, but you can call it again at any point (for example, after mutating env vars in
a test). Values are auto-coerced as int → float → bool → str. Force a specific type with a
trailing ::Type suffix:
| Suffix | Result |
|---|---|
::int |
Python int |
::float |
Python float |
::bool |
true/false → bool |
::str |
Force string, no auto-coercion |
::Secret |
Wrap as Secret (masked in logs/repr) |
::yaml_r |
Parse as YAML, replace the existing value wholesale (works for any YAML type) |
::yaml_m |
Parse as YAML, merge into the existing dict (only when both sides are dicts) |
Examples:
CFG__DB__PORT=5432 # int via auto-coercion
CFG__DB__TIMEOUT=2.5 # float via auto-coercion
CFG__FEATURES__BETA=true # bool via auto-coercion
CFG__DB__PASSWORD=hunter2::Secret # masked Secret
CFG__SERVERS=[host-a, host-b, host-c]::yaml_r # list — replaces existing
CFG__DB={port: 6543, ssl: true}::yaml_m # dict — merged into Cfg.db
CFG__DB={port: 6543, ssl: true}::yaml_r # dict — wipes Cfg.db and writes only these two keys
::yaml_m falls back to replace semantics when either side is not a dict. Overrides for missing
dotted paths are logged at DEBUG level and skipped.
Custom YAML constructors
Use these tags inside any YAML file:
| Tag | Returns | Description |
|---|---|---|
!string |
str |
Join sequence parts as one string. |
!encr |
Secret |
Decrypt a base64 secret using PYFLEX_CFG_KEY. |
!path |
Path |
Host-native concrete path from the given parts. |
!home_dir |
Path |
Host-native path rooted at the current user's home. |
!proj_root |
Path |
Host-native path rooted at the project root (see above). |
!path_posix |
PurePosixPath |
Pure Posix path (legacy alias of !pure_path_posix). |
!path_win |
PureWindowsPath |
Pure Windows path (legacy alias of !pure_path_win). |
!pure_path |
PurePath |
OS-default pure-flavor path. |
!pure_path_posix |
PurePosixPath |
Explicit Posix-flavor path regardless of host OS. |
!pure_path_win |
PureWindowsPath |
Explicit Windows-flavor path regardless of host OS. |
The "pure" variants exist so users on one OS can compose paths that target another OS (e.g. building a
Posix path inside config that runs on a Windows host that talks to a remote Unix box). PyFlexCfg never
auto-resolves a !pure_path_* to the local flavor — you asked for it, you get it.
Examples:
greeting: !string ['Hello, ', 'world!']
log_file: !proj_root [logs, app.log]
home_cache: !home_dir [.cache, my-app]
remote_log: !pure_path_posix [/var, log, remote, app.log]
windows_share: !pure_path_win ['C:\', Shares, app]
Handling secrets
Sensitive values must be encrypted before being committed.
Encrypt:
import os
from pyflexcfg import AESCipher
aes = AESCipher(os.environ['PYFLEX_CFG_KEY'])
ciphertext = aes.encrypt('hunter2')
print(ciphertext) # 'A1u6BIE2xGtYTSoFRE83H0VHsAW3nrv4WB+T/FEAj1fsh8HIId9r/Rskl0bnDHTI'
encrypt() returns a base64 ASCII string — paste it directly into YAML, no quoting needed:
my_secret: !encr A1u6BIE2xGtYTSoFRE83H0VHsAW3nrv4WB+T/FEAj1fsh8HIId9r/Rskl0bnDHTI
At load time PyFlexCfg decrypts the value into a Secret, whose repr/str/format outputs are all
masked as ********. Equality, slicing, and other str methods still work on the underlying value.
PyFlexCfg requires PYFLEX_CFG_KEY only when at least one !encr value is loaded; if all your config
is plain text, the variable is not needed.
Runtime reload
Cfg.reload_config(config_path=None, project_root=None, reset=True) re-walks the configuration tree.
config_path— switch config roots (handy for tests). Defaults to the currentCfg.config_root.project_root— explicit project root for the!proj_rootconstructor. When omitted, the value is resolved from env vars per the rule above. Pass this to override without touching the environment (the typical test pattern).reset=False— overlay loaded top-level keys onto the existing configuration without dropping siblings.
Logging
PyFlexCfg writes DEBUG-level messages under the logger name pyflexcfg. To enable them:
import logging
logging.getLogger('pyflexcfg').setLevel(logging.DEBUG)
Add your own handlers as needed.
Development
This project uses uv for dependency management.
uv sync # install dependencies
uv run pytest # run the test suite
uv run pytest tests/test_handler.py # run a single file
uv run ruff check . # lint
uv run ruff format . # format
uv build # produce wheel + sdist in dist/
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 pyflexcfg-2.0.0.tar.gz.
File metadata
- Download URL: pyflexcfg-2.0.0.tar.gz
- Upload date:
- Size: 58.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.17
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
31ce334dd9c56707b85c9b6c43efa1842ac1067fa9690275f753df8f2c2f52cf
|
|
| MD5 |
6e243c04b63c4746bbcd52456a042f22
|
|
| BLAKE2b-256 |
75951bf78d85bac152f7ad92ce2b664d69d5a0b26a2108e0ab5bdbeaa59b47b3
|
File details
Details for the file pyflexcfg-2.0.0-py3-none-any.whl.
File metadata
- Download URL: pyflexcfg-2.0.0-py3-none-any.whl
- Upload date:
- Size: 15.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.17
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ac3969e3e1add02892d6e6310c6bd8aaaeb8a2c45474807c76e78f898a75c36
|
|
| MD5 |
9d6e0de63242465792144684843e83ba
|
|
| BLAKE2b-256 |
c4c1f67ddcac6c6038276b06008b1236c5fdb360a2f774a9a5cee77aab68b93c
|