lea-cli 0.7.2__tar.gz → 0.7.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lea-cli
3
- Version: 0.7.2
3
+ Version: 0.7.3
4
4
  Summary: A minimalist alternative to dbt
5
5
  Author: Max Halford
6
6
  Author-email: maxhalford25@gmail.com
@@ -115,7 +115,7 @@ class Session:
115
115
 
116
116
  if self.incremental_field_name is not None:
117
117
  self.filterable_table_refs = {
118
- table_ref
118
+ table_ref.replace_dataset(self.write_dataset)
119
119
  for table_ref in scripts
120
120
  if any(
121
121
  field.name == incremental_field_name
@@ -123,7 +123,7 @@ class Session:
123
123
  )
124
124
  }
125
125
  self.incremental_table_refs = {
126
- table_ref
126
+ table_ref.replace_dataset(self.write_dataset)
127
127
  for table_ref in selected_table_refs | set(existing_audit_tables)
128
128
  if any(
129
129
  field.name == incremental_field_name and FieldTag.INCREMENTAL in field.tags
@@ -140,36 +140,18 @@ class Session:
140
140
  def add_write_context_to_table_ref(self, table_ref: TableRef) -> TableRef:
141
141
  table_ref = table_ref.replace_dataset(self.write_dataset)
142
142
  table_ref = table_ref.add_audit_suffix()
143
- if isinstance(self.database_client, databases.BigQueryClient):
144
- table_ref = table_ref.replace_project(self.database_client.write_project_id)
145
143
  return table_ref
146
144
 
147
145
  def remove_write_context_from_table_ref(self, table_ref: TableRef) -> TableRef:
148
146
  table_ref = table_ref.replace_dataset(self.base_dataset)
149
147
  table_ref = table_ref.remove_audit_suffix()
150
- if isinstance(self.database_client, databases.BigQueryClient):
151
- table_ref = table_ref.replace_project(None)
152
148
  return table_ref
153
149
 
154
150
  def add_context_to_script(self, script: Script) -> Script:
155
- # If a script is marked as incremental, it implies that it can be run incrementally. This
156
- # means that we have to filter the script's dependencies, as well as filter the output.
157
- # This logic is implemented by the script's SQL dialect.
158
- if script.table_ref in self.incremental_table_refs:
159
- script = dataclasses.replace(
160
- script,
161
- code=script.sql_dialect.add_dependency_filters(
162
- code=script.code,
163
- incremental_field_name=self.incremental_field_name,
164
- incremental_field_values=self.incremental_field_values,
165
- # One caveat is the dependencies which are not incremental do not have to be
166
- # filtered. Indeed, they are already filtered by the fact that they are
167
- # incremental.
168
- dependencies_to_filter=self.filterable_table_refs - self.incremental_table_refs,
169
- ),
170
- )
151
+ def add_context_to_dependency(dependency: TableRef) -> TableRef | None:
152
+ if dependency.project != script.table_ref.project:
153
+ return None
171
154
 
172
- def add_context_to_dependency(dependency: TableRef) -> TableRef:
173
155
  if (
174
156
  dependency.replace_dataset(self.base_dataset)
175
157
  in self.selected_table_refs
@@ -180,27 +162,36 @@ class Session:
180
162
  and dependency.replace_dataset(self.base_dataset) in self.scripts
181
163
  ):
182
164
  dependency = dependency.add_audit_suffix()
165
+
183
166
  dependency = dependency.replace_dataset(self.write_dataset)
184
167
 
185
- # Replace occurences of the dataset in script queries clauses to ensure it points
186
- # to the right project. If we don't do that and a separate compute project is used,
187
- # queries will default to looking for tables in the compute project, which is not
188
- # what we want.
189
- if (
190
- isinstance(self.database_client, databases.BigQueryClient)
191
- and dependency.project is None
192
- ):
193
- dependency = dependency.replace_project(self.database_client.write_project_id)
194
168
  return dependency
195
169
 
196
170
  script = replace_script_dependencies(script=script, replace_func=add_context_to_dependency)
197
171
 
172
+ # If a script is marked as incremental, it implies that it can be run incrementally. This
173
+ # means that we have to filter the script's dependencies, as well as filter the output.
174
+ # This logic is implemented by the script's SQL dialect.
175
+ if script.table_ref.replace_dataset(self.write_dataset) in self.incremental_table_refs:
176
+ script = dataclasses.replace(
177
+ script,
178
+ code=script.sql_dialect.add_dependency_filters(
179
+ code=script.code,
180
+ incremental_field_name=self.incremental_field_name,
181
+ incremental_field_values=self.incremental_field_values,
182
+ # One caveat is the dependencies which are not incremental do not have to be
183
+ # filtered. Indeed, they are already filtered by the fact that they are
184
+ # incremental.
185
+ dependencies_to_filter=self.filterable_table_refs - self.incremental_table_refs,
186
+ ),
187
+ )
188
+
198
189
  # If the script is not incremental, we're not out of the woods! All scripts are
199
190
  # materialized into side-tables which we call "audit" tables. This is the WAP pattern.
200
191
  # Therefore, if a script is not incremental, but it depends on an incremental script, we
201
192
  # have to modify the script to use both the incremental and non-incremental versions of
202
193
  # the dependency. This is handled by the script's SQL dialect.
203
- if script.table_ref not in self.incremental_table_refs and self.incremental_table_refs:
194
+ elif self.incremental_table_refs:
204
195
  script = dataclasses.replace(
205
196
  script,
206
197
  code=script.sql_dialect.handle_incremental_dependencies(
@@ -208,10 +199,8 @@ class Session:
208
199
  incremental_field_name=self.incremental_field_name,
209
200
  incremental_field_values=self.incremental_field_values,
210
201
  incremental_dependencies={
211
- self.add_write_context_to_table_ref(
212
- table_ref
213
- ).remove_audit_suffix(): self.add_write_context_to_table_ref(table_ref)
214
- for table_ref in self.incremental_table_refs
202
+ incremental_table_ref: incremental_table_ref.add_audit_suffix()
203
+ for incremental_table_ref in self.incremental_table_refs
215
204
  },
216
205
  ),
217
206
  )
@@ -234,7 +223,8 @@ class Session:
234
223
  self.jobs.append(job)
235
224
 
236
225
  msg = f"{job.status} {script.table_ref}"
237
- if script.table_ref in self.incremental_table_refs:
226
+
227
+ if script.table_ref.remove_audit_suffix() in self.incremental_table_refs:
238
228
  msg += " (incremental)"
239
229
  log.info(msg)
240
230
 
@@ -291,9 +281,7 @@ class Session:
291
281
  to_table_ref = table_ref.remove_audit_suffix()
292
282
 
293
283
  is_incremental = (
294
- self.incremental_field_name is not None
295
- and to_table_ref.replace_dataset(self.base_dataset).replace_project(None)
296
- in self.incremental_table_refs
284
+ self.incremental_field_name is not None and to_table_ref in self.incremental_table_refs
297
285
  )
298
286
  if is_incremental:
299
287
  database_job = self.database_client.delete_and_insert(
@@ -345,11 +333,21 @@ def replace_script_dependencies(
345
333
 
346
334
  """
347
335
  code = script.code
336
+
348
337
  for dependency_to_edit in script.dependencies:
349
- dependency_to_edit_str = script.sql_dialect.format_table_ref(dependency_to_edit)
350
338
  new_dependency = replace_func(dependency_to_edit)
339
+ if new_dependency is None:
340
+ continue
341
+
342
+ dependency_to_edit_without_project_str = script.sql_dialect.format_table_ref(
343
+ dependency_to_edit.replace_project(None)
344
+ )
351
345
  new_dependency_str = script.sql_dialect.format_table_ref(new_dependency)
352
- code = re.sub(rf"\b{dependency_to_edit_str}\b", new_dependency_str, code)
346
+ code = re.sub(
347
+ rf"\b{dependency_to_edit_without_project_str}\b",
348
+ new_dependency_str,
349
+ code,
350
+ )
353
351
 
354
352
  # We also have to handle the case where the table is referenced to access a field.
355
353
  # TODO: refactor this with the above
@@ -394,32 +392,45 @@ def delete_table_refs(
394
392
 
395
393
 
396
394
  class Conductor:
397
- def __init__(self, scripts_dir: str, dataset_name: str | None = None):
395
+ def __init__(
396
+ self, scripts_dir: str, dataset_name: str | None = None, project_name: str | None = None
397
+ ):
398
398
  # Load environment variables from .env file
399
399
  # TODO: is is Pythonic to do this here?
400
400
  dotenv.load_dotenv(".env", verbose=True)
401
401
 
402
+ self.warehouse = os.environ["LEA_WAREHOUSE"].lower()
403
+
402
404
  self.scripts_dir = pathlib.Path(scripts_dir)
403
405
  if not self.scripts_dir.is_dir():
404
406
  raise ValueError(f"Directory {self.scripts_dir} not found")
405
- dataset_name = dataset_name or os.environ.get("LEA_BQ_DATASET_NAME")
407
+
408
+ if dataset_name is None:
409
+ if self.warehouse == "bigquery":
410
+ dataset_name = os.environ.get("LEA_BQ_DATASET_NAME")
406
411
  if dataset_name is None:
407
412
  raise ValueError("Dataset name could not be inferred")
408
413
  self.dataset_name = dataset_name
409
414
 
415
+ if project_name is None:
416
+ if self.warehouse == "bigquery":
417
+ project_name = os.environ.get("LEA_BQ_PROJECT_ID")
418
+ if project_name is None:
419
+ raise ValueError("Project name could not be inferred")
420
+ self.project_name = project_name
421
+
410
422
  log.info("📝 Reading scripts")
411
423
 
412
424
  self.dag = DAGOfScripts.from_directory(
413
425
  scripts_dir=self.scripts_dir,
414
426
  sql_dialect=BigQueryDialect(),
415
427
  dataset_name=self.dataset_name,
428
+ project_name=self.project_name,
416
429
  )
417
430
  log.info(f"{len(self.dag.scripts):,d} scripts found")
418
431
 
419
432
  def make_client(self, dry_run: bool = False, print_mode: bool = False) -> DatabaseClient:
420
- warehouse = os.environ["LEA_WAREHOUSE"]
421
-
422
- if warehouse.lower() == "bigquery":
433
+ if self.warehouse.lower() == "bigquery":
423
434
  # Do imports here to avoid loading them all the time
424
435
  from google.oauth2 import service_account
425
436
 
@@ -448,7 +459,7 @@ class Conductor:
448
459
  print_mode=print_mode,
449
460
  )
450
461
 
451
- raise ValueError(f"Unsupported warehouse {warehouse!r}")
462
+ raise ValueError(f"Unsupported warehouse {self.warehouse!r}")
452
463
 
453
464
  def name_user_dataset(self) -> str:
454
465
  username = os.environ.get("LEA_USERNAME", getpass.getuser())
@@ -611,10 +622,14 @@ def promote_audit_tables(session: Session):
611
622
 
612
623
 
613
624
  def delete_audit_tables(session: Session):
614
- if session.existing_audit_tables:
625
+ table_refs_to_delete = set(session.existing_audit_tables) | {
626
+ session.add_write_context_to_table_ref(table_ref)
627
+ for table_ref in session.selected_table_refs
628
+ }
629
+ if table_refs_to_delete:
615
630
  log.info("🧹 Deleting audit tables")
616
631
  delete_table_refs(
617
- table_refs=session.existing_audit_tables,
632
+ table_refs=table_refs_to_delete,
618
633
  database_client=session.database_client,
619
634
  executor=concurrent.futures.ThreadPoolExecutor(max_workers=None),
620
635
  verbose=False,
@@ -19,19 +19,28 @@ class DAGOfScripts(graphlib.TopologicalSorter):
19
19
  scripts: list[Script],
20
20
  scripts_dir: pathlib.Path,
21
21
  dataset_name: str,
22
+ project_name: str,
22
23
  ):
23
24
  graphlib.TopologicalSorter.__init__(self, dependency_graph)
24
25
  self.dependency_graph = dependency_graph
25
26
  self.scripts = {script.table_ref: script for script in scripts}
26
27
  self.scripts_dir = scripts_dir
27
28
  self.dataset_name = dataset_name
29
+ self.project_name = project_name
28
30
 
29
31
  @classmethod
30
32
  def from_directory(
31
- cls, scripts_dir: pathlib.Path, sql_dialect: SQLDialect, dataset_name: str
33
+ cls,
34
+ scripts_dir: pathlib.Path,
35
+ sql_dialect: SQLDialect,
36
+ dataset_name: str,
37
+ project_name: str,
32
38
  ) -> DAGOfScripts:
33
39
  scripts = read_scripts(
34
- scripts_dir=scripts_dir, sql_dialect=sql_dialect, dataset_name=dataset_name
40
+ scripts_dir=scripts_dir,
41
+ sql_dialect=sql_dialect,
42
+ dataset_name=dataset_name,
43
+ project_name=project_name,
35
44
  )
36
45
 
37
46
  # Fields in the script's code may contain tags. These tags induce assertion tests, which
@@ -53,6 +62,7 @@ class DAGOfScripts(graphlib.TopologicalSorter):
53
62
  scripts=scripts,
54
63
  scripts_dir=scripts_dir,
55
64
  dataset_name=dataset_name,
65
+ project_name=project_name,
56
66
  )
57
67
 
58
68
  def select(self, *queries: str) -> set[TableRef]:
@@ -111,7 +121,12 @@ class DAGOfScripts(graphlib.TopologicalSorter):
111
121
  return
112
122
 
113
123
  *schema, name = query.split(".")
114
- table_ref = TableRef(dataset=self.dataset_name, schema=tuple(schema), name=name)
124
+ table_ref = TableRef(
125
+ dataset=self.dataset_name,
126
+ schema=tuple(schema),
127
+ name=name,
128
+ project=self.project_name,
129
+ )
115
130
  yield table_ref
116
131
  if include_ancestors:
117
132
  yield from self.iter_ancestors(node=table_ref)
@@ -285,7 +285,9 @@ class BigQueryClient:
285
285
  """
286
286
  job = self.client.query(query, location=self.location)
287
287
  return {
288
- BigQueryDialect.parse_table_ref(f"{dataset_name}.{row['table_id']}"): TableStats(
288
+ BigQueryDialect.parse_table_ref(
289
+ f"{self.write_project_id}.{dataset_name}.{row['table_id']}"
290
+ ): TableStats(
289
291
  n_rows=row["row_count"],
290
292
  n_bytes=row["size_bytes"],
291
293
  updated_at=(
@@ -302,9 +304,9 @@ class BigQueryClient:
302
304
  """
303
305
  job = self.client.query(query, location=self.location)
304
306
  return {
305
- BigQueryDialect.parse_table_ref(f"{dataset_name}.{table_name}"): [
306
- scripts.Field(name=row["column_name"]) for _, row in rows.iterrows()
307
- ]
307
+ BigQueryDialect.parse_table_ref(
308
+ f"{self.write_project_id}.{dataset_name}.{table_name}"
309
+ ): [scripts.Field(name=row["column_name"]) for _, row in rows.iterrows()]
308
310
  for table_name, rows in job.result()
309
311
  .to_dataframe()
310
312
  .sort_values(["table_name", "column_name"])
@@ -320,3 +322,19 @@ class BigQueryClient:
320
322
  dry_run=self.dry_run,
321
323
  **kwargs,
322
324
  )
325
+
326
+ # The approach we use works best if the tables are clustered by account_slug. This is
327
+ # because we only need to refresh the data for a subset of accounts, and clustering by
328
+ # account_slug allows BigQuery to only scan the data for the accounts that need to be
329
+ # refreshed. This is a good practice in general, but it's particularly important in this
330
+ # case.
331
+ # WIP
332
+ # if (
333
+ # script is not None
334
+ # and not script.table_ref.is_test
335
+ # and script.table_ref.name.endswith("___audit")
336
+ # and "account_slug" in {field.name for field in script.fields}
337
+ # ):
338
+ # job_config.clustering_fields = ["account_slug"]
339
+
340
+ # return job_config
@@ -72,7 +72,11 @@ class SQLScript:
72
72
 
73
73
  @classmethod
74
74
  def from_path(
75
- cls, scripts_dir: pathlib.Path, relative_path: pathlib.Path, sql_dialect: SQLDialect
75
+ cls,
76
+ scripts_dir: pathlib.Path,
77
+ relative_path: pathlib.Path,
78
+ sql_dialect: SQLDialect,
79
+ project_name: str,
76
80
  ) -> SQLScript:
77
81
  # Either the file is a Jinja template
78
82
  if relative_path.suffixes == [".sql", ".jinja"]:
@@ -85,7 +89,9 @@ class SQLScript:
85
89
  code = (scripts_dir / relative_path).read_text().rstrip().rstrip(";")
86
90
 
87
91
  return cls(
88
- table_ref=TableRef.from_path(scripts_dir=scripts_dir, relative_path=relative_path),
92
+ table_ref=TableRef.from_path(
93
+ scripts_dir=scripts_dir, relative_path=relative_path, project_name=project_name
94
+ ),
89
95
  code=code,
90
96
  sql_dialect=sql_dialect,
91
97
  updated_at=dt.datetime.fromtimestamp(
@@ -107,8 +113,15 @@ class SQLScript:
107
113
 
108
114
  @functools.cached_property
109
115
  def dependencies(self) -> set[TableRef]:
116
+ def add_default_project(table_ref: TableRef) -> TableRef:
117
+ if table_ref.project is None:
118
+ return table_ref.replace_project(self.table_ref.project)
119
+ return table_ref
120
+
110
121
  return {
111
- self.sql_dialect.parse_table_ref(table_ref=sqlglot.exp.table_name(table))
122
+ add_default_project(
123
+ self.sql_dialect.parse_table_ref(table_ref=sqlglot.exp.table_name(table))
124
+ )
112
125
  for scope in sqlglot.optimizer.scope.traverse_scope(self.ast)
113
126
  for table in scope.tables
114
127
  if (
@@ -136,6 +149,7 @@ class SQLScript:
136
149
  dataset=self.table_ref.dataset,
137
150
  schema=("tests",),
138
151
  name=f"{'__'.join(self.table_ref.schema)}__{self.table_ref.name}__{field.name}___{tag.lower().lstrip('#')}",
152
+ project=self.table_ref.project,
139
153
  )
140
154
 
141
155
  def make_assertion_test(table_ref, field, tag):
@@ -181,14 +195,14 @@ class SQLScript:
181
195
  def __rich__(self):
182
196
  code = textwrap.dedent(self.code).strip()
183
197
  code_with_table_ref = f"""-- {self.table_ref}\n\n{code}\n"""
184
- return rich.syntax.Syntax(code_with_table_ref, "sql", line_numbers=True, theme="ansi_dark")
198
+ return rich.syntax.Syntax(code_with_table_ref, "sql", line_numbers=False, theme="ansi_dark")
185
199
 
186
200
 
187
201
  Script = SQLScript
188
202
 
189
203
 
190
204
  def read_scripts(
191
- scripts_dir: pathlib.Path, sql_dialect: SQLDialect, dataset_name: str
205
+ scripts_dir: pathlib.Path, sql_dialect: SQLDialect, dataset_name: str, project_name: str
192
206
  ) -> list[Script]:
193
207
  def read_script(path: pathlib.Path) -> Script:
194
208
  match tuple(path.suffixes):
@@ -197,6 +211,7 @@ def read_scripts(
197
211
  scripts_dir=scripts_dir,
198
212
  relative_path=path.relative_to(scripts_dir),
199
213
  sql_dialect=sql_dialect,
214
+ project_name=project_name,
200
215
  )
201
216
  case _:
202
217
  raise ValueError(f"Unsupported script type: {path}")
@@ -12,33 +12,39 @@ class TableRef:
12
12
  dataset: str
13
13
  schema: tuple[str, ...]
14
14
  name: str
15
- project: str | None = None
15
+ project: str | None
16
16
 
17
17
  def __str__(self):
18
18
  return ".".join(filter(None, [self.project, self.dataset, *self.schema, self.name]))
19
19
 
20
20
  @classmethod
21
- def from_path(cls, scripts_dir: pathlib.Path, relative_path: pathlib.Path) -> TableRef:
21
+ def from_path(
22
+ cls, scripts_dir: pathlib.Path, relative_path: pathlib.Path, project_name: str
23
+ ) -> TableRef:
22
24
  parts = list(filter(None, relative_path.parts))
23
25
  *schema, filename = parts
24
26
  return cls(
25
27
  dataset=scripts_dir.name,
26
28
  schema=tuple(schema),
27
- # Remove the ex
28
- name=filename.split(".")[0],
29
+ name=filename.split(".")[0], # remove the extension
30
+ project=project_name,
29
31
  )
30
32
 
31
33
  def replace_dataset(self, dataset: str) -> TableRef:
32
34
  return dataclasses.replace(self, dataset=dataset)
33
35
 
34
- def replace_project(self, project: str | None) -> TableRef:
36
+ def replace_project(self, project: str) -> TableRef:
35
37
  return dataclasses.replace(self, project=project)
36
38
 
37
39
  def add_audit_suffix(self) -> TableRef:
40
+ if self.is_audit_table:
41
+ return self
38
42
  return dataclasses.replace(self, name=f"{self.name}{AUDIT_TABLE_SUFFIX}")
39
43
 
40
44
  def remove_audit_suffix(self) -> TableRef:
41
- return dataclasses.replace(self, name=re.sub(rf"{AUDIT_TABLE_SUFFIX}$", "", self.name))
45
+ if self.is_audit_table:
46
+ return dataclasses.replace(self, name=re.sub(rf"{AUDIT_TABLE_SUFFIX}$", "", self.name))
47
+ return self
42
48
 
43
49
  @property
44
50
  def is_audit_table(self) -> bool:
@@ -19,7 +19,7 @@ def scripts() -> dict[TableRef, Script]:
19
19
  script.table_ref: script
20
20
  for script in [
21
21
  Script(
22
- table_ref=TableRef("read", ("raw",), "users"),
22
+ table_ref=TableRef("read", ("raw",), "users", "test_project"),
23
23
  code="""
24
24
  SELECT * FROM UNNEST([
25
25
  STRUCT(1 AS id, 'Alice' AS name, 30 AS age),
@@ -30,7 +30,7 @@ def scripts() -> dict[TableRef, Script]:
30
30
  sql_dialect=BigQueryDialect(),
31
31
  ),
32
32
  Script(
33
- table_ref=TableRef("read", ("core",), "users"),
33
+ table_ref=TableRef("read", ("core",), "users", "test_project"),
34
34
  code="""
35
35
  SELECT
36
36
  id,
@@ -42,7 +42,7 @@ def scripts() -> dict[TableRef, Script]:
42
42
  sql_dialect=BigQueryDialect(),
43
43
  ),
44
44
  Script(
45
- table_ref=TableRef("read", ("analytics",), "n_users"),
45
+ table_ref=TableRef("read", ("analytics",), "n_users", "test_project"),
46
46
  code="""
47
47
  SELECT COUNT(*)
48
48
  FROM read.core__users
@@ -70,7 +70,9 @@ def test_simple_run(scripts):
70
70
  )
71
71
 
72
72
  assert_queries_are_equal(
73
- session.add_context_to_script(scripts[TableRef("read", ("raw",), "users")]).code,
73
+ session.add_context_to_script(
74
+ scripts[TableRef("read", ("raw",), "users", "test_project")]
75
+ ).code,
74
76
  """
75
77
  SELECT * FROM UNNEST([
76
78
  STRUCT(1 AS id, 'Alice' AS name, 30 AS age),
@@ -80,10 +82,12 @@ def test_simple_run(scripts):
80
82
  """,
81
83
  )
82
84
  assert_queries_are_equal(
83
- session.add_context_to_script(scripts[TableRef("read", ("analytics",), "n_users")]).code,
85
+ session.add_context_to_script(
86
+ scripts[TableRef("read", ("analytics",), "n_users", "test_project")]
87
+ ).code,
84
88
  """
85
89
  SELECT COUNT(*)
86
- FROM write.core__users___audit
90
+ FROM `test_project`.write.core__users___audit
87
91
  """,
88
92
  )
89
93
 
@@ -101,29 +105,33 @@ def test_incremental_field(scripts):
101
105
  )
102
106
 
103
107
  assert_queries_are_equal(
104
- session.add_context_to_script(scripts[TableRef("read", ("core",), "users")]).code,
108
+ session.add_context_to_script(
109
+ scripts[TableRef("read", ("core",), "users", "test_project")]
110
+ ).code,
105
111
  """
106
112
  SELECT *
107
113
  FROM (
108
114
  SELECT id, name, age
109
- FROM write.raw__users___audit
115
+ FROM `test_project`.write.raw__users___audit
110
116
  )
111
117
  WHERE name IN ('Alice')
112
118
  """,
113
119
  )
114
120
 
115
121
  assert_queries_are_equal(
116
- session.add_context_to_script(scripts[TableRef("read", ("analytics",), "n_users")]).code,
122
+ session.add_context_to_script(
123
+ scripts[TableRef("read", ("analytics",), "n_users", "test_project")]
124
+ ).code,
117
125
  """
118
126
  SELECT COUNT(*) FROM (
119
127
  SELECT *
120
- FROM write.core__users___audit
128
+ FROM `test_project`.write.core__users___audit
121
129
  WHERE name IN ('Alice')
122
130
 
123
131
  UNION ALL
124
132
 
125
133
  SELECT *
126
- FROM write.core__users
134
+ FROM `test_project`.write.core__users
127
135
  WHERE name NOT IN ('Alice')
128
136
  )
129
137
  """,
@@ -136,21 +144,23 @@ def test_incremental_field_but_no_incremental_table_selected(scripts):
136
144
  base_dataset="read",
137
145
  write_dataset="write",
138
146
  scripts=scripts,
139
- selected_table_refs={TableRef("read", ("analytics",), "n_users")},
147
+ selected_table_refs={TableRef("read", ("analytics",), "n_users", "test_project")},
140
148
  existing_audit_tables={},
141
149
  incremental_field_name="name",
142
150
  incremental_field_values={"Alice"},
143
151
  )
144
152
 
145
153
  assert_queries_are_equal(
146
- session.add_context_to_script(scripts[TableRef("read", ("core",), "users")]).code,
154
+ session.add_context_to_script(
155
+ scripts[TableRef("read", ("core",), "users", "test_project")]
156
+ ).code,
147
157
  """
148
158
  SELECT
149
159
  id,
150
160
  -- #INCREMENTAL
151
161
  name,
152
162
  age
153
- FROM write.raw__users
163
+ FROM `test_project`.write.raw__users
154
164
  """,
155
165
  )
156
166
 
@@ -161,19 +171,21 @@ def test_incremental_field_with_just_incremental_table_selected(scripts):
161
171
  base_dataset="read",
162
172
  write_dataset="write",
163
173
  scripts=scripts,
164
- selected_table_refs={TableRef("read", ("core",), "users")},
174
+ selected_table_refs={TableRef("read", ("core",), "users", "test_project")},
165
175
  existing_audit_tables={},
166
176
  incremental_field_name="name",
167
177
  incremental_field_values={"Alice"},
168
178
  )
169
179
 
170
180
  assert_queries_are_equal(
171
- session.add_context_to_script(scripts[TableRef("read", ("core",), "users")]).code,
181
+ session.add_context_to_script(
182
+ scripts[TableRef("read", ("core",), "users", "test_project")]
183
+ ).code,
172
184
  """
173
185
  SELECT *
174
186
  FROM (
175
187
  SELECT id, name, age
176
- FROM write.raw__users
188
+ FROM `test_project`.write.raw__users
177
189
  )
178
190
  WHERE name IN ('Alice')
179
191
  """,
@@ -188,19 +200,23 @@ def test_incremental_field_with_just_incremental_table_selected_and_materialized
188
200
  base_dataset="read",
189
201
  write_dataset="write",
190
202
  scripts=scripts,
191
- selected_table_refs={TableRef("read", ("core",), "users")},
192
- existing_audit_tables={TableRef("read", ("raw",), "users"): DUMMY_TABLE_STATS},
203
+ selected_table_refs={TableRef("read", ("core",), "users", "test_project")},
204
+ existing_audit_tables={
205
+ TableRef("read", ("raw",), "users", "test_project"): DUMMY_TABLE_STATS
206
+ },
193
207
  incremental_field_name="name",
194
208
  incremental_field_values={"Alice"},
195
209
  )
196
210
 
197
211
  assert_queries_are_equal(
198
- session.add_context_to_script(scripts[TableRef("read", ("core",), "users")]).code,
212
+ session.add_context_to_script(
213
+ scripts[TableRef("read", ("core",), "users", "test_project")]
214
+ ).code,
199
215
  """
200
216
  SELECT *
201
217
  FROM (
202
218
  SELECT id, name, age
203
- FROM write.raw__users___audit
219
+ FROM `test_project`.write.raw__users___audit
204
220
  )
205
221
  WHERE name IN ('Alice')
206
222
  """,
@@ -215,27 +231,29 @@ def test_incremental_field_but_no_incremental_table_selected_and_yet_dependency_
215
231
  base_dataset="read",
216
232
  write_dataset="write",
217
233
  scripts=scripts,
218
- selected_table_refs={TableRef("read", ("analytics",), "n_users")},
234
+ selected_table_refs={TableRef("read", ("analytics",), "n_users", "test_project")},
219
235
  existing_audit_tables={
220
- TableRef("read", ("core",), "users"): DUMMY_TABLE_STATS,
236
+ TableRef("read", ("core",), "users", "test_project"): DUMMY_TABLE_STATS,
221
237
  },
222
238
  incremental_field_name="name",
223
239
  incremental_field_values={"Alice"},
224
240
  )
225
241
 
226
242
  assert_queries_are_equal(
227
- session.add_context_to_script(scripts[TableRef("read", ("analytics",), "n_users")]).code,
243
+ session.add_context_to_script(
244
+ scripts[TableRef("read", ("analytics",), "n_users", "test_project")]
245
+ ).code,
228
246
  """
229
247
  SELECT COUNT(*)
230
248
  FROM (
231
249
  SELECT *
232
- FROM write.core__users___audit
250
+ FROM `test_project`.write.core__users___audit
233
251
  WHERE name IN ('Alice')
234
252
 
235
253
  UNION ALL
236
254
 
237
255
  SELECT *
238
- FROM write.core__users
256
+ FROM `test_project`.write.core__users
239
257
  WHERE name NOT IN ('Alice')
240
258
  )
241
259
  """,
@@ -255,27 +273,29 @@ def test_incremental_field_but_no_incremental_table_selected_and_yet_dependency_
255
273
  base_dataset="read",
256
274
  write_dataset="write",
257
275
  scripts=scripts,
258
- selected_table_refs={TableRef("read", ("analytics",), "n_users")},
276
+ selected_table_refs={TableRef("read", ("analytics",), "n_users", "test_project")},
259
277
  existing_audit_tables={
260
- TableRef("read", ("core",), "users"): DUMMY_TABLE_STATS,
278
+ TableRef("read", ("core",), "users", "test_project"): DUMMY_TABLE_STATS,
261
279
  },
262
280
  incremental_field_name="name",
263
281
  incremental_field_values={"Alice"},
264
282
  )
265
283
 
266
284
  assert_queries_are_equal(
267
- session.add_context_to_script(scripts[TableRef("read", ("analytics",), "n_users")]).code,
285
+ session.add_context_to_script(
286
+ scripts[TableRef("read", ("analytics",), "n_users", "test_project")]
287
+ ).code,
268
288
  """
269
289
  SELECT COUNT(*)
270
290
  FROM (
271
291
  SELECT *
272
- FROM `write-project-id`.write.core__users___audit
292
+ FROM `test_project`.write.core__users___audit
273
293
  WHERE name IN ('Alice')
274
294
 
275
295
  UNION ALL
276
296
 
277
297
  SELECT *
278
- FROM `write-project-id`.write.core__users
298
+ FROM `test_project`.write.core__users
279
299
  WHERE name NOT IN ('Alice')
280
300
  )
281
301
  """,
@@ -12,11 +12,17 @@ from lea.table_ref import TableRef
12
12
  [
13
13
  pytest.param(table_ref, expected, id=str(table_ref))
14
14
  for table_ref, expected in [
15
- (TableRef("my_dataset", ("my_schema",), "my_table"), "my_dataset.my_schema.my_table"),
16
- (TableRef("my_dataset", (), "my_table"), "my_dataset.my_table"),
17
15
  (
18
- TableRef("my_dataset", ("my_schema", "my_subschema"), "my_table"),
19
- "my_dataset.my_schema.my_subschema.my_table",
16
+ TableRef("my_dataset", ("my_schema",), "my_table", "my_project"),
17
+ "my_project.my_dataset.my_schema.my_table",
18
+ ),
19
+ (
20
+ TableRef("my_dataset", (), "my_table", "my_project"),
21
+ "my_project.my_dataset.my_table",
22
+ ),
23
+ (
24
+ TableRef("my_dataset", ("my_schema", "my_subschema"), "my_table", "my_project"),
25
+ "my_project.my_dataset.my_schema.my_subschema.my_table",
20
26
  ),
21
27
  ]
22
28
  ],
@@ -31,16 +37,16 @@ def test_str(table_ref, expected):
31
37
  pytest.param(table_ref, expected, id=str(table_ref))
32
38
  for table_ref, expected in [
33
39
  (
34
- TableRef("my_dataset", ("my_schema",), "my_table"),
40
+ TableRef("my_dataset", ("my_schema",), "my_table", None),
35
41
  "TableRef(dataset='my_dataset', schema=('my_schema',), name='my_table', project=None)",
36
42
  ),
37
43
  (
38
- TableRef("my_dataset", (), "my_table"),
44
+ TableRef("my_dataset", (), "my_table", None),
39
45
  "TableRef(dataset='my_dataset', schema=(), name='my_table', project=None)",
40
46
  ),
41
47
  (
42
- TableRef("my_dataset", ("my_schema", "my_subschema"), "my_table"),
43
- "TableRef(dataset='my_dataset', schema=('my_schema', 'my_subschema'), name='my_table', project=None)",
48
+ TableRef("my_dataset", ("my_schema", "my_subschema"), "my_table", "my_project"),
49
+ "TableRef(dataset='my_dataset', schema=('my_schema', 'my_subschema'), name='my_table', project='my_project')",
44
50
  ),
45
51
  ]
46
52
  ],
@@ -52,5 +58,5 @@ def test_repr(table_ref, expected):
52
58
  def test_from_path():
53
59
  scripts_dir = pathlib.Path("my_dataset")
54
60
  relative_path = pathlib.Path("my_schema/my_table.sql")
55
- table_ref = TableRef.from_path(scripts_dir, relative_path)
56
- assert table_ref == TableRef("my_dataset", ("my_schema",), "my_table")
61
+ table_ref = TableRef.from_path(scripts_dir, relative_path, "my_project")
62
+ assert table_ref == TableRef("my_dataset", ("my_schema",), "my_table", "my_project")
@@ -5,7 +5,7 @@ name = "lea-cli"
5
5
  packages = [
6
6
  {include = "lea", from = "."},
7
7
  ]
8
- version = "0.7.2"
8
+ version = "0.7.3"
9
9
 
10
10
  [tool.poetry.dependencies]
11
11
  click = "^8.1.7"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes