-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Summary Largely clones `NamespacedMetadataSet` into a new `NamespacedTagSet` ABC which can be used to define a set of tags which will be logically set together in code, and which have a namespace prefix. A bit simpler, in that all values must be strings. ## Test Plan New little unit test suite.
- Loading branch information
Showing
6 changed files
with
147 additions
and
36 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
1 change: 1 addition & 0 deletions
1
python_modules/dagster/dagster/_core/definitions/tags/__init__.py
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 |
---|---|---|
@@ -0,0 +1 @@ | ||
from .tag_set import NamespacedTagSet as NamespacedTagSet |
45 changes: 45 additions & 0 deletions
45
python_modules/dagster/dagster/_core/definitions/tags/tag_set.py
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 |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from typing import Any, cast | ||
|
||
from typing_extensions import TypeVar, get_args | ||
|
||
from dagster import _check as check | ||
from dagster._core.definitions.metadata.metadata_set import NamespacedKVSet | ||
from dagster._model.pydantic_compat_layer import model_fields | ||
from dagster._utils.typing_api import is_closed_python_optional_type | ||
|
||
T_NamespacedTagSet = TypeVar("T_NamespacedTagSet", bound="NamespacedTagSet") | ||
|
||
|
||
class NamespacedTagSet(NamespacedKVSet): | ||
"""Extend this class to define a set of tags in the same namespace. | ||
Supports splatting to a dictionary that can be placed inside a tags argument along with | ||
other tags. | ||
.. code-block:: python | ||
my_tags: NamespacedTagsSet = ... | ||
@asset( | ||
tags={**my_tags} | ||
) | ||
def my_asset(): | ||
pass | ||
""" | ||
|
||
def __init__(self, *args, **kwargs) -> None: | ||
for field_name, field in model_fields(self).items(): | ||
annotation_type = field.annotation | ||
|
||
is_optional_str = is_closed_python_optional_type(annotation_type) and str in get_args( | ||
annotation_type | ||
) | ||
if not (is_optional_str or annotation_type is str): | ||
check.failed( | ||
f"Type annotation for field '{field_name}' is not str or Optional[str]" | ||
) | ||
super().__init__(*args, **kwargs) | ||
|
||
@classmethod | ||
def _extract_value(cls, field_name: str, value: Any) -> str: | ||
"""Since all tag values are strings, we don't need to do any type coercion.""" | ||
return cast(str, value) |
Empty file.
55 changes: 55 additions & 0 deletions
55
python_modules/dagster/dagster_tests/definitions_tests/tags_tests/test_tag_set.py
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 |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from typing import Optional | ||
|
||
import pydantic | ||
import pytest | ||
from dagster._check import CheckError | ||
from dagster._core.definitions.tags import NamespacedTagSet | ||
|
||
|
||
def test_invalid_tag_set() -> None: | ||
class MyJunkTagSet(NamespacedTagSet): | ||
junk: int | ||
|
||
@classmethod | ||
def namespace(cls) -> str: | ||
return "dagster" | ||
|
||
with pytest.raises( | ||
CheckError, match="Type annotation for field 'junk' is not str or Optional\\[str\\]" | ||
): | ||
MyJunkTagSet(junk=1) | ||
|
||
|
||
def test_basic_tag_set_validation() -> None: | ||
class MyValidTagSet(NamespacedTagSet): | ||
foo: Optional[str] = None | ||
bar: Optional[str] = None | ||
|
||
@classmethod | ||
def namespace(cls) -> str: | ||
return "dagster" | ||
|
||
with pytest.raises(pydantic.ValidationError): | ||
MyValidTagSet(foo="lorem", bar=lambda x: x) # type: ignore | ||
|
||
with pytest.raises(pydantic.ValidationError): | ||
MyValidTagSet(foo="lorem", baz="ipsum") # type: ignore | ||
|
||
|
||
def test_basic_tag_set_functionality() -> None: | ||
class MyValidTagSet(NamespacedTagSet): | ||
foo: Optional[str] = None | ||
bar: Optional[str] = None | ||
|
||
@classmethod | ||
def namespace(cls) -> str: | ||
return "dagster" | ||
|
||
tag_set = MyValidTagSet(foo="lorem", bar="ipsum") | ||
assert tag_set.foo == "lorem" | ||
assert tag_set.bar == "ipsum" | ||
|
||
assert tag_set.extract((dict(tag_set))) == tag_set | ||
assert tag_set.extract({}) == MyValidTagSet(foo=None, bar=None) | ||
|
||
assert dict(tag_set) == {"dagster/foo": "lorem", "dagster/bar": "ipsum"} |