pytrilogy 0.0.2.27__tar.gz → 0.0.2.28__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 (108) hide show
  1. {pytrilogy-0.0.2.27/pytrilogy.egg-info → pytrilogy-0.0.2.28}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/pytrilogy.egg-info/SOURCES.txt +1 -0
  4. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_environment.py +1 -1
  5. pytrilogy-0.0.2.28/tests/test_executor.py +8 -0
  6. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_parsing.py +1 -3
  7. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_undefined_concept.py +1 -1
  8. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/__init__.py +1 -1
  9. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/models.py +38 -10
  10. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/utility.py +3 -6
  11. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/executor.py +17 -1
  12. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/LICENSE.md +0 -0
  13. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/README.md +0 -0
  14. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/pyproject.toml +0 -0
  15. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/pytrilogy.egg-info/dependency_links.txt +0 -0
  16. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/pytrilogy.egg-info/entry_points.txt +0 -0
  17. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/pytrilogy.egg-info/requires.txt +0 -0
  18. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/pytrilogy.egg-info/top_level.txt +0 -0
  19. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/setup.cfg +0 -0
  20. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/setup.py +0 -0
  21. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_datatypes.py +0 -0
  22. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_declarations.py +0 -0
  23. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_derived_concepts.py +0 -0
  24. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_discovery_nodes.py +0 -0
  25. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_functions.py +0 -0
  26. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_imports.py +0 -0
  27. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_metadata.py +0 -0
  28. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_models.py +0 -0
  29. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_multi_join_assignments.py +0 -0
  30. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_partial_handling.py +0 -0
  31. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_query_processing.py +0 -0
  32. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_select.py +0 -0
  33. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_show.py +0 -0
  34. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_statements.py +0 -0
  35. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/tests/test_where_clause.py +0 -0
  36. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/compiler.py +0 -0
  37. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/constants.py +0 -0
  38. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/__init__.py +0 -0
  39. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/constants.py +0 -0
  40. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/enums.py +0 -0
  41. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/env_processor.py +0 -0
  42. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/environment_helpers.py +0 -0
  43. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/ergonomics.py +0 -0
  44. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/exceptions.py +0 -0
  45. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/functions.py +0 -0
  46. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/graph_models.py +0 -0
  47. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/internal.py +0 -0
  48. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/optimization.py +0 -0
  49. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/optimizations/__init__.py +0 -0
  50. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/optimizations/base_optimization.py +0 -0
  51. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/optimizations/inline_constant.py +0 -0
  52. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/optimizations/inline_datasource.py +0 -0
  53. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  54. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/__init__.py +0 -0
  55. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  56. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/graph_utils.py +0 -0
  57. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/__init__.py +0 -0
  58. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  59. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/common.py +0 -0
  60. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  61. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/group_node.py +0 -0
  62. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  63. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  64. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  65. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  66. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  67. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/select_node.py +0 -0
  68. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  69. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/node_generators/window_node.py +0 -0
  70. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/nodes/__init__.py +0 -0
  71. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/nodes/base_node.py +0 -0
  72. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/nodes/filter_node.py +0 -0
  73. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/nodes/group_node.py +0 -0
  74. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/nodes/merge_node.py +0 -0
  75. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  76. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  77. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/processing/nodes/window_node.py +0 -0
  78. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/core/query_processor.py +0 -0
  79. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/__init__.py +0 -0
  80. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/base.py +0 -0
  81. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/bigquery.py +0 -0
  82. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/common.py +0 -0
  83. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/config.py +0 -0
  84. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/duckdb.py +0 -0
  85. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/enums.py +0 -0
  86. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/postgres.py +0 -0
  87. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/presto.py +0 -0
  88. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/snowflake.py +0 -0
  89. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/dialect/sql_server.py +0 -0
  90. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/engine.py +0 -0
  91. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/hooks/__init__.py +0 -0
  92. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/hooks/base_hook.py +0 -0
  93. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/hooks/graph_hook.py +0 -0
  94. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/hooks/query_debugger.py +0 -0
  95. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/metadata/__init__.py +0 -0
  96. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/parser.py +0 -0
  97. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/parsing/__init__.py +0 -0
  98. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/parsing/common.py +0 -0
  99. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/parsing/config.py +0 -0
  100. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/parsing/exceptions.py +0 -0
  101. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/parsing/helpers.py +0 -0
  102. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/parsing/parse_engine.py +0 -0
  103. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/parsing/render.py +0 -0
  104. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/parsing/trilogy.lark +0 -0
  105. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/py.typed +0 -0
  106. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/scripts/__init__.py +0 -0
  107. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/scripts/trilogy.py +0 -0
  108. {pytrilogy-0.0.2.27 → pytrilogy-0.0.2.28}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.27
3
+ Version: 0.0.2.28
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.27
3
+ Version: 0.0.2.28
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -13,6 +13,7 @@ tests/test_declarations.py
13
13
  tests/test_derived_concepts.py
14
14
  tests/test_discovery_nodes.py
15
15
  tests/test_environment.py
16
+ tests/test_executor.py
16
17
  tests/test_functions.py
