pytrilogy 0.0.3.3__tar.gz → 0.0.3.5__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 (128) hide show
  1. {pytrilogy-0.0.3.3/pytrilogy.egg-info → pytrilogy-0.0.3.5}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/pytrilogy.egg-info/SOURCES.txt +1 -0
  4. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_datatypes.py +1 -1
  5. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_executor.py +5 -2
  6. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_imports.py +12 -1
  7. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_parsing.py +2 -2
  8. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_where_clause.py +1 -1
  9. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/__init__.py +1 -1
  10. pytrilogy-0.0.3.5/trilogy/authoring/__init__.py +90 -0
  11. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/models/author.py +3 -7
  12. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/models/environment.py +45 -22
  13. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/concept_strategies_v3.py +1 -1
  14. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/query_processor.py +1 -1
  15. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/executor.py +7 -5
  16. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/parsing/common.py +3 -9
  17. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/parsing/parse_engine.py +28 -22
  18. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/LICENSE.md +0 -0
  19. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/README.md +0 -0
  20. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/pyproject.toml +0 -0
  21. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/pytrilogy.egg-info/dependency_links.txt +0 -0
  22. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/pytrilogy.egg-info/entry_points.txt +0 -0
  23. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/pytrilogy.egg-info/requires.txt +0 -0
  24. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/pytrilogy.egg-info/top_level.txt +0 -0
  25. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/setup.cfg +0 -0
  26. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/setup.py +0 -0
  27. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_declarations.py +0 -0
  28. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_derived_concepts.py +0 -0
  29. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_discovery_nodes.py +0 -0
  30. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_enums.py +0 -0
  31. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_environment.py +0 -0
  32. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_functions.py +0 -0
  33. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_metadata.py +0 -0
  34. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_models.py +0 -0
  35. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_multi_join_assignments.py +0 -0
  36. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_parse_engine.py +0 -0
  37. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_partial_handling.py +0 -0
  38. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_query_processing.py +0 -0
  39. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_select.py +0 -0
  40. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_show.py +0 -0
  41. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_statements.py +0 -0
  42. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/tests/test_undefined_concept.py +0 -0
  43. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/compiler.py +0 -0
  44. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/constants.py +0 -0
  45. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/__init__.py +0 -0
  46. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/constants.py +0 -0
  47. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/enums.py +0 -0
  48. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/env_processor.py +0 -0
  49. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/environment_helpers.py +0 -0
  50. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/ergonomics.py +0 -0
  51. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/exceptions.py +0 -0
  52. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/functions.py +0 -0
  53. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/graph_models.py +0 -0
  54. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/internal.py +0 -0
  55. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/models/__init__.py +0 -0
  56. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/models/build.py +0 -0
  57. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/models/build_environment.py +0 -0
  58. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/models/core.py +0 -0
  59. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/models/datasource.py +0 -0
  60. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/models/execute.py +0 -0
  61. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/optimization.py +0 -0
  62. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/optimizations/__init__.py +0 -0
  63. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/optimizations/base_optimization.py +0 -0
  64. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/optimizations/inline_constant.py +0 -0
  65. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/optimizations/inline_datasource.py +0 -0
  66. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  67. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/__init__.py +0 -0
  68. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/graph_utils.py +0 -0
  69. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/__init__.py +0 -0
  70. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  71. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/common.py +0 -0
  72. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  73. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/group_node.py +0 -0
  74. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  75. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  76. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  77. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  78. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  79. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  80. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  81. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/select_node.py +0 -0
  82. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  83. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/union_node.py +0 -0
  84. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  85. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/node_generators/window_node.py +0 -0
  86. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/nodes/__init__.py +0 -0
  87. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/nodes/base_node.py +0 -0
  88. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/nodes/filter_node.py +0 -0
  89. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/nodes/group_node.py +0 -0
  90. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/nodes/merge_node.py +0 -0
  91. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  92. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/nodes/union_node.py +0 -0
  93. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  94. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/nodes/window_node.py +0 -0
  95. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/processing/utility.py +0 -0
  96. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/statements/__init__.py +0 -0
  97. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/statements/author.py +0 -0
  98. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/statements/build.py +0 -0
  99. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/statements/common.py +0 -0
  100. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/core/statements/execute.py +0 -0
  101. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/__init__.py +0 -0
  102. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/base.py +0 -0
  103. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/bigquery.py +0 -0
  104. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/common.py +0 -0
  105. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/config.py +0 -0
  106. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/duckdb.py +0 -0
  107. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/enums.py +0 -0
  108. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/postgres.py +0 -0
  109. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/presto.py +0 -0
  110. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/snowflake.py +0 -0
  111. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/dialect/sql_server.py +0 -0
  112. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/engine.py +0 -0
  113. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/hooks/__init__.py +0 -0
  114. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/hooks/base_hook.py +0 -0
  115. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/hooks/graph_hook.py +0 -0
  116. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/hooks/query_debugger.py +0 -0
  117. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/metadata/__init__.py +0 -0
  118. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/parser.py +0 -0
  119. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/parsing/__init__.py +0 -0
  120. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/parsing/config.py +0 -0
  121. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/parsing/exceptions.py +0 -0
  122. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/parsing/helpers.py +0 -0
  123. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/parsing/render.py +0 -0
  124. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/parsing/trilogy.lark +0 -0
  125. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/py.typed +0 -0
  126. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/scripts/__init__.py +0 -0
  127. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/scripts/trilogy.py +0 -0
  128. {pytrilogy-0.0.3.3 → pytrilogy-0.0.3.5}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.3
3
+ Version: 0.0.3.5
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.3
3
+ Version: 0.0.3.5
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -37,6 +37,7 @@ trilogy/executor.py
37
37
  trilogy/parser.py
38
38
  trilogy/py.typed
39
39
  trilogy/utility.py
40
+ trilogy/authoring/__init__.py
40
41
  trilogy/core/__init__.py
41
42
  trilogy/core/constants.py
42
43
  trilogy/core/enums.py
@@ -9,7 +9,7 @@ from trilogy.parsing.parse_engine import (
9
9
 
10
10
  def test_numeric():
11
11
  env, _ = parse_text(
12
- "const order_id numeric(12,2); const rounded <- cast(order_id as numeric(15,2));"
12
+ "const order_id numeric(12,2); auto rounded <- cast(order_id as numeric(15,2));"
13
13
  )
14
14
  assert env.concepts["order_id"].datatype == NumericType(precision=12, scale=2)
15
15
 
@@ -5,8 +5,11 @@ from trilogy.core.models.environment import Environment
5
5
 
6
6
 
7
7
  def test_file_parsing():
8
- target = Path(__file__).parent / "test_env.preql"
9
- parsed = Dialects.DUCK_DB.default_executor().parse_file(target)
8
+ directory = Path(__file__).parent
9
+ target = directory / "test_env.preql"
10
+ parsed = Dialects.DUCK_DB.default_executor(
11
+ environment=Environment(working_path=directory)
12
+ ).parse_file(target)
10
13
  assert len(list(parsed)) == 1
11
14
 
12
15
 
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- from trilogy.core.models.environment import Environment
3
+ from trilogy.core.models.environment import Environment, LazyEnvironment
4
4
 
5
5
 
6
6
  def test_multi_environment():
@@ -41,3 +41,14 @@ key id2 int;
41
41
  assert basic.concepts["id"].name == "id"
42
42
  assert basic.concepts["id2"].name == "id2"
43
43
  assert basic.concepts["id"].namespace == basic.concepts["id2"].namespace
44
+
45
+
46
+ def test_import_concept_resolution():
47
+ basic = LazyEnvironment(
48
+ load_path=Path(__file__).parent / "test_lazy_env.preql",
49
+ working_path=Path(__file__).parent,
50
+ setup_queries=[],
51
+ )
52
+ materialized = basic.materialize_for_select()
53
+ assert "one.two.import_key" in materialized.materialized_concepts
54
+ assert "two.two.import_key" in materialized.materialized_concepts
@@ -323,7 +323,7 @@ def test_circular_aliasing():
323
323
  property composite_id.first <- split(composite_id, '-')[1];
324
324
  property composite_id.second <- split(composite_id, '-')[2];
325
325
 
326
- key composite_id_alt <- concat(first, '-', second);
326
+ auto composite_id_alt <- concat(first, '-', second);
327
327
 
328
328
  merge composite_id_alt into composite_id;
329
329
 
@@ -367,7 +367,7 @@ def test_circular_aliasing_inverse():
367
367
  property composite_id.first <- split(composite_id, '-')[1];
368
368
  property composite_id.second <- split(composite_id, '-')[2];
369
369
 
370
- key composite_id_alt <- concat(first, '-', second);
370
+ auto composite_id_alt <- concat(first, '-', second);
371
371
 
372
372
  merge composite_id_alt into composite_id;
373
373
 
@@ -89,7 +89,7 @@ select
89
89
 
90
90
  def test_select_where_attribute_v2(test_environment):
91
91
  declarations = """
92
- key special_category <- filter category_id where like(category_name, '%special%') is True;
92
+ auto special_category <- filter category_id where like(category_name, '%special%') is True;
93
93
 
94
94
  select
95
95
  order_id,
@@ -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.3"
7
+ __version__ = "0.0.3.5"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -0,0 +1,90 @@
1
+ from trilogy.constants import DEFAULT_NAMESPACE
2
+ from trilogy.core.enums import (
3
+ BooleanOperator,
4
+ ComparisonOperator,
5
+ FunctionClass,
6
+ FunctionType,
7
+ InfiniteFunctionArgs,
8
+ Ordering,
9
+ Purpose,
10
+ )
11
+ from trilogy.core.functions import FunctionFactory
12
+ from trilogy.core.models.author import (
13
+ AggregateWrapper,
14
+ CaseElse,
15
+ CaseWhen,
16
+ Comparison,
17
+ Concept,
18
+ ConceptRef,
19
+ Conditional,
20
+ FilterItem,
21
+ Function,
22
+ HavingClause,
23
+ MagicConstants,
24
+ Metadata,
25
+ OrderBy,
26
+ OrderItem,
27
+ Parenthetical,
28
+ SubselectComparison,
29
+ WhereClause,
30
+ WindowItem,
31
+ WindowItemOrder,
32
+ WindowItemOver,
33
+ WindowOrder,
34
+ WindowType,
35
+ )
36
+ from trilogy.core.models.core import DataType, ListType, ListWrapper, StructType
37
+ from trilogy.core.models.environment import Environment
38
+ from trilogy.core.statements.author import (
39
+ ConceptDeclarationStatement,
40
+ ConceptTransform,
41
+ SelectItem,
42
+ SelectStatement,
43
+ )
44
+ from trilogy.parsing.common import arbitrary_to_concept, arg_to_datatype
45
+
46
+ __all__ = [
47
+ "Concept",
48
+ "Function",
49
+ "WhereClause",
50
+ "Comparison",
51
+ "FilterItem",
52
+ "CaseWhen",
53
+ "CaseElse",
54
+ "AggregateWrapper",
55
+ "WindowItem",
56
+ "WindowOrder",
57
+ "WindowType",
58
+ "WindowItemOrder",
59
+ "WindowItemOver",
60
+ "DataType",
61
+ "StructType",
62
+ "ListType",
63
+ "ListWrapper",
64
+ "FunctionType",
65
+ "FunctionFactory",
66
+ "ConceptDeclarationStatement",
67
+ "ConceptTransform",
68
+ "SelectItem",
69
+ "SelectStatement",
70
+ "Environment",
71
+ "ConceptRef",
72
+ "HavingClause",
73
+ "MagicConstants",
74
+ "Metadata",
75
+ "OrderBy",
76
+ "OrderItem",
77
+ "Parenthetical",
78
+ "SubselectComparison",
79
+ "Conditional",
80
+ "BooleanOperator",
81
+ "ComparisonOperator",
82
+ "FunctionClass",
83
+ "FunctionType",
84
+ "InfiniteFunctionArgs",
85
+ "Ordering",
86
+ "Purpose",
87
+ "DEFAULT_NAMESPACE",
88
+ "arbitrary_to_concept",
89
+ "arg_to_datatype",
90
+ ]
@@ -166,10 +166,8 @@ class UndefinedConcept(ConceptRef):
166
166
 
167
167
 
168
168
  def address_with_namespace(address: str, namespace: str) -> str:
169
- ns = address.split(".", 1)[0]
170
- if ns == namespace:
171
- return address
172
- if ns == DEFAULT_NAMESPACE:
169
+ existing_ns = address.split(".", 1)[0]
170
+ if existing_ns == DEFAULT_NAMESPACE:
173
171
  return f"{namespace}.{address.split('.',1)[1]}"
174
172
  return f"{namespace}.{address}"
175
173
 
@@ -203,7 +201,7 @@ class Parenthetical(
203
201
  def __repr__(self):
204
202
  return f"({str(self.content)})"
205
203
 
206
- def with_namespace(self, namespace: str):
204
+ def with_namespace(self, namespace: str) -> Parenthetical:
207
205
  return Parenthetical.model_construct(
208
206
  content=(
209
207
  self.content.with_namespace(namespace)
@@ -917,8 +915,6 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
917
915
  return self.name.replace(".", "_")
918
916
 
919
917
  def with_namespace(self, namespace: str) -> Self:
920
- if namespace == self.namespace:
921
- return self
922
918
  return self.__class__.model_construct(
923
919
  name=self.name,
924
920
  datatype=self.datatype,
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
  from typing import (
9
9
  TYPE_CHECKING,
10
10
  Annotated,
11
+ Any,
11
12
  Dict,
12
13
  ItemsView,
13
14
  List,
@@ -383,14 +384,13 @@ class Environment(BaseModel):
383
384
  ):
384
385
  exists = True
385
386
  imp_stm = Import(alias=alias, path=Path(source.working_path))
386
- same_namespace = alias == self.namespace
387
+ same_namespace = alias == DEFAULT_NAMESPACE
387
388
 
388
389
  if not exists:
389
390
  self.imports[alias].append(imp_stm)
390
391
  # we can't exit early
391
392
  # as there may be new concepts
392
393
  for k, concept in source.concepts.items():
393
-
394
394
  # skip internal namespace
395
395
  if INTERNAL_NAMESPACE in concept.address:
396
396
  continue
@@ -416,7 +416,6 @@ class Environment(BaseModel):
416
416
  self.alias_origin_lookup[address_with_namespace(key, alias)] = (
417
417
  val.with_namespace(alias)
418
418
  )
419
-
420
419
  return self
421
420
 
422
421
  def add_file_import(
@@ -457,8 +456,10 @@ class Environment(BaseModel):
457
456
  token_address=target,
458
457
  )
459
458
  nparser.set_text(text)
459
+ nparser.environment.concepts.fail_on_missing = False
460
460
  nparser.transform(PARSER.parse(text))
461
461
  nparser.hydrate_missing()
462
+ nparser.environment.concepts.fail_on_missing = True
462
463
 
463
464
  except Exception as e:
464
465
  raise ImportError(
@@ -664,37 +665,59 @@ class LazyEnvironment(Environment):
664
665
  until relevant attributes accessed."""
665
666
 
666
667
  load_path: Path
668
+ setup_queries: list[Any] = Field(default_factory=list)
667
669
  loaded: bool = False
668
670
 
671
+ @property
672
+ def setup_path(self) -> Path:
673
+ return self.load_path.parent / "setup.preql"
674
+
669
675
  def __init__(self, **data):
676
+ if not data.get("working_path"):
677
+ data["working_path"] = data["load_path"].parent
670
678
  super().__init__(**data)
679
+ assert self.working_path == self.load_path.parent
671
680
 
672
681
  def _add_path_concepts(self):
673
682
  pass
674
683
 
684
+ def _load(self):
685
+ if self.loaded:
686
+ return
687
+ from trilogy import parse
688
+
689
+ env = Environment(working_path=self.load_path.parent)
690
+ assert env.working_path == self.load_path.parent
691
+ with open(self.load_path, "r") as f:
692
+ env, _ = parse(f.read(), env)
693
+ if self.setup_path.exists():
694
+ with open(self.setup_path, "r") as f2:
695
+ env, q = parse(f2.read(), env)
696
+ for q in q:
697
+ self.setup_queries.append(q)
698
+ self.loaded = True
699
+ self.datasources = env.datasources
700
+ self.concepts = env.concepts
701
+ self.imports = env.imports
702
+ self.alias_origin_lookup = env.alias_origin_lookup
703
+ self.materialized_concepts = env.materialized_concepts
704
+ self.functions = env.functions
705
+ self.data_types = env.data_types
706
+ self.cte_name_map = env.cte_name_map
707
+
675
708
  def __getattribute__(self, name):
676
- if name in (
677
- "load_path",
678
- "loaded",
679
- "working_path",
680
- "model_config",
681
- "model_fields",
682
- "model_post_init",
709
+ if name not in (
710
+ "datasources",
711
+ "concepts",
712
+ "imports",
713
+ "materialized_concepts",
714
+ "functions",
715
+ "datatypes",
716
+ "cte_name_map",
683
717
  ) or name.startswith("_"):
684
718
  return super().__getattribute__(name)
685
719
  if not self.loaded:
686
- logger.info(
687
- f"lazily evaluating load path {self.load_path} to access {name}"
688
- )
689
- from trilogy import parse
690
-
691
- env = Environment(working_path=str(self.working_path))
692
- with open(self.load_path, "r") as f:
693
- parse(f.read(), env)
694
- self.loaded = True
695
- self.datasources = env.datasources
696
- self.concepts = env.concepts
697
- self.imports = env.imports
720
+ self._load()
698
721
  return super().__getattribute__(name)
699
722
 
700
723
 
@@ -1046,7 +1046,7 @@ def source_query_concepts(
1046
1046
  f"{c.address}<{c.purpose}>{c.derivation}>" for c in output_concepts
1047
1047
  ]
1048
1048
  raise ValueError(
1049
- f"Could not resolve conections between {error_strings} from environment graph."
1049
+ f"Could not resolve connections between {error_strings} from environment graph."
1050
1050
  )
1051
1051
  final = [x for x in root.output_concepts if x.address not in root.hidden_concepts]
1052
1052
  logger.info(
@@ -385,7 +385,7 @@ def get_query_node(
385
385
  )
386
386
  graph = generate_graph(build_environment)
387
387
  logger.info(
388
- f"{LOGGER_PREFIX} getting source datasource for outputs {statement.output_components} grain {build_statement.grain}"
388
+ f"{LOGGER_PREFIX} getting source datasource for outputs {build_statement.output_components} grain {build_statement.grain}"
389
389
  )
390
390
 
391
391
  search_concepts: list[BuildConcept] = build_statement.output_components
@@ -346,10 +346,10 @@ class Executor(object):
346
346
  file = Path(file)
347
347
  with open(file, "r") as f:
348
348
  command = f.read()
349
- return self.parse_text_generator(command, persist=persist)
349
+ return self.parse_text_generator(command, persist=persist, root=file)
350
350
 
351
351
  def parse_text(
352
- self, command: str, persist: bool = False
352
+ self, command: str, persist: bool = False, root: Path | None = None
353
353
  ) -> List[
354
354
  ProcessedQuery
355
355
  | ProcessedQueryPersist
@@ -357,9 +357,11 @@ class Executor(object):
357
357
  | ProcessedRawSQLStatement
358
358
  | ProcessedCopyStatement
359
359
  ]:
360
- return list(self.parse_text_generator(command, persist=persist))
360
+ return list(self.parse_text_generator(command, persist=persist, root=root))
361
361
 
362
- def parse_text_generator(self, command: str, persist: bool = False) -> Generator[
362
+ def parse_text_generator(
363
+ self, command: str, persist: bool = False, root: Path | None = None
364
+ ) -> Generator[
363
365
  ProcessedQuery
364
366
  | ProcessedQueryPersist
365
367
  | ProcessedShowStatement
@@ -369,7 +371,7 @@ class Executor(object):
369
371
  None,
370
372
  ]:
371
373
  """Process a preql text command"""
372
- _, parsed = parse_text(command, self.environment)
374
+ _, parsed = parse_text(command, self.environment, root=root)
373
375
  generatable = [
374
376
  x
375
377
  for x in parsed
@@ -152,7 +152,6 @@ def constant_to_concept(
152
152
  parent: ListWrapper | MapWrapper | list | int | float | str,
153
153
  name: str,
154
154
  namespace: str,
155
- purpose: Purpose | None = None,
156
155
  metadata: Metadata | None = None,
157
156
  ) -> Concept:
158
157
  const_function: Function = Function(
@@ -340,7 +339,6 @@ def filter_item_to_concept(
340
339
  name: str,
341
340
  namespace: str,
342
341
  environment: Environment,
343
- purpose: Purpose | None = None,
344
342
  metadata: Metadata | None = None,
345
343
  ) -> Concept:
346
344
  fmetadata = metadata or Metadata()
@@ -377,14 +375,13 @@ def window_item_to_concept(
377
375
  name: str,
378
376
  namespace: str,
379
377
  environment: Environment,
380
- purpose: Purpose | None = None,
381
378
  metadata: Metadata | None = None,
382
379
  ) -> Concept:
383
380
  fmetadata = metadata or Metadata()
384
381
  bcontent = environment.concepts[parent.content.address]
385
382
  if isinstance(bcontent, UndefinedConcept):
386
383
  return UndefinedConcept(address=f"{namespace}.{name}", metadata=fmetadata)
387
- local_purpose, keys = get_purpose_and_keys(purpose, (bcontent,), environment)
384
+ local_purpose, keys = get_purpose_and_keys(None, (bcontent,), environment)
388
385
 
389
386
  if parent.order_by:
390
387
  grain_components = parent.over + [bcontent.output]
@@ -568,7 +565,6 @@ def arbitrary_to_concept(
568
565
  namespace: str | None = None,
569
566
  name: str | None = None,
570
567
  metadata: Metadata | None = None,
571
- purpose: Purpose | None = None,
572
568
  ) -> Concept:
573
569
  namespace = namespace or environment.namespace
574
570
  if isinstance(parent, AggregateWrapper):
@@ -585,7 +581,6 @@ def arbitrary_to_concept(
585
581
  name,
586
582
  namespace,
587
583
  environment=environment,
588
- purpose=purpose,
589
584
  metadata=metadata,
590
585
  )
591
586
  elif isinstance(parent, FilterItem):
@@ -596,7 +591,6 @@ def arbitrary_to_concept(
596
591
  name,
597
592
  namespace,
598
593
  environment=environment,
599
- purpose=purpose,
600
594
  metadata=metadata,
601
595
  )
602
596
  elif isinstance(parent, Function):
@@ -612,8 +606,8 @@ def arbitrary_to_concept(
612
606
  elif isinstance(parent, ListWrapper):
613
607
  if not name:
614
608
  name = f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(parent))}"
615
- return constant_to_concept(parent, name, namespace, purpose, metadata)
609
+ return constant_to_concept(parent, name, namespace, metadata)
616
610
  else:
617
611
  if not name:
618
612
  name = f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(parent))}"
619
- return constant_to_concept(parent, name, namespace, purpose, metadata)
613
+ return constant_to_concept(parent, name, namespace, metadata)
@@ -147,6 +147,9 @@ with open(join(dirname(__file__), "trilogy.lark"), "r") as f:
147
147
 
148
148
 
149
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?
150
153
  return path + alias + parent
151
154
 
152
155
 
@@ -252,10 +255,12 @@ class ParseToObjects(Transformer):
252
255
  for _, v in self.parsed.items():
253
256
  v.prepare_parse()
254
257
 
255
- def hydrate_missing(self):
258
+ def hydrate_missing(self, force: bool = False):
259
+ if self.token_address not in self.tokens:
260
+ return []
256
261
  self.parse_pass = ParsePass.VALIDATION
257
- for k, v in self.parsed.items():
258
- if v.parse_pass == ParsePass.VALIDATION:
262
+ for _, v in list(self.parsed.items()):
263
+ if v.parse_pass == ParsePass.VALIDATION and not force:
259
264
  continue
260
265
  v.hydrate_missing()
261
266
  reparsed = self.transform(self.tokens[self.token_address])
@@ -390,7 +395,6 @@ class ParseToObjects(Transformer):
390
395
 
391
396
  @v_args(meta=True)
392
397
  def column_assignment(self, meta: Meta, args):
393
- # TODO -> deal with conceptual modifiers
394
398
  modifiers = []
395
399
  alias = args[0]
396
400
  concept_list = args[1]
@@ -398,6 +402,7 @@ class ParseToObjects(Transformer):
398
402
  if len(concept_list) > 1:
399
403
  modifiers += concept_list[:-1]
400
404
  concept = concept_list[-1]
405
+ assert not self.environment.concepts.fail_on_missing
401
406
  resolved = self.environment.concepts.__getitem__( # type: ignore
402
407
  key=concept, line_no=meta.line, file=self.token_address
403
408
  )
@@ -508,8 +513,6 @@ class ParseToObjects(Transformer):
508
513
  else:
509
514
  metadata = None
510
515
  purpose = args[0]
511
- if purpose == Purpose.AUTO:
512
- purpose = None
513
516
  raw_name = args[1]
514
517
  # abc.def.property pattern
515
518
  if isinstance(raw_name, str):
@@ -538,10 +541,14 @@ class ParseToObjects(Transformer):
538
541
  name=name,
539
542
  namespace=namespace,
540
543
  environment=self.environment,
541
- purpose=purpose,
542
544
  metadata=metadata,
543
545
  )
544
546
 
547
+ if purpose and purpose != Purpose.AUTO and concept.purpose != purpose:
548
+ raise SyntaxError(
549
+ f'Concept {name} purpose {concept.purpose} does not match declared purpose {purpose}. Suggest defaulting to "auto"'
550
+ )
551
+
545
552
  if concept.metadata:
546
553
  concept.metadata.line_number = meta.line
547
554
  self.environment.add_concept(concept, meta=meta)
@@ -552,7 +559,6 @@ class ParseToObjects(Transformer):
552
559
  source_value,
553
560
  name=name,
554
561
  namespace=namespace,
555
- purpose=purpose,
556
562
  metadata=metadata,
557
563
  )
558
564
  if concept.metadata:
@@ -577,15 +583,6 @@ class ParseToObjects(Transformer):
577
583
  namespace=self.environment.namespace or DEFAULT_NAMESPACE,
578
584
  )
579
585
 
580
- # clean up current definitions
581
- # to_delete = set()
582
- # if output.name in self.environment.named_statements:
583
- # for k, v in self.environment.concepts.items():
584
- # if v.derivation == Derivation.ROWSET and v.lineage.rowset.name == name:
585
- # to_delete.add(k)
586
- # for k in to_delete:
587
- # self.environment.concepts.pop(k)
588
-
589
586
  for new_concept in rowset_to_concepts(output, self.environment):
590
587
  if new_concept.metadata:
591
588
  new_concept.metadata.line_number = meta.line
@@ -887,10 +884,14 @@ class ParseToObjects(Transformer):
887
884
  if cache_lookup in self.parsed:
888
885
  nparser = self.parsed[cache_lookup]
889
886
  new_env = nparser.environment
887
+ if nparser.parse_pass != ParsePass.VALIDATION:
888
+ # nparser.transform(raw_tokens)
889
+ nparser.hydrate_missing()
890
890
  else:
891
891
  try:
892
892
  new_env = Environment(
893
893
  working_path=dirname(target),
894
+ env_file_path=token_lookup,
894
895
  )
895
896
  new_env.concepts.fail_on_missing = False
896
897
  self.parsed[self.parse_address] = self
@@ -908,10 +909,11 @@ class ParseToObjects(Transformer):
908
909
  raise ImportError(
909
910
  f"Unable to import file {target}, parsing error: {e}"
910
911
  ) from e
912
+ parsed_path = Path(args[0])
913
+ imps = ImportStatement(alias=alias, path=parsed_path)
911
914
 
912
- imps = ImportStatement(alias=alias, path=Path(args[0]))
913
915
  self.environment.add_import(
914
- alias, new_env, Import(alias=alias, path=Path(args[0]))
916
+ alias, new_env, Import(alias=alias, path=parsed_path)
915
917
  )
916
918
  return imps
917
919
 
@@ -1602,7 +1604,9 @@ def parse_text_raw(text: str, environment: Optional[Environment] = None):
1602
1604
  PARSER.parse(text)
1603
1605
 
1604
1606
 
1605
- def parse_text(text: str, environment: Optional[Environment] = None) -> Tuple[
1607
+ def parse_text(
1608
+ text: str, environment: Optional[Environment] = None, root: Path | None = None
1609
+ ) -> Tuple[
1606
1610
  Environment,
1607
1611
  List[
1608
1612
  Datasource
@@ -1614,7 +1618,9 @@ def parse_text(text: str, environment: Optional[Environment] = None) -> Tuple[
1614
1618
  | None
1615
1619
  ],
1616
1620
  ]:
1617
- environment = environment or Environment()
1621
+ environment = environment or (
1622
+ Environment(working_path=root) if root else Environment()
1623
+ )
1618
1624
  parser = ParseToObjects(environment=environment)
1619
1625
 
1620
1626
  try:
@@ -1623,7 +1629,7 @@ def parse_text(text: str, environment: Optional[Environment] = None) -> Tuple[
1623
1629
  parser.prepare_parse()
1624
1630
  parser.transform(PARSER.parse(text))
1625
1631
  # this will reset fail on missing
1626
- pass_two = parser.hydrate_missing()
1632
+ pass_two = parser.hydrate_missing(force=True)
1627
1633
  output = [v for v in pass_two if v]
1628
1634
  environment.concepts.fail_on_missing = True
1629
1635
  except VisitError as e:
File without changes
File without changes
File without changes
File without changes
File without changes