Skip to main content

Computed attributes

A computed attribute is a read-only Text or URL value calculated from data already modeled in Infrahub. Use a computed attribute where that value should stay synchronized with the attributes and relationships it is built from, rather than being entered and maintained separately.

Define the calculation in the schema as a Jinja2 template, or reference a Python Transformation where the calculation needs cardinality-many relationships, nested relationships, or logic a template cannot express.

When to use a computed attribute

Computed attributes fit values that can be calculated from data you already model:

  • Produce a name or identifier in the pattern another system requires. Where a tool or a process reads a hostname, a circuit ID, an interface description, or a URL and expects a particular pattern, compute that string from the attributes and relationships holding the data it encodes — a device name from its site, role, and index, for example.
  • Include data from a related object in a description. An interface description can name the peer device, the circuit, and the role at the other end of the link. Connect the interface to a different peer, and Infrahub recalculates the description from the new relationship.
  • Combine several modeled values into one string. A circuit description can list its endpoints and their locations; a URL can combine an environment with an object identifier.
  • Compute more than one string from the same data. A node can carry several computed attributes — one name for network operations, another for asset or finance reporting — each calculated from the same attributes and relationships.

Modeling site, role, platform, index, and status as separate attributes and relationships keeps each value available to query on its own, and lets you compute whatever combined string a person or a downstream system needs to read.

Choose the right derived value

Infrahub provides several ways to represent an object using data from its attributes and relationships. Choose the mechanism based on how the value will be used.

You needUse
A readable representation of an object in Infrahub lists, selectors, breadcrumbs, and diffsdisplay_label
A stable, unique way to identify or reference an object across systemshuman_friendly_id
A Text or URL attribute calculated from modeled data that automation, integrations, or users need to query as an attributeComputed attribute

Use a computed attribute when the generated value needs to exist as part of the object and be available to queries, automation, or integrations.

display_label also uses a Jinja2 template, but it defines a single label for the whole node — not an attribute you can query — and only supports one level of direct, cardinality-one relationships. Use a computed attribute instead of display_label when you need to query the derived value, filter on it, or feed it to automation or integrations.

Choose between Jinja2 and Python

Use Jinja2 when the value can be calculated from attributes on the same object and direct cardinality-one relationships. Use a Python Transformation when the calculation needs more complex logic, cardinality-many relationships, or nested relationships.

Jinja2Python Transformation
Use whenThe value can be calculated from attributes and direct cardinality-one relationshipsThe calculation needs more complex logic, cardinality-many relationships, or nested relationships
Relationship supportDirect relationships only; cardinality many is not supportedNo equivalent relationship restriction
Mandatory attribute supportSupportedNot supported; the attribute must be optional
ExecutionA change on the object itself is recalculated as part of the same update; a change on a related object is processed asynchronously. Recalculation is deferred if the template reads a not-yet-allocated pool-sourced attributeRuns asynchronously on workers
Limitations

Computed attributes have some inherent restrictions due to system constraints and performance considerations. Keep these in mind when designing your schema:

  • Only URL and Text attribute kinds are supported for computed attributes.

When Infrahub recalculates a value

Infrahub recalculates a computed attribute when data used by its calculation changes. The inputs Infrahub tracks, and when the updated value becomes available, depend on whether the attribute uses Jinja2 or a Python Transformation.

  • Jinja2 — Infrahub tracks the attributes and direct relationships referenced by the template. When one of those inputs changes, Infrahub recalculates the computed value. Changes to inputs on the same node are recalculated as part of the same update; changes to data on a related object are processed asynchronously. If the template references a pool-sourced attribute on the same node that has not been allocated yet, Infrahub defers that recalculation until the pool assigns a value. A template that references a path the schema does not have, or the attribute itself, is rejected when the schema loads.
  • Python — Infrahub uses the Transformation's GraphQL query to determine which data changes affect the computed value, tracking only the specific fields the query reads on each node kind, not every field. Infrahub recomputes the value directly whenever the object that owns it is created or updated. A change to a related object recomputes only the owners whose Transformation already read that object — not every object of that kind. When one of those tracked changes happens, Infrahub schedules the Transformation on a worker and updates the computed attribute once the task completes.

