Skip to content

Commit 6d59727

Browse files
gibsondanclaude
authored andcommitted
[core] swap AssetCheckSummaryRecord for AssetCheckExecutionRecord in asset_graph_view (#24773)
## Summary - Three call sites in `asset_graph_view.py` (`compute_subset_with_status`, `_compute_latest_check_run_matches`, `_compute_updated_since_time_subset`) loaded an `AssetCheckSummaryRecord` only to read `last_check_execution_record`. - Swap them to `AssetCheckExecutionRecord.gen`, which routes through `get_latest_asset_check_execution_by_key` — a single batched query (OSS) or N queries (cloud) — and returns exactly that record. - The replaced `get_asset_check_summary_records` path fires N or 2N queries per call to additionally populate `last_completed_check_execution_record`, which these callers discard. ## Test plan - [x] `just ruff` - [x] `just ty` (0 errors) - [x] `tox -e py312-asset_defs_tests -- -k asset_graph_view` (66 passed) - [x] `tox -e py312-declarative_automation_tests -- -k check` (22 passed) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Internal-RevId: 7291b6d9e3ed62901c1d7d3968fe9423b392242e
1 parent 0fbacae commit 6d59727

4 files changed

Lines changed: 171 additions & 25 deletions

File tree

python_modules/dagster-cloud/dagster_cloud/storage/event_logs/queries.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,31 @@
123123
"""
124124
)
125125

126-
ASSET_CHECK_STATE_FRAGMENT = (
126+
ASSET_CHECK_EXECUTION_RECORD_FRAGMENT = (
127127
EVENT_LOG_ENTRY_FRAGMENT
128128
+ """
129+
fragment AssetCheckExecutionRecordFragment on AssetCheckExecutionRecord {
130+
assetCheckKey {
131+
assetKey {
132+
path
133+
}
134+
name
135+
}
136+
id
137+
runId
138+
status
139+
event {
140+
...EventLogEntryFragment
141+
}
142+
createTimestamp
143+
partition
144+
}
145+
"""
146+
)
147+
148+
ASSET_CHECK_STATE_FRAGMENT = (
149+
ASSET_CHECK_EXECUTION_RECORD_FRAGMENT
150+
+ """
129151
fragment AssetCheckSummaryRecordFragment on AssetCheckSummaryRecord {
130152
assetCheckKey {
131153
assetKey {
@@ -134,22 +156,10 @@
134156
name
135157
}
136158
lastCheckExecutionRecord {
137-
id
138-
runId
139-
status
140-
event {
141-
...EventLogEntryFragment
142-
}
143-
createTimestamp
159+
...AssetCheckExecutionRecordFragment
144160
}
145161
lastCompletedCheckExecutionRecord {
146-
id
147-
runId
148-
status
149-
event {
150-
...EventLogEntryFragment
151-
}
152-
createTimestamp
162+
...AssetCheckExecutionRecordFragment
153163
}
154164
lastRunId
155165
}
@@ -297,6 +307,19 @@
297307
"""
298308
)
299309

310+
GET_LATEST_ASSET_CHECK_EXECUTION_BY_KEY_QUERY = (
311+
ASSET_CHECK_EXECUTION_RECORD_FRAGMENT
312+
+ """
313+
query getLatestAssetCheckExecutionByKey($assetCheckKeys: [String!]!, $partitionFilter: PartitionKeyFilter) {
314+
eventLogs {
315+
getLatestAssetCheckExecutionByKey(assetCheckKeys: $assetCheckKeys, partitionFilter: $partitionFilter) {
316+
...AssetCheckExecutionRecordFragment
317+
}
318+
}
319+
}
320+
"""
321+
)
322+
300323
GET_ALL_ASSET_KEYS_QUERY = """
301324
query getAllAssetKeys {
302325
eventLogs {

python_modules/dagster-cloud/dagster_cloud/storage/event_logs/storage.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
GET_DYNAMIC_PARTITIONS_QUERY,
9494
GET_EVENT_RECORDS_QUERY,
9595
GET_EVENT_TAGS_FOR_ASSET,
96+
GET_LATEST_ASSET_CHECK_EXECUTION_BY_KEY_QUERY,
9697
GET_LATEST_ASSET_PARTITION_MATERIALIZATION_ATTEMPTS_WITHOUT_MATERIALIZATIONS,
9798
GET_LATEST_MATERIALIZATION_EVENTS_QUERY,
9899
GET_LATEST_PLANNED_MATERIALIZATION_INFO,
@@ -1244,7 +1245,22 @@ def get_latest_asset_check_execution_by_key(
12441245
check_keys: Sequence[AssetCheckKey],
12451246
partition_filter: PartitionKeyFilter | None = None,
12461247
) -> Mapping[AssetCheckKey, AssetCheckExecutionRecord]:
1247-
raise NotImplementedError("Not callable from user cloud")
1248+
if not check_keys:
1249+
return {}
1250+
res = self._execute_query(
1251+
GET_LATEST_ASSET_CHECK_EXECUTION_BY_KEY_QUERY,
1252+
variables={
1253+
"assetCheckKeys": [key.to_user_string() for key in check_keys],
1254+
"partitionFilter": (
1255+
{"key": partition_filter.key} if partition_filter is not None else None
1256+
),
1257+
},
1258+
)
1259+
records = {}
1260+
for result in res["data"]["eventLogs"]["getLatestAssetCheckExecutionByKey"]:
1261+
check_key = AssetCheckKey.from_graphql_input(result["assetCheckKey"])
1262+
records[check_key] = _asset_check_execution_record_from_graphql(result, check_key)
1263+
return records
12481264

12491265
def get_asset_check_partition_info(
12501266
self,

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ async def compute_subset_with_status(
585585
from_subset: EntitySubset,
586586
) -> EntitySubset[AssetCheckKey]:
587587
"""Returns the subset of an asset check that matches a given status."""
588-
from dagster._core.storage.event_log.base import AssetCheckSummaryRecord
588+
from dagster._core.storage.asset_check_execution_record import AssetCheckExecutionRecord
589589

590590
# Handle partitioned asset checks
591591
if self._get_partitions_def(key):
@@ -595,8 +595,7 @@ async def compute_subset_with_status(
595595
)
596596

597597
# Handle non-partitioned asset checks with existing logic
598-
summary = await AssetCheckSummaryRecord.gen(self, key)
599-
latest_record = summary.last_check_execution_record if summary else None
598+
latest_record = await AssetCheckExecutionRecord.gen(self, key)
600599
resolved_status = (
601600
await latest_record.resolve_status(self)
602601
if latest_record and await latest_record.targets_latest_materialization(self)
@@ -882,12 +881,11 @@ async def _compute_latest_check_run_matches(
882881
query_key: AssetCheckKey,
883882
filter_fn: Callable[["RunRecord"], bool],
884883
) -> bool:
884+
from dagster._core.storage.asset_check_execution_record import AssetCheckExecutionRecord
885885
from dagster._core.storage.dagster_run import RunRecord
886-
from dagster._core.storage.event_log.base import AssetCheckSummaryRecord
887886

888887
check.invariant(partition_key is None, "Partitioned checks not supported")
889-
summary = await AssetCheckSummaryRecord.gen(self, query_key)
890-
check_record = summary.last_check_execution_record if summary else None
888+
check_record = await AssetCheckExecutionRecord.gen(self, query_key)
891889
if check_record and check_record.event:
892890
run_record = await RunRecord.gen(self, check_record.event.run_id)
893891
return bool(run_record) and filter_fn(run_record)
@@ -1011,11 +1009,10 @@ async def _compute_updated_since_time_subset(
10111009
self, key: AssetCheckKey, time: datetime
10121010
) -> EntitySubset[AssetCheckKey]:
10131011
from dagster._core.events import DagsterEventType
1014-
from dagster._core.storage.event_log.base import AssetCheckSummaryRecord
1012+
from dagster._core.storage.asset_check_execution_record import AssetCheckExecutionRecord
10151013

10161014
# intentionally left unimplemented for AssetKey, as this is a less performant query
1017-
summary = await AssetCheckSummaryRecord.gen(self, key)
1018-
record = summary.last_check_execution_record if summary else None
1015+
record = await AssetCheckExecutionRecord.gen(self, key)
10191016
if (
10201017
record is None
10211018
or record.event is None

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

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5870,6 +5870,9 @@ def test_asset_checks(
58705870
check_key_1 = dg.AssetCheckKey(dg.AssetKey(["my_asset"]), "my_check")
58715871
check_key_2 = dg.AssetCheckKey(dg.AssetKey(["my_asset"]), "my_check_2")
58725872

5873+
# empty input short-circuits to empty mapping
5874+
assert storage.get_latest_asset_check_execution_by_key([]) == {}
5875+
58735876
for asset_key in {dg.AssetKey(["my_asset"]), dg.AssetKey(["my_other_asset"])}:
58745877
storage.store_event(
58755878
dg.EventLogEntry(
@@ -5913,6 +5916,13 @@ def test_asset_checks(
59135916
assert latest_checks[check_key_1].status == AssetCheckExecutionRecordStatus.PLANNED
59145917
assert latest_checks[check_key_1].run_id == run_id_1
59155918

5919+
# PartitionKeyFilter(key=None) matches unpartitioned records
5920+
latest_unpartitioned = storage.get_latest_asset_check_execution_by_key(
5921+
[check_key_1], partition_filter=PartitionKeyFilter(key=None)
5922+
)
5923+
assert check_key_1 in latest_unpartitioned
5924+
assert latest_unpartitioned[check_key_1].partition is None
5925+
59165926
# update the planned check
59175927
storage.store_event(
59185928
dg.EventLogEntry(
@@ -6038,6 +6048,106 @@ def test_asset_checks(
60386048
latest_checks = storage.get_latest_asset_check_execution_by_key([check_key_1, check_key_2])
60396049
assert len(latest_checks) == 0
60406050

6051+
@pytest.mark.parametrize(
6052+
"partitions_def_type, partition_keys",
6053+
[
6054+
("static", ["a", "b", "c"]),
6055+
("dynamic", ["x", "y", "z"]),
6056+
("time_window", ["2023-01-01", "2023-01-02", "2023-01-03"]),
6057+
],
6058+
ids=["static", "dynamic", "time_window"],
6059+
)
6060+
def test_get_latest_asset_check_execution_by_key_partitioned_lifecycle(
6061+
self,
6062+
storage: EventLogStorage,
6063+
partitions_def_type: str,
6064+
partition_keys: list,
6065+
):
6066+
run_id = make_new_run_id()
6067+
asset_key = dg.AssetKey(["my_partitioned_asset_focused"])
6068+
check_key = dg.AssetCheckKey(asset_key, "my_partitioned_check_focused")
6069+
6070+
if partitions_def_type == "static":
6071+
partitions_def = dg.StaticPartitionsDefinition(partition_keys)
6072+
elif partitions_def_type == "dynamic":
6073+
partitions_def = dg.DynamicPartitionsDefinition(
6074+
name="latest_check_focused_dynamic_partitions"
6075+
)
6076+
storage.add_dynamic_partitions(
6077+
partitions_def_name="latest_check_focused_dynamic_partitions",
6078+
partition_keys=partition_keys,
6079+
)
6080+
elif partitions_def_type == "time_window":
6081+
partitions_def = dg.DailyPartitionsDefinition(start_date="2023-01-01")
6082+
else:
6083+
raise ValueError(f"Unknown partitions_def_type: {partitions_def_type}")
6084+
6085+
partitions_subset = partitions_def.subset_with_partition_keys(
6086+
partition_keys
6087+
).to_serializable_subset()
6088+
key_a, key_b, key_c = partition_keys
6089+
6090+
# a single planned event with a multi-partition subset creates one row per partition
6091+
storage.store_event(
6092+
_create_check_planned_event(run_id, check_key, partitions_subset=partitions_subset)
6093+
)
6094+
6095+
# each partition is PLANNED in isolation, with the correct partition + run_id
6096+
for partition_key in partition_keys:
6097+
result = storage.get_latest_asset_check_execution_by_key(
6098+
[check_key], partition_filter=PartitionKeyFilter(key=partition_key)
6099+
)
6100+
assert check_key in result
6101+
assert result[check_key].partition == partition_key
6102+
assert result[check_key].run_id == run_id
6103+
assert result[check_key].status == AssetCheckExecutionRecordStatus.PLANNED
6104+
6105+
# PartitionKeyFilter(key=None) excludes partitioned rows
6106+
result = storage.get_latest_asset_check_execution_by_key(
6107+
[check_key], partition_filter=PartitionKeyFilter(key=None)
6108+
)
6109+
assert check_key not in result
6110+
6111+
# complete partition "a" successfully
6112+
storage.store_event(
6113+
_create_check_evaluation_event(run_id, check_key, passed=True, partition=key_a)
6114+
)
6115+
6116+
# "a" is SUCCEEDED; "b" and "c" still PLANNED
6117+
assert (
6118+
storage.get_latest_asset_check_execution_by_key(
6119+
[check_key], partition_filter=PartitionKeyFilter(key=key_a)
6120+
)[check_key].status
6121+
== AssetCheckExecutionRecordStatus.SUCCEEDED
6122+
)
6123+
for partition_key in (key_b, key_c):
6124+
assert (
6125+
storage.get_latest_asset_check_execution_by_key(
6126+
[check_key], partition_filter=PartitionKeyFilter(key=partition_key)
6127+
)[check_key].status
6128+
== AssetCheckExecutionRecordStatus.PLANNED
6129+
)
6130+
6131+
# fail partition "b"
6132+
storage.store_event(
6133+
_create_check_evaluation_event(run_id, check_key, passed=False, partition=key_b)
6134+
)
6135+
6136+
expected = {
6137+
key_a: AssetCheckExecutionRecordStatus.SUCCEEDED,
6138+
key_b: AssetCheckExecutionRecordStatus.FAILED,
6139+
key_c: AssetCheckExecutionRecordStatus.PLANNED,
6140+
}
6141+
for partition_key, status in expected.items():
6142+
record = storage.get_latest_asset_check_execution_by_key(
6143+
[check_key], partition_filter=PartitionKeyFilter(key=partition_key)
6144+
)[check_key]
6145+
assert record.partition == partition_key
6146+
assert record.status == status
6147+
6148+
latest = storage.get_latest_asset_check_execution_by_key([check_key])
6149+
assert check_key in latest
6150+
60416151
def test_duplicate_asset_check_planned_events(self, storage: EventLogStorage):
60426152
run_id = make_new_run_id()
60436153
for _ in range(2):

0 commit comments

Comments
 (0)