pytrilogy 0.3.142__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- LICENSE.md +19 -0
- _preql_import_resolver/__init__.py +5 -0
- _preql_import_resolver/_preql_import_resolver.cpython-313-x86_64-linux-gnu.so +0 -0
- pytrilogy-0.3.142.dist-info/METADATA +555 -0
- pytrilogy-0.3.142.dist-info/RECORD +200 -0
- pytrilogy-0.3.142.dist-info/WHEEL +5 -0
- pytrilogy-0.3.142.dist-info/entry_points.txt +2 -0
- pytrilogy-0.3.142.dist-info/licenses/LICENSE.md +19 -0
- trilogy/__init__.py +16 -0
- trilogy/ai/README.md +10 -0
- trilogy/ai/__init__.py +19 -0
- trilogy/ai/constants.py +92 -0
- trilogy/ai/conversation.py +107 -0
- trilogy/ai/enums.py +7 -0
- trilogy/ai/execute.py +50 -0
- trilogy/ai/models.py +34 -0
- trilogy/ai/prompts.py +100 -0
- trilogy/ai/providers/__init__.py +0 -0
- trilogy/ai/providers/anthropic.py +106 -0
- trilogy/ai/providers/base.py +24 -0
- trilogy/ai/providers/google.py +146 -0
- trilogy/ai/providers/openai.py +89 -0
- trilogy/ai/providers/utils.py +68 -0
- trilogy/authoring/README.md +3 -0
- trilogy/authoring/__init__.py +148 -0
- trilogy/constants.py +113 -0
- trilogy/core/README.md +52 -0
- trilogy/core/__init__.py +0 -0
- trilogy/core/constants.py +6 -0
- trilogy/core/enums.py +443 -0
- trilogy/core/env_processor.py +120 -0
- trilogy/core/environment_helpers.py +320 -0
- trilogy/core/ergonomics.py +193 -0
- trilogy/core/exceptions.py +123 -0
- trilogy/core/functions.py +1227 -0
- trilogy/core/graph_models.py +139 -0
- trilogy/core/internal.py +85 -0
- trilogy/core/models/__init__.py +0 -0
- trilogy/core/models/author.py +2669 -0
- trilogy/core/models/build.py +2521 -0
- trilogy/core/models/build_environment.py +180 -0
- trilogy/core/models/core.py +501 -0
- trilogy/core/models/datasource.py +322 -0
- trilogy/core/models/environment.py +751 -0
- trilogy/core/models/execute.py +1177 -0
- trilogy/core/optimization.py +251 -0
- trilogy/core/optimizations/__init__.py +12 -0
- trilogy/core/optimizations/base_optimization.py +17 -0
- trilogy/core/optimizations/hide_unused_concept.py +47 -0
- trilogy/core/optimizations/inline_datasource.py +102 -0
- trilogy/core/optimizations/predicate_pushdown.py +245 -0
- trilogy/core/processing/README.md +94 -0
- trilogy/core/processing/READMEv2.md +121 -0
- trilogy/core/processing/VIRTUAL_UNNEST.md +30 -0
- trilogy/core/processing/__init__.py +0 -0
- trilogy/core/processing/concept_strategies_v3.py +508 -0
- trilogy/core/processing/constants.py +15 -0
- trilogy/core/processing/discovery_node_factory.py +451 -0
- trilogy/core/processing/discovery_utility.py +548 -0
- trilogy/core/processing/discovery_validation.py +167 -0
- trilogy/core/processing/graph_utils.py +43 -0
- trilogy/core/processing/node_generators/README.md +9 -0
- trilogy/core/processing/node_generators/__init__.py +31 -0
- trilogy/core/processing/node_generators/basic_node.py +160 -0
- trilogy/core/processing/node_generators/common.py +268 -0
- trilogy/core/processing/node_generators/constant_node.py +38 -0
- trilogy/core/processing/node_generators/filter_node.py +315 -0
- trilogy/core/processing/node_generators/group_node.py +213 -0
- trilogy/core/processing/node_generators/group_to_node.py +117 -0
- trilogy/core/processing/node_generators/multiselect_node.py +205 -0
- trilogy/core/processing/node_generators/node_merge_node.py +653 -0
- trilogy/core/processing/node_generators/recursive_node.py +88 -0
- trilogy/core/processing/node_generators/rowset_node.py +165 -0
- trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +261 -0
- trilogy/core/processing/node_generators/select_merge_node.py +748 -0
- trilogy/core/processing/node_generators/select_node.py +95 -0
- trilogy/core/processing/node_generators/synonym_node.py +98 -0
- trilogy/core/processing/node_generators/union_node.py +91 -0
- trilogy/core/processing/node_generators/unnest_node.py +182 -0
- trilogy/core/processing/node_generators/window_node.py +201 -0
- trilogy/core/processing/nodes/README.md +28 -0
- trilogy/core/processing/nodes/__init__.py +179 -0
- trilogy/core/processing/nodes/base_node.py +519 -0
- trilogy/core/processing/nodes/filter_node.py +75 -0
- trilogy/core/processing/nodes/group_node.py +194 -0
- trilogy/core/processing/nodes/merge_node.py +420 -0
- trilogy/core/processing/nodes/recursive_node.py +46 -0
- trilogy/core/processing/nodes/select_node_v2.py +242 -0
- trilogy/core/processing/nodes/union_node.py +53 -0
- trilogy/core/processing/nodes/unnest_node.py +62 -0
- trilogy/core/processing/nodes/window_node.py +56 -0
- trilogy/core/processing/utility.py +823 -0
- trilogy/core/query_processor.py +596 -0
- trilogy/core/statements/README.md +35 -0
- trilogy/core/statements/__init__.py +0 -0
- trilogy/core/statements/author.py +536 -0
- trilogy/core/statements/build.py +0 -0
- trilogy/core/statements/common.py +20 -0
- trilogy/core/statements/execute.py +155 -0
- trilogy/core/table_processor.py +66 -0
- trilogy/core/utility.py +8 -0
- trilogy/core/validation/README.md +46 -0
- trilogy/core/validation/__init__.py +0 -0
- trilogy/core/validation/common.py +161 -0
- trilogy/core/validation/concept.py +146 -0
- trilogy/core/validation/datasource.py +227 -0
- trilogy/core/validation/environment.py +73 -0
- trilogy/core/validation/fix.py +256 -0
- trilogy/dialect/__init__.py +32 -0
- trilogy/dialect/base.py +1392 -0
- trilogy/dialect/bigquery.py +308 -0
- trilogy/dialect/common.py +147 -0
- trilogy/dialect/config.py +144 -0
- trilogy/dialect/dataframe.py +50 -0
- trilogy/dialect/duckdb.py +231 -0
- trilogy/dialect/enums.py +147 -0
- trilogy/dialect/metadata.py +173 -0
- trilogy/dialect/mock.py +190 -0
- trilogy/dialect/postgres.py +117 -0
- trilogy/dialect/presto.py +110 -0
- trilogy/dialect/results.py +89 -0
- trilogy/dialect/snowflake.py +129 -0
- trilogy/dialect/sql_server.py +137 -0
- trilogy/engine.py +48 -0
- trilogy/execution/config.py +75 -0
- trilogy/executor.py +568 -0
- trilogy/hooks/__init__.py +4 -0
- trilogy/hooks/base_hook.py +40 -0
- trilogy/hooks/graph_hook.py +139 -0
- trilogy/hooks/query_debugger.py +166 -0
- trilogy/metadata/__init__.py +0 -0
- trilogy/parser.py +10 -0
- trilogy/parsing/README.md +21 -0
- trilogy/parsing/__init__.py +0 -0
- trilogy/parsing/common.py +1069 -0
- trilogy/parsing/config.py +5 -0
- trilogy/parsing/exceptions.py +8 -0
- trilogy/parsing/helpers.py +1 -0
- trilogy/parsing/parse_engine.py +2813 -0
- trilogy/parsing/render.py +769 -0
- trilogy/parsing/trilogy.lark +540 -0
- trilogy/py.typed +0 -0
- trilogy/render.py +42 -0
- trilogy/scripts/README.md +9 -0
- trilogy/scripts/__init__.py +0 -0
- trilogy/scripts/agent.py +41 -0
- trilogy/scripts/agent_info.py +303 -0
- trilogy/scripts/common.py +355 -0
- trilogy/scripts/dependency/Cargo.lock +617 -0
- trilogy/scripts/dependency/Cargo.toml +39 -0
- trilogy/scripts/dependency/README.md +131 -0
- trilogy/scripts/dependency/build.sh +25 -0
- trilogy/scripts/dependency/src/directory_resolver.rs +177 -0
- trilogy/scripts/dependency/src/lib.rs +16 -0
- trilogy/scripts/dependency/src/main.rs +770 -0
- trilogy/scripts/dependency/src/parser.rs +435 -0
- trilogy/scripts/dependency/src/preql.pest +208 -0
- trilogy/scripts/dependency/src/python_bindings.rs +303 -0
- trilogy/scripts/dependency/src/resolver.rs +716 -0
- trilogy/scripts/dependency/tests/base.preql +3 -0
- trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
- trilogy/scripts/dependency/tests/customer.preql +6 -0
- trilogy/scripts/dependency/tests/main.preql +9 -0
- trilogy/scripts/dependency/tests/orders.preql +7 -0
- trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
- trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
- trilogy/scripts/dependency.py +323 -0
- trilogy/scripts/display.py +512 -0
- trilogy/scripts/environment.py +46 -0
- trilogy/scripts/fmt.py +32 -0
- trilogy/scripts/ingest.py +471 -0
- trilogy/scripts/ingest_helpers/__init__.py +1 -0
- trilogy/scripts/ingest_helpers/foreign_keys.py +123 -0
- trilogy/scripts/ingest_helpers/formatting.py +93 -0
- trilogy/scripts/ingest_helpers/typing.py +161 -0
- trilogy/scripts/init.py +105 -0
- trilogy/scripts/parallel_execution.py +713 -0
- trilogy/scripts/plan.py +189 -0
- trilogy/scripts/run.py +63 -0
- trilogy/scripts/serve.py +140 -0
- trilogy/scripts/serve_helpers/__init__.py +41 -0
- trilogy/scripts/serve_helpers/file_discovery.py +142 -0
- trilogy/scripts/serve_helpers/index_generation.py +206 -0
- trilogy/scripts/serve_helpers/models.py +38 -0
- trilogy/scripts/single_execution.py +131 -0
- trilogy/scripts/testing.py +119 -0
- trilogy/scripts/trilogy.py +68 -0
- trilogy/std/__init__.py +0 -0
- trilogy/std/color.preql +3 -0
- trilogy/std/date.preql +13 -0
- trilogy/std/display.preql +18 -0
- trilogy/std/geography.preql +22 -0
- trilogy/std/metric.preql +15 -0
- trilogy/std/money.preql +67 -0
- trilogy/std/net.preql +14 -0
- trilogy/std/ranking.preql +7 -0
- trilogy/std/report.preql +5 -0
- trilogy/std/semantic.preql +6 -0
- trilogy/utility.py +34 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from trilogy.core.graph_models import (
|
|
2
|
+
ReferenceGraph,
|
|
3
|
+
concept_to_node,
|
|
4
|
+
datasource_to_node,
|
|
5
|
+
)
|
|
6
|
+
from trilogy.core.models.build import BuildConcept, BuildDatasource
|
|
7
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def add_concept(
|
|
11
|
+
concept: BuildConcept,
|
|
12
|
+
g: ReferenceGraph,
|
|
13
|
+
concept_mapping: dict[str, BuildConcept],
|
|
14
|
+
default_concept_graph: dict[str, BuildConcept],
|
|
15
|
+
seen: set[str],
|
|
16
|
+
):
|
|
17
|
+
|
|
18
|
+
# if we have sources, recursively add them
|
|
19
|
+
node_name = concept_to_node(concept)
|
|
20
|
+
if node_name in seen:
|
|
21
|
+
return
|
|
22
|
+
seen.add(node_name)
|
|
23
|
+
g.concepts[node_name] = concept
|
|
24
|
+
g.add_node(node_name)
|
|
25
|
+
if concept.concept_arguments:
|
|
26
|
+
for source in concept.concept_arguments:
|
|
27
|
+
if not isinstance(source, BuildConcept):
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"Invalid non-build concept {source} passed into graph generation from {concept}"
|
|
30
|
+
)
|
|
31
|
+
generic = get_default_grain_concept(source, default_concept_graph)
|
|
32
|
+
generic_node = concept_to_node(generic)
|
|
33
|
+
add_concept(generic, g, concept_mapping, default_concept_graph, seen)
|
|
34
|
+
|
|
35
|
+
g.add_edge(generic_node, node_name, fast=True)
|
|
36
|
+
for ps_address in concept.pseudonyms:
|
|
37
|
+
if ps_address not in concept_mapping:
|
|
38
|
+
raise SyntaxError(f"Concept {concept} has invalid pseudonym {ps_address}")
|
|
39
|
+
pseudonym = concept_mapping[ps_address]
|
|
40
|
+
pseudonym = get_default_grain_concept(pseudonym, default_concept_graph)
|
|
41
|
+
pseudonym_node = concept_to_node(pseudonym)
|
|
42
|
+
if (pseudonym_node, node_name) in g.edges and (
|
|
43
|
+
node_name,
|
|
44
|
+
pseudonym_node,
|
|
45
|
+
) in g.edges:
|
|
46
|
+
continue
|
|
47
|
+
if pseudonym_node.split("@")[0] == node_name.split("@")[0]:
|
|
48
|
+
continue
|
|
49
|
+
g.add_edge(pseudonym_node, node_name, fast=True)
|
|
50
|
+
g.add_edge(node_name, pseudonym_node, fast=True)
|
|
51
|
+
g.pseudonyms.add((pseudonym_node, node_name))
|
|
52
|
+
g.pseudonyms.add((node_name, pseudonym_node))
|
|
53
|
+
add_concept(pseudonym, g, concept_mapping, default_concept_graph, seen)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_default_grain_concept(
|
|
57
|
+
concept: BuildConcept, default_concept_graph: dict[str, BuildConcept]
|
|
58
|
+
) -> BuildConcept:
|
|
59
|
+
"""Get the default grain concept from the graph."""
|
|
60
|
+
if concept.address in default_concept_graph:
|
|
61
|
+
return default_concept_graph[concept.address]
|
|
62
|
+
default = concept.with_default_grain()
|
|
63
|
+
default_concept_graph[concept.address] = default
|
|
64
|
+
return default
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def generate_adhoc_graph(
|
|
68
|
+
concepts: list[BuildConcept],
|
|
69
|
+
datasources: list[BuildDatasource],
|
|
70
|
+
default_concept_graph: dict[str, BuildConcept],
|
|
71
|
+
restrict_to_listed: bool = False,
|
|
72
|
+
) -> ReferenceGraph:
|
|
73
|
+
g = ReferenceGraph()
|
|
74
|
+
concept_mapping = {x.address: x for x in concepts}
|
|
75
|
+
seen: set[str] = set()
|
|
76
|
+
for concept in concepts:
|
|
77
|
+
if not isinstance(concept, BuildConcept):
|
|
78
|
+
raise ValueError(f"Invalid non-build concept {concept}")
|
|
79
|
+
|
|
80
|
+
# add all parsed concepts
|
|
81
|
+
for concept in concepts:
|
|
82
|
+
|
|
83
|
+
add_concept(concept, g, concept_mapping, default_concept_graph, seen)
|
|
84
|
+
|
|
85
|
+
for dataset in datasources:
|
|
86
|
+
node = datasource_to_node(dataset)
|
|
87
|
+
g.add_datasource_node(node, dataset)
|
|
88
|
+
for concept in dataset.concepts:
|
|
89
|
+
cnode = concept_to_node(concept)
|
|
90
|
+
g.concepts[cnode] = concept
|
|
91
|
+
g.add_node(cnode)
|
|
92
|
+
if restrict_to_listed:
|
|
93
|
+
if cnode not in g.nodes:
|
|
94
|
+
continue
|
|
95
|
+
g.add_edge(node, cnode, fast=True)
|
|
96
|
+
g.add_edge(cnode, node, fast=True)
|
|
97
|
+
# if there is a key on a table at a different grain
|
|
98
|
+
# add an FK edge to the canonical source, if it exists
|
|
99
|
+
# for example, order ID on order product table
|
|
100
|
+
default = get_default_grain_concept(concept, default_concept_graph)
|
|
101
|
+
|
|
102
|
+
if concept != default:
|
|
103
|
+
dcnode = concept_to_node(default)
|
|
104
|
+
g.concepts[dcnode] = default
|
|
105
|
+
g.add_node(dcnode)
|
|
106
|
+
g.add_edge(cnode, dcnode, fast=True)
|
|
107
|
+
g.add_edge(dcnode, cnode, fast=True)
|
|
108
|
+
return g
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def generate_graph(
|
|
112
|
+
environment: BuildEnvironment,
|
|
113
|
+
) -> ReferenceGraph:
|
|
114
|
+
default_concept_graph: dict[str, BuildConcept] = {}
|
|
115
|
+
return generate_adhoc_graph(
|
|
116
|
+
list(environment.concepts.values())
|
|
117
|
+
+ list(environment.alias_origin_lookup.values()),
|
|
118
|
+
list(environment.datasources.values()),
|
|
119
|
+
default_concept_graph=default_concept_graph,
|
|
120
|
+
)
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
from trilogy.constants import DEFAULT_NAMESPACE
|
|
2
|
+
from trilogy.core.enums import ConceptSource, DatePart, FunctionType, Purpose
|
|
3
|
+
from trilogy.core.functions import AttrAccess
|
|
4
|
+
from trilogy.core.models.author import Concept, Function, Grain, Metadata, TraitDataType
|
|
5
|
+
from trilogy.core.models.core import DataType, StructType, arg_to_datatype
|
|
6
|
+
from trilogy.core.models.environment import Environment
|
|
7
|
+
from trilogy.parsing.common import Meta
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_date_concepts(concept: Concept, environment: Environment):
|
|
11
|
+
if concept.metadata and concept.metadata.line_number:
|
|
12
|
+
base_line_number = concept.metadata.line_number
|
|
13
|
+
else:
|
|
14
|
+
base_line_number = None
|
|
15
|
+
arg_tuples: list[tuple[FunctionType, TraitDataType]] = [
|
|
16
|
+
(FunctionType.MONTH, TraitDataType(type=DataType.INTEGER, traits=["month"])),
|
|
17
|
+
(FunctionType.YEAR, TraitDataType(type=DataType.INTEGER, traits=["year"])),
|
|
18
|
+
(
|
|
19
|
+
FunctionType.QUARTER,
|
|
20
|
+
TraitDataType(type=DataType.INTEGER, traits=["quarter"]),
|
|
21
|
+
),
|
|
22
|
+
(FunctionType.DAY, TraitDataType(type=DataType.INTEGER, traits=["day"])),
|
|
23
|
+
(
|
|
24
|
+
FunctionType.DAY_OF_WEEK,
|
|
25
|
+
TraitDataType(type=DataType.INTEGER, traits=["day_of_week"]),
|
|
26
|
+
),
|
|
27
|
+
]
|
|
28
|
+
for ftype, dtype in arg_tuples:
|
|
29
|
+
fname = ftype.name.lower()
|
|
30
|
+
address = concept.address + f".{fname}"
|
|
31
|
+
if address in environment.concepts:
|
|
32
|
+
continue
|
|
33
|
+
default_type = (
|
|
34
|
+
Purpose.CONSTANT
|
|
35
|
+
if concept.purpose == Purpose.CONSTANT
|
|
36
|
+
else Purpose.PROPERTY
|
|
37
|
+
)
|
|
38
|
+
function = Function.model_construct(
|
|
39
|
+
operator=ftype,
|
|
40
|
+
arguments=[concept.reference],
|
|
41
|
+
output_datatype=dtype,
|
|
42
|
+
output_purpose=default_type,
|
|
43
|
+
)
|
|
44
|
+
new_concept = Concept.model_construct(
|
|
45
|
+
name=f"{concept.name}.{fname}",
|
|
46
|
+
datatype=function.output_datatype,
|
|
47
|
+
purpose=default_type,
|
|
48
|
+
lineage=function,
|
|
49
|
+
grain=concept.grain,
|
|
50
|
+
namespace=concept.namespace,
|
|
51
|
+
keys=set(
|
|
52
|
+
[concept.address],
|
|
53
|
+
),
|
|
54
|
+
metadata=Metadata(
|
|
55
|
+
line_number=base_line_number,
|
|
56
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
60
|
+
for grain in [DatePart.MONTH, DatePart.YEAR]:
|
|
61
|
+
address = concept.address + f".{grain.value}_start"
|
|
62
|
+
if address in environment.concepts:
|
|
63
|
+
continue
|
|
64
|
+
function = Function.model_construct(
|
|
65
|
+
operator=FunctionType.DATE_TRUNCATE,
|
|
66
|
+
arguments=[concept.reference, grain],
|
|
67
|
+
output_datatype=DataType.DATE,
|
|
68
|
+
output_purpose=default_type,
|
|
69
|
+
arg_count=2,
|
|
70
|
+
)
|
|
71
|
+
new_concept = Concept.model_construct(
|
|
72
|
+
name=f"{concept.name}.{grain.value}_start",
|
|
73
|
+
datatype=DataType.DATE,
|
|
74
|
+
purpose=Purpose.PROPERTY,
|
|
75
|
+
lineage=function,
|
|
76
|
+
grain=concept.grain.model_copy(),
|
|
77
|
+
namespace=concept.namespace,
|
|
78
|
+
keys=set(
|
|
79
|
+
[concept.address],
|
|
80
|
+
),
|
|
81
|
+
metadata=Metadata(
|
|
82
|
+
# description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
|
|
83
|
+
line_number=base_line_number,
|
|
84
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
92
|
+
if concept.metadata and concept.metadata.line_number:
|
|
93
|
+
base_line_number = concept.metadata.line_number
|
|
94
|
+
else:
|
|
95
|
+
base_line_number = None
|
|
96
|
+
setup_tuples: list[tuple[FunctionType, DataType | TraitDataType]] = [
|
|
97
|
+
(FunctionType.DATE, DataType.DATE),
|
|
98
|
+
(FunctionType.HOUR, TraitDataType(type=DataType.INTEGER, traits=["hour"])),
|
|
99
|
+
(FunctionType.MINUTE, TraitDataType(type=DataType.INTEGER, traits=["minute"])),
|
|
100
|
+
(FunctionType.SECOND, TraitDataType(type=DataType.INTEGER, traits=["second"])),
|
|
101
|
+
]
|
|
102
|
+
for ftype, datatype in setup_tuples:
|
|
103
|
+
fname = ftype.name.lower()
|
|
104
|
+
address = concept.address + f".{fname}"
|
|
105
|
+
if address in environment.concepts:
|
|
106
|
+
continue
|
|
107
|
+
default_type = (
|
|
108
|
+
Purpose.CONSTANT
|
|
109
|
+
if concept.purpose == Purpose.CONSTANT
|
|
110
|
+
else Purpose.PROPERTY
|
|
111
|
+
)
|
|
112
|
+
const_function = Function.model_construct(
|
|
113
|
+
operator=ftype,
|
|
114
|
+
arguments=[concept.reference],
|
|
115
|
+
output_datatype=datatype,
|
|
116
|
+
output_purpose=default_type,
|
|
117
|
+
)
|
|
118
|
+
new_concept = Concept.model_construct(
|
|
119
|
+
name=f"{concept.name}.{fname}",
|
|
120
|
+
datatype=datatype,
|
|
121
|
+
purpose=default_type,
|
|
122
|
+
lineage=const_function,
|
|
123
|
+
grain=concept.grain.model_copy(),
|
|
124
|
+
namespace=concept.namespace,
|
|
125
|
+
keys=set(
|
|
126
|
+
[concept.address],
|
|
127
|
+
),
|
|
128
|
+
metadata=Metadata(
|
|
129
|
+
line_number=base_line_number,
|
|
130
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
if new_concept.name in environment.concepts:
|
|
134
|
+
continue
|
|
135
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def generate_key_concepts(concept: Concept, environment: Environment):
|
|
139
|
+
if concept.metadata and concept.metadata.line_number:
|
|
140
|
+
base_line_number = concept.metadata.line_number
|
|
141
|
+
else:
|
|
142
|
+
base_line_number = None
|
|
143
|
+
for ftype in [FunctionType.COUNT]:
|
|
144
|
+
address = concept.address + f".{ftype.name.lower()}"
|
|
145
|
+
if address in environment.concepts:
|
|
146
|
+
continue
|
|
147
|
+
fname = ftype.name.lower()
|
|
148
|
+
default_type = Purpose.METRIC
|
|
149
|
+
const_function: Function = Function.model_construct(
|
|
150
|
+
operator=ftype,
|
|
151
|
+
output_datatype=DataType.INTEGER,
|
|
152
|
+
output_purpose=default_type,
|
|
153
|
+
arguments=[concept.reference],
|
|
154
|
+
)
|
|
155
|
+
new_concept = Concept.model_construct(
|
|
156
|
+
name=f"{concept.name}.{fname}",
|
|
157
|
+
datatype=DataType.INTEGER,
|
|
158
|
+
purpose=default_type,
|
|
159
|
+
lineage=const_function,
|
|
160
|
+
grain=Grain(),
|
|
161
|
+
namespace=concept.namespace,
|
|
162
|
+
keys=set(),
|
|
163
|
+
metadata=Metadata(
|
|
164
|
+
# description=f"Auto-derived integer. The {ftype.value} of {concept.address}, {base_description}",
|
|
165
|
+
line_number=base_line_number,
|
|
166
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
environment.add_concept(new_concept, add_derived=False)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def remove_date_concepts(concept: Concept, environment: Environment):
|
|
173
|
+
"""Remove auto-generated date-related concepts for the given concept"""
|
|
174
|
+
date_suffixes = ["month", "year", "quarter", "day", "day_of_week"]
|
|
175
|
+
grain_suffixes = ["month_start", "year_start"]
|
|
176
|
+
|
|
177
|
+
for suffix in date_suffixes + grain_suffixes:
|
|
178
|
+
address = concept.address + f".{suffix}"
|
|
179
|
+
if address in environment.concepts:
|
|
180
|
+
derived_concept = environment.concepts[address]
|
|
181
|
+
# Only remove if it was auto-derived from this concept
|
|
182
|
+
if (
|
|
183
|
+
derived_concept.metadata
|
|
184
|
+
and derived_concept.metadata.concept_source
|
|
185
|
+
== ConceptSource.AUTO_DERIVED
|
|
186
|
+
and derived_concept.keys
|
|
187
|
+
and concept.address in derived_concept.keys
|
|
188
|
+
):
|
|
189
|
+
environment.remove_concept(address)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def remove_datetime_concepts(concept: Concept, environment: Environment):
|
|
193
|
+
"""Remove auto-generated datetime-related concepts for the given concept"""
|
|
194
|
+
datetime_suffixes = ["date", "hour", "minute", "second"]
|
|
195
|
+
|
|
196
|
+
for suffix in datetime_suffixes:
|
|
197
|
+
address = concept.address + f".{suffix}"
|
|
198
|
+
if address in environment.concepts:
|
|
199
|
+
derived_concept = environment.concepts[address]
|
|
200
|
+
# Only remove if it was auto-derived from this concept
|
|
201
|
+
if (
|
|
202
|
+
derived_concept.metadata
|
|
203
|
+
and derived_concept.metadata.concept_source
|
|
204
|
+
== ConceptSource.AUTO_DERIVED
|
|
205
|
+
and derived_concept.keys
|
|
206
|
+
and concept.address in derived_concept.keys
|
|
207
|
+
):
|
|
208
|
+
environment.remove_concept(address)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def remove_key_concepts(concept: Concept, environment: Environment):
|
|
212
|
+
"""Remove auto-generated key-related concepts for the given concept"""
|
|
213
|
+
key_suffixes = ["count"]
|
|
214
|
+
|
|
215
|
+
for suffix in key_suffixes:
|
|
216
|
+
address = concept.address + f".{suffix}"
|
|
217
|
+
if address in environment.concepts:
|
|
218
|
+
derived_concept = environment.concepts[address]
|
|
219
|
+
if (
|
|
220
|
+
derived_concept.metadata
|
|
221
|
+
and derived_concept.metadata.concept_source
|
|
222
|
+
== ConceptSource.AUTO_DERIVED
|
|
223
|
+
):
|
|
224
|
+
environment.remove_concept(address)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def remove_struct_concepts(concept: Concept, environment: Environment):
|
|
228
|
+
"""Remove auto-generated struct field concepts for the given concept"""
|
|
229
|
+
if not isinstance(concept.datatype, StructType):
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
target_namespace = (
|
|
233
|
+
environment.namespace + "." + concept.name
|
|
234
|
+
if environment.namespace and environment.namespace != DEFAULT_NAMESPACE
|
|
235
|
+
else concept.name
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Get all concepts in the target namespace that were auto-derived
|
|
239
|
+
concepts_to_remove = []
|
|
240
|
+
for address, derived_concept in environment.concepts.items():
|
|
241
|
+
if (
|
|
242
|
+
derived_concept.namespace == target_namespace
|
|
243
|
+
and derived_concept.metadata
|
|
244
|
+
and derived_concept.metadata.concept_source == ConceptSource.AUTO_DERIVED
|
|
245
|
+
and isinstance(derived_concept.lineage, Function)
|
|
246
|
+
and derived_concept.lineage.operator == FunctionType.ATTR_ACCESS
|
|
247
|
+
and len(derived_concept.lineage.arguments) >= 1
|
|
248
|
+
and derived_concept.lineage.arguments[0] == concept.reference
|
|
249
|
+
):
|
|
250
|
+
concepts_to_remove.append(address)
|
|
251
|
+
|
|
252
|
+
for address in concepts_to_remove:
|
|
253
|
+
environment.remove_concept(address)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def remove_related_concepts(concept: Concept, environment: Environment):
|
|
257
|
+
"""Remove all auto-generated concepts that were derived from the given concept"""
|
|
258
|
+
|
|
259
|
+
# Remove key-related concepts
|
|
260
|
+
if concept.purpose == Purpose.KEY:
|
|
261
|
+
remove_key_concepts(concept, environment)
|
|
262
|
+
|
|
263
|
+
# Remove datatype-specific concepts
|
|
264
|
+
if concept.datatype == DataType.DATE:
|
|
265
|
+
remove_date_concepts(concept, environment)
|
|
266
|
+
elif concept.datatype == DataType.DATETIME:
|
|
267
|
+
remove_date_concepts(concept, environment)
|
|
268
|
+
remove_datetime_concepts(concept, environment)
|
|
269
|
+
elif concept.datatype == DataType.TIMESTAMP:
|
|
270
|
+
remove_date_concepts(concept, environment)
|
|
271
|
+
remove_datetime_concepts(concept, environment)
|
|
272
|
+
|
|
273
|
+
# Remove struct field concepts
|
|
274
|
+
if isinstance(concept.datatype, StructType):
|
|
275
|
+
remove_struct_concepts(concept, environment)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def generate_related_concepts(
|
|
279
|
+
concept: Concept,
|
|
280
|
+
environment: Environment,
|
|
281
|
+
meta: Meta | None = None,
|
|
282
|
+
add_derived: bool = False,
|
|
283
|
+
):
|
|
284
|
+
"""Auto populate common derived concepts on types"""
|
|
285
|
+
if concept.purpose == Purpose.KEY and add_derived:
|
|
286
|
+
generate_key_concepts(concept, environment)
|
|
287
|
+
|
|
288
|
+
# datatype types
|
|
289
|
+
if concept.datatype == DataType.DATE and add_derived:
|
|
290
|
+
generate_date_concepts(concept, environment)
|
|
291
|
+
elif concept.datatype == DataType.DATETIME and add_derived:
|
|
292
|
+
|
|
293
|
+
generate_date_concepts(concept, environment)
|
|
294
|
+
generate_datetime_concepts(concept, environment)
|
|
295
|
+
elif concept.datatype == DataType.TIMESTAMP and add_derived:
|
|
296
|
+
generate_date_concepts(concept, environment)
|
|
297
|
+
generate_datetime_concepts(concept, environment)
|
|
298
|
+
|
|
299
|
+
if isinstance(concept.datatype, StructType):
|
|
300
|
+
for key, value in concept.datatype.fields_map.items():
|
|
301
|
+
auto = Concept.model_construct(
|
|
302
|
+
name=key,
|
|
303
|
+
datatype=arg_to_datatype(value),
|
|
304
|
+
purpose=Purpose.PROPERTY,
|
|
305
|
+
namespace=(
|
|
306
|
+
environment.namespace + "." + concept.name
|
|
307
|
+
if environment.namespace
|
|
308
|
+
and environment.namespace != DEFAULT_NAMESPACE
|
|
309
|
+
else concept.name
|
|
310
|
+
),
|
|
311
|
+
lineage=AttrAccess([concept.reference, key], environment=environment),
|
|
312
|
+
grain=concept.grain,
|
|
313
|
+
metadata=Metadata(
|
|
314
|
+
concept_source=ConceptSource.AUTO_DERIVED,
|
|
315
|
+
),
|
|
316
|
+
keys=concept.keys,
|
|
317
|
+
)
|
|
318
|
+
environment.add_concept(auto, meta=meta)
|
|
319
|
+
if isinstance(value, Concept):
|
|
320
|
+
environment.merge_concept(auto, value, modifiers=[])
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from trilogy.constants import CONFIG
|
|
2
|
+
|
|
3
|
+
# source: https://github.com/aaronbassett/Pass-phrase
|
|
4
|
+
CTE_NAMES = """quizzical
|
|
5
|
+
highfalutin
|
|
6
|
+
wakeful
|
|
7
|
+
cheerful
|
|
8
|
+
thoughtful
|
|
9
|
+
cooperative
|
|
10
|
+
questionable
|
|
11
|
+
abundant
|
|
12
|
+
uneven
|
|
13
|
+
yummy
|
|
14
|
+
juicy
|
|
15
|
+
vacuous
|
|
16
|
+
concerned
|
|
17
|
+
young
|
|
18
|
+
sparkling
|
|
19
|
+
abhorrent
|
|
20
|
+
sweltering
|
|
21
|
+
late
|
|
22
|
+
macho
|
|
23
|
+
scrawny
|
|
24
|
+
friendly
|
|
25
|
+
kaput
|
|
26
|
+
divergent
|
|
27
|
+
busy
|
|
28
|
+
charming
|
|
29
|
+
protective
|
|
30
|
+
premium
|
|
31
|
+
puzzled
|
|
32
|
+
waggish
|
|
33
|
+
rambunctious
|
|
34
|
+
puffy
|
|
35
|
+
hard
|
|
36
|
+
sedate
|
|
37
|
+
yellow
|
|
38
|
+
resonant
|
|
39
|
+
dapper
|
|
40
|
+
courageous
|
|
41
|
+
vast
|
|
42
|
+
cool
|
|
43
|
+
elated
|
|
44
|
+
wary
|
|
45
|
+
bewildered
|
|
46
|
+
level
|
|
47
|
+
wooden
|
|
48
|
+
ceaseless
|
|
49
|
+
tearful
|
|
50
|
+
cloudy
|
|
51
|
+
gullible
|
|
52
|
+
flashy
|
|
53
|
+
trite
|
|
54
|
+
quick
|
|
55
|
+
nondescript
|
|
56
|
+
round
|
|
57
|
+
slow
|
|
58
|
+
spiritual
|
|
59
|
+
brave
|
|
60
|
+
tenuous
|
|
61
|
+
abstracted
|
|
62
|
+
colossal
|
|
63
|
+
sloppy
|
|
64
|
+
obsolete
|
|
65
|
+
elegant
|
|
66
|
+
fabulous
|
|
67
|
+
vivacious
|
|
68
|
+
exuberant
|
|
69
|
+
faithful
|
|
70
|
+
helpless
|
|
71
|
+
odd
|
|
72
|
+
sordid
|
|
73
|
+
blue
|
|
74
|
+
imported
|
|
75
|
+
ugly
|
|
76
|
+
ruthless
|
|
77
|
+
deeply
|
|
78
|
+
eminent
|
|
79
|
+
badger
|
|
80
|
+
barracuda
|
|
81
|
+
bear
|
|
82
|
+
boa
|
|
83
|
+
cheetah
|
|
84
|
+
chimpanzee
|
|
85
|
+
civet
|
|
86
|
+
cobra
|
|
87
|
+
cougar
|
|
88
|
+
coyote
|
|
89
|
+
crocodile
|
|
90
|
+
dingo
|
|
91
|
+
eagle
|
|
92
|
+
eel
|
|
93
|
+
fossa
|
|
94
|
+
fox
|
|
95
|
+
human
|
|
96
|
+
jackal
|
|
97
|
+
jaguar
|
|
98
|
+
komodo
|
|
99
|
+
leopard
|
|
100
|
+
lion
|
|
101
|
+
lynx
|
|
102
|
+
mamba
|
|
103
|
+
mandrill
|
|
104
|
+
marlin
|
|
105
|
+
monitor
|
|
106
|
+
ocelot
|
|
107
|
+
petrel
|
|
108
|
+
python
|
|
109
|
+
ray
|
|
110
|
+
salamander
|
|
111
|
+
serval
|
|
112
|
+
shark
|
|
113
|
+
skua
|
|
114
|
+
tiger
|
|
115
|
+
viper
|
|
116
|
+
wolf
|
|
117
|
+
wolverine
|
|
118
|
+
albatross
|
|
119
|
+
avocet
|
|
120
|
+
budgie
|
|
121
|
+
canary
|
|
122
|
+
chick
|
|
123
|
+
chickadee
|
|
124
|
+
chicken
|
|
125
|
+
cockatiel
|
|
126
|
+
cockatoo
|
|
127
|
+
coot
|
|
128
|
+
covey
|
|
129
|
+
crow
|
|
130
|
+
cuckoo
|
|
131
|
+
darter
|
|
132
|
+
dove
|
|
133
|
+
duck
|
|
134
|
+
falcon
|
|
135
|
+
finch
|
|
136
|
+
flamingo
|
|
137
|
+
fowl
|
|
138
|
+
goldfinch
|
|
139
|
+
goose
|
|
140
|
+
grouse
|
|
141
|
+
hawk
|
|
142
|
+
heron
|
|
143
|
+
jackdaw
|
|
144
|
+
jay
|
|
145
|
+
kestrel
|
|
146
|
+
lark
|
|
147
|
+
loon
|
|
148
|
+
macaw
|
|
149
|
+
magpie
|
|
150
|
+
martin
|
|
151
|
+
osprey
|
|
152
|
+
ostrich
|
|
153
|
+
owl
|
|
154
|
+
parakeet
|
|
155
|
+
parrot
|
|
156
|
+
pelican
|
|
157
|
+
penguin
|
|
158
|
+
pigeon
|
|
159
|
+
pintail
|
|
160
|
+
puffin
|
|
161
|
+
quail
|
|
162
|
+
quetzal
|
|
163
|
+
rail
|
|
164
|
+
raven
|
|
165
|
+
razorbill
|
|
166
|
+
rhea
|
|
167
|
+
rook
|
|
168
|
+
shrike
|
|
169
|
+
skylark
|
|
170
|
+
snipe
|
|
171
|
+
sparrow
|
|
172
|
+
starling
|
|
173
|
+
stork
|
|
174
|
+
swallow
|
|
175
|
+
swift
|
|
176
|
+
tanager
|
|
177
|
+
thrush
|
|
178
|
+
toucan
|
|
179
|
+
turkey
|
|
180
|
+
vulture
|
|
181
|
+
warbler""".split(
|
|
182
|
+
"\n"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def generate_cte_names():
|
|
187
|
+
if CONFIG.randomize_cte_names:
|
|
188
|
+
from random import shuffle
|
|
189
|
+
|
|
190
|
+
new = [*CTE_NAMES]
|
|
191
|
+
shuffle(new)
|
|
192
|
+
return new
|
|
193
|
+
return CTE_NAMES
|