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

Update time spine configs for sub-daily granularity & custom calendar #293

Closed
wants to merge 5 commits into from
Closed
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-20240617-124717.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Support for configuring multiple time spines at different granularities.
time: 2024-06-17T12:47:17.06019-07:00
custom:
Author: courtneyholcomb
Issue: "280"
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
UNKNOWN_VERSION_SENTINEL,
PydanticSemanticVersion,
)
from dbt_semantic_interfaces.implementations.time_spine_table_configuration import (
PydanticTimeSpineTableConfiguration,
)
from dbt_semantic_interfaces.implementations.time_spine import PydanticTimeSpine
from dbt_semantic_interfaces.protocols import ProtocolHint
from dbt_semantic_interfaces.protocols.project_configuration import ProjectConfiguration
from dsi_pydantic_shim import validator
Expand All @@ -29,7 +27,7 @@ class PydanticProjectConfiguration(HashableBaseModel, ModelWithMetadataParsing,
def _implements_protocol(self) -> ProjectConfiguration:
return self

time_spine_table_configurations: List[PydanticTimeSpineTableConfiguration]
time_spines: List[PydanticTimeSpine]
metadata: Optional[PydanticMetadata] = None
dsi_package_version: PydanticSemanticVersion = UNKNOWN_VERSION_SENTINEL

Expand Down
15 changes: 10 additions & 5 deletions dbt_semantic_interfaces/implementations/semantic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
SemanticModelConfig,
SemanticModelDefaults,
)
from dbt_semantic_interfaces.protocols.semantic_model import NodeRelation
from dbt_semantic_interfaces.references import (
EntityReference,
LinkableElementReference,
Expand All @@ -28,14 +29,18 @@
from dsi_pydantic_shim import Field, validator


class NodeRelation(HashableBaseModel):
class PydanticNodeRelation(HashableBaseModel, ProtocolHint[NodeRelation]):
Copy link
Collaborator

Choose a reason for hiding this comment

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

The changes for this class are not breaking for core

"""Path object to where the data should be."""

alias: str
schema_name: str
database: Optional[str] = None
relation_name: str = ""

@override
def _implements_protocol(self) -> NodeRelation: # noqa: D
return self

@validator("relation_name", always=True)
@classmethod
def __create_default_relation_name(cls, value: Any, values: Any) -> str: # type: ignore[misc]
Expand All @@ -57,12 +62,12 @@ def __create_default_relation_name(cls, value: Any, values: Any) -> str: # type
return value

@staticmethod
def from_string(sql_str: str) -> NodeRelation: # noqa: D
def from_string(sql_str: str) -> PydanticNodeRelation: # noqa: D
sql_str_split = sql_str.split(".")
if len(sql_str_split) == 2:
return NodeRelation(schema_name=sql_str_split[0], alias=sql_str_split[1])
return PydanticNodeRelation(schema_name=sql_str_split[0], alias=sql_str_split[1])
elif len(sql_str_split) == 3:
return NodeRelation(database=sql_str_split[0], schema_name=sql_str_split[1], alias=sql_str_split[2])
return PydanticNodeRelation(database=sql_str_split[0], schema_name=sql_str_split[1], alias=sql_str_split[2])
raise RuntimeError(
f"Invalid input for a SQL table, expected form '<schema>.<table>' or '<db>.<schema>.<table>' "
f"but got: {sql_str}"
Expand Down Expand Up @@ -95,7 +100,7 @@ def _implements_protocol(self) -> SemanticModel:
name: str
defaults: Optional[PydanticSemanticModelDefaults]
description: Optional[str]
node_relation: NodeRelation
node_relation: PydanticNodeRelation

primary_entity: Optional[str]
entities: Sequence[PydanticEntity] = []
Expand Down
25 changes: 25 additions & 0 deletions dbt_semantic_interfaces/implementations/time_spine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from __future__ import annotations

from typing_extensions import override

from dbt_semantic_interfaces.implementations.base import (
HashableBaseModel,
ModelWithMetadataParsing,
)
from dbt_semantic_interfaces.implementations.semantic_model import PydanticNodeRelation
from dbt_semantic_interfaces.protocols import ProtocolHint
from dbt_semantic_interfaces.protocols.time_spine import TimeSpine
from dbt_semantic_interfaces.type_enums import TimeGranularity


class PydanticTimeSpine(HashableBaseModel, ModelWithMetadataParsing, ProtocolHint[TimeSpine]):
Copy link
Collaborator

Choose a reason for hiding this comment

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

We import the "old implementaiton" (PydanticTimeSpineTableConfiguration) directly into core. Moving it is a breaking change. That is fine, but should be noted in version bump when being released. Our cap in core is <0.7.0 I believe, so this will need to go into a >=0.7.0 release

"""Pydantic implementation of TimeSpine."""

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

name: str
node_relation: PydanticNodeRelation
base_column: str
base_granularity: TimeGranularity

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -540,15 +540,15 @@
"$id": "project_configuration_schema",
"additionalProperties": false,
"properties": {
"time_spine_table_configurations": {
"time_spines": {
"items": {
"$ref": "#/definitions/time_spine_table_configuration_schema"
"$ref": "#/definitions/time_spine_schema"
},
"type": "array"
}
},
"required": [
"time_spine_table_configurations"
"time_spines"
],
"type": "object"
},
Expand Down Expand Up @@ -680,14 +680,14 @@
],
"type": "object"
},
"time_spine_table_configuration_schema": {
"$id": "time_spine_table_configuration_schema",
"time_spine_schema": {
"$id": "time_spine_schema",
"additionalProperties": false,
"properties": {
"column_name": {
"base_column": {
"type": "string"
},
"grain": {
"base_granularity": {
"enum": [
"NANOSECOND",
"MICROSECOND",
Expand All @@ -713,14 +713,18 @@
"year"
]
},
"location": {
"name": {
"type": "string"
},
"node_relation": {
"$ref": "#/definitions/node_relation_schema"
}
},
"required": [
"location",
"column_name",
"grain"
"name",
"node_relation",
"base_column",
"base_granularity"
],
"type": "object"
},
Expand Down
21 changes: 11 additions & 10 deletions dbt_semantic_interfaces/parsing/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,30 +318,31 @@
}


time_spine_table_configuration_schema = {
"$id": "time_spine_table_configuration_schema",
time_spine_schema = {
"$id": "time_spine_schema",
"type": "object",
"properties": {
"location": {"type": "string"},
"column_name": {"type": "string"},
"grain": {"enum": time_granularity_values},
"name": {"type": "string"},
"node_relation": {"$ref": "node_relation_schema"},
"base_column": {"type": "string"},
"base_granularity": {"enum": time_granularity_values},
},
"additionalProperties": False,
"required": ["location", "column_name", "grain"],
"required": ["name", "node_relation", "base_column", "base_granularity"],
}


project_configuration_schema = {
"$id": "project_configuration_schema",
"type": "object",
"properties": {
"time_spine_table_configurations": {
"time_spines": {
"type": "array",
"items": {"$ref": "time_spine_table_configuration_schema"},
"items": {"$ref": "time_spine_schema"},
},
},
"additionalProperties": False,
"required": ["time_spine_table_configurations"],
"required": ["time_spines"],
}

export_config_schema = {
Expand Down Expand Up @@ -456,7 +457,7 @@
metric_input_schema["$id"]: metric_input_schema,
node_relation_schema["$id"]: node_relation_schema,
semantic_model_defaults_schema["$id"]: semantic_model_defaults_schema,
time_spine_table_configuration_schema["$id"]: time_spine_table_configuration_schema,
time_spine_schema["$id"]: time_spine_schema,
export_schema["$id"]: export_schema,
export_config_schema["$id"]: export_config_schema,
saved_query_query_params_schema["$id"]: saved_query_query_params_schema,
Expand Down
6 changes: 2 additions & 4 deletions dbt_semantic_interfaces/protocols/project_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
from typing import Protocol, Sequence

from dbt_semantic_interfaces.protocols.semantic_version import SemanticVersion
from dbt_semantic_interfaces.protocols.time_spine_configuration import (
TimeSpineTableConfiguration,
)
from dbt_semantic_interfaces.protocols.time_spine import TimeSpine


class ProjectConfiguration(Protocol):
Expand All @@ -18,6 +16,6 @@ def dsi_package_version(self) -> SemanticVersion:

@property
@abstractmethod
def time_spine_table_configurations(self) -> Sequence[TimeSpineTableConfiguration]:
def time_spines(self) -> Sequence[TimeSpine]:
"""The time spine table configurations. Multiple allowed for different time grains."""
pass
46 changes: 46 additions & 0 deletions dbt_semantic_interfaces/protocols/time_spine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from abc import abstractmethod
from typing import Protocol

from dbt_semantic_interfaces.protocols.semantic_model import NodeRelation
from dbt_semantic_interfaces.type_enums import TimeGranularity


class TimeSpine(Protocol):
"""Describes the configuration for a time spine table.

A time spine table is a table with at least one column containing dates at a specific grain.

e.g. with day granularity:
...
2020-01-01
2020-01-02
2020-01-03
...
"""

@property
@abstractmethod
def name(self) -> str:
"""Name used to reference this time spine."""
pass

@property
@abstractmethod
def node_relation(self) -> NodeRelation:
"""dbt model that represents the time spine.""" # noqa: D403
pass

@property
@abstractmethod
def base_column(self) -> str:
"""The name of the column in the time spine table that has the values at the base granularity."""
pass

@property
@abstractmethod
def base_granularity(self) -> TimeGranularity:
"""The grain of the dates in the base_column.

Must map to one of the default TimeGranularity values, not a custom granularity.
"""
pass
37 changes: 0 additions & 37 deletions dbt_semantic_interfaces/protocols/time_spine_configuration.py

This file was deleted.

6 changes: 3 additions & 3 deletions dbt_semantic_interfaces/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
PydanticSemanticManifest,
)
from dbt_semantic_interfaces.implementations.semantic_model import (
NodeRelation,
PydanticNodeRelation,
PydanticSemanticModel,
)
from dbt_semantic_interfaces.parsing.objects import YamlConfigFile
Expand Down Expand Up @@ -141,7 +141,7 @@ def metric_with_guaranteed_meta(
def semantic_model_with_guaranteed_meta(
name: str,
description: Optional[str] = None,
node_relation: Optional[NodeRelation] = None,
node_relation: Optional[PydanticNodeRelation] = None,
metadata: PydanticMetadata = default_meta(),
entities: Sequence[PydanticEntity] = (),
measures: Sequence[PydanticMeasure] = (),
Expand All @@ -153,7 +153,7 @@ def semantic_model_with_guaranteed_meta(
"""
created_node_relation = node_relation
if created_node_relation is None:
created_node_relation = NodeRelation(
created_node_relation = PydanticNodeRelation(
schema_name="schema",
alias="table",
)
Expand Down
Loading
Loading