Skip to content

Fix DagsterInvalidInvocationError when checking size, emptiness, or membership of historical entity subsets with deleted partitions#33911

Draft
imann128 wants to merge 1 commit into
dagster-io:masterfrom
imann128:fix-deleted-partition-fallback
Draft

Fix DagsterInvalidInvocationError when checking size, emptiness, or membership of historical entity subsets with deleted partitions#33911
imann128 wants to merge 1 commit into
dagster-io:masterfrom
imann128:fix-deleted-partition-fallback

Conversation

@imann128

@imann128 imann128 commented Jun 5, 2026

Copy link
Copy Markdown

Summary:
This PR aims to resolve a regression in which reading historical asset records, tracking asset evaluation health, or performing partition membership lookups via GraphQL crashes the user interface or the system tracking.
Specifically, when accessing properties like .size, .is_empty, or evaluating membership checks using the in operator (contains) on a SerializableEntitySubset, Dagster throws a DagsterInvalidInvocationError if an underlying dynamic partition key has been deleted out from under that historical evaluation subset.
By catching DagsterInvalidInvocationError within the size, is_empty, and contains evaluations, we provide a clean, defensive fallback. The system safely treats the broken subset evaluations as having a size of 0, an empty state of True, and membership presence of False rather than crashing the execution path.

What was Changed:
serializable_entity_subset.py: Refactored the size, is_empty, and contains methods to catch DagsterInvalidInvocationError. If caught, they cleanly fall back to 0, True, and False, respectively, instead of crashing the thread.
test_serializable_entity_subset.py: Added test_deleted_partition_graceful_fallback to ensure regression protection by utilizing pytest's monkeypatch fixture to safely mock a native DefaultPartitionsSubset class footprint, simulating a missing/deleted partition exception path without leaking state across the test suite execution.

Test
A unit test (test_deleted_partition_graceful_fallback) has been added to the test suite to ensure structural durability and guard against regression.
The test uses an isolated runtime monkeypatch that dynamically injects a crashing scenario (DagsterInvalidInvocationError) directly into the DefaultPartitionsSubset class's footprint to cleanly simulate dynamic partition deletion. It verifies that:

entity_subset.size returns 0 instead of propagating the error.

entity_subset.is_empty correctly returns True.

Membership evaluation using the in operator gracefully returns False

@greptile-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds DagsterInvalidInvocationError guards to SerializableEntitySubset.size, is_empty, and __contains__ so that historical subset evaluations with deleted dynamic partition keys degrade gracefully instead of crashing.

  • size and is_empty are correctly guarded and fall back to 0/True when the underlying PartitionsSubset raises the error.
  • __contains__ was restructured to accept "AssetSubset" instead of AssetKeyPartitionKey, but AssetSubset is not exported from entity_subset.py and the non-partitioned branch now calls item.bool_value — an attribute that does not exist on AssetKeyPartitionKey, which is what the existing caller in legacy_context.py passes. This regression will raise AttributeError for non-partitioned assets in will_update_asset_partition.
  • The new test correctly uses monkeypatch to avoid class-level state leakage, but the __contains__ error path is not actually exercised since DefaultPartitionsSubset.__contains__ is not mocked to raise.

Confidence Score: 3/5

The size and is_empty fixes are correct, but the __contains__ rewrite introduces a new AttributeError for non-partitioned assets that go through will_update_asset_partition in legacy_context.py.

The changed __contains__ non-partitioned branch calls item.bool_value on whatever is passed, but the sole existing caller (legacy_context.py:369) passes AssetKeyPartitionKey — a NamedTuple with no bool_value attribute. Any non-partitioned asset evaluated by will_update_asset_partition will now raise AttributeError rather than returning the correct boolean, which is a concrete regression introduced by this PR on a live code path.

serializable_entity_subset.py — specifically the __contains__ method and its interaction with the existing AssetKeyPartitionKey caller in legacy_context.py.

Important Files Changed

