ingest(*, outlet_events): outlet_events[sales].extra = {"day": "2026-03-10"} outlet_events[sales].add_partitions("2026-03-10") Same object. One is a dict the scheduler never reads. One is what it schedules on. 28 / 106
Assets — datasets became assets, and the model was reworked underneath, explicitly to make room for partitions. 75 · New Asset-Centric Syntax — @asset, so a Dag can be written data-first instead of task-first. 33 / 106
the timetable the task Decided when before the run exists while the run executes Producer API CronPartitionTimetable PartitionedAtRuntime Use it when the key follows the cadence the key comes from the data 39 / 106
uri="file://incoming/live-region.csv", schedule=PartitionedAtRuntime(), ) def live_region_stats(self, outlet_events): outlet_events[self].add_partitions("tw") PartitionedAtRuntime has no cadence at all — and still produces partitions. 42 / 106
schedule=PartitionedAtRuntime(), ) def live_region_stats(self, outlet_events): outlet_events[self].add_partitions("tw") For a watermark read from the source, a late file, a key from the payload. 45 / 106
format. 2 Write down what your mapper produces. 3 Confirm the strings can be equal. BEFORE YOU DEPLOY The one thing that does fail loudly is coming up in Part 3. 70 / 106
minutes DayWindow twenty-four hours WeekWindow seven days MonthWindow every day of the month QuarterWindow every day of the quarter YearWindow every day of the year SegmentWindow every key you declare 78 / 106
RollupMapper( upstream_mapper=StartOfHourMapper(), window=DayWindow(), wait_policy=MinimumCount(23), ) With the default, the run waits. Forever. 81 / 106
are not queued, and an audit row is written: event="partition fan-out exceeded" Omitted → falls back to [scheduler] partition_mapper_max_downstream_keys, default 1000. 85 / 106
— where keys come from AirflowPlugin.timetables a partition mapper — how keys map AirflowPlugin.partition_mappers a window — what one period contains AirflowPlugin.windows 88 / 106
— producer yes — the date range becomes a partitiondate range PartitionedAssetTimetable — no — listed in consumer DagNonPeriodicScheduleException 91 / 106
daily_sales_summary ✓ 21 received · 3 required, not yet arrived · 2026-03-10 hours 09, 11, 22 status: held, not started "Waiting on 3 of 24" is a fact you read, not a guess. 96 / 106
on the producer — CronPartitionTimetable. Nothing downstream changes yet. Then · one consumer on PartitionedAssetTimetable. Default mapper, nothing to configure. The one real hazard: partitioned events do not trigger non-partition-aware Dags. Move a producer and its consumers together. 100 / 106
NEEDS IF YOUR DATA… REACH FOR upstreams disagree on grain StartOf*Mapper · partition_mapper_config one slice is a region and a day ProductMapper a period is many upstream slices RollupMapper + a window …and one of them is always late MinimumCount on that rollup one coarse input drives many fine runs FanOutMapper + max_downstream_keys a fixed set of names, not a time grain SegmentWindow + FixedKeyMapper 101 / 106
Window, custom partition-aware timetable. Shipped examples: a namespace-stripping mapper, a business-day window, a cron timetable that defers its key to runtime. 102 / 106