-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Handle adding and removing deps in AssetsDefinition.map_asset_specs (#…
…26144) # Background This PR changes `AssetsDefinition.map_asset_specs` to allow adding additional deps to an `AssetSpec`. To do this, we create `Nothing` inputs under the hood which map to the asset dependency in question. This is important mostly for integration cases where we use the decorator pattern `@dbt_assets`, `@fivetran_assets`, etc. If you want to link an asset dependency to assets defined with those decorators, it's currently not feasible for users. # Caveats **Only supports op-backed assets** This PR adds support only for op-backed assets, not graph-backed assets. I attempted to add support and found myself blocked since graph-backed assets currently do not support non-argument deps (at least from what I could gather). I added a linear issue to track this and referenced it in the relevant place in code. **Doesn't work for argument-based deps** This PR adds support for the deletion of non-argument based deps; but does not support the deletion of argument-based deps. I think deletion of argument-based deps is impossible, since you would have nothing to pass to the underlying function. We error cleanly instead, and I think that's the best we can do. # Testing A bunch of tests for every case that I could think of: - add additional dependencies - add the same dependency multiple times - remove a dependency - add dependencies with partition mappings - add dependencies to a non-executable assets def - attempt to add deps to a graph-backed asset
- Loading branch information
Showing
6 changed files
with
218 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,14 @@ | |
|
||
import dagster as dg | ||
import pytest | ||
from dagster import AssetSpec, AutoMaterializePolicy, AutomationCondition | ||
from dagster import ( | ||
AssetSpec, | ||
AutoMaterializePolicy, | ||
AutomationCondition, | ||
IdentityPartitionMapping, | ||
LastPartitionMapping, | ||
) | ||
from dagster._check import CheckError | ||
from dagster._core.definitions.asset_dep import AssetDep | ||
from dagster._core.definitions.asset_key import AssetKey | ||
from dagster._core.definitions.assets import AssetsDefinition | ||
|
@@ -230,3 +237,123 @@ def my_other_multi_asset(): | |
assert all( | ||
spec.owners == ["[email protected]"] for asset in mapped_assets for spec in asset.specs | ||
) | ||
|
||
|
||
def test_map_asset_specs_additional_deps() -> None: | ||
@dg.multi_asset(specs=[AssetSpec(key="a")]) | ||
def my_asset(): | ||
pass | ||
|
||
@dg.multi_asset(specs=[AssetSpec(key="c", deps=["a"])]) | ||
def my_other_asset(): | ||
pass | ||
|
||
assets = [my_asset, my_other_asset] | ||
|
||
mapped_assets = dg.map_asset_specs( | ||
lambda spec: spec.merge_attributes(deps=["b"]) if spec.key == my_other_asset.key else spec, | ||
assets, | ||
) | ||
|
||
c_asset = next(iter(asset for asset in mapped_assets if asset.key == my_other_asset.key)) | ||
assert set(next(iter(c_asset.specs)).deps) == {AssetDep("a"), AssetDep("b")} | ||
|
||
|
||
def test_map_asset_specs_multiple_deps_same_key() -> None: | ||
@dg.multi_asset(specs=[AssetSpec(key="a", deps=[AssetDep("b")])]) | ||
def my_asset(): | ||
pass | ||
|
||
# This works because the dep is coerced to an identical object. | ||
|
||
dg.map_asset_specs(lambda spec: spec.merge_attributes(deps=[AssetKey("b")]), [my_asset]) | ||
|
||
# This doesn't work because we change the object. | ||
with pytest.raises(DagsterInvariantViolationError): | ||
dg.map_asset_specs( | ||
lambda spec: spec.merge_attributes( | ||
deps=[AssetDep(AssetKey("b"), partition_mapping=LastPartitionMapping())] | ||
), | ||
[my_asset], | ||
) | ||
|
||
|
||
def test_map_asset_specs_nonarg_dep_removal() -> None: | ||
@dg.multi_asset(specs=[AssetSpec(key="a", deps=[AssetDep("b")])]) | ||
def my_asset(): | ||
pass | ||
|
||
new_asset = next( | ||
iter(dg.map_asset_specs(lambda spec: spec.replace_attributes(deps=[]), [my_asset])) | ||
) | ||
new_spec = next(iter(new_asset.specs)) | ||
assert new_spec.deps == [] | ||
# Ensure that dep removal propogated to the underlying op | ||
assert new_asset.keys_by_input_name == {} | ||
assert len(new_asset.op.input_defs) == 0 | ||
|
||
|
||
def test_map_asset_specs_arg_dep_removal() -> None: | ||
@dg.asset(key="a") | ||
def my_asset(b): | ||
pass | ||
|
||
with pytest.raises(CheckError): | ||
dg.map_asset_specs(lambda spec: spec.replace_attributes(deps=[]), [my_asset]) | ||
|
||
|
||
def test_map_additional_deps_partition_mapping() -> None: | ||
@dg.multi_asset( | ||
specs=[AssetSpec(key="a", deps=[AssetDep("b", partition_mapping=LastPartitionMapping())])] | ||
) | ||
def my_asset(): | ||
pass | ||
|
||
a_asset = next( | ||
iter( | ||
dg.map_asset_specs( | ||
lambda spec: spec.merge_attributes( | ||
deps=[AssetDep("c", partition_mapping=IdentityPartitionMapping())] | ||
), | ||
[my_asset], | ||
) | ||
) | ||
) | ||
a_spec = next(iter(a_asset.specs)) | ||
b_dep = next(iter(dep for dep in a_spec.deps if dep.asset_key == AssetKey("b"))) | ||
assert b_dep.partition_mapping == LastPartitionMapping() | ||
c_dep = next(iter(dep for dep in a_spec.deps if dep.asset_key == AssetKey("c"))) | ||
assert c_dep.partition_mapping == IdentityPartitionMapping() | ||
assert a_asset.get_partition_mapping(AssetKey("c")) == IdentityPartitionMapping() | ||
assert a_asset.get_partition_mapping(AssetKey("b")) == LastPartitionMapping() | ||
|
||
|
||
def test_add_specs_non_executable_asset() -> None: | ||
assets_def = ( | ||
dg.Definitions(assets=[AssetSpec(key="foo")]) | ||
.get_repository_def() | ||
.assets_defs_by_key[AssetKey("foo")] | ||
) | ||
foo_spec = next( | ||
iter( | ||
next( | ||
iter( | ||
dg.map_asset_specs(lambda spec: spec.merge_attributes(deps=["a"]), [assets_def]) | ||
) | ||
).specs | ||
) | ||
) | ||
assert foo_spec.deps == [AssetDep("a")] | ||
|
||
|
||
def test_graph_backed_asset_additional_deps() -> None: | ||
@dg.op | ||
def foo_op(): | ||
pass | ||
|
||
@dg.graph_asset() | ||
def foo(): | ||
return foo_op() | ||
|
||
with pytest.raises(CheckError): | ||
dg.map_asset_specs(lambda spec: spec.merge_attributes(deps=["baz"]), [foo]) |