pytrilogy 0.0.3.91__tar.gz → 0.0.3.92__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 (151) hide show
  1. {pytrilogy-0.0.3.91/pytrilogy.egg-info → pytrilogy-0.0.3.92}/PKG-INFO +9 -6
  2. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/README.md +8 -5
  3. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92/pytrilogy.egg-info}/PKG-INFO +9 -6
  4. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_user_functions.py +19 -0
  5. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/__init__.py +1 -1
  6. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/env_processor.py +31 -9
  7. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/graph_models.py +11 -1
  8. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/models/author.py +48 -14
  9. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/models/build.py +189 -42
  10. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/models/core.py +12 -0
  11. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/node_merge_node.py +25 -23
  12. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/parsing/common.py +50 -29
  13. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/parsing/parse_engine.py +22 -3
  14. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/parsing/trilogy.lark +3 -1
  15. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/LICENSE.md +0 -0
  16. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/pyproject.toml +0 -0
  17. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/pytrilogy.egg-info/SOURCES.txt +0 -0
  18. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/pytrilogy.egg-info/dependency_links.txt +0 -0
  19. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/pytrilogy.egg-info/entry_points.txt +0 -0
  20. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/pytrilogy.egg-info/requires.txt +0 -0
  21. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/pytrilogy.egg-info/top_level.txt +0 -0
  22. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/setup.cfg +0 -0
  23. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/setup.py +0 -0
  24. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_datatypes.py +0 -0
  25. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_declarations.py +0 -0
  26. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_derived_concepts.py +0 -0
  27. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_discovery_nodes.py +0 -0
  28. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_enums.py +0 -0
  29. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_environment.py +0 -0
  30. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_execute_models.py +0 -0
  31. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_executor.py +0 -0
  32. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_failure.py +0 -0
  33. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_functions.py +0 -0
  34. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_imports.py +0 -0
  35. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_metadata.py +0 -0
  36. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_models.py +0 -0
  37. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_multi_join_assignments.py +0 -0
  38. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_parse_engine.py +0 -0
  39. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_parsing.py +0 -0
  40. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_parsing_failures.py +0 -0
  41. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_partial_handling.py +0 -0
  42. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_query_processing.py +0 -0
  43. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_query_render.py +0 -0
  44. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_select.py +0 -0
  45. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_show.py +0 -0
  46. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_statements.py +0 -0
  47. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_typing.py +0 -0
  48. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_undefined_concept.py +0 -0
  49. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/tests/test_where_clause.py +0 -0
  50. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/authoring/__init__.py +0 -0
  51. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/compiler.py +0 -0
  52. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/constants.py +0 -0
  53. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/__init__.py +0 -0
  54. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/constants.py +0 -0
  55. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/enums.py +0 -0
  56. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/environment_helpers.py +0 -0
  57. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/ergonomics.py +0 -0
  58. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/exceptions.py +0 -0
  59. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/functions.py +0 -0
  60. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/internal.py +0 -0
  61. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/models/__init__.py +0 -0
  62. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/models/build_environment.py +0 -0
  63. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/models/datasource.py +0 -0
  64. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/models/environment.py +0 -0
  65. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/models/execute.py +0 -0
  66. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/optimization.py +0 -0
  67. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/optimizations/__init__.py +0 -0
  68. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/optimizations/base_optimization.py +0 -0
  69. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/optimizations/inline_datasource.py +0 -0
  70. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  71. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/__init__.py +0 -0
  72. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  73. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/discovery_loop.py +0 -0
  74. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/discovery_node_factory.py +0 -0
  75. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/discovery_utility.py +0 -0
  76. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/discovery_validation.py +0 -0
  77. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/graph_utils.py +0 -0
  78. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/__init__.py +0 -0
  79. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  80. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/common.py +0 -0
  81. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/constant_node.py +0 -0
  82. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  83. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/group_node.py +0 -0
  84. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  85. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  86. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  87. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  88. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  89. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  90. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  91. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/select_node.py +0 -0
  92. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  93. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/union_node.py +0 -0
  94. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  95. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/window_node.py +0 -0
  96. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/__init__.py +0 -0
  97. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/base_node.py +0 -0
  98. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/filter_node.py +0 -0
  99. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/group_node.py +0 -0
  100. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/merge_node.py +0 -0
  101. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  102. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  103. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/union_node.py +0 -0
  104. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  105. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/window_node.py +0 -0
  106. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/processing/utility.py +0 -0
  107. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/query_processor.py +0 -0
  108. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/statements/__init__.py +0 -0
  109. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/statements/author.py +0 -0
  110. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/statements/build.py +0 -0
  111. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/statements/common.py +0 -0
  112. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/statements/execute.py +0 -0
  113. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/core/utility.py +0 -0
  114. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/__init__.py +0 -0
  115. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/base.py +0 -0
  116. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/bigquery.py +0 -0
  117. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/common.py +0 -0
  118. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/config.py +0 -0
  119. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/dataframe.py +0 -0
  120. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/duckdb.py +0 -0
  121. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/enums.py +0 -0
  122. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/postgres.py +0 -0
  123. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/presto.py +0 -0
  124. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/snowflake.py +0 -0
  125. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/dialect/sql_server.py +0 -0
  126. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/engine.py +0 -0
  127. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/executor.py +0 -0
  128. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/hooks/__init__.py +0 -0
  129. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/hooks/base_hook.py +0 -0
  130. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/hooks/graph_hook.py +0 -0
  131. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/hooks/query_debugger.py +0 -0
  132. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/metadata/__init__.py +0 -0
  133. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/parser.py +0 -0
  134. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/parsing/__init__.py +0 -0
  135. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/parsing/config.py +0 -0
  136. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/parsing/exceptions.py +0 -0
  137. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/parsing/helpers.py +0 -0
  138. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/parsing/render.py +0 -0
  139. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/py.typed +0 -0
  140. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/render.py +0 -0
  141. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/scripts/__init__.py +0 -0
  142. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/scripts/trilogy.py +0 -0
  143. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/std/__init__.py +0 -0
  144. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/std/date.preql +0 -0
  145. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/std/display.preql +0 -0
  146. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/std/geography.preql +0 -0
  147. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/std/money.preql +0 -0
  148. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/std/net.preql +0 -0
  149. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/std/ranking.preql +0 -0
  150. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/std/report.preql +0 -0
  151. {pytrilogy-0.0.3.91 → pytrilogy-0.0.3.92}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.91
