pytrilogy 0.0.3.55__py3-none-any.whl → 0.0.3.56__py3-none-any.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.
Potentially problematic release.
This version of pytrilogy might be problematic. Click here for more details.
- {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.56.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.56.dist-info}/RECORD +23 -18
- trilogy/__init__.py +1 -1
- trilogy/core/enums.py +1 -0
- trilogy/core/models/author.py +6 -4
- trilogy/core/processing/concept_strategies_v3.py +323 -895
- trilogy/core/processing/discovery_loop.py +0 -0
- trilogy/core/processing/discovery_node_factory.py +469 -0
- trilogy/core/processing/discovery_utility.py +123 -0
- trilogy/core/processing/discovery_validation.py +155 -0
- trilogy/core/processing/node_generators/select_node.py +6 -8
- trilogy/core/processing/nodes/__init__.py +2 -4
- trilogy/dialect/snowflake.py +1 -1
- trilogy/parsing/common.py +1 -3
- trilogy/parsing/parse_engine.py +6 -0
- trilogy/std/date.preql +3 -1
- trilogy/std/geography.preql +4 -0
- trilogy/std/money.preql +65 -4
- trilogy/std/net.preql +8 -0
- {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.56.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.56.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.56.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.55.dist-info → pytrilogy-0.0.3.56.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from trilogy.core.models.build import (
|
|
6
|
+
BuildConcept,
|
|
7
|
+
BuildWhereClause,
|
|
8
|
+
)
|
|
9
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
10
|
+
from trilogy.core.processing.nodes import (
|
|
11
|
+
StrategyNode,
|
|
12
|
+
)
|
|
13
|
+
from trilogy.core.processing.utility import (
|
|
14
|
+
get_disconnected_components,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ValidationResult(Enum):
|
|
19
|
+
COMPLETE = 1
|
|
20
|
+
DISCONNECTED = 2
|
|
21
|
+
INCOMPLETE = 3
|
|
22
|
+
INCOMPLETE_CONDITION = 4
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def validate_concept(
|
|
26
|
+
concept: BuildConcept,
|
|
27
|
+
node: StrategyNode,
|
|
28
|
+
found_addresses: set[str],
|
|
29
|
+
non_partial_addresses: set[str],
|
|
30
|
+
partial_addresses: set[str],
|
|
31
|
+
virtual_addresses: set[str],
|
|
32
|
+
found_map: dict[str, set[BuildConcept]],
|
|
33
|
+
accept_partial: bool,
|
|
34
|
+
seen: set[str],
|
|
35
|
+
environment: BuildEnvironment,
|
|
36
|
+
):
|
|
37
|
+
found_map[str(node)].add(concept)
|
|
38
|
+
seen.add(concept.address)
|
|
39
|
+
if concept not in node.partial_concepts:
|
|
40
|
+
found_addresses.add(concept.address)
|
|
41
|
+
non_partial_addresses.add(concept.address)
|
|
42
|
+
# remove it from our partial tracking
|
|
43
|
+
if concept.address in partial_addresses:
|
|
44
|
+
partial_addresses.remove(concept.address)
|
|
45
|
+
if concept.address in virtual_addresses:
|
|
46
|
+
virtual_addresses.remove(concept.address)
|
|
47
|
+
if concept in node.partial_concepts:
|
|
48
|
+
if concept.address in non_partial_addresses:
|
|
49
|
+
return None
|
|
50
|
+
partial_addresses.add(concept.address)
|
|
51
|
+
if accept_partial:
|
|
52
|
+
found_addresses.add(concept.address)
|
|
53
|
+
found_map[str(node)].add(concept)
|
|
54
|
+
for v_address in concept.pseudonyms:
|
|
55
|
+
if v_address in seen:
|
|
56
|
+
return
|
|
57
|
+
v = environment.concepts[v_address]
|
|
58
|
+
if v.address in seen:
|
|
59
|
+
return
|
|
60
|
+
if v.address == concept.address:
|
|
61
|
+
return
|
|
62
|
+
validate_concept(
|
|
63
|
+
v,
|
|
64
|
+
node,
|
|
65
|
+
found_addresses,
|
|
66
|
+
non_partial_addresses,
|
|
67
|
+
partial_addresses,
|
|
68
|
+
virtual_addresses,
|
|
69
|
+
found_map,
|
|
70
|
+
accept_partial,
|
|
71
|
+
seen=seen,
|
|
72
|
+
environment=environment,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def validate_stack(
|
|
77
|
+
environment: BuildEnvironment,
|
|
78
|
+
stack: List[StrategyNode],
|
|
79
|
+
concepts: List[BuildConcept],
|
|
80
|
+
mandatory_with_filter: List[BuildConcept],
|
|
81
|
+
conditions: BuildWhereClause | None = None,
|
|
82
|
+
accept_partial: bool = False,
|
|
83
|
+
) -> tuple[ValidationResult, set[str], set[str], set[str], set[str]]:
|
|
84
|
+
found_map: dict[str, set[BuildConcept]] = defaultdict(set)
|
|
85
|
+
found_addresses: set[str] = set()
|
|
86
|
+
non_partial_addresses: set[str] = set()
|
|
87
|
+
partial_addresses: set[str] = set()
|
|
88
|
+
virtual_addresses: set[str] = set()
|
|
89
|
+
seen: set[str] = set()
|
|
90
|
+
|
|
91
|
+
for node in stack:
|
|
92
|
+
resolved = node.resolve()
|
|
93
|
+
|
|
94
|
+
for concept in resolved.output_concepts:
|
|
95
|
+
if concept.address in resolved.hidden_concepts:
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
validate_concept(
|
|
99
|
+
concept,
|
|
100
|
+
node,
|
|
101
|
+
found_addresses,
|
|
102
|
+
non_partial_addresses,
|
|
103
|
+
partial_addresses,
|
|
104
|
+
virtual_addresses,
|
|
105
|
+
found_map,
|
|
106
|
+
accept_partial,
|
|
107
|
+
seen,
|
|
108
|
+
environment,
|
|
109
|
+
)
|
|
110
|
+
for concept in node.virtual_output_concepts:
|
|
111
|
+
if concept.address in non_partial_addresses:
|
|
112
|
+
continue
|
|
113
|
+
found_addresses.add(concept.address)
|
|
114
|
+
virtual_addresses.add(concept.address)
|
|
115
|
+
if not conditions:
|
|
116
|
+
conditions_met = True
|
|
117
|
+
else:
|
|
118
|
+
conditions_met = all(
|
|
119
|
+
[node.preexisting_conditions == conditions.conditional for node in stack]
|
|
120
|
+
) or all([c.address in found_addresses for c in mandatory_with_filter])
|
|
121
|
+
# zip in those we know we found
|
|
122
|
+
if not all([c.address in found_addresses for c in concepts]) or not conditions_met:
|
|
123
|
+
if not all([c.address in found_addresses for c in concepts]):
|
|
124
|
+
return (
|
|
125
|
+
ValidationResult.INCOMPLETE,
|
|
126
|
+
found_addresses,
|
|
127
|
+
{c.address for c in concepts if c.address not in found_addresses},
|
|
128
|
+
partial_addresses,
|
|
129
|
+
virtual_addresses,
|
|
130
|
+
)
|
|
131
|
+
return (
|
|
132
|
+
ValidationResult.INCOMPLETE_CONDITION,
|
|
133
|
+
found_addresses,
|
|
134
|
+
{c.address for c in concepts if c.address not in mandatory_with_filter},
|
|
135
|
+
partial_addresses,
|
|
136
|
+
virtual_addresses,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
graph_count, _ = get_disconnected_components(found_map)
|
|
140
|
+
if graph_count in (0, 1):
|
|
141
|
+
return (
|
|
142
|
+
ValidationResult.COMPLETE,
|
|
143
|
+
found_addresses,
|
|
144
|
+
set(),
|
|
145
|
+
partial_addresses,
|
|
146
|
+
virtual_addresses,
|
|
147
|
+
)
|
|
148
|
+
# if we have too many subgraphs, we need to keep searching
|
|
149
|
+
return (
|
|
150
|
+
ValidationResult.DISCONNECTED,
|
|
151
|
+
found_addresses,
|
|
152
|
+
set(),
|
|
153
|
+
partial_addresses,
|
|
154
|
+
virtual_addresses,
|
|
155
|
+
)
|
|
@@ -19,8 +19,7 @@ LOGGER_PREFIX = "[GEN_SELECT_NODE]"
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def gen_select_node(
|
|
22
|
-
|
|
23
|
-
local_optional: list[BuildConcept],
|
|
22
|
+
concepts: list[BuildConcept],
|
|
24
23
|
environment: BuildEnvironment,
|
|
25
24
|
g,
|
|
26
25
|
depth: int,
|
|
@@ -28,12 +27,11 @@ def gen_select_node(
|
|
|
28
27
|
fail_if_not_found: bool = True,
|
|
29
28
|
conditions: BuildWhereClause | None = None,
|
|
30
29
|
) -> StrategyNode | None:
|
|
31
|
-
|
|
32
|
-
all_lcl = LooseBuildConceptList(concepts=all_concepts)
|
|
30
|
+
all_lcl = LooseBuildConceptList(concepts=concepts)
|
|
33
31
|
materialized_lcl = LooseBuildConceptList(
|
|
34
32
|
concepts=[
|
|
35
33
|
x
|
|
36
|
-
for x in
|
|
34
|
+
for x in concepts
|
|
37
35
|
if x.address in environment.materialized_concepts
|
|
38
36
|
or x.derivation == Derivation.CONSTANT
|
|
39
37
|
]
|
|
@@ -41,15 +39,15 @@ def gen_select_node(
|
|
|
41
39
|
if materialized_lcl != all_lcl:
|
|
42
40
|
missing = all_lcl.difference(materialized_lcl)
|
|
43
41
|
logger.info(
|
|
44
|
-
f"{padding(depth)}{LOGGER_PREFIX} Skipping select node generation for {
|
|
42
|
+
f"{padding(depth)}{LOGGER_PREFIX} Skipping select node generation for {concepts}"
|
|
45
43
|
f" as it + optional includes non-materialized concepts (looking for all {all_lcl}, missing {missing}) "
|
|
46
44
|
)
|
|
47
45
|
if fail_if_not_found:
|
|
48
|
-
raise NoDatasourceException(f"No datasource exists for {
|
|
46
|
+
raise NoDatasourceException(f"No datasource exists for {concepts}")
|
|
49
47
|
return None
|
|
50
48
|
|
|
51
49
|
return gen_select_merge_node(
|
|
52
|
-
|
|
50
|
+
concepts,
|
|
53
51
|
g=g,
|
|
54
52
|
environment=environment,
|
|
55
53
|
depth=depth,
|
|
@@ -150,7 +150,6 @@ class History(BaseModel):
|
|
|
150
150
|
environment: BuildEnvironment,
|
|
151
151
|
g,
|
|
152
152
|
depth: int,
|
|
153
|
-
source_concepts,
|
|
154
153
|
fail_if_not_found: bool = False,
|
|
155
154
|
accept_partial: bool = False,
|
|
156
155
|
accept_partial_optional: bool = False,
|
|
@@ -169,8 +168,7 @@ class History(BaseModel):
|
|
|
169
168
|
if fingerprint in self.select_history:
|
|
170
169
|
return self.select_history[fingerprint]
|
|
171
170
|
gen = gen_select_node(
|
|
172
|
-
concept,
|
|
173
|
-
local_optional,
|
|
171
|
+
[concept] + local_optional,
|
|
174
172
|
environment,
|
|
175
173
|
g,
|
|
176
174
|
depth + 1,
|
|
@@ -190,8 +188,8 @@ __all__ = [
|
|
|
190
188
|
"WindowNode",
|
|
191
189
|
"StrategyNode",
|
|
192
190
|
"NodeJoin",
|
|
193
|
-
"ConstantNode",
|
|
194
191
|
"UnnestNode",
|
|
192
|
+
"ConstantNode",
|
|
195
193
|
"UnionNode",
|
|
196
194
|
"History",
|
|
197
195
|
"WhereSafetyNode",
|
trilogy/dialect/snowflake.py
CHANGED
|
@@ -47,7 +47,7 @@ SNOWFLAKE_SQL_TEMPLATE = Template(
|
|
|
47
47
|
CREATE OR REPLACE TABLE {{ output.address.location }} AS
|
|
48
48
|
{% endif %}{%- if ctes %}
|
|
49
49
|
WITH {% if recursive%}RECURSIVE{% endif %}{% for cte in ctes %}
|
|
50
|
-
{{cte.name}} as ({{cte.statement}}){% if not loop.last %},{% endif %}{% else %}
|
|
50
|
+
"{{cte.name}}" as ({{cte.statement}}){% if not loop.last %},{% endif %}{% else %}
|
|
51
51
|
{% endfor %}{% endif %}
|
|
52
52
|
{%- if full_select -%}
|
|
53
53
|
{{full_select}}
|
trilogy/parsing/common.py
CHANGED
|
@@ -397,7 +397,7 @@ def group_function_to_concept(
|
|
|
397
397
|
modifiers=modifiers,
|
|
398
398
|
grain=grain,
|
|
399
399
|
metadata=fmetadata,
|
|
400
|
-
derivation=Derivation.
|
|
400
|
+
derivation=Derivation.GROUP_TO,
|
|
401
401
|
granularity=granularity,
|
|
402
402
|
)
|
|
403
403
|
return r
|
|
@@ -654,7 +654,6 @@ def agg_wrapper_to_concept(
|
|
|
654
654
|
fmetadata = metadata or Metadata()
|
|
655
655
|
aggfunction = parent.function
|
|
656
656
|
modifiers = get_upstream_modifiers(parent.concept_arguments, environment)
|
|
657
|
-
# derivation = Concept.calculate_derivation(parent, Purpose.PROPERTY)
|
|
658
657
|
grain = Grain.from_concepts(parent.by, environment) if parent.by else Grain()
|
|
659
658
|
granularity = Concept.calculate_granularity(Derivation.AGGREGATE, grain, parent)
|
|
660
659
|
|
|
@@ -778,7 +777,6 @@ def rowset_to_concepts(rowset: RowsetDerivationStatement, environment: Environme
|
|
|
778
777
|
for x in pre_output:
|
|
779
778
|
x.lineage = RowsetItem(
|
|
780
779
|
content=orig_map[x.address].reference,
|
|
781
|
-
# where=rowset.select.where_clause,
|
|
782
780
|
rowset=RowsetLineage(
|
|
783
781
|
name=rowset.name,
|
|
784
782
|
derived_concepts=[x.reference for x in pre_output],
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -74,6 +74,7 @@ from trilogy.core.models.author import (
|
|
|
74
74
|
Parenthetical,
|
|
75
75
|
RowsetItem,
|
|
76
76
|
SubselectComparison,
|
|
77
|
+
UndefinedConceptFull,
|
|
77
78
|
WhereClause,
|
|
78
79
|
Window,
|
|
79
80
|
WindowItem,
|
|
@@ -962,6 +963,11 @@ class ParseToObjects(Transformer):
|
|
|
962
963
|
targets = {sources[0].address: self.environment.concepts[target]}
|
|
963
964
|
|
|
964
965
|
if self.parse_pass == ParsePass.VALIDATION:
|
|
966
|
+
for source_c in sources:
|
|
967
|
+
if isinstance(source_c, UndefinedConceptFull):
|
|
968
|
+
raise SyntaxError(
|
|
969
|
+
f"Cannot merge non-existent source concept {source_c.address} on line: {meta.line}"
|
|
970
|
+
)
|
|
965
971
|
new = MergeStatementV2(
|
|
966
972
|
sources=sources,
|
|
967
973
|
targets=targets,
|
trilogy/std/date.preql
CHANGED
trilogy/std/geography.preql
CHANGED
trilogy/std/money.preql
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
|
+
# generic currency types
|
|
1
2
|
|
|
3
|
+
# Major global currencies
|
|
4
|
+
type usd numeric; # US Dollar
|
|
5
|
+
type eur numeric; # Euro
|
|
6
|
+
type gbp numeric; # British Pound Sterling
|
|
7
|
+
type jpy numeric; # Japanese Yen
|
|
8
|
+
type chf numeric; # Swiss Franc
|
|
9
|
+
type cad numeric; # Canadian Dollar
|
|
10
|
+
type aud numeric; # Australian Dollar
|
|
11
|
+
type nzd numeric; # New Zealand Dollar
|
|
2
12
|
|
|
3
|
-
#
|
|
4
|
-
type
|
|
5
|
-
type
|
|
6
|
-
type
|
|
13
|
+
# Asian currencies
|
|
14
|
+
type cny numeric; # Chinese Yuan
|
|
15
|
+
type hkd numeric; # Hong Kong Dollar
|
|
16
|
+
type sgd numeric; # Singapore Dollar
|
|
17
|
+
type krw numeric; # South Korean Won
|
|
18
|
+
type inr numeric; # Indian Rupee
|
|
19
|
+
type thb numeric; # Thai Baht
|
|
20
|
+
type php numeric; # Philippine Peso
|
|
21
|
+
type myr numeric; # Malaysian Ringgit
|
|
22
|
+
type idr numeric; # Indonesian Rupiah
|
|
23
|
+
type vnd numeric; # Vietnamese Dong
|
|
24
|
+
|
|
25
|
+
# European currencies (non-Euro)
|
|
26
|
+
type sek numeric; # Swedish Krona
|
|
27
|
+
type nok numeric; # Norwegian Krone
|
|
28
|
+
type dkk numeric; # Danish Krone
|
|
29
|
+
type pln numeric; # Polish Zloty
|
|
30
|
+
type czk numeric; # Czech Koruna
|
|
31
|
+
type huf numeric; # Hungarian Forint
|
|
32
|
+
type ron numeric; # Romanian Leu
|
|
33
|
+
type bgn numeric; # Bulgarian Lev
|
|
34
|
+
type rub numeric; # Russian Ruble
|
|
35
|
+
|
|
36
|
+
# Middle Eastern currencies
|
|
37
|
+
type aed numeric; # UAE Dirham
|
|
38
|
+
type sar numeric; # Saudi Riyal
|
|
39
|
+
type qar numeric; # Qatari Riyal
|
|
40
|
+
type kwd numeric; # Kuwaiti Dinar
|
|
41
|
+
type bhd numeric; # Bahraini Dinar
|
|
42
|
+
type omr numeric; # Omani Rial
|
|
43
|
+
type jod numeric; # Jordanian Dinar
|
|
44
|
+
type ils numeric; # Israeli Shekel
|
|
45
|
+
type try numeric; # Turkish Lira
|
|
46
|
+
|
|
47
|
+
# African currencies
|
|
48
|
+
type zar numeric; # South African Rand
|
|
49
|
+
type egp numeric; # Egyptian Pound
|
|
50
|
+
type ngn numeric; # Nigerian Naira
|
|
51
|
+
type mad numeric; # Moroccan Dirham
|
|
52
|
+
type kes numeric; # Kenyan Shilling
|
|
53
|
+
type ghs numeric; # Ghanaian Cedi
|
|
54
|
+
|
|
55
|
+
# Latin American currencies
|
|
56
|
+
type mxn numeric; # Mexican Peso
|
|
57
|
+
type brl numeric; # Brazilian Real
|
|
58
|
+
type ars numeric; # Argentine Peso
|
|
59
|
+
type cop numeric; # Colombian Peso
|
|
60
|
+
type pen numeric; # Peruvian Sol
|
|
61
|
+
type clp numeric; # Chilean Peso
|
|
62
|
+
type uyu numeric; # Uruguayan Peso
|
|
63
|
+
|
|
64
|
+
# Other notable currencies
|
|
65
|
+
type isk numeric; # Icelandic Krona
|
|
66
|
+
type xof numeric; # West African CFA Franc
|
|
67
|
+
type xaf numeric; # Central African CFA Franc
|
trilogy/std/net.preql
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|