Skip to content

Commit

Permalink
InProgress: Add tags to SavedQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
theyostalservice committed Nov 13, 2024
1 parent aa3d8b5 commit 73dbdd1
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 4 deletions.
8 changes: 7 additions & 1 deletion dbt_semantic_interfaces/implementations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,18 @@ def __parse_with_custom_handling(
to the caller to be pre-validated, and so we do not bother guarding against that here.
"""
if isinstance(input, dict):
return cls(**input) # type: ignore
return cls._pre_parse_dict_input(input)
elif isinstance(input, cls):
return input
else:
return cls._from_yaml_value(input)

@classmethod
def _pre_parse_dict_input(
cls: Type[PydanticCustomInputParser[ModelObjectT_co]], input: dict
) -> PydanticCustomInputParser[ModelObjectT_co]:
return cls(**input) # type: ignore

@classmethod
@abstractmethod
def _from_yaml_value(
Expand Down
27 changes: 24 additions & 3 deletions dbt_semantic_interfaces/implementations/saved_query.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from __future__ import annotations

from typing import List, Optional
from typing import Any, List, Optional, Union

from typing_extensions import override
from typing_extensions import Self, override

from dbt_semantic_interfaces.implementations.base import (
HashableBaseModel,
Expand All @@ -20,6 +20,10 @@
)
from dsi_pydantic_shim import Field

# Type alias for the implicit "Any" type used as input and output for Pydantic's parsing API
PydanticParseableValueType = Any # type: ignore[misc]
# ModelObjectT_co = TypeVar("ModelObjectT_co", covariant=True, bound=BaseModel)


class PydanticSavedQueryQueryParams(HashableBaseModel, ProtocolHint[SavedQueryQueryParams]):
"""Pydantic implementation of SavedQuery."""
Expand All @@ -35,7 +39,12 @@ def _implements_protocol(self) -> SavedQueryQueryParams:
where: Optional[PydanticWhereFilterIntersection] = None


class PydanticSavedQuery(HashableBaseModel, ModelWithMetadataParsing, ProtocolHint[SavedQuery]):
class PydanticSavedQuery(
# PydanticCustomInputParser,
HashableBaseModel,
ModelWithMetadataParsing,
ProtocolHint[SavedQuery],
):
"""Pydantic implementation of SavedQuery."""

@override
Expand All @@ -48,3 +57,15 @@ def _implements_protocol(self) -> SavedQuery:
metadata: Optional[PydanticMetadata] = None
label: Optional[str] = None
exports: List[PydanticExport] = Field(default_factory=list)
tags: Union[str, List[str]] = Field(
default_factory=list,
)

@classmethod
def parse_obj(cls, input: Any) -> Self: # noqa
if isinstance(input, dict):
if isinstance(input.get("tags"), str):
input["tags"] = [input["tags"]]
if isinstance(input.get("tags"), list):
input["tags"].sort()
return super(HashableBaseModel, cls).parse_obj(input)
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,19 @@
},
"query_params": {
"$ref": "#/definitions/saved_query_query_params_schema"
},
"tags": {
"oneOf": [
{
"type": "string"
},
{
"items": {
"type": "string"
},
"type": "array"
}
]
}
},
"required": [
Expand Down
9 changes: 9 additions & 0 deletions dbt_semantic_interfaces/parsing/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,15 @@
"query_params": {"$ref": "saved_query_query_params_schema"},
"label": {"type": "string"},
"exports": {"type": "array", "items": {"$ref": "export_schema"}},
"tags": {
"oneOf": [
{"type": "string"},
{
"type": "array",
"items": {"type": "string"},
},
],
},
},
"required": ["name", "query_params"],
"additionalProperties": False,
Expand Down
6 changes: 6 additions & 0 deletions dbt_semantic_interfaces/protocols/saved_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,9 @@ def label(self) -> Optional[str]:
def exports(self) -> Sequence[Export]:
"""Exports that can run using this saved query."""
pass

@property
@abstractmethod
def tags(self) -> Sequence[str]:
"""List of tags to be used as part of resource selection in dbt."""
pass
115 changes: 115 additions & 0 deletions tests/parsing/test_saved_query_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ def test_saved_query_group_by() -> None:
"""\
saved_query:
name: test_saved_query_group_bys
tags:
- "tag_1"
- "tag_2"
query_params:
metrics:
- test_metric_a
Expand Down Expand Up @@ -173,6 +176,118 @@ def test_saved_query_where() -> None:
assert where == saved_query.query_params.where.where_filters[0].where_sql_template


def test_saved_query_with_single_tag_string() -> None:
"""Test for parsing a single string (not a list) tag in a saved query."""
yaml_contents = textwrap.dedent(
"""\
saved_query:
name: test_saved_query_group_bys
tags: "tag_1"
query_params:
metrics:
- test_metric_a
"""
)
file = YamlConfigFile(filepath="test_dir/inline_for_test", contents=yaml_contents)

build_result = parse_yaml_files_to_semantic_manifest(files=[file, EXAMPLE_PROJECT_CONFIGURATION_YAML_CONFIG_FILE])
assert len(build_result.semantic_manifest.saved_queries) == 1
saved_query = build_result.semantic_manifest.saved_queries[0]
assert saved_query.tags is not None
assert len(saved_query.tags) == 1
assert saved_query.tags == ["tag_1"]


def test_saved_query_with_multiline_list_of_tags() -> None:
"""Test for parsing a multiline list of tags in a saved query."""
yaml_contents = textwrap.dedent(
"""\
saved_query:
name: test_saved_query_group_bys
tags: ["tag_1", "tag_2"]
query_params:
metrics:
- test_metric_a
"""
)
file = YamlConfigFile(filepath="test_dir/inline_for_test", contents=yaml_contents)

build_result = parse_yaml_files_to_semantic_manifest(files=[file, EXAMPLE_PROJECT_CONFIGURATION_YAML_CONFIG_FILE])
assert len(build_result.semantic_manifest.saved_queries) == 1
saved_query = build_result.semantic_manifest.saved_queries[0]
assert saved_query.tags is not None
assert len(saved_query.tags) == 2
assert saved_query.tags == ["tag_1", "tag_2"]


def test_saved_query_with_single_line_list_of_tags() -> None:
"""Test for parsing a single-line list of tags in a saved query."""
yaml_contents = textwrap.dedent(
"""\
saved_query:
name: test_saved_query_group_bys
tags:
- "tag_1"
- "tag_2"
query_params:
metrics:
- test_metric_a
"""
)
file = YamlConfigFile(filepath="test_dir/inline_for_test", contents=yaml_contents)

build_result = parse_yaml_files_to_semantic_manifest(files=[file, EXAMPLE_PROJECT_CONFIGURATION_YAML_CONFIG_FILE])
assert len(build_result.semantic_manifest.saved_queries) == 1
saved_query = build_result.semantic_manifest.saved_queries[0]
assert saved_query.tags is not None
assert len(saved_query.tags) == 2
assert saved_query.tags == ["tag_1", "tag_2"]


def test_saved_query_tags_are_sorted() -> None:
"""Test tags in a saved query are SORTED after parsing."""
yaml_contents = textwrap.dedent(
"""\
saved_query:
name: test_saved_query_group_bys
tags:
- "tag_2"
- "tag_1"
query_params:
metrics:
- test_metric_a
"""
)
file = YamlConfigFile(filepath="test_dir/inline_for_test", contents=yaml_contents)

build_result = parse_yaml_files_to_semantic_manifest(files=[file, EXAMPLE_PROJECT_CONFIGURATION_YAML_CONFIG_FILE])
assert len(build_result.semantic_manifest.saved_queries) == 1
saved_query = build_result.semantic_manifest.saved_queries[0]
assert saved_query.tags is not None
assert len(saved_query.tags) == 2
assert saved_query.tags == ["tag_1", "tag_2"]


def test_saved_query_with_no_tags_defaults_to_empty_list() -> None:
"""Test tags in a saved query will default to empty list if missing."""
yaml_contents = textwrap.dedent(
"""\
saved_query:
name: test_saved_query_group_bys
query_params:
metrics:
- test_metric_a
"""
)
file = YamlConfigFile(filepath="test_dir/inline_for_test", contents=yaml_contents)

build_result = parse_yaml_files_to_semantic_manifest(files=[file, EXAMPLE_PROJECT_CONFIGURATION_YAML_CONFIG_FILE])
assert len(build_result.semantic_manifest.saved_queries) == 1
saved_query = build_result.semantic_manifest.saved_queries[0]
assert saved_query.tags is not None
assert saved_query.tags == []


def test_saved_query_exports() -> None:
"""Test for parsing exports referenced in a saved query."""
yaml_contents = textwrap.dedent(
Expand Down

0 comments on commit 73dbdd1

Please sign in to comment.