pytrilogy 0.0.1.118__tar.gz → 0.0.2.1__tar.gz
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.
Potentially problematic release.
This version of pytrilogy might be problematic. Click here for more details.
- {pytrilogy-0.0.1.118/pytrilogy.egg-info → pytrilogy-0.0.2.1}/PKG-INFO +1 -1
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/pytrilogy.egg-info/SOURCES.txt +0 -1
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_discovery_nodes.py +10 -2
- pytrilogy-0.0.2.1/tests/test_environment.py +64 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_models.py +1 -1
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_parsing.py +10 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_query_processing.py +1 -1
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/constants.py +6 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/enums.py +7 -2
- pytrilogy-0.0.2.1/trilogy/core/env_processor.py +63 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/functions.py +1 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/models.py +666 -146
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/optimization.py +31 -28
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/optimizations/inline_constant.py +4 -1
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/optimizations/inline_datasource.py +25 -4
- pytrilogy-0.0.2.1/trilogy/core/optimizations/predicate_pushdown.py +145 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/concept_strategies_v3.py +69 -39
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/graph_utils.py +3 -3
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/__init__.py +0 -2
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/basic_node.py +30 -17
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/filter_node.py +3 -1
- pytrilogy-0.0.2.1/trilogy/core/processing/node_generators/node_merge_node.py +418 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/rowset_node.py +18 -16
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/select_node.py +44 -83
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/nodes/__init__.py +2 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/nodes/base_node.py +22 -5
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/nodes/filter_node.py +3 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/nodes/group_node.py +20 -2
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/nodes/merge_node.py +32 -18
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/nodes/select_node_v2.py +17 -3
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/utility.py +100 -8
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/query_processor.py +77 -24
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/base.py +11 -46
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/bigquery.py +1 -1
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/common.py +11 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/duckdb.py +1 -1
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/presto.py +1 -0
- pytrilogy-0.0.2.1/trilogy/hooks/graph_hook.py +69 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/hooks/query_debugger.py +1 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/parsing/common.py +8 -5
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/parsing/parse_engine.py +48 -27
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/parsing/render.py +13 -6
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/parsing/trilogy.lark +12 -7
- pytrilogy-0.0.1.118/tests/test_environment.py +0 -22
- pytrilogy-0.0.1.118/trilogy/core/env_processor.py +0 -39
- pytrilogy-0.0.1.118/trilogy/core/optimizations/predicate_pushdown.py +0 -105
- pytrilogy-0.0.1.118/trilogy/core/processing/node_generators/concept_merge_node.py +0 -214
- pytrilogy-0.0.1.118/trilogy/core/processing/node_generators/node_merge_node.py +0 -169
- pytrilogy-0.0.1.118/trilogy/hooks/graph_hook.py +0 -24
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/LICENSE.md +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/README.md +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/pyproject.toml +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/setup.cfg +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/setup.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_select.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.1.118 → pytrilogy-0.0.2.1}/trilogy/utility.py +0 -0
|
@@ -58,7 +58,6 @@ trilogy/core/processing/utility.py
|
|
|
58
58
|
trilogy/core/processing/node_generators/__init__.py
|
|
59
59
|
trilogy/core/processing/node_generators/basic_node.py
|
|
60
60
|
trilogy/core/processing/node_generators/common.py
|
|
61
|
-
trilogy/core/processing/node_generators/concept_merge_node.py
|
|
62
61
|
trilogy/core/processing/node_generators/filter_node.py
|
|
63
62
|
trilogy/core/processing/node_generators/group_node.py
|
|
64
63
|
trilogy/core/processing/node_generators/group_to_node.py
|
|
@@ -39,7 +39,11 @@ def test_group_node_property(test_environment: Environment, test_environment_gra
|
|
|
39
39
|
source_concepts=search_concepts,
|
|
40
40
|
depth=0,
|
|
41
41
|
)
|
|
42
|
-
input_concept_names = {
|
|
42
|
+
input_concept_names = {
|
|
43
|
+
x.name
|
|
44
|
+
for x in group_node.parents[0].output_concepts
|
|
45
|
+
if x not in group_node.parents[0].hidden_concepts
|
|
46
|
+
}
|
|
43
47
|
assert input_concept_names == {"category_name_length", "category_id"}
|
|
44
48
|
# assert len(input_concept.grain.components) == 1
|
|
45
49
|
# assert input_concept.grain.components[0].name == "category_id"
|
|
@@ -60,7 +64,11 @@ def test_group_node_property_all(test_environment: Environment, test_environment
|
|
|
60
64
|
source_concepts=search_concepts,
|
|
61
65
|
depth=0,
|
|
62
66
|
)
|
|
63
|
-
input_concept_names = {
|
|
67
|
+
input_concept_names = {
|
|
68
|
+
x.name
|
|
69
|
+
for x in group_node.parents[0].output_concepts
|
|
70
|
+
if x not in group_node.parents[0].hidden_concepts
|
|
71
|
+
}
|
|
64
72
|
assert input_concept_names == {"category_name_length", "category_id"}
|
|
65
73
|
final = group_node.resolve()
|
|
66
74
|
assert len(final.datasources) == 1
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from trilogy.core.models import Environment
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from trilogy.core.enums import Modifier
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_environment_serialization(test_environment: Environment):
|
|
7
|
+
str(test_environment)
|
|
8
|
+
path = test_environment.to_cache()
|
|
9
|
+
|
|
10
|
+
test_environment2 = Environment.from_cache(path)
|
|
11
|
+
assert test_environment2
|
|
12
|
+
|
|
13
|
+
assert test_environment.concepts == test_environment2.concepts
|
|
14
|
+
assert test_environment.datasources == test_environment2.datasources
|
|
15
|
+
for k, v in test_environment.concepts.items():
|
|
16
|
+
assert v == test_environment2.concepts[k]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_environment_from_path():
|
|
20
|
+
|
|
21
|
+
env = Environment.from_file(Path(__file__).parent / "test_env.preql")
|
|
22
|
+
|
|
23
|
+
assert "id" in env.concepts
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_environment_merge():
|
|
27
|
+
env1: Environment
|
|
28
|
+
env1, _ = Environment().parse(
|
|
29
|
+
"""
|
|
30
|
+
key order_id int;
|
|
31
|
+
|
|
32
|
+
datasource orders
|
|
33
|
+
(order_id:order_id)
|
|
34
|
+
grain (order_id)
|
|
35
|
+
address orders;
|
|
36
|
+
"""
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
env2, _ = Environment().parse(
|
|
40
|
+
"""
|
|
41
|
+
key order_id int;
|
|
42
|
+
|
|
43
|
+
datasource replacements
|
|
44
|
+
(order_id:order_id)
|
|
45
|
+
grain (order_id)
|
|
46
|
+
address replacements;
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
env1.add_import("replacements", env2)
|
|
53
|
+
|
|
54
|
+
_ = env1.merge_concept(
|
|
55
|
+
env1.concepts["replacements.order_id"],
|
|
56
|
+
env1.concepts["order_id"],
|
|
57
|
+
modifiers=[Modifier.PARTIAL],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
assert env1.concepts["order_id"] == env1.concepts["replacements.order_id"]
|
|
61
|
+
|
|
62
|
+
order_id = env1.datasources["replacements.replacements"].columns[0]
|
|
63
|
+
assert order_id.concept == env1.concepts["order_id"]
|
|
64
|
+
assert order_id.modifiers == [Modifier.PARTIAL]
|
|
@@ -132,7 +132,7 @@ def test_undefined(test_environment: Environment):
|
|
|
132
132
|
environment=test_environment.concepts,
|
|
133
133
|
)
|
|
134
134
|
|
|
135
|
-
y = x.
|
|
135
|
+
y = x.with_select_context(Grain(components=[test_environment.concepts["order_id"]]))
|
|
136
136
|
|
|
137
137
|
assert y.grain == Grain(components=[test_environment.concepts["order_id"]])
|
|
138
138
|
|
|
@@ -15,6 +15,7 @@ from trilogy.parsing.parse_engine import (
|
|
|
15
15
|
)
|
|
16
16
|
from trilogy.constants import MagicConstants
|
|
17
17
|
from trilogy.dialect.base import BaseDialect
|
|
18
|
+
from trilogy.core.enums import BooleanOperator
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def test_in():
|
|
@@ -127,6 +128,15 @@ def test_show(test_environment):
|
|
|
127
128
|
)
|
|
128
129
|
|
|
129
130
|
|
|
131
|
+
def test_conditional(test_environment):
|
|
132
|
+
_, parsed = parse_text(
|
|
133
|
+
"const order_id <- 4; SELECT order_id WHERE order_id =4 and order_id = 10;"
|
|
134
|
+
)
|
|
135
|
+
query = parsed[-1]
|
|
136
|
+
assert isinstance(query, SelectStatement)
|
|
137
|
+
assert query.where_clause.conditional.operator == BooleanOperator.AND
|
|
138
|
+
|
|
139
|
+
|
|
130
140
|
def test_as_transform(test_environment):
|
|
131
141
|
_, parsed = parse_text("const order_id <- 4; SELECT order_id as new_order_id;")
|
|
132
142
|
query = parsed[-1]
|
|
@@ -134,7 +134,7 @@ def test_query_aggregation(test_environment, test_environment_graph):
|
|
|
134
134
|
environment=test_environment, graph=test_environment_graph, statement=select
|
|
135
135
|
)
|
|
136
136
|
|
|
137
|
-
assert {datasource.identifier} == {"
|
|
137
|
+
assert {datasource.identifier} == {"local_revenue_at_local_order_id_at_abstract"}
|
|
138
138
|
check = datasource
|
|
139
139
|
assert len(check.input_concepts) == 2
|
|
140
140
|
assert check.input_concepts[0].name == "revenue"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from logging import getLogger
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from enum import Enum
|
|
4
|
+
import random
|
|
4
5
|
|
|
5
6
|
logger = getLogger("trilogy")
|
|
6
7
|
|
|
@@ -34,7 +35,12 @@ class Config:
|
|
|
34
35
|
validate_missing: bool = True
|
|
35
36
|
optimizations: Optimizations = field(default_factory=Optimizations)
|
|
36
37
|
|
|
38
|
+
def set_random_seed(self, seed: int):
|
|
39
|
+
random.seed(seed)
|
|
40
|
+
|
|
37
41
|
|
|
38
42
|
CONFIG = Config()
|
|
39
43
|
|
|
44
|
+
CONFIG.set_random_seed(42)
|
|
45
|
+
|
|
40
46
|
CONFIG.strict_mode = True
|
|
@@ -43,7 +43,6 @@ class PurposeLineage(Enum):
|
|
|
43
43
|
ROOT = "root"
|
|
44
44
|
ROWSET = "rowset"
|
|
45
45
|
MULTISELECT = "multiselect"
|
|
46
|
-
MERGE = "merge"
|
|
47
46
|
|
|
48
47
|
|
|
49
48
|
class Granularity(Enum):
|
|
@@ -263,7 +262,6 @@ class DatePart(Enum):
|
|
|
263
262
|
class SourceType(Enum):
|
|
264
263
|
FILTER = "filter"
|
|
265
264
|
SELECT = "select"
|
|
266
|
-
MERGE = "merge"
|
|
267
265
|
ABSTRACT = "abstract"
|
|
268
266
|
DIRECT_SELECT = "direct_select"
|
|
269
267
|
GROUP = "group"
|
|
@@ -271,8 +269,15 @@ class SourceType(Enum):
|
|
|
271
269
|
UNNEST = "unnest"
|
|
272
270
|
CONSTANT = "constant"
|
|
273
271
|
ROWSET = "rowset"
|
|
272
|
+
MERGE = "merge"
|
|
274
273
|
|
|
275
274
|
|
|
276
275
|
class ShowCategory(Enum):
|
|
277
276
|
MODELS = "models"
|
|
278
277
|
CONCEPTS = "concepts"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class SelectFiltering(Enum):
|
|
281
|
+
NONE = "none"
|
|
282
|
+
EXPLICIT = "explicit" # the filtering contains only selected values
|
|
283
|
+
IMPLICIT = "implicit" # the filtering contains unselected values
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from trilogy.core.graph_models import (
|
|
2
|
+
ReferenceGraph,
|
|
3
|
+
concept_to_node,
|
|
4
|
+
datasource_to_node,
|
|
5
|
+
)
|
|
6
|
+
from trilogy.core.models import Environment, Concept, Datasource
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def add_concept(concept: Concept, g: ReferenceGraph):
|
|
10
|
+
g.add_node(concept)
|
|
11
|
+
# if we have sources, recursively add them
|
|
12
|
+
node_name = concept_to_node(concept)
|
|
13
|
+
if concept.sources:
|
|
14
|
+
for source in concept.sources:
|
|
15
|
+
generic = source.with_default_grain()
|
|
16
|
+
g.add_edge(generic, node_name)
|
|
17
|
+
for _, pseudonym in concept.pseudonyms.items():
|
|
18
|
+
pseudonym = pseudonym.with_default_grain()
|
|
19
|
+
pseudonym_node = concept_to_node(pseudonym)
|
|
20
|
+
if pseudonym_node.split("@")[0] == node_name.split("@")[0]:
|
|
21
|
+
continue
|
|
22
|
+
g.add_edge(pseudonym_node, node_name, pseudonym=True)
|
|
23
|
+
g.add_edge(node_name, pseudonym_node, pseudonym=True)
|
|
24
|
+
add_concept(pseudonym, g)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def generate_adhoc_graph(
|
|
28
|
+
concepts: list[Concept],
|
|
29
|
+
datasources: list[Datasource],
|
|
30
|
+
restrict_to_listed: bool = False,
|
|
31
|
+
) -> ReferenceGraph:
|
|
32
|
+
g = ReferenceGraph()
|
|
33
|
+
|
|
34
|
+
# add all parsed concepts
|
|
35
|
+
for concept in concepts:
|
|
36
|
+
add_concept(concept, g)
|
|
37
|
+
|
|
38
|
+
for dataset in datasources:
|
|
39
|
+
node = datasource_to_node(dataset)
|
|
40
|
+
g.add_node(dataset, type="datasource", datasource=dataset)
|
|
41
|
+
for concept in dataset.concepts:
|
|
42
|
+
if restrict_to_listed:
|
|
43
|
+
if concept_to_node(concept) not in g.nodes:
|
|
44
|
+
continue
|
|
45
|
+
g.add_edge(node, concept)
|
|
46
|
+
g.add_edge(concept, node)
|
|
47
|
+
# if there is a key on a table at a different grain
|
|
48
|
+
# add an FK edge to the canonical source, if it exists
|
|
49
|
+
# for example, order ID on order product table
|
|
50
|
+
default = concept.with_default_grain()
|
|
51
|
+
if concept != default:
|
|
52
|
+
g.add_edge(concept, default)
|
|
53
|
+
g.add_edge(default, concept)
|
|
54
|
+
return g
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def generate_graph(
|
|
58
|
+
environment: Environment,
|
|
59
|
+
) -> ReferenceGraph:
|
|
60
|
+
|
|
61
|
+
return generate_adhoc_graph(
|
|
62
|
+
list(environment.concepts.values()), list(environment.datasources.values())
|
|
63
|
+
)
|