ConfigPlusPlus
CONFIG, MADE LEGIBLE
Typed configuration for Python — load it from environment variables or YAML, display it grouped and readable, and mask secrets automatically. A small, dependency-light library meant to be shared across every service in a stack.
Declare a config class, read each field with
env(...)or from a YAML file, and
Why ConfigPlusPlus
- One obvious place for configuration. A config is a class; every
UPPERCASEattribute is a field. No scatteredos.getenvcalls, no untyped dictionaries. - Readable by default.
print(MyConfig)renders a boxed, prefix-grouped, aligned view — on the class itself, no instance required. - Secrets never leak into logs. Fields whose name contains
SECRET,API_KEY,PASSWORD,TOKENorCREDENTIALare masked automatically, everywhere the config is displayed. - Typed, with a precise casting contract.
env(..., cast=int|bool|float|pathlib.Path)with a documented, stable boolean rule — the same across every service that depends on it.
Installation
pip install configplusplus
# or
poetry add configplusplus
Requires Python 3.10+.
Quickstart
from configplusplus import EnvConfigLoader, env
import pathlib
class AppConfig(EnvConfigLoader):
DATABASE_HOST = env("DATABASE_HOST") # required (raises if missing)
DATABASE_PORT = env("DATABASE_PORT", cast=int) # typed
DATA_DIR = env("DATA_DIR", cast=pathlib.Path) # pathlib.Path
DEBUG_MODE = env("DEBUG_MODE", cast=bool, default=False) # optional with default
SECRET_API_KEY = env("SECRET_API_KEY") # masked on display
print(AppConfig.DATABASE_HOST) # -> 'localhost'
print(AppConfig) # -> grouped, aligned, masked view
print(AppConfig) renders:
╔════════════════════════════════════════════╗
║ APPCONFIG ║
╚════════════════════════════════════════════╝
▶ API
API_ENDPOINT = 'https://api.example.com'
API_KEY = 'key…56 (hidden)'
▶ DATABASE
DATABASE_HOST = 'localhost'
DATABASE_PORT = 5432
▶ DEBUG
DEBUG_MODE = False
▶ SECRET
SECRET_JWT_KEY = 'sk_…89 (hidden)'
Only UPPERCASE, non-callable attributes are part of a config. Fields are grouped by the prefix
before the first underscore (DATABASE_HOST + DATABASE_PORT → DATABASE).
EnvConfigLoaderbodies are evaluated at import time — the environment must already be populated when the class is defined. Load your.envbefore importing the config (see below).
The env() casting contract
env(key: str, *, default=None, cast=str, required=True)
| Argument | Default | Behaviour |
|---|---|---|
cast |
str |
int, float, bool, pathlib.Path, or any 1-arg callable |
default |
None |
Returned when the variable is unset |
required |
True |
Raise RuntimeError if unset and no default is provided |
env_optional(key, *, default=None, cast=str) is the shorthand for required=False.
env_list(key, *, default=None, sep=",", cast=str, required=True) reads a delimited value as
a list ("a, b ,c" → ["a", "b", "c"]; cast=int on "80,443" → [80, 443]).
Boolean casting (cast=bool) — these strings are False; everything else is True:
"false" "False" "FALSE" "0" "no" "No" "NO" ""
Loading .env files
from configplusplus import safe_load_envs
safe_load_envs() # load ./.env (default)
safe_load_envs(".env") # explicit file
safe_load_envs("config/.env") # nested file
safe_load_envs("./config") # a directory: loads every *.env inside it
safe_load_envs(verbose=False) # silent
# Typical entrypoint: load the environment BEFORE importing config classes.
Returns True if at least one file was loaded, False otherwise. Accepts a str or a
pathlib.Path, a single *.env file or a directory of them.
YAML configuration
from configplusplus import YamlConfigLoader
class UiConfig(YamlConfigLoader):
def __post_init__(self) -> None:
self.app_name = self._raw_config["application"]["name"]
self.theme = self._raw_config["display"]["theme"]
config = UiConfig("config.yaml")
config.get("database.host") # dot-notation access
config.get("api.timeout", default=30) # with a fallback
config.has("database.host") # membership test
config.to_dict() # plain dict
print(config) # same grouped, masked display
Unlike EnvConfigLoader, YamlConfigLoader is instantiated with a path and runs a
__post_init__ hook where you shape the raw YAML into typed attributes.
Secret masking
Masking is a safety feature, applied wherever a config is displayed. A field is masked when its name contains any of:
SECRET API_KEY PASSWORD TOKEN CREDENTIAL
SECRET_API_KEY = "sk_live_abc123xyz789" # shown as 'sk_…89 (hidden)'
PASSWORD = "short" # shown as '***hidden***' (≤ 6 chars)
Masking applies to the display. to_dict() returns raw values (so you can read them);
use to_dict(mask=True) when logging the whole config. Extend the keyword set per class
(extend only, never narrow):
class MyConfig(EnvConfigLoader):
_sensitive_keywords = EnvConfigLoader._sensitive_keywords + ("PRIVATE_KEY",)
Custom validation
class APIConfig(EnvConfigLoader):
PORT = env("PORT", cast=int, default=8000)
@classmethod
def validate(cls) -> None:
super().validate() # always call super().validate()
if not (1024 <= cls.PORT <= 65535):
raise RuntimeError("PORT out of range")
APIConfig.validate()
Architecture
graph TD
Meta["ConfigMeta (metaclass)<br/>display · grouping · masking"]
Base["ConfigBase<br/>re-dispatches __repr__ to the metaclass"]
Env["EnvConfigLoader<br/>static · body runs at import time"]
Yaml["YamlConfigLoader<br/>instance · __post_init__ hook"]
Meta --> Base
Base --> Env
Meta -. "duplicates mask + __repr__" .-> Yaml
The display lives on the metaclass, which is why print(MyConfig) works on the class with no
instance. YamlConfigLoader intentionally re-implements masking so it can display instances the
same way.
Public API
| Symbol | Kind | Purpose |
|---|---|---|
EnvConfigLoader |
class | Static, class-based config read from environment variables |
YamlConfigLoader |
class | Instance-based config read from a YAML file |
ConfigBase |
class | Base for custom loaders; delegates display to ConfigMeta |
ConfigMeta |
metaclass | Owns to_dict, grouping and masking |
env |
function | Read one variable with casting / default / required |
env_optional |
function | env(..., required=False) shorthand |
env_list |
function | Read a delimited variable as a typed list |
safe_load_envs |
function | Load .env file(s) from a path or directory, with logging |
The package ships a PEP 561 py.typed marker — its type hints are visible to downstream
type-checkers.
Documentation
| Guide | Contents |
|---|---|
| Installation | Install options and requirements |
| Usage | Full walkthrough of every feature |
| Reference | Concise API cheat-sheet |
examples/ |
Runnable end-to-end scripts |
Project layout
src/configplusplus/
├── __init__.py public API + __all__
├── base.py ConfigMeta (display, grouping, masking) + ConfigBase
├── env_loader.py EnvConfigLoader — static, import-time evaluation
├── yaml_loader.py YamlConfigLoader — instance, __post_init__, dot-notation get()
└── utils.py env() · env_optional() · safe_load_envs()
Development
poetry install # editable install (src layout)
poetry run pytest # tests + coverage
poetry run black src/ tests/ examples/ # formatting (checked in CI)
poetry run ruff check src/ tests/ examples/ # linting
poetry run mypy src/ # type checking
Releases are automated. Commit with Conventional Commits
(fix:, feat:, feat!:); merging to main lets release-please open a version-bump PR,
and merging that PR tags the release and publishes to PyPI via Trusted Publishing (OIDC).
License
MIT © Florian BARRE
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 configplusplus-0.3.0.tar.gz.
File metadata
- Download URL: configplusplus-0.3.0.tar.gz
- Upload date:
- Size: 15.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d4a38a4f0fdb40cd3c7a22da2fc0489878e1d61eeae86b4e351609d74a3fb07
|
|
| MD5 |
bc3639df536478b8ed7776735ce5d652
|
|
| BLAKE2b-256 |
322210af6045545cf8a13a18ceecd035a29f426999311fad45653d3e7ac69838
|
Provenance
The following attestation bundles were made for configplusplus-0.3.0.tar.gz:
Publisher:
publish.yml on Florian-BARRE/ConfigPlusPlus
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
configplusplus-0.3.0.tar.gz -
Subject digest:
0d4a38a4f0fdb40cd3c7a22da2fc0489878e1d61eeae86b4e351609d74a3fb07 - Sigstore transparency entry: 2688497266
- Sigstore integration time:
-
Permalink:
Florian-BARRE/ConfigPlusPlus@899aebeb0fc87e8567f2fb06c99d732a90907e04 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Florian-BARRE
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@899aebeb0fc87e8567f2fb06c99d732a90907e04 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file configplusplus-0.3.0-py3-none-any.whl.
File metadata
- Download URL: configplusplus-0.3.0-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38c2f610b357aa39a94c0a8884d86fd36822bec4c0bcef7b47bf59cca7c1fa98
|
|
| MD5 |
f1213205b0886a9f596aec6da206d81b
|
|
| BLAKE2b-256 |
2cbeef9ae52cd66407f160e349199216be845c5487b1aec69439645ef37a5af3
|
Provenance
The following attestation bundles were made for configplusplus-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on Florian-BARRE/ConfigPlusPlus
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
configplusplus-0.3.0-py3-none-any.whl -
Subject digest:
38c2f610b357aa39a94c0a8884d86fd36822bec4c0bcef7b47bf59cca7c1fa98 - Sigstore transparency entry: 2688497383
- Sigstore integration time:
-
Permalink:
Florian-BARRE/ConfigPlusPlus@899aebeb0fc87e8567f2fb06c99d732a90907e04 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Florian-BARRE
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@899aebeb0fc87e8567f2fb06c99d732a90907e04 -
Trigger Event:
workflow_dispatch
-
Statement type: