config
Hierarchical, profile-aware configuration for Python. Supports YAML, JSON, Java
.properties, and .env files, environment variable binding with relaxed naming,
placeholder resolution, and transparent jasypt-compatible decryption — with
Spring Boot-aligned precedence.
Port of @alt-javascript/config
to Python.
Install
uv add alt-python-config # or: pip install alt-python-config
Requires Python 3.12+, PyYAML >= 6 (for YAML files), and pysypt (for
ENC(...) decryption).
Quick Start
In-memory config
from config import EphemeralConfig, ConfigFactory
# Simple lookup
cfg = EphemeralConfig({
"db": {"host": "localhost", "port": 5432},
"logging": {"level": {"/": "info"}},
})
cfg.get("db.host") # "localhost"
cfg.get("db.port") # 5432
cfg.get("missing", "def") # "def"
cfg.has("db.host") # True
# Wrap for placeholder resolution and ENC() decryption
extended = ConfigFactory.get_config(config=cfg)
Profile-based config (Spring-aligned)
from config import config # module-level singleton
port = config.get("server.port")
name = config.get("app.name", "unnamed")
The singleton discovers config files from config/ and the current working
directory. Set PY_ACTIVE_PROFILES to activate profile overlays.
Property Source Precedence
When using ProfileConfigLoader, sources are queried in this order (highest
priority first):
| Priority | Source |
|---|---|
| 1 | Programmatic overrides |
| 2 | Environment variables (os.environ) with relaxed binding |
| 3 | Profile .env files (application-{profile}.env) |
| 4 | Default .env file (application.env) |
| 5 | Profile structured files (application-{profile}.{yaml,json,properties}) |
| 6 | Default structured files (application.{yaml,json,properties}) |
| 7 | Fallback (explicit dict or config-like object) |
Real environment variables (priority 2) always win over .env file values
(priority 3–4) — .env files never override what the shell or container already
provides.
File Formats
Files are discovered in config/ first, then the current working directory.
All matching files at each level are loaded and merged.
YAML
# application.yaml
app:
name: My App
secret: ENC(Ho8XdYf6/r+FdJ/ZC55BBA==)
server:
port: 8080
JSON
{
"app": { "name": "My App" },
"server": { "port": 8080 }
}
.properties
Full Java .properties support:
# application.properties
app.name=My App
server.port=8080
security.roles[0]=USER
security.roles[1]=ADMIN
servers[0].host=web1.example.com
Supports key=value, key:value, and key value separators; #/! comment
lines; \ line continuation; standard escape sequences; and \uXXXX Unicode.
Dotted keys produce nested dicts. Array notation (key[0]=value) produces
Python lists.
.env
# application.env
DB_HOST=localhost
DB_PORT=5432
export API_KEY=abc123 # export prefix stripped
SECRET="contains spaces" # double-quoted: escapes processed
NOTE='literal \n value' # single-quoted: no escape processing
INLINE=value # this is a comment # inline comment (must follow whitespace)
.env values pass through EnvPropertySource, so relaxed binding applies:
.env key |
Config path |
|---|---|
DB_HOST |
db.host |
MY_APP_PORT |
my.app.port |
APP__NAME |
app.name (double underscore → dot) |
Profiles
Activate profiles with PY_ACTIVE_PROFILES (comma-separated):
PY_ACTIVE_PROFILES=dev,local python main.py
Or pass profiles= directly:
from config import ProfileConfigLoader
chain = ProfileConfigLoader.load(
base_path="/etc/myapp",
profiles="dev,local",
)
Profile files override defaults at the same path. Later profiles in the list override earlier ones.
Environment Variable Binding
Environment variables are available with relaxed binding:
| Environment variable | Config path |
|---|---|
SERVER_PORT |
server.port |
DB_HOST |
db.host |
APP__NAME |
app.name |
The original uppercase key is also accessible directly (config.get("SERVER_PORT")).
Placeholder Resolution
Reference other config values using ${path} or ${path:default}:
base_url: http://localhost:8080
api_url: "${base_url}/api"
config.get("api_url") # "http://localhost:8080/api"
Unresolvable placeholders are returned as-is without error.
Encrypted Values
Set PY_CONFIG_PASSPHRASE in the environment, then store encrypted values as
ENC(...) or enc.<b64>:
python -c "
from pysypt import Jasypt
print(Jasypt().encrypt('mysecret', 'mypassphrase'))
"
# application.yaml
db:
password: ENC(Ho8XdYf6/r+FdJ/ZC55BBA==)
PY_CONFIG_PASSPHRASE=mypassphrase python main.py
config.get("db.password") # "mysecret"
Both ENC(...) (parenthesis notation) and enc.<b64> (prefix notation) are
decrypted transparently on get().
API Reference
EphemeralConfig(obj)
Lightweight config backed by a plain dict.
| Method | Description |
|---|---|
get(path, default=MISSING) |
Get a value by dot-notation path. Raises KeyError if missing and no default given. |
has(path) |
Return True if the path exists, including paths with falsy values (0, False, ""). |
path uses dot notation: "server.port" traverses obj["server"]["port"].
A flat key with a literal dot (e.g. obj["a.b"]) is also accessible.
PropertySourceChain(sources)
Ordered list of config sources. Queries them in index order; first source that has the path wins.
| Method | Description |
|---|---|
has(path) |
Return True if any source has the path. |
get(path, default=MISSING) |
Return the first value found. Raises KeyError if no source has it and no default given. |
add_source(source, priority=None) |
Add a source. Lower index = higher priority. Appends to end if priority is omitted. |
EnvPropertySource(env=None)
Wraps an environment variable dict with relaxed binding.
| Method | Description |
|---|---|
has(path) |
Check by exact key or relaxed dotted form. |
get(path, default=None) |
Return value or default. |
PropertiesParser
| Method | Description |
|---|---|
PropertiesParser.parse(text) |
Parse a .properties string into a nested dict. |
DotEnvParser
| Method | Description |
|---|---|
DotEnvParser.parse(text) |
Parse a .env string into a flat {KEY: value} dict. |
ProfileConfigLoader
| Method | Description |
|---|---|
ProfileConfigLoader.load(overrides=None, fallback=None, base_path=None, profiles=None, env=None, name="application") |
Build a PropertySourceChain with full Spring-aligned precedence. |
Parameters:
| Parameter | Type | Description |
|---|---|---|
overrides |
dict |
Programmatic overrides (highest priority) |
fallback |
dict or config-like |
Fallback source (lowest priority) |
base_path |
str |
Base directory for file discovery (default: cwd) |
profiles |
str |
Comma-separated profile names (default: PY_ACTIVE_PROFILES env var) |
env |
dict |
Environment variables (default: os.environ) |
name |
str |
Config file base name (default: "application") |
ConfigFactory
| Method | Description |
|---|---|
ConfigFactory.get_config(config=None, resolver=None, password=None) |
Build a ValueResolvingConfig with the full resolver chain (placeholders, ENC() decryption). |
ConfigFactory.load_config(**kwargs) |
Convenience alias for ProfileConfigLoader.load(). |
ValueResolvingConfig(config, resolver, path=None)
Config wrapper that applies the resolver chain at get() time.
| Method | Description |
|---|---|
has(path) |
Delegates to the backing config. |
get(path, default=MISSING) |
Resolves and returns the value at path after applying all resolvers. |
Selectors
| Class | Matches | resolve_value() |
|---|---|---|
PrefixSelector(prefix) |
Values starting with prefix |
Strips prefix |
ParenthesisSelector(prefix) |
PREFIX(value) notation |
Returns the inner value |
PlaceholderSelector() |
Values containing ${...} |
n/a (used by PlaceholderResolver) |
Resolvers
| Class | Description |
|---|---|
Resolver |
Abstract base. Provides map_values_deep(values, callback). |
SelectiveResolver(selector) |
Base for resolvers that only process matching values. |
DelegatingResolver(resolvers) |
Chains multiple resolvers in sequence. |
PlaceholderResolver(selector, reference) |
Resolves ${path} and ${path:default} placeholders. |
JasyptDecryptor(selector, password) |
Decrypts enc.<b64> and ENC(<b64>) values via pysypt. |
All Exports
from config import (
EphemeralConfig,
PropertySourceChain,
EnvPropertySource,
PropertiesParser,
DotEnvParser,
ProfileConfigLoader,
Selector,
PrefixSelector,
ParenthesisSelector,
PlaceholderSelector,
Resolver,
SelectiveResolver,
DelegatingResolver,
PlaceholderResolver,
JasyptDecryptor,
ValueResolvingConfig,
ConfigFactory,
config, # module-level singleton
)
Troubleshooting
KeyError: Config path 'x.y' not found
The path does not exist in any source and no default was given. Use has("x.y")
first, or pass a default: config.get("x.y", None).
ENC(...) value is returned as-is, not decrypted
Either PY_CONFIG_PASSPHRASE is not set, or the value was encrypted with a
different password. Set the env var, or pass password= to ConfigFactory.get_config().
Profile overlay not applied
Check that PY_ACTIVE_PROFILES=dev is set in the environment, or pass
profiles="dev" to ProfileConfigLoader.load(). Verify the profile file exists
at application-dev.yaml (or .json, .properties, .env).
YAML file not loaded
PyYAML must be installed: pip install PyYAML. The loader raises ImportError
with a clear message if it encounters a .yaml file without the parser available.
.env file values not accessible via dot path
.env files are loaded via EnvPropertySource, which applies relaxed binding.
DB_HOST is accessible as db.host or DB_HOST. The exact dotted path
"db.host" only works if the variable name is DB_HOST — not db.host.
Metadata
Release files for alt-python-config 1.1.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 | Provenance | |
|---|---|---|---|---|
| alt_python_config-1.1.1.tar.gz | 18.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| alt_python_config-1.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 38.0 kB
Release files / alt_python_config-1.1.1.tar.gz
| Download URL | alt_python_config-1.1.1.tar.gz |
|---|---|
| Size | 18.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
438f8d12d977393ccd929c54b1306e7f6fb4ea5f55ec0ac0f35911acf40de315
|
|
BLAKE2b-256 checksum How to use checksums |
dc5eb5332d4f4338bbaca3a996b739797d88e27273bd01c0b86fb6107e9f7462
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / alt_python_config-1.1.1-py3-none-any.whl
| Download URL | alt_python_config-1.1.1-py3-none-any.whl |
|---|---|
| Size | 19.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
655b371b0518903701baf24a162ae03d8827fb8d9d5521348809763c79610505
|
|
BLAKE2b-256 checksum How to use checksums |
b3a978d3cb1f03516f6d4306716ee33b82c02b31be43fc969ad8abc91782c123
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for alt-python-config 1.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.