User Guide
Testing
Testing with a ConfigSingleton
Because a ConfigSingleton is shared across your
application, tests that modify it can affect each other. Use
_reset() in tearDown to restore
the singleton to a clean state after each test.
from unittest import TestCase
from yourmodule.conf import CFG
class MyTests(TestCase):
def tearDown(t):
CFG._reset()
def test_with_override(t):
from batconf import insert_source, NamespaceSource
from argparse import Namespace
args = Namespace()
setattr(args, 'yourmodule.server.host', 'testhost')
insert_source(cfg=CFG, source=NamespaceSource(args))
t.assertEqual(CFG.server.host, 'testhost')
def test_reads_default(t):
# CFG was reset by tearDown — reads from the real sources again
t.assertEqual(CFG.server.host, 'localhost')
Testing without a singleton
If test isolation is a concern, call get_config() directly in each
test instead of using the shared CFG. This creates a fresh
Configuration per test with no shared state.
from yourmodule.conf import get_config
class MyTests(TestCase):
def test_something(t):
cfg = get_config()
t.assertEqual(cfg.server.host, 'localhost')
Custom Configuration Sources
Any object with a get(key, path) method satisfies
SourceInterfaceP,
so you can pull config values from any backend — a secrets manager,
a database, a remote API — without changing the rest of your config setup.
Using the Protocol (structural subtyping)
The simplest approach: implement get on any class, no base class required.
class VaultSource:
def __init__(self, client):
self._client = client
def get(self, key: str, path: str | None = None) -> str | None:
full_key = f'{path}.{key}' if path else key
return self._client.read(full_key)
Subclassing SourceInterface
Subclassing SourceInterface
gives you the ABC enforcement and is useful if you want type checkers to
flag incomplete implementations.
from batconf.source import SourceInterface
class VaultSource(SourceInterface):
def __init__(self, client):
self._client = client
def get(self, key: str, path: str | None = None) -> str | None:
full_key = f'{path}.{key}' if path else key
return self._client.read(full_key)
Registering a custom source
Pass your custom source to SourceList like any
built-in source, or add it at runtime with insert_source():
from batconf import insert_source
from yourmodule.conf import CFG
insert_source(cfg=CFG, source=VaultSource(vault_client))
The source is inserted at index 0 by default, giving it the highest
priority. Pass index= to place it elsewhere in the lookup order.
Important constraints
getmust return astrorNone— never a non-string value. Some sources (e.g. environment variables) can only store strings, and BatConf treats any falsey return value (False,None,"") as “not found”.Keep
getfast and side-effect-free — it is called on every config lookup, not cached by BatConf itself.