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'
to_dict() Dict[source]

Convert back to plain dictionary.

Returns:

Plain dictionary

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}'}
embody(template: Any, params: Dict[str, Any]) Any[source]

Embody a template (compiles on first use, caches compilation).

Parameters:
  • template – Template to embody

  • params – Parameters for substitution

Returns:

Embodied object

embody_compiled(compiled: Dict, params: Dict[str, Any]) Any[source]

Embody a pre-compiled template.

Parameters:
  • compiled – Compiled template from compile()

  • params – Parameters for substitution

Returns:

Embodied object

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

get(key: str, default=None) Any[source]

Get a parameter with a default value.

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'))
to_dict() Dict[str, Any][source]

Convert to a plain dictionary (resolving all values).

Returns:

Dictionary with all parameters resolved

update(params: Dict[str, Any]) Context[source]

Create a new context with updated parameters.

Parameters:

params – Parameters to add/override

Returns:

New Context with updated parameters

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}
get_signature() list[str][source]

Get the list of parameters required by the template.

Returns:

List of parameter names

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']
get_nested(*path_parts) Any[source]

Get a value by path components.

Parameters:

*path_parts – Path components

Returns:

Value at path

Examples

>>> nested = {'a': {'b': {'c': 42}}}
>>> flat = FlatMapping(nested)
>>> flat.get_nested('a', 'b', 'c')
42
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
exception embody.InvalidPathError[source]

Raised when a path is malformed or invalid.

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

embody(template: Any, params: Dict[str, Any]) Any[source]

Embody using iterative stack-based traversal.

Parameters:
  • template – Template to embody

  • params – Parameters for substitution

Returns:

Embodied object

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}}}
exception embody.KeyCollisionError[source]

Raised when dynamic keys resolve to the same value.

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}}
exception embody.PathNotFoundError[source]

Raised when a path doesn’t exist in a data structure.

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'}
get_dependencies() set[str][source]

Extract all template variables from the template.

Returns:

Set of variable names used in the template

validate_params(params: Dict | Context) list[str][source]

Check which template dependencies are missing from params.

Parameters:

params – Parameters to check against

Returns:

List of missing parameter names

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'
resolve(data: Any, default: Any = None) Any[source]

Resolve the path against data.

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]}}