Skip to main content

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, .env files, 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 prefix is 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 .env file. Requires pip 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 secret
  • key — (optional) specific key within the secret
  • modifiers — (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 list to 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 certificates
  • verify=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/token by 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 vault command).

License

MIT

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

justconf-0.2.0a2.tar.gz (55.2 kB view details)

Uploaded Source

Built Distribution

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

justconf-0.2.0a2-py3-none-any.whl (14.2 kB view details)

Uploaded Python 3

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

Hashes for justconf-0.2.0a2.tar.gz
Algorithm Hash digest
SHA256 b14cc69d953aaf15c43cc30a2d6bd2217f1965964c72d574f90710f2b44e2fdd
MD5 f093d94fba365f8fc471b5d2edebf8e8
BLAKE2b-256 b5963264eeacccb81776286b07df93f8965c3fcfb1ddbcb06c15df9a13a54c4e

See more details on using hashes here.

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

Hashes for justconf-0.2.0a2-py3-none-any.whl
Algorithm Hash digest
SHA256 c30fb4b1a214e7be0ff578e1fcb64f89dfb3138e4ddab997b0694d6f8ceea437
MD5 4aa3b606328c40987a73b46678aa2e90
BLAKE2b-256 05663f387fbc9b53173d2eccb8fdd2ab4a3920f7db2f89a2bd7f47b41c7d918e

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