17
18
  tests/test_imports.py
18
19
  tests/test_metadata.py
@@ -20,7 +20,7 @@ def test_environment_from_path():
20
20
 
21
21
  env = Environment.from_file(Path(__file__).parent / "test_env.preql")
22
22
 
23
- assert "id" in env.concepts
23
+ assert "local.id" in env.concepts
24
24
 
25
25
 
26
26
  def test_environment_merge():
@@ -0,0 +1,8 @@
1
+ from trilogy import Dialects
2
+ from pathlib import Path
3
+
4
+
5
+ def test_file_parsing():
6
+ target = Path(__file__).parent / "test_env.preql"
7
+ parsed = Dialects.DUCK_DB.default_executor().parse_file(target)
8
+ assert len(list(parsed)) == 1
@@ -178,7 +178,7 @@ select
178
178
  )
179
179
 
180
180
  for name in ["name_alphabetical", "name_alphabetical_2"]:
181
- assert name in env.concepts
181
+ assert f"local.{name}" in env.concepts
182
182
  assert env.concepts[name].purpose == Purpose.PROPERTY
183
183
  assert env.concepts[name].keys == (env.concepts["id"],)
184
184
 
@@ -197,7 +197,6 @@ select
197
197
  )
198
198
 
199
199
  for name in ["join_id"]:
200
- assert name in env.concepts
201
200
  assert env.concepts[name].purpose == Purpose.PROPERTY
202
201
  assert env.concepts[name].keys == (
203
202
  env.concepts["id"],
@@ -225,7 +224,6 @@ select
225
224
  )
226
225
  # assert output_purpose == Purpose.METRIC
227
226
  for name in ["test_name_count"]:
228
- assert name in env.concepts
229
227
  assert env.concepts[name].purpose == Purpose.METRIC
230
228
 
231
229
 
@@ -32,6 +32,6 @@ def test_undefined_concept_dict():
32
32
  try:
33
33
  env["orid"]
34
34
  except UndefinedConceptException as e:
35
- assert e.suggestions == ["order_id"]
35
+ assert e.suggestions == ["local.order_id"]
36
36
  assert "suggestions" in e.message.lower()
37
37
  assert "order_id" in e.message.lower()
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.2.27"
7
+ __version__ = "0.0.2.28"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -606,6 +606,8 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
606
606
  return self.grain.components_copy if self.grain else []
607
607
 
608
608
  def with_namespace(self, namespace: str) -> "Concept":
609
+ if namespace == self.namespace:
610
+ return self
609
611
  return self.__class__(
610
612
  name=self.name,
611
613
  datatype=self.datatype,
@@ -3253,7 +3255,6 @@ class EnvironmentConceptDict(dict):
3253
3255
  )
3254
3256
  self.undefined[key] = undefined
3255
3257
  return undefined
3256
-
3257
3258
  matches = self._find_similar_concepts(key)
3258
3259
  message = f"Undefined concept: {key}."
3259
3260
  if matches:
@@ -3263,8 +3264,15 @@ class EnvironmentConceptDict(dict):
3263
3264
  raise UndefinedConceptException(f"line: {line_no}: " + message, matches)
3264
3265
  raise UndefinedConceptException(message, matches)
3265
3266
 
3266
- def _find_similar_concepts(self, concept_name):
3267
- matches = difflib.get_close_matches(concept_name, self.keys())
3267
+ def _find_similar_concepts(self, concept_name: str):
3268
+ def strip_local(input: str):
3269
+ if input.startswith(f"{DEFAULT_NAMESPACE}."):
3270
+ return input[len(DEFAULT_NAMESPACE) + 1 :]
3271
+ return input
3272
+
3273
+ matches = difflib.get_close_matches(
3274
+ strip_local(concept_name), [strip_local(x) for x in self.keys()]
3275
+ )
3268
3276
  return matches
3269
3277
 
3270
3278
  def items(self) -> ItemsView[str, Concept]: # type: ignore
@@ -3325,7 +3333,6 @@ class Environment(BaseModel):
3325
3333
 
3326
3334
  materialized_concepts: List[Concept] = Field(default_factory=list)
3327
3335
  alias_origin_lookup: Dict[str, Concept] = Field(default_factory=dict)
3328
- canonical_map: Dict[str, str] = Field(default_factory=dict)
3329
3336
  _parse_count: int = 0
3330
3337
 
3331
3338
  @classmethod
@@ -3444,14 +3451,38 @@ class Environment(BaseModel):
3444
3451
  exists = True
3445
3452
  imp_stm = ImportStatement(alias=alias, path=Path(source.working_path))
3446
3453
 
3454
+ same_namespace = alias == self.namespace
3455
+
3447
3456
  if not exists:
3448
3457
  self.imports[alias].append(imp_stm)
3449
3458
 
3450
- for _, concept in source.concepts.items():
3451
- self.add_concept(concept.with_namespace(alias), _ignore_cache=True)
3459
+ for k, concept in source.concepts.items():
3460
+ if same_namespace:
3461
+ new = self.add_concept(concept, _ignore_cache=True)
3462
+ else:
3463
+ new = self.add_concept(
3464
+ concept.with_namespace(alias), _ignore_cache=True
3465
+ )
3466
+
3467
+ k = address_with_namespace(k, alias)
3468
+ # set this explicitly, to handle aliasing
3469
+ self.concepts[k] = new
3452
3470
 
3453
3471
  for _, datasource in source.datasources.items():
3454
- self.add_datasource(datasource.with_namespace(alias), _ignore_cache=True)
3472
+ if same_namespace:
3473
+ self.add_datasource(datasource, _ignore_cache=True)
3474
+ else:
3475
+ self.add_datasource(
3476
+ datasource.with_namespace(alias), _ignore_cache=True
3477
+ )
3478
+ for key, val in source.alias_origin_lookup.items():
3479
+ if same_namespace:
3480
+ self.alias_origin_lookup[key] = val
3481
+ else:
3482
+ self.alias_origin_lookup[address_with_namespace(key, alias)] = (
3483
+ val.with_namespace(alias)
3484
+ )
3485
+
3455
3486
  self.gen_concept_list_caches()
3456
3487
  return self
3457
3488
 
@@ -3542,8 +3573,6 @@ class Environment(BaseModel):
3542
3573
  existing = self.validate_concept(concept, meta=meta)
3543
3574
  if existing:
3544
3575
  concept = existing
3545
- if concept.namespace == DEFAULT_NAMESPACE:
3546
- self.concepts[concept.name] = concept
3547
3576
  self.concepts[concept.address] = concept
3548
3577
  from trilogy.core.environment_helpers import generate_related_concepts
3549
3578
 
@@ -3631,7 +3660,6 @@ class Environment(BaseModel):
3631
3660
  v.pseudonyms.add(source.address)
3632
3661
  if v.address == source.address:
3633
3662
  replacements[k] = target
3634
- self.canonical_map[k] = target.address
3635
3663
  v.pseudonyms.add(target.address)
3636
3664
  # we need to update keys and grains of all concepts
3637
3665
  else:
@@ -66,9 +66,6 @@ def resolve_join_order_v2(
66
66
  ) -> list[JoinOrderOutput]:
67
67
  datasources = [x for x in g.nodes if x.startswith("ds~")]
68
68
  concepts = [x for x in g.nodes if x.startswith("c~")]
69
- # from trilogy.hooks.graph_hook import GraphHook
70
-
71
- # GraphHook().query_graph_built(g)
72
69
 
73
70
  output: list[JoinOrderOutput] = []
74
71
  pivot_map = {
@@ -78,7 +75,7 @@ def resolve_join_order_v2(
78
75
  pivots = list(
79
76
  sorted(
80
77
  [x for x in pivot_map if len(pivot_map[x]) > 1],
81
- key=lambda x: len(pivot_map[x]),
78
+ key=lambda x: (len(pivot_map[x]), len(x), x),
82
79
  )
83
80
  )
84
81
  solo = [x for x in pivot_map if len(pivot_map[x]) == 1]
@@ -95,7 +92,7 @@ def resolve_join_order_v2(
95
92
  root = pivots.pop()
96
93
 
97
94
  # sort so less partials is last and eligible lefts are
98
- def score_key(x: str) -> int:
95
+ def score_key(x: str) -> tuple[int, int, str]:
99
96
  base = 1
100
97
  # if it's left, higher weight
101
98
  if x in eligible_left:
@@ -103,7 +100,7 @@ def resolve_join_order_v2(
103
100
  # if it has the concept as a partial, lower weight
104
101
  if root in partials.get(x, []):
105
102
  base -= 1
106
- return base
103
+ return (base, len(x), x)
107
104
 
108
105
  # get remainig un-joined datasets
109
106
  to_join = sorted(
@@ -276,6 +276,20 @@ class Executor(object):
276
276
  output.append(compiled_sql)
277
277
  return output
278
278
 
279
+ def parse_file(self, file: str | Path, persist: bool = False) -> Generator[
280
+ ProcessedQuery
281
+ | ProcessedQueryPersist
282
+ | ProcessedShowStatement
283
+ | ProcessedRawSQLStatement
284
+ | ProcessedCopyStatement,
285
+ None,
286
+ None,
287
+ ]:
288
+ file = Path(file)
289
+ with open(file, "r") as f:
290
+ command = f.read()
291
+ return self.parse_text_generator(command, persist=persist)
292
+
279
293
  def parse_text(
280
294
  self, command: str, persist: bool = False
281
295
  ) -> List[
@@ -319,9 +333,11 @@ class Executor(object):
319
333
  x = self.generator.generate_queries(
320
334
  self.environment, [t], hooks=self.hooks
321
335
  )[0]
336
+
337
+ yield x
338
+
322
339
  if persist and isinstance(x, ProcessedQueryPersist):
323
340
  self.environment.add_datasource(x.datasource)
324
- yield x
325
341
 
326
342
  def execute_raw_sql(
327
343
  self, command: str, variables: dict | None = None
File without changes
File without changes
File without changes
File without changes