Skip to content

Commit c0d0d14

Browse files
committed
[pac] Update status resolution logic
1 parent bf76c19 commit c0d0d14

5 files changed

Lines changed: 409 additions & 37 deletions

File tree

python_modules/dagster/dagster/_core/asset_graph_view/asset_graph_view.py

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import functools
23
from collections.abc import Awaitable, Iterable
34
from datetime import datetime, timedelta
@@ -162,10 +163,7 @@ def get_inner_queryer_for_back_compat(self) -> "CachingInstanceQueryer":
162163
return self._queryer
163164

164165
def _get_partitions_def(self, key: T_EntityKey) -> Optional["PartitionsDefinition"]:
165-
if isinstance(key, AssetKey):
166-
return self.asset_graph.get(key).partitions_def
167-
else:
168-
return None
166+
return self.asset_graph.get(key).partitions_def
169167

170168
@cached_method
171169
@use_partition_loading_context
@@ -374,6 +372,16 @@ def get_asset_subset_from_asset_partitions(
374372
)
375373
return EntitySubset(self, key=key, value=_ValidatedEntitySubsetValue(value))
376374

375+
@use_partition_loading_context
376+
def get_subset_from_partition_keys(
377+
self,
378+
key: T_EntityKey,
379+
partitions_def: "PartitionsDefinition",
380+
partition_keys: AbstractSet[str],
381+
) -> EntitySubset[T_EntityKey]:
382+
value = partitions_def.subset_with_partition_keys(partition_keys)
383+
return EntitySubset(self, key=key, value=_ValidatedEntitySubsetValue(value))
384+
377385
@use_partition_loading_context
378386
def compute_parent_subset_and_required_but_nonexistent_subset(
379387
self, parent_key, subset: EntitySubset[T_EntityKey]
@@ -548,11 +556,19 @@ def compute_latest_time_window_subset(
548556
check.failed(f"Unsupported partitions_def: {partitions_def}")
549557

550558
async def compute_subset_with_status(
551-
self, key: AssetCheckKey, status: Optional["AssetCheckExecutionResolvedStatus"]
552-
):
559+
self,
560+
key: AssetCheckKey,
561+
status: Optional["AssetCheckExecutionResolvedStatus"],
562+
from_subset: EntitySubset,
563+
) -> EntitySubset[AssetCheckKey]:
564+
"""Returns the subset of an asset check that matches a given status."""
553565
from dagster._core.storage.event_log.base import AssetCheckSummaryRecord
554566

555-
"""Returns the subset of an asset check that matches a given status."""
567+
# Handle partitioned asset checks with partition-level granularity
568+
if self._get_partitions_def(key):
569+
return await self._get_partitioned_check_subset_with_status(key, status, from_subset)
570+
571+
# Handle non-partitioned asset checks with existing logic
556572
summary = await AssetCheckSummaryRecord.gen(self, key)
557573
latest_record = summary.last_check_execution_record if summary else None
558574
resolved_status = (
@@ -586,31 +602,61 @@ async def compute_subset_with_freshness_state(
586602
return self.get_empty_subset(key=key)
587603

588604
async def _compute_run_in_progress_check_subset(
589-
self, key: AssetCheckKey
605+
self, key: AssetCheckKey, from_subset: EntitySubset
590606
) -> EntitySubset[AssetCheckKey]:
591607
from dagster._core.storage.asset_check_execution_record import (
592608
AssetCheckExecutionResolvedStatus,
593609
)
594610

595611
return await self.compute_subset_with_status(
596-
key, AssetCheckExecutionResolvedStatus.IN_PROGRESS
612+
key, AssetCheckExecutionResolvedStatus.IN_PROGRESS, from_subset
597613
)
598614

599615
async def _compute_execution_failed_check_subset(
600-
self, key: AssetCheckKey
616+
self, key: AssetCheckKey, from_subset: EntitySubset
601617
) -> EntitySubset[AssetCheckKey]:
602618
from dagster._core.storage.asset_check_execution_record import (
603619
AssetCheckExecutionResolvedStatus,
604620
)
605621

606622
return await self.compute_subset_with_status(
607-
key, AssetCheckExecutionResolvedStatus.EXECUTION_FAILED
623+
key, AssetCheckExecutionResolvedStatus.EXECUTION_FAILED, from_subset
608624
)
609625

610626
async def _compute_missing_check_subset(
611-
self, key: AssetCheckKey
627+
self, key: AssetCheckKey, from_subset: EntitySubset
628+
) -> EntitySubset[AssetCheckKey]:
629+
return await self.compute_subset_with_status(key, None, from_subset)
630+
631+
async def _get_asset_check_partition_status(
632+
self, key: AssetCheckKey, partition: str
633+
) -> Optional["AssetCheckExecutionResolvedStatus"]:
634+
# NOTE: we should add a LoadingContext-native version of this
635+
record = self.instance.event_log_storage.get_latest_asset_check_execution_by_key(
636+
[key], partition
637+
).get(key)
638+
if record:
639+
targets_latest = await record.targets_latest_materialization(self)
640+
return await record.resolve_status(self) if targets_latest else None
641+
else:
642+
return None
643+
644+
async def _get_partitioned_check_subset_with_status(
645+
self,
646+
key: AssetCheckKey,
647+
status: Optional["AssetCheckExecutionResolvedStatus"],
648+
from_subset: EntitySubset,
612649
) -> EntitySubset[AssetCheckKey]:
613-
return await self.compute_subset_with_status(key, None)
650+
check_node = self.asset_graph.get(key)
651+
if not check_node or not check_node.partitions_def:
652+
check.failed(f"Asset check {key} not found or not partitioned.")
653+
654+
partitions = list(from_subset.expensively_compute_partition_keys())
655+
statuses = await asyncio.gather(
656+
*(self._get_asset_check_partition_status(key, p) for p in partitions)
657+
)
658+
matching_partitions = {p for p, s in zip(partitions, statuses) if s == status}
659+
return from_subset.compute_intersection_with_partition_keys(matching_partitions)
614660

615661
async def _compute_run_in_progress_asset_subset(self, key: AssetKey) -> EntitySubset[AssetKey]:
616662
from dagster._core.storage.partition_status_cache import AssetStatusCacheValue
@@ -735,15 +781,21 @@ async def _compute_missing_asset_subset(
735781
)
736782

737783
@cached_method
738-
async def compute_run_in_progress_subset(self, *, key: EntityKey) -> EntitySubset:
784+
async def compute_run_in_progress_subset(
785+
self, *, key: EntityKey, from_subset: EntitySubset
786+
) -> EntitySubset:
739787
return await _dispatch(
740788
key=key,
741-
check_method=self._compute_run_in_progress_check_subset,
789+
check_method=functools.partial(
790+
self._compute_run_in_progress_check_subset, from_subset=from_subset
791+
),
742792
asset_method=self._compute_run_in_progress_asset_subset,
743793
)
744794

745795
@cached_method
746-
async def compute_backfill_in_progress_subset(self, *, key: EntityKey) -> EntitySubset:
796+
async def compute_backfill_in_progress_subset(
797+
self, *, key: EntityKey, from_subset: EntitySubset
798+
) -> EntitySubset:
747799
async def get_empty_subset(key: EntityKey) -> EntitySubset:
748800
return self.get_empty_subset(key=key)
749801

@@ -755,10 +807,14 @@ async def get_empty_subset(key: EntityKey) -> EntitySubset:
755807
)
756808

757809
@cached_method
758-
async def compute_execution_failed_subset(self, *, key: EntityKey) -> EntitySubset:
810+
async def compute_execution_failed_subset(
811+
self, *, key: EntityKey, from_subset: EntitySubset
812+
) -> EntitySubset:
759813
return await _dispatch(
760814
key=key,
761-
check_method=self._compute_execution_failed_check_subset,
815+
check_method=functools.partial(
816+
self._compute_execution_failed_check_subset, from_subset=from_subset
817+
),
762818
asset_method=self._compute_execution_failed_asset_subset,
763819
)
764820

@@ -768,7 +824,9 @@ async def compute_missing_subset(
768824
) -> EntitySubset:
769825
return await _dispatch(
770826
key=key,
771-
check_method=self._compute_missing_check_subset,
827+
check_method=functools.partial(
828+
self._compute_missing_check_subset, from_subset=from_subset
829+
),
772830
asset_method=functools.partial(
773831
self._compute_missing_asset_subset, from_subset=from_subset
774832
),

python_modules/dagster/dagster/_core/asset_graph_view/entity_subset.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,13 @@ def convert_to_serializable_subset(self) -> SerializableEntitySubset[T_EntityKey
6969
return SerializableEntitySubset(key=self._key, value=self._value)
7070

7171
def expensively_compute_partition_keys(self) -> AbstractSet[str]:
72-
return {
73-
check.not_none(akpk.partition_key, "No None partition keys")
74-
for akpk in self.expensively_compute_asset_partitions()
75-
}
72+
internal_value = self.get_internal_value()
73+
if isinstance(internal_value, PartitionsSubset):
74+
return set(internal_value.get_partition_keys())
75+
elif internal_value:
76+
check.failed("Subset is not partitioned")
77+
else:
78+
return set()
7679

7780
def expensively_compute_asset_partitions(self) -> AbstractSet[AssetKeyPartitionKey]:
7881
if not isinstance(self.key, AssetKey):
@@ -106,11 +109,13 @@ def compute_intersection(self, other: Self) -> Self:
106109
return self._oper(other, operator.and_)
107110

108111
def compute_intersection_with_partition_keys(
109-
self: "EntitySubset[AssetKey]", partition_keys: AbstractSet[str]
110-
) -> "EntitySubset[AssetKey]":
111-
key = check.inst(self.key, AssetKey)
112-
partition_subset = self._asset_graph_view.get_asset_subset_from_asset_partitions(
113-
self.key, {AssetKeyPartitionKey(key, pk) for pk in partition_keys}
112+
self: "EntitySubset[T_EntityKey]", partition_keys: AbstractSet[str]
113+
) -> "EntitySubset[T_EntityKey]":
114+
if self.partitions_def is None:
115+
return self._asset_graph_view.get_empty_subset(key=self.key)
116+
117+
partition_subset = self._asset_graph_view.get_subset_from_partition_keys(
118+
self.key, self.partitions_def, partition_keys
114119
)
115120
return self.compute_intersection(partition_subset)
116121

python_modules/dagster/dagster/_core/definitions/declarative_automation/operands/operands.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ def name(self) -> str:
102102
return "run_in_progress"
103103

104104
async def compute_subset(self, context: AutomationContext) -> EntitySubset: # pyright: ignore[reportIncompatibleMethodOverride]
105-
return await context.asset_graph_view.compute_run_in_progress_subset(key=context.key)
105+
return await context.asset_graph_view.compute_run_in_progress_subset(
106+
key=context.key, from_subset=context.candidate_subset
107+
)
106108

107109

108110
@whitelist_for_serdes
@@ -113,7 +115,9 @@ def name(self) -> str:
113115
return "backfill_in_progress"
114116

115117
async def compute_subset(self, context: AutomationContext) -> EntitySubset: # pyright: ignore[reportIncompatibleMethodOverride]
116-
return await context.asset_graph_view.compute_backfill_in_progress_subset(key=context.key)
118+
return await context.asset_graph_view.compute_backfill_in_progress_subset(
119+
key=context.key, from_subset=context.candidate_subset
120+
)
117121

118122

119123
@whitelist_for_serdes(storage_name="FailedAutomationCondition")
@@ -124,7 +128,9 @@ def name(self) -> str:
124128
return "execution_failed"
125129

126130
async def compute_subset(self, context: AutomationContext) -> EntitySubset: # pyright: ignore[reportIncompatibleMethodOverride]
127-
return await context.asset_graph_view.compute_execution_failed_subset(key=context.key)
131+
return await context.asset_graph_view.compute_execution_failed_subset(
132+
key=context.key, from_subset=context.candidate_subset
133+
)
128134

129135

130136
@whitelist_for_serdes
@@ -322,5 +328,5 @@ async def compute_subset( # pyright: ignore[reportIncompatibleMethodOverride]
322328
else AssetCheckExecutionResolvedStatus.FAILED
323329
)
324330
return await context.asset_graph_view.compute_subset_with_status(
325-
key=context.key, status=target_status
331+
key=context.key, status=target_status, from_subset=context.candidate_subset
326332
)

python_modules/dagster/dagster/_core/storage/asset_check_execution_record.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,28 @@ async def resolve_status(
173173
check.failed(f"Unexpected status {self.status}")
174174

175175
async def targets_latest_materialization(self, loading_context: LoadingContext) -> bool:
176-
from dagster._core.storage.event_log.base import AssetRecord
176+
from dagster._core.storage.event_log.base import AssetRecord, AssetRecordsFilter
177177

178178
resolved_status = await self.resolve_status(loading_context)
179179
if resolved_status == AssetCheckExecutionResolvedStatus.IN_PROGRESS:
180180
# all in-progress checks execute against the latest version
181181
return True
182182

183-
asset_record = await AssetRecord.gen(loading_context, self.key.asset_key)
184-
latest_materialization = (
185-
asset_record.asset_entry.last_materialization_record if asset_record else None
186-
)
183+
if self.partition is None:
184+
asset_record = await AssetRecord.gen(loading_context, self.key.asset_key)
185+
latest_materialization = (
186+
asset_record.asset_entry.last_materialization_record if asset_record else None
187+
)
188+
else:
189+
records = loading_context.instance.fetch_materializations(
190+
AssetRecordsFilter(
191+
asset_key=self.key.asset_key,
192+
asset_partitions=[self.partition],
193+
),
194+
limit=1,
195+
)
196+
latest_materialization = records.records[0] if records.records else None
197+
187198
if not latest_materialization:
188199
# no previous materialization, so it's executing against the lastest version
189200
return True

0 commit comments

Comments
 (0)