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 New SavedQuery Protocol #148

Merged
merged 8 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/Features-20230913-172754.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Add New SavedQuery Protocol
time: 2023-09-13T17:27:54.018355-07:00
custom:
Author: plypaul
Issue: "144"
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ repos:
- id: ruff
verbose: true
language: system
args: [--fix]

- repo: https://github.com/pre-commit/mirrors-mypy # configured via mypy.ini
rev: v1.3.0
Expand Down
32 changes: 32 additions & 0 deletions dbt_semantic_interfaces/implementations/saved_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations

from typing import List, Optional

from typing_extensions import override

from dbt_semantic_interfaces.implementations.base import (
HashableBaseModel,
ModelWithMetadataParsing,
)
from dbt_semantic_interfaces.implementations.filters.where_filter import (
PydanticWhereFilter,
)
from dbt_semantic_interfaces.implementations.metadata import PydanticMetadata
from dbt_semantic_interfaces.protocols import ProtocolHint
from dbt_semantic_interfaces.protocols.saved_query import SavedQuery


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

@override
def _implements_protocol(self) -> SavedQuery:
return self

name: str
metrics: List[str]
group_bys: List[str] = []
where: List[PydanticWhereFilter] = []

description: Optional[str] = None
metadata: Optional[PydanticMetadata] = None
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from dbt_semantic_interfaces.implementations.project_configuration import (
PydanticProjectConfiguration,
)
from dbt_semantic_interfaces.implementations.saved_query import PydanticSavedQuery
from dbt_semantic_interfaces.implementations.semantic_model import PydanticSemanticModel
from dbt_semantic_interfaces.protocols import ProtocolHint, SemanticManifest

