Skip to main content

Overview

Computed attributes are read-only fields that are dynamically calculated based on other attributes and relationships. They’re useful for creating derived data, formatted displays, and dynamic labels without storing redundant information.

Computed Attribute Types

Infrahub supports two types of computed attributes:

Jinja2 Templates

Simple template-based computation for formatting and concatenation

Python Transforms

Complex logic using custom Python functions for advanced calculations

Jinja2 Computed Attributes

Basic Example

Create a human-readable description from multiple attributes:
nodes:
  - name: Device
    namespace: Infra
    attributes:
      - name: name
        kind: Text
      - name: type
        kind: Text
      - name: role
        kind: Text
      - name: computed_description
        kind: Text
        description: "Auto-generated device description"
        read_only: true
        optional: false
        computed_attribute:
          kind: Jinja2
          jinja2_template: "{{ type__value }} device used as {{ role__value }} on {{ site__name__value|lower }}"
    relationships:
      - name: site
        peer: LocationSite
        cardinality: one
Result: For a device with type=“Router”, role=“Core”, site=“NYC-DC1”:
Router device used as Core on nyc-dc1

Accessing Attribute Values

Use the __value suffix to access attribute values:
computed_attribute:
  kind: Jinja2
  jinja2_template: "{{ name__value }} - {{ description__value }}"

Accessing Relationships

Traverse relationships using the relationship name:
computed_attribute:
  kind: Jinja2
  # Access related object's attributes
  jinja2_template: "Interface {{ name__value }} on {{ device__name__value }}"

Jinja2 Filters

Use Jinja2’s built-in filters for formatting:
computed_attribute:
  kind: Jinja2
  jinja2_template: "{{ name__value|upper }} ({{ type__value|capitalize }})"

Complex Jinja2 Example

nodes:
  - name: Interface
    namespace: Infra
    attributes:
      - name: name
        kind: Text
      - name: speed
        kind: Number
      - name: enabled
        kind: Boolean
      - name: display_name
        kind: Text
        read_only: true
        computed_attribute:
          kind: Jinja2
          jinja2_template: >
            {{ device__name__value }}/{{ name__value }}
            ({{ speed__value }}Mbps)
            {% if enabled__value %}[UP]{% else %}[DOWN]{% endif %}
    relationships:
      - name: device
        peer: InfraDevice
        cardinality: one
Result: router-01/GigabitEthernet0/0 (1000Mbps) [UP]

Python Transform Computed Attributes

For complex calculations that exceed Jinja2’s capabilities, use Python transforms.

Schema Definition

nodes:
  - name: Circuit
    namespace: Infra
    attributes:
      - name: circuit_id
        kind: Text
      - name: computed_description
        kind: Text
        read_only: true
        optional: true
        computed_attribute:
          kind: TransformPython
          transform: computed_circuit_description
    relationships:
      - name: provider
        peer: OrganizationProvider
        cardinality: one
      - name: endpoints
        peer: InfraCircuitEndpoint
        cardinality: many

Transform Implementation

Create a Python transform function:
transforms/circuit_description.py
from infrahub.core.transforms import InfrahubTransform

class ComputedCircuitDescription(InfrahubTransform):
    name = "computed_circuit_description"
    query = """
    query CircuitDescription($circuit_id: String!) {
      InfraCircuit(circuit_id__value: $circuit_id) {
        edges {
          node {
            id
            circuit_id {
              value
            }
            provider {
              node {
                name {
                  value
                }
              }
            }
            endpoints {
              edges {
                node {
                  site {
                    node {
                      name {
                        value
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    """

    async def run(self, data: dict) -> str:
        """Generate circuit description from provider and endpoints."""
        circuit = data["InfraCircuit"]["edges"][0]["node"]
        
        circuit_id = circuit["circuit_id"]["value"]
        provider = circuit["provider"]["node"]["name"]["value"]
        
        # Extract site names from endpoints
        sites = [
            endpoint["node"]["site"]["node"]["name"]["value"]
            for endpoint in circuit["endpoints"]["edges"]
            if endpoint["node"]["site"]
        ]
        
        if len(sites) == 2:
            return f"{provider} circuit {circuit_id} connecting {sites[0]} <-> {sites[1]}"
        elif len(sites) == 1:
            return f"{provider} circuit {circuit_id} at {sites[0]}"
        else:
            return f"{provider} circuit {circuit_id}"

Register the Transform

Register your transform in Infrahub’s transform registry:
from infrahub.core import registry
from transforms.circuit_description import ComputedCircuitDescription

registry.transform.register(ComputedCircuitDescription)

Computed Attribute Properties

Required Properties

read_only
boolean
default:"true"
Computed attributes are always read-only. Users cannot directly set their values.
computed_attribute
object
required
Defines the computation method and template/transform.
computed_attribute.kind
enum
required
Either Jinja2 or TransformPython.

For Jinja2

computed_attribute.jinja2_template
string
required
The Jinja2 template string for computation.

For TransformPython

computed_attribute.transform
string
required
The name or ID of the registered Python transform.

