Skip to main content

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

  1. Installation
  2. Quick start
  3. Configuration root and project root
  4. .env files
  5. Environment-variable overrides
  6. Custom YAML constructors
  7. Handling secrets
  8. Runtime reload
  9. Logging
  10. 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 ./config relative to the current working directory. Override with PYFLEX_CFG_ROOT_PATH (absolute path).
  • Project root — the anchor used by the !proj_root YAML constructor. Resolved as follows:
    1. PYFLEX_PROJECT_ROOT_PATH env var, if set — used verbatim.
    2. Path.cwd() when PYFLEX_CFG_ROOT_PATH is not set (default layout: ./config lives inside the project root, so cwd is the project root).
    3. Otherwise (custom config root, no project-root env var) — unresolved. Loading a YAML that uses !proj_root will raise RuntimeError. 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 intfloatboolstr. Force a specific type with a trailing ::Type suffix:

Suffix Result
::int Python int
::float Python float
::bool true/falsebool
::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 current Cfg.config_root.
  • project_root — explicit project root for the !proj_root constructor. 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

pyflexcfg-2.0.0.tar.gz (58.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyflexcfg-2.0.0-py3-none-any.whl (15.3 kB view details)

Uploaded Python 3

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

Hashes for pyflexcfg-2.0.0.tar.gz
Algorithm Hash digest
SHA256 31ce334dd9c56707b85c9b6c43efa1842ac1067fa9690275f753df8f2c2f52cf
MD5 6e243c04b63c4746bbcd52456a042f22
BLAKE2b-256 75951bf78d85bac152f7ad92ce2b664d69d5a0b26a2108e0ab5bdbeaa59b47b3

See more details on using hashes here.

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

Hashes for pyflexcfg-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6ac3969e3e1add02892d6e6310c6bd8aaaeb8a2c45474807c76e78f898a75c36
MD5 9d6e0de63242465792144684843e83ba
BLAKE2b-256 c4c1f67ddcac6c6038276b06008b1236c5fdb360a2f774a9a5cee77aab68b93c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page