How to Define Nested Configurations Using Hypster's Pythonic API
Define nested configurations in Hypster by calling hp.nest(child_func, name="identifier") inside a parent configuration function, passing child-specific values via dotted keys like "identifier.param" or nested dictionaries where the explicit nested dict takes precedence over dotted notation.
Hypster, the lightweight configuration framework from gilad-rubin/hypster, enables composition of complex parameter trees through pure Python functions. By leveraging the HP.nest method implemented in src/hypster/core.py, you can embed child configurations within parent scopes while maintaining isolated namespaces and automatic parameter tracking. This approach eliminates YAML boilerplate and lets you define hierarchical machine learning pipelines or application stacks using standard Python control flow.
How HP.nest Orchestrates Nested Configurations
The nesting mechanism centers on the HP.nest method defined in [core.py lines 35‑95](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/core.py#L35). When you invoke hp.nest(child, name="child"), Hypster executes a four-phase orchestration:
- Namespace validation. The method checks the
nested_scopesregistry (see [core.pylines 55‑57](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/core.py#L55)) to prevent duplicate prefix definitions and ensure consistent addressing. - Value extraction and merging. Hypster collects all values prefixed with
"child."from the parent’s inputs. It then usesutils.unflatten_dict([utils.pylines 34‑45](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/utils.py#L34)) to transform dotted notation into hierarchical dictionaries. If you provide an explicit nested dict undervalues["child"],utils.merge_nested_dicts([utils.pylines 47‑82](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/utils.py#L47)) merges the two sources, with the explicit nested dict overriding dotted keys. - Child HP instantiation. A fresh
HPinstance receives the extracted values and an extendednamespace_stack(via_get_full_param_path, [core.pylines 58‑62](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/core.py#L58)), ensuring parameters inside the child see fully-qualified paths likechild.x. - Parameter tracking. After the child function executes,
HP.nestmerges the child’scalled_paramsback into the parent’s tracking dictionary, converting relative names to absolute paths (e.g.,xbecomeschild.x) as shown in [core.pylines 97‑104](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/core.py#L97).
This architecture guarantees that each nested scope maintains its own parameter namespace while remaining addressable from the parent configuration.
Basic Nesting Pattern
At minimum, a nested configuration requires a child function that accepts an hp: HP argument and a parent that calls hp.nest() with a unique name:
from hypster import HP, instantiate
def child(hp: HP):
x = hp.int(10, name="x")
return {"x": x}
def parent(hp: HP):
child_cfg = hp.nest(child, name="child")
y = hp.int(20, name="y")
return {"child": child_cfg, "y": y}
result = instantiate(parent)
# → {'child': {'x': 10}, 'y': 20}
Source: Implementation validated in [tests/test_nesting.py lines 8‑21](https://github.com/gilad-rubin/hypster/blob/master/tests/test_nesting.py#L8).
Passing Arguments to Nested Functions
You can inject additional Python arguments into child configurations using args and kwargs. Hypster validates the child function signature via utils.validate_config_func_signature ([utils.py lines 85‑115](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/utils.py#L85)), ensuring the hp: HP parameter remains first:
def child(hp: HP, multiplier: int, offset: int = 0):
base = hp.int(5, name="base")
return base * multiplier + offset
def parent(hp: HP):
a = hp.nest(child, name="calc1", args=(2,))
b = hp.nest(child, name="calc2", args=(3,), kwargs={"offset": 10})
return {"calc1": a, "calc2": b}
result = instantiate(parent)
# → {'calc1': 10, 'calc2': 25}
Source: Example extracted from [tests/test_nesting.py lines 28‑42](https://github.com/gilad-rubin/hypster/blob/master/tests/test_nesting.py#L28).
Conditional Nesting and Control Flow
Because Hypster configurations are pure Python, you can nest different child configurations based on runtime selections:
def model_a(hp: HP):
return {"type": "A", "param": hp.int(1, name="param")}
def model_b(hp: HP):
return {"type": "B", "param": hp.float(2.0, name="param")}
def config(hp: HP):
model_type = hp.select(["a", "b"], name="model_type", default="a")
if model_type == "a":
model = hp.nest(model_a, name="model")
else:
model = hp.nest(model_b, name="model")
return {"model": model}
# Default behavior
instantiate(config)
# → {'model': {'type': 'A', 'param': 1}}
# Override via dotted keys
instantiate(config, values={"model_type": "b", "model.param": 3.0})
# → {'model': {'type': 'B', 'param': 3.0}}
Source: Logic demonstrated in [tests/test_nesting.py lines 44‑68](https://github.com/gilad-rubin/hypster/blob/master/tests/test_nesting.py#L44).
Value Precedence: Dotted Keys vs. Nested Dictionaries
When overriding nested parameters, you can use either dotted notation ("child.x") or an explicit nested dict ({"child": {"x": …}}). According to utils.merge_nested_dicts ([utils.py lines 47‑82](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/utils.py#L47)), the explicit nested dictionary takes precedence over dotted keys when both define the same parameter:
def child(hp: HP):
x = hp.int(10, name="x")
y = hp.int(20, name="y")
return {"x": x, "y": y}
def parent(hp: HP):
return hp.nest(child, name="child")
result = instantiate(
parent,
values={
"child.x": 100, # dotted notation
"child": {"x": 200}, # explicit nested dict
},
)
# → {'x': 200, 'y': 20} # nested dict overrides dotted key
Source: Precedence rules tested in [tests/test_nesting.py lines 70‑89](https://github.com/gilad-rubin/hypster/blob/master/tests/test_nesting.py#L70).
Summary
- Use
hp.nest(func, name="id")to embed child configurations; the child must accepthp: HPas its first argument. - Address child parameters via fully-qualified dotted keys (
"child.param") or nested dictionaries, with explicit dicts overriding dotted notation. - Pass additional arguments to child functions using
argsandkwargsafter thenameparameter. - Leverage Python control flow (if/else, loops) to conditionally select which nested configuration to instantiate.
- Track parameter origins through
HP.called_paramsandHP.nested_scopes, which prevent duplicate definitions and prefix collisions.
Frequently Asked Questions
Can I nest configurations multiple levels deep?
Yes. Hypster’s namespace_stack mechanism in HP._get_full_param_path ([core.py lines 58‑62](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/core.py#L58)) supports arbitrary nesting depths. Each level appends its name to the stack, producing paths like grandparent.parent.child.param. The same rules for value extraction and merging apply recursively at each layer.
What happens if I define the same parameter in both dotted notation and a nested dict?
The explicit nested dictionary wins. The utils.merge_nested_dicts function ([utils.py lines 47‑82](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/utils.py#L47)) merges the two input sources, emitting a warning when the same key appears in both forms but consistently giving precedence to the hierarchical structure over the flattened dotted keys.
How does Hypster validate that child functions accept the HP argument?
Before invoking a nested function, Hypster calls utils.validate_config_func_signature ([utils.py lines 85‑115](https://github.com/gilad-rubin/hypster/blob/master/src/hypster/utils.py#L85)) to inspect the function signature. If the first parameter is not named hp or lacks the HP type annotation, Hypster raises a ValueError immediately, preventing runtime failures inside the configuration tree.
Can I pass additional Python arguments to nested configuration functions?
Yes. The HP.nest method accepts optional args (tuple) and kwargs (dict) parameters that are forwarded to the child function after the hp argument. This allows you to inject constants, file paths, or environment-specific flags into reusable configuration components without polluting the parameter space tracked by Hypster.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →