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:
String Formatting
Conditional Logic
Default Values
Combined Fields
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]
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
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 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
Computed attributes are always read-only. Users cannot directly set their values.
Defines the computation method and template/transform.
Either Jinja2 or TransformPython.
For Jinja2
computed_attribute.jinja2_template
The Jinja2 template string for computation.
computed_attribute.transform
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
Complex calculations needed
Multiple data sources
External API calls
Database queries
Advanced business logic
Examples:
IP address calculations
Aggregated metrics
Cross-object computations
API integrations
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 %}
attributes :
- name : fqdn
kind : Text
read_only : true
computed_attribute :
kind : Jinja2
jinja2_template : "{{ name__value }}.{{ site__name__value|lower }}.example.com"
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)"
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"
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