pytrilogy 0.0.2.47__py3-none-any.whl → 0.0.2.49__py3-none-any.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.

Potentially problematic release.


This version of pytrilogy might be problematic. Click here for more details.

Files changed (69) hide show
  1. {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.49.dist-info}/METADATA +1 -1
  2. pytrilogy-0.0.2.49.dist-info/RECORD +85 -0
  3. trilogy/__init__.py +2 -2
  4. trilogy/constants.py +4 -2
  5. trilogy/core/enums.py +7 -1
  6. trilogy/core/env_processor.py +1 -2
  7. trilogy/core/environment_helpers.py +5 -5
  8. trilogy/core/functions.py +11 -10
  9. trilogy/core/internal.py +2 -3
  10. trilogy/core/models.py +449 -393
  11. trilogy/core/optimization.py +37 -21
  12. trilogy/core/optimizations/__init__.py +1 -1
  13. trilogy/core/optimizations/base_optimization.py +6 -6
  14. trilogy/core/optimizations/inline_constant.py +7 -4
  15. trilogy/core/optimizations/inline_datasource.py +14 -5
  16. trilogy/core/optimizations/predicate_pushdown.py +20 -10
  17. trilogy/core/processing/concept_strategies_v3.py +43 -24
  18. trilogy/core/processing/graph_utils.py +2 -3
  19. trilogy/core/processing/node_generators/__init__.py +7 -5
  20. trilogy/core/processing/node_generators/basic_node.py +4 -4
  21. trilogy/core/processing/node_generators/common.py +10 -11
  22. trilogy/core/processing/node_generators/filter_node.py +7 -9
  23. trilogy/core/processing/node_generators/group_node.py +10 -11
  24. trilogy/core/processing/node_generators/group_to_node.py +5 -5
  25. trilogy/core/processing/node_generators/multiselect_node.py +10 -12
  26. trilogy/core/processing/node_generators/node_merge_node.py +7 -9
  27. trilogy/core/processing/node_generators/rowset_node.py +36 -15
  28. trilogy/core/processing/node_generators/select_merge_node.py +11 -10
  29. trilogy/core/processing/node_generators/select_node.py +5 -5
  30. trilogy/core/processing/node_generators/union_node.py +75 -0
  31. trilogy/core/processing/node_generators/unnest_node.py +2 -3
  32. trilogy/core/processing/node_generators/window_node.py +3 -4
  33. trilogy/core/processing/nodes/__init__.py +9 -5
  34. trilogy/core/processing/nodes/base_node.py +45 -13
  35. trilogy/core/processing/nodes/filter_node.py +3 -4
  36. trilogy/core/processing/nodes/group_node.py +17 -13
  37. trilogy/core/processing/nodes/merge_node.py +14 -12
  38. trilogy/core/processing/nodes/select_node_v2.py +13 -9
  39. trilogy/core/processing/nodes/union_node.py +50 -0
  40. trilogy/core/processing/nodes/unnest_node.py +2 -3
  41. trilogy/core/processing/nodes/window_node.py +2 -3
  42. trilogy/core/processing/utility.py +38 -41
  43. trilogy/core/query_processor.py +71 -51
  44. trilogy/dialect/base.py +95 -53
  45. trilogy/dialect/bigquery.py +2 -3
  46. trilogy/dialect/common.py +5 -4
  47. trilogy/dialect/config.py +0 -2
  48. trilogy/dialect/duckdb.py +2 -2
  49. trilogy/dialect/enums.py +5 -5
  50. trilogy/dialect/postgres.py +2 -2
  51. trilogy/dialect/presto.py +3 -4
  52. trilogy/dialect/snowflake.py +2 -2
  53. trilogy/dialect/sql_server.py +3 -4
  54. trilogy/engine.py +2 -1
  55. trilogy/executor.py +43 -30
  56. trilogy/hooks/base_hook.py +5 -4
  57. trilogy/hooks/graph_hook.py +2 -1
  58. trilogy/hooks/query_debugger.py +18 -8
  59. trilogy/parsing/common.py +15 -20
  60. trilogy/parsing/parse_engine.py +125 -88
  61. trilogy/parsing/render.py +32 -35
  62. trilogy/parsing/trilogy.lark +8 -1
  63. trilogy/scripts/trilogy.py +6 -4
  64. trilogy/utility.py +1 -1
  65. pytrilogy-0.0.2.47.dist-info/RECORD +0 -83
  66. {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.49.dist-info}/LICENSE.md +0 -0
  67. {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.49.dist-info}/WHEEL +0 -0
  68. {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.49.dist-info}/entry_points.txt +0 -0
  69. {pytrilogy-0.0.2.47.dist-info → pytrilogy-0.0.2.49.dist-info}/top_level.txt +0 -0
trilogy/dialect/common.py CHANGED
@@ -1,13 +1,14 @@
1
+ from typing import Callable
2
+
3
+ from trilogy.core.enums import Modifier, UnnestMode
1
4
  from trilogy.core.models import (
2
- Join,
3
- InstantiatedUnnestJoin,
4
5
  CTE,
5
6
  Concept,
6
7
  Function,
8
+ InstantiatedUnnestJoin,
9
+ Join,
7
10
  RawColumnExpr,
8
11
  )
9
- from trilogy.core.enums import UnnestMode, Modifier
10
- from typing import Callable
11
12
 
12
13
 
13
14
  def null_wrapper(lval: str, rval: str, modifiers: list[Modifier]) -> str:
trilogy/dialect/config.py CHANGED
@@ -1,5 +1,4 @@
1
1
  class DialectConfig:
2
-
3
2
  def __init__(self):
4
3
  pass
5
4
 
@@ -101,7 +100,6 @@ class PrestoConfig(DialectConfig):
101
100
 
102
101
 
103
102
  class TrinoConfig(PrestoConfig):
104
-
105
103
  def connection_string(self) -> str:
106
104
  if self.schema:
107
105
  return f"trino://{self.username}:{self.password}@{self.host}:{self.port}/{self.catalog}/{self.schema}"
trilogy/dialect/duckdb.py CHANGED
@@ -1,8 +1,8 @@
1
- from typing import Mapping, Callable, Any
1
+ from typing import Any, Callable, Mapping
2
2
 
3
3
  from jinja2 import Template
4
4
 
5
- from trilogy.core.enums import FunctionType, WindowType, UnnestMode
5
+ from trilogy.core.enums import FunctionType, UnnestMode, WindowType
6
6
  from trilogy.dialect.base import BaseDialect
7
7
 
8
8
  WINDOW_FUNCTION_MAP: Mapping[WindowType, Callable[[Any, Any, Any], str]] = {}
trilogy/dialect/enums.py CHANGED
@@ -1,12 +1,12 @@
1
1
  from enum import Enum
2
- from typing import List, TYPE_CHECKING, Optional, Callable
2
+ from typing import TYPE_CHECKING, Callable, List, Optional
3
3
 
4
4
  if TYPE_CHECKING:
5
+ from trilogy import Environment, Executor
5
6
  from trilogy.hooks.base_hook import BaseHook
6
- from trilogy import Executor, Environment
7
7
 
8
- from trilogy.dialect.config import DialectConfig
9
8
  from trilogy.constants import logger
9
+ from trilogy.dialect.config import DialectConfig
10
10
 
11
11
 
12
12
  def default_factory(conf: DialectConfig, config_type):
@@ -42,6 +42,7 @@ class Dialects(Enum):
42
42
  if self == Dialects.BIGQUERY:
43
43
  from google.auth import default
44
44
  from google.cloud import bigquery
45
+
45
46
  from trilogy.dialect.config import BigQueryConfig
46
47
 
47
48
  credentials, project = default()
@@ -52,7 +53,6 @@ class Dialects(Enum):
52
53
  BigQueryConfig,
53
54
  )
54
55
  elif self == Dialects.SQL_SERVER:
55
-
56
56
  raise NotImplementedError()
57
57
  elif self == Dialects.DUCK_DB:
58
58
  from trilogy.dialect.config import DuckDBConfig
@@ -98,7 +98,7 @@ class Dialects(Enum):
98
98
  conf: DialectConfig | None = None,
99
99
  _engine_factory: Callable | None = None,
100
100
  ) -> "Executor":
101
- from trilogy import Executor, Environment
101
+ from trilogy import Environment, Executor
102
102
 
103
103
  if _engine_factory is not None:
104
104
  return Executor(
@@ -1,8 +1,8 @@
1
- from typing import Mapping, Callable, Any
1
+ from typing import Any, Callable, Mapping
2
2
 
3
3
  from jinja2 import Template
4
4
 
5
- from trilogy.core.enums import FunctionType, WindowType, DatePart
5
+ from trilogy.core.enums import DatePart, FunctionType, WindowType
6
6
  from trilogy.dialect.base import BaseDialect
7
7
 
8
8
 
trilogy/dialect/presto.py CHANGED
@@ -1,11 +1,10 @@
1
- from typing import Mapping, Callable, Any
1
+ from typing import Any, Callable, Mapping
2
2
 
3
3
  from jinja2 import Template
4
4
 
5
- from trilogy.core.enums import FunctionType, WindowType
6
- from trilogy.dialect.base import BaseDialect
5
+ from trilogy.core.enums import FunctionType, UnnestMode, WindowType
7
6
  from trilogy.core.models import DataType
8
- from trilogy.core.enums import UnnestMode
7
+ from trilogy.dialect.base import BaseDialect
9
8
 
10
9
  WINDOW_FUNCTION_MAP: Mapping[WindowType, Callable[[Any, Any, Any], str]] = {}
11
10
 
@@ -1,8 +1,8 @@
1
- from typing import Mapping, Callable, Any
1
+ from typing import Any, Callable, Mapping
2
2
 
3
3
  from jinja2 import Template
4
4
 
5
- from trilogy.core.enums import FunctionType, WindowType, UnnestMode
5
+ from trilogy.core.enums import FunctionType, UnnestMode, WindowType
6
6
  from trilogy.dialect.base import BaseDialect
7
7
 
8
8
  ENV_SNOWFLAKE_PW = "PREQL_SNOWFLAKE_PW"
@@ -1,17 +1,16 @@
1
- from typing import Mapping, Callable, Any
1
+ from typing import Any, Callable, Mapping
2
2
 
3
3
  from jinja2 import Template
4
- from trilogy.utility import string_to_hash
5
-
6
4
 
7
5
  from trilogy.core.enums import FunctionType, WindowType
8
6
  from trilogy.core.models import (
9
7
  ProcessedQuery,
10
8
  ProcessedQueryPersist,
11
- ProcessedShowStatement,
12
9
  ProcessedRawSQLStatement,
10
+ ProcessedShowStatement,
13
11
  )
14
12
  from trilogy.dialect.base import BaseDialect
13
+ from trilogy.utility import string_to_hash
15
14
 
16
15
  WINDOW_FUNCTION_MAP: Mapping[WindowType, Callable[[Any, Any, Any], str]] = {}
17
16
 
trilogy/engine.py CHANGED
@@ -1,6 +1,7 @@
1
- from sqlalchemy.engine import Engine, Connection, CursorResult
2
1
  from typing import Protocol
3
2
 
3
+ from sqlalchemy.engine import Connection, CursorResult, Engine
4
+
4
5
 
5
6
  class EngineResult(Protocol):
6
7
  pass
trilogy/executor.py CHANGED
@@ -1,39 +1,40 @@
1
- from typing import List, Optional, Any, Generator, Protocol
1
+ from dataclasses import dataclass
2
2
  from functools import singledispatchmethod
3
+ from pathlib import Path
4
+ from typing import Any, Generator, List, Optional, Protocol
5
+
3
6
  from sqlalchemy import text
4
- from sqlalchemy.engine import Engine, CursorResult
7
+ from sqlalchemy.engine import CursorResult, Engine
5
8
 
6
9
  from trilogy.constants import logger
10
+ from trilogy.core.enums import Granularity, IOType
7
11
  from trilogy.core.models import (
12
+ Concept,
13
+ ConceptDeclarationStatement,
14
+ CopyStatement,
15
+ Datasource,
8
16
  Environment,
17
+ Function,
18
+ FunctionType,
19
+ ImportStatement,
20
+ ListWrapper,
21
+ MapWrapper,
22
+ MergeStatementV2,
23
+ MultiSelectStatement,
24
+ PersistStatement,
25
+ ProcessedCopyStatement,
9
26
  ProcessedQuery,
10
- ProcessedShowStatement,
11
27
  ProcessedQueryPersist,
12
28
  ProcessedRawSQLStatement,
13
- ProcessedCopyStatement,
29
+ ProcessedShowStatement,
14
30
  RawSQLStatement,
15
- MultiSelectStatement,
16
31
  SelectStatement,
17
- PersistStatement,
18
32
  ShowStatement,
19
- Concept,
20
- ConceptDeclarationStatement,
21
- Datasource,
22
- CopyStatement,
23
- ImportStatement,
24
- MergeStatementV2,
25
- Function,
26
- FunctionType,
27
- MapWrapper,
28
- ListWrapper,
29
33
  )
30
34
  from trilogy.dialect.base import BaseDialect
31
35
  from trilogy.dialect.enums import Dialects
32
- from trilogy.core.enums import IOType, Granularity
33
- from trilogy.parser import parse_text
34
36
  from trilogy.hooks.base_hook import BaseHook
35
- from pathlib import Path
36
- from dataclasses import dataclass
37
+ from trilogy.parser import parse_text
37
38
 
38
39
 
39
40
  class ResultProtocol(Protocol):
@@ -103,7 +104,6 @@ class Executor(object):
103
104
 
104
105
  self.generator = PostgresDialect()
105
106
  elif self.dialect == Dialects.SNOWFLAKE:
106
-
107
107
  from trilogy.dialect.snowflake import SnowflakeDialect
108
108
 
109
109
  self.generator = SnowflakeDialect()
@@ -146,7 +146,6 @@ class Executor(object):
146
146
 
147
147
  @execute_query.register
148
148
  def _(self, query: Datasource) -> CursorResult:
149
-
150
149
  return MockResult(
151
150
  [
152
151
  {
@@ -232,22 +231,23 @@ class Executor(object):
232
231
  @execute_query.register
233
232
  def _(self, query: ProcessedQuery) -> CursorResult:
234
233
  sql = self.generator.compile_statement(query)
235
- output = self.execute_raw_sql(sql)
234
+ output = self.execute_raw_sql(sql, local_concepts=query.local_concepts)
236
235
  return output
237
236
 
238
237
  @execute_query.register
239
238
  def _(self, query: ProcessedQueryPersist) -> CursorResult:
240
-
241
239
  sql = self.generator.compile_statement(query)
242
240
 
243
- output = self.execute_raw_sql(sql)
241
+ output = self.execute_raw_sql(sql, local_concepts=query.local_concepts)
244
242
  self.environment.add_datasource(query.datasource)
245
243
  return output
246
244
 
247
245
  @execute_query.register
248
246
  def _(self, query: ProcessedCopyStatement) -> CursorResult:
249
247
  sql = self.generator.compile_statement(query)
250
- output: CursorResult = self.execute_raw_sql(sql)
248
+ output: CursorResult = self.execute_raw_sql(
249
+ sql, local_concepts=query.local_concepts
250
+ )
251
251
  if query.target_type == IOType.CSV:
252
252
  import csv
253
253
 
@@ -355,7 +355,6 @@ class Executor(object):
355
355
  | ProcessedRawSQLStatement
356
356
  | ProcessedCopyStatement
357
357
  ]:
358
-
359
358
  return list(self.parse_text_generator(command, persist=persist))
360
359
 
361
360
  def parse_text_generator(self, command: str, persist: bool = False) -> Generator[
@@ -395,12 +394,20 @@ class Executor(object):
395
394
  if persist and isinstance(x, ProcessedQueryPersist):
396
395
  self.environment.add_datasource(x.datasource)
397
396
 
398
- def _hydrate_param(self, param: str) -> Any:
397
+ def _hydrate_param(
398
+ self, param: str, local_concepts: dict[str, Concept] | None = None
399
+ ) -> Any:
399
400
  matched = [
400
401
  v
401
402
  for v in self.environment.concepts.values()
402
403
  if v.safe_address == param or v.address == param
403
404
  ]
405
+ if local_concepts and not matched:
406
+ matched = [
407
+ v
408
+ for v in local_concepts.values()
409
+ if v.safe_address == param or v.address == param
410
+ ]
404
411
  if not matched:
405
412
  raise SyntaxError(f"No concept found for parameter {param}")
406
413
 
@@ -424,7 +431,10 @@ class Executor(object):
424
431
  return results[0]
425
432
 
426
433
  def execute_raw_sql(
427
- self, command: str, variables: dict | None = None
434
+ self,
435
+ command: str,
436
+ variables: dict | None = None,
437
+ local_concepts: dict[str, Concept] | None = None,
428
438
  ) -> CursorResult:
429
439
  """Run a command against the raw underlying
430
440
  execution engine"""
@@ -435,7 +445,10 @@ class Executor(object):
435
445
  else:
436
446
  params = q.compile().params
437
447
  if params:
438
- final_params = {x: self._hydrate_param(x) for x in params}
448
+ final_params = {
449
+ x: self._hydrate_param(x, local_concepts=local_concepts)
450
+ for x in params
451
+ }
439
452
 
440
453
  if final_params:
441
454
  return self.connection.execute(text(command), final_params)
@@ -1,10 +1,11 @@
1
1
  from trilogy.core.models import (
2
- QueryDatasource,
3
2
  CTE,
4
- SelectStatement,
5
- PersistStatement,
6
3
  MultiSelectStatement,
4
+ PersistStatement,
5
+ QueryDatasource,
7
6
  RowsetDerivationStatement,
7
+ SelectStatement,
8
+ UnionCTE,
8
9
  )
9
10
  from trilogy.core.processing.nodes import StrategyNode
10
11
 
@@ -30,7 +31,7 @@ class BaseHook:
30
31
  def process_root_datasource(self, datasource: QueryDatasource):
31
32
  pass
32
33
 
33
- def process_root_cte(self, cte: CTE):
34
+ def process_root_cte(self, cte: CTE | UnionCTE):
34
35
  pass
35
36
 
36
37
  def process_root_strategy_node(self, node: StrategyNode):
@@ -1,6 +1,7 @@
1
- from trilogy.hooks.base_hook import BaseHook
2
1
  import networkx as nx
3
2
 
3
+ from trilogy.hooks.base_hook import BaseHook
4
+
4
5
 
5
6
  class GraphHook(BaseHook):
6
7
  def __init__(self):
@@ -1,14 +1,18 @@
1
+ from enum import Enum
2
+ from logging import DEBUG, StreamHandler
1
3
  from typing import Union
2
- from trilogy.core.models import QueryDatasource, CTE, Datasource, SelectStatement
3
4
 
4
- from trilogy.hooks.base_hook import BaseHook
5
5
  from trilogy.constants import logger
6
- from logging import StreamHandler, DEBUG
6
+ from trilogy.core.models import (
7
+ CTE,
8
+ Datasource,
9
+ QueryDatasource,
10
+ SelectStatement,
11
+ UnionCTE,
12
+ )
7
13
  from trilogy.core.processing.nodes import StrategyNode
8
-
9
14
  from trilogy.dialect.bigquery import BigqueryDialect
10
-
11
- from enum import Enum
15
+ from trilogy.hooks.base_hook import BaseHook
12
16
 
13
17
 
14
18
  class PrintMode(Enum):
@@ -85,7 +89,9 @@ def print_recursive_nodes(
85
89
  return display
86
90
 
87
91
 
88
- def print_recursive_ctes(input: CTE, depth: int = 0, max_depth: int | None = None):
92
+ def print_recursive_ctes(
93
+ input: CTE | UnionCTE, depth: int = 0, max_depth: int | None = None
94
+ ):
89
95
  if max_depth and depth > max_depth:
90
96
  return
91
97
  select_statement = [c.address for c in input.output_columns]
@@ -96,6 +102,10 @@ def print_recursive_ctes(input: CTE, depth: int = 0, max_depth: int | None = Non
96
102
  if isinstance(input, CTE):
97
103
  for child in input.parent_ctes:
98
104
  print_recursive_ctes(child, depth + 1)
105
+ elif isinstance(input, UnionCTE):
106
+ for child in input.parent_ctes:
107
+ for parent in child.parent_ctes:
108
+ print_recursive_ctes(parent, depth + 1)
99
109
 
100
110
 
101
111
  class DebuggingHook(BaseHook):
@@ -128,7 +138,7 @@ class DebuggingHook(BaseHook):
128
138
  for row in printed:
129
139
  print("".join([str(v) for v in row]))
130
140
 
131
- def process_root_cte(self, cte: CTE):
141
+ def process_root_cte(self, cte: CTE | UnionCTE):
132
142
  if self.process_ctes != PrintMode.OFF:
133
143
  print_recursive_ctes(cte, max_depth=self.max_depth)
134
144
 
trilogy/parsing/common.py CHANGED
@@ -1,32 +1,28 @@
1
+ from typing import List, Tuple
2
+
3
+ from trilogy.constants import (
4
+ VIRTUAL_CONCEPT_PREFIX,
5
+ )
6
+ from trilogy.core.enums import FunctionType, Modifier, PurposeLineage, WindowType
7
+ from trilogy.core.functions import arg_to_datatype, function_args_to_output_purpose
1
8
  from trilogy.core.models import (
2
9
  AggregateWrapper,
3
10
  Concept,
11
+ DataType,
12
+ Environment,
13
+ FilterItem,
4
14
  Function,
15
+ FunctionClass,
5
16
  Grain,
6
- Purpose,
7
- Metadata,
8
- FilterItem,
9
17
  ListWrapper,
10
18
  MapWrapper,
11
- WindowItem,
12
19
  Meta,
20
+ Metadata,
13
21
  Parenthetical,
14
- FunctionClass,
15
- Environment,
16
- DataType,
17
- )
18
- from typing import List, Tuple
19
- from trilogy.core.functions import (
20
- function_args_to_output_purpose,
21
- FunctionType,
22
- arg_to_datatype,
23
- )
24
- from trilogy.utility import unique, string_to_hash
25
- from trilogy.core.enums import PurposeLineage
26
- from trilogy.constants import (
27
- VIRTUAL_CONCEPT_PREFIX,
22
+ Purpose,
23
+ WindowItem,
28
24
  )
29
- from trilogy.core.enums import Modifier, WindowType
25
+ from trilogy.utility import string_to_hash, unique
30
26
 
31
27
 
32
28
  def get_upstream_modifiers(keys: List[Concept]) -> list[Modifier]:
@@ -304,7 +300,6 @@ def arbitrary_to_concept(
304
300
  metadata: Metadata | None = None,
305
301
  purpose: Purpose | None = None,
306
302
  ) -> Concept:
307
-
308
303
  if isinstance(parent, AggregateWrapper):
309
304
  if not name:
310
305
  name = f"{VIRTUAL_CONCEPT_PREFIX}_agg_{parent.function.operator.value}_{string_to_hash(str(parent))}"