ddp_utils.config

import ddp_utils.config

Best-effort configuration parsing, INI loading, environment overrides and watching.

Parsing is heuristic, not schema validation. Check the documented edge cases before accepting untrusted input or depending on exact preservation of strings.

Examples

Use this public operation:

import ddp_utils.config
ddp_utils.config.smart_parse(value: str) → Any

Convert a stripped configuration string using ordered heuristics.

Case-insensitive true/false and null/none are recognized before numbers. Current boolean checks use substring membership: nonempty substrings of ‘true’ become True, then substrings of ‘false’ become False. This is not strict boolean parsing; use to_bool for explicit accepted tokens.

Integer/decimal/exponent forms become numbers. Braced/bracketed values use parse_json_like; comma-and-colon text is tried as a dictionary; remaining comma text becomes a list with empty parts omitted. Finally matching outer quotes are removed, or the stripped text is returned. Bare yes/no/on/off stay strings; 1/0 are integers. Quote removal happens after comma splitting.

Parameters

Name

Type

Description

value

str

Input string; whitespace-only input returns ‘’.

Returns

Type

Description

Any

Parsed scalar/list/dict or string. This does not evaluate Python code.

Examples

Use this public operation:

result = ddp_utils.config.smart_parse(value=value_value)
ddp_utils.config.parse_json_like(s: str) → dict | list | str

Parse JSON-like text after heuristic textual normalization.

Replaces all single quotes with double quotes, quotes bare word keys, and replaces True/False/None tokens. These substitutions are not quote-aware and can alter quoted contents. Successful JSON is passed to normalize_parsed; JSONDecodeError falls back to parse_custom_structure on the original input.

Parameters

Name

Type

Description

s

str

JSON-like input string, including braced mappings and bracketed lists.

Returns

Type

Description

dict | list | str

Parsed and normalized object, or a best-effort structure/string fallback. Scalar JSON can also return a scalar despite the narrower annotation.

Examples

Use this public operation:

result = ddp_utils.config.parse_json_like(s=s_value)
ddp_utils.config.parse_custom_structure(s: str) → dict | list | str

Parse braced dictionaries or bracketed lists without requiring valid JSON.

Parameters

Name

Type

Description

s

str

String stripped before parsing. Top-level commas are split with split_preserve_brackets. Dict entries without a colon are skipped; keys lose matching quotes; values pass through smart_parse.

Returns

Type

Description

dict | list | str

Dict/list for recognized delimiters, {} or [] for empty structures, otherwise the stripped original string. Duplicate dict keys use the last value.

Examples

Use this public operation:

result = ddp_utils.config.parse_custom_structure(s=s_value)
ddp_utils.config.split_preserve_brackets(text: str, delimiter: str = ',') → list

Split on a character outside quotes and nested brackets, stripping parts.

Parameters

Name

Type

Description

text

str

Input string. Bracket balance is tracked but not validated.

delimiter

str

One-character separator; multi-character values never match. Quoted text and (), [] and {} protect enclosed separators.

Returns

Type

Description

list

List of stripped pieces. Consecutive/leading delimiters produce empty pieces; a final delimiter produces no extra trailing piece. Empty text returns []. Escaped quotes use a simple preceding-backslash check.

Examples

Use this public operation:

result = ddp_utils.config.split_preserve_brackets(text=text_value)
ddp_utils.config.normalize_parsed(obj: Any) → Any

Recursively normalize dict values, list items and scalar strings.

Parameters

Name

Type

Description

obj

Any

Parsed object. Dict keys and non-string scalars are unchanged. Matching outer quotes are removed from strings; case-insensitive true/false/null become booleans/None, then float (with a dot) or int conversion is attempted. ‘none’ is not treated as null here.

Returns

Type

Description

Any

New dict/list containers with converted values, or the normalized scalar. Exponent-only strings such as ‘1e2’ remain strings without a decimal point.

Examples

Use this public operation:

result = ddp_utils.config.normalize_parsed(obj=obj_value)
ddp_utils.config.cnf_get_all(ini: str | bytes | PathLike, path_to_merge: str | bytes | PathLike | None = None) → Dict[str, Any]

Read an INI file without interpolation and parse values with smart_parse.

