Python Nodes
Factories
Generate Python nodes programmatically with @factory.
A factory is a function that generates Python nodes instead of authoring them one at a time. Use @factory when you want to create many similar tasks, assets, loaders, or checks from a list, a config, or a loop, rather than hand-writing each one.
Factories are an advanced feature. Most projects author nodes directly; reach for a factory when you find yourself copy-pasting near-identical node definitions.
Defining a factory
Section titled “Defining a factory”A @factory function takes no arguments and returns one or more decorated node functions:
from sqlbuild.factories import factoryfrom sqlbuild.assets import asset
TABLES = ["orders", "customers", "payments"]
@factorydef export_assets(): nodes = [] for table in TABLES: @asset(name=f"export_{table}", tags=("export",)) def export(ctx, table=table): return ctx.result(metadata={"table": table}, materialized=True) nodes.append(export) return nodesThe returned nodes are discovered and added to the graph exactly as if you had written them by hand. They participate in selection, lifecycle, the DAG artifact, and integrations like any other node.
A factory may return a single node or a list, tuple, or set of nodes. Every returned item must be a decorated @task, @asset, @loader, or @check function.
Folder rules
Section titled “Folder rules”Where a factory lives determines what it is allowed to emit:
| Location | May emit |
|---|---|
loaders/ |
loaders only |
tasks/ |
tasks only |
assets/ |
assets only |
checks/ |
checks only |
factories/ |
any kind, including a mix |
- A single-kind factory can live in that kind’s folder (e.g. an asset-only factory in
assets/), keeping it next to the nodes it generates. It can also live infactories/. - A factory that emits more than one kind must live in
factories/.
This keeps each kind folder honest: everything in assets/, whether hand-written or factory-generated, is an asset.
SQLBuild enforces this at discovery time. A factory in a kind folder that returns a foreign kind raises an error pointing you to factories/:
Factory export_pipeline in assets/ returned a loader 'raw_orders';mixed-kind factories must live in factories/.The factories/ folder is not created by sqb init - add it when you need it.
Mixed-kind factories
Section titled “Mixed-kind factories”A factory in factories/ can generate a whole related pipeline at once - a loader, the asset that reads it, and a check on the result:
from sqlbuild.factories import factoryfrom sqlbuild.loaders import loaderfrom sqlbuild.assets import assetfrom sqlbuild.checks import check
@factorydef orders_pipeline(): @loader(name="raw_orders") def load(ctx): return fetch_orders()
@asset(name="orders_export", depends_on=load) def export(ctx): return ctx.result(materialized=True)
@check(depends_on=export) def orders_export_check(ctx): return ctx.pass_("export ready")
return [load, export, orders_export_check]Generated nodes follow the same rules as directly-authored ones, including the SQL boundary: a factory-generated loader still binds to a managed source, and a factory-generated check still may not depend on SQL models.
Naming
Section titled “Naming”Generated nodes need unique names across the project. Pass an explicit name= to each node a factory creates (factories almost always generate names from a loop variable or config), since relying on the function’s own name would produce duplicates.