When to Use Each Type

  • Formatting text strings
  • Concatenating attributes
  • Simple conditional logic
  • No external data needed
  • Read-only display values
Examples:
  • Display labels
  • Formatted descriptions
  • Status indicators
  • Name composition

Common Use Cases

Display Labels

attributes:
  - name: display_label
    kind: Text
    read_only: true
    computed_attribute:
      kind: Jinja2
      jinja2_template: "{{ site__name__value }}/{{ rack__name__value }}/{{ name__value }}"

Status Indicators

attributes:
  - name: health_status
    kind: Text
    read_only: true
    computed_attribute:
      kind: Jinja2
      jinja2_template: >
        {% if status__value == 'active' and enabled__value %}Healthy
        {% elif status__value == 'maintenance' %}Maintenance
        {% else %}Degraded{% endif %}

Formatted Identifiers

attributes:
  - name: fqdn
    kind: Text
    read_only: true
    computed_attribute:
      kind: Jinja2
      jinja2_template: "{{ name__value }}.{{ site__name__value|lower }}.example.com"

Aggregated Information

attributes:
  - name: interface_summary
    kind: Text
    read_only: true
    computed_attribute:
      kind: Jinja2
      jinja2_template: "{{ interfaces|length }} interfaces ({{ interfaces|selectattr('enabled__value')|list|length }} enabled)"

Performance Considerations

Computed attributes are evaluated on every read. Complex computations or deep relationship traversals can impact performance.

Best Practices

Keep It Simple

Use simple computations for frequently accessed attributes

Limit Traversals

Avoid deep relationship chains in templates

Cache Results

For expensive computations, consider caching in Python transforms

Index Dependencies

Ensure fields used in computations are indexed

Updating Computed Attributes

Computed attributes automatically update when their dependencies change:
from infrahub_sdk import InfrahubClient

client = InfrahubClient(address="http://localhost:8000")

# Get device
device = await client.get(kind="InfraDevice", name__value="router-01")

print(device.computed_description.value)
# Output: "Router device used as core on nyc-dc1"

# Update the role
device.role.value = "edge"
await device.save()

# Computed attribute automatically updates
print(device.computed_description.value)
# Output: "Router device used as edge on nyc-dc1"

Debugging Computed Attributes

Testing Jinja2 Templates

Test templates before adding to schema:
from jinja2 import Template

# Mock data
data = {
    "name__value": "router-01",
    "type__value": "Router",
    "role__value": "Core",
    "site__name__value": "NYC-DC1"
}

template = Template("{{ type__value }} device used as {{ role__value }} on {{ site__name__value|lower }}")
result = template.render(data)
print(result)
# Output: "Router device used as Core on nyc-dc1"

Debugging Python Transforms

Add logging to your transforms:
import logging
from infrahub.core.transforms import InfrahubTransform

logger = logging.getLogger(__name__)

class ComputedCircuitDescription(InfrahubTransform):
    name = "computed_circuit_description"
    
    async def run(self, data: dict) -> str:
        logger.debug(f"Computing description with data: {data}")
        
        # Your computation logic
        result = "..."
        
        logger.debug(f"Computed result: {result}")
        return result

Complete Example

Here’s a complete schema with multiple computed attributes:
version: "1.0"
nodes:
  - name: Device
    namespace: Infra
    description: "Network Device with computed attributes"
    default_filter: name__value
    display_label: "{{ device_label__value }}"
    attributes:
      - name: name
        kind: Text
        unique: true
      - name: type
        kind: Text
        enum: ["router", "switch", "firewall"]
      - name: role
        kind: Text
        enum: ["core", "edge", "access"]
      - name: serial_number
        kind: Text
        optional: true
      
      # Computed: Device label for UI
      - name: device_label
        kind: Text
        read_only: true
        computed_attribute:
          kind: Jinja2
          jinja2_template: "{{ name__value }} ({{ type__value|capitalize }})"
      
      # Computed: Full description
      - name: full_description
        kind: Text
        read_only: true
        computed_attribute:
          kind: Jinja2
          jinja2_template: >
            {{ type__value|capitalize }} device {{ name__value }}
            at {{ site__name__value }}, role: {{ role__value }}
            {% if serial_number__value %}(S/N: {{ serial_number__value }}){% endif %}
      
      # Computed: Status summary
      - name: status_summary
        kind: Text
        read_only: true
        computed_attribute:
          kind: Jinja2
          jinja2_template: >
            {% set active = interfaces|selectattr('status__value', 'equalto', 'active')|list|length %}
            {% set total = interfaces|length %}
            {{ active }}/{{ total }} interfaces active
    
    relationships:
      - name: site
        peer: LocationSite
        cardinality: one
        optional: false
      - name: interfaces
        peer: InfraInterface
        cardinality: many
        optional: true

Next Steps

Create Schema

Learn how to define complete schemas

Jinja Templates

Explore Jinja2 templating in depth

Python Transforms

Create custom Python transforms

Relationships

Understand relationship traversal

Build docs developers (and LLMs) love