Section and option names are stripped, lowercased and have spaces replaced with underscores. DEFAULT values are also inherited by ordinary sections according to ConfigParser behavior. Missing files yield {‘default’: {}}.

Parameters

Name

Type

Description

ini

str | bytes | PathLike

Filename accepted by ConfigParser.read; decoding follows its default.

path_to_merge

str | bytes | PathLike | None

Compatibility argument, currently ignored.

Returns

Type

Description

Dict[str, Any]

Mapping of normalized section names to parsed option dictionaries, always including ‘default’. INI parsing errors propagate.

Examples

Use this public operation:

result = ddp_utils.config.cnf_get_all(ini=ini_value)
ddp_utils.config.to_bool(value: Any, default: T = False) → bool | T

Safely convert config values to bool.

Supports:

  • bool

  • int / float

  • strings: true/false/1/0/yes/no/on/off

The default value is intentionally generic. This allows strict callers to pass default=None and detect invalid values instead of silently falling back to False.

Parameters

Name

Type

Description

value

Any

raw config value.

default

T

fallback if value is None or unrecognized.

Returns

Type

Description

bool | T

bool if conversion succeeds, otherwise default.

Examples

Use this public operation:

result = ddp_utils.config.to_bool(value=value_value)
ddp_utils.config.to_int(value: Any, default: int = 0) → int

Coerce runtime value to int with safe fallback.

Parameters

Name

Type

Description

value

Any

raw value.

default

int

fallback if conversion fails.

Returns

Type

Description

int

int

Examples

Use this public operation:

result = ddp_utils.config.to_int(value=value_value)
ddp_utils.config.to_float(value: Any, default: float = 0.0) → float

Coerce runtime value to float with safe fallback.

Parameters

Name

Type

Description

value

Any

raw value.

default

float

fallback if conversion fails.

Returns

Type

Description

float

float

Examples

Use this public operation:

result = ddp_utils.config.to_float(value=value_value)
ddp_utils.config.path_list_to_str(obj: Any, key: str) → Any

Recursively mutate matching values in dict/list containers via os.path.join.

Despite the function name, the implementation passes the value as ONE argument rather than unpacking a list of path components. A list-valued matching key therefore raises TypeError; a string/path-like value works.

Parameters

Name

Type

Description

obj

Any

Dict/list to mutate; other objects pass through unchanged.

key

str

Exact dictionary key whose value is passed to os.path.join.

Returns

Type

Description

Any

Same input object after recursive mutation; earlier changes remain on failure.

Examples

Use this public operation:

result = ddp_utils.config.path_list_to_str(obj=obj_value, key=key_value)
ddp_utils.config.load_project_manifest(manifest_dir: str | bytes | PathLike, filename: str = 'manifest.cfg') → Dict[str, Any] | None

Load an INI manifest and separate defaults from named sections.

Returns None when the target is missing or not a regular file. Otherwise cnf_get_all supplies the data for this structure:

{
    "default": {...},
    "sections": {
        "section1": {...},
        "section2": {...}
    },
    "options": {...},   # default keys plus nested section dictionaries
    "path": "<absolute manifest path>"
}

Parameters

Name

Type

Description

manifest_dir

str | bytes | PathLike

Directory containing the manifest.

filename

str

Manifest name, default ‘manifest.cfg’.

Returns

Type

Description

Dict[str, Any] | None

Dict with default, sections, options and path keys, or None. Section names override colliding default keys in options; options does not flatten the fields of named sections. Read/parse errors propagate.

Examples

Use this public operation:

result = ddp_utils.config.load_project_manifest(manifest_dir=manifest_dir_value)
ddp_utils.config.cnf_apply_env(cfg: Dict[str, Any], prefix: str = '', separator: str = '__') → Dict[str, Any]

Apply environment variables over cfg, mutating it in place.

Names are matched case-insensitively against prefix plus separator. Remaining names are lowercased and split ONCE: SECTION__KEY or a top-level KEY. Any additional separators remain in the field name. Values use smart_parse.

Parameters

Name

Type

Description

cfg

Dict[str, Any]

Destination dict; a nested target section must already be a dict or absent.

prefix

str

Application prefix; an empty string applies ALL environment variables.

separator

str

Nonempty separator string, default ‘__’.

Returns

Type

Description

Dict[str, Any]

Same cfg object. Invalid separator or incompatible section values can raise; updates already applied are not rolled back.

Examples

Apply a prefixed environment variable to an existing configuration:

import os
from unittest.mock import patch

from ddp_utils.config import cnf_apply_env

config = {"database": {"host": "localhost"}}
with patch.dict(os.environ, {"APP__DATABASE__HOST": "db.example.com"}):
    result = cnf_apply_env(config, prefix="APP")

assert result is config
assert config["database"]["host"] == "db.example.com"
class ddp_utils.config.WatchedConfig(ini_path: str | PathLike, prefix: str = '', on_reload: Any | None = None)

Bases: object

Load INI configuration and optionally watch file-modified events with watchdog.

Without the separately installed watchdog dependency, the initial data is retained without live watching. The implementation handles modification events for the exact path, not every possible atomic-replace/save pattern.

Examples

Load a configuration and always stop its optional watcher:

from pathlib import Path
from tempfile import TemporaryDirectory

from ddp_utils.config import WatchedConfig

with TemporaryDirectory() as directory:
    path = Path(directory, "config.ini")
    path.write_text("[database]\nhost = localhost\n", encoding="utf-8")
    config = WatchedConfig(path)
    try:
        assert config.get("database", "host") == "localhost"
    finally:
        config.stop()

Load the INI file immediately, invoke the callback and attempt watching.

Parameters

Name

Type

Description

ini_path

str | PathLike

INI filename, resolved to an absolute path.

prefix

str

Nonempty prefix applies cnf_apply_env after each read. An empty prefix skips environment overrides entirely here.

on_reload

Any | None

Optional callback fn(data), invoked after the initial load and each successful reload. Ordinary callback exceptions are swallowed. The callback receives the live dictionary.

Note

Missing watchdog silently disables watching. Other startup errors may propagate. Construction immediately loads the file and attempts to start watching; it is not a lazy configuration descriptor.

stop() → None

Request watcher stop and forget it; retain the last loaded data.

Returns

Type

Description

None

None. Repeated calls are harmless; this method does not join the watcher thread.

Examples

Use this public operation:

result = instance.stop()
get(*keys: str, default: Any = None) → Any

Traverse keys in the current configuration snapshot.

Parameters

Name

Type

Description

*keys

str

Exact dictionary keys, e.g. (‘database’, ‘host’). No keys returns the live data dictionary, not a copy.

default

Any

Used when lookup fails or a non-dict blocks traversal. A dict default may itself be traversed by subsequent keys.

Returns

Type

Description

Any

Referenced value or default; nested mutable values are not copied.

Examples

Read nested and missing values from a watched configuration:

host = config.get("database", "host", default="localhost")
debug = config.get("debug", default=False)
all() → Dict[str, Any]

Return a shallow top-level copy; nested dictionaries remain shared.

Returns

Type

Description

Dict[str, Any]

A new top-level dictionary containing the current configuration. Mutating a nested mutable value also affects the stored snapshot.

Examples

Obtain a top-level copy without exposing the original mapping:

snapshot = config.all()
snapshot["debug"] = True
ddp_utils.config.cnf_merge(*sources: str | PathLike | Dict[str, Any], prefix: str = '') → Dict[str, Any]

Merge configuration sources in order, then apply prefixed environment values.

Later sources override earlier ones; nested dictionaries are merged recursively.

Parameters

Name

Type

Description

*sources

str | PathLike | Dict[str, Any]

INI paths or dictionaries.

prefix

str

Nonempty prefix enables cnf_apply_env; empty skips it.

Returns

Type

Description

Dict[str, Any]

New top-level dictionary. Newly assigned nested values are shared with source dictionaries; later recursive merges can therefore mutate those shared nested dictionaries. This is not a deep-copy merge.

Examples

Merge dictionaries from left to right:

from ddp_utils.config import cnf_merge

result = cnf_merge(
    {"database": {"host": "localhost", "port": 5432}},
    {"database": {"host": "db.example.com"}},
)
assert result["database"] == {
    "host": "db.example.com",
    "port": 5432,
}