Skip to main content

Layered configuration loader merging env vars, files, and defaults.

Project description

philiprehberger-config-kit

Tests PyPI version Last updated

Layered configuration loader merging env vars, files, and defaults.

Installation

pip install philiprehberger-config-kit

Usage

from philiprehberger_config_kit import Config

config = Config(
    sources=[
        Config.defaults({"port": 3000, "debug": False, "log_level": "info"}),
        Config.json_file("config.json", optional=True),
        Config.env_file(".env", optional=True),
        Config.env(prefix="APP_"),
    ]
)

# Typed access
port = config.get_int("port")
debug = config.get_bool("debug")
db_url = config.get_str("database_url")
timeout = config.get_float("timeout", default=5.0)
hosts = config.get_list("allowed_hosts")

Dot-Notation Access

Retrieve nested values using dot-separated keys:

from philiprehberger_config_kit import Config

config = Config(
    sources=[
        Config.defaults({
            "database": {"host": "localhost", "port": 5432},
            "cache": {"redis": {"url": "redis://localhost"}},
        }),
    ]
)

host = config.get("database.host")           # "localhost"
port = config.get_int("database.port")        # 5432
url = config.get_str("cache.redis.url")       # "redis://localhost"

Source Priority

Sources are applied in order -- later sources override earlier ones:

from philiprehberger_config_kit import Config

config = Config(sources=[
    Config.defaults({...}),       # lowest priority
    Config.json_file("..."),      # overrides defaults
    Config.env_file(".env"),      # overrides JSON
    Config.env(prefix="APP_"),    # highest priority
])

Schema Validation

Define expected keys, types, and allowed values, then validate:

from philiprehberger_config_kit import Config, ConfigSchema, SchemaError

config = Config(sources=[
    Config.defaults({"host": "localhost", "port": 5432, "mode": "dev"}),
])

schema = ConfigSchema()
schema.required("host", str)
schema.required("port", int)
schema.optional("debug", bool)
schema.required("mode", str, choices=["dev", "prod", "test"])

config.validate(schema)  # raises SchemaError with all failures listed

Reload

Refresh configuration from all sources at runtime:

from philiprehberger_config_kit import Config

config = Config(sources=[Config.env(prefix="APP_")])
port = config.get("port")

# After environment changes...
config.reload()
port = config.get("port")  # picks up the new value

Change Listeners

Get notified when a value changes after reload():

from philiprehberger_config_kit import Config

config = Config(sources=[Config.json_file("config.json")])

def log_change(key, old, new):
    print(f"{key}: {old!r} -> {new!r}")

unsubscribe = config.on_change(log_change)

# Later, after the source file changes on disk:
config.reload()
# log_change is called once per dotted key whose value changed

unsubscribe()  # stop listening

Export Methods

from philiprehberger_config_kit import Config

config = Config(sources=[
    Config.defaults({"db": {"host": "localhost", "port": 5432}, "debug": True}),
])

# Deep copy as nested dict
data = config.to_dict()
# {"db": {"host": "localhost", "port": 5432}, "debug": True}

# Flat dict with dot-notation keys (string values)
flat = config.flatten()
# {"db.host": "localhost", "db.port": "5432", "debug": "True"}

# Environment variable format (UPPER_SNAKE_CASE, string values)
env = config.to_env(prefix="APP")
# {"APP_DB_HOST": "localhost", "APP_DB_PORT": "5432", "APP_DEBUG": "True"}

Config Snapshot Diffing

Capture config state and compare snapshots to see what changed:

from philiprehberger_config_kit import Config

config = Config(sources=[
    Config.defaults({"host": "localhost", "port": 3000}),
    Config.env(prefix="APP_"),
])

before = config.snapshot()

# ... environment changes, then reload ...
config.reload()
after = config.snapshot()

diff = before.diff(after)
# {"added": {...}, "removed": {...}, "changed": {"port": {"old": "3000", "new": "8080"}}}

Typed List Getters

Parse comma-separated values into typed lists:

from philiprehberger_config_kit import Config

config = Config(sources=[
    Config.defaults({"ports": "8080,8081,8082", "rates": "1.5,2.0,3.7"}),
])

ports = config.get_int_list("ports")      # [8080, 8081, 8082]
rates = config.get_float_list("rates")    # [1.5, 2.0, 3.7]

# Custom separator
config2 = Config(sources=[Config.defaults({"ids": "1|2|3"})])
config2.get_int_list("ids", sep="|")      # [1, 2, 3]

Environment Variables

With prefix="APP_", env vars are mapped:

  • APP_PORT -> port
  • APP_DATABASE__HOST -> database.host (double underscore = nested)

Bool Coercion

get_bool() accepts: true/false, 1/0, yes/no, on/off

API

Function / Class Description
Config(sources) Layered configuration with typed access
Config.get(key, default) Get a value by key with dot-notation support
Config.get_str(key, default) Get a string value
Config.get_int(key, default) Get an integer value
Config.get_float(key, default) Get a float value
Config.get_bool(key, default) Get a boolean value with coercion
Config.get_list(key, separator, default) Get a list by splitting a string value
Config.get_int_list(key, sep) Split a string value and convert each element to int
Config.get_float_list(key, sep) Split a string value and convert each element to float
Config.require(*keys) Raise ConfigError if any keys are missing
Config.has(key) Check if a key exists
Config.validate(schema) Validate config against a ConfigSchema
Config.reload() Reload configuration from all sources
Config.on_change(callback) Register (key, old, new) listener; returns unsubscribe
Config.to_dict() Return a deep copy as a nested dictionary
Config.to_env(prefix) Export as UPPER_SNAKE_CASE environment variable pairs
Config.flatten(prefix) Export as flat dict with dot-notation keys
Config.snapshot() Capture current state as a ConfigSnapshot
Config.freeze() Freeze the config to prevent mutation
ConfigSchema Define expected keys, types, required/optional, and choices
ConfigSchema.required(key, type, choices) Add a required field to the schema
ConfigSchema.optional(key, type, choices) Add an optional field to the schema
ConfigSnapshot Immutable snapshot of config state
ConfigSnapshot.diff(other) Compare two snapshots and return added/removed/changed keys
ConfigError(missing) Raised when required config keys are missing
SchemaError(errors) Raised when config values fail schema validation

Development

pip install -e .
python -m pytest tests/ -v

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

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

philiprehberger_config_kit-0.4.0.tar.gz (14.0 kB view details)

Uploaded Source

Built Distribution

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

philiprehberger_config_kit-0.4.0-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

Details for the file philiprehberger_config_kit-0.4.0.tar.gz.

File metadata

File hashes

Hashes for philiprehberger_config_kit-0.4.0.tar.gz
Algorithm Hash digest
SHA256 3dbe4631ae16c20604f30dc6f82e6dc353955d23eb5d1badf20c8a6e85657975
MD5 c806ba02cab0feb6b79d09f18dc1d9e9
BLAKE2b-256 ae29aee698947f097ffe68af5508fb9e4e51f4bc9a6a650067942a921d0c4a94

See more details on using hashes here.

File details

Details for the file philiprehberger_config_kit-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for philiprehberger_config_kit-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6ec37994c1ccfc7865f83405bcdb99f12bb3af087dc016ddda1dace2a865b09b
MD5 090be91dae850fb5f0eee689e188a9bb
BLAKE2b-256 a52974afe648caacb87d1a65ec5d897dc79cb965842e50c2034ef57fd86e0fa0

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