Skip to content

Commit fc68c31

Browse files
committed
[pac] Add get_asset_check_partitions storage method
1 parent 1306e55 commit fc68c31

6 files changed

Lines changed: 196 additions & 2 deletions

File tree

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from collections.abc import Iterable
33
from typing import NamedTuple, Optional, cast
44

5+
from dagster_shared.record import record
56
from dagster_shared.serdes import deserialize_value
67

78
import dagster._check as check
@@ -238,3 +239,12 @@ async def targets_latest_materialization(self, loading_context: LoadingContext)
238239
)
239240
else:
240241
check.failed(f"Unexpected check status {resolved_status}")
242+
243+
244+
@record
245+
class AssetCheckPartitionRecord:
246+
partition_key: Optional[str]
247+
last_execution_status: AssetCheckExecutionRecordStatus
248+
last_execution_target_materialization_storage_id: Optional[int]
249+
last_planned_run_id: str
250+
last_event_id: int

python_modules/dagster/dagster/_core/storage/event_log/base.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from dagster._core.storage.asset_check_execution_record import (
3737
AssetCheckExecutionRecord,
3838
AssetCheckExecutionRecordStatus,
39+
AssetCheckPartitionRecord,
3940
)
4041
from dagster._core.storage.dagster_run import DagsterRunStatsSnapshot
4142
from dagster._core.storage.partition_status_cache import get_and_update_asset_status_cache_value
@@ -648,6 +649,15 @@ def get_latest_asset_check_execution_by_key(
648649
"""Get the latest executions for a list of asset checks."""
649650
pass
650651

652+
@abstractmethod
653+
def get_asset_check_partition_records(
654+
self,
655+
check_key: AssetCheckKey,
656+
after_event_storage_id: Optional[int] = None,
657+
) -> Sequence[AssetCheckPartitionRecord]:
658+
"""Get asset check partition records with execution status and planned run info."""
659+
pass
660+
651661
@abstractmethod
652662
def fetch_materializations(
653663
self,

python_modules/dagster/dagster/_core/storage/event_log/sql_event_log.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
COMPLETED_ASSET_CHECK_EXECUTION_RECORD_STATUSES,
6060
AssetCheckExecutionRecord,
6161
AssetCheckExecutionRecordStatus,
62+
AssetCheckPartitionRecord,
6263
)
6364
from dagster._core.storage.dagster_run import DagsterRunStatsSnapshot
6465
from dagster._core.storage.event_log.base import (
@@ -3008,6 +3009,7 @@ def _store_asset_check_evaluation_planned(
30083009
execution_status=AssetCheckExecutionRecordStatus.PLANNED.value,
30093010
evaluation_event=serialize_value(event),
30103011
evaluation_event_timestamp=self._event_insert_timestamp(event),
3012+
evaluation_event_storage_id=event_id,
30113013
)
30123014
for partition_key in partition_keys
30133015
]
@@ -3232,6 +3234,73 @@ def get_latest_asset_check_execution_by_key(
32323234
results[check_key] = AssetCheckExecutionRecord.from_db_row(row, key=check_key)
32333235
return results
32343236

3237+
def get_asset_check_partition_records(
3238+
self,
3239+
check_key: AssetCheckKey,
3240+
after_event_storage_id: Optional[int] = None,
3241+
) -> Sequence[AssetCheckPartitionRecord]:
3242+
check.inst_param(check_key, "check_key", AssetCheckKey)
3243+
check.opt_int_param(after_event_storage_id, "after_event_storage_id")
3244+
3245+
# Build the base filter conditions
3246+
filter_conditions = [
3247+
AssetCheckExecutionsTable.c.asset_key == check_key.asset_key.to_string(),
3248+
AssetCheckExecutionsTable.c.check_name == check_key.name,
3249+
# Historical records may have NULL in the evaluation_event_storage_id column for
3250+
# PLANNED events
3251+
AssetCheckExecutionsTable.c.evaluation_event_storage_id.isnot(None),
3252+
]
3253+
3254+
if after_event_storage_id is not None:
3255+
# Filter records where evaluation_event_storage_id > after_event_storage_id
3256+
filter_conditions.append(
3257+
AssetCheckExecutionsTable.c.evaluation_event_storage_id > after_event_storage_id
3258+
)
3259+
3260+
# Subquery to find the max id for each partition
3261+
latest_ids_subquery = db_subquery(
3262+
db_select(
3263+
[
3264+
db.func.max(AssetCheckExecutionsTable.c.id).label("id"),
3265+
]
3266+
)
3267+
.where(db.and_(*filter_conditions))
3268+
.group_by(AssetCheckExecutionsTable.c.partition)
3269+
)
3270+
3271+
# Main query to get all columns for the latest records
3272+
query = db_select(
3273+
[
3274+
AssetCheckExecutionsTable.c.id,
3275+
AssetCheckExecutionsTable.c.partition,
3276+
AssetCheckExecutionsTable.c.execution_status,
3277+
AssetCheckExecutionsTable.c.evaluation_event_storage_id,
3278+
AssetCheckExecutionsTable.c.materialization_event_storage_id,
3279+
AssetCheckExecutionsTable.c.run_id,
3280+
]
3281+
).select_from(
3282+
AssetCheckExecutionsTable.join(
3283+
latest_ids_subquery,
3284+
AssetCheckExecutionsTable.c.id == latest_ids_subquery.c.id,
3285+
)
3286+
)
3287+
3288+
with self.index_connection() as conn:
3289+
rows = db_fetch_mappings(conn, query)
3290+
3291+
return [
3292+
AssetCheckPartitionRecord(
3293+
partition_key=row["partition"],
3294+
last_execution_status=AssetCheckExecutionRecordStatus(row["execution_status"]),
3295+
last_execution_target_materialization_storage_id=row[
3296+
"materialization_event_storage_id"
3297+
],
3298+
last_planned_run_id=row["run_id"],
3299+
last_event_id=row["evaluation_event_storage_id"],
3300+
)
3301+
for row in rows
3302+
]
3303+
32353304
@property
32363305
def supports_asset_checks(self): # pyright: ignore[reportIncompatibleMethodOverride]
32373306
return self.has_table(AssetCheckExecutionsTable.name)

python_modules/dagster/dagster/_core/storage/event_log/sqlite/sqlite_event_log.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,12 @@ def store_event(self, event: EventLogEntry) -> None:
276276
self.store_asset_event_tags([event], [event_id])
277277

278278
if event.is_dagster_event and event.dagster_event_type in ASSET_CHECK_EVENTS:
279-
self.store_asset_check_event(event, None)
279+
# mirror the event in the cross-run index database
280+
with self.index_connection() as conn:
281+
result = conn.execute(insert_event_statement)
282+
event_id = result.inserted_primary_key[0]
283+
284+
self.store_asset_check_event(event, event_id)
280285

281286
if event.is_dagster_event and event.dagster_event_type in EVENT_TYPE_TO_PIPELINE_RUN_STATUS:
282287
# should mirror run status change events in the index shard

python_modules/dagster/dagster/_core/storage/legacy_storage.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from dagster._core.storage.asset_check_execution_record import (
1717
AssetCheckExecutionRecord,
1818
AssetCheckExecutionRecordStatus,
19+
AssetCheckPartitionRecord,
1920
)
2021
from dagster._core.storage.base_storage import DagsterStorage
2122
from dagster._core.storage.event_log.base import (
@@ -765,6 +766,16 @@ def get_latest_asset_check_execution_by_key(
765766
check_keys, partition=partition
766767
)
767768

769+
def get_asset_check_partition_records(
770+
self,
771+
check_key: "AssetCheckKey",
772+
after_event_storage_id: Optional[int] = None,
773+
) -> Sequence[AssetCheckPartitionRecord]:
774+
return self._storage.event_log_storage.get_asset_check_partition_records(
775+
check_key=check_key,
776+
after_event_storage_id=after_event_storage_id,
777+
)
778+
768779

769780
class LegacyScheduleStorage(ScheduleStorage, ConfigurableClass):
770781
def __init__(self, storage: DagsterStorage, inst_data: Optional[ConfigurableClassData] = None):

python_modules/dagster/dagster_tests/storage_tests/utils/event_log_storage.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7092,6 +7092,42 @@ def test_asset_check_partitioned_planned_and_evaluation(
70927092
assert latest_c[check_key].partition == "c"
70937093
assert latest_c[check_key].status == AssetCheckExecutionRecordStatus.PLANNED
70947094

7095+
# Test get_asset_check_partition_records - returns all partitions with latest status
7096+
partition_records = storage.get_asset_check_partition_records(check_key)
7097+
assert len(partition_records) == 3
7098+
7099+
records_by_partition = {r.partition_key: r for r in partition_records}
7100+
assert set(records_by_partition.keys()) == {"a", "b", "c"}
7101+
7102+
# Verify each partition has correct status
7103+
assert (
7104+
records_by_partition["a"].last_execution_status
7105+
== AssetCheckExecutionRecordStatus.SUCCEEDED
7106+
)
7107+
assert (
7108+
records_by_partition["b"].last_execution_status
7109+
== AssetCheckExecutionRecordStatus.FAILED
7110+
)
7111+
assert (
7112+
records_by_partition["c"].last_execution_status
7113+
== AssetCheckExecutionRecordStatus.PLANNED
7114+
)
7115+
7116+
# Verify all records have the same run_id (all planned in same run)
7117+
assert records_by_partition["a"].last_planned_run_id == run_id
7118+
assert records_by_partition["b"].last_planned_run_id == run_id
7119+
assert records_by_partition["c"].last_planned_run_id == run_id
7120+
7121+
# Verify last_event_id is set for all records (this is the row id in the table)
7122+
assert records_by_partition["a"].last_event_id is not None
7123+
assert records_by_partition["b"].last_event_id is not None
7124+
assert records_by_partition["c"].last_event_id is not None
7125+
7126+
filtered_records = storage.get_asset_check_partition_records(
7127+
check_key, after_event_storage_id=999999
7128+
)
7129+
assert len(filtered_records) == 0
7130+
70957131
def test_asset_check_partitioned_multiple_runs_same_partition(
70967132
self,
70977133
storage: EventLogStorage,
@@ -7107,22 +7143,54 @@ def test_asset_check_partitioned_multiple_runs_same_partition(
71077143
partitions_def = dg.StaticPartitionsDefinition(["a"])
71087144
partitions_subset = partitions_def.subset_with_partition_keys(["a"])
71097145

7110-
# Run 1: Store planned + evaluation for partition "a" with passed=True
7146+
# Run 1: Store planned event for partition "a"
71117147
storage.store_event(
71127148
_create_check_planned_event(run_id_1, check_key, partitions_subset=partitions_subset)
71137149
)
7150+
7151+
# status for partition "a" should be PLANNED
7152+
partition_records = storage.get_asset_check_partition_records(check_key)
7153+
assert len(partition_records) == 1
7154+
record = partition_records[0]
7155+
assert record.partition_key == "a"
7156+
assert record.last_execution_status == AssetCheckExecutionRecordStatus.PLANNED
7157+
assert record.last_planned_run_id == run_id_1
7158+
7159+
# Run 1: Now store evaluation event for partition "a" with passed=True
71147160
storage.store_event(
71157161
_create_check_evaluation_event(run_id_1, check_key, passed=True, partition="a")
71167162
)
71177163

7164+
# status for partition "a" should be SUCCEEDED
7165+
partition_records = storage.get_asset_check_partition_records(check_key)
7166+
assert len(partition_records) == 1
7167+
record = partition_records[0]
7168+
assert record.partition_key == "a"
7169+
assert record.last_execution_status == AssetCheckExecutionRecordStatus.SUCCEEDED
7170+
assert record.last_planned_run_id == run_id_1
7171+
71187172
# Run 2: Store planned + evaluation for partition "a" with passed=False
71197173
storage.store_event(
71207174
_create_check_planned_event(run_id_2, check_key, partitions_subset=partitions_subset)
71217175
)
7176+
7177+
# back to PLANNED
7178+
partition_records = storage.get_asset_check_partition_records(check_key)
7179+
record = partition_records[0]
7180+
assert record.last_execution_status == AssetCheckExecutionRecordStatus.PLANNED
7181+
assert record.last_planned_run_id == run_id_2
7182+
7183+
# Run 2: Now store evaluation event for partition "a" with passed=False
71227184
storage.store_event(
71237185
_create_check_evaluation_event(run_id_2, check_key, passed=False, partition="a")
71247186
)
71257187

7188+
# onto FAILED
7189+
partition_records = storage.get_asset_check_partition_records(check_key)
7190+
record = partition_records[0]
7191+
assert record.last_execution_status == AssetCheckExecutionRecordStatus.FAILED
7192+
assert record.last_planned_run_id == run_id_2
7193+
71267194
# Verify get_asset_check_execution_history returns 2 records for partition "a"
71277195
checks = storage.get_asset_check_execution_history(check_key, limit=10, partition="a")
71287196
assert len(checks) == 2
@@ -7149,6 +7217,16 @@ def test_asset_check_partitioned_multiple_runs_same_partition(
71497217
assert check_key in latest_overall
71507218
assert latest_overall[check_key].run_id == run_id_2
71517219

7220+
# Test get_asset_check_partition_records returns only the latest record per partition
7221+
partition_records = storage.get_asset_check_partition_records(check_key)
7222+
assert len(partition_records) == 1 # Only partition "a" exists
7223+
7224+
record = partition_records[0]
7225+
assert record.partition_key == "a"
7226+
# Should be the latest execution (run_id_2, FAILED)
7227+
assert record.last_execution_status == AssetCheckExecutionRecordStatus.FAILED
7228+
assert record.last_planned_run_id == run_id_2
7229+
71527230
def test_asset_check_partitioned_with_target_materialization(
71537231
self,
71547232
storage: EventLogStorage,
@@ -7237,6 +7315,17 @@ def test_asset_check_partitioned_with_target_materialization(
72377315
assert check_data.target_materialization_data.storage_id == m1_storage_id
72387316
assert check_data.target_materialization_data.storage_id != m2_storage_id
72397317

7318+
# Test get_asset_check_partition_records includes target_materialization_storage_id
7319+
partition_records = storage.get_asset_check_partition_records(check_key)
7320+
assert len(partition_records) == 1
7321+
7322+
record = partition_records[0]
7323+
assert record.partition_key == "a"
7324+
assert record.last_execution_status == AssetCheckExecutionRecordStatus.SUCCEEDED
7325+
assert record.last_planned_run_id == run_id_1
7326+
# Verify materialization_event_storage_id matches M1
7327+
assert record.last_execution_target_materialization_storage_id == m1_storage_id
7328+
72407329

72417330
def _create_check_planned_event(
72427331
run_id: str,

0 commit comments

Comments
 (0)