Spring-inspired profile-aware config for Python
Project description
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.
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 alt_python_config-1.1.1.tar.gz.
File metadata
- Download URL: alt_python_config-1.1.1.tar.gz
- Upload date:
- Size: 18.3 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
438f8d12d977393ccd929c54b1306e7f6fb4ea5f55ec0ac0f35911acf40de315
|
|
| MD5 |
0fc1c6dbbb424f6972017e4912850007
|
|
| BLAKE2b-256 |
dc5eb5332d4f4338bbaca3a996b739797d88e27273bd01c0b86fb6107e9f7462
|
File details
Details for the file alt_python_config-1.1.1-py3-none-any.whl.
File metadata
- Download URL: alt_python_config-1.1.1-py3-none-any.whl
- Upload date:
- Size: 19.7 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
655b371b0518903701baf24a162ae03d8827fb8d9d5521348809763c79610505
|
|
| MD5 |
96e72d02cd6cf4c3e6ea69df4db74635
|
|
| BLAKE2b-256 |
b3a978d3cb1f03516f6d4306716ee33b82c02b31be43fc969ad8abc91782c123
|