Dagster cheat sheet
Software-defined assets, resources, partitions, and sensors — the Dagster model that differs most from Airflow.
Assets, not tasks
@asset def orders_raw() -> pd.DataFrame: return pd.read_parquet("s3://lake/bronze/orders/")- An asset declares the table it produces, not a step to run. Dagster derives the graph from what each asset depends on.
@asset def orders_enriched(orders_raw, customers): return orders_raw.merge(customers, on="customer_id")- Dependencies come from parameter names. No explicit set_upstream wiring anywhere in the project.
@asset(key_prefix=["silver"], group_name="orders")- Namespacing and grouping. Key prefixes become schema paths in the asset lineage view.
AssetOut / @multi_asset- For one computation that writes several tables — a common ELT shape that Airflow forces into artificial task splits.
Resources and config
@asset def orders(snowflake: SnowflakeResource): with snowflake.get_connection() as conn: ...- Resources are injected by type annotation, which makes swapping a real warehouse for a fake in tests trivial.
defs = Definitions(assets=[...], resources={"snowflake": SnowflakeResource(...)})- The single entry point Dagster loads. Environment differences live here, not scattered through asset code.
EnvVar("SNOWFLAKE_PASSWORD")- Late-bound secret resolution — the value is read at run time, so it never lands in a serialized definition.
Partitions and backfills
@asset(partitions_def=DailyPartitionsDefinition(start_date="2026-01-01"))- Declares the asset as one row of data per day. Backfilling becomes selecting a partition range in the UI.
context.partition_key- How the asset body learns which date it is computing. The equivalent of Airflow's data_interval_start.
MultiPartitionsDefinition- Two-dimensional partitions, such as date crossed with region. Useful and easy to overuse — the cross product grows fast.
Schedules and sensors
build_schedule_from_partitioned_job(job)- Derives the cron from the partition definition so schedule and partitioning can never drift apart.
@sensor(job=my_job) def new_file_sensor(context): if new_files(): yield RunRequest(run_key=...)- Event-driven triggering. run_key provides deduplication so the same file cannot launch two runs.
@asset_sensor(asset_key=AssetKey("orders_raw"))- Fires when an upstream asset materializes — cross-project dependencies without a shared scheduler.
AutoMaterializePolicy.eager()- Declarative freshness. Dagster decides when to rebuild based on upstream changes instead of a fixed cron.
dbt integration
@dbt_assets(manifest=dbt_manifest_path)- Loads every dbt model as an individual Dagster asset, so lineage spans ingestion and transformation in one graph.
dbt.cli(["build"], context=context).stream()- Streams dbt events into Dagster logs, marking each model asset materialized as it completes.
Testing and CLI
materialize([orders_raw, orders_enriched])- Runs assets in-process from a pytest function — the main practical advantage over testing Airflow DAGs.
dagster dev- Local UI plus daemon in one command. The default development loop.
dagster asset materialize --select orders_enriched- CLI materialization with the same selection syntax the UI uses.
From DataLane — tutorials at/blog, practice SQL live in theplayground.