3
+ Version: 0.0.3.92
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -52,12 +52,12 @@ The Trilogy language is an experiment in better SQL for analytics - a streamline
52
52
  - Testability
53
53
  - Easy to use for humans and LLMs
54
54
 
55
- Trilogy is epsecially targeted at data consumption, providing a rich metadata layer that makes visualizing Trilogy easy and expressive.
55
+ Trilogy is especially powerful for data consumption, providing a rich metadata layer that makes creating, interperting, and visualizing queries easy and expressive.
56
56
 
57
57
  > [!TIP]
58
58
  > You can try Trilogy in a [open-source studio](https://trilogydata.dev/trilogy-studio-core/). More details on the language can be found on the [documentation](https://trilogydata.dev/).
59
59
 
60
- Start in the studio to explore Trilogy. For deeper work and integration, `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
60
+ We recommend starting with the studio to explore Trilogy. For integration, `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
61
61
 
62
62
  Installation: `pip install pytrilogy`
63
63
 
@@ -104,10 +104,13 @@ Save the following code in a file named `hello.preql`
104
104
 
105
105
  ```python
106
106
  # semantic model is abstract from data
107
+
108
+ type word string; # types can be used to provide expressive metadata tags that propagate through dataflow
109
+
107
110
  key sentence_id int;
108
- property sentence_id.word_one string; # comments after a definition
109
- property sentence_id.word_two string; # are syntactic sugar for adding
110
- property sentence_id.word_three string; # a description to it
111
+ property sentence_id.word_one string::word; # comments after a definition
112
+ property sentence_id.word_two string::word; # are syntactic sugar for adding
113
+ property sentence_id.word_three string::word; # a description to it
111
114
 
112
115
  # comments in other places are just comments
113
116
 
@@ -14,12 +14,12 @@ The Trilogy language is an experiment in better SQL for analytics - a streamline
14
14
  - Testability
15
15
  - Easy to use for humans and LLMs
16
16
 
17
- Trilogy is epsecially targeted at data consumption, providing a rich metadata layer that makes visualizing Trilogy easy and expressive.
17
+ Trilogy is especially powerful for data consumption, providing a rich metadata layer that makes creating, interperting, and visualizing queries easy and expressive.
18
18
 
19
19
  > [!TIP]
20
20
  > You can try Trilogy in a [open-source studio](https://trilogydata.dev/trilogy-studio-core/). More details on the language can be found on the [documentation](https://trilogydata.dev/).
21
21
 
22
- Start in the studio to explore Trilogy. For deeper work and integration, `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
22
+ We recommend starting with the studio to explore Trilogy. For integration, `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
23
23
 
24
24
  Installation: `pip install pytrilogy`
25
25
 
@@ -66,10 +66,13 @@ Save the following code in a file named `hello.preql`
66
66
 
67
67
  ```python
68
68
  # semantic model is abstract from data
69
+
70
+ type word string; # types can be used to provide expressive metadata tags that propagate through dataflow
71
+
69
72
  key sentence_id int;
70
- property sentence_id.word_one string; # comments after a definition
71
- property sentence_id.word_two string; # are syntactic sugar for adding
72
- property sentence_id.word_three string; # a description to it
73
+ property sentence_id.word_one string::word; # comments after a definition
74
+ property sentence_id.word_two string::word; # are syntactic sugar for adding
75
+ property sentence_id.word_three string::word; # a description to it
73
76
 
74
77
  # comments in other places are just comments
75
78
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.91
3
+ Version: 0.0.3.92
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -52,12 +52,12 @@ The Trilogy language is an experiment in better SQL for analytics - a streamline
52
52
  - Testability
53
53
  - Easy to use for humans and LLMs
54
54
 
55
- Trilogy is epsecially targeted at data consumption, providing a rich metadata layer that makes visualizing Trilogy easy and expressive.
55
+ Trilogy is especially powerful for data consumption, providing a rich metadata layer that makes creating, interperting, and visualizing queries easy and expressive.
56
56
 
57
57
  > [!TIP]
58
58
  > You can try Trilogy in a [open-source studio](https://trilogydata.dev/trilogy-studio-core/). More details on the language can be found on the [documentation](https://trilogydata.dev/).
59
59
 
60
- Start in the studio to explore Trilogy. For deeper work and integration, `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
60
+ We recommend starting with the studio to explore Trilogy. For integration, `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
61
61
 
62
62
  Installation: `pip install pytrilogy`
63
63
 
@@ -104,10 +104,13 @@ Save the following code in a file named `hello.preql`
104
104
 
105
105
  ```python
106
106
  # semantic model is abstract from data
107
+
108
+ type word string; # types can be used to provide expressive metadata tags that propagate through dataflow
109
+
107
110
  key sentence_id int;
108
- property sentence_id.word_one string; # comments after a definition
109
- property sentence_id.word_two string; # are syntactic sugar for adding
110
- property sentence_id.word_three string; # a description to it
111
+ property sentence_id.word_one string::word; # comments after a definition
112
+ property sentence_id.word_two string::word; # are syntactic sugar for adding
113
+ property sentence_id.word_three string::word; # a description to it
111
114
 
112
115
  # comments in other places are just comments
113
116
 
@@ -2,6 +2,7 @@ from pathlib import Path
2
2
 
3
3
  from trilogy import Dialects, Environment
4
4
  from trilogy.core.enums import Derivation, Purpose
5
+ from trilogy.hooks import DebuggingHook
5
6
 
6
7
 
7
8
  def test_user_function_def():
@@ -20,6 +21,24 @@ select @percent_ratio(10, 100) as ratio;
20
21
  assert results.fetchall()[0].ratio == 10.0
21
22
 
22
23
 
24
+ def test_user_function_def_with_default():
25
+ x = Dialects.DUCK_DB.default_executor()
26
+ DebuggingHook()
27
+ results = x.execute_query(
28
+ """
29
+ def percent_ratio(a, b, digits=3) -> round(a / b * 100, digits);
30
+
31
+ select @percent_ratio(21, 1000) as ratio,
32
+ @percent_ratio(21, 1000, 0) as ratio_two;
33
+
34
+ """
35
+ )
36
+
37
+ results = results.fetchall()
38
+ assert results[0].ratio == 2.1
39
+ assert results[0].ratio_two == 2
40
+
41
+
23
42
  def test_user_function_aggregate():
24
43
  x = Dialects.DUCK_DB.default_executor()
25
44
 
@@ -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.91"
7
+ __version__ = "0.0.3.92"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -8,26 +8,34 @@ from trilogy.core.models.build_environment import BuildEnvironment
8
8
 
9
9
 
10
10
  def add_concept(
11
- concept: BuildConcept, g: ReferenceGraph, concept_mapping: dict[str, BuildConcept]
11
+ concept: BuildConcept,
12
+ g: ReferenceGraph,
13
+ concept_mapping: dict[str, BuildConcept],
14
+ default_concept_graph: dict[str, BuildConcept],
15
+ seen: set[str],
12
16
  ):
13
- g.add_node(concept)
17
+
14
18
  # if we have sources, recursively add them
15
19
  node_name = concept_to_node(concept)
20
+ if node_name in seen:
21
+ return
22
+ seen.add(node_name)
23
+ g.add_node(concept)
16
24
  if concept.concept_arguments:
17
25
  for source in concept.concept_arguments:
18
26
  if not isinstance(source, BuildConcept):
19
27
  raise ValueError(
20
28
  f"Invalid non-build concept {source} passed into graph generation from {concept}"
21
29
  )
22
- generic = source.with_default_grain()
23
- add_concept(generic, g, concept_mapping)
30
+ generic = get_default_grain_concept(source, default_concept_graph)
31
+ add_concept(generic, g, concept_mapping, default_concept_graph, seen)
24
32
 
25
33
  g.add_edge(generic, node_name)
26
34
  for ps_address in concept.pseudonyms:
27
35
  if ps_address not in concept_mapping:
28
36
  raise SyntaxError(f"Concept {concept} has invalid pseudonym {ps_address}")
29
37
  pseudonym = concept_mapping[ps_address]
30
- pseudonym = pseudonym.with_default_grain()
38
+ pseudonym = get_default_grain_concept(pseudonym, default_concept_graph)
31
39
  pseudonym_node = concept_to_node(pseudonym)
32
40
  if (pseudonym_node, node_name) in g.edges and (
33
41
  node_name,
@@ -38,16 +46,29 @@ def add_concept(
38
46
  continue
39
47
  g.add_edge(pseudonym_node, node_name, pseudonym=True)
40
48
  g.add_edge(node_name, pseudonym_node, pseudonym=True)
41
- add_concept(pseudonym, g, concept_mapping)
49
+ add_concept(pseudonym, g, concept_mapping, default_concept_graph, seen)
50
+
51
+
52
+ def get_default_grain_concept(
53
+ concept: BuildConcept, default_concept_graph: dict[str, BuildConcept]
54
+ ) -> BuildConcept:
55
+ """Get the default grain concept from the graph."""
56
+ if concept.address in default_concept_graph:
57
+ return default_concept_graph[concept.address]
58
+ default = concept.with_default_grain()
59
+ default_concept_graph[concept.address] = default
60
+ return default
42
61
 
43
62
 
44
63
  def generate_adhoc_graph(
45
64
  concepts: list[BuildConcept],
46
65
  datasources: list[BuildDatasource],
66
+ default_concept_graph: dict[str, BuildConcept],
47
67
  restrict_to_listed: bool = False,
48
68
  ) -> ReferenceGraph:
49
69
  g = ReferenceGraph()
50
70
  concept_mapping = {x.address: x for x in concepts}
71
+ seen: set[str] = set()
51
72
  for concept in concepts:
52
73
  if not isinstance(concept, BuildConcept):
53
74
  raise ValueError(f"Invalid non-build concept {concept}")
@@ -55,7 +76,7 @@ def generate_adhoc_graph(
55
76
  # add all parsed concepts
56
77
  for concept in concepts:
57
78
 
58
- add_concept(concept, g, concept_mapping)
79
+ add_concept(concept, g, concept_mapping, default_concept_graph, seen)
59
80
 
60
81
  for dataset in datasources:
61
82
  node = datasource_to_node(dataset)
@@ -69,7 +90,7 @@ def generate_adhoc_graph(
69
90
  # if there is a key on a table at a different grain
70
91
  # add an FK edge to the canonical source, if it exists
71
92
  # for example, order ID on order product table
72
- default = concept.with_default_grain()
93
+ default = get_default_grain_concept(concept, default_concept_graph)
73
94
  if concept != default:
74
95
  g.add_edge(concept, default)
75
96
  g.add_edge(default, concept)
@@ -79,9 +100,10 @@ def generate_adhoc_graph(
79
100
  def generate_graph(
80
101
  environment: BuildEnvironment,
81
102
  ) -> ReferenceGraph:
82
-
103
+ default_concept_graph: dict[str, BuildConcept] = {}
83
104
  return generate_adhoc_graph(
84
105
  list(environment.concepts.values())
85
106
  + list(environment.alias_origin_lookup.values()),
86
107
  list(environment.datasources.values()),
108
+ default_concept_graph=default_concept_graph,
87
109
  )
@@ -54,7 +54,7 @@ def prune_sources_for_conditions(
54
54
  def concept_to_node(input: BuildConcept) -> str:
55
55
  # if input.purpose == Purpose.METRIC:
56
56
  # return f"c~{input.namespace}.{input.name}@{input.grain}"
57
- return f"c~{input.address}@{input.grain.without_condition()}"
57
+ return f"c~{input.address}@{input.grain.str_no_condition}"
58
58
 
59
59
 
60
60
  def datasource_to_node(input: BuildDatasource) -> str:
@@ -72,11 +72,15 @@ class ReferenceGraph(nx.DiGraph):
72
72
  def add_node(self, node_for_adding, **attr):
73
73
  if isinstance(node_for_adding, BuildConcept):
74
74
  node_name = concept_to_node(node_for_adding)
75
+ # if node_name in self.nodes:
76
+ # return
75
77
  attr["type"] = "concept"
76
78
  attr["concept"] = node_for_adding
77
79
  attr["grain"] = node_for_adding.grain
78
80
  elif isinstance(node_for_adding, BuildDatasource):
79
81
  node_name = datasource_to_node(node_for_adding)
82
+ # if node_name in self.nodes:
83
+ # return
80
84
  attr["type"] = "datasource"
81
85
  attr["ds"] = node_for_adding
82
86
  attr["grain"] = node_for_adding.grain
@@ -91,7 +95,10 @@ class ReferenceGraph(nx.DiGraph):
91
95
  if u_of_edge not in self.nodes:
92
96
  self.add_node(orig)
93
97
  elif isinstance(u_of_edge, BuildDatasource):
98
+ orig = u_of_edge
94
99
  u_of_edge = datasource_to_node(u_of_edge)
100
+ if u_of_edge not in self.nodes:
101
+ self.add_node(orig)
95
102
 
96
103
  if isinstance(v_of_edge, BuildConcept):
97
104
  orig = v_of_edge
@@ -99,5 +106,8 @@ class ReferenceGraph(nx.DiGraph):
99
106
  if v_of_edge not in self.nodes:
100
107
  self.add_node(orig)
101
108
  elif isinstance(v_of_edge, BuildDatasource):
109
+ orig = v_of_edge
102
110
  v_of_edge = datasource_to_node(v_of_edge)
111
+ if v_of_edge not in self.nodes:
112
+ self.add_node(orig)
103
113
  super().add_edge(u_of_edge, v_of_edge, **attr)
@@ -460,6 +460,8 @@ class HavingClause(WhereClause):
460
460
  class Grain(Namespaced, BaseModel):
461
461
  components: set[str] = Field(default_factory=set)
462
462
  where_clause: Optional["WhereClause"] = None
463
+ _str: str | None = None
464
+ _abstract: bool = False
463
465
 
464
466
  def without_condition(self):
465
467
  return Grain(components=self.components)
@@ -484,12 +486,9 @@ class Grain(Namespaced, BaseModel):
484
486
  from trilogy.parsing.common import concepts_to_grain_concepts
485
487
 
486
488
  x = Grain.model_construct(
487
- components={
488
- c.address
489
- for c in concepts_to_grain_concepts(
490
- concepts, environment=environment, local_concepts=local_concepts
491
- )
492
- },
489
+ components=concepts_to_grain_concepts(
490
+ concepts, environment=environment, local_concepts=local_concepts
491
+ ),
493
492
  where_clause=where_clause,
494
493
  )
495
494
 
@@ -550,17 +549,22 @@ class Grain(Namespaced, BaseModel):
550
549
  where_clause=self.where_clause,
551
550
  )
552
551
 
553
- @property
554
- def abstract(self):
552
+ def _gen_abstract(self) -> bool:
555
553
  return not self.components or all(
556
554
  [c.endswith(ALL_ROWS_CONCEPT) for c in self.components]
557
555
  )
558
556
 
557
+ @property
558
+ def abstract(self):
559
+ if not self._abstract:
560
+ self._abstract = self._gen_abstract()
561
+ return self._abstract
562
+
559
563
  def __eq__(self, other: object):
560
564
  if isinstance(other, list):
561
- if not all([isinstance(c, Concept) for c in other]):
562
- return False
563
- return self.components == set([c.address for c in other])
565
+ if all([isinstance(c, Concept) for c in other]):
566
+ return self.components == set([c.address for c in other])
567
+ return False
564
568
  if not isinstance(other, Grain):
565
569
  return False
566
570
  if self.components == other.components:
@@ -581,15 +585,20 @@ class Grain(Namespaced, BaseModel):
581
585
  intersection = self.components.intersection(other.components)
582
586
  return Grain(components=intersection)
583
587
 
584
- def __str__(self):
588
+ def _gen_str(self) -> str:
585
589
  if self.abstract:
586
590
  base = "Grain<Abstract>"
587
591
  else:
588
- base = "Grain<" + ",".join([c for c in sorted(list(self.components))]) + ">"
592
+ base = "Grain<" + ",".join(sorted(self.components)) + ">"
589
593
  if self.where_clause:
590
594
  base += f"|{str(self.where_clause)}"
591
595
  return base
592
596
 
597
+ def __str__(self):
598
+ if not self._str:
599
+ self._str = self._gen_str()
600
+ return self._str
601
+
593
602
  def __radd__(self, other) -> "Grain":
594
603
  if other == 0:
595
604
  return self
@@ -2334,11 +2343,16 @@ class AlignItem(Namespaced, BaseModel):
2334
2343
 
2335
2344
  class CustomFunctionFactory:
2336
2345
  def __init__(
2337
- self, function: Expr, namespace: str, function_arguments: list[ArgBinding]
2346
+ self,
2347
+ function: Expr,
2348
+ namespace: str,
2349
+ function_arguments: list[ArgBinding],
2350
+ name: str,
2338
2351
  ):
2339
2352
  self.namespace = namespace
2340
2353
  self.function = function
2341
2354
  self.function_arguments = function_arguments
2355
+ self.name = name
2342
2356
 
2343
2357
  def with_namespace(self, namespace: str):
2344
2358
  self.namespace = namespace
@@ -2363,7 +2377,27 @@ class CustomFunctionFactory:
2363
2377
  for binding in self.function_arguments[len(creation_arg_list) :]:
2364
2378
  if binding.default is None:
2365
2379
  raise ValueError(f"Missing argument {binding.name}")
2380
+
2366
2381
  creation_arg_list.append(binding.default)
2382
+ for arg_idx, arg in enumerate(self.function_arguments):
2383
+ if not arg.datatype or arg.datatype == DataType.UNKNOWN:
2384
+ continue
2385
+ if arg_idx > len(creation_arg_list):
2386
+ continue
2387
+ comparison = arg_to_datatype(creation_arg_list[arg_idx])
2388
+ if comparison != arg.datatype:
2389
+ raise TypeError(
2390
+ f"Invalid type passed into custom function @{self.name} in position {arg_idx+1} for argument {arg.name}, expected {arg.datatype}, got {comparison}"
2391
+ )
2392
+ if isinstance(arg.datatype, TraitDataType):
2393
+ if not (
2394
+ isinstance(comparison, TraitDataType)
2395
+ and all(x in comparison.traits for x in arg.datatype.traits)
2396
+ ):
2397
+ raise TypeError(
2398
+ f"Invalid argument type passed into custom function @{self.name} in position {arg_idx+1} for argument {arg.name}, expected traits {arg.datatype.traits}, got {comparison}"
2399
+ )
2400
+
2367
2401
  if isinstance(nout, Mergeable):
2368
2402
  for idx, x in enumerate(creation_arg_list):
2369
2403
  if self.namespace == DEFAULT_NAMESPACE: