pytrilogy 0.3.142__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

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.
Files changed (200) hide show
  1. LICENSE.md +19 -0
  2. _preql_import_resolver/__init__.py +5 -0
  3. _preql_import_resolver/_preql_import_resolver.cpython-313-x86_64-linux-gnu.so +0 -0
  4. pytrilogy-0.3.142.dist-info/METADATA +555 -0
  5. pytrilogy-0.3.142.dist-info/RECORD +200 -0
  6. pytrilogy-0.3.142.dist-info/WHEEL +5 -0
  7. pytrilogy-0.3.142.dist-info/entry_points.txt +2 -0
  8. pytrilogy-0.3.142.dist-info/licenses/LICENSE.md +19 -0
  9. trilogy/__init__.py +16 -0
  10. trilogy/ai/README.md +10 -0
  11. trilogy/ai/__init__.py +19 -0
  12. trilogy/ai/constants.py +92 -0
  13. trilogy/ai/conversation.py +107 -0
  14. trilogy/ai/enums.py +7 -0
  15. trilogy/ai/execute.py +50 -0
  16. trilogy/ai/models.py +34 -0
  17. trilogy/ai/prompts.py +100 -0
  18. trilogy/ai/providers/__init__.py +0 -0
  19. trilogy/ai/providers/anthropic.py +106 -0
  20. trilogy/ai/providers/base.py +24 -0
  21. trilogy/ai/providers/google.py +146 -0
  22. trilogy/ai/providers/openai.py +89 -0
  23. trilogy/ai/providers/utils.py +68 -0
  24. trilogy/authoring/README.md +3 -0
  25. trilogy/authoring/__init__.py +148 -0
  26. trilogy/constants.py +113 -0
  27. trilogy/core/README.md +52 -0
  28. trilogy/core/__init__.py +0 -0
  29. trilogy/core/constants.py +6 -0
  30. trilogy/core/enums.py +443 -0
  31. trilogy/core/env_processor.py +120 -0
  32. trilogy/core/environment_helpers.py +320 -0
  33. trilogy/core/ergonomics.py +193 -0
  34. trilogy/core/exceptions.py +123 -0
  35. trilogy/core/functions.py +1227 -0
  36. trilogy/core/graph_models.py +139 -0
  37. trilogy/core/internal.py +85 -0
  38. trilogy/core/models/__init__.py +0 -0
  39. trilogy/core/models/author.py +2669 -0
  40. trilogy/core/models/build.py +2521 -0
  41. trilogy/core/models/build_environment.py +180 -0
  42. trilogy/core/models/core.py +501 -0
  43. trilogy/core/models/datasource.py +322 -0
  44. trilogy/core/models/environment.py +751 -0
  45. trilogy/core/models/execute.py +1177 -0
  46. trilogy/core/optimization.py +251 -0
  47. trilogy/core/optimizations/__init__.py +12 -0
  48. trilogy/core/optimizations/base_optimization.py +17 -0
  49. trilogy/core/optimizations/hide_unused_concept.py +47 -0
  50. trilogy/core/optimizations/inline_datasource.py +102 -0
  51. trilogy/core/optimizations/predicate_pushdown.py +245 -0
  52. trilogy/core/processing/README.md +94 -0
  53. trilogy/core/processing/READMEv2.md +121 -0
  54. trilogy/core/processing/VIRTUAL_UNNEST.md +30 -0
  55. trilogy/core/processing/__init__.py +0 -0
  56. trilogy/core/processing/concept_strategies_v3.py +508 -0
  57. trilogy/core/processing/constants.py +15 -0
  58. trilogy/core/processing/discovery_node_factory.py +451 -0
  59. trilogy/core/processing/discovery_utility.py +548 -0
  60. trilogy/core/processing/discovery_validation.py +167 -0
  61. trilogy/core/processing/graph_utils.py +43 -0
  62. trilogy/core/processing/node_generators/README.md +9 -0
  63. trilogy/core/processing/node_generators/__init__.py +31 -0
  64. trilogy/core/processing/node_generators/basic_node.py +160 -0
  65. trilogy/core/processing/node_generators/common.py +268 -0
  66. trilogy/core/processing/node_generators/constant_node.py +38 -0
  67. trilogy/core/processing/node_generators/filter_node.py +315 -0
  68. trilogy/core/processing/node_generators/group_node.py +213 -0
  69. trilogy/core/processing/node_generators/group_to_node.py +117 -0
  70. trilogy/core/processing/node_generators/multiselect_node.py +205 -0
  71. trilogy/core/processing/node_generators/node_merge_node.py +653 -0
  72. trilogy/core/processing/node_generators/recursive_node.py +88 -0
  73. trilogy/core/processing/node_generators/rowset_node.py +165 -0
  74. trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  75. trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +261 -0
  76. trilogy/core/processing/node_generators/select_merge_node.py +748 -0
  77. trilogy/core/processing/node_generators/select_node.py +95 -0
  78. trilogy/core/processing/node_generators/synonym_node.py +98 -0
  79. trilogy/core/processing/node_generators/union_node.py +91 -0
  80. trilogy/core/processing/node_generators/unnest_node.py +182 -0
  81. trilogy/core/processing/node_generators/window_node.py +201 -0
  82. trilogy/core/processing/nodes/README.md +28 -0
  83. trilogy/core/processing/nodes/__init__.py +179 -0
  84. trilogy/core/processing/nodes/base_node.py +519 -0
  85. trilogy/core/processing/nodes/filter_node.py +75 -0
  86. trilogy/core/processing/nodes/group_node.py +194 -0
  87. trilogy/core/processing/nodes/merge_node.py +420 -0
  88. trilogy/core/processing/nodes/recursive_node.py +46 -0
  89. trilogy/core/processing/nodes/select_node_v2.py +242 -0
  90. trilogy/core/processing/nodes/union_node.py +53 -0
  91. trilogy/core/processing/nodes/unnest_node.py +62 -0
  92. trilogy/core/processing/nodes/window_node.py +56 -0
  93. trilogy/core/processing/utility.py +823 -0
  94. trilogy/core/query_processor.py +596 -0
  95. trilogy/core/statements/README.md +35 -0
  96. trilogy/core/statements/__init__.py +0 -0
  97. trilogy/core/statements/author.py +536 -0
  98. trilogy/core/statements/build.py +0 -0
  99. trilogy/core/statements/common.py +20 -0
  100. trilogy/core/statements/execute.py +155 -0
  101. trilogy/core/table_processor.py +66 -0
  102. trilogy/core/utility.py +8 -0
  103. trilogy/core/validation/README.md +46 -0
  104. trilogy/core/validation/__init__.py +0 -0
  105. trilogy/core/validation/common.py +161 -0
  106. trilogy/core/validation/concept.py +146 -0
  107. trilogy/core/validation/datasource.py +227 -0
  108. trilogy/core/validation/environment.py +73 -0
  109. trilogy/core/validation/fix.py +256 -0
  110. trilogy/dialect/__init__.py +32 -0
  111. trilogy/dialect/base.py +1392 -0
  112. trilogy/dialect/bigquery.py +308 -0
  113. trilogy/dialect/common.py +147 -0
  114. trilogy/dialect/config.py +144 -0
  115. trilogy/dialect/dataframe.py +50 -0
  116. trilogy/dialect/duckdb.py +231 -0
  117. trilogy/dialect/enums.py +147 -0
  118. trilogy/dialect/metadata.py +173 -0
  119. trilogy/dialect/mock.py +190 -0
  120. trilogy/dialect/postgres.py +117 -0
  121. trilogy/dialect/presto.py +110 -0
  122. trilogy/dialect/results.py +89 -0
  123. trilogy/dialect/snowflake.py +129 -0
  124. trilogy/dialect/sql_server.py +137 -0
  125. trilogy/engine.py +48 -0
  126. trilogy/execution/config.py +75 -0
  127. trilogy/executor.py +568 -0
  128. trilogy/hooks/__init__.py +4 -0
  129. trilogy/hooks/base_hook.py +40 -0
  130. trilogy/hooks/graph_hook.py +139 -0
  131. trilogy/hooks/query_debugger.py +166 -0
  132. trilogy/metadata/__init__.py +0 -0
  133. trilogy/parser.py +10 -0
  134. trilogy/parsing/README.md +21 -0
  135. trilogy/parsing/__init__.py +0 -0
  136. trilogy/parsing/common.py +1069 -0
  137. trilogy/parsing/config.py +5 -0
  138. trilogy/parsing/exceptions.py +8 -0
  139. trilogy/parsing/helpers.py +1 -0
  140. trilogy/parsing/parse_engine.py +2813 -0
  141. trilogy/parsing/render.py +769 -0
  142. trilogy/parsing/trilogy.lark +540 -0
  143. trilogy/py.typed +0 -0
  144. trilogy/render.py +42 -0
  145. trilogy/scripts/README.md +9 -0
  146. trilogy/scripts/__init__.py +0 -0
  147. trilogy/scripts/agent.py +41 -0
  148. trilogy/scripts/agent_info.py +303 -0
  149. trilogy/scripts/common.py +355 -0
  150. trilogy/scripts/dependency/Cargo.lock +617 -0
  151. trilogy/scripts/dependency/Cargo.toml +39 -0
  152. trilogy/scripts/dependency/README.md +131 -0
  153. trilogy/scripts/dependency/build.sh +25 -0
  154. trilogy/scripts/dependency/src/directory_resolver.rs +177 -0
  155. trilogy/scripts/dependency/src/lib.rs +16 -0
  156. trilogy/scripts/dependency/src/main.rs +770 -0
  157. trilogy/scripts/dependency/src/parser.rs +435 -0
  158. trilogy/scripts/dependency/src/preql.pest +208 -0
  159. trilogy/scripts/dependency/src/python_bindings.rs +303 -0
  160. trilogy/scripts/dependency/src/resolver.rs +716 -0
  161. trilogy/scripts/dependency/tests/base.preql +3 -0
  162. trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
  163. trilogy/scripts/dependency/tests/customer.preql +6 -0
  164. trilogy/scripts/dependency/tests/main.preql +9 -0
  165. trilogy/scripts/dependency/tests/orders.preql +7 -0
  166. trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
  167. trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
  168. trilogy/scripts/dependency.py +323 -0
  169. trilogy/scripts/display.py +512 -0
  170. trilogy/scripts/environment.py +46 -0
  171. trilogy/scripts/fmt.py +32 -0
  172. trilogy/scripts/ingest.py +471 -0
  173. trilogy/scripts/ingest_helpers/__init__.py +1 -0
  174. trilogy/scripts/ingest_helpers/foreign_keys.py +123 -0
  175. trilogy/scripts/ingest_helpers/formatting.py +93 -0
  176. trilogy/scripts/ingest_helpers/typing.py +161 -0
  177. trilogy/scripts/init.py +105 -0
  178. trilogy/scripts/parallel_execution.py +713 -0
  179. trilogy/scripts/plan.py +189 -0
  180. trilogy/scripts/run.py +63 -0
  181. trilogy/scripts/serve.py +140 -0
  182. trilogy/scripts/serve_helpers/__init__.py +41 -0
  183. trilogy/scripts/serve_helpers/file_discovery.py +142 -0
  184. trilogy/scripts/serve_helpers/index_generation.py +206 -0
  185. trilogy/scripts/serve_helpers/models.py +38 -0
  186. trilogy/scripts/single_execution.py +131 -0
  187. trilogy/scripts/testing.py +119 -0
  188. trilogy/scripts/trilogy.py +68 -0
  189. trilogy/std/__init__.py +0 -0
  190. trilogy/std/color.preql +3 -0
  191. trilogy/std/date.preql +13 -0
  192. trilogy/std/display.preql +18 -0
  193. trilogy/std/geography.preql +22 -0
  194. trilogy/std/metric.preql +15 -0
  195. trilogy/std/money.preql +67 -0
  196. trilogy/std/net.preql +14 -0
  197. trilogy/std/ranking.preql +7 -0
  198. trilogy/std/report.preql +5 -0
  199. trilogy/std/semantic.preql +6 -0
  200. trilogy/utility.py +34 -0
