pytrilogy 0.0.3.5__tar.gz → 0.0.3.7__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.

Files changed (129) hide show
  1. {pytrilogy-0.0.3.5/pytrilogy.egg-info → pytrilogy-0.0.3.7}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/pytrilogy.egg-info/SOURCES.txt +1 -0
  4. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_parse_engine.py +1 -1
  5. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/__init__.py +1 -1
  6. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/functions.py +26 -6
  7. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/models/datasource.py +6 -0
  8. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/models/environment.py +4 -3
  9. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/config.py +9 -0
  10. pytrilogy-0.0.3.7/trilogy/dialect/dataframe.py +42 -0
  11. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/enums.py +12 -1
  12. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/engine.py +11 -4
  13. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/executor.py +10 -2
  14. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/parsing/parse_engine.py +24 -23
  15. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/LICENSE.md +0 -0
  16. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/README.md +0 -0
  17. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/pyproject.toml +0 -0
  18. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/pytrilogy.egg-info/dependency_links.txt +0 -0
  19. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/pytrilogy.egg-info/entry_points.txt +0 -0
  20. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/pytrilogy.egg-info/requires.txt +0 -0
  21. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/pytrilogy.egg-info/top_level.txt +0 -0
  22. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/setup.cfg +0 -0
  23. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/setup.py +0 -0
  24. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_datatypes.py +0 -0
  25. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_declarations.py +0 -0
  26. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_derived_concepts.py +0 -0
  27. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_discovery_nodes.py +0 -0
  28. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_enums.py +0 -0
  29. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_environment.py +0 -0
  30. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_executor.py +0 -0
  31. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_functions.py +0 -0
  32. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_imports.py +0 -0
  33. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_metadata.py +0 -0
  34. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_models.py +0 -0
  35. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_multi_join_assignments.py +0 -0
  36. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_parsing.py +0 -0
  37. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_partial_handling.py +0 -0
  38. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_query_processing.py +0 -0
  39. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_select.py +0 -0
  40. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_show.py +0 -0
  41. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_statements.py +0 -0
  42. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_undefined_concept.py +0 -0
  43. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/tests/test_where_clause.py +0 -0
  44. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/authoring/__init__.py +0 -0
  45. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/compiler.py +0 -0
  46. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/constants.py +0 -0
  47. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/__init__.py +0 -0
  48. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/constants.py +0 -0
  49. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/enums.py +0 -0
  50. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/env_processor.py +0 -0
  51. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/environment_helpers.py +0 -0
  52. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/ergonomics.py +0 -0
  53. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/exceptions.py +0 -0
  54. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/graph_models.py +0 -0
  55. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/internal.py +0 -0
  56. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/models/__init__.py +0 -0
  57. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/models/author.py +0 -0
  58. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/models/build.py +0 -0
  59. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/models/build_environment.py +0 -0
  60. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/models/core.py +0 -0
  61. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/models/execute.py +0 -0
  62. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/optimization.py +0 -0
  63. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/optimizations/__init__.py +0 -0
  64. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/optimizations/base_optimization.py +0 -0
  65. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/optimizations/inline_constant.py +0 -0
  66. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/optimizations/inline_datasource.py +0 -0
  67. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  68. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/__init__.py +0 -0
  69. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  70. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/graph_utils.py +0 -0
  71. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/__init__.py +0 -0
  72. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  73. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/common.py +0 -0
  74. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  75. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/group_node.py +0 -0
  76. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  77. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  78. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  79. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  80. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  81. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  82. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  83. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/select_node.py +0 -0
  84. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  85. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/union_node.py +0 -0
  86. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  87. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/node_generators/window_node.py +0 -0
  88. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/nodes/__init__.py +0 -0
  89. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/nodes/base_node.py +0 -0
  90. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/nodes/filter_node.py +0 -0
  91. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/nodes/group_node.py +0 -0
  92. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/nodes/merge_node.py +0 -0
  93. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  94. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/nodes/union_node.py +0 -0
  95. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  96. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/nodes/window_node.py +0 -0
  97. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/processing/utility.py +0 -0
  98. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/query_processor.py +0 -0
  99. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/statements/__init__.py +0 -0
  100. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/statements/author.py +0 -0
  101. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/statements/build.py +0 -0
  102. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/statements/common.py +0 -0
  103. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/core/statements/execute.py +0 -0
  104. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/__init__.py +0 -0
  105. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/base.py +0 -0
  106. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/bigquery.py +0 -0
  107. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/common.py +0 -0
  108. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/duckdb.py +0 -0
  109. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/postgres.py +0 -0
  110. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/presto.py +0 -0
  111. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/snowflake.py +0 -0
  112. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/dialect/sql_server.py +0 -0
  113. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/hooks/__init__.py +0 -0
  114. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/hooks/base_hook.py +0 -0
  115. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/hooks/graph_hook.py +0 -0
  116. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/hooks/query_debugger.py +0 -0
  117. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/metadata/__init__.py +0 -0
  118. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/parser.py +0 -0
  119. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/parsing/__init__.py +0 -0
  120. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/parsing/common.py +0 -0
  121. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/parsing/config.py +0 -0
  122. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/parsing/exceptions.py +0 -0
  123. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/parsing/helpers.py +0 -0
  124. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/parsing/render.py +0 -0
  125. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/parsing/trilogy.lark +0 -0
  126. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/py.typed +0 -0
  127. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/scripts/__init__.py +0 -0
  128. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/scripts/trilogy.py +0 -0
  129. {pytrilogy-0.0.3.5 → pytrilogy-0.0.3.7}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.5
3
+ Version: 0.0.3.7
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.5
3
+ Version: 0.0.3.7
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -103,6 +103,7 @@ trilogy/dialect/base.py
103
103
  trilogy/dialect/bigquery.py
104
104
  trilogy/dialect/common.py
105
105
  trilogy/dialect/config.py
106
+ trilogy/dialect/dataframe.py
106
107
  trilogy/dialect/duckdb.py
107
108
  trilogy/dialect/enums.py
108
109
  trilogy/dialect/postgres.py
@@ -23,7 +23,7 @@ def test_parser():
23
23
  try:
24
24
  tokens = PARSER.parse(TEXT)
25
25
  x.transform(tokens)
26
- x.hydrate_missing()
26
+ x.run_second_parse_pass()
27
27
  except Exception as e:
28
28
  failed = True
29
29
  with raises(UndefinedConceptException):
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.5"
7
+ __version__ = "0.0.3.7"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -503,32 +503,52 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
503
503
  arg_count=1,
504
504
  ),
505
505
  FunctionType.ADD: FunctionConfig(
506
- valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
506
+ valid_inputs={
507
+ DataType.INTEGER,
508
+ DataType.FLOAT,
509
+ DataType.NUMBER,
510
+ DataType.NUMERIC,
511
+ },
507
512
  output_purpose=Purpose.PROPERTY,
508
513
  output_type=DataType.INTEGER,
509
514
  arg_count=InfiniteFunctionArgs,
510
515
  ),
511
516
  FunctionType.SUBTRACT: FunctionConfig(
512
- valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
517
+ valid_inputs={
518
+ DataType.INTEGER,
519
+ DataType.FLOAT,
520
+ DataType.NUMBER,
521
+ DataType.NUMERIC,
522
+ },
513
523
  output_purpose=Purpose.PROPERTY,
514
524
  output_type=DataType.INTEGER,
515
525
  arg_count=InfiniteFunctionArgs,
516
526
  ),
517
527
  FunctionType.MULTIPLY: FunctionConfig(
518
- valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
528
+ valid_inputs={
529
+ DataType.INTEGER,
530
+ DataType.FLOAT,
531
+ DataType.NUMBER,
532
+ DataType.NUMERIC,
533
+ },
519
534
  output_purpose=Purpose.PROPERTY,
520
535
  output_type=DataType.INTEGER,
521
536
  arg_count=InfiniteFunctionArgs,
522
537
  ),
523
538
  FunctionType.DIVIDE: FunctionConfig(
524
- valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
539
+ valid_inputs={
540
+ DataType.INTEGER,
541
+ DataType.FLOAT,
542
+ DataType.NUMBER,
543
+ DataType.NUMERIC,
544
+ },
525
545
  output_purpose=Purpose.PROPERTY,
526
546
  output_type=DataType.INTEGER,
527
547
  arg_count=InfiniteFunctionArgs,
528
548
  ),
529
549
  FunctionType.MOD: FunctionConfig(
530
550
  valid_inputs=[
531
- {DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
551
+ {DataType.INTEGER, DataType.FLOAT, DataType.NUMBER, DataType.NUMERIC},
532
552
  {DataType.INTEGER},
533
553
  ],
534
554
  output_purpose=Purpose.PROPERTY,
@@ -537,7 +557,7 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
537
557
  ),
538
558
  FunctionType.ROUND: FunctionConfig(
539
559
  valid_inputs=[
540
- {DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
560
+ {DataType.INTEGER, DataType.FLOAT, DataType.NUMBER, DataType.NUMERIC},
541
561
  {DataType.INTEGER},
542
562
  ],
543
563
  output_purpose=Purpose.PROPERTY,
@@ -117,6 +117,12 @@ class Datasource(HasUUID, Namespaced, BaseModel):
117
117
  where: Optional[WhereClause] = None
118
118
  non_partial_for: Optional[WhereClause] = None
119
119
 
120
+ @property
121
+ def safe_address(self) -> str:
122
+ if isinstance(self.address, Address):
123
+ return self.address.location
124
+ return self.address
125
+
120
126
  def __eq__(self, other):
121
127
  if not isinstance(other, Datasource):
122
128
  return False
@@ -426,7 +426,6 @@ class Environment(BaseModel):
426
426
  from trilogy.parsing.parse_engine import (
427
427
  PARSER,
428
428
  ParseToObjects,
429
- gen_cache_lookup,
430
429
  )
431
430
 
432
431
  if isinstance(path, str):
@@ -440,7 +439,8 @@ class Environment(BaseModel):
440
439
  else:
441
440
  target = path
442
441
  if not env:
443
- parse_address = gen_cache_lookup(str(target), alias, str(self.working_path))
442
+ import_keys = ["root", alias]
443
+ parse_address = "-".join(import_keys)
444
444
  try:
445
445
  with open(target, "r", encoding="utf-8") as f:
446
446
  text = f.read()
@@ -454,11 +454,12 @@ class Environment(BaseModel):
454
454
  ),
455
455
  parse_address=parse_address,
456
456
  token_address=target,
457
+ import_keys=import_keys,
457
458
  )
458
459
  nparser.set_text(text)
459
460
  nparser.environment.concepts.fail_on_missing = False
460
461
  nparser.transform(PARSER.parse(text))
461
- nparser.hydrate_missing()
462
+ nparser.run_second_parse_pass()
462
463
  nparser.environment.concepts.fail_on_missing = True
463
464
 
464
465
  except Exception as e:
@@ -1,3 +1,6 @@
1
+ from pandas import DataFrame
2
+
3
+
1
4
  class DialectConfig:
2
5
  def __init__(self):
3
6
  pass
@@ -104,3 +107,9 @@ class TrinoConfig(PrestoConfig):
104
107
  if self.schema:
105
108
  return f"trino://{self.username}:{self.password}@{self.host}:{self.port}/{self.catalog}/{self.schema}"
106
109
  return f"trino://{self.username}:{self.password}@{self.host}:{self.port}/{self.catalog}"
110
+
111
+
112
+ class DataFrameConfig(DuckDBConfig):
113
+ def __init__(self, dataframes: dict[str, DataFrame]):
114
+ super().__init__()
115
+ self.dataframes = dataframes
@@ -0,0 +1,42 @@
1
+ from typing import Any
2
+
3
+ from pandas import DataFrame
4
+ from sqlalchemy import text
5
+
6
+ from trilogy.core.models.environment import Environment
7
+ from trilogy.dialect.duckdb import DuckDBDialect
8
+ from trilogy.engine import ExecutionEngine
9
+
10
+
11
+ class DataframeDialect(DuckDBDialect):
12
+ pass
13
+
14
+
15
+ class DataframeConnectionWrapper(ExecutionEngine):
16
+ def __init__(self, engine: ExecutionEngine, dataframes: dict[str, DataFrame]):
17
+ self.engine = engine
18
+ self.dataframes = dataframes
19
+ self.connection = None
20
+
21
+ def setup(self, env: Environment, connection):
22
+ self._register_dataframes(env, connection)
23
+
24
+ def _register_dataframes(self, env: Environment, connection):
25
+ for ds in env.datasources.values():
26
+ if ds.safe_address in self.dataframes:
27
+ connection.execute(
28
+ text("register(:name, :df)"),
29
+ {"name": ds.safe_address, "df": self.dataframes[ds.safe_address]},
30
+ )
31
+ else:
32
+ raise ValueError(
33
+ f"Dataframe {ds.safe_address} not found in dataframes on connection config, have {self.dataframes.keys()}"
34
+ )
35
+ pass
36
+
37
+ def add_dataframe(self, name: str, df: DataFrame, connection, env: Environment):
38
+ self.dataframes[name] = df
39
+ self._register_dataframes(env, connection)
40
+
41
+ def connect(self) -> Any:
42
+ return self.engine.connect()
@@ -16,7 +16,7 @@ def default_factory(conf: DialectConfig, config_type):
16
16
 
17
17
  if not isinstance(conf, config_type):
18
18
  raise TypeError(
19
- f"Invalid dialect configuration for type {type(config_type).__name__}"
19
+ f"Invalid dialect configuration for type {type(config_type).__name__}, is {type(conf)}"
20
20
  )
21
21
  if conf.connect_args:
22
22
  return create_engine(
@@ -33,6 +33,7 @@ class Dialects(Enum):
33
33
  TRINO = "trino"
34
34
  POSTGRES = "postgres"
35
35
  SNOWFLAKE = "snowflake"
36
+ DATAFRAME = "dataframe"
36
37
 
37
38
  @classmethod
38
39
  def _missing_(cls, value):
@@ -88,6 +89,16 @@ class Dialects(Enum):
88
89
  from trilogy.dialect.config import TrinoConfig
89
90
 
90
91
  return _engine_factory(conf, TrinoConfig)
92
+ elif self == Dialects.DATAFRAME:
93
+ from trilogy.dialect.config import DataFrameConfig
94
+ from trilogy.dialect.dataframe import DataframeConnectionWrapper
95
+
96
+ if not conf:
97
+ conf = DataFrameConfig(dataframes={})
98
+
99
+ base = _engine_factory(conf, DataFrameConfig)
100
+
101
+ return DataframeConnectionWrapper(base, dataframes=conf.dataframes)
91
102
  else:
92
103
  raise ValueError(
93
104
  f"Unsupported dialect {self} for default engine creation; create one explicitly."
@@ -1,7 +1,9 @@
1
- from typing import Protocol
1
+ from typing import Any, Protocol
2
2
 
3
3
  from sqlalchemy.engine import Connection, CursorResult, Engine
4
4
 
5
+ from trilogy.core.models.environment import Environment
6
+
5
7
 
6
8
  class EngineResult(Protocol):
7
9
  pass
@@ -13,7 +15,7 @@ class EngineResult(Protocol):
13
15
  class EngineConnection(Protocol):
14
16
  pass
15
17
 
16
- def execute(self, statement: str) -> EngineResult:
18
+ def execute(self, statement: str, parameters: Any | None = None) -> EngineResult:
17
19
  pass
18
20
 
19
21
 
@@ -23,6 +25,9 @@ class ExecutionEngine(Protocol):
23
25
  def connect(self) -> EngineConnection:
24
26
  pass
25
27
 
28
+ def setup(self, env: Environment, connection):
29
+ pass
30
+
26
31
 
27
32
  ### Begin default SQLAlchemy implementation
28
33
  class SqlAlchemyResult(EngineResult):
@@ -37,8 +42,10 @@ class SqlAlchemyConnection(EngineConnection):
37
42
  def __init__(self, connection: Connection):
38
43
  self.connection = connection
39
44
 
40
- def execute(self, statement: str) -> SqlAlchemyResult:
41
- return SqlAlchemyResult(self.connection.execute(statement))
45
+ def execute(
46
+ self, statement: str, parameters: Any | None = None
47
+ ) -> SqlAlchemyResult:
48
+ return SqlAlchemyResult(self.connection.execute(statement, parameters))
42
49
 
43
50
 
44
51
  class SqlAlchemyEngine(ExecutionEngine):
@@ -4,7 +4,7 @@ from pathlib import Path
4
4
  from typing import Any, Generator, List, Optional, Protocol
5
5
 
6
6
  from sqlalchemy import text
7
- from sqlalchemy.engine import CursorResult, Engine
7
+ from sqlalchemy.engine import CursorResult
8
8
 
9
9
  from trilogy.constants import logger
10
10
  from trilogy.core.enums import FunctionType, Granularity, IOType
@@ -33,6 +33,7 @@ from trilogy.core.statements.execute import (
33
33
  )
34
34
  from trilogy.dialect.base import BaseDialect
35
35
  from trilogy.dialect.enums import Dialects
36
+ from trilogy.engine import ExecutionEngine
36
37
  from trilogy.hooks.base_hook import BaseHook
37
38
  from trilogy.parser import parse_text
38
39
 
@@ -71,7 +72,7 @@ class Executor(object):
71
72
  def __init__(
72
73
  self,
73
74
  dialect: Dialects,
74
- engine: Engine,
75
+ engine: ExecutionEngine,
75
76
  environment: Optional[Environment] = None,
76
77
  hooks: List[BaseHook] | None = None,
77
78
  ):
@@ -109,9 +110,16 @@ class Executor(object):
109
110
  from trilogy.dialect.snowflake import SnowflakeDialect
110
111
 
111
112
  self.generator = SnowflakeDialect()
113
+ elif self.dialect == Dialects.DATAFRAME:
114
+ from trilogy.dialect.dataframe import DataframeDialect
115
+
116
+ self.generator = DataframeDialect()
112
117
  else:
113
118
  raise ValueError(f"Unsupported dialect {self.dialect}")
114
119
  self.connection = self.engine.connect()
120
+ # TODO: make generic
121
+ if self.dialect == Dialects.DATAFRAME:
122
+ self.engine.setup(self.environment, self.connection)
115
123
 
116
124
  def execute_statement(self, statement) -> Optional[CursorResult]:
117
125
  if not isinstance(
@@ -129,6 +129,8 @@ CONSTANT_TYPES = (int, float, str, bool, list, ListWrapper, MapWrapper)
129
129
 
130
130
  SELF_LABEL = "root"
131
131
 
132
+ MAX_PARSE_DEPTH = 10
133
+
132
134
 
133
135
  @dataclass
134
136
  class WholeGrainWrapper:
@@ -146,13 +148,6 @@ with open(join(dirname(__file__), "trilogy.lark"), "r") as f:
146
148
  )
147
149
 
148
150
 
149
- def gen_cache_lookup(path: str, alias: str, parent: str) -> str:
150
- # path is the path of the file
151
- # alias is what it's being imported under
152
- # parent is the...direct parnet?
153
- return path + alias + parent
154
-
155
-
156
151
  def parse_concept_reference(
157
152
  name: str, environment: Environment, purpose: Optional[Purpose] = None
158
153
  ) -> Tuple[str, str, str, str | None]:
@@ -226,6 +221,8 @@ class ParseToObjects(Transformer):
226
221
  parsed: dict[str, "ParseToObjects"] | None = None,
227
222
  tokens: dict[Path | str, ParseTree] | None = None,
228
223
  text_lookup: dict[Path | str, str] | None = None,
224
+ environment_lookup: dict[str, Environment] | None = None,
225
+ import_keys: list[str] | None = None,
229
226
  ):
230
227
  Transformer.__init__(self, True)
231
228
  self.environment: Environment = environment
@@ -233,6 +230,7 @@ class ParseToObjects(Transformer):
233
230
  self.token_address: Path | str = token_address or SELF_LABEL
234
231
  self.parsed: dict[str, ParseToObjects] = parsed if parsed is not None else {}
235
232
  self.tokens: dict[Path | str, ParseTree] = tokens if tokens is not None else {}
233
+ self.environments: dict[str, Environment] = environment_lookup or {}
236
234
  self.text_lookup: dict[Path | str, str] = (
237
235
  text_lookup if text_lookup is not None else {}
238
236
  )
@@ -240,6 +238,7 @@ class ParseToObjects(Transformer):
240
238
  # after initial parsing
241
239
  self.parse_pass = ParsePass.INITIAL
242
240
  self.function_factory = FunctionFactory(self.environment)
241
+ self.import_keys: list[str] = import_keys or ["root"]
243
242
 
244
243
  def set_text(self, text: str):
245
244
  self.text_lookup[self.token_address] = text
@@ -255,14 +254,14 @@ class ParseToObjects(Transformer):
255
254
  for _, v in self.parsed.items():
256
255
  v.prepare_parse()
257
256
 
258
- def hydrate_missing(self, force: bool = False):
257
+ def run_second_parse_pass(self, force: bool = False):
259
258
  if self.token_address not in self.tokens:
260
259
  return []
261
260
  self.parse_pass = ParsePass.VALIDATION
262
261
  for _, v in list(self.parsed.items()):
263
- if v.parse_pass == ParsePass.VALIDATION and not force:
262
+ if v.parse_pass == ParsePass.VALIDATION:
264
263
  continue
265
- v.hydrate_missing()
264
+ v.run_second_parse_pass()
266
265
  reparsed = self.transform(self.tokens[self.token_address])
267
266
  self.environment.concepts.undefined = {}
268
267
  return reparsed
@@ -306,11 +305,6 @@ class ParseToObjects(Transformer):
306
305
  def QUOTED_IDENTIFIER(self, args) -> str:
307
306
  return args.value[1:-1]
308
307
 
309
- # @v_args(meta=True)
310
- # def concept_lit(self, meta: Meta, args) -> ConceptRef:
311
- # address = args[0]
312
- # return self.environment.concepts.__getitem__(address, meta.line)
313
- # return ConceptRef(address=address, line_no=meta.line)
314
308
  @v_args(meta=True)
315
309
  def concept_lit(self, meta: Meta, args) -> ConceptRef:
316
310
  address = args[0]
@@ -402,7 +396,6 @@ class ParseToObjects(Transformer):
402
396
  if len(concept_list) > 1:
403
397
  modifiers += concept_list[:-1]
404
398
  concept = concept_list[-1]
405
- assert not self.environment.concepts.fail_on_missing
406
399
  resolved = self.environment.concepts.__getitem__( # type: ignore
407
400
  key=concept, line_no=meta.line, file=self.token_address
408
401
  )
@@ -858,8 +851,10 @@ class ParseToObjects(Transformer):
858
851
  def import_statement(self, args: list[str]) -> ImportStatement:
859
852
  if len(args) == 2:
860
853
  alias = args[-1]
854
+ cache_key = args[-1]
861
855
  else:
862
856
  alias = self.environment.namespace
857
+ cache_key = args[0]
863
858
  path = args[0].split(".")
864
859
 
865
860
  target = join(self.environment.working_path, *path) + ".preql"
@@ -867,10 +862,14 @@ class ParseToObjects(Transformer):
867
862
  # tokens + text are cached by path
868
863
  token_lookup = Path(target)
869
864
 
870
- # cache lookups by the target, the alias, and the file we're importing it from
871
- cache_lookup = gen_cache_lookup(
872
- path=target, alias=alias, parent=str(self.token_address)
873
- )
865
+ # parser + env has to be cached by prior import path + current key
866
+ key_path = self.import_keys + [cache_key]
867
+ cache_lookup = "-".join(key_path)
868
+
869
+ # we don't iterate past the max parse depth
870
+ if len(key_path) > MAX_PARSE_DEPTH:
871
+ return ImportStatement(alias=alias, path=Path(target))
872
+
874
873
  if token_lookup in self.tokens:
875
874
  raw_tokens = self.tokens[token_lookup]
876
875
  text = self.text_lookup[token_lookup]
@@ -886,7 +885,7 @@ class ParseToObjects(Transformer):
886
885
  new_env = nparser.environment
887
886
  if nparser.parse_pass != ParsePass.VALIDATION:
888
887
  # nparser.transform(raw_tokens)
889
- nparser.hydrate_missing()
888
+ nparser.run_second_parse_pass()
890
889
  else:
891
890
  try:
892
891
  new_env = Environment(
@@ -902,6 +901,7 @@ class ParseToObjects(Transformer):
902
901
  parsed=self.parsed,
903
902
  tokens=self.tokens,
904
903
  text_lookup=self.text_lookup,
904
+ import_keys=self.import_keys + [cache_key],
905
905
  )
906
906
  nparser.transform(raw_tokens)
907
907
  self.parsed[cache_lookup] = nparser
@@ -909,6 +909,7 @@ class ParseToObjects(Transformer):
909
909
  raise ImportError(
910
910
  f"Unable to import file {target}, parsing error: {e}"
911
911
  ) from e
912
+
912
913
  parsed_path = Path(args[0])
913
914
  imps = ImportStatement(alias=alias, path=parsed_path)
914
915
 
@@ -1621,7 +1622,7 @@ def parse_text(
1621
1622
  environment = environment or (
1622
1623
  Environment(working_path=root) if root else Environment()
1623
1624
  )
1624
- parser = ParseToObjects(environment=environment)
1625
+ parser = ParseToObjects(environment=environment, import_keys=["root"])
1625
1626
 
1626
1627
  try:
1627
1628
  parser.set_text(text)
@@ -1629,7 +1630,7 @@ def parse_text(
1629
1630
  parser.prepare_parse()
1630
1631
  parser.transform(PARSER.parse(text))
1631
1632
  # this will reset fail on missing
1632
- pass_two = parser.hydrate_missing(force=True)
1633
+ pass_two = parser.run_second_parse_pass()
1633
1634
  output = [v for v in pass_two if v]
1634
1635
  environment.concepts.fail_on_missing = True
1635
1636
  except VisitError as e:
File without changes
File without changes
File without changes
File without changes
File without changes