When you change a Python Transformation, Infrahub recomputes the attributes that use it.

Jinja2 computed attributes

Define a Jinja2 computed attribute directly on the attribute in your node schema.

Use Jinja2 when the calculation is concise and depends only on attributes on the same object or direct cardinality-one relationships.

Restrictions

warning
  • Jinja2 computed attributes cannot reference relationships with cardinality many.
  • Only direct relationships are available; a relationship of a relationship cannot be referenced.
  • A computed attribute can be inherited from only one generic, because Infrahub cannot determine the evaluation order if multiple generics define the same computed attribute.
  • Infrahub evaluates computed attributes inside the API server, in a restricted execution context that only allows fully trusted filters — filters that make network calls or run regular expressions, for example, are blocked. See Execution contexts in the SDK Templating Reference for the full list of allowed filters, and security.restrict_untrusted_jinja2_filters in the configuration reference to change this default.

Add a Jinja2 computed attribute

Everything happens in the schema definition. See Schema Creation for how to build and load a schema.

This example calculates a description for a NetworkDevice from the device's role and the name of its related site.

---
version: "1.0"
nodes:
- name: Site
namespace: Location
attributes:
- name: name
kind: Text
unique: true
relationships:
- name: devices
cardinality: many
peer: NetworkDevice
kind: Component

- name: Device
namespace: Network
attributes:
- name: hostname
kind: Text
unique: true
- name: role
kind: Dropdown
choices:
- name: core
- name: edge
- name: spine
- name: leaf
- name: description
kind: Text
computed_attribute:
kind: Jinja2
jinja2_template: "{{ role__value|title }} device located on {{ site__name__value|lower }}"
read_only: true # You must set the attribute to read-only as the value will be handled by the system
optional: false # As it's a Jinja2 kind of attribute you can make it mandatory
relationships:
- name: site
peer: LocationSite
optional: false
cardinality: one
kind: Attribute
note

In your template, you can utilize most of the filters provided by Jinja2 and Netutils!

For more information, please consult the SDK Templating Reference.

After loading the schema, Infrahub calculates the description attribute from the values referenced by the template.

Test Jinja2 computed attributes locally

Test Jinja2 calculation logic locally with Pytest and the Jinja2Template class from the Infrahub Python SDK before loading the updated schema. This validates formatting, filters, and edge cases independently of the schema-loading workflow.

import pytest
from infrahub_sdk.template import Jinja2Template

INTF_INDEX_JINJA2 = "{{ \"%03d\"| format(name__value | split_interface | last | int) }}"

@pytest.mark.parametrize(
"intf_name,expected",
[
("Ethernet12", "012"),
("Ethernet4", "004"),
]
)
async def test_intf_index(intf_name, expected):
tpl = Jinja2Template(template=INTF_INDEX_JINJA2)
rendered = await tpl.render(variables={"name__value": intf_name})
assert rendered == expected

Add test cases for the input formats and boundary conditions your template needs to support.

Python computed attributes

A Python computed attribute uses a Python Transformation to calculate the value. Reference the Transformation from the schema, then define the GraphQL query and Python logic in a linked repository.

Use Python when the calculation needs cardinality-many relationships, nested relationships, or logic that cannot be expressed cleanly in Jinja2.

Python computed attributes run asynchronously on Infrahub workers, so the updated value may not appear immediately after a dependent value changes.

Restrictions

warning
  • Python computed attributes cannot be mandatory.
  • Python computed attributes run as asynchronous Transformations. Consider the amount of data queried and the work performed by the Transformation when designing the calculation.

Add a Python computed attribute

Creating a Python computed attribute has five parts:

  1. Add the computed attribute to the schema and reference the Transformation.
  2. Create the GraphQL query that provides the Transformation inputs.
  3. Create the Python Transformation that returns the computed string.
  4. Register the query and Transformation in .infrahub.yml.
  5. Commit the Transformation files to a Git repository connected to Infrahub.

This example calculates a description for an InfraCircuit from its endpoints and their locations.

Add the attribute to the schema

Here's the schema we'll use as a starting point:

---
version: "1.0"
nodes:
- name: Site
namespace: Location
display_label: "{{ name__value }}"
attributes:
- name: name
kind: Text
unique: true
relationships:
- name: circuit_endpoints
cardinality: many
optional: true
peer: InfraCircuitEndpoint
kind: Component

- name: Circuit
namespace: Infra
display_label: "{{ circuit_id__value }}"
attributes:
- name: circuit_id
kind: Text
unique: true
- name: computed_description
kind: Text
computed_attribute:
kind: TransformPython
transform: computed_circuit_description # Transform's name
read_only: true # You must set the attribute to read-only as the value will be handled by the system.
optional: true # As it's a Python kind of attribute it must be optional
relationships:
- name: endpoints
peer: InfraCircuitEndpoint
optional: true
cardinality: many
kind: Component

- name: CircuitEndpoint
namespace: Infra
label: Circuit endpoint
display_label: "{{ name__value }}"
attributes:
- name: name
kind: Text
unique: true
relationships:
- name: circuit
peer: InfraCircuit
cardinality: one
kind: Attribute
- name: location
peer: LocationSite
cardinality: one
kind: Attribute

Load this schema into Infrahub.

note

You can specify a Transformation that doesn't yet exist in Infrahub, and the attribute remains empty. Once the Transformation is loaded, Infrahub automatically catches up and computes the value of this attribute.

Create the Python Transformation

Please refer to the Python Transformation guide for further details.

  1. Create a GraphQL query that returns the data required by the Transformation.
warning

GraphQL query used to compute attribute's value requires ID as parameter.

query MyQuery($id: ID!) {
MyNode(ids: [$id]) {
# ...
}
}
computed_circuit_description.gql
query CircuitDescriptionQuery($id: ID!) {
InfraCircuit(ids: [$id]) {
edges {
node {
circuit_id {
value
}
endpoints {
edges {
node {
name { value }
location {
node {
name {
value
}
}
}
}
}
}
}
}
}
}
  1. Create the Python Transformation and return the computed value as a string.
computed_circuit_description.py
from infrahub_sdk.transforms import InfrahubTransform


class ComputedCircuitDescription(InfrahubTransform):
query = "computed_circuit_description"
url = "computed_circuit_description"

async def transform(self, data):
circuit_dict: dict = data["InfraCircuit"]["edges"][0]["node"]

detailed_endpoints: list[str] = []

for endpoint in circuit_dict["endpoints"]["edges"]:
detailed_endpoints.append(
f'{endpoint["node"]["location"]["node"]["name"]["value"]}::{endpoint["node"]["name"]["value"]}'
)

return f' <- {circuit_dict["circuit_id"]["value"]} -> '.join(detailed_endpoints)
note

Make sure that the script returns a string!

  1. Register the query and Transformation in .infrahub.yml.
.infrahub.yml
---
python_transforms:
- name: computed_circuit_description
class_name: ComputedCircuitDescription
file_path: computed_circuit_description.py

queries:
- name: computed_circuit_description
file_path: computed_circuit_description.gql

Test the Transformation with infrahubctl before loading it into Infrahub.

note

To create a meaningful test, you may want to create: two sites, one circuit, and two endpoints connecting the circuit to the two sites.

❯ infrahubctl transform computed_circuit_description id=180b4900-7ea4-f4b3-3b5f-c519831a4b93
"site1::endpoint-a <- circuit1 -> site2::endpoint-b"
  1. Commit the Transformation files to a Git repository and connect the repository to Infrahub.

See adding a repository to Infrahub.

success

The circuit's description is now automatically computed (this process may take a few seconds). Any changes to related attributes (i.e., endpoint name) will trigger a reevaluation and update the value accordingly.