pytrilogy 0.0.3.96__tar.gz → 0.0.3.97__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 (158) hide show
  1. {pytrilogy-0.0.3.96/pytrilogy.egg-info → pytrilogy-0.0.3.97}/PKG-INFO +16 -2
  2. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/README.md +15 -1
  3. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97/pytrilogy.egg-info}/PKG-INFO +16 -2
  4. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/pytrilogy.egg-info/SOURCES.txt +1 -0
  5. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_parsing.py +49 -0
  6. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/__init__.py +1 -1
  7. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/constants.py +1 -0
  8. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/enums.py +9 -0
  9. pytrilogy-0.0.3.97/trilogy/core/exceptions.py +105 -0
  10. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/validation/common.py +53 -2
  11. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/validation/concept.py +16 -4
  12. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/validation/datasource.py +22 -20
  13. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/validation/environment.py +2 -1
  14. pytrilogy-0.0.3.97/trilogy/core/validation/fix.py +106 -0
  15. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/base.py +2 -1
  16. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/parsing/parse_engine.py +8 -6
  17. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/parsing/render.py +30 -3
  18. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/parsing/trilogy.lark +7 -4
  19. pytrilogy-0.0.3.96/trilogy/core/exceptions.py +0 -51
  20. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/LICENSE.md +0 -0
  21. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/pyproject.toml +0 -0
  22. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/pytrilogy.egg-info/dependency_links.txt +0 -0
  23. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/pytrilogy.egg-info/entry_points.txt +0 -0
  24. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/pytrilogy.egg-info/requires.txt +0 -0
  25. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/pytrilogy.egg-info/top_level.txt +0 -0
  26. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/setup.cfg +0 -0
  27. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/setup.py +0 -0
  28. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_datatypes.py +0 -0
  29. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_declarations.py +0 -0
  30. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_derived_concepts.py +0 -0
  31. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_discovery_nodes.py +0 -0
  32. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_enums.py +0 -0
  33. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_environment.py +0 -0
  34. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_execute_models.py +0 -0
  35. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_executor.py +0 -0
  36. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_failure.py +0 -0
  37. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_functions.py +0 -0
  38. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_imports.py +0 -0
  39. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_metadata.py +0 -0
  40. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_models.py +0 -0
  41. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_multi_join_assignments.py +0 -0
  42. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_parse_engine.py +0 -0
  43. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_parsing_failures.py +0 -0
  44. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_partial_handling.py +0 -0
  45. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_query_processing.py +0 -0
  46. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_query_render.py +0 -0
  47. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_select.py +0 -0
  48. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_show.py +0 -0
  49. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_statements.py +0 -0
  50. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_typing.py +0 -0
  51. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_undefined_concept.py +0 -0
  52. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_user_functions.py +0 -0
  53. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/tests/test_where_clause.py +0 -0
  54. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/authoring/__init__.py +0 -0
  55. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/__init__.py +0 -0
  56. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/constants.py +0 -0
  57. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/env_processor.py +0 -0
  58. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/environment_helpers.py +0 -0
  59. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/ergonomics.py +0 -0
  60. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/functions.py +0 -0
  61. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/graph_models.py +0 -0
  62. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/internal.py +0 -0
  63. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/models/__init__.py +0 -0
  64. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/models/author.py +0 -0
  65. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/models/build.py +0 -0
  66. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/models/build_environment.py +0 -0
  67. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/models/core.py +0 -0
  68. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/models/datasource.py +0 -0
  69. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/models/environment.py +0 -0
  70. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/models/execute.py +0 -0
  71. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/optimization.py +0 -0
  72. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/optimizations/__init__.py +0 -0
  73. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/optimizations/base_optimization.py +0 -0
  74. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/optimizations/inline_datasource.py +0 -0
  75. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  76. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/__init__.py +0 -0
  77. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  78. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/discovery_loop.py +0 -0
  79. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/discovery_node_factory.py +0 -0
  80. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/discovery_utility.py +0 -0
  81. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/discovery_validation.py +0 -0
  82. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/graph_utils.py +0 -0
  83. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/__init__.py +0 -0
  84. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  85. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/common.py +0 -0
  86. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/constant_node.py +0 -0
  87. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  88. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/group_node.py +0 -0
  89. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  90. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  91. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  92. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  93. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  94. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  95. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  96. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  97. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/select_node.py +0 -0
  98. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  99. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/union_node.py +0 -0
  100. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  101. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/node_generators/window_node.py +0 -0
  102. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/nodes/__init__.py +0 -0
  103. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/nodes/base_node.py +0 -0
  104. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/nodes/filter_node.py +0 -0
  105. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/nodes/group_node.py +0 -0
  106. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/nodes/merge_node.py +0 -0
  107. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  108. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  109. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/nodes/union_node.py +0 -0
  110. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  111. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/nodes/window_node.py +0 -0
  112. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/processing/utility.py +0 -0
  113. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/query_processor.py +0 -0
  114. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/statements/__init__.py +0 -0
  115. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/statements/author.py +0 -0
  116. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/statements/build.py +0 -0
  117. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/statements/common.py +0 -0
  118. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/statements/execute.py +0 -0
  119. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/utility.py +0 -0
  120. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/core/validation/__init__.py +0 -0
  121. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/__init__.py +0 -0
  122. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/bigquery.py +0 -0
  123. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/common.py +0 -0
  124. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/config.py +0 -0
  125. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/dataframe.py +0 -0
  126. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/duckdb.py +0 -0
  127. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/enums.py +0 -0
  128. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/metadata.py +0 -0
  129. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/postgres.py +0 -0
  130. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/presto.py +0 -0
  131. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/snowflake.py +0 -0
  132. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/dialect/sql_server.py +0 -0
  133. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/engine.py +0 -0
  134. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/executor.py +0 -0
  135. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/hooks/__init__.py +0 -0
  136. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/hooks/base_hook.py +0 -0
  137. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/hooks/graph_hook.py +0 -0
  138. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/hooks/query_debugger.py +0 -0
  139. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/metadata/__init__.py +0 -0
  140. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/parser.py +0 -0
  141. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/parsing/__init__.py +0 -0
  142. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/parsing/common.py +0 -0
  143. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/parsing/config.py +0 -0
  144. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/parsing/exceptions.py +0 -0
  145. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/parsing/helpers.py +0 -0
  146. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/py.typed +0 -0
  147. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/render.py +0 -0
  148. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/scripts/__init__.py +0 -0
  149. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/scripts/trilogy.py +0 -0
  150. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/std/__init__.py +0 -0
  151. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/std/date.preql +0 -0
  152. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/std/display.preql +0 -0
  153. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/std/geography.preql +0 -0
  154. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/std/money.preql +0 -0
  155. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/std/net.preql +0 -0
  156. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/std/ranking.preql +0 -0
  157. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/std/report.preql +0 -0
  158. {pytrilogy-0.0.3.96 → pytrilogy-0.0.3.97}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.96
3
+ Version: 0.0.3.97
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -334,7 +334,21 @@ from pytrilogy.authoring import Concept, Function, ...
334
334
 
335
335
  Are likely to be unstable. Open an issue if you need to take dependencies on other modules outside those two paths.
336
336
 
337
- ## Trilogy Syntax Reference
337
+ ## MCP/Server
338
+
339
+ Trilogy is straightforward to run as a server/MCP server; the former to generate SQL on demand and integrate into other tools, and MCP
340
+ for full interactive query loops.
341
+
342
+ This makes it easy to integrate Trilogy into existing tools or workflows.
343
+
344
+ You can see examples of both use cases in the trilogy-studio codebase [here](https://github.com/trilogy-data/trilogy-studio-core)
345
+ and install and run an MCP server directly with that codebase.
346
+
347
+ If you're interested in a more fleshed out standalone server or MCP server, please open an issue and we'll prioritize it!
348
+
349
+ ## Trilogy Syntax Reference
350
+
351
+ Not exhaustive - see [documentation](https://trilogydata.dev/) for more details.
338
352
 
339
353
  ### Import
340
354
  ```sql
@@ -296,7 +296,21 @@ from pytrilogy.authoring import Concept, Function, ...
296
296
 
297
297
  Are likely to be unstable. Open an issue if you need to take dependencies on other modules outside those two paths.
298
298
 
299
- ## Trilogy Syntax Reference
299
+ ## MCP/Server
300
+
301
+ Trilogy is straightforward to run as a server/MCP server; the former to generate SQL on demand and integrate into other tools, and MCP
302
+ for full interactive query loops.
303
+
304
+ This makes it easy to integrate Trilogy into existing tools or workflows.
305
+
306
+ You can see examples of both use cases in the trilogy-studio codebase [here](https://github.com/trilogy-data/trilogy-studio-core)
307
+ and install and run an MCP server directly with that codebase.
308
+
309
+ If you're interested in a more fleshed out standalone server or MCP server, please open an issue and we'll prioritize it!
310
+
311
+ ## Trilogy Syntax Reference
312
+
313
+ Not exhaustive - see [documentation](https://trilogydata.dev/) for more details.
300
314
 
301
315
  ### Import
302
316
  ```sql
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.96
3
+ Version: 0.0.3.97
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -334,7 +334,21 @@ from pytrilogy.authoring import Concept, Function, ...
334
334
 
335
335
  Are likely to be unstable. Open an issue if you need to take dependencies on other modules outside those two paths.
336
336
 
337
- ## Trilogy Syntax Reference
337
+ ## MCP/Server
338
+
339
+ Trilogy is straightforward to run as a server/MCP server; the former to generate SQL on demand and integrate into other tools, and MCP
340
+ for full interactive query loops.
341
+
342
+ This makes it easy to integrate Trilogy into existing tools or workflows.
343
+
344
+ You can see examples of both use cases in the trilogy-studio codebase [here](https://github.com/trilogy-data/trilogy-studio-core)
345
+ and install and run an MCP server directly with that codebase.
346
+
347
+ If you're interested in a more fleshed out standalone server or MCP server, please open an issue and we'll prioritize it!
348
+
349
+ ## Trilogy Syntax Reference
350
+
351
+ Not exhaustive - see [documentation](https://trilogydata.dev/) for more details.
338
352
 
339
353
  ### Import
340
354
  ```sql
@@ -116,6 +116,7 @@ trilogy/core/validation/common.py
116
116
  trilogy/core/validation/concept.py
117
117
  trilogy/core/validation/datasource.py
118
118
  trilogy/core/validation/environment.py
119
+ trilogy/core/validation/fix.py
119
120
  trilogy/dialect/__init__.py
120
121
  trilogy/dialect/base.py
121
122
  trilogy/dialect/bigquery.py
@@ -321,6 +321,55 @@ def test_the_comments():
321
321
  assert rendered == "null"
322
322
 
323
323
 
324
+ def test_the_comment_multiline():
325
+ env, parsed = parse_text(
326
+ """const
327
+ # comment here?
328
+ order_id <- 4; # this is the order id
329
+ # order ids are important
330
+
331
+ SELECT
332
+ # TOOD - add in more columns?
333
+ order_id
334
+ WHERE
335
+ # order_id should not be null
336
+ order_id
337
+ # in this comp
338
+ is not
339
+ null; # nulls are the worst
340
+
341
+ """
342
+ )
343
+ parsed[-1]
344
+ assert env.concepts["order_id"].metadata.description is not None
345
+ assert " this is the order id" in env.concepts["order_id"].metadata.description
346
+ assert " order ids are important" in env.concepts["order_id"].metadata.description
347
+
348
+
349
+ def test_the_comment_multiline_enter():
350
+ # we should not associate it as a description if there is a newline
351
+ env, parsed = parse_text(
352
+ """const
353
+ # comment here?
354
+ order_id <- 4;
355
+ # this is the order id
356
+ # order ids are important
357
+
358
+ SELECT
359
+ # TOOD - add in more columns?
360
+ order_id
361
+ WHERE
362
+ # order_id should not be null
363
+ order_id
364
+ # in this comp
365
+ is not
366
+ null; # nulls are the worst
367
+
368
+ """
369
+ )
370
+ assert env.concepts["order_id"].metadata.description is None
371
+
372
+
324
373
  def test_purpose_nesting():
325
374
  env, parsed = parse_text(
326
375
  """key year int;
@@ -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.96"
7
+ __version__ = "0.0.3.97"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -16,6 +16,7 @@ ENV_CACHE_NAME = ".preql_cache.json"
16
16
 
17
17
  class MagicConstants(Enum):
18
18
  NULL = "null"
19
+ LINE_SEPARATOR = "\n"
19
20
 
20
21
 
21
22
  NULL_VALUE = MagicConstants.NULL
@@ -82,6 +82,15 @@ class Modifier(Enum):
82
82
  return Modifier.NULLABLE
83
83
  return super()._missing_(value=strval.capitalize())
84
84
 
85
+ def __lt__(self, other):
86
+ order = [
87
+ Modifier.HIDDEN,
88
+ Modifier.PARTIAL,
89
+ Modifier.NULLABLE,
90
+ Modifier.OPTIONAL,
91
+ ]
92
+ return order.index(self) < order.index(other)
93
+
85
94
 
86
95
  class JoinType(Enum):
87
96
  INNER = "inner"
@@ -0,0 +1,105 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, List, Sequence
3
+
4
+ from trilogy.core.enums import Modifier
5
+ from trilogy.core.models.core import (
6
+ ArrayType,
7
+ DataType,
8
+ MapType,
9
+ NumericType,
10
+ StructType,
11
+ TraitDataType,
12
+ )
13
+
14
+
15
+ class UndefinedConceptException(Exception):
16
+ def __init__(self, message, suggestions: List[str]):
17
+ super().__init__(self, message)
18
+ self.message = message
19
+ self.suggestions = suggestions
20
+
21
+
22
+ class FrozenEnvironmentException(Exception):
23
+ pass
24
+
25
+
26
+ class InvalidSyntaxException(Exception):
27
+ pass
28
+
29
+
30
+ class UnresolvableQueryException(Exception):
31
+ pass
32
+
33
+
34
+ class NoDatasourceException(UnresolvableQueryException):
35
+ pass
36
+
37
+
38
+ class ModelValidationError(Exception):
39
+ def __init__(
40
+ self,
41
+ message,
42
+ children: Sequence["ModelValidationError"] | None = None,
43
+ **kwargs,
44
+ ):
45
+ super().__init__(self, message, **kwargs)
46
+ self.message = message
47
+ self.children = children
48
+
49
+
50
+ class DatasourceModelValidationError(ModelValidationError):
51
+ pass
52
+
53
+
54
+ class DatasourceGrainValidationError(DatasourceModelValidationError):
55
+ pass
56
+
57
+
58
+ @dataclass
59
+ class DatasourceColumnBindingData:
60
+ address: str
61
+ value: Any
62
+ value_type: (
63
+ DataType | ArrayType | StructType | MapType | NumericType | TraitDataType
64
+ )
65
+ value_modifiers: List[Modifier]
66
+ actual_type: (
67
+ DataType | ArrayType | StructType | MapType | NumericType | TraitDataType
68
+ )
69
+ actual_modifiers: List[Modifier]
70
+
71
+ def format_failure(self):
72
+ return f"Concept {self.address} value '{self.value}' with type {self.value_modifiers} does not conform to expected type {str(self.actual_type)} with modifiers {self.actual_modifiers}"
73
+
74
+ def is_modifier_issue(self) -> bool:
75
+ return len(self.value_modifiers) > 0 and any(
76
+ [x not in self.actual_modifiers for x in self.value_modifiers]
77
+ )
78
+
79
+ def is_type_issue(self) -> bool:
80
+ return self.value_type != self.actual_type
81
+
82
+
83
+ class DatasourceColumnBindingError(DatasourceModelValidationError):
84
+ def __init__(
85
+ self,
86
+ address: str,
87
+ errors: list[DatasourceColumnBindingData],
88
+ message: str | None = None,
89
+ ):
90
+ if not message:
91
+ message = f"Datasource {address} failed validation. Found rows that do not conform to types: {[failure.format_failure() for failure in errors]}"
92
+ super().__init__(message)
93
+ self.errors = errors
94
+ self.dataset_address = address
95
+
96
+
97
+ class ConceptModelValidationError(ModelValidationError):
98
+ pass
99
+
100
+
101
+ class AmbiguousRelationshipResolutionException(UnresolvableQueryException):
102
+ def __init__(self, message, parents: List[set[str]]):
103
+ super().__init__(self, message)
104
+ self.message = message
105
+ self.parents = parents
@@ -2,13 +2,25 @@ from dataclasses import dataclass
2
2
  from enum import Enum
3
3
 
4
4
  from trilogy import Environment
5
- from trilogy.authoring import ConceptRef
5
+ from trilogy.authoring import (
6
+ ConceptRef,
7
+ DataType,
8
+ Ordering,
9
+ Purpose,
10
+ )
11
+ from trilogy.constants import MagicConstants
12
+ from trilogy.core.enums import ComparisonOperator, FunctionType
6
13
  from trilogy.core.exceptions import ModelValidationError
7
14
  from trilogy.core.models.build import (
15
+ BuildCaseElse,
16
+ BuildCaseWhen,
8
17
  BuildComparison,
9
18
  BuildConcept,
10
19
  BuildConditional,
11
20
  BuildDatasource,
21
+ BuildFunction,
22
+ BuildOrderBy,
23
+ BuildOrderItem,
12
24
  )
13
25
  from trilogy.core.models.environment import EnvironmentConceptDict
14
26
  from trilogy.core.models.execute import (
@@ -39,6 +51,32 @@ class ValidationType(Enum):
39
51
  CONCEPTS = "concepts"
40
52
 
41
53
 
54
+ def build_order_args(concepts: list[BuildConcept]) -> list[BuildFunction]:
55
+ order_args = []
56
+ for concept in concepts:
57
+ order_args.append(
58
+ BuildFunction(
59
+ operator=FunctionType.CASE,
60
+ arguments=[
61
+ BuildCaseWhen(
62
+ comparison=BuildComparison(
63
+ left=concept,
64
+ operator=ComparisonOperator.IS,
65
+ right=MagicConstants.NULL,
66
+ ),
67
+ expr=1,
68
+ ),
69
+ BuildCaseElse(expr=0),
70
+ ],
71
+ output_data_type=DataType.INTEGER,
72
+ output_purpose=Purpose.PROPERTY,
73
+ arg_count=2,
74
+ )
75
+ )
76
+
77
+ return order_args
78
+
79
+
42
80
  def easy_query(
43
81
  concepts: list[BuildConcept],
44
82
  datasource: BuildDatasource,
@@ -81,7 +119,6 @@ def easy_query(
81
119
  group_to_grain=True,
82
120
  base_alias_override=datasource.safe_identifier,
83
121
  )
84
-
85
122
  filter_cte = CTE(
86
123
  name=f"datasource_{datasource.name}_filter",
87
124
  source=QueryDatasource(
@@ -100,6 +137,20 @@ def easy_query(
100
137
  grain=cte.grain,
101
138
  condition=condition,
102
139
  limit=limit,
140
+ order_by=BuildOrderBy(
141
+ items=[
142
+ BuildOrderItem(
143
+ expr=BuildFunction(
144
+ operator=FunctionType.SUM,
145
+ arguments=build_order_args(concepts),
146
+ output_data_type=DataType.INTEGER,
147
+ output_purpose=Purpose.PROPERTY,
148
+ arg_count=len(concepts),
149
+ ),
150
+ order=Ordering.DESCENDING,
151
+ )
152
+ ]
153
+ ),
103
154
  )
104
155
 
105
156
  return ProcessedQuery(
@@ -1,8 +1,9 @@
1
1
  from trilogy import Environment, Executor
2
- from trilogy.core.enums import Derivation, Purpose
2
+ from trilogy.core.enums import Derivation, Modifier, Purpose
3
3
  from trilogy.core.exceptions import (
4
4
  ConceptModelValidationError,
5
- DatasourceModelValidationError,
5
+ DatasourceColumnBindingData,
6
+ DatasourceColumnBindingError,
6
7
  )
7
8
  from trilogy.core.models.build import (
8
9
  BuildConcept,
@@ -69,8 +70,19 @@ def validate_key_concept(
69
70
  err = None
70
71
  datasource_count: int = seen.get(datasource.name, 0)
71
72
  if datasource_count < max_seen and assignment.is_complete:
72
- err = DatasourceModelValidationError(
73
- f"Key concept {concept.address} is missing values in datasource {datasource.name} (max cardinality in data {max_seen}, datasource has {seen[datasource.name]} values) but is not marked as partial."
73
+ err = DatasourceColumnBindingError(
74
+ address=datasource.identifier,
75
+ errors=[
76
+ DatasourceColumnBindingData(
77
+ address=concept.address,
78
+ value=None,
79
+ value_type=concept.datatype,
80
+ value_modifiers=[Modifier.PARTIAL],
81
+ actual_type=concept.datatype,
82
+ actual_modifiers=concept.modifiers,
83
+ )
84
+ ],
85
+ message=f"Key concept {concept.address} is missing values in datasource {datasource.name} (max cardinality in data {max_seen}, datasource has {seen[datasource.name]} values) but is not marked as partial.",
74
86
  )
75
87
  results.append(
76
88
  ValidationTest(
@@ -10,9 +10,14 @@ from trilogy.authoring import (
10
10
  NumericType,
11
11
  StructType,
12
12
  TraitDataType,
13
+ arg_to_datatype,
14
+ )
15
+ from trilogy.core.enums import ComparisonOperator, Modifier
16
+ from trilogy.core.exceptions import (
17
+ DatasourceColumnBindingData,
18
+ DatasourceColumnBindingError,
19
+ DatasourceModelValidationError,
13
20
  )
14
- from trilogy.core.enums import ComparisonOperator
15
- from trilogy.core.exceptions import DatasourceModelValidationError
16
21
  from trilogy.core.models.build import (
17
22
  BuildComparison,
18
23
  BuildDatasource,
@@ -64,6 +69,7 @@ def validate_datasource(
64
69
  env: Environment,
65
70
  build_env: BuildEnvironment,
66
71
  exec: Executor | None = None,
72
+ fix: bool = False,
67
73
  ) -> list[ValidationTest]:
68
74
  results: list[ValidationTest] = []
69
75
  # we might have merged concepts, where both will map out to the same
@@ -109,14 +115,7 @@ def validate_datasource(
109
115
  )
110
116
  )
111
117
  return results
112
- failures: list[
113
- tuple[
114
- str,
115
- Any,
116
- DataType | ArrayType | StructType | MapType | NumericType | TraitDataType,
117
- bool,
118
- ]
119
- ] = []
118
+ failures: list[DatasourceColumnBindingData] = []
120
119
  cols_with_error = set()
121
120
  for row in rows:
122
121
  for col in datasource.columns:
@@ -127,26 +126,29 @@ def validate_datasource(
127
126
  passed = type_check(rval, col.concept.datatype, col.is_nullable)
128
127
  if not passed:
129
128
  failures.append(
130
- (
131
- col.concept.address,
132
- rval,
133
- col.concept.datatype,
134
- col.is_nullable,
129
+ DatasourceColumnBindingData(
130
+ address=col.concept.address,
131
+ value=rval,
132
+ value_type=(
133
+ arg_to_datatype(rval)
134
+ if rval is not None
135
+ else col.concept.datatype
136
+ ),
137
+ value_modifiers=[Modifier.NULLABLE] if rval is None else [],
138
+ actual_type=col.concept.datatype,
139
+ actual_modifiers=col.concept.modifiers,
135
140
  )
136
141
  )
137
142
  cols_with_error.add(actual_address)
138
143
 
139
- def format_failure(failure):
140
- return f"Concept {failure[0]} value '{failure[1]}' does not conform to expected type {str(failure[2])} (nullable={failure[3]})"
141
-
142
144
  if failures:
143
145
  results.append(
144
146
  ValidationTest(
145
147
  check_type=ExpectationType.LOGICAL,
146
148
  expected="datatype_match",
147
149
  ran=True,
148
- result=DatasourceModelValidationError(
149
- f"Datasource {datasource.name} failed validation. Found rows that do not conform to types: {[format_failure(failure) for failure in failures]}",
150
+ result=DatasourceColumnBindingError(
151
+ address=datasource.identifier, errors=failures
150
152
  ),
151
153
  )
152
154
  )
@@ -15,9 +15,10 @@ def validate_environment(
15
15
  scope: ValidationScope = ValidationScope.ALL,
16
16
  targets: list[str] | None = None,
17
17
  exec: Executor | None = None,
18
+ generate_only: bool = False,
18
19
  ) -> list[ValidationTest]:
19
20
  # avoid mutating the environment for validation
20
- generate_only = exec is None
21
+ generate_only = exec is None or generate_only
21
22
  env = env.duplicate()
22
23
  grain_check = function_to_concept(
23
24
  parent=Function(
@@ -0,0 +1,106 @@
1
+ from collections import defaultdict
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ from trilogy import Environment, Executor
6
+ from trilogy.authoring import ConceptDeclarationStatement, Datasource
7
+ from trilogy.core.exceptions import (
8
+ DatasourceColumnBindingData,
9
+ DatasourceColumnBindingError,
10
+ )
11
+ from trilogy.core.validation.environment import validate_environment
12
+ from trilogy.parsing.render import Renderer
13
+
14
+
15
+ def rewrite_file_with_errors(
16
+ statements: list[Any], errors: list[DatasourceColumnBindingError]
17
+ ):
18
+ renderer = Renderer()
19
+ output = []
20
+ ds_error_map: dict[str, list[DatasourceColumnBindingData]] = defaultdict(list)
21
+ concept_error_map: dict[str, list[DatasourceColumnBindingData]] = defaultdict(list)
22
+ for error in errors:
23
+ if isinstance(error, DatasourceColumnBindingError):
24
+ for x in error.errors:
25
+ if error.dataset_address not in ds_error_map:
26
+ ds_error_map[error.dataset_address] = []
27
+ # this is by dataset address
28
+ if x.is_modifier_issue():
29
+ ds_error_map[error.dataset_address].append(x)
30
+ # this is by column
31
+ if x.is_type_issue():
32
+ concept_error_map[x.address].append(x)
33
+ for statement in statements:
34
+ if isinstance(statement, Datasource):
35
+ if statement.identifier in ds_error_map:
36
+ error_cols = ds_error_map[statement.identifier]
37
+ for col in statement.columns:
38
+ if col.concept.address in [x.address for x in error_cols]:
39
+ error_col = [
40
+ x for x in error_cols if x.address == col.concept.address
41
+ ][0]
42
+ col.modifiers = list(
43
+ set(col.modifiers + error_col.value_modifiers)
44
+ )
45
+ elif isinstance(statement, ConceptDeclarationStatement):
46
+ if statement.concept.address in concept_error_map:
47
+ error_cols = concept_error_map[statement.concept.address]
48
+ statement.concept.datatype = error_cols[0].value_type
49
+ output.append(statement)
50
+
51
+ return renderer.render_statement_string(output)
52
+
53
+
54
+ DEPTH_CUTOFF = 3
55
+
56
+
57
+ def validate_and_rewrite(
58
+ input: Path | str, exec: Executor | None = None, depth: int = 0
59
+ ) -> str | None:
60
+ if depth > DEPTH_CUTOFF:
61
+ print(f"Reached depth cutoff of {DEPTH_CUTOFF}, stopping.")
62
+ return None
63
+ if isinstance(input, str):
64
+ raw = input
65
+ env = Environment()
66
+ else:
67
+ with open(input, "r") as f:
68
+ raw = f.read()
69
+ env = Environment(working_path=input.parent)
70
+ if exec:
71
+ env = exec.environment
72
+ env, statements = env.parse(raw)
73
+
74
+ validation_results = validate_environment(env, exec=exec, generate_only=True)
75
+
76
+ errors = [
77
+ x.result
78
+ for x in validation_results
79
+ if isinstance(x.result, DatasourceColumnBindingError)
80
+ ]
81
+
82
+ if not errors:
83
+ print("No validation errors found")
84
+ return None
85
+ print(
86
+ f"Found {len(errors)} validation errors, attempting to fix, current depth: {depth}..."
87
+ )
88
+ for error in errors:
89
+ for item in error.errors:
90
+ print(f"- {item.format_failure()}")
91
+
92
+ new_text = rewrite_file_with_errors(statements, errors)
93
+
94
+ while iteration := validate_and_rewrite(new_text, exec=exec, depth=depth + 1):
95
+ depth = depth + 1
96
+ if depth >= DEPTH_CUTOFF:
97
+ break
98
+ if iteration:
99
+ new_text = iteration
100
+ depth += 1
101
+ if isinstance(input, Path):
102
+ with open(input, "w") as f:
103
+ f.write(new_text)
104
+ return None
105
+ else:
106
+ return new_text
@@ -761,6 +761,7 @@ class BaseDialect:
761
761
  elif isinstance(e, MagicConstants):
762
762
  if e == MagicConstants.NULL:
763
763
  return "null"
764
+ return str(e.value)
764
765
  elif isinstance(e, date):
765
766
  return self.FUNCTION_MAP[FunctionType.DATE_LITERAL](e)
766
767
  elif isinstance(e, datetime):
@@ -1139,7 +1140,7 @@ class BaseDialect:
1139
1140
  if isinstance(query, ProcessedShowStatement):
1140
1141
  return ";\n".join(
1141
1142
  [
1142
- f'{self.EXPLAIN_KEYWORD} {self.compile_statement(x)}'
1143
+ f"{self.EXPLAIN_KEYWORD} {self.compile_statement(x)}"
1143
1144
  for x in query.output_values
1144
1145
  if isinstance(x, (ProcessedQuery, ProcessedCopyStatement))
1145
1146
  ]
@@ -379,14 +379,16 @@ class ParseToObjects(Transformer):
379
379
  def start(self, args):
380
380
  return args
381
381
 
382
+ def LINE_SEPARATOR(self, args):
383
+ return MagicConstants.LINE_SEPARATOR
384
+
382
385
  def block(self, args):
383
386
  output = args[0]
384
387
  if isinstance(output, ConceptDeclarationStatement):
385
- if len(args) > 1 and isinstance(args[1], Comment):
386
- output.concept.metadata.description = (
387
- output.concept.metadata.description
388
- or args[1].text.split("#")[1].strip()
389
- )
388
+ if len(args) > 1 and args[1] != MagicConstants.LINE_SEPARATOR:
389
+ comments = [x for x in args[1:] if isinstance(x, Comment)]
390
+ merged = "\n".join([x.text.split("#")[1].rstrip() for x in comments])
391
+ output.concept.metadata.description = merged
390
392
  # this is a bad plan for now;
391
393
  # because a comment after an import statement is very common
392
394
  # and it's not intuitive that it modifies the import description
@@ -913,7 +915,7 @@ class ParseToObjects(Transformer):
913
915
  return Comment(text=args[0].value)
914
916
 
915
917
  def PARSE_COMMENT(self, args):
916
- return Comment(text=args.value)
918
+ return Comment(text=args.value.rstrip())
917
919
 
918
920
  @v_args(meta=True)
919
921
  def select_transform(self, meta: Meta, args) -> ConceptTransform: