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.
- tinybird/__cli__.py +2 -2
- tinybird/ch_utils/constants.py +1 -0
- tinybird/ch_utils/engine.py +90 -54
- tinybird/client.py +80 -89
- tinybird/config.py +0 -4
- tinybird/datafile_common.py +72 -45
- tinybird/datatypes.py +34 -12
- tinybird/feedback_manager.py +5 -0
- tinybird/git_settings.py +1 -3
- tinybird/sql.py +3 -10
- tinybird/sql_template.py +128 -98
- tinybird/sql_template_fmt.py +10 -15
- tinybird/sql_toolset.py +63 -11
- tinybird/syncasync.py +9 -54
- tinybird/tb_cli_modules/branch.py +4 -25
- tinybird/tb_cli_modules/cicd.py +2 -0
- tinybird/tb_cli_modules/cli.py +5 -9
- tinybird/tb_cli_modules/common.py +33 -87
- tinybird/tb_cli_modules/config.py +1 -2
- tinybird/tb_cli_modules/datasource.py +15 -55
- tinybird/tb_cli_modules/pipe.py +49 -34
- tinybird/tb_cli_modules/tinyunit/tinyunit.py +4 -4
- tinybird/tb_cli_modules/token.py +1 -2
- tinybird/tb_cli_modules/workspace.py +11 -13
- tinybird/tornado_template.py +227 -83
- tinybird/utils/__init__.py +0 -0
- tinybird/utils/bools.py +19 -0
- {tinybird_cli-6.5.6.dev0.dist-info → tinybird_cli-6.5.8.dev0.dist-info}/METADATA +15 -1
- tinybird_cli-6.5.8.dev0.dist-info/RECORD +45 -0
- tinybird_cli-6.5.6.dev0.dist-info/RECORD +0 -43
- {tinybird_cli-6.5.6.dev0.dist-info → tinybird_cli-6.5.8.dev0.dist-info}/WHEEL +0 -0
- {tinybird_cli-6.5.6.dev0.dist-info → tinybird_cli-6.5.8.dev0.dist-info}/entry_points.txt +0 -0
- {tinybird_cli-6.5.6.dev0.dist-info → tinybird_cli-6.5.8.dev0.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
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
|
-
"--
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
201
|
-
if
|
|
202
|
-
|
|
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
|
-
"--
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
220
|
+
wait=wait,
|
|
259
221
|
)
|
|
260
|
-
if
|
|
261
|
-
_echo_v1_import_jobs_queued(job_ids
|
|
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",
|
|
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
|
-
|
|
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)
|
tinybird/tb_cli_modules/pipe.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
332
|
-
|
|
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
|
-
|
|
347
|
-
|
|
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
|
-
|
|
362
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
|
757
|
-
|
|
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
|
-
|
|
761
|
-
|
|
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
|
-
|
|
764
|
-
|
|
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
|
-
|
|
773
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
-
|
|
105
|
+
if self.error:
|
|
106
106
|
return Status.build_error_with_msg(self.error)
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
tinybird/tb_cli_modules/token.py
CHANGED
|
@@ -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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
[
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
|