tinybird-cli 6.5.6.dev0__py3-none-any.whl → 6.5.8.dev0__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.
@@ -37,9 +37,7 @@ from tinybird.tb_cli_modules.common import (
37
37
  wait_job,
38
38
  )
39
39
  from tinybird.tb_cli_modules.config import CLIConfig
40
- from tinybird.tb_cli_modules.exceptions import CLIDatasourceException, CLIException
41
-
42
- EXPERIMENTAL_FEATURE_USE_V1 = "use_v1"
40
+ from tinybird.tb_cli_modules.exceptions import CLIDatasourceException
43
41
 
44
42
 
45
43
  @cli.group()
@@ -54,28 +52,6 @@ def _echo_v1_import_jobs_queued(job_ids: list[str], operation: str) -> None:
54
52
  click.echo(FeedbackManager.info_import_job_status(job_id=job_id))
55
53
 
56
54
 
57
- async def _wait_for_v1_import_jobs(client: TinyB, job_ids: list[str], operation: str) -> None:
58
- for job_id in job_ids:
59
- try:
60
- await wait_job(client, job_id, f"/v0/jobs/{job_id}", f"{operation} import")
61
- except CLIException:
62
- if await _echo_v1_import_job_details(client, job_id):
63
- raise click.exceptions.Exit(1)
64
- raise
65
- click.echo(FeedbackManager.success_import_job_completed(operation=operation, job_id=job_id))
66
-
67
-
68
- async def _echo_v1_import_job_details(client: TinyB, job_id: str) -> bool:
69
- try:
70
- job = await client.job(job_id)
71
- except Exception:
72
- return False
73
- click.echo(FeedbackManager.info_job(job=job_id))
74
- echo_safe_humanfriendly_tables_format_smart_table([job.values()], column_names=job.keys())
75
- click.echo("\n")
76
- return True
77
-
78
-
79
55
  @datasource.command(name="ls")
80
56
  @click.option("--match", default=None, help="Retrieve any resources matching the pattern. eg --match _test")
81
57
  @click.option(
@@ -166,12 +142,10 @@ async def datasource_ls(ctx: Context, match: Optional[str], format_: str):
166
142
  hidden=True,
167
143
  )
168
144
  @click.option("--concurrency", help="How many files to submit concurrently", default=1, hidden=True)
169
- @click.option("--wait", is_flag=True, default=False, help="Wait for a v1 import job to finish.")
170
145
  @click.option(
171
- "--experimental",
172
- type=click.Choice([EXPERIMENTAL_FEATURE_USE_V1]),
173
- multiple=True,
174
- help="Enable an experimental feature. May be specified multiple times.",
146
+ "--wait/--no-wait",
147
+ default=True,
148
+ help="Wait for the import to finish (default); use --no-wait to only queue it.",
175
149
  )
176
150
  @click.pass_context
177
151
  @coro
@@ -183,7 +157,6 @@ async def datasource_append(
183
157
  incremental: Optional[str],
184
158
  ignore_empty: bool,
185
159
  concurrency: int,
186
- experimental: tuple[str, ...],
187
160
  wait: bool,
188
161
  ):
189
162
  """
@@ -197,14 +170,9 @@ async def datasource_append(
197
170
 
198
171
  if not url:
199
172
  raise CLIDatasourceException(FeedbackManager.error_missing_url(datasource=datasource_name))
200
- use_v1 = EXPERIMENTAL_FEATURE_USE_V1 in experimental
201
- if wait and not use_v1:
202
- raise CLIDatasourceException("--wait requires --experimental=use_v1.")
203
- job_ids = await push_data(ctx, datasource_name, url, mode="append", concurrency=concurrency, use_v1=use_v1)
204
- if use_v1:
205
- _echo_v1_import_jobs_queued(job_ids or [], "Append")
206
- if wait:
207
- await _wait_for_v1_import_jobs(ctx.obj["client"], job_ids or [], "Append")
173
+ job_ids = await push_data(ctx, datasource_name, url, mode="append", concurrency=concurrency, wait=wait)
174
+ if job_ids:
175
+ _echo_v1_import_jobs_queued(job_ids, "Append")
208
176
 
209
177
 
210
178
  @datasource.command(name="replace")
@@ -212,12 +180,10 @@ async def datasource_append(
212
180
  @click.argument("url", nargs=-1)
213
181
  @click.option("--sql-condition", default=None, help="SQL WHERE condition to replace data", hidden=True)
214
182
  @click.option("--skip-incompatible-partition-key", is_flag=True, default=False, hidden=True)
215
- @click.option("--wait", is_flag=True, default=False, help="Wait for a v1 import job to finish.")
216
183
  @click.option(
217
- "--experimental",
218
- type=click.Choice([EXPERIMENTAL_FEATURE_USE_V1]),
219
- multiple=True,
220
- help="Enable an experimental feature. May be specified multiple times.",
184
+ "--wait/--no-wait",
185
+ default=True,
186
+ help="Wait for the import to finish (default); use --no-wait to only queue it.",
221
187
  )
222
188
  @click.pass_context
223
189
  @coro
@@ -227,7 +193,6 @@ async def datasource_replace(
227
193
  url,
228
194
  sql_condition,
229
195
  skip_incompatible_partition_key,
230
- experimental: tuple[str, ...],
231
196
  wait: bool,
232
197
  ):
233
198
  """
