Skip to main content

Platform-aware configuration management (CLI flags + env + YAML/JSON deep-merge)

Project description

Confumo

Confumo derives from "Confuse more". It's a thin Python module that merges command-line flags, environment variables, and YAML/JSON files into a single, deep-merged configuration object. The object is always a singleton per application name and can be exposed at module scope so existing code keeps working.

One line to register your flags, one line to expose the config to the rest of your project.

Highlights

Description
Merged sources argparse • ENV vars • YAML/JSON • defaults
Singleton registry Only one ArgumentParser run per app_name; mismatched config classes raise instead of silently reusing the wrong singleton
Auto flag inheritance Base class + mix-ins + leaf class all show up in --help
Lazy “print-once” help Libraries defer --help; root application prints merged help
Module-level proxy from my_app.config import log_level still works; alias collisions raise unless explicitly replaced
Deep-merge Powered by confuse
Per-key source lookup config.get_source("log_level") reports the winning CLI/env/YAML/default source
Cross-platform paths Uses platformdirs

Installation

pip install confumo

1 · Define a config class

# ui_layer/config.py
import argparse
from confumo import Confumo

class UIConfig(Confumo):
    def add_args(self, p: argparse.ArgumentParser):
        p.add_argument('--theme', choices=['light', 'dark', 'auto'], default='auto')
        p.add_argument('--font', default='Roboto')

    def _init_subclass(self):
        self.theme = self.args.theme
        self.font = self.cfg['font'].get(str)

    def to_dict(self):
        return {'theme': self.theme, 'font': self.font}

2 · Expose it (library mode)

UIConfig is meant to be reused by many apps, so the library defers --help printing:

# ui_layer/config.py  (bottom of file)
config = Confumo.expose(
    'ui_layer',
    UIConfig,
    globals(),
    root=False,
)

Now any package can do:

from ui_layer import config
print(config.theme)

and the singleton is shared process-wide.

3 · Extend it in an application

# cool_app/config.py
import argparse
from confumo import Confumo
from ui_layer.config import UIConfig

class CoolAppConfig(UIConfig):
    def add_args(self, p: argparse.ArgumentParser):
        p.add_argument('--log_level', default='INFO',
                       choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'])
        p.add_argument('--library_path', default='~/Pictures')

    def _init_subclass(self):
        super()._init_subclass()
        self.log_level = self.cfg['log_level'].get(str)
        self.library_path = self.cfg['library_path'].get(str)

    def to_dict(self):
        base = super().to_dict()
        base.update({'log_level': self.log_level,
                     'library_path': self.library_path})
        return base

config = Confumo.expose(
    'cool_app',
    CoolAppConfig,
    globals(),
)

Running the application:

python cool_app/main.py --help

prints all flags exactly once.

Confumo collects add_args() methods across the config class MRO, so a subclass does not have to call super().add_args(p) to inherit parent flags. Calling super().add_args(p) is also safe: exact duplicate inherited argument registrations are ignored, while real conflicting option definitions still raise argparse errors.

4 · Access the config

from cool_app import config
print(config.library_path)

from confumo import Confumo
cfg = Confumo.get('cool_app', CoolAppConfig)

Both lines refer to the same singleton.

Safety checks

Confumo keys singleton instances by app_name, but it also validates the requested concrete config class on registry hits. If Confumo.get('demo', DemoConfig) created the singleton first, a later Confumo.get('demo', OtherConfig) raises ConfumoSingletonTypeError instead of returning a config object with the wrong shape. Requesting a base class remains valid when the existing singleton is an instance of that base class.

Confumo.expose(..., publish_to=(...)) also refuses to replace an existing Confumo-managed module proxy by default. Publish targets that are already imported are preflighted before the requested config singleton is constructed, so an unsafe cross-package alias is rejected before the importing package can run its own config bootstrap. This prevents one package from silently repointing another package's config, __getattr__, and __dir__ at an unrelated singleton. Use replace_existing=True only for an intentional compatibility shim where that replacement has been audited.

Precedence order

  1. CLI flags (--foo bar)
  2. Environment variables (COOL_APP_FOO=bar; nested keys use __, e.g. COOL_APP_DATABASE__HOST=localhost)
  3. YAML file (-c settings.yml) or default path
  4. Defaults hard-coded in your subclass

confuse handles deep-merging dicts and lists.

Debug where a value came from

get_source() reports the winning source for a key after Confumo applies its normal precedence rules:

source = config.get_source('log_level')
# ConfigSource(kind='env', key='COOL_APP_LOG_LEVEL', origin='<process env>')

source = config.get_source('theme')
# ConfigSource(kind='cli', key='--theme', origin=None)

The API is deliberately winner-only. It answers “why is this value currently what it is?” without exposing the full loader history. Dotted paths are accepted for nested values, for example config.get_source('database.host'). Missing keys return None.

Persist the merged config

path = config.save_yaml()  # ~/.config/cool_app/cool_app_config.yaml (Linux)

The file contains the fully merged state for later inspection.

Testing helpers

config.copy() returns a detached clone whose built-in mutable containers and argparse namespace state do not alias the singleton. Runtime objects that cannot be copied, such as GUI widgets stored as leaf values, are preserved by reference.

License

This repository ships under 0BSD; see LICENSE.

Licensing / provenance notice

This repository may include AI-generated material and minimal human-authored material. To the extent any portion of this repository is not copyrightable, it is made available without restriction.

No third-party source or license texts are currently bundled in-tree. Any third-party material added later remains subject to its original terms.

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

confumo-0.2.2.tar.gz (22.9 kB view details)

Uploaded Source

Built Distribution

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

confumo-0.2.2-py3-none-any.whl (12.3 kB view details)

Uploaded Python 3

File details

Details for the file confumo-0.2.2.tar.gz.

File metadata

  • Download URL: confumo-0.2.2.tar.gz
  • Upload date:
  • Size: 22.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for confumo-0.2.2.tar.gz
Algorithm Hash digest
SHA256 40c9bba7958fee0fab2e04209997b1309a1e0ae1ff83e590139e95eaf198974b
MD5 0f66e11b285d8c18a6eed4f978def9a2
BLAKE2b-256 b8e6c99e48d0fad3451b2f9d89ca3b808383bb53ff9777ef0cc6f955b3b0f921

See more details on using hashes here.

File details

Details for the file confumo-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: confumo-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 12.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for confumo-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b718e693ca9fcc264eee57dbdfbafd7d1fdf1e8dd343a74e96f38d294e20fd95
MD5 c27b377661c12977c84ecef9561538cc
BLAKE2b-256 0480e1ca1e24e9950a8e3d959fdb28ca2ac5d540876bfc381f99d0e9eae4f40e

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