Minimal schema-agnostic configuration loader library
Project description
justconf
Minimal schema-agnostic configuration library for Python.
Provides simple, composable building blocks for configuration management:
- Loaders — fetch config from various sources (environment variables,
.envfiles, TOML) - Merge — combine multiple configs with deep merge and priority control
- Processors — resolve placeholders from external sources (HashiCorp Vault)
Schema-agnostic: use your preferred validation library (Pydantic, msgspec, dataclasses) or none at all.
Table of Contents
Installation
pip install justconf
For .env file support:
pip install justconf[dotenv]
Quick Start
from justconf import env_loader, dotenv_loader, toml_loader, merge
# Load from multiple sources and merge (later sources have higher priority)
config = merge(
toml_loader("config.toml"), # base config
dotenv_loader(".env"), # override with .env
env_loader(prefix="APP"), # highest priority: environment variables
)
# Use with your preferred validation library
from pydantic import BaseModel
class DatabaseConfig(BaseModel):
host: str
port: int = 5432
class AppConfig(BaseModel):
debug: bool = False
database: DatabaseConfig
app_config = AppConfig(**config)
With Secret Resolution
from justconf import merge, toml_loader, process
from justconf.processor import VaultProcessor, TokenAuth
# Load and merge config
config = merge(
toml_loader("config.toml"),
{"db_password": "${vault:db#password}"}, # placeholder for secret
)
# Resolve secrets from Vault
processor = VaultProcessor(
url="http://vault:8200",
auth=TokenAuth(token="hvs.xxx"),
mount_path="secret", # KV v2 secrets engine mount path
)
config = process(config, [processor])
# {"db_password": "actual_password_from_vault", ...}
Loaders
Loaders fetch configuration from various sources and return a dictionary.
-
env_loader(prefix=None, case_sensitive=False) — loads from environment variables. If
prefixis set, filters variables by prefix and strips it from keys.config = env_loader(prefix="APP") # APP_DEBUG=true, APP_PORT=8080 -> {"debug": "true", "port": "8080"}
-
dotenv_loader(path=".env", prefix=None, case_sensitive=False, encoding="utf-8") — loads from
.envfile. Requirespip install justconf[dotenv]. Supports variable interpolation (${VAR}).config = dotenv_loader(".env", prefix="APP")
-
toml_loader(path="config.toml", encoding="utf-8") — loads from TOML file using Python's built-in
tomllib. Native TOML types are preserved (int, float, bool, list, dict, datetime).config = toml_loader("config.toml")
Nested Configuration
Use double underscores (__) to create nested structures from flat environment variables:
export DATABASE__HOST=localhost
export DATABASE__PORT=5432
config = env_loader()
# {"database": {"host": "localhost", "port": "5432"}}
Merge
The merge function combines multiple dictionaries with deep merge. Later arguments have higher priority.
from justconf import merge
config = merge(
{"db": {"host": "localhost", "port": 5432}, "tags": ["a", "b"]},
{"db": {"port": 3306}, "tags": ["c"]},
)
# {"db": {"host": "localhost", "port": 3306}, "tags": ["c"]}
Merge strategy:
dict+dict→ recursive deep merge- Everything else (list, str, int, etc.) → overwrite
Processors
Processors resolve placeholders in your configuration, fetching values from external sources.
Placeholder Syntax
${processor:path#key|modifier:value}
processor— name of the processor (e.g.,vault)path— path to the secretkey— (optional) specific key within the secretmodifiers— (optional) post-processing modifiers
Placeholders can be embedded within strings:
config = {"dsn": "postgres://user:${vault:db#password}@localhost/db"}
VaultProcessor
Fetches secrets from HashiCorp Vault (KV v2).
from justconf import process
from justconf.processor import VaultProcessor, TokenAuth
processor = VaultProcessor(
url="http://vault:8200",
auth=TokenAuth(token="hvs.xxx"),
mount_path="secret", # KV v2 secrets engine mount path (required)
timeout=30, # request timeout in seconds
verify=True, # SSL verification (default: True)
)
config = {"api_key": "${vault:myapp/api#key}"}
result = process(config, [processor])
# {"api_key": "actual_key"}
Understanding Mount Path
The mount_path parameter specifies where the KV v2 secrets engine is mounted in Vault. This is a required parameter.
How to find your mount path:
- Vault UI: Go to Secrets → the engine name shown is your mount path
- Vault CLI: Run
vault secrets listto see all mounted engines
Path structure explained:
Full Vault path: secret/data/myapp/database
~~~~~~ ~~~~ ~~~~~~~~~~~~~~~
│ │ │
│ │ └── secret path (used in placeholder)
│ └── KV v2 internal prefix (added automatically)
└── mount_path (passed to VaultProcessor)
Placeholder: ${vault:myapp/database#password}
~~~~~~~~~~~~~~~
│
└── only the secret path, without mount_path
Examples with different mount paths:
from justconf.processor import VaultProcessor, TokenAuth, KubernetesAuth
# Default Vault dev server (mount path: "secret")
processor = VaultProcessor(
url="http://localhost:8200",
auth=TokenAuth(token="root"),
mount_path="secret",
)
# Custom mount path for a team
processor = VaultProcessor(
url="https://vault.company.com:8200",
auth=KubernetesAuth(role="myapp"),
mount_path="team-backend/kv",
)
SSL Verification
The verify parameter controls SSL certificate verification:
verify=True(default) — use system CA certificatesverify=False— disable SSL verification (not recommended for production)verify="/path/to/ca-bundle.crt"— use custom CA bundle
# For internal Vault with self-signed certificate
processor = VaultProcessor(
url="https://vault.internal:8200",
auth=TokenAuth(token="hvs.xxx"),
mount_path="secret",
verify="/etc/ssl/certs/internal-ca.crt",
)
Authentication Methods
VaultProcessor supports multiple Vault auth methods:
- TokenAuth(token) — direct token authentication
- AppRoleAuth(role_id, secret_id, mount_path="approle") — for AppRole automated workflows
- JwtAuth(role, jwt, mount_path="jwt") — for JWT/OIDC (GitLab CI/CD, etc.)
- KubernetesAuth(role, jwt=None, jwt_path="...", mount_path="kubernetes") — for Kubernetes pods; JWT is read from
/var/run/secrets/kubernetes.io/serviceaccount/tokenby default - UserpassAuth(username, password, mount_path="userpass") — username/password authentication
Auth Fallback Chain
Pass a list of auth methods to try them in order until one succeeds:
import os
processor = VaultProcessor(
url="http://vault:8200",
auth=[
TokenAuth(token=os.environ.get("VAULT_TOKEN", "")),
KubernetesAuth(role="myapp"),
AppRoleAuth(role_id="xxx", secret_id="yyy"),
],
mount_path="secret",
)
File Modifier
Write secrets to files instead of keeping them in memory. Useful for certificates and keys:
config = {
"tls_cert": "${vault:tls#cert|file:/etc/ssl/cert.pem}",
"tls_key": "${vault:tls#key|file:/etc/ssl/key.pem|encoding:utf-8}",
}
result = process(config, [processor])
# {"tls_cert": "/etc/ssl/cert.pem", "tls_key": "/etc/ssl/key.pem"}
# Files are created with the secret content
If the value is a dict or list, it's serialized as JSON.
Development
Debugging with a real Vault server
You can use a real Vault server to debug this project. To make this process
easier, this project includes a docker-compose.yml file that can run a
ready-to-use Vault server.
To run the server and set it up, run the following commands:
docker compose up
make vault
After that, you will have a Vault server running at http://localhost:8200, where you can authorize in three ways:
- using the root token (which is
token) - using the JWT method (role=
jwt_role, token=link) - using the AppRole method (the values of role_id and secret_id can be found in the logs of the
make vaultcommand).
License
MIT
Project details
Release history Release notifications | RSS feed
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 justconf-0.2.0a2.tar.gz.
File metadata
- Download URL: justconf-0.2.0a2.tar.gz
- Upload date:
- Size: 55.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","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 |
b14cc69d953aaf15c43cc30a2d6bd2217f1965964c72d574f90710f2b44e2fdd
|
|
| MD5 |
f093d94fba365f8fc471b5d2edebf8e8
|
|
| BLAKE2b-256 |
b5963264eeacccb81776286b07df93f8965c3fcfb1ddbcb06c15df9a13a54c4e
|
File details
Details for the file justconf-0.2.0a2-py3-none-any.whl.
File metadata
- Download URL: justconf-0.2.0a2-py3-none-any.whl
- Upload date:
- Size: 14.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","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 |
c30fb4b1a214e7be0ff578e1fcb64f89dfb3138e4ddab997b0694d6f8ceea437
|
|
| MD5 |
4aa3b606328c40987a73b46678aa2e90
|
|
| BLAKE2b-256 |
05663f387fbc9b53173d2eccb8fdd2ab4a3920f7db2f89a2bd7f47b41c7d918e
|