embody
Generate templated objects with advanced structural embodiment features.
This package provides powerful tools for template-based object generation, supporting: - Multiple syntax styles (${var}, {var}, [[var]]) - Type-preserving substitution - Multiple traversal strategies (recursive, compiled, iterative) - Cycle detection - Mapping interfaces with various access patterns - Path-based addressing (JSON Pointer, dot notation, tuple paths)
- Basic usage:
>>> from embody import embody >>> template = {'name': '${name}', 'age': '${age}'} >>> result = embody(template, {'name': 'Alice', 'age': 30}) >>> result {'name': 'Alice', 'age': 30}
- Advanced usage:
>>> from embody import Embodier, Context >>> template = {'greeting': 'Hello ${name}', 'count': '${num}'} >>> embodier = Embodier(template, syntax='dollar_brace', strict=True) >>> embodier({'name': 'Alice', 'num': 42}) {'greeting': 'Hello Alice', 'count': 42}
- class embody.AttributeMapping(data: Dict)[source]
Mapping that allows attribute access (Box pattern).
Allows accessing dictionary keys as attributes for more fluid syntax.
Examples
>>> data = {'user': {'name': 'Alice', 'age': 30}} >>> attr_map = AttributeMapping(data) >>> attr_map.user.name 'Alice' >>> attr_map.user.age 30 >>> attr_map['user']['name'] # Still works as dict 'Alice'
- class embody.CompiledPathEngine(*args, **kwargs)[source]
Pre-compiled path-based traversal strategy.
This engine flattens the template into a path map, performs substitutions, and then reconstructs the structure. It’s best for: - Templates embodied repeatedly (e.g., API responses) - Static templates with known structure - Performance-critical paths
The compilation happens once, then embodiment is a linear scan.
Examples
>>> engine = CompiledPathEngine() >>> template = {'a': {'b': '${x}'}, 'c': '${y}'} >>> compiled = engine.compile(template) >>> engine.embody_compiled(compiled, {'x': 1, 'y': 2}) {'a': {'b': 1}, 'c': 2}
- compile(template: Any) Dict[source]
Compile a template into a flat path map.
- Parameters:
template – Template to compile
- Returns:
Compiled template data
Examples
>>> engine = CompiledPathEngine() >>> template = {'a': {'b': '${x}'}} >>> compiled = engine.compile(template) >>> compiled['flat'] {('a', 'b'): '${x}'}
- class embody.Context(params: Dict[str, Any] = None, parent: Context = None, resolvers: Dict[str, Callable] = None, auto_call: bool = True)[source]
Parameter store for template embodiment with support for resolvers.
The Context holds parameters for substitution and supports: - Hierarchical scoping (global → local overrides) - Resolver functions (lazy evaluation) - Callable values that are invoked on access
Examples
>>> ctx = Context({'name': 'Alice', 'age': 30}) >>> ctx['name'] 'Alice'
>>> import datetime >>> ctx = Context({'now': lambda: datetime.datetime.now()}) >>> ctx['now'] datetime.datetime(...)
>>> ctx = Context({'base': 10}, parent={'base': 5, 'other': 20}) >>> ctx['base'] # Local overrides parent 10 >>> ctx['other'] # Falls back to parent 20
- child(params: Dict[str, Any] = None) Context[source]
Create a child context with this context as parent.
- Parameters:
params – Parameters for the child context
- Returns:
New Context with this as parent
- register_resolver(name: str, func: Callable)[source]
Register a named resolver function.
- Parameters:
name – Name of the resolver
func – Callable that returns the resolved value
Examples
>>> import os >>> ctx = Context() >>> ctx.register_resolver('env', lambda: os.environ.get('USER', 'unknown'))
- exception embody.CycleError[source]
Raised when a circular reference is detected in a data structure.
- class embody.DotPath(path: str, separator: str = '.')[source]
Dot notation path handler (e.g., ‘user.address.city’).
Examples
>>> data = {'user': {'address': {'city': 'NYC'}}} >>> path = DotPath('user.address.city') >>> path.resolve(data) 'NYC'
- resolve(data: Any, default: Any = None) Any[source]
Resolve the path against data.
- Parameters:
data – Data to resolve against
default – Default value if resolution fails
- Returns:
Resolved value or default
- to_json_pointer() JSONPointer[source]
Convert to a JSON Pointer.
- Returns:
JSONPointer instance
- class embody.EmbodiedMapping(data: Dict[str, Any])[source]
Read-only Mapping wrapper for embodied objects with lazy evaluation.
This wrapper provides a Mapping interface around an embodied object, with support for lazy embodiment (values materialized on access).
Examples
>>> from embody import embody >>> template = {'name': '${name}', 'age': '${age}'} >>> params = {'name': 'Alice', 'age': 30} >>> # Embody eagerly first >>> data = embody(template, params) >>> mapping = EmbodiedMapping(data) >>> mapping['name'] 'Alice' >>> len(mapping) 2
- class embody.Embodier(template: Any, strategy: str = 'auto', syntax: str = 'dollar_brace', strict: bool = False, check_cycles: bool = True, key_collision: str = 'error')[source]
Main class for embodying templates with parameters.
This is the primary API for the embody framework. It supports: - Multiple traversal strategies (recursive, compiled, auto) - Multiple syntax styles - Strict or lenient parameter checking - Cycle detection
Examples
>>> template = {'greeting': 'Hello ${name}', 'age': '${age}'} >>> embodier = Embodier(template) >>> embodier({'name': 'Alice', 'age': 30}) {'greeting': 'Hello Alice', 'age': 30}
>>> template = {'count': '${num}'} >>> embodier = Embodier(template) >>> embodier({'num': 42}) # Type preserved {'count': 42}
- class embody.FlatMapping(data: Dict, separator: str = '.')[source]
Mapping with flattened nested structure for path-based access.
Allows accessing nested values using dot notation as single keys.
Examples
>>> nested = {'user': {'name': 'Alice', 'age': 30}} >>> flat = FlatMapping(nested) >>> flat['user.name'] 'Alice' >>> flat['user.age'] 30 >>> list(flat.keys()) ['user.name', 'user.age']
- class embody.FrozenMapping(data: Dict)[source]
Immutable Mapping wrapper.
Provides a frozen (immutable) view of a dictionary.
Examples
>>> data = {'a': 1, 'b': 2} >>> frozen = FrozenMapping(data) >>> frozen['a'] 1 >>> # Modifications to original don't affect frozen (deep copy) >>> data['c'] = 3 >>> 'c' in frozen False
- class embody.IterativeStackEngine(syntax: str = 'dollar_brace', strict: bool = False, key_collision: str = 'error')[source]
Iterative stack-based traversal strategy.
This engine uses an explicit stack instead of recursion, which is more memory-efficient for very deep structures and avoids Python’s recursion limit.
Best for: - Very deeply nested structures (>100 levels) - When recursion depth is a concern
- class embody.JSONPointer(pointer: str)[source]
JSON Pointer implementation (RFC 6901).
JSON Pointer defines a string syntax for identifying a specific value within a JSON document. Example: /foo/bar/0 refers to the first element of the ‘bar’ array in the ‘foo’ object.
Examples
>>> data = {'foo': {'bar': [1, 2, 3]}} >>> ptr = JSONPointer('/foo/bar/0') >>> ptr.resolve(data) 1
>>> data = {'user': {'name': 'Alice', 'age': 30}} >>> JSONPointer('/user/name').resolve(data) 'Alice'
- classmethod from_parts(parts: List[str]) JSONPointer[source]
Create a JSON Pointer from path components.
- Parameters:
parts – List of path components
- Returns:
JSONPointer instance
Examples
>>> ptr = JSONPointer.from_parts(['foo', 'bar', '0']) >>> ptr.pointer '/foo/bar/0'
- resolve(data: Any, default: Any = None) Any[source]
Resolve the pointer against data.
- Parameters:
data – Data to resolve against
default – Default value if resolution fails
- Returns:
Resolved value or default
Examples
>>> data = {'a': {'b': [1, 2, 3]}} >>> JSONPointer('/a/b/1').resolve(data) 2 >>> JSONPointer('/a/x').resolve(data, default='not found') 'not found'
- set(data: Any, value: Any, create_intermediate: bool = False)[source]
Set a value at the pointer location.
- Parameters:
data – Data to modify (in place)
value – Value to set
create_intermediate – If True, create intermediate structures
- Raises:
InvalidPathError – If path cannot be set
Examples
>>> data = {'a': {'b': {}}} >>> JSONPointer('/a/b/c').set(data, 42) >>> data {'a': {'b': {'c': 42}}}
- class embody.LazyEmbodiedMapping(template: Dict, params: Dict, embodier: Embodier)[source]
Lazy Mapping that embodies values on access.
This is useful for large templates where you only need to access a subset of values. Values are embodied on-demand and cached.
Examples
>>> from embody import Embodier >>> template = {'a': '${x}', 'b': '${y}', 'c': '${z}'} >>> params = {'x': 1, 'y': 2, 'z': 3} >>> embodier = Embodier(template) >>> lazy = LazyEmbodiedMapping(template, params, embodier) >>> lazy['a'] # Only 'a' is embodied 1
- exception embody.MissingParameterError[source]
Raised when a required template parameter is missing.
- class embody.MutableEmbodiedMapping(data: Dict)[source]
Mutable Mapping wrapper for embodied objects.
Provides full MutableMapping interface for modifying embodied results.
Examples
>>> data = {'a': 1, 'b': 2} >>> mutable = MutableEmbodiedMapping(data) >>> mutable['c'] = 3 >>> mutable['a'] = 10 >>> del mutable['b'] >>> dict(mutable) {'a': 10, 'c': 3}
- freeze() FrozenMapping[source]
Convert to an immutable FrozenMapping.
- Returns:
Frozen version of this mapping
- class embody.PathMapping(data: Dict, separator: str = '.')[source]
Mapping supporting multiple path access styles.
Supports: - Regular dict access: mapping[‘key’] - Dot notation: mapping[‘a.b.c’] - Tuple paths: mapping[(‘a’, ‘b’, ‘c’)] - JSON Pointer: mapping[‘/a/b/c’]
Examples
>>> data = {'a': {'b': {'c': 42}}} >>> pm = PathMapping(data) >>> pm['a.b.c'] 42 >>> pm[('a', 'b', 'c')] 42 >>> pm['/a/b/c'] # JSON Pointer 42 >>> pm['a'] # Regular access {'b': {'c': 42}}
- class embody.RecursiveVisitorEngine(syntax: str = 'dollar_brace', strict: bool = False, key_collision: str = 'error')[source]
Dynamic traversal strategy using the Visitor pattern.
This engine recursively walks the template structure at runtime, visiting each node and performing substitutions. It’s best for: - One-off templates - Dynamic templates with conditional logic - Templates where structure changes based on parameters
Examples
>>> engine = RecursiveVisitorEngine() >>> template = {'greeting': 'Hello ${name}', 'count': '${num}'} >>> engine.embody(template, {'name': 'Alice', 'num': 42}) {'greeting': 'Hello Alice', 'count': 42}
- embody(template: Any, params: Dict[str, Any], visited: Set[int] | None = None) Any[source]
Recursively embody a template.
- Parameters:
template – Template to embody
params – Parameters for substitution
visited – Set of visited object IDs (for cycle detection)
- Returns:
Embodied object
- Raises:
CycleError – If a circular reference is detected
- class embody.SubstitutionSyntax[source]
Defines the syntax patterns for template variable substitution.
- classmethod get_pattern(syntax: str = 'dollar_brace') Pattern[source]
Get the regex pattern for the specified syntax.
- Parameters:
syntax – One of ‘dollar_brace’, ‘brace’, ‘double_bracket’
- Returns:
Compiled regex pattern
Examples
>>> pattern = SubstitutionSyntax.get_pattern('dollar_brace') >>> pattern.findall('${name} is ${age}') ['name', 'age']
- class embody.TemplateWrapper(template: Any, syntax: str = 'dollar_brace', check_cycles: bool = True)[source]
Wrapper for template data with introspection capabilities.
This class wraps template data and provides methods to: - Extract variable dependencies - Detect cycles - Parse and validate the template structure
Examples
>>> template = TemplateWrapper({'name': '${user}', 'age': '${years}'}) >>> template.get_dependencies() {'user', 'years'}
- class embody.TuplePath(parts: Tuple)[source]
Tuple-based path (unambiguous, e.g., (‘user’, ‘address’, ‘city’)).
Examples
>>> data = {'user': {'address': {'city': 'NYC'}}} >>> path = TuplePath(('user', 'address', 'city')) >>> path.resolve(data) 'NYC'
- to_json_pointer() JSONPointer[source]
Convert to a JSON Pointer.
- embody.as_mapping(data: Any, style: str = 'basic') Mapping[source]
Convert data to a Mapping with the specified style.
- Parameters:
data – Data to wrap
style – Style of mapping (‘basic’, ‘attribute’, ‘flat’, ‘path’, ‘frozen’)
- Returns:
Mapping instance
Examples
>>> data = {'user': {'name': 'Alice'}} >>> m = as_mapping(data, 'attribute') >>> m.user.name 'Alice'
>>> m = as_mapping(data, 'flat') >>> m['user.name'] 'Alice'
- embody.count_template_markers(obj: Any, syntax: str = 'dollar_brace') int[source]
Count the number of template markers in a nested structure.
- Parameters:
obj – Object to count markers in
syntax – Template syntax to look for
- Returns:
Number of template markers found
Examples
>>> count_template_markers({'a': '${x}', 'b': '${y}'}) 2 >>> count_template_markers(['${a}', 'literal', '${b} and ${c}']) 3
- embody.detect_cycle(obj: Any, visited: Set[int] = None, path: List[str] = None) None[source]
Detect cycles in a nested data structure.
- Parameters:
obj – The object to check for cycles
visited – Set of object IDs already visited
path – Current path in the structure (for error reporting)
- Raises:
CycleError – If a cycle is detected
Examples
>>> d = {'a': 1, 'b': [2, 3]} >>> detect_cycle(d) # No cycle, returns None
>>> circular = {'a': 1} >>> circular['self'] = circular >>> try: ... detect_cycle(circular) ... except CycleError as e: ... print("Cycle detected") Cycle detected
- embody.embody(template: Any, params: Dict | Context = None, **kwargs) Any[source]
Convenience function for template embodiment.
This is the main entry point for simple embodiment use cases.
- Parameters:
template – The template to embody
params – Parameters for substitution
**kwargs – Additional parameters or config options
- Returns:
Embodied object
Examples
>>> embody({'name': '${name}'}, {'name': 'Alice'}) {'name': 'Alice'}
>>> embody({'count': '${n}'}, {'n': 42}) {'count': 42}
>>> embody(['${a}', '${b}'], {'a': 1, 'b': 2}) [1, 2]
- embody.extract_template_vars(template: str, syntax: str = 'dollar_brace') list[str][source]
Extract all template variable names from a string.
- Parameters:
template – Template string containing variables
syntax – Variable syntax to use
- Returns:
List of variable names found in the template
Examples
>>> extract_template_vars('Hello ${name}, you are ${age} years old') ['name', 'age'] >>> extract_template_vars('Hello {name}', syntax='brace') ['name']
- embody.flatten_dict(nested_dict: Dict[str, Any], separator: str = '.', parent_key: str = '') Dict[str, Any][source]
Flatten a nested dictionary into a single-level dictionary with path keys.
- Parameters:
nested_dict – Nested dictionary to flatten
separator – String to use for separating path components
parent_key – Key of the parent (used in recursion)
- Returns:
Flattened dictionary with path keys
Examples
>>> d = {'a': {'b': {'c': 1}}, 'd': 2} >>> flatten_dict(d) {'a.b.c': 1, 'd': 2}
>>> d = {'user': {'name': 'Alice', 'age': 30}} >>> flatten_dict(d) {'user.name': 'Alice', 'user.age': 30}
>>> d = {'items': [1, 2, 3]} >>> flatten_dict(d) {'items.0': 1, 'items.1': 2, 'items.2': 3}
- embody.flatten_to_tuples(obj: Any, parent_path: Tuple = ()) Dict[Tuple, Any][source]
Flatten a nested structure using tuple paths (unambiguous).
This avoids the ambiguity of string separators. A tuple path like (‘a’, ‘b’, 0) is unambiguous, while ‘a.b.0’ could mean multiple things if keys contain dots.
- Parameters:
obj – Object to flatten
parent_path – Current path as tuple
- Returns:
Dictionary mapping tuple paths to values
Examples
>>> d = {'a': {'b': [1, 2]}} >>> flatten_to_tuples(d) {('a', 'b', 0): 1, ('a', 'b', 1): 2}
- embody.get_by_path(obj: Any, path: str | Tuple, separator: str = '.', default: Any = None) Any[source]
Get a value from a nested structure by path.
- Parameters:
obj – Nested structure
path – Path as string or tuple
separator – Separator for string paths
default – Default value if path not found
- Returns:
Value at the path, or default if not found
Examples
>>> d = {'a': {'b': {'c': 42}}} >>> get_by_path(d, 'a.b.c') 42 >>> get_by_path(d, ('a', 'b', 'c')) 42 >>> get_by_path(d, 'a.b.x', default='not found') 'not found'
- embody.get_engine(strategy: str = 'recursive', **kwargs) BaseEmbodimentEngine[source]
Get an embodiment engine by name.
- Parameters:
strategy – Name of strategy (‘recursive’, ‘compiled’, ‘iterative’)
**kwargs – Arguments to pass to engine constructor
- Returns:
Embodiment engine instance
Examples
>>> engine = get_engine('recursive', syntax='brace') >>> isinstance(engine, RecursiveVisitorEngine) True
- embody.is_exact_match(template: str, syntax: str = 'dollar_brace') str | None[source]
Check if template is exactly one variable placeholder (no other text).
This is crucial for type preservation. If the template is exactly ${var}, we should return the raw value, not convert it to a string.
- Parameters:
template – Template string to check
syntax – Variable syntax to use
- Returns:
Variable name if exact match, None otherwise
Examples
>>> is_exact_match('${name}') 'name' >>> is_exact_match('Hello ${name}')
>>> is_exact_match('${count}') 'count'
- embody.max_depth(obj: Any, current_depth: int = 0) int[source]
Calculate the maximum depth of a nested structure.
- Parameters:
obj – Object to measure
current_depth – Current depth (used in recursion)
- Returns:
Maximum depth
Examples
>>> max_depth({'a': 1}) 1 >>> max_depth({'a': {'b': {'c': 1}}}) 3 >>> max_depth([1, [2, [3, 4]]]) 3
- embody.parse_path(path: str | Tuple | List, format: str = 'auto') JSONPointer | DotPath | TuplePath[source]
Parse a path in various formats.
- Parameters:
path – Path in any supported format
format – Format hint (‘auto’, ‘json_pointer’, ‘dot’, ‘tuple’)
- Returns:
Path object
Examples
>>> parse_path('/user/name') <embody.paths.JSONPointer object at ...>
>>> parse_path('user.name') <embody.paths.DotPath object at ...>
>>> parse_path(('user', 'name')) <embody.paths.TuplePath object at ...>
- embody.resolve_path(data: Any, path: str | Tuple | List, default: Any = None, format: str = 'auto') Any[source]
Resolve a path against data.
- Parameters:
data – Data to resolve against
path – Path in any supported format
default – Default value if resolution fails
format – Format hint
- Returns:
Resolved value or default
Examples
>>> data = {'user': {'name': 'Alice', 'age': 30}} >>> resolve_path(data, '/user/name') 'Alice' >>> resolve_path(data, 'user.age') 30 >>> resolve_path(data, ('user', 'name')) 'Alice'
- embody.set_by_path(obj: Dict, path: str | Tuple, value: Any, separator: str = '.', create_intermediate: bool = True) None[source]
Set a value in a nested structure by path (modifies in place).
- Parameters:
obj – Nested structure to modify
path – Path as string or tuple
value – Value to set
separator – Separator for string paths
create_intermediate – If True, create intermediate dicts/lists as needed
Examples
>>> d = {} >>> set_by_path(d, 'a.b.c', 42) >>> d {'a': {'b': {'c': 42}}}
- embody.set_path(data: Any, path: str | Tuple | List, value: Any, format: str = 'auto', create_intermediate: bool = True)[source]
Set a value at a path.
- Parameters:
data – Data to modify
path – Path in any supported format
value – Value to set
format – Format hint
create_intermediate – If True, create intermediate structures
Examples
>>> data = {} >>> set_path(data, '/user/name', 'Alice') >>> data {'user': {'name': 'Alice'}}
- embody.substitute(template: Any, params: Dict[str, Any], syntax: str = 'dollar_brace', strict: bool = False) Any[source]
Perform type-preserving substitution on a template value.
This is the core substitution function. It handles: 1. Exact match: ${var} -> returns params[‘var’] with type preserved 2. Partial match: “Count is ${var}” -> string interpolation 3. Non-string templates: returned unchanged
- Parameters:
template – The template value (can be any type)
params – Dictionary of parameters for substitution
syntax – Variable syntax to use
strict – If True, raise KeyError for missing variables
- Returns:
The substituted value with type preservation
Examples
>>> substitute('${count}', {'count': 42}) 42 >>> substitute('Count: ${count}', {'count': 42}) 'Count: 42' >>> substitute('${active}', {'active': True}) True >>> substitute('${items}', {'items': [1, 2, 3]}) [1, 2, 3] >>> substitute(42, {}) 42
- embody.unflatten_dict(flat_dict: Dict[str, Any], separator: str = '.') Dict[str, Any][source]
Unflatten a dictionary with path keys into a nested structure.
- Parameters:
flat_dict – Flattened dictionary with path keys
separator – String used for separating path components
- Returns:
Nested dictionary structure
Examples
>>> flat = {'a.b.c': 1, 'd': 2} >>> unflatten_dict(flat) {'a': {'b': {'c': 1}}, 'd': 2}
>>> flat = {'items.0': 'a', 'items.1': 'b'} >>> unflatten_dict(flat) {'items': ['a', 'b']}
- embody.unflatten_from_tuples(flat_dict: Dict[Tuple, Any]) Any[source]
Unflatten a dictionary with tuple paths into a nested structure.
- Parameters:
flat_dict – Dictionary with tuple paths as keys
- Returns:
Nested structure (dict or list)
Examples
>>> flat = {('a', 'b', 0): 1, ('a', 'b', 1): 2} >>> unflatten_from_tuples(flat) {'a': {'b': [1, 2]}}