@@ -245,9 +210,6 @@ async def datasource_replace(
245
210
  replace_options = set()
246
211
  if skip_incompatible_partition_key:
247
212
  replace_options.add("skip_incompatible_partition_key")
248
- use_v1 = EXPERIMENTAL_FEATURE_USE_V1 in experimental
249
- if wait and not use_v1:
250
- raise CLIDatasourceException("--wait requires --experimental=use_v1.")
251
213
  job_ids = await push_data(
252
214
  ctx,
253
215
  datasource_name,
@@ -255,12 +217,10 @@ async def datasource_replace(
255
217
  mode="replace",
256
218
  sql_condition=sql_condition,
257
219
  replace_options=replace_options,
258
- use_v1=use_v1,
220
+ wait=wait,
259
221
  )
260
- if use_v1:
261
- _echo_v1_import_jobs_queued(job_ids or [], "Replace")
262
- if wait:
263
- await _wait_for_v1_import_jobs(ctx.obj["client"], job_ids or [], "Replace")
222
+ if job_ids:
223
+ _echo_v1_import_jobs_queued(job_ids, "Replace")
264
224
 
265
225
 
266
226
  @datasource.command(name="analyze")
@@ -425,7 +385,7 @@ async def datasource_truncate(ctx, datasource_name, yes, cascade):
425
385
 
426
386
  @datasource.command(name="delete")
427
387
  @click.argument("datasource_name")
428
- @click.option("--sql-condition", default=None, help="SQL WHERE condition to remove rows", hidden=True, required=True)
388
+ @click.option("--sql-condition", help="SQL WHERE condition to remove rows", hidden=True, required=True)
429
389
  @click.option("--yes", is_flag=True, default=False, help="Do not ask for confirmation")
430
390
  @click.option(
431
391
  "--wait/--no-wait",
@@ -557,7 +517,7 @@ async def datasource_delete_rows(
557
517
  )
558
518
  )
559
519
  break
560
- elif res["status"] == "error":
520
+ if res["status"] == "error":
561
521
  print("\n") # noqa: T201
562
522
  raise CLIDatasourceException(FeedbackManager.error_exception(error=res["error"]))
563
523
  await asyncio.sleep(1)
@@ -16,7 +16,15 @@ from click import Context
16
16
  from tinybird import context
17
17
  from tinybird.client import AuthNoTokenException, DoesNotExistException, TinyB
18
18
  from tinybird.config import DEFAULT_API_HOST, FeatureFlags
19
- from tinybird.datafile_common import PipeNodeTypes, PipeTypes, folder_push, get_name_version, process_file, wait_job
19
+ from tinybird.datafile_common import (
20
+ PipeNodeTypes,
21
+ PipeTypes,
22
+ folder_push,
23
+ get_name_version,
24
+ local_copy_node_uses_on_demand_compute,
25
+ process_file,
26
+ wait_job,
27
+ )
20
28
  from tinybird.feedback_manager import FeedbackManager
21
29
  from tinybird.tb_cli_modules.branch import warn_if_in_live
22
30
  from tinybird.tb_cli_modules.cli import cli
@@ -272,7 +280,7 @@ async def pipe_populate(
272
280
  if not materialized_ids:
273
281
  raise CLIPipeException(FeedbackManager.error_populate_no_materialized_in_pipe(pipe=pipe_name))
274
282
 
275
- elif len(materialized_ids) > 1:
283
+ if len(materialized_ids) > 1:
276
284
  raise CLIPipeException(FeedbackManager.error_populate_several_materialized_in_pipe(pipe=pipe_name))
277
285
 
278
286
  node = materialized_ids[0]
@@ -328,9 +336,8 @@ async def pipe_unlink_output_node(
328
336
 
329
337
  if not node_uid:
330
338
  raise CLIPipeException(FeedbackManager.error_unlinking_pipe_not_linked(pipe=pipe_name_or_id))
331
- else:
332
- await client.pipe_unlink_materialized(pipe["name"], node_uid)
333
- click.echo(FeedbackManager.success_pipe_unlinked(pipe=pipe["name"]))
339
+ await client.pipe_unlink_materialized(pipe["name"], node_uid)
340
+ click.echo(FeedbackManager.success_pipe_unlinked(pipe=pipe["name"]))
334
341
 
335
342
  if pipe["type"] == PipeTypes.COPY:
336
343
  click.echo(FeedbackManager.info_unlinking_copy_pipe(pipe=pipe["name"]))
@@ -343,9 +350,8 @@ async def pipe_unlink_output_node(
343
350
 
344
351
  if not node_uid:
345
352
  raise CLIPipeException(FeedbackManager.error_unlinking_pipe_not_linked(pipe=pipe_name_or_id))
346
- else:
347
- await client.pipe_remove_copy(pipe["name"], node_uid)
348
- click.echo(FeedbackManager.success_pipe_unlinked(pipe=pipe["name"]))
353
+ await client.pipe_remove_copy(pipe["name"], node_uid)
354
+ click.echo(FeedbackManager.success_pipe_unlinked(pipe=pipe["name"]))
349
355
 
350
356
  if pipe["type"] == PipeTypes.DATA_SINK:
351
357
  click.echo(FeedbackManager.info_unlinking_sink_pipe(pipe=pipe["name"]))
@@ -358,9 +364,8 @@ async def pipe_unlink_output_node(
358
364
 
359
365
  if not node_uid:
360
366
  raise CLIPipeException(FeedbackManager.error_unlinking_pipe_not_linked(pipe=pipe_name_or_id))
361
- else:
362
- await client.pipe_remove_sink(pipe["name"], node_uid)
363
- click.echo(FeedbackManager.success_pipe_unlinked(pipe=pipe["name"]))
367
+ await client.pipe_remove_sink(pipe["name"], node_uid)
368
+ click.echo(FeedbackManager.success_pipe_unlinked(pipe=pipe["name"]))
364
369
 
365
370
  if pipe["type"] == PipeTypes.STREAM:
366
371
  click.echo(FeedbackManager.info_unlinking_stream_pipe(pipe=pipe["name"]))
@@ -368,9 +373,8 @@ async def pipe_unlink_output_node(
368
373
 
369
374
  if not node_uid:
370
375
  raise CLIPipeException(FeedbackManager.error_unlinking_pipe_not_linked(pipe=pipe_name_or_id))
371
- else:
372
- await client.pipe_remove_stream(pipe["name"], node_uid)
373
- click.echo(FeedbackManager.success_pipe_unlinked(pipe=pipe["name"]))
376
+ await client.pipe_remove_stream(pipe["name"], node_uid)
377
+ click.echo(FeedbackManager.success_pipe_unlinked(pipe=pipe["name"]))
374
378
 
375
379
  except AuthNoTokenException:
376
380
  raise
@@ -753,30 +757,41 @@ async def pipe_copy_run(
753
757
 
754
758
  params = dict(key_value.split("=") for key_value in param) if param else {}
755
759
 
756
- if yes or click.confirm(FeedbackManager.warning_confirm_copy_pipe(pipe=pipe_name_or_id)):
757
- click.echo(FeedbackManager.info_copy_job_running(pipe=pipe_name_or_id))
758
- client: TinyB = ctx.ensure_object(dict)["client"]
760
+ if not yes and not click.confirm(FeedbackManager.warning_confirm_copy_pipe(pipe=pipe_name_or_id)):
761
+ return
759
762
 
760
- try:
761
- response = await client.pipe_run_copy(pipe_name_or_id, params, mode, on_demand_compute=on_demand_compute)
763
+ if (
764
+ on_demand_compute
765
+ and not yes
766
+ and local_copy_node_uses_on_demand_compute(".", pipe_name_or_id) is False
767
+ and not click.confirm(
768
+ FeedbackManager.warning_confirm_on_demand_compute_differs_from_datafile(pipe=pipe_name_or_id),
769
+ default=False,
770
+ )
771
+ ):
772
+ return
762
773
 
763
- job_id = response["job"]["job_id"]
764
- job_url = response["job"]["job_url"]
765
- target_datasource_id = response["tags"]["copy_target_datasource"]
766
- target_datasource = await client.get_datasource(target_datasource_id)
767
- target_datasource_name = target_datasource["name"]
768
- click.echo(
769
- FeedbackManager.success_copy_job_created(target_datasource=target_datasource_name, job_url=job_url)
770
- )
774
+ click.echo(FeedbackManager.info_copy_job_running(pipe=pipe_name_or_id))
775
+ client: TinyB = ctx.ensure_object(dict)["client"]
771
776
 
772
- if wait:
773
- await wait_job(client, job_id, job_url, "** Copying data")
774
- click.echo(FeedbackManager.success_data_copied_to_ds(target_datasource=target_datasource_name))
777
+ try:
778
+ response = await client.pipe_run_copy(pipe_name_or_id, params, mode, on_demand_compute=on_demand_compute)
775
779
 
776
- except AuthNoTokenException:
777
- raise
778
- except Exception as e:
779
- raise CLIPipeException(FeedbackManager.error_creating_copy_job(error=e))
780
+ job_id = response["job"]["job_id"]
781
+ job_url = response["job"]["job_url"]
782
+ target_datasource_id = response["tags"]["copy_target_datasource"]
783
+ target_datasource = await client.get_datasource(target_datasource_id)
784
+ target_datasource_name = target_datasource["name"]
785
+ click.echo(FeedbackManager.success_copy_job_created(target_datasource=target_datasource_name, job_url=job_url))
786
+
787
+ if wait:
788
+ await wait_job(client, job_id, job_url, "** Copying data")
789
+ click.echo(FeedbackManager.success_data_copied_to_ds(target_datasource=target_datasource_name))
790
+
791
+ except AuthNoTokenException:
792
+ raise
793
+ except Exception as e:
794
+ raise CLIPipeException(FeedbackManager.error_creating_copy_job(error=e))
780
795
 
781
796
 
782
797
  @pipe_copy.command(name="resume", short_help="Resume a paused copy pipe")
@@ -102,18 +102,18 @@ class TestResult:
102
102
  def status(self) -> Status:
103
103
  if len(self.data) > 0:
104
104
  return FAILED
105
- elif self.error:
105
+ if self.error:
106
106
  return Status.build_error_with_msg(self.error)
107
- elif (
107
+ if (
108
108
  self.max_bytes_read is not None
109
109
  and self.max_elapsed_time is not None
110
110
  and self.read_bytes > self.max_bytes_read
111
111
  and self.elapsed_time > self.max_elapsed_time
112
112
  ):
113
113
  return PASS_OVER_TIME_AND_READ_BYTES
114
- elif self.max_bytes_read is not None and self.read_bytes > self.max_bytes_read:
114
+ if self.max_bytes_read is not None and self.read_bytes > self.max_bytes_read:
115
115
  return PASS_OVER_READ_BYTES
116
- elif self.max_elapsed_time is not None and self.elapsed_time > self.max_elapsed_time:
116
+ if self.max_elapsed_time is not None and self.elapsed_time > self.max_elapsed_time:
117
117
  return PASS_OVER_TIME
118
118
  return PASS
119
119
 
@@ -6,11 +6,10 @@ import pyperclip
6
6
  from click import Context
7
7
  from humanfriendly import parse_timespan
8
8
 
9
- from tinybird.client import AuthNoTokenException, TinyB
9
+ from tinybird.client import AuthNoTokenException, DoesNotExistException, TinyB
10
10
  from tinybird.feedback_manager import FeedbackManager
11
11
  from tinybird.tb_cli_modules.cli import cli
12
12
  from tinybird.tb_cli_modules.common import (
13
- DoesNotExistException,
14
13
  coro,
15
14
  echo_safe_humanfriendly_tables_format_smart_table,
16
15
  )
@@ -115,17 +115,16 @@ async def clear_workspace(ctx: Context, yes: bool, dry_run: bool) -> None:
115
115
  if config["id"] == workspace["id"]:
116
116
  if workspace.get("is_branch"):
117
117
  raise CLIWorkspaceException(FeedbackManager.error_not_allowed_in_branch(cli="tb"))
118
- else:
119
- click.echo(FeedbackManager.info_current_workspace())
120
- table.append(
121
- [
122
- workspace["name"],
123
- workspace["id"],
124
- workspace["role"],
125
- _get_workspace_plan_name(workspace["plan"]),
126
- True,
127
- ]
128
- )
118
+ click.echo(FeedbackManager.info_current_workspace())
119
+ table.append(
120
+ [
121
+ workspace["name"],
122
+ workspace["id"],
123
+ workspace["role"],
124
+ _get_workspace_plan_name(workspace["plan"]),
125
+ True,
126
+ ]
127
+ )
129
128
  break
130
129
 
131
130
  echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
@@ -178,8 +177,7 @@ async def clear_workspace(ctx: Context, yes: bool, dry_run: bool) -> None:
178
177
  except Exception as e:
179
178
  if "is a Shared Data Source" in str(e):
180
179
  raise CLIWorkspaceException(FeedbackManager.error_operation_can_not_be_performed(error=e))
181
- else:
182
- raise CLIWorkspaceException(FeedbackManager.error_exception(error=e))
180
+ raise CLIWorkspaceException(FeedbackManager.error_exception(error=e))
183
181
  else:
184
182
  click.echo(FeedbackManager.info_dry_removing_datasource(datasource=ds_name))
185
183