pytrilogy 0.0.3.90__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.
- {pytrilogy-0.0.3.90/pytrilogy.egg-info → pytrilogy-0.0.3.92}/PKG-INFO +9 -6
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/README.md +8 -5
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92/pytrilogy.egg-info}/PKG-INFO +9 -6
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_user_functions.py +19 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/env_processor.py +31 -9
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/graph_models.py +11 -1
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/models/author.py +48 -15
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/models/build.py +189 -42
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/models/core.py +12 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/node_merge_node.py +25 -23
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/parsing/common.py +50 -29
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/parsing/parse_engine.py +22 -3
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/parsing/trilogy.lark +3 -1
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/pyproject.toml +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/setup.cfg +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/setup.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_execute_models.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_failure.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_models.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_parsing_failures.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_query_render.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_select.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_show.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_typing.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/authoring/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/models/build_environment.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/models/datasource.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/models/environment.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/models/execute.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/concept_strategies_v3.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/discovery_loop.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/discovery_node_factory.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/discovery_utility.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/discovery_validation.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/constant_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/recursive_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/statements/author.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/statements/execute.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/core/utility.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/base.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/render.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/std/__init__.py +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/std/date.preql +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/std/display.preql +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/std/geography.preql +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/std/money.preql +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/std/net.preql +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/std/ranking.preql +0 -0
- {pytrilogy-0.0.3.90 → pytrilogy-0.0.3.92}/trilogy/std/report.preql +0 -0
- {pytrilogy-0.0.3.90 → 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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -8,26 +8,34 @@ from trilogy.core.models.build_environment import BuildEnvironment
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def add_concept(
|
|
11
|
-
concept: 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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
489
|
-
|
|
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
|
-
|
|
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
|
|
562
|
-
return
|
|
563
|
-
return
|
|
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
|
|
588
|
+
def _gen_str(self) -> str:
|
|
585
589
|
if self.abstract:
|
|
586
590
|
base = "Grain<Abstract>"
|
|
587
591
|
else:
|
|
588
|
-
base = "Grain<" + ",".join(
|
|
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
|
|
@@ -1666,7 +1675,6 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
|
|
|
1666
1675
|
def parse_output_datatype(cls, v, info: ValidationInfo):
|
|
1667
1676
|
values = info.data
|
|
1668
1677
|
if values.get("operator") == FunctionType.ATTR_ACCESS:
|
|
1669
|
-
print(v)
|
|
1670
1678
|
if isinstance(v, StructType):
|
|
1671
1679
|
raise SyntaxError
|
|
1672
1680
|
return v
|
|
@@ -2335,11 +2343,16 @@ class AlignItem(Namespaced, BaseModel):
|
|
|
2335
2343
|
|
|
2336
2344
|
class CustomFunctionFactory:
|
|
2337
2345
|
def __init__(
|
|
2338
|
-
self,
|
|
2346
|
+
self,
|
|
2347
|
+
function: Expr,
|
|
2348
|
+
namespace: str,
|
|
2349
|
+
function_arguments: list[ArgBinding],
|
|
2350
|
+
name: str,
|
|
2339
2351
|
):
|
|
2340
2352
|
self.namespace = namespace
|
|
2341
2353
|
self.function = function
|
|
2342
2354
|
self.function_arguments = function_arguments
|
|
2355
|
+
self.name = name
|
|
2343
2356
|
|
|
2344
2357
|
def with_namespace(self, namespace: str):
|
|
2345
2358
|
self.namespace = namespace
|
|
@@ -2364,7 +2377,27 @@ class CustomFunctionFactory:
|
|
|
2364
2377
|
for binding in self.function_arguments[len(creation_arg_list) :]:
|
|
2365
2378
|
if binding.default is None:
|
|
2366
2379
|
raise ValueError(f"Missing argument {binding.name}")
|
|
2380
|
+
|
|
2367
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
|
+
|
|
2368
2401
|
if isinstance(nout, Mergeable):
|
|
2369
2402
|
for idx, x in enumerate(creation_arg_list):
|
|
2370
2403
|
if self.namespace == DEFAULT_NAMESPACE:
|