Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AssetChecksDefinitionProps to replace untyped dictionary shenanigans #16747

Closed
wants to merge 1 commit into from

Conversation

schrockn
Copy link
Member

@schrockn schrockn commented Sep 23, 2023

Summary & Motivation

Right in now in AssetChecksDefinition and AssetsDefinition we have a bunch of keyword arguments to construct definitions objects, and then for places where we need to make copies keep a parallel get_attributes_dict method that has to be kept in sync. Instead here we just create a tuple with the "core properties" of the definition and leverage the typing system to keep everything in sync. This mean less code, and the satisfying deletion of the comment "if adding new fields, make sure to handle them in the get_attributes_dict method" and its associated get_attributes_dict method.

This also has another key advantage. We can, as a rule, have the props object be exactly what was passed in to the origin construction, rather than anything post-processed. That means that copies will more guaranteed fidelity. We are at risk right now––especially in AssetsDefinition––of having the __init__ method process data in subtle ways that might alter behavior when passed through __init__ again when copies are made. There is an underlying assumption in that pattern that __init__ is idempotent, and it is difficult to guarantee that assumption. This pattern avoids that issue entirely.

Another nice side benefit if the elimination of AssetChecksDefinitionInputOutputProps, which was quite the mouthful. This just hoists its properties into the top-level object.

I would like to eventually use this pattern in AssetsDefinition as part of cleanup of that class. However it is easy to add earlier rather than later, so adding this to AssetChecksDefinition now before its complexity inevitably grows.

How I Tested These Changes

BK

@schrockn
Copy link
Member Author

schrockn commented Sep 23, 2023

@schrockn schrockn requested review from johannkm and sryza September 23, 2023 23:07
@schrockn schrockn marked this pull request as ready for review September 24, 2023 00:40
Copy link
Contributor

@sryza sryza left a comment

Choose a reason for hiding this comment

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

A downside here is that a user who wants to construct an AssetChecksDefinition needs to go through the extra hoop of constructing one of these props objects.

Might it make sense to make AssetChecksDefinition itself a NamedTuple?

@schrockn
Copy link
Member Author

Might it make sense to make AssetChecksDefinition itself a NamedTuple?

I don't think so. You want the option to add variables in __init__ or on demand.

@schrockn
Copy link
Member Author

A downside here is that a user who wants to construct an AssetChecksDefinition needs to go through the extra hoop of constructing one of these props objects.

I agree this is a nuisance, but a fairly minor one IMO since people constructing AssetChecksDefinitions directly are likely to be framework authors.

I can take a stab at some shenanigans, but it will likely involve having an __init__ or __new__ with broken out arguments and keeping them in sync with unit tests that iterate over the types themselves.

@schrockn schrockn changed the base branch from master to reflection-helper September 26, 2023 14:08
@schrockn schrockn force-pushed the asset-checks-definition-props branch from fab5c9a to 55e1992 Compare September 26, 2023 14:08
@@ -330,12 +330,15 @@ def check_concurrency_claim(
return claim_status.with_sleep_interval(float(self._sleep_interval))


def get_all_direct_subclasses_of_marker(marker_interface_cls: Type) -> List[Type]:
import dagster as dagster
def get_all_direct_subclasses_of_marker(marker_interface_cls: Type, module=None) -> List[Type]:
Copy link
Member Author

Choose a reason for hiding this comment

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

X

Base automatically changed from reflection-helper to master September 27, 2023 02:04
in progress adherence test

version that does not require props in __init__

cp

cp

cp

type as ModuleType
@schrockn schrockn force-pushed the asset-checks-definition-props branch from 6af1750 to eade43c Compare September 27, 2023 02:17
Copy link
Member Author

@schrockn schrockn left a comment

Choose a reason for hiding this comment

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

@alangenfeld this is the pattern I want to institute to prevent the description shenanigans you called brutal in #16755 (review)

Copy link
Member

@alangenfeld alangenfeld left a comment

Choose a reason for hiding this comment

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

I like the conceptual set-up of explicitly capturing the immutable props separate from memoization/utility instance members.

self._props.XXX is a little bit unfortunate but it does make it clear on every touch whats "fundamental" properties of the definition vs not

Comment on lines +18 to +28
hard_coded_list = [
(AssetChecksDefinition, AssetChecksDefinitionProps),
]

# keep hard_coded_list up-to-date with all subclasses of IDefinitionPropsClassMatchesInit
assert set([def_type.__name__ for def_type in def_types]) == set(
[entry[0].__name__ for entry in hard_coded_list]
), (
"You likely added a subclass of IDefinitionPropsClassMatchesInit without adding it to the"
" hard_coded_list"
)
Copy link
Member

Choose a reason for hiding this comment

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

this is just here because you couldn't figure out how to pluck TPropsType & TDefType from the class object?

Comment on lines +335 to +343
marker_interface_cls: Type, module: Optional[ModuleType] = None
) -> List[Type]:
"""Get all direct subclasses of a given marker interface class from a given module. If the
module is not specified, it defaults to the dagster module.
"""
if module is None:
import dagster as dagster

module = dagster
Copy link
Member

Choose a reason for hiding this comment

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

what motivated this change?

@experimental
class AssetChecksDefinitionInputOutputProps(NamedTuple):

class IDefinitionPropsClassMatchesInit(Generic[TPropsType, TDefType], ABC):
Copy link
Member

Choose a reason for hiding this comment

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

nit: something in the direction of IDefinitionWithProps / PropsBackedDefinition, i think it makes more sense to target the fundamentlal pattern then a specific thing we enforce when participating in it

Comment on lines +169 to +176
return self.__class__(
**self._props._replace(
resource_defs=merge_resource_defs(
old_resource_defs=self._props.resource_defs,
resource_defs_to_merge_in=resource_defs,
requires_resources=self,
)
)._asdict()
Copy link
Member

Choose a reason for hiding this comment

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

[1]

Comment on lines +39 to +40
@staticmethod
def from_props(props: TPropsType) -> TDefType: ...
Copy link
Member

Choose a reason for hiding this comment

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

this appears unused - should we instead capture [1] with a method on this class?

@schrockn schrockn closed this Apr 30, 2024
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.

3 participants