> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sqlbuild.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 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](/concepts/python-nodes/tasks), [assets](/concepts/python-nodes/assets), [loaders](/concepts/python-nodes/loaders), or [checks](/concepts/python-nodes/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:

```python theme={null}
# factories/exports.py
from sqlbuild.factories import factory
from sqlbuild.assets import asset

TABLES = ["orders", "customers", "payments"]


@factory
def 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 nodes
```

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:

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

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

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:

```python theme={null}
# factories/orders.py
from sqlbuild.factories import factory
from sqlbuild.loaders import loader
from sqlbuild.assets import asset
from sqlbuild.checks import check


@factory
def 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](/concepts/python-nodes/overview#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.
