Assets
An asset is a named piece of data that should exist and stay current: a table of companies, a file of meter readings, a set of normalized listings. You describe the asset, and Synchronoid decides when and how to build it.
Defining an asset
An asset is a Python function decorated with @sy.asset. The function name is the asset name. The function receives a context and the data of its inputs, and returns or yields records.
import synchronoid as sy @sy.asset(layer="staged", source=companies, retry=sy.Retry(max_attempts=5)) def company_profiles(ctx, raw): for item in raw.records(): yield {"org_id": item["id"], "name": item["name"].strip()}
The decorator describes what the asset is. The function describes how to compute it. This split lets the platform schedule, retry, replay and inspect the asset without understanding your code.
Asset options
| Option | Type | Meaning |
|---|---|---|
layer | "raw", "staged", "published" | Where the output is stored. See storage layers. |
source | sy.Source | The source whose payloads this asset reads or fetches. |
deps | list of assets | Upstream assets. Defines the lineage graph. |
retry | sy.Retry | Retry policy for failures in this asset. |
partitions | sy.Partitions | Splits the asset into independent slices, such as days or years. |
key | string | Record key used for merging and change tracking. |
description | string | Shown in the control room. |
Dependencies and lineage
When you list other assets in deps, their data is passed to your function as an argument with the same name. Synchronoid builds the lineage graph from these declarations.
@sy.asset(layer="published", deps=[company_profiles]) def companies_current(ctx, company_profiles): return company_profiles.merge(key="org_id", track_changes=True)
The argument is a reference to stored data, not a copy. Large inputs are read lazily in batches, so memory use stays low and runs stay fast.
Materialization
Running an asset is called materializing it. Each materialization produces a new version of the output, and records the run id, input versions and timing. You can materialize assets in three ways:
- By hand, in the control room or with
syn materialize company_profiles. - On a schedule or sensor.
- As part of a backfill for past partitions.
Materialization is replay-safe. If you run the same asset twice for the same inputs, the second run produces the same output and creates no duplicates.
Synchronoid guarantees idempotent storage writes. Your function should not cause side effects outside of Synchronoid, such as sending email, unless you guard them with ctx.run_id.
Partitions
A partitioned asset is split into slices that can be run independently. Use partitions when the data naturally divides by time or by key.
@sy.asset(layer="staged", partitions=sy.Partitions.daily(start="2026-01-01")) def meter_readings(ctx, raw): day = ctx.partition # e.g. "2026-03-14" ctx.log.info(f"processing {day}") yield from normalize(raw.records())
If one day fails, only that partition is retried. The rest are not touched.
Checks and metadata
You can attach checks to an asset with @sy.check, as shown in the tutorial. You can also attach metadata, which appears in the control room next to each materialization:
ctx.add_metadata({"row_count": n, "oldest_record": oldest.isoformat()})Asset or step?
Use an asset when you care about the result and want it stored, versioned and visible. Use a step for work inside a pipeline that is only a means to an end. If you are unsure, start with an asset.