Propagation Modes¶
Propagation modes control how attribute values flow through the resource tree. Choosing the right mode is key to effective hierarchical configuration.
Overview¶
| Mode | Direction | Use Case |
|---|---|---|
INHERIT |
Ancestors → Resource | Inherit defaults, allow overrides |
AGGREGATE |
Descendants → Resource | Aggregate values, collect metrics |
MERGE |
Ancestors → Resource | Deep-merge dictionaries |
REQUIRE_PATH |
Ancestors → Resource | All ancestors must have truthy values |
COLLECT_ANCESTORS |
Ancestors → Resource | Collect all ancestor values as a list |
NONE |
Local only | Get only directly set values |
INHERIT - Inherit from Ancestors¶
Values cascade from parent to children. The closest ancestor with the attribute wins.
from hrcp import ResourceTree, PropagationMode, get_value
tree = ResourceTree(root_name="org")
tree.root.set_attribute("timeout", 30)
tree.create("/org/team", attributes={"timeout": 60})
tree.create("/org/team/project")
tree.create("/org/team/project/service")
# Each resource inherits from closest ancestor with the value
root_timeout = get_value(tree.root, "timeout", PropagationMode.INHERIT)
# 30 - set locally
team_timeout = get_value(tree.get("/org/team"), "timeout", PropagationMode.INHERIT)
# 60 - set locally (overrides parent)
project_timeout = get_value(tree.get("/org/team/project"), "timeout", PropagationMode.INHERIT)
# 60 - inherited from /org/team
service_timeout = get_value(tree.get("/org/team/project/service"), "timeout", PropagationMode.INHERIT)
# 60 - inherited from /org/team (closest ancestor)
When to Use INHERIT¶
- Default configurations that should apply to all children
- Environment settings (production, staging, development)
- Policy values that cascade through a hierarchy
- Feature flags that can be overridden at any level
AGGREGATE - Aggregate from Descendants¶
Collect all values from the subtree beneath a resource.
tree = ResourceTree(root_name="company")
tree.create("/company/eng", attributes={"headcount": 50})
tree.create("/company/eng/platform", attributes={"headcount": 15})
tree.create("/company/eng/mobile", attributes={"headcount": 10})
tree.create("/company/sales", attributes={"headcount": 30})
# Aggregate from all descendants
company = tree.root
headcounts = get_value(company, "headcount", PropagationMode.AGGREGATE)
# [50, 15, 10, 30] - all values from subtree
# Get from a subtree
eng = tree.get("/company/eng")
eng_headcounts = get_value(eng, "headcount", PropagationMode.AGGREGATE)
# [50, 15, 10] - only engineering subtree
Result Type
AGGREGATE propagation always returns a list of all values found in the subtree, including the resource itself if it has the attribute.
When to Use AGGREGATE¶
- Rollup metrics (headcount, budget, resource usage)
- Collecting tags or labels from children
- Auditing what values exist in a subtree
- Validation to ensure all children have required values
MERGE - Deep Dictionary Merge¶
Recursively merge dictionaries from ancestors. Child values override parent values at each key level.
tree = ResourceTree(root_name="platform")
tree.root.set_attribute("config", {
"database": {
"host": "localhost",
"port": 5432,
"pool_size": 10
},
"cache": {
"enabled": True,
"ttl": 300
}
})
tree.create("/platform/prod", attributes={
"config": {
"database": {
"host": "prod.db.internal",
"pool_size": 50
}
}
})
prod = tree.get("/platform/prod")
config = get_value(prod, "config", PropagationMode.MERGE)
# {
# "database": {
# "host": "prod.db.internal", # overridden
# "port": 5432, # inherited
# "pool_size": 50 # overridden
# },
# "cache": {
# "enabled": True, # inherited
# "ttl": 300 # inherited
# }
# }
Merge Rules¶
- Child values override parent values for the same key
- Merge happens recursively for nested dicts
- Non-dict values are not merged (child wins completely)
- New keys from child are added
When to Use MERGE¶
- Complex configuration objects with many nested settings
- Layered overrides where children customize parts of a structure
- Feature configurations with many options
REQUIRE_PATH - All Ancestors Must Enable¶
Returns the local value only if ALL ancestors (from self to root) have a truthy value for the attribute. If any ancestor is missing the attribute or has a falsy value, returns None.
This is ideal for opt-in features where every level must explicitly enable.
tree = ResourceTree(root_name="platform")
# Platform enables the feature
tree.root.set_attribute("basket_enabled", True)
# Org enables it
tree.create("/platform/org", attributes={"basket_enabled": True})
# Account enables it
account = tree.create("/platform/org/account", attributes={"basket_enabled": True})
# All ancestors enabled → returns True
enabled = get_value(account, "basket_enabled", PropagationMode.REQUIRE_PATH)
# True
# Now try with org disabled
tree.get("/platform/org").set_attribute("basket_enabled", False)
enabled = get_value(account, "basket_enabled", PropagationMode.REQUIRE_PATH)
# None - org has falsy value, breaks the chain
Use Case: Feature Flags with Opt-In¶
# Basket feature: ALL levels must enable (opt-in)
def has_basket_feature(account):
return bool(get_value(account, "basket_enabled", PropagationMode.REQUIRE_PATH))
# Platform: True, Org: True, Account: True → True
# Platform: True, Org: True, Account: False → False
# Platform: True, Org: False, Account: True → False
# Platform: False, Org: True, Account: True → False
When to Use REQUIRE_PATH¶
- Opt-in features that require explicit enablement at every level
- Compliance requirements where all levels must approve
- Cascading permissions that can be revoked at any level
COLLECT_ANCESTORS - Collect All Ancestor Values¶
Collects all values from the resource up to the root as a list. Returns values in order from the resource (first) to root (last).
This allows custom AND/OR logic for more complex inheritance patterns.
tree = ResourceTree(root_name="platform")
tree.root.set_attribute("enabled", True)
tree.create("/platform/org", attributes={"enabled": True})
account = tree.create("/platform/org/account", attributes={"enabled": False})
# Collect all values from account to root
values = get_value(account, "enabled", PropagationMode.COLLECT_ANCESTORS)
# [False, True, True] - account, org, platform
# Custom logic
all(values) # False - AND (like REQUIRE_PATH)
any(values) # True - OR (at least one ancestor has it)
Use Case: Feature Flags with Inheritance (STP-style)¶
# STP feature: If platform/org enables, accounts inherit automatically
def has_stp_feature(account):
values = get_value(account, "stp_enabled", PropagationMode.COLLECT_ANCESTORS)
return any(values) if values else False
# Platform: True, Org: True, Account: None → True (inherited)
# Platform: True, Org: False, Account: None → True (from platform)
# Platform: False, Org: False, Account: False → False
When to Use COLLECT_ANCESTORS¶
- Custom inheritance logic (AND, OR, majority vote, etc.)
- Debugging to see what values exist along the path
- Complex feature flag patterns that don't fit INHERIT or REQUIRE_PATH
NONE - Local Only¶
Return only the value set directly on the resource. No inheritance, no aggregation.
tree = ResourceTree(root_name="org")
tree.root.set_attribute("global_id", "ORG-001")
tree.create("/org/team")
team = tree.get("/org/team")
# NONE returns only local value
local = get_value(team, "global_id", PropagationMode.NONE)
# None - not set on this resource
# INHERIT would inherit
inherited = get_value(team, "global_id", PropagationMode.INHERIT)
# "ORG-001" - inherited from root
When to Use NONE¶
- Checking if a value is explicitly set on a specific resource
- Avoiding unintended inheritance for resource-specific values
- Validation to ensure required values are set locally
Choosing the Right Mode¶
graph TD
A[Need to resolve an attribute?] --> B{How should it flow?}
B -->|Parent to child| C{What behavior?}
B -->|Child to parent| D[Use AGGREGATE]
B -->|No flow needed| E[Use NONE]
C -->|First ancestor wins| F[Use INHERIT]
C -->|All must enable| G[Use REQUIRE_PATH]
C -->|Custom AND/OR| H[Use COLLECT_ANCESTORS]
C -->|Merge dicts| I[Use MERGE]
| Scenario | Recommended Mode |
|---|---|
| Default timeout for all services | INHERIT |
| Environment name (prod/staging) | INHERIT |
| Opt-in feature (all levels must enable) | REQUIRE_PATH |
| Inherited feature (any ancestor enables) | INHERIT or COLLECT_ANCESTORS + any() |
| Team-specific budget | NONE or INHERIT |
| Total headcount across org | AGGREGATE |
| Layered feature flags | MERGE |
| Database connection config | MERGE |
| Resource-specific identifier | NONE |
| Custom permission logic | COLLECT_ANCESTORS |
Backward Compatibility¶
The following aliases are available for backward compatibility but are deprecated:
| Deprecated | Use Instead |
|---|---|
DOWN |
INHERIT |
UP |
AGGREGATE |
MERGE_DOWN |
MERGE |