inherit¶
Hierarchical Configuration Inheritance Pattern
This module provides a DRY (Don’t Repeat Yourself) solution for configuration management by implementing inheritance patterns similar to object-oriented programming, but for JSON-like data structures.
Problem It Solves
When managing configurations for multiple environments (dev, staging, prod), you often need to repeat common settings across environments. This leads to duplication and maintenance overhead.
Solution
Use a special _defaults section to define default values that automatically
inherit to other sections, while allowing environment-specific overrides.
How It Works
The _defaults section contains JSON path patterns that specify where default
values should be applied. Values are only set if they don’t already exist
(no overwriting).
Basic Example
Input configuration:
{
"_defaults": {
"*.username": "root", # Apply to all environments
"*.memory": 2 # Default memory allocation
},
"dev": {
"password": "dev123" # Dev-specific setting
},
"prod": {
"password": "prod456", # Prod-specific setting
"memory": 8 # Override default memory
}
}
After applying inheritance, becomes:
{
"dev": {
"username": "root", # Inherited from _defaults
"password": "dev123", # Original value
"memory": 2 # Inherited from _defaults
},
"prod": {
"username": "root", # Inherited from _defaults
"password": "prod456", # Original value
"memory": 8 # Override (not replaced)
}
}
JSON Path Patterns
*.field: Apply to all top-level keys (except _defaults)env.field: Apply to specific environment*.db.*.port: Apply to nested structures with wildcardsenv.services.port: Apply to specific nested path
Key Features
Non-destructive: Existing values are never overwritten
Recursive: Supports nested _defaults sections for fine-grained control
Flexible: Works with dictionaries and lists of dictionaries
Order-aware: Evaluation order matters for overlapping patterns
- configcraft.inherit.DEFAULTS = '_defaults'¶
Special key used to define default values that can be inherited by other configuration sections.
- configcraft.inherit.make_type_error(prefix: str, key: str) TypeError[source]¶
Create a descriptive TypeError when trying to set a value on incompatible data types.
This helper creates user-friendly error messages when the inheritance process encounters data that isn’t a dict or list of dicts, which are the only structures that support key assignment.
- Parameters:
prefix – The JSON path prefix where the error occurred
key – The key we were trying to set
- Raises:
TypeError – with descriptive message about the invalid operation
- configcraft.inherit.inherit_value(path: str, value: Any, data: Dict[str, Any] | List[Dict[str, Any]], _prefix: str | None = None) None[source]¶
Apply a default value to a JSON path pattern, preserving existing values.
This is the core inheritance mechanism that implements setdefault-like behavior for nested configuration structures. Like dict.setdefault(), it only sets values where keys don’t already exist, never overwriting existing configuration.
What it does
Follows JSON path patterns like
"*.username"or"dev.database.port"Sets values only where they’re missing (non-destructive)
Handles wildcards (*) to apply to multiple targets
Works with nested dicts and lists of dicts
Examples
Path
"*.memory"-> Setsmemory=2in all top-level environmentsPath
"dev.db.port"-> Setsport=5432only indev.dbPath
"*.servers.*.cpu"-> Setscpu=1in all servers across all environments
- Parameters:
path – JSON path pattern (e.g.,
"*.username","dev.db.port")value – The default value to set
data – Configuration dict/list to modify in-place
_prefix – Internal recursion parameter (do not use)
- Raises:
ValueError – If path ends with “*” (incomplete path)
TypeError – If trying to set values on incompatible data types
- Returns:
None
Important
The input param
datawill be modified in-place. If you want to keep the original data, do this before calling this function:import copy new_data = copy.deepcopy(data) inherit_value(path, value, new_data)
- configcraft.inherit.apply_inheritance(data: dict[str, Any]) None[source]¶
Transform configuration data by applying all
_defaultsinheritance rules.This is the main entry point that processes an entire configuration structure, finding all _defaults sections and applying their inheritance rules to create the final resolved configuration.
What it does:
Recursively processes nested _defaults sections (deeper ones override shallower ones)
Applies each JSON path pattern in the _defaults section in definition order
Removes all _defaults sections from the final output
Modifies the input data in-place
Path Execution Order Within Same _defaults:
Within a single _defaults section, paths are processed from top to bottom. If multiple paths affect the same node, the earlier path takes effect due to setdefault behavior. This enables powerful exception-then-default patterns.
Example - setting defaults with specific exceptions:
{ "_defaults": { "*.servers.blue.cpu": 4, # Exception: blue gets 4 CPU "*.servers.*.cpu": 2 # Default: all others get 2 CPU }, "env": { "servers": { "blue": {}, # Gets cpu=4 (from first rule) "green": {} # Gets cpu=2 (from second rule) } } }
The exception must be defined before the wildcard pattern to take effect.
Child _defaults Overrides Parent _defaults:
Each nested object can have its own _defaults section. When both parent and child _defaults sections would affect the same node, the child wins due to processing order (children processed before parents).
Example - nested inheritance hierarchy:
{ "_defaults": { "*.servers.*.memory": 1024 # Parent default }, "env": { "servers": { "_defaults": { "*.memory": 2048 # Child override }, "web": {} # Gets memory=2048 (child wins) } } }
This design allows fine-grained control where specific sections can override broader defaults while maintaining the inheritance hierarchy.
Basic Example:
>>> data = { ... "_defaults": { ... "*.memory": 2 ... }, ... "dev": {}, ... "prod": { ... "memory": 8 ... } ... } >>> apply_inheritance(data) >>> data { "dev": {"memory": 2}, # Inherited default "prod": {"memory": 8} # Kept existing value }
- Parameters:
data – Configuration dictionary with _defaults sections to process
Important
The input param
datawill be modified in-place, all _defaults sections will be removed and their rules applied. If you want to keep the original data, do this before calling this function:import copy new_data = copy.deepcopy(data) apply_inheritance(new_data)