Expand All @@ -21,3 +22,4 @@ def _implements_protocol(self) -> SemanticManifest:
semantic_models: List[PydanticSemanticModel]
metrics: List[PydanticMetric]
project_configuration: PydanticProjectConfiguration
saved_queries: List[PydanticSavedQuery] = []
27 changes: 23 additions & 4 deletions dbt_semantic_interfaces/parsing/dir_to_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from dbt_semantic_interfaces.implementations.project_configuration import (
PydanticProjectConfiguration,
)
from dbt_semantic_interfaces.implementations.saved_query import PydanticSavedQuery
from dbt_semantic_interfaces.implementations.semantic_manifest import (
PydanticSemanticManifest,
)
Expand All @@ -20,6 +21,7 @@
from dbt_semantic_interfaces.parsing.schemas import (
metric_validator,
project_configuration_validator,
saved_query_validator,
semantic_model_validator,
)
from dbt_semantic_interfaces.parsing.yaml_loader import (
Expand All @@ -45,8 +47,9 @@
METRIC_TYPE = "metric"
SEMANTIC_MODEL_TYPE = "semantic_model"
PROJECT_CONFIGURATION_TYPE = "project_configuration"
SAVED_QUERY_TYPE = "saved_query"

DOCUMENT_TYPES = [METRIC_TYPE, SEMANTIC_MODEL_TYPE, PROJECT_CONFIGURATION_TYPE]
DOCUMENT_TYPES = [METRIC_TYPE, SEMANTIC_MODEL_TYPE, PROJECT_CONFIGURATION_TYPE, SAVED_QUERY_TYPE]


@dataclass(frozen=True)
Expand All @@ -65,7 +68,7 @@ class FileParsingResult:
issues: Issues found when trying to parse the file
"""

elements: List[Union[PydanticSemanticModel, PydanticMetric, PydanticProjectConfiguration]]
elements: List[Union[PydanticSemanticModel, PydanticMetric, PydanticProjectConfiguration, PydanticSavedQuery]]
issues: List[ValidationIssue]


Expand Down Expand Up @@ -191,6 +194,7 @@ def parse_yaml_files_to_semantic_manifest(
semantic_model_class: Type[PydanticSemanticModel] = PydanticSemanticModel,
metric_class: Type[PydanticMetric] = PydanticMetric,
project_configuration_class: Type[PydanticProjectConfiguration] = PydanticProjectConfiguration,
saved_query_class: Type[PydanticSavedQuery] = PydanticSavedQuery,
) -> SemanticManifestBuildResult:
"""Builds SemanticManifest from list of config files (as strings).

Expand All @@ -202,7 +206,14 @@ def parse_yaml_files_to_semantic_manifest(
semantic_models = []
metrics = []
project_configurations = []
valid_object_classes = [semantic_model_class.__name__, metric_class.__name__, project_configuration_class.__name__]
saved_queries = []

valid_object_classes = [
semantic_model_class.__name__,
metric_class.__name__,
project_configuration_class.__name__,
saved_query_class.__name__,
]
issues: List[ValidationIssue] = []

for config_file in files:
Expand All @@ -211,6 +222,7 @@ def parse_yaml_files_to_semantic_manifest(
semantic_model_class=semantic_model_class,
metric_class=metric_class,
project_configuration_class=project_configuration_class,
saved_query_class=saved_query_class,
)
file_issues = parsing_result.issues
for obj in parsing_result.elements:
Expand All @@ -220,6 +232,8 @@ def parse_yaml_files_to_semantic_manifest(
metrics.append(obj)
elif isinstance(obj, project_configuration_class):
project_configurations.append(obj)
elif isinstance(obj, saved_query_class):
saved_queries.append(obj)
else:
file_issues.append(
ValidationError(
Expand All @@ -241,6 +255,7 @@ def parse_yaml_files_to_semantic_manifest(
semantic_models=semantic_models,
metrics=metrics,
project_configuration=project_configurations[0],
saved_queries=saved_queries,
),
issues=SemanticManifestValidationResults.from_issues_sequence(issues),
)
Expand All @@ -251,9 +266,10 @@ def parse_config_yaml(
semantic_model_class: Type[PydanticSemanticModel] = PydanticSemanticModel,
metric_class: Type[PydanticMetric] = PydanticMetric,
project_configuration_class: Type[PydanticProjectConfiguration] = PydanticProjectConfiguration,
saved_query_class: Type[PydanticSavedQuery] = PydanticSavedQuery,
) -> FileParsingResult:
"""Parses transform config file passed as string - Returns list of model objects."""
results: List[Union[PydanticSemanticModel, PydanticMetric, PydanticProjectConfiguration]] = []
results: List[Union[PydanticSemanticModel, PydanticMetric, PydanticProjectConfiguration, PydanticSavedQuery]] = []
ctx: Optional[ParsingContext] = None
issues: List[ValidationIssue] = []
try:
Expand Down Expand Up @@ -322,6 +338,9 @@ def parse_config_yaml(
elif document_type == PROJECT_CONFIGURATION_TYPE:
project_configuration_validator.validate(config_document[document_type])
results.append(project_configuration_class.parse_obj(object_cfg))
elif document_type == SAVED_QUERY_TYPE:
saved_query_validator.validate(config_document[document_type])
results.append(saved_query_class.parse_obj(object_cfg))
else:
issues.append(
ValidationError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,41 @@
],
"type": "object"
},
"saved_query_schema": {
"$id": "saved_query_schema",
"additionalProperties": false,
"properties": {
"description": {
"type": "string"
},
"group_bys": {
"items": {
"type": "string"
},
"type": "array"
},
"metrics": {
"items": {
"type": "string"
},
"type": "array"
},
"name": {
"type": "string"
},
"where": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"name",
"metrics"
],
"type": "object"
},
"semantic_model_defaults_schema": {
"$id": "semantic_model_defaults_schema",
"additionalProperties": false,
Expand Down
25 changes: 25 additions & 0 deletions dbt_semantic_interfaces/parsing/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,29 @@
}


saved_query_schema = {
"$id": "saved_query_schema",
"type": "object",
"properties": {
"name": {"type": "string"},
"description": {"type": "string"},
"metrics": {
"type": "array",
"items": {"type": "string"},
},
"group_bys": {
"type": "array",
"items": {"type": "string"},
},
"where": {
"type": "array",
"items": {"type": "string"},
},
},
"required": ["name", "metrics"],
"additionalProperties": False,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: I don't think we have to, but we should probably mark the properties name and metrics to be required. I think parsing will error out either way if its not specified, as the creation of the object will fail. But specifying them in the jsonschema spec will give better errors I believe.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

}

semantic_model_schema = {
"$id": "semantic_model_schema",
"type": "object",
Expand Down Expand Up @@ -297,6 +320,7 @@
metric_schema["$id"]: metric_schema,
semantic_model_schema["$id"]: semantic_model_schema,
project_configuration_schema["$id"]: project_configuration_schema,
saved_query_schema["$id"]: saved_query_schema,
# Sub-object schemas
metric_input_measure_schema["$id"]: metric_input_measure_schema,
metric_type_params_schema["$id"]: metric_type_params_schema,
Expand All @@ -318,3 +342,4 @@
semantic_model_validator = SchemaValidator(semantic_model_schema, resolver=resolver)
metric_validator = SchemaValidator(metric_schema, resolver=resolver)
project_configuration_validator = SchemaValidator(project_configuration_schema, resolver=resolver)
saved_query_validator = SchemaValidator(saved_query_schema, resolver=resolver)
1 change: 1 addition & 0 deletions dbt_semantic_interfaces/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
MetricTypeParams,
)
from dbt_semantic_interfaces.protocols.protocol_hint import ProtocolHint # noqa:F401
from dbt_semantic_interfaces.protocols.saved_query import SavedQuery # noqa:F401
from dbt_semantic_interfaces.protocols.semantic_manifest import ( # noqa:F401
SemanticManifest,
SemanticManifestT,
Expand Down
39 changes: 39 additions & 0 deletions dbt_semantic_interfaces/protocols/saved_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from abc import abstractmethod
from typing import Optional, Protocol, Sequence

from dbt_semantic_interfaces.protocols.metadata import Metadata
from dbt_semantic_interfaces.protocols.where_filter import WhereFilter


class SavedQuery(Protocol):
QMalcolm marked this conversation as resolved.
Show resolved Hide resolved
"""Represents a query that the user wants to run repeatedly."""

@property
@abstractmethod
def metadata(self) -> Optional[Metadata]: # noqa: D
pass

@property
@abstractmethod
def name(self) -> str: # noqa: D
pass

@property
@abstractmethod
def description(self) -> Optional[str]: # noqa: D
pass

@property
@abstractmethod
def metrics(self) -> Sequence[str]: # noqa: D
pass

@property
@abstractmethod
def group_bys(self) -> Sequence[str]: # noqa: D
pass

@property
@abstractmethod
def where(self) -> Sequence[WhereFilter]: # noqa: D
pass
5 changes: 5 additions & 0 deletions dbt_semantic_interfaces/protocols/semantic_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from dbt_semantic_interfaces.protocols.metric import Metric
from dbt_semantic_interfaces.protocols.project_configuration import ProjectConfiguration
from dbt_semantic_interfaces.protocols.saved_query import SavedQuery
from dbt_semantic_interfaces.protocols.semantic_model import SemanticModel


Expand All @@ -24,5 +25,9 @@ def metrics(self) -> Sequence[Metric]: # noqa: D
def project_configuration(self) -> ProjectConfiguration: # noqa: D
pass

@property
def saved_queries(self) -> Sequence[SavedQuery]: # noqa: D
pass


SemanticManifestT = TypeVar("SemanticManifestT", bound=SemanticManifest)
5 changes: 1 addition & 4 deletions dbt_semantic_interfaces/validations/primary_entity.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import logging
from typing import Generic, List, Sequence

from dbt_semantic_interfaces.implementations.semantic_manifest import (
PydanticSemanticManifest,
)
from dbt_semantic_interfaces.protocols import SemanticManifestT, SemanticModel
from dbt_semantic_interfaces.references import SemanticModelReference
from dbt_semantic_interfaces.type_enums import EntityType
Expand Down Expand Up @@ -95,7 +92,7 @@ def _check_model(semantic_model: SemanticModel) -> Sequence[ValidationIssue]:

@staticmethod
@validate_safely("Check that semantic models in the manifest have properly configured primary entities.")
def validate_manifest(semantic_manifest: PydanticSemanticManifest) -> Sequence[ValidationIssue]: # noqa: D
def validate_manifest(semantic_manifest: SemanticManifestT) -> Sequence[ValidationIssue]: # noqa: D
issues: List[ValidationIssue] = []
for semantic_model in semantic_manifest.semantic_models:
issues += PrimaryEntityRule._check_model(semantic_model)
Expand Down
Loading
Loading