Infrahub Transform Creator
Overview
Expert guidance for creating Infrahub transforms.
Transforms convert Infrahub data into different formats
-- JSON, text, CSV, device configs, or any text-based
output -- using Python classes or Jinja2 templates.
Project Context
Infrahub config:
!cat .infrahub.yml 2>/dev/null || echo "No .infrahub.yml found"
Existing transforms:
!find . -name "*.py" -path "*/transforms/*" -o -name "*.j2" -path "*/templates/*" 2>/dev/null | head -20
When to Use
- Building data transformations (Infrahub data -> another format)
- Generating device configurations from infrastructure data
- Creating CSV reports, cable matrices, or inventory exports
- Rendering Jinja2 templates with query data
- Combining Python logic with Jinja2 rendering
- Connecting transforms to artifacts for automated output
Rule Categories
| Priority |
Category |
Prefix |
Description |
| CRITICAL |
Types |
types- |
Python vs Jinja2 choice |
| CRITICAL |
Python |
python- |
InfrahubTransform class |
| CRITICAL |
Jinja2 |
jinja2- |
Template syntax, filters |
| HIGH |
Hybrid |
hybrid- |
Python + Jinja2 combined |
| HIGH |
Artifacts |
artifacts- |
Output files, targets |
| HIGH |
API Ref |
api- |
Class attrs, methods |
| MEDIUM |
Patterns |
patterns- |
Utilities, CSV, shared |
| HIGH |
Testing |
testing- |
Resources Testing Framework, transform/render commands |
Schema Features This Skill Depends On
A transform reads schema-shaped data and produces a
file. Misalignment between the transform and the
schema fails late — at artifact-render time, when
someone is waiting for the output.
Before writing Python
If the transform body is string formatting — f-strings,
concatenation, conditional sections — Jinja2 expresses
the same output in fewer lines, renders directly in
the proposed-change UI, and lives under
jinja2_transforms in .infrahub.yml instead of
python_transforms. Walk this ladder before reaching
for InfrahubTransform:
| Signal |
Cheaper layer |
See rule |
Transform body is return f"..." or "\n".join([...]) built from query results |
Jinja2 template file |
yagni-python-transform-that-could-be-jinja2 |
| Transform copies query data verbatim without computation |
The GraphQL query alone — no transform needed |
Ladder step 1 (drop the requirement); judgment call, no rule |
Conditionals are if x: out += ...; else: out += ... and nothing else |
Jinja2 {% if %} blocks |
yagni-python-transform-that-could-be-jinja2 |
Use Python when the transform parses, computes, or
reshapes — IP/subnet math, hashing, ordered
aggregation, structural JSON re-shaping. See
rules/python-transform.md
for the legitimate cases.
When the transform reads objects through the SDK, type those calls with
generated protocol classes rather than string kinds — client.filters(NetworkLink, ...),
not kind="NetworkLink" — so schema drift fails type-check instead of at
runtime. See
protocols-adopt-typed-kinds.
Transform Basics
Two types of transforms:
| Type |
Output |
Entry Point |
| Python |
JSON/dict or text |
InfrahubTransform.transform() |
| Jinja2 |
Text |
.j2 template file |
from infrahub_sdk.transforms import InfrahubTransform
class MyTransform(InfrahubTransform):
query = "my_query"
async def transform(self, data: dict) -> dict:
device = data["DcimDevice"]["edges"][0]["node"]
return {"hostname": device["name"]["value"]}
Workflow
Follow these steps when creating a transform:
- Choose the transform type — Python for JSON/dict
or complex logic, Jinja2 for text templates, hybrid
for both. Read
rules/types-overview.md.
- Write the GraphQL query — Create a
.gql file
that fetches the data to transform. Read
../infrahub-common/graphql-queries.md
for query patterns.
- Implement the transform — For Python, inherit
from
InfrahubTransform and implement transform().
Read rules/python-transform.md.
For Jinja2, create a .j2 template. Read
rules/jinja2-template.md.
For hybrid, read
rules/hybrid-python-jinja2.md.
- Connect to artifacts — If the transform output
should be stored as a file, configure artifact
definitions. See
rules/artifacts-definitions.md.
- Register in .infrahub.yml — Add under
python_transforms or jinja2_transforms. See
rules/api-reference.md.
- Add tests — Create YAML-driven test definitions
(smoke, unit, integration) alongside the transform so
it is validated automatically in the proposed change
pipeline. Read
rules/testing-resource-framework.md.
- Test locally — Run
infrahubctl transform or
infrahubctl render to validate. See
rules/testing-commands.md.
Supporting References
1---2name: infrahub-managing-transforms-33description: Creates Infrahub transforms that convert data into JSON, text, CSV, or device configs using Python or Jinja2 templates, with YAML-driven tests. TRIGGER when: building config generation, data export, format conversion, Jinja2 templates, artifact pipelines, writing or running tests for a transform. DO NOT TRIGGER when: designing schemas, writing validation checks, creating generators, querying live data.4---56# Infrahub Transform Creator78## Overview910Expert guidance for creating Infrahub transforms.11Transforms convert Infrahub data into different formats12-- JSON, text, CSV, device configs, or any text-based13output -- using Python classes or Jinja2 templates.1415## Project Context1617Infrahub config:18!`cat .infrahub.yml 2>/dev/null || echo "No .infrahub.yml found"`1920Existing transforms:21!`find . -name "*.py" -path "*/transforms/*" -o -name "*.j2" -path "*/templates/*" 2>/dev/null | head -20`2223## When to Use2425- Building data transformations (Infrahub data -> another format)26- Generating device configurations from infrastructure data27- Creating CSV reports, cable matrices, or inventory exports28- Rendering Jinja2 templates with query data29- Combining Python logic with Jinja2 rendering30- Connecting transforms to artifacts for automated output3132## Rule Categories3334| Priority | Category | Prefix | Description |35| -------- | --------- | ------------ | ------------------------------------------------------ |36| CRITICAL | Types | `types-` | Python vs Jinja2 choice |37| CRITICAL | Python | `python-` | InfrahubTransform class |38| CRITICAL | Jinja2 | `jinja2-` | Template syntax, filters |39| HIGH | Hybrid | `hybrid-` | Python + Jinja2 combined |40| HIGH | Artifacts | `artifacts-` | Output files, targets |41| HIGH | API Ref | `api-` | Class attrs, methods |42| MEDIUM | Patterns | `patterns-` | Utilities, CSV, shared |43| HIGH | Testing | `testing-` | Resources Testing Framework, transform/render commands |4445## Schema Features This Skill Depends On4647A transform reads schema-shaped data and produces a48file. Misalignment between the transform and the49schema fails late — at artifact-render time, when50someone is waiting for the output.5152| If the transform... | The schema (or .infrahub.yml) must... | See |53| ------------------- | ------------------------------------- | --- |54| Will feed an `artifact_definitions` entry | The target node must `inherit_from: CoreArtifactTarget` so the artifact pipeline can attach to it | [../infrahub-managing-schemas/rules/extension-artifact-target.md](../infrahub-managing-schemas/rules/extension-artifact-target.md) |55| Reads attributes from a node | Define those attributes with their full `__value` access path in GraphQL — silent empty strings come from accessing the node, not the value | [../infrahub-managing-schemas/rules/attribute-defaults-and-types.md](../infrahub-managing-schemas/rules/attribute-defaults-and-types.md) |56| Picks a template per device by platform/role | The schema must expose that platform/role as a real attribute or relationship — string-matching on `display_label` is brittle | [../infrahub-managing-schemas/rules/display-human-friendly-id.md](../infrahub-managing-schemas/rules/display-human-friendly-id.md) |57| Is referenced from `artifact_definitions.transformation` | The transform's registered `name` must match the `transformation:` field exactly — mismatch produces "transformation not found" at render time | [rules/artifacts-definitions.md](./rules/artifacts-definitions.md) |58| Uses Jinja2 (not Python) | Register under `jinja2_transforms` with a top-level `query:` field — `python_transforms` binds query on the class, the two keys are not interchangeable | [rules/api-reference.md](./rules/api-reference.md) |5960## Before writing Python6162If the transform body is string formatting — f-strings,63concatenation, conditional sections — Jinja2 expresses64the same output in fewer lines, renders directly in65the proposed-change UI, and lives under66`jinja2_transforms` in `.infrahub.yml` instead of67`python_transforms`. Walk this ladder before reaching68for `InfrahubTransform`:6970| Signal | Cheaper layer | See rule |71| ------ | ------------- | -------- |72| Transform body is `return f"..."` or `"\n".join([...])` built from query results | Jinja2 template file | [yagni-python-transform-that-could-be-jinja2](../infrahub-auditing-repo/rules/yagni-python-transform-that-could-be-jinja2.md) |73| Transform copies query data verbatim without computation | The GraphQL query alone — no transform needed | Ladder step 1 (drop the requirement); judgment call, no rule |74| Conditionals are `if x: out += ...; else: out += ...` and nothing else | Jinja2 `{% if %}` blocks | [yagni-python-transform-that-could-be-jinja2](../infrahub-auditing-repo/rules/yagni-python-transform-that-could-be-jinja2.md) |7576Use Python when the transform parses, computes, or77reshapes — IP/subnet math, hashing, ordered78aggregation, structural JSON re-shaping. See79[rules/python-transform.md](./rules/python-transform.md)80for the legitimate cases.8182When the transform reads objects through the SDK, type those calls with83generated protocol classes rather than string kinds — `client.filters(NetworkLink, ...)`,84not `kind="NetworkLink"` — so schema drift fails type-check instead of at85runtime. See86[protocols-adopt-typed-kinds](../infrahub-common/rules/protocols-adopt-typed-kinds.md).8788## Transform Basics8990Two types of transforms:9192| Type | Output | Entry Point |93| ---------- | ----------------- | ------------------------------- |94| **Python** | JSON/dict or text | `InfrahubTransform.transform()` |95| **Jinja2** | Text | `.j2` template file |9697```python98from infrahub_sdk.transforms import InfrahubTransform99100class MyTransform(InfrahubTransform):101 query = "my_query"102103 async def transform(self, data: dict) -> dict:104 device = data["DcimDevice"]["edges"][0]["node"]105 return {"hostname": device["name"]["value"]}106```107108## Workflow109110Follow these steps when creating a transform:1111121. **Choose the transform type** — Python for JSON/dict113 or complex logic, Jinja2 for text templates, hybrid114 for both. Read115 [rules/types-overview.md](./rules/types-overview.md).1162. **Write the GraphQL query** — Create a `.gql` file117 that fetches the data to transform. Read118 [../infrahub-common/graphql-queries.md](../infrahub-common/graphql-queries.md)119 for query patterns.1203. **Implement the transform** — For Python, inherit121 from `InfrahubTransform` and implement `transform()`.122 Read [rules/python-transform.md](./rules/python-transform.md).123 For Jinja2, create a `.j2` template. Read124 [rules/jinja2-template.md](./rules/jinja2-template.md).125 For hybrid, read126 [rules/hybrid-python-jinja2.md](./rules/hybrid-python-jinja2.md).1274. **Connect to artifacts** — If the transform output128 should be stored as a file, configure artifact129 definitions. See130 [rules/artifacts-definitions.md](./rules/artifacts-definitions.md).1315. **Register in .infrahub.yml** — Add under132 `python_transforms` or `jinja2_transforms`. See133 [rules/api-reference.md](./rules/api-reference.md).1346. **Add tests** — Create YAML-driven test definitions135 (smoke, unit, integration) alongside the transform so136 it is validated automatically in the proposed change137 pipeline. Read138 [rules/testing-resource-framework.md](./rules/testing-resource-framework.md).1397. **Test locally** — Run `infrahubctl transform` or140 `infrahubctl render` to validate. See141 [rules/testing-commands.md](./rules/testing-commands.md).142143## Supporting References144145- **[reference.md](./reference.md)** -- Class API,146 lifecycle, return-type matrix, `.infrahub.yml`147 registration shapes, filter env overview148- **[examples.md](./examples.md)** -- Complete transform149 patterns (Python, Jinja2, hybrid, CSV)150- **[../infrahub-common/graphql-queries.md](../infrahub-common/graphql-queries.md)**151 -- GraphQL query writing reference152- **[infrahub-yml-reference.md](../infrahub-common/infrahub-yml-reference.md)**153 -- .infrahub.yml project configuration154- **[../infrahub-common/rules/](../infrahub-common/rules/)** -- Shared rules155 (git integration, caching) across all skills156- **[../infrahub-common/rules/workflow-information-priority.md](../infrahub-common/rules/workflow-information-priority.md)**157 -- Skill content first; how to consult `docs.infrahub.app`158 on a genuine gap (e.g. deleting nodes)159- **[../infrahub-managing-schemas/SKILL.md](../infrahub-managing-schemas/SKILL.md)**160 -- Schema definitions transforms work with161- **[rules/](./rules/)** -- Individual rules by category