Skip to main content

Computed attributes

The computed attributes feature enables the dynamic calculation of attribute values based on user-defined logic.

Currently, Infrahub supports two main types of computed attributes:

  • Jinja2 – Lightweight, but limited in relationship handling.
  • Python – More flexible and powerful, but requires async execution.
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.

Choosing between Jinja2 and Python

Computed Attribute KindMandatory Attribute SupportRelationship ConstraintsComplexity Handling
Jinja2✅ SupportedCannot use many cardinality; only direct relationshipsBest for straightforward operations
Python❌ Not supportedNo restrictionsSuitable for complex logic

Jinja2 computed attributes

Users can input a concise Jinja2 template directly within the schema definition. Any change to any field used to compute the value will automatically update it.

Use Jinja2 when the calculation logic is straightforward and concise.

Restrictions

warning
  • Jinja2 computed attributes cannot reference relationships with cardinality many.
  • Only direct relationships can be used (i.e., a relationship of a relationship is not accessible).
  • A computed attribute can only be inherited from one generic, as the order of operations would be unclear if the same computed attribute was defined on multiple generics.

How to add a Jinja2 computed attribute

To create a Jinja2 computed attribute everything happens directly in the schema definition. Refer to this guide to find further information about Schema Creation.

In the following example we will create a computed attribute description for our NetworkDevice node. The value will be dynamically generated by combining the device's role with the name of its associated 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.

You can now load this schema into your Infrahub instance. For more details, refer to the Schema Creation Guide.

How to test Jinja2 computed attributes locally

When developing Jinja2 computed attributes, it can be difficult to get the filter syntax right, especially if you need to push the schema every time to test a change. Instead, you can test your Jinja2 computed attribute logic locally using Pytest and the Jinja2Template class from the Infrahub Python SDK.

Example:

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

This approach allows you to quickly iterate and validate your computed attribute logic without needing to push changes to the schema. Adjust the template and test cases as needed to cover different scenarios.

Python computed attributes

Python computed attributes leverage Infrahub's Python Transformation feature. Users can define which Transformation to apply directly within the schema. The computation of the value is delegated to workers as asynchronous tasks, which may cause the value to take a few seconds to update.

Python scripts do not have the limitations of Jinja2 computed attributes and can include more complex logic, including relationships with cardinality many and nested relationships.

Keep Python Transformations straightforward to avoid impacting system performance.

Restrictions

warning
  • Mandatory attributes cannot use Python computed attributes.
  • Complex scripts may impact system performance.

How to add a Python computed attribute

Creating a Python computed attribute is a two-step process:

  1. Add the attribute to the schema.
  2. Prepare a Python Transformation to implement the logic.

In the following example we will create a computed attribute description for our InfraCircuit node. The value will be a combination of all endpoints 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

Once completed, you can 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. First we prepare a GraphQL query that returns the data we need
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. Next, the Python script that implements the logic
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. Tie everything together into .infrahub.yml file
.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

At this stage you should be able to test your Transformation using infrahubctl

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. Load the Transformation in Infrahub

You need to commit these files to a Git repository and link the repository to Infrahub. Please refer to the adding a repository to Infrahub guide.

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.