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.

@@ -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
- concept: BuildConcept,
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
- all_concepts = [concept] + local_optional
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 all_concepts
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 {concept.address}"
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 {concept}")
46
+ raise NoDatasourceException(f"No datasource exists for {concepts}")
49
47
  return None
50
48
 
51
49
  return gen_select_merge_node(
52
- [concept] + local_optional,
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",
@@ -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.BASIC,
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],
@@ -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
@@ -2,7 +2,9 @@
2
2
 
3
3
  type year int;
4
4
  type month int;
5
+ type week int;
5
6
  type day int;
6
7
  type hour int;
7
8
  type minute int;
8
- type second int;
9
+ type second int;
10
+ type day_of_week int;
@@ -15,3 +15,7 @@ type city string; # City name
15
15
  type country string; # Country name
16
16
  type timezone string; # Timezone name
17
17
  type region string; # Region name
18
+
19
+
20
+ ## special formats
21
+ type geojson string; # GeoJSON format for geographic data
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
- # generic currency types
4
- type usd numeric;
5
- type eur numeric;
6
- type gbp numeric;
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
@@ -0,0 +1,8 @@
1
+
2
+
3
+ type url string;
4
+ type domain string;
5
+ type ip_net_mask string;
6
+ type ipv6_address string;
7
+ type ipv4_address string;
8
+ type suffix string;