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

Bump version #316

Merged
merged 6 commits into from
Jul 16, 2024
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-20240716-104215.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-07-16T10:42:15.662883-07:00
custom:
Author: courtneyholcomb
Issue: "280"
55 changes: 55 additions & 0 deletions dbt_semantic_interfaces/implementations/node_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from __future__ import annotations

from typing import Any, Optional

from typing_extensions import override

from dbt_semantic_interfaces.implementations.base import HashableBaseModel
from dbt_semantic_interfaces.protocols import ProtocolHint
from dbt_semantic_interfaces.protocols.node_relation import NodeRelation
from dsi_pydantic_shim import validator


class PydanticNodeRelation(HashableBaseModel, ProtocolHint[NodeRelation]):
"""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]
"""Dynamically build the dot path for `relation_name`, if not specified."""
if value:
# Only build the relation_name if it was not present in config.
return value

alias, schema, database = values.get("alias"), values.get("schema_name"), values.get("database")
if alias is None or schema is None:
raise ValueError(
f"Failed to build relation_name because alias and/or schema was None. schema: {schema}, alias: {alias}"
)

if database is not None:
value = f"{database}.{schema}.{alias}"
else:
value = f"{schema}.{alias}"
return value

@staticmethod
def from_string(sql_str: str) -> PydanticNodeRelation: # noqa: D
sql_str_split = sql_str.split(".")
if len(sql_str_split) == 2:
return PydanticNodeRelation(schema_name=sql_str_split[0], alias=sql_str_split[1])
elif len(sql_str_split) == 3:
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}"
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
UNKNOWN_VERSION_SENTINEL,
PydanticSemanticVersion,
)
from dbt_semantic_interfaces.implementations.time_spine import PydanticTimeSpine
from dbt_semantic_interfaces.implementations.time_spine_table_configuration import (
PydanticTimeSpineTableConfiguration,
)
Expand All @@ -32,6 +33,7 @@ def _implements_protocol(self) -> ProjectConfiguration:
time_spine_table_configurations: List[PydanticTimeSpineTableConfiguration]
metadata: Optional[PydanticMetadata] = None
dsi_package_version: PydanticSemanticVersion = UNKNOWN_VERSION_SENTINEL
time_spines: List[PydanticTimeSpine] = []

@validator("dsi_package_version", always=True)
@classmethod
Expand Down
46 changes: 3 additions & 43 deletions dbt_semantic_interfaces/implementations/semantic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from dbt_semantic_interfaces.implementations.elements.entity import PydanticEntity
from dbt_semantic_interfaces.implementations.elements.measure import PydanticMeasure
from dbt_semantic_interfaces.implementations.metadata import PydanticMetadata
from dbt_semantic_interfaces.implementations.node_relation import PydanticNodeRelation
from dbt_semantic_interfaces.protocols import (
ProtocolHint,
SemanticModel,
Expand All @@ -26,48 +27,7 @@
SemanticModelReference,
TimeDimensionReference,
)
from dsi_pydantic_shim import Field, validator


class NodeRelation(HashableBaseModel):
"""Path object to where the data should be."""

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

@validator("relation_name", always=True)
@classmethod
def __create_default_relation_name(cls, value: Any, values: Any) -> str: # type: ignore[misc]
"""Dynamically build the dot path for `relation_name`, if not specified."""
if value:
# Only build the relation_name if it was not present in config.
return value

alias, schema, database = values.get("alias"), values.get("schema_name"), values.get("database")
if alias is None or schema is None:
raise ValueError(
f"Failed to build relation_name because alias and/or schema was None. schema: {schema}, alias: {alias}"
)

if database is not None:
value = f"{database}.{schema}.{alias}"
else:
value = f"{schema}.{alias}"
return value

@staticmethod
def from_string(sql_str: str) -> NodeRelation: # 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])
elif len(sql_str_split) == 3:
return NodeRelation(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}"
)
from dsi_pydantic_shim import Field


class PydanticSemanticModelDefaults(HashableBaseModel, ProtocolHint[SemanticModelDefaults]): # noqa: D
Expand Down Expand Up @@ -96,7 +56,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
35 changes: 35 additions & 0 deletions dbt_semantic_interfaces/implementations/time_spine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from __future__ import annotations

from typing_extensions import override

from dbt_semantic_interfaces.implementations.base import HashableBaseModel
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,
TimeSpinePrimaryColumn,
)
from dbt_semantic_interfaces.type_enums import TimeGranularity


class PydanticTimeSpinePrimaryColumn(HashableBaseModel, ProtocolHint[TimeSpinePrimaryColumn]):
"""Legacy Pydantic implementation of SemanticVersion. In the process of deprecation."""

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

name: str
time_granularity: TimeGranularity


class PydanticTimeSpine(HashableBaseModel, ProtocolHint[TimeSpine]):
"""Legacy Pydantic implementation of SemanticVersion. In the process of deprecation."""

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

name: str
node_relation: PydanticNodeRelation
primary_column: PydanticTimeSpinePrimaryColumn
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
class PydanticTimeSpineTableConfiguration(
HashableBaseModel, ModelWithMetadataParsing, ProtocolHint[TimeSpineTableConfiguration]
):
"""Pydantic implementation of SemanticVersion."""
"""Legacy Pydantic implementation of SemanticVersion. In the process of deprecation."""

@override
def _implements_protocol(self) -> TimeSpineTableConfiguration:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,11 +621,15 @@
"$ref": "#/definitions/time_spine_table_configuration_schema"
},
"type": "array"
},
"time_spines": {
"items": {
"$ref": "#/definitions/time_spine_schema"
},
"type": "array"
}
},
"required": [
"time_spine_table_configurations"
],
"required": [],
"type": "object"
},
"saved_query_query_params_schema": {
Expand Down Expand Up @@ -756,6 +760,67 @@
],
"type": "object"
},
"time_spine_primary_column_schema": {
"$id": "time_spine_primary_column_schema",
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"time_granularity": {
"enum": [
"NANOSECOND",
"MICROSECOND",
"MILLISECOND",
"SECOND",
"MINUTE",
"HOUR",
"DAY",
"WEEK",
"MONTH",
"QUARTER",
"YEAR",
"nanosecond",
"microsecond",
"millisecond",
"second",
"minute",
"hour",
"day",
"week",
"month",
"quarter",
"year"
]
}
},
"required": [
"name",
"time_granularity"
],
"type": "object"
},
"time_spine_schema": {
"$id": "time_spine_schema",
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"node_relation": {
"$ref": "#/definitions/node_relation_schema"
},
"primary_column": {
"$ref": "#/definitions/time_spine_primary_column_schema"
}
},
"required": [
"name",
"node_relation",
"primary_column"
],
"type": "object"
},
"time_spine_table_configuration_schema": {
"$id": "time_spine_table_configuration_schema",
"additionalProperties": false,
Expand Down
31 changes: 30 additions & 1 deletion dbt_semantic_interfaces/parsing/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,29 @@
"required": ["location", "column_name", "grain"],
}

time_spine_primary_column_schema = {
"$id": "time_spine_primary_column_schema",
"type": "object",
"properties": {
"name": {"type": "string"},
"time_granularity": {"enum": time_granularity_values},
},
"additionalProperties": False,
"required": ["name", "time_granularity"],
}

time_spine_schema = {
"$id": "time_spine_schema",
"type": "object",
"properties": {
"name": {"type": "string"},
"node_relation": {"$ref": "node_relation_schema"},
"primary_column": {"$ref": "time_spine_primary_column_schema"},
},
"additionalProperties": False,
"required": ["name", "node_relation", "primary_column"],
}


project_configuration_schema = {
"$id": "project_configuration_schema",
Expand All @@ -356,9 +379,13 @@
"type": "array",
"items": {"$ref": "time_spine_table_configuration_schema"},
},
"time_spines": {
"type": "array",
"items": {"$ref": "time_spine_schema"},
},
},
"additionalProperties": False,
"required": ["time_spine_table_configurations"],
"required": [],
}

export_config_schema = {
Expand Down Expand Up @@ -475,6 +502,8 @@
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,
time_spine_primary_column_schema["$id"]: time_spine_primary_column_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
28 changes: 28 additions & 0 deletions dbt_semantic_interfaces/protocols/node_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from __future__ import annotations

from abc import abstractmethod
from typing import Optional, Protocol


class NodeRelation(Protocol):
"""Path object to where the data should be."""

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

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

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

@property
@abstractmethod
def relation_name(self) -> str: # noqa: D
pass
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Protocol, Sequence

from dbt_semantic_interfaces.protocols.semantic_version import SemanticVersion
from dbt_semantic_interfaces.protocols.time_spine import TimeSpine
from dbt_semantic_interfaces.protocols.time_spine_configuration import (
TimeSpineTableConfiguration,
)
Expand All @@ -18,6 +19,12 @@ 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

@property
@abstractmethod
def time_spine_table_configurations(self) -> Sequence[TimeSpineTableConfiguration]:
"""Legacy time spine table configurations. In the process of deprecation."""
pass
Loading
Loading