228 lines
6.2 KiB
Python
228 lines
6.2 KiB
Python
|
from dbt.contracts.graph.parsed import (
|
||
|
HasTestMetadata,
|
||
|
ParsedNode,
|
||
|
ParsedAnalysisNode,
|
||
|
ParsedDataTestNode,
|
||
|
ParsedHookNode,
|
||
|
ParsedModelNode,
|
||
|
ParsedExposure,
|
||
|
ParsedResource,
|
||
|
ParsedRPCNode,
|
||
|
ParsedSchemaTestNode,
|
||
|
ParsedSeedNode,
|
||
|
ParsedSnapshotNode,
|
||
|
ParsedSourceDefinition,
|
||
|
SeedConfig,
|
||
|
TestConfig,
|
||
|
same_seeds,
|
||
|
)
|
||
|
from dbt.node_types import NodeType
|
||
|
from dbt.contracts.util import Replaceable
|
||
|
|
||
|
from dbt.dataclass_schema import dbtClassMixin
|
||
|
from dataclasses import dataclass, field
|
||
|
from typing import Optional, List, Union, Dict, Type
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class InjectedCTE(dbtClassMixin, Replaceable):
|
||
|
id: str
|
||
|
sql: str
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class CompiledNodeMixin(dbtClassMixin):
|
||
|
# this is a special mixin class to provide a required argument. If a node
|
||
|
# is missing a `compiled` flag entirely, it must not be a CompiledNode.
|
||
|
compiled: bool
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class CompiledNode(ParsedNode, CompiledNodeMixin):
|
||
|
compiled_sql: Optional[str] = None
|
||
|
extra_ctes_injected: bool = False
|
||
|
extra_ctes: List[InjectedCTE] = field(default_factory=list)
|
||
|
relation_name: Optional[str] = None
|
||
|
_pre_injected_sql: Optional[str] = None
|
||
|
|
||
|
def set_cte(self, cte_id: str, sql: str):
|
||
|
"""This is the equivalent of what self.extra_ctes[cte_id] = sql would
|
||
|
do if extra_ctes were an OrderedDict
|
||
|
"""
|
||
|
for cte in self.extra_ctes:
|
||
|
if cte.id == cte_id:
|
||
|
cte.sql = sql
|
||
|
break
|
||
|
else:
|
||
|
self.extra_ctes.append(InjectedCTE(id=cte_id, sql=sql))
|
||
|
|
||
|
def __post_serialize__(self, dct):
|
||
|
dct = super().__post_serialize__(dct)
|
||
|
if '_pre_injected_sql' in dct:
|
||
|
del dct['_pre_injected_sql']
|
||
|
return dct
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class CompiledAnalysisNode(CompiledNode):
|
||
|
resource_type: NodeType = field(metadata={'restrict': [NodeType.Analysis]})
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class CompiledHookNode(CompiledNode):
|
||
|
resource_type: NodeType = field(
|
||
|
metadata={'restrict': [NodeType.Operation]}
|
||
|
)
|
||
|
index: Optional[int] = None
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class CompiledModelNode(CompiledNode):
|
||
|
resource_type: NodeType = field(metadata={'restrict': [NodeType.Model]})
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class CompiledRPCNode(CompiledNode):
|
||
|
resource_type: NodeType = field(metadata={'restrict': [NodeType.RPCCall]})
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class CompiledSeedNode(CompiledNode):
|
||
|
# keep this in sync with ParsedSeedNode!
|
||
|
resource_type: NodeType = field(metadata={'restrict': [NodeType.Seed]})
|
||
|
config: SeedConfig = field(default_factory=SeedConfig)
|
||
|
|
||
|
@property
|
||
|
def empty(self):
|
||
|
""" Seeds are never empty"""
|
||
|
return False
|
||
|
|
||
|
def same_body(self, other) -> bool:
|
||
|
return same_seeds(self, other)
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class CompiledSnapshotNode(CompiledNode):
|
||
|
resource_type: NodeType = field(metadata={'restrict': [NodeType.Snapshot]})
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class CompiledDataTestNode(CompiledNode):
|
||
|
resource_type: NodeType = field(metadata={'restrict': [NodeType.Test]})
|
||
|
# Was not able to make mypy happy and keep the code working. We need to
|
||
|
# refactor the various configs.
|
||
|
config: TestConfig = field(default_factory=TestConfig) # type:ignore
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class CompiledSchemaTestNode(CompiledNode, HasTestMetadata):
|
||
|
# keep this in sync with ParsedSchemaTestNode!
|
||
|
resource_type: NodeType = field(metadata={'restrict': [NodeType.Test]})
|
||
|
column_name: Optional[str] = None
|
||
|
# Was not able to make mypy happy and keep the code working. We need to
|
||
|
# refactor the various configs.
|
||
|
config: TestConfig = field(default_factory=TestConfig) # type:ignore
|
||
|
|
||
|
def same_contents(self, other) -> bool:
|
||
|
if other is None:
|
||
|
return False
|
||
|
|
||
|
return (
|
||
|
self.same_config(other) and
|
||
|
self.same_fqn(other) and
|
||
|
True
|
||
|
)
|
||
|
|
||
|
|
||
|
CompiledTestNode = Union[CompiledDataTestNode, CompiledSchemaTestNode]
|
||
|
|
||
|
|
||
|
PARSED_TYPES: Dict[Type[CompiledNode], Type[ParsedResource]] = {
|
||
|
CompiledAnalysisNode: ParsedAnalysisNode,
|
||
|
CompiledModelNode: ParsedModelNode,
|
||
|
CompiledHookNode: ParsedHookNode,
|
||
|
CompiledRPCNode: ParsedRPCNode,
|
||
|
CompiledSeedNode: ParsedSeedNode,
|
||
|
CompiledSnapshotNode: ParsedSnapshotNode,
|
||
|
CompiledDataTestNode: ParsedDataTestNode,
|
||
|
CompiledSchemaTestNode: ParsedSchemaTestNode,
|
||
|
}
|
||
|
|
||
|
|
||
|
COMPILED_TYPES: Dict[Type[ParsedResource], Type[CompiledNode]] = {
|
||
|
ParsedAnalysisNode: CompiledAnalysisNode,
|
||
|
ParsedModelNode: CompiledModelNode,
|
||
|
ParsedHookNode: CompiledHookNode,
|
||
|
ParsedRPCNode: CompiledRPCNode,
|
||
|
ParsedSeedNode: CompiledSeedNode,
|
||
|
ParsedSnapshotNode: CompiledSnapshotNode,
|
||
|
ParsedDataTestNode: CompiledDataTestNode,
|
||
|
ParsedSchemaTestNode: CompiledSchemaTestNode,
|
||
|
}
|
||
|
|
||
|
|
||
|
# for some types, the compiled type is the parsed type, so make this easy
|
||
|
CompiledType = Union[Type[CompiledNode], Type[ParsedResource]]
|
||
|
CompiledResource = Union[ParsedResource, CompiledNode]
|
||
|
|
||
|
|
||
|
def compiled_type_for(parsed: ParsedNode) -> CompiledType:
|
||
|
if type(parsed) in COMPILED_TYPES:
|
||
|
return COMPILED_TYPES[type(parsed)]
|
||
|
else:
|
||
|
return type(parsed)
|
||
|
|
||
|
|
||
|
def parsed_instance_for(compiled: CompiledNode) -> ParsedResource:
|
||
|
cls = PARSED_TYPES.get(type(compiled))
|
||
|
if cls is None:
|
||
|
# how???
|
||
|
raise ValueError('invalid resource_type: {}'
|
||
|
.format(compiled.resource_type))
|
||
|
|
||
|
return cls.from_dict(compiled.to_dict(omit_none=True))
|
||
|
|
||
|
|
||
|
NonSourceCompiledNode = Union[
|
||
|
CompiledAnalysisNode,
|
||
|
CompiledDataTestNode,
|
||
|
CompiledModelNode,
|
||
|
CompiledHookNode,
|
||
|
CompiledRPCNode,
|
||
|
CompiledSchemaTestNode,
|
||
|
CompiledSeedNode,
|
||
|
CompiledSnapshotNode,
|
||
|
]
|
||
|
|
||
|
NonSourceParsedNode = Union[
|
||
|
ParsedAnalysisNode,
|
||
|
ParsedDataTestNode,
|
||
|
ParsedHookNode,
|
||
|
ParsedModelNode,
|
||
|
ParsedRPCNode,
|
||
|
ParsedSchemaTestNode,
|
||
|
ParsedSeedNode,
|
||
|
ParsedSnapshotNode,
|
||
|
]
|
||
|
|
||
|
|
||
|
# This is anything that can be in manifest.nodes.
|
||
|
ManifestNode = Union[
|
||
|
NonSourceCompiledNode,
|
||
|
NonSourceParsedNode,
|
||
|
]
|
||
|
|
||
|
# We allow either parsed or compiled nodes, or parsed sources, as some
|
||
|
# 'compile()' calls in the runner actually just return the original parsed
|
||
|
# node they were given.
|
||
|
CompileResultNode = Union[
|
||
|
ManifestNode,
|
||
|
ParsedSourceDefinition,
|
||
|
]
|
||
|
|
||
|
# anything that participates in the graph: sources, exposures, manifest nodes
|
||
|
GraphMemberNode = Union[
|
||
|
CompileResultNode,
|
||
|
ParsedExposure,
|
||
|
]
|