@@ -0,0 +1,137 @@
1
+ from typing import Any, Callable, Mapping
2
+
3
+ from jinja2 import Template
4
+
5
+ from trilogy.core.enums import FunctionType, WindowType
6
+ from trilogy.core.statements.execute import (
7
+ PROCESSED_STATEMENT_TYPES,
8
+ ProcessedQuery,
9
+ ProcessedQueryPersist,
10
+ )
11
+ from trilogy.dialect.base import BaseDialect
12
+ from trilogy.utility import string_to_hash
13
+
14
+ WINDOW_FUNCTION_MAP: Mapping[WindowType, Callable[[Any, Any, Any], str]] = {}
15
+
16
+ FUNCTION_MAP = {
17
+ FunctionType.COUNT: lambda args, types: f"count({args[0]})",
18
+ FunctionType.SUM: lambda args, types: f"sum({args[0]})",
19
+ FunctionType.AVG: lambda args, types: f"avg({args[0]})",
20
+ FunctionType.LENGTH: lambda args, types: f"length({args[0]})",
21
+ FunctionType.LIKE: lambda args, types: (
22
+ f" CASE WHEN {args[0]} like {args[1]} THEN True ELSE False END"
23
+ ),
24
+ FunctionType.CONCAT: lambda args, types: (
25
+ f"CONCAT({','.join([f''' '{a}' ''' for a in args])})"
26
+ ),
27
+ }
28
+
29
+ # if an aggregate function is called on a source that is at the same grain as the aggregate
30
+ # we may return a static value
31
+ FUNCTION_GRAIN_MATCH_MAP = {
32
+ **FUNCTION_MAP,
33
+ FunctionType.COUNT_DISTINCT: lambda args, types: f"CASE WHEN{args[0]} IS NOT NULL THEN 1 ELSE 0 END",
34
+ FunctionType.COUNT: lambda args, types: f"CASE WHEN {args[0]} IS NOT NULL THEN 1 ELSE 0 END",
35
+ FunctionType.SUM: lambda args, types: f"{args[0]}",
36
+ FunctionType.AVG: lambda args, types: f"{args[0]}",
37
+ }
38
+
39
+ TSQL_TEMPLATE = Template(
40
+ """{%- if ctes %}
41
+ WITH {% for cte in ctes %}
42
+ {{cte.name}} as ({{cte.statement}}){% if not loop.last %},{% endif %}{% endfor %}{% endif %}
43
+ {%- if full_select -%}{{full_select}}
44
+ {%- else -%}{%- if comment %}
45
+ -- {{ comment }}{%- endif -%}
46
+ SELECT
47
+ {%- if limit is not none %}
48
+ TOP {{ limit }}{% endif %}
49
+ {%- for select in select_columns %}
50
+ {{ select }}{% if not loop.last %},{% endif %}{% endfor %}
51
+ {% if base %}FROM
52
+ {{ base }}{% endif %}{% if joins %}
53
+ {% for join in joins %}
54
+ {{ join }}
55
+ {% endfor %}{% endif %}
56
+ {% if where %}WHERE
57
+ {{ where }}
58
+ {% endif %}
59
+ {%- if group_by %}
60
+ GROUP BY {% for group in group_by %}
61
+ {{group}}{% if not loop.last %},{% endif %}
62
+ {% endfor %}{% endif %}{% if having %}
63
+ HAVING
64
+ \t{{ having }}{% endif %}
65
+ {%- if order_by %}
66
+ ORDER BY {% for order in order_by %}
67
+ {{ order }}{% if not loop.last %},{% endif %}
68
+ {% endfor %}{% endif %}{% endif %}
69
+ """
70
+ )
71
+
72
+ MAX_IDENTIFIER_LENGTH = 128
73
+
74
+
75
+ class SqlServerDialect(BaseDialect):
76
+ WINDOW_FUNCTION_MAP = {**BaseDialect.WINDOW_FUNCTION_MAP, **WINDOW_FUNCTION_MAP}
77
+ FUNCTION_MAP = {**BaseDialect.FUNCTION_MAP, **FUNCTION_MAP}
78
+ FUNCTION_GRAIN_MATCH_MAP = {
79
+ **BaseDialect.FUNCTION_GRAIN_MATCH_MAP,
80
+ **FUNCTION_GRAIN_MATCH_MAP,
81
+ }
82
+ QUOTE_CHARACTER = '"'
83
+ SQL_TEMPLATE = TSQL_TEMPLATE
84
+
85
+ def get_table_schema(
86
+ self, executor, table_name: str, schema: str | None = None
87
+ ) -> list[tuple]:
88
+ """Defaults to 'dbo' schema if none specified."""
89
+ if not schema:
90
+ schema = "dbo"
91
+
92
+ column_query = f"""
93
+ SELECT
94
+ column_name,
95
+ data_type,
96
+ is_nullable,
97
+ '' as column_comment
98
+ FROM information_schema.columns
99
+ WHERE table_name = '{table_name}'
100
+ AND table_schema = '{schema}'
101
+ ORDER BY ordinal_position
102
+ """
103
+
104
+ rows = executor.execute_raw_sql(column_query).fetchall()
105
+ return rows
106
+
107
+ def get_table_primary_keys(
108
+ self, executor, table_name: str, schema: str | None = None
109
+ ) -> list[str]:
110
+ """Uses sys catalog views for more reliable constraint information."""
111
+ if not schema:
112
+ schema = "dbo"
113
+
114
+ pk_query = f"""
115
+ SELECT c.name
116
+ FROM sys.indexes i
117
+ INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
118
+ INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
119
+ INNER JOIN sys.tables t ON i.object_id = t.object_id
120
+ INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
121
+ WHERE i.is_primary_key = 1
122
+ AND t.name = '{table_name}'
123
+ AND s.name = '{schema}'
124
+ ORDER BY ic.key_ordinal
125
+ """
126
+
127
+ rows = executor.execute_raw_sql(pk_query).fetchall()
128
+ return [row[0] for row in rows]
129
+
130
+ def compile_statement(self, query: PROCESSED_STATEMENT_TYPES) -> str:
131
+ base = super().compile_statement(query)
132
+ if isinstance(query, (ProcessedQuery, ProcessedQueryPersist)):
133
+ for cte in query.ctes:
134
+ if len(cte.name) > MAX_IDENTIFIER_LENGTH:
135
+ new_name = f"rhash_{string_to_hash(cte.name)}"
136
+ base = base.replace(cte.name, new_name)
137
+ return base
trilogy/engine.py ADDED
@@ -0,0 +1,48 @@
1
+ from typing import Any, Generator, List, Optional, Protocol
2
+
3
+ from trilogy.core.models.environment import Environment
4
+
5
+
6
+ class ResultProtocol(Protocol):
7
+
8
+ def fetchall(self) -> List[Any]: ...
9
+
10
+ def keys(self) -> List[str]: ...
11
+
12
+ def fetchone(self) -> Optional[Any]: ...
13
+
14
+ def fetchmany(self, size: int) -> List[Any]: ...
15
+
16
+ def __iter__(self) -> Generator[Any, None, None]: ...
17
+
18
+
19
+ class EngineConnection(Protocol):
20
+ pass
21
+
22
+ def execute(self, statement: str, parameters: Any | None = None) -> ResultProtocol:
23
+ pass
24
+
25
+ def commit(self):
26
+ raise NotImplementedError()
27
+
28
+ def begin(self):
29
+ raise NotImplementedError()
30
+
31
+ def rollback(self):
32
+ raise NotImplementedError()
33
+
34
+ def close(self) -> None:
35
+ return
36
+
37
+
38
+ class ExecutionEngine(Protocol):
39
+ pass
40
+
41
+ def connect(self) -> EngineConnection:
42
+ pass
43
+
44
+ def setup(self, env: Environment, connection):
45
+ pass
46
+
47
+ def dispose(self, close: bool = True):
48
+ pass
@@ -0,0 +1,75 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+ from tomllib import loads
5
+
6
+ from trilogy.dialect import (
7
+ BigQueryConfig,
8
+ DialectConfig,
9
+ DuckDBConfig,
10
+ PostgresConfig,
11
+ PrestoConfig,
12
+ SnowflakeConfig,
13
+ SQLServerConfig,
14
+ )
15
+ from trilogy.dialect.enums import Dialects
16
+
17
+ DEFAULT_PARALLELISM = 4
18
+
19
+
20
+ @dataclass
21
+ class RuntimeConfig:
22
+
23
+ startup_trilogy: list[Path]
24
+ startup_sql: list[Path]
25
+ parallelism: int = DEFAULT_PARALLELISM
26
+ engine_dialect: Dialects | None = None
27
+ engine_config: DialectConfig | None = None
28
+
29
+
30
+ def load_config_file(path: Path) -> RuntimeConfig:
31
+ with open(path, "r") as f:
32
+ toml_content = f.read()
33
+ config_data = loads(toml_content)
34
+
35
+ engine_raw: dict = config_data.get("engine", {})
36
+ engine_config_raw = engine_raw.get("config", {})
37
+ engine = Dialects(engine_raw.get("dialect")) if engine_raw.get("dialect") else None
38
+ engine_config: DialectConfig | None
39
+ if engine:
40
+ if engine == Dialects.DUCK_DB:
41
+ engine_config = (
42
+ DuckDBConfig(**engine_config_raw) if engine_config_raw else None
43
+ )
44
+ elif engine == Dialects.POSTGRES:
45
+ engine_config = (
46
+ PostgresConfig(**engine_config_raw) if engine_config_raw else None
47
+ )
48
+ elif engine == Dialects.PRESTO:
49
+ engine_config = (
50
+ PrestoConfig(**engine_config_raw) if engine_config_raw else None
51
+ )
52
+ elif engine == Dialects.SNOWFLAKE:
53
+ engine_config = (
54
+ SnowflakeConfig(**engine_config_raw) if engine_config_raw else None
55
+ )
56
+ elif engine == Dialects.SQL_SERVER:
57
+ engine_config = (
58
+ SQLServerConfig(**engine_config_raw) if engine_config_raw else None
59
+ )
60
+ elif engine == Dialects.BIGQUERY:
61
+ engine_config = (
62
+ BigQueryConfig(**engine_config_raw) if engine_config_raw else None
63
+ )
64
+ else:
65
+ engine_config = None
66
+ else:
67
+ engine_config = None
68
+ setup: dict = config_data.get("setup", {})
69
+ return RuntimeConfig(
70
+ startup_trilogy=[path.parent / p for p in setup.get("trilogy", [])],
71
+ startup_sql=[path.parent / p for p in setup.get("sql", [])],
72
+ parallelism=config_data.get("parallelism", DEFAULT_PARALLELISM),
73
+ engine_dialect=engine,
74
+ engine_config=engine_config,
75
+ )