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
- Changing what an existing transform or template renders
- Debugging wrong output, or an artifact that fails to generate
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.
| If the transform... | The schema (or .infrahub.yml) must... | See |
|---|---|---|
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 |
| 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 |
| 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 |
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 |
| 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 |
| Is a Python transform | Carry a watch: block naming every first-party module it imports — sibling modules included, since imports are never followed. Without the key, Infrahub cannot trust the dependency list and re-renders the artifacts on every commit |
rules/artifacts-watch-dependencies.md |
| Reads a data file at runtime, or includes a template by variable name | Name those paths in watch.files — detection cannot see a path that exists only as a string, so the artifacts go stale on a change with no error raised |
rules/artifacts-watch-dependencies.md |
| Has its artifacts regenerated by a caller (catalog page, CI job, orchestrator) | That caller must poll CoreArtifact until each artifact is body-ready — the POST only queues the regen, and the node exists before its content does |
rules/artifacts-async-regen-polling.md |
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 | Whatever the artifact's content_type asks for: a dict only under application/json or application/yaml, a str for the other six |
InfrahubTransform.transform() |
| Jinja2 | Text | .j2 template file |
from infrahub_sdk.transforms import InfrahubTransform
class MyTransform(InfrahubTransform):
query = "my_query"
# Returns a dict, so the artifact definition has to declare
# content_type: application/json (or application/yaml). Under any
# other content type this dict is stored as str(dict), with no
# error. See rules/artifacts-definitions.md.
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
.gqlfile that fetches the data to transform. Read ../infrahub-common/graphql-queries.md for query patterns. - Implement the transform — For Python, inherit
from
InfrahubTransformand implementtransform(). Read rules/python-transform.md. For Jinja2, create a.j2template. 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_transformsorjinja2_transforms. See rules/api-reference.md. Then declare the transform's dependencies withwatch.files: read the entry point's imports and runtime file reads, and name every first-party path they resolve to. A Python transform should always carry the key —files: []when it genuinely has no dependency beyond its own file. Read rules/artifacts-watch-dependencies.md, which also covers reviewing an existingwatchblock for entries that are missing, stale, or wrong. - 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 transformorinfrahubctl renderto validate. See rules/testing-commands.md.
Supporting References
- rules/artifacts-definitions.md
-- The eight
content_typevalues and how each serialises the transform's return value. Authority on which return type a content type accepts - reference.md -- Class API,
lifecycle,
.infrahub.ymlregistration shapes, filter env overview - examples.md -- Complete transform patterns (Python, Jinja2, hybrid, CSV)
- ../infrahub-common/graphql-queries.md -- GraphQL query writing reference
- infrahub-yml-reference.md -- .infrahub.yml project configuration
- ../infrahub-common/rules/ -- Shared rules (git integration, caching) across all skills
- ../infrahub-common/rules/workflow-information-priority.md
-- Skill content first; how to consult
docs.infrahub.appon a genuine gap (e.g. deleting nodes) - ../infrahub-managing-schemas/SKILL.md -- Schema definitions transforms work with
- rules/ -- Individual rules by category