Skip to main content
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

A @factory function takes no arguments and returns one or more decorated node functions:
The 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

Where a factory lives determines what it is allowed to emit:
  • 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 in factories/.
  • 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/:
The factories/ folder is not created by sqb init - add it when you need it.

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:
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

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.