Creating a computed attribute in Infrahub
Infrahub supports two different types of computed attributes:
- Jinja2 uses a concise Jinja2 template directly within the schema definition.
 - Python leverages Infrahub's Python transform feature to offer endless possibilities.
 
Recommendations
- We recommend to keep Python transforms straightforward to not impact system performance.
 - Use 
Jinja2when the calculation logic is straightforward and concise. 
Adding 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
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.
Adding Python computed attribute
Creating a Python computed attribute is a two-step process:
- Add the attribute to the schema.
 - Prepare a Python Transform 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_labels:
      - 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_labels:
      - 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_labels:
      - 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.
You can specify a Transform that doesn't yet exist in Infrahub, attribute will just remain empty. Once the Transform is loaded, Infrahub will automatically catch up and compute the value of this attribute.
Create the Python transform
Please refer to the Python Transform guide for further details.
- First we prepare a GraphQL query that returns the data we need
 
GraphQL query used to compute attribute's value requires ID as parameter.
query MyQuery($id: ID!) {
  MyNode(ids: [$id]) {
    # ...
  }
}
query CircuitDescriptionQuery($id: ID!) {
  InfraCircuit(ids: [$id]) {
    edges {
      node {
        circuit_id {
          value
        }
        endpoints {
          edges {
            node {
              name { value }
              location {
                node {
                  name {
                    value
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
- Next, the Python script that implements the logic
 
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)
Make sure that the script returns a string!
- Tie everything together into 
.infrahub.ymlfile 
---
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 Transform using infrahubctl
To create a meaningful test, you may want to create: two sites, one circuit, and two endpoints connecting the circuit to the two sites.
"site1::endpoint-a <- circuit1 -> site2::endpoint-b"
- Load the Transform 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.
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.
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.