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

In-Memory Dict Source

Layer programmatic overrides on top of file/env sources without writing to disk. Both flat and nested forms work:

from philiprehberger_config_kit import Config

config = Config([
    Config.defaults({"port": 8080}),
    Config.dict_source({"db": {"host": "x", "port": 5432}}),  # nested
    Config.json_file("config.json", optional=True),           # overrides above
])

Runtime Mutations with set()

Config.set(key, value) updates a value at runtime and fires on_change listeners — only when the value actually changes.

config.on_change(lambda key, old, new: print(f"{key}: {old} -> {new}"))

config.set("db.host", "new-host")
# db.host: localhost -> new-host

config.set("db.host", "new-host")  # same value, listener does NOT fire

API

Function / Class Description
Config(sources) Layered configuration with typed access
Config.dict_source(values) Create an in-memory dict source (flat or nested)
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.set(key, value) Set a value at runtime; fires on_change listeners on real change
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.5.0.tar.gz (15.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.5.0-py3-none-any.whl (10.5 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for philiprehberger_config_kit-0.5.0.tar.gz
Algorithm Hash digest
SHA256 764c19a45cf523e463d4c857be2b6b45705613f257d3347f2c405201fb3bdea5
MD5 9ade5e99dfc1842fa2a421156092d743
BLAKE2b-256 9a4b34ff55622f2b0bff98941dcd2bd0a9cb46d676b25a91ea60e3e5cc2c2b91

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for philiprehberger_config_kit-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6dce51964c018d7c7923062397743cbb05e352681a4ff52e2a23a3f85ce2a4dd
MD5 9b97219f644a961ff748360a491d6c03
BLAKE2b-256 dfe0eebb9273f597927fa80b0054cc3d1d914108da03ea8d7d216f315541c772

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