Filename Overview
python_modules/dagster/dagster/_core/asset_graph_view/serializable_entity_subset.py Adds DagsterInvalidInvocationError guards to size, is_empty, and __contains__; changes __contains__ type signature from AssetKeyPartitionKey to a non-existent AssetSubset type, and breaks the non-partitioned branch by calling item.bool_value on callers that pass AssetKeyPartitionKey.
python_modules/dagster/dagster_tests/core_tests/asset_graph_view_tests/test_serializable_entity_subset.py Adds regression test using monkeypatch for size and is_empty fallbacks; test for __contains__ doesn't actually trigger the DagsterInvalidInvocationError fallback path since DefaultPartitionsSubset.__contains__ is not mocked.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Caller: item in SerializableEntitySubset"] --> B{is_partitioned?}
    B -- "No (non-partitioned)" --> C["NEW: self.bool_value and item.bool_value"]
    C --> D{"item has .bool_value?"}
    D -- "AssetSubset ✓" --> E["Returns bool"]
    D -- "AssetKeyPartitionKey ✗" --> F["AttributeError 💥"]
    B -- "Yes (partitioned)" --> G["try: item.partition_key in self.subset_value"]
    G --> H{"DagsterInvalidInvocationError?"}
    H -- "No" --> I["Returns bool ✓"]
    H -- "Yes (deleted partition)" --> J["Returns False ✓"]
Loading

Reviews (2): Last reviewed commit: "Fix DagsterInvalidInvocationError when r..." | Re-trigger Greptile

Comment on lines +366 to +374
broken_subset.__class__.__len__ = lambda self: (_ for _ in ()).throw(
DagsterInvalidInvocationError("Nonexistent partition keys")
)

type(broken_subset).is_empty = property(
lambda self: (_ for _ in ()).throw(
DagsterInvalidInvocationError("Nonexistent partition keys")
)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Class-level mutation leaks into other tests

Both broken_subset.__class__.__len__ = ... and type(broken_subset).is_empty = property(...) modify DefaultPartitionsSubset at the class level — not just on the instance. Because there is no teardown, the overridden __len__ and is_empty remain in place for every subsequent test in the same process that instantiates a DefaultPartitionsSubset. Any test that later calls len() or .is_empty on a static-partition subset will raise DagsterInvalidInvocationError instead of returning the real value, producing false failures with misleading error messages.

The fix is to use pytest's monkeypatch fixture, which automatically restores the original attributes after the test exits.

Comment on lines +380 to +381
assert entity_subset.size == 0
assert entity_subset.is_empty is True No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing newline at end of file.

Suggested change
assert entity_subset.size == 0
assert entity_subset.is_empty is True
assert entity_subset.size == 0
assert entity_subset.is_empty is True

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@imann128 imann128 marked this pull request as draft June 5, 2026 20:33
@imann128 imann128 force-pushed the fix-deleted-partition-fallback branch from 1d56534 to cf87c27 Compare June 5, 2026 20:47
@imann128 imann128 changed the title Fix DagsterInvalidInvocationError when reading historical entity subsets with deleted partitions Fix DagsterInvalidInvocationError when checking size, emptiness, or membership of historical entity subsets with deleted partitions Jun 5, 2026
@imann128 imann128 marked this pull request as ready for review June 5, 2026 20:52
Comment on lines +206 to +215
def __contains__(self, item: "AssetSubset") -> bool:
if not self.is_partitioned:
return item.asset_key == self.key and item.partition_key is None and self.bool_value
else:
return item.asset_key == self.key and item.partition_key in self.subset_value

return self.bool_value and item.bool_value

try:
return item.partition_key in self.subset_value
except DagsterInvalidInvocationError:
# If a dynamic partition key was deleted out from under this historical
# subset evaluation, gracefully treat it as not contained.
return False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 AttributeError on non-partitioned __contains__ with existing callers

The non-partitioned branch now evaluates item.bool_value, but the known caller in legacy_context.py (line 369: asset_partition in parent_subset.convert_to_serializable_subset()) passes an AssetKeyPartitionKey, which is a NamedTuple with only asset_key and partition_key fields — no bool_value. Any non-partitioned asset going through will_update_asset_partition will raise AttributeError instead of the previous graceful logic. The old implementation checked item.partition_key is None and self.bool_value, which correctly handled AssetKeyPartitionKey without accessing a non-existent attribute.

@imann128 imann128 marked this pull request as draft June 5, 2026 21:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant