pytrilogy 0.0.1.103__py3-none-any.whl → 0.0.1.105__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.

@@ -9,6 +9,7 @@ from lark.exceptions import (
9
9
  UnexpectedToken,
10
10
  VisitError,
11
11
  )
12
+ from pathlib import Path
12
13
  from lark.tree import Meta
13
14
  from pydantic import ValidationError
14
15
  from trilogy.core.internal import INTERNAL_NAMESPACE, ALL_ROWS_CONCEPT
@@ -16,6 +17,7 @@ from trilogy.constants import (
16
17
  DEFAULT_NAMESPACE,
17
18
  NULL_VALUE,
18
19
  VIRTUAL_CONCEPT_PREFIX,
20
+ MagicConstants,
19
21
  )
20
22
  from trilogy.core.enums import (
21
23
  BooleanOperator,
@@ -61,6 +63,7 @@ from trilogy.core.models import (
61
63
  ColumnAssignment,
62
64
  Comment,
63
65
  Comparison,
66
+ SubselectComparison,
64
67
  Concept,
65
68
  ConceptTransform,
66
69
  Conditional,
@@ -107,6 +110,7 @@ from trilogy.parsing.common import (
107
110
  function_to_concept,
108
111
  filter_item_to_concept,
109
112
  constant_to_concept,
113
+ arbitrary_to_concept,
110
114
  )
111
115
 
112
116
  CONSTANT_TYPES = (int, float, str, bool, ListWrapper)
@@ -186,9 +190,9 @@ grammar = r"""
186
190
 
187
191
  // FUNCTION blocks
188
192
  function: raw_function
189
- function_binding_item: IDENTIFIER data_type
193
+ function_binding_item: IDENTIFIER ":" data_type
190
194
  function_binding_list: (function_binding_item ",")* function_binding_item ","?
191
- raw_function: "def" "rawsql" IDENTIFIER "(" function_binding_list ")" "-" ">" data_type "as"i MULTILINE_STRING
195
+ raw_function: "bind" "sql" IDENTIFIER "(" function_binding_list ")" "-" ">" data_type "as"i MULTILINE_STRING
192
196
 
193
197
 
194
198
  // user_id where state = Mexico
@@ -245,7 +249,9 @@ grammar = r"""
245
249
 
246
250
  COMPARISON_OPERATOR: (/is[\s]+not/ | "is" |"=" | ">" | "<" | ">=" | "<=" | "!=" )
247
251
 
248
- comparison: (expr COMPARISON_OPERATOR expr) | (expr array_comparison expr_tuple)
252
+ comparison: (expr COMPARISON_OPERATOR expr) | (expr array_comparison expr_tuple)
253
+
254
+ subselect_comparison: expr array_comparison expr
249
255
 
250
256
  expr_tuple: "(" (expr ",")* expr ","? ")"
251
257
 
@@ -257,7 +263,7 @@ grammar = r"""
257
263
 
258
264
  parenthetical: "(" (conditional | expr) ")"
259
265
 
260
- expr: window_item | filter_item | comparison | fgroup | aggregate_functions | unnest | _string_functions | _math_functions | _generic_functions | _constant_functions| _date_functions | literal | expr_reference | index_access | attr_access | parenthetical
266
+ expr: window_item | filter_item | comparison | subselect_comparison | fgroup | aggregate_functions | unnest | _string_functions | _math_functions | _generic_functions | _constant_functions| _date_functions | literal | expr_reference | index_access | attr_access | parenthetical
261
267
 
262
268
  // functions
263
269
 
@@ -466,25 +472,34 @@ class ParseToObjects(Transformer):
466
472
  text,
467
473
  environment: Environment,
468
474
  parse_address: str | None = None,
469
- parsed: dict | None = None,
475
+ parsed: dict[str, "ParseToObjects"] | None = None,
470
476
  ):
471
477
  Transformer.__init__(self, visit_tokens)
472
478
  self.text = text
473
479
  self.environment: Environment = environment
474
- self.imported: set[str] = set()
475
480
  self.parse_address = parse_address or "root"
476
481
  self.parsed: dict[str, ParseToObjects] = parsed if parsed else {}
477
482
  # we do a second pass to pick up circular dependencies
478
483
  # after initial parsing
479
484
  self.pass_count = 1
485
+ self._results_stash = None
486
+
487
+ def transform(self, tree):
488
+ results = super().transform(tree)
489
+ self._results_stash = results
490
+ self.environment._parse_count += 1
491
+ return results
480
492
 
481
493
  def hydrate_missing(self):
482
494
  self.pass_count = 2
483
495
  for k, v in self.parsed.items():
496
+
484
497
  if v.pass_count == 2:
485
498
  continue
486
499
  v.hydrate_missing()
487
500
  self.environment.concepts.fail_on_missing = True
501
+ # if not self.environment.concepts.undefined:
502
+ # return self._results_stash
488
503
  reparsed = self.transform(PARSER.parse(self.text))
489
504
  self.environment.concepts.undefined = {}
490
505
  return reparsed
@@ -515,43 +530,11 @@ class ParseToObjects(Transformer):
515
530
  concept.metadata.line_number = meta.line
516
531
  self.environment.add_concept(concept, meta=meta)
517
532
  final.append(concept)
518
- elif isinstance(arg, FilterItem):
533
+ elif isinstance(
534
+ arg, (FilterItem, WindowItem, AggregateWrapper, ListWrapper)
535
+ ):
519
536
  id_hash = string_to_hash(str(arg))
520
- concept = filter_item_to_concept(
521
- arg,
522
- name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
523
- namespace=self.environment.namespace,
524
- )
525
- if concept.metadata:
526
- concept.metadata.line_number = meta.line
527
- self.environment.add_concept(concept, meta=meta)
528
- final.append(concept)
529
- elif isinstance(arg, WindowItem):
530
- id_hash = string_to_hash(str(arg))
531
- concept = window_item_to_concept(
532
- arg,
533
- namespace=self.environment.namespace,
534
- name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
535
- )
536
- if concept.metadata:
537
- concept.metadata.line_number = meta.line
538
- self.environment.add_concept(concept, meta=meta)
539
- final.append(concept)
540
- elif isinstance(arg, AggregateWrapper):
541
- id_hash = string_to_hash(str(arg))
542
- concept = agg_wrapper_to_concept(
543
- arg,
544
- namespace=self.environment.namespace,
545
- name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
546
- )
547
- if concept.metadata:
548
- concept.metadata.line_number = meta.line
549
- self.environment.add_concept(concept, meta=meta)
550
- final.append(concept)
551
- # we don't need virtual types for most constants
552
- elif isinstance(arg, (ListWrapper)):
553
- id_hash = string_to_hash(str(arg))
554
- concept = constant_to_concept(
537
+ concept = arbitrary_to_concept(
555
538
  arg,
556
539
  name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
557
540
  namespace=self.environment.namespace,
@@ -560,6 +543,7 @@ class ParseToObjects(Transformer):
560
543
  concept.metadata.line_number = meta.line
561
544
  self.environment.add_concept(concept, meta=meta)
562
545
  final.append(concept)
546
+
563
547
  else:
564
548
  final.append(arg)
565
549
  return final
@@ -763,8 +747,10 @@ class ParseToObjects(Transformer):
763
747
  while isinstance(source_value, Parenthetical):
764
748
  source_value = source_value.content
765
749
 
766
- if isinstance(source_value, FilterItem):
767
- concept = filter_item_to_concept(
750
+ if isinstance(
751
+ source_value, (FilterItem, WindowItem, AggregateWrapper, Function)
752
+ ):
753
+ concept = arbitrary_to_concept(
768
754
  source_value,
769
755
  name=name,
770
756
  namespace=namespace,
@@ -772,31 +758,6 @@ class ParseToObjects(Transformer):
772
758
  metadata=metadata,
773
759
  )
774
760
 
775
- if concept.metadata:
776
- concept.metadata.line_number = meta.line
777
- self.environment.add_concept(concept, meta=meta)
778
- return ConceptDerivation(concept=concept)
779
- elif isinstance(source_value, WindowItem):
780
-
781
- concept = window_item_to_concept(
782
- source_value,
783
- name=name,
784
- namespace=namespace,
785
- purpose=purpose,
786
- metadata=metadata,
787
- )
788
- if concept.metadata:
789
- concept.metadata.line_number = meta.line
790
- self.environment.add_concept(concept, meta=meta)
791
- return ConceptDerivation(concept=concept)
792
- elif isinstance(source_value, AggregateWrapper):
793
- concept = agg_wrapper_to_concept(
794
- source_value,
795
- namespace=namespace,
796
- name=name,
797
- metadata=metadata,
798
- purpose=purpose,
799
- )
800
761
  if concept.metadata:
801
762
  concept.metadata.line_number = meta.line
802
763
  self.environment.add_concept(concept, meta=meta)
@@ -814,19 +775,6 @@ class ParseToObjects(Transformer):
814
775
  self.environment.add_concept(concept, meta=meta)
815
776
  return ConceptDerivation(concept=concept)
816
777
 
817
- elif isinstance(source_value, Function):
818
- function: Function = source_value
819
-
820
- concept = function_to_concept(
821
- function,
822
- name=name,
823
- namespace=namespace,
824
- )
825
- if concept.metadata:
826
- concept.metadata.line_number = meta.line
827
- self.environment.add_concept(concept, meta=meta)
828
- return ConceptDerivation(concept=concept)
829
-
830
778
  raise SyntaxError(
831
779
  f"Received invalid type {type(args[2])} {args[2]} as input to select"
832
780
  " transform"
@@ -932,7 +880,7 @@ class ParseToObjects(Transformer):
932
880
  )
933
881
  for column in columns:
934
882
  column.concept = column.concept.with_grain(datasource.grain)
935
- self.environment.datasources[datasource.identifier] = datasource
883
+ self.environment.add_datasource(datasource, meta=meta)
936
884
  return datasource
937
885
 
938
886
  @v_args(meta=True)
@@ -1046,12 +994,11 @@ class ParseToObjects(Transformer):
1046
994
  self.environment.add_concept(new, meta=meta)
1047
995
  return merge
1048
996
 
1049
- def import_statement(self, args: list[str]):
997
+ def import_statement(self, args: list[str]) -> ImportStatement:
1050
998
  alias = args[-1]
1051
999
  path = args[0].split(".")
1052
1000
 
1053
1001
  target = join(self.environment.working_path, *path) + ".preql"
1054
- self.imported.add(target)
1055
1002
  if target in self.parsed:
1056
1003
  nparser = self.parsed[target]
1057
1004
  else:
@@ -1070,21 +1017,23 @@ class ParseToObjects(Transformer):
1070
1017
  )
1071
1018
  nparser.transform(PARSER.parse(text))
1072
1019
  self.parsed[target] = nparser
1020
+ # add the parsed objects of the import in
1021
+ self.parsed = {**self.parsed, **nparser.parsed}
1073
1022
  except Exception as e:
1074
1023
  raise ImportError(
1075
1024
  f"Unable to import file {dirname(target)}, parsing error: {e}"
1076
1025
  )
1077
1026
 
1078
- for key, concept in nparser.environment.concepts.items():
1079
- # self.environment.concepts[f"{alias}.{key}"] = concept.with_namespace(new_namespace)
1027
+ for _, concept in nparser.environment.concepts.items():
1080
1028
  self.environment.add_concept(concept.with_namespace(alias))
1081
1029
 
1082
- for key, datasource in nparser.environment.datasources.items():
1030
+ for _, datasource in nparser.environment.datasources.items():
1083
1031
  self.environment.add_datasource(datasource.with_namespace(alias))
1084
- # self.environment.datasources[f"{alias}.{key}"] = datasource.with_namespace(new_namespace)
1085
-
1086
- self.environment.imports[alias] = ImportStatement(alias=alias, path=args[0])
1087
- return None
1032
+ imps = ImportStatement(
1033
+ alias=alias, path=Path(args[0]), environment=nparser.environment
1034
+ )
1035
+ self.environment.imports[alias] = imps
1036
+ return imps
1088
1037
 
1089
1038
  @v_args(meta=True)
1090
1039
  def show_category(self, meta: Meta, args) -> ShowCategory:
@@ -1208,7 +1157,14 @@ class ParseToObjects(Transformer):
1208
1157
  def where(self, args):
1209
1158
  root = args[0]
1210
1159
  if not isinstance(root, (Comparison, Conditional, Parenthetical)):
1211
- root = Comparison(left=root, right=True, operator=ComparisonOperator.EQ)
1160
+ if arg_to_datatype(root) == DataType.BOOL:
1161
+ root = Comparison(left=root, right=True, operator=ComparisonOperator.EQ)
1162
+ else:
1163
+ root = Comparison(
1164
+ left=root,
1165
+ right=MagicConstants.NULL,
1166
+ operator=ComparisonOperator.IS_NOT,
1167
+ )
1212
1168
  return WhereClause(conditional=root)
1213
1169
 
1214
1170
  @v_args(meta=True)
@@ -1221,7 +1177,6 @@ class ParseToObjects(Transformer):
1221
1177
 
1222
1178
  @v_args(meta=True)
1223
1179
  def raw_function(self, meta: Meta, args) -> Function:
1224
- print(args)
1225
1180
  identity = args[0]
1226
1181
  fargs = args[1]
1227
1182
  output = args[2]
@@ -1262,6 +1217,22 @@ class ParseToObjects(Transformer):
1262
1217
  def comparison(self, args) -> Comparison:
1263
1218
  return Comparison(left=args[0], right=args[2], operator=args[1])
1264
1219
 
1220
+ @v_args(meta=True)
1221
+ def subselect_comparison(self, meta: Meta, args) -> SubselectComparison:
1222
+ right = args[2]
1223
+ if not isinstance(right, Concept):
1224
+ right = arbitrary_to_concept(
1225
+ right,
1226
+ namespace=self.environment.namespace,
1227
+ name=f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(right))}",
1228
+ )
1229
+ self.environment.add_concept(right)
1230
+ return SubselectComparison(
1231
+ left=args[0],
1232
+ right=right,
1233
+ operator=args[1],
1234
+ )
1235
+
1265
1236
  def expr_tuple(self, args):
1266
1237
  return Parenthetical(content=args)
1267
1238
 
trilogy/parsing/render.py CHANGED
@@ -298,128 +298,6 @@ class Renderer:
298
298
  def _(self, arg: "FilterItem"):
299
299
  return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
300
300
 
301
- @to_string.register
302
- def _(self, arg: "WindowItem"):
303
- over = ",".join(self.to_string(c) for c in arg.over)
304
- order = ",".join(self.to_string(c) for c in arg.order_by)
305
- if over:
306
- return (
307
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
308
- )
309
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
310
-
311
- @to_string.register
312
- def _(self, arg: "FilterItem"):
313
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
314
-
315
- @to_string.register
316
- def _(self, arg: "ImportStatement"):
317
- return f"import {arg.path} as {arg.alias};"
318
-
319
- @to_string.register
320
- def _(self, arg: "WindowItem"):
321
- over = ",".join(self.to_string(c) for c in arg.over)
322
- order = ",".join(self.to_string(c) for c in arg.order_by)
323
- if over:
324
- return (
325
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
326
- )
327
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
328
-
329
- @to_string.register
330
- def _(self, arg: "FilterItem"):
331
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
332
-
333
- @to_string.register
334
- def _(self, arg: "ImportStatement"):
335
- return f"import {arg.path} as {arg.alias};"
336
-
337
- @to_string.register
338
- def _(self, arg: "WindowItem"):
339
- over = ",".join(self.to_string(c) for c in arg.over)
340
- order = ",".join(self.to_string(c) for c in arg.order_by)
341
- if over:
342
- return (
343
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
344
- )
345
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
346
-
347
- @to_string.register
348
- def _(self, arg: "FilterItem"):
349
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
350
-
351
- @to_string.register
352
- def _(self, arg: "ImportStatement"):
353
- return f"import {arg.path} as {arg.alias};"
354
-
355
- @to_string.register
356
- def _(self, arg: "WindowItem"):
357
- over = ",".join(self.to_string(c) for c in arg.over)
358
- order = ",".join(self.to_string(c) for c in arg.order_by)
359
- if over:
360
- return (
361
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
362
- )
363
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
364
-
365
- @to_string.register
366
- def _(self, arg: "FilterItem"):
367
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
368
-
369
- @to_string.register
370
- def _(self, arg: "ImportStatement"):
371
- return f"import {arg.path} as {arg.alias};"
372
-
373
- @to_string.register
374
- def _(self, arg: "WindowItem"):
375
- over = ",".join(self.to_string(c) for c in arg.over)
376
- order = ",".join(self.to_string(c) for c in arg.order_by)
377
- if over:
378
- return (
379
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
380
- )
381
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
382
-
383
- @to_string.register
384
- def _(self, arg: "FilterItem"):
385
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
386
-
387
- @to_string.register
388
- def _(self, arg: "ImportStatement"):
389
- return f"import {arg.path} as {arg.alias};"
390
-
391
- @to_string.register
392
- def _(self, arg: "WindowItem"):
393
- over = ",".join(self.to_string(c) for c in arg.over)
394
- order = ",".join(self.to_string(c) for c in arg.order_by)
395
- if over:
396
- return (
397
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
398
- )
399
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
400
-
401
- @to_string.register
402
- def _(self, arg: "FilterItem"):
403
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
404
-
405
- @to_string.register
406
- def _(self, arg: "ImportStatement"):
407
- return f"import {arg.path} as {arg.alias};"
408
-
409
- @to_string.register
410
- def _(self, arg: "WindowItem"):
411
- over = ",".join(self.to_string(c) for c in arg.over)
412
- order = ",".join(self.to_string(c) for c in arg.order_by)
413
- if over:
414
- return (
415
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
416
- )
417
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
418
-
419
- @to_string.register
420
- def _(self, arg: "FilterItem"):
421
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
422
-
423
301
  @to_string.register
424
302
  def _(self, arg: "ImportStatement"):
425
303
  return f"import {arg.path} as {arg.alias};"