Materialize and backfill
Factories are in preview. If you want changes or improvements, talk to the Union team.
Once a factory is declared, there is one verb for running it: materialize. You name an artifact and a partition or a range, and the factory builds whatever is needed to produce it.
Deploy
Deploy the tasks, then the factory:
import flyte
flyte.init_from_config()
flyte.deploy(env) # the tasks
analytics.deploy() # the factoryflyte deploy analytics.py env
flyte factory deploy analytics.pyflyte factory deploy checks every build against its deployed task, including argument names, required parameters, partition mappings, and the number of outputs. It checks every source against the registry, then registers the factory as a task of type factory. Mistakes fail here, with a message per build, instead of halfway through a backfill. Add --dry-run (or analytics.deploy(dryrun=True)) to validate without registering.
One partition
run = analytics.materialize(daily_report, date="2026-08-02")
run.wait()flyte factory materialize analytics daily_report --partition date=2026-08-02 --waitThe factory walks back from daily_report[2026-08-02]. That partition needs features for three days, each of which needs events for every region, each of which needs raw_events. The whole materialization is one run, and each partition built along the way is a child action you can open in the UI.
In Python, materialize returns an ordinary flyte.remote.Run.
A range: backfill
A backfill is the same command over a range:
run = analytics.materialize(daily_report, date="2026-08-01..2026-08-31")flyte factory materialize analytics daily_report --partition date=2026-08-01..2026-08-31a..b is an inclusive range, expanded at the partition’s granularity, and a,b lists values. The range crosses with the other keys, so this builds events for every region on every day that a report needs. Partitions build in parallel. Ordering follows the graph, not the calendar: the report for August 31 waits only on its own three-day window.
- Rerunning resumes. Run the same command again. Everything already built is a cache hit, so only the failed partitions and what depends on them run. There is no separate backfill object or resume command.
- Failures are partial. Partitions that succeeded are published and usable. A failed partition blocks only what depends on it, and the run fails naming them.
- A missing source blocks only its dependents. If
raw_eventshas no version for one day, the rest of the range still builds.
Preview the plan
run = analytics.materialize(daily_report, date="2026-08-01..2026-08-31", plan_only=True)
run.wait()flyte factory plan analytics daily_report --partition date=2026-08-01..2026-08-31plan is short for materialize --plan --wait. It launches nothing and prints, per artifact, how many partitions are in each state:
| State | Meaning |
|---|---|
source |
A source partition found in the registry |
missing |
A source partition that doesn’t exist, or a build partition with no published version yet |
existing |
A published version exists. The cache decides at run time whether it is reused |
reused |
During a run: a cache hit, nothing rebuilt |
built |
During a run: the task ran and a new version was published |
failed / blocked |
The task failed, or something upstream of it did |
Freshness is the task cache
A partition is fresh if calling its task with the same inputs is a cache hit. That means the same task version, the same upstream artifact versions, the same partition values, and the same constants. So there’s nothing to configure:
- A code change to
cleanchanges its cache version, soeventsand everything downstream rebuild. - A new upstream version, such as a corrected
raw_eventsfor one day, changes the inputs for that day only. - A task without caching rebuilds every time. Use
cache="auto"on factory tasks.
To force work anyway:
# Treat every partition of one build as stale.
analytics.materialize(daily_report, date="2026-08-01..2026-08-31", rebuild=["events"])
# Ignore the cache everywhere.
analytics.materialize(daily_report, date="2026-08-01..2026-08-31", rebuild_all=True)# Treat every partition of one build as stale.
flyte factory materialize analytics daily_report --partition date=2026-08-01..2026-08-31 --rebuild events
# Ignore the cache everywhere.
flyte factory materialize analytics daily_report --partition date=2026-08-01..2026-08-31 --rebuild-all--rebuild names a build by an artifact it makes, since the same task can back several builds. A task name also works.
Other options
| Python | CLI | Effect |
|---|---|---|
params={"events": {"min_quality": 20}} |
--param events.min_quality=20 |
Override a build’s constant for this materialization only |
concurrency=50 |
--concurrency 50 |
Limit how many partitions build at once |
queue="gpu" |
--queue gpu |
Run the materialization and every build on this queue |
downstream=True |
--downstream |
Also refresh downstream partitions that become stale |
run.wait() |
--wait |
Wait for the run to finish. The CLI also prints the plan with results |
What gets published
Every artifact built during a materialization is published as a version with its partition values, not only the target you asked for. Each version’s source is the action that ran the task, so lineage points at the work that made it. From there the results behave like any other artifact: retrieve them by partition, trigger on them, or mount them in an app.
Inspect factories
print(analytics.graph()) # the definition, as Mermaid
print(analytics.validate()) # structural problems, if anyflyte factory get # factories in this project and domain
flyte factory get analytics --graph # one factory's definition, as Mermaid