Subchapter 2.7
references/automation/declarative-automation/advanced.mdMarkdown7 KBView on GitHub
This document covers advanced topics for deep understanding of the declarative automation system.
Understanding the distinction between statuses and events is fundamental to building correct automation conditions.
Statuses are persistent conditions that remain true for multiple evaluation ticks.
Examples:
AutomationCondition.missing() - Stays true until the partition is materializedAutomationCondition.in_progress() - True while a run is executingAutomationCondition.in_latest_time_window() - True for the latest time partition(s)Characteristic: If the underlying state doesn’t change, the status will be true for consecutive evaluations.
Events are transient conditions that are true only on a single evaluation tick.
Examples:
AutomationCondition.newly_updated() - True only on the tick when materialization occursAutomationCondition.code_version_changed() - True only on the first tick after code changesAutomationCondition.cron_tick_passed() - True only on the first tick after the cron tickCharacteristic: Even if evaluated immediately again, the event would be false (assuming no new change).
Status → Event with newly_true():
# missing() is a status (stays true for many ticks)
# newly_true() converts it to an event (true only when becoming missing)
condition = dg.AutomationCondition.missing().newly_true()Use case: Prevent repeated requests during persistent states. A partition stays missing while a run is in progress. Using newly_true() ensures you only request it once.
Two Events → Status with since():
# Both newly_updated() and newly_requested() are events
# since() converts them to a status: "updated more recently than requested"
condition = dg.AutomationCondition.newly_updated().since(dg.AutomationCondition.newly_requested())Use case: Create persistent states from transient events. This condition becomes true when an update occurs and stays true until a request is made.
The default eager() condition uses this pattern:
(
dg.AutomationCondition.newly_missing() | dg.AutomationCondition.any_deps_updated()
).since_last_handled()newly_missing() and any_deps_updated() are eventssince_last_handled() converts them to a status that persists until the asset is requested or updatedRun grouping allows multiple assets to execute in a single run even though downstream assets’ dependencies haven’t been materialized yet.
Consider assets A → B → C, all with eager() conditions:
The will_be_requested() operand is true for assets that will be requested in the current tick. Dependency conditions use this to group assets:
# From any_deps_updated() definition:
dg.AutomationCondition.any_deps_match(
(dg.AutomationCondition.newly_updated() & ~dg.AutomationCondition.executed_with_root_target())
| dg.AutomationCondition.will_be_requested() # Enables run grouping
)When evaluating B:
Two assets can execute in the same run if:
PartitionsDefinition objectsTimeWindowPartitionMapping or IdentityPartitionMappingIf these requirements aren’t met, assets execute in separate runs even with run grouping logic.
Dependency operators (any_deps_match(), all_deps_match()) check conditions on upstream assets. Filtering controls which upstreams are checked.
Only dependencies in the selection are checked:
condition = dg.AutomationCondition.any_deps_match(dg.AutomationCondition.missing()).allow(
dg.AssetSelection.groups("critical")
)If the asset has 10 upstreams but only 2 are in the “critical” group, only those 2 are checked.
Dependencies in the selection are excluded:
condition = dg.AutomationCondition.any_deps_updated().ignore(
dg.AssetSelection.assets("test_data", "staging_data")
)Updates to “test_data” and “staging_data” won’t trigger the condition.
When applied to composite conditions (AND/OR), filtering propagates to all sub-conditions:
# Applies to both any_deps_missing() and any_deps_in_progress() within eager()
condition = dg.AutomationCondition.eager().allow(dg.AssetSelection.groups("production"))What gets filtered: All any_deps_match() and all_deps_match() calls
What doesn’t get filtered: Direct operands like missing() on the asset itself
since_last_handled() is a convenience method that converts events to a status:
condition = dg.AutomationCondition.newly_missing()
# These are equivalent:
condition.since_last_handled()
condition.since(
dg.AutomationCondition.newly_requested()
| dg.AutomationCondition.newly_updated()
| dg.AutomationCondition.initial_evaluation()
)Behavior:
condition becomes trueUse case: Persist an event until it’s “handled” by either requesting or materializing the asset. This prevents duplicate requests while ensuring the event isn’t lost.
dg.AutomationCondition.any_deps_match(
(dg.AutomationCondition.newly_updated() & ~dg.AutomationCondition.executed_with_root_target())
| dg.AutomationCondition.will_be_requested()
)Checks if any dependency has newly updated (excluding same-run updates) OR will be requested this tick.
dg.AutomationCondition.any_deps_match(
dg.AutomationCondition.missing() & ~dg.AutomationCondition.will_be_requested()
)Checks if any dependency is missing AND will NOT be requested this tick. Dependencies that will be requested aren’t considered blocking.
dg.AutomationCondition.all_deps_match(
dg.AutomationCondition.newly_updated().since(
dg.AutomationCondition.cron_tick_passed(cron_schedule, cron_timezone)
)
)For each dependency, checks if it has been updated since the last cron tick. All dependencies must have at least one partition updated since the tick.