Computed Attributes
Relevant source files
The following files were used as context for generating this wiki page:
Computed attributes generate node attribute values dynamically based on other properties or relationships within the schema. Infrahub uses two types of computed attributes: Jinja2 template-based attributes and Python transform-based attributes. These attributes allow developers to automate values such as device identifiers, descriptions, and structural names directly from data dependencies.
The computed attribute system evaluates dependencies using two distinct execution paths. Local updates evaluate immediately within the original mutation transaction. Remote updates execute asynchronously using background tasks.
Sources: dev/specs/archive/infp-460-local-computed-attributes/spec.md, dev/specs/001-scope-computed-attr-recompute/plan.md, specs/archive/ifc-2273-local-computation-jinja2/plan.md
Architectural Design and Dual Execution Paths
The system categorizes updates into local updates and remote updates to minimize task queue overhead and latency.
Local Evaluation Path
When you create a node, or modify attributes or relationships on an existing node, the local execution path runs. If the modified properties feed into a Jinja2 computed attribute on that same node, Infrahub calculates the new value inline within the _update() sequence. The database transaction blocks until the calculation succeeds. If the calculation fails, the transaction rolls back atomically. This design eliminates background tasks for same-node operations and reflects updates instantly in the mutation response.
Remote Evaluation Path
When you modify properties on a related node that another node's computed attribute references, the remote execution path runs. Infrahub routes these asynchronous updates through background tasks.
Sources: dev/specs/archive/infp-460-local-computed-attributes/spec.md:12-25, dev/specs/archive/infp-460-local-computed-attributes/spec.md:46-59, dev/specs/archive/ifc-2273-local-computation-jinja2/tasks.md:T005-T006, specs/archive/ifc-2273-local-computation-jinja2/plan.md:Summary
The diagram below shows how the execution path splits based on where the database mutation occurs.
This flowchart maps the branching logic between synchronous inline execution and asynchronous background processing.
Sources: dev/specs/archive/infp-460-local-computed-attributes/spec.md:162-175, specs/archive/ifc-2273-local-computation-jinja2/plan.md:Summary
Subsystem Components
The execution of computed attributes relies on specialized registries, filters, and tracking modules.
This class diagram illustrates the key components responsible for dependency extraction and local node evaluation.
Sources: dev/specs/archive/ifc-2273-local-computation-jinja2/tasks.md:T001-T003, dev/specs/001-scope-computed-attr-recompute/plan.md:Summary
Core Evaluation Functions
_recompute_local_jinja2(): This asynchronous method executes duringNode._update(). It resolves context variables from in-memory attributes and pre-loaded relationships, renders templates viaInfrahubJinja2Template.render(), and saves changed values._collect_extra_filters(): This method extends relationship pre-loading. It aggregates relationship targets to fetch peer node attributes in a single database query.get_local_jinja2_targets(): This method filters the schema registry to return target definitions specific to the active node kind.
Sources: dev/specs/archive/ifc-2273-local-computation-jinja2/tasks.md:T001-T005
Registry Systems and Dependency Derivation
The system manages computation metadata via two registries:
- Jinja2ComputedRegistry: This registry maintains explicit tracking of local fields and relationship dependencies declared in templates.
- PythonTransformRegistry: This registry governs transform-based attributes. It utilizes
GraphQLQueryAnalyzerto parse stored GraphQL queries to map deep data dependencies.
Sources: dev/specs/001-scope-computed-attr-recompute/plan.md:Summary
| Component Name | Primary File Path | Responsibility |
|---|---|---|
Node Evaluation | backend/infrahub/core/node/__init__.py | Executes inline recomputations and manages database transaction hooks. |
| Schema Registry | backend/infrahub/core/schema/schema_branch_computed.py | Maps fields and filters local versus remote evaluation targets. |
| Query Analysis | backend/infrahub/graphql/analyzer.py | Evaluates GraphQL structures to isolate Python transform reads. |
| Scoping Component | backend/infrahub/computed_attribute/scoping.py | Intersects schema changes with computed attributes to avoid unnecessary jobs. |
Sources: dev/specs/archive/ifc-2273-local-computation-jinja2/tasks.md:T001-T003, dev/specs/001-scope-computed-attr-recompute/plan.md:Project Structure
Schema Change Recomputation Scoping
When you change the database schema, a SchemaUpdatedEvent fires. Historically, this event forced a complete recomputation of all computed attributes across the entire branch. The system scopes this fan-out by matching the changed schema elements against the dependency sets of defined attributes.
Sources: dev/specs/001-scope-computed-attr-recompute/plan.md:Summary
This sequence diagram depicts how the schema update path isolates and filters recompute jobs based on explicit field intersections.
Sources: dev/specs/001-scope-computed-attr-recompute/plan.md:Summary, dev/specs/001-scope-computed-attr-recompute/plan.md:Project Structure
Fail-Safe Fallbacks
The scoping mechanism operates under two strict fail-safe parameters:
- Path-Level Fallback: If a schema event lacks an explicit diff payload (such as during branch deletion, merges, or rebases), the system falls back to a full branch recomputation.
- Attribute-Level Fallback: If the precise dependency depth of a specific python-transform query cannot be determined, that single attribute is marked as "depends on everything." The system always recomputes it without escalating the rest of the branch.
Sources: dev/specs/001-scope-computed-attr-recompute/plan.md:Summary
Data Models and Change Tracking
Local updates consolidate all modifications into a single NodeUpdatedEvent. The payload contains both the explicit user edits and the cascading computed attribute changes.
Sources: dev/specs/archive/infp-460-local-computed-attributes/spec.md:62-75, dev/specs/archive/ifc-2273-local-computation-jinja2/tasks.md:T018
Event Notification Consolidation
To minimize event noise, self-targeting trigger conditions use placeholder values.
Using _trigger_placeholder for self-targeting configurations suppresses duplicate background worker loops for same-node updates, while keeping the structural triggers active for schema-wide changes.
Sources: dev/specs/archive/ifc-2273-local-computation-jinja2/tasks.md:T015
Implementation of Local Jinja2 Recomputation
The following structural outline shows how inline recomputation hooks into the node update pipeline:
# Reference module path: backend/infrahub/core/node/__init__.py
async def _recompute_local_jinja2(self, fields: set[str]):
# 1. Fetch schema branch context
schema_branch = self._schema_branch
# 2. Scope targets restricted to this node kind
targets = schema_branch.computed_attributes.get_local_jinja2_targets(
kind=self._schema.kind,
updates=fields
)
# 3. Sort by dependency topology
sorted_targets = self._sort_computed_dependencies(targets)
for target in sorted_targets:
try:
# 4. Resolve variables from in-memory attributes and loaded peers
context = self._resolve_template_context(target)
# 5. Render using the standard engine
new_value = InfrahubJinja2Template(target.template).render(context)
if self.attributes[target.name].value != new_value:
self.attributes[target.name].value = new_value
await self.attributes[target.name].save()
self._record_in_changelog(target.name, new_value)
except Jinja2RenderingError as err:
# Fallback behavior: log warning and leave value unchanged
logger.warning(f"Failed rendering local computed attribute {target.name}: {err}")
Sources: dev/specs/archive/ifc-2273-local-computation-jinja2/tasks.md:T005-T007
Conclusion
Infrahub's computed attribute engine separates local and remote updates to maximize mutation efficiency. Synchronous inline processing guarantees that same-node changes propagate immediately within the active database transaction. For schema-wide shifts, selective scoping prevents background worker saturation by mapping field-level dependency graphs.