tinybird 4.6.13.dev0__tar.gz → 4.6.15.dev0__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.
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/PKG-INFO +6 -1
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/client.py +34 -10
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/datafile/common.py +1 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/datafile/parse_pipe.py +22 -5
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/feedback_manager.py +3 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/sql_template.py +1 -1
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/sql_toolset.py +1 -1
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/__cli__.py +2 -2
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/client.py +25 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/common.py +15 -1
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/config.py +1 -1
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/build.py +1 -1
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/pipe_checker.py +2 -2
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/playground.py +1 -1
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datasource.py +72 -4
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/common.py +15 -1
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/config.py +1 -1
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/datasource.py +68 -4
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tornado_template.py +5 -5
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird.egg-info/PKG-INFO +6 -1
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/setup.cfg +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/__cli__.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/ch_utils/engine.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/check_pypi.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/config.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/context.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/datafile/exceptions.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/datafile/parse_connection.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/datafile/parse_datasource.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/datatypes.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/git_settings.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/iterating/__init__.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/iterating/data_branch_modes.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/prompts.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/service_datasources.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/sql.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/syncasync.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/check_pypi.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/cli.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/config.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/branch.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/build.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/build_common.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/cicd.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/cli.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/connection.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/connection_dynamodb.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/connection_kafka.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/connection_s3.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/copy.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/create.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/build_common.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/diff.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/fixture.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/format_common.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/format_connection.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/pull.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/deployment.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/deployment_common.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/deprecations.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/endpoint.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/exceptions.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/feedback_manager.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/fmt.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/info.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/infra.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/job.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/job_common.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/llm.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/llm_utils.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/local.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/local_common.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/local_logs.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/login.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/login_common.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/logout.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/logs.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/materialization.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/open.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/pipe.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/preview.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/project.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/project_commands.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/py_project.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/query_output.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/regions.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/secret.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/secret_common.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/sink.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/table.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/telemetry.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/test.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/test_common.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/token.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/ts_project.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/watch.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/workspace.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/workspace_members.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/cli.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/fmt.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/tag.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird.egg-info/SOURCES.txt +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird.egg-info/dependency_links.txt +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird.egg-info/entry_points.txt +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird.egg-info/requires.txt +0 -0
- {tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybird
|
|
3
|
-
Version: 4.6.
|
|
3
|
+
Version: 4.6.15.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/forward/commands
|
|
6
6
|
Author: Tinybird
|
|
@@ -52,6 +52,11 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
52
52
|
Changelog
|
|
53
53
|
----------
|
|
54
54
|
|
|
55
|
+
4.6.13
|
|
56
|
+
********
|
|
57
|
+
|
|
58
|
+
- `Added` ``tb datasource append`` and ``tb datasource replace`` support the experimental ``--experimental=use_v1`` option for local CSV, NDJSON, and Parquet files. It submits the import through the v1 endpoint and prints the queued job ID; pass ``--wait`` to wait for completion.
|
|
59
|
+
|
|
55
60
|
4.6.12
|
|
56
61
|
********
|
|
57
62
|
|
|
@@ -441,6 +441,7 @@ class TinyB:
|
|
|
441
441
|
sql_condition: Optional[str] = None,
|
|
442
442
|
format: str = "csv",
|
|
443
443
|
replace_options: Optional[Set[str]] = None,
|
|
444
|
+
use_v1: bool = False,
|
|
444
445
|
):
|
|
445
446
|
params = {"name": datasource_name, "mode": mode, "format": format, "debug": "blocks_block_log"}
|
|
446
447
|
|
|
@@ -452,20 +453,43 @@ class TinyB:
|
|
|
452
453
|
|
|
453
454
|
async with aiofiles.open(file, "rb") as content:
|
|
454
455
|
file_content = await content.read()
|
|
455
|
-
if format == "csv":
|
|
456
|
-
files = {"csv": ("csv", file_content)}
|
|
457
|
-
else:
|
|
458
|
-
files = {"ndjson": ("ndjson", file_content)}
|
|
459
456
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
457
|
+
if use_v1:
|
|
458
|
+
v1_params = {"format": format}
|
|
459
|
+
if sql_condition:
|
|
460
|
+
v1_params["replace_condition"] = sql_condition
|
|
461
|
+
if replace_options:
|
|
462
|
+
for option in list(replace_options):
|
|
463
|
+
v1_params[option] = "true"
|
|
464
|
+
content_types = {
|
|
465
|
+
"csv": "text/csv",
|
|
466
|
+
"ndjson": "application/x-ndjson",
|
|
467
|
+
"parquet": "application/vnd.apache.parquet",
|
|
468
|
+
}
|
|
469
|
+
headers = {"Content-Type": content_types[format]}
|
|
470
|
+
if str(file).endswith(".gz"):
|
|
471
|
+
headers["Content-Encoding"] = "gzip"
|
|
472
|
+
return await self._req(
|
|
473
|
+
f"/v1/datasources/{quote(datasource_name, safe='')}/{mode}?{urlencode(v1_params, safe='')}",
|
|
474
|
+
data=file_content,
|
|
475
|
+
headers=headers,
|
|
463
476
|
method="POST",
|
|
464
477
|
)
|
|
465
|
-
if status_callback:
|
|
466
|
-
status_callback(res)
|
|
467
478
|
|
|
468
|
-
|
|
479
|
+
if format == "csv":
|
|
480
|
+
files = {"csv": ("csv", file_content)}
|
|
481
|
+
else:
|
|
482
|
+
files = {"ndjson": ("ndjson", file_content)}
|
|
483
|
+
|
|
484
|
+
res = await self._req(
|
|
485
|
+
f"v0/datasources?{urlencode(params, safe='')}",
|
|
486
|
+
files=files,
|
|
487
|
+
method="POST",
|
|
488
|
+
)
|
|
489
|
+
if status_callback:
|
|
490
|
+
status_callback(res)
|
|
491
|
+
|
|
492
|
+
return res
|
|
469
493
|
|
|
470
494
|
async def datasource_truncate(self, datasource_name: str):
|
|
471
495
|
return await self._req(f"/v0/datasources/{datasource_name}/truncate", method="POST", data="")
|
|
@@ -2091,6 +2091,7 @@ def parse(
|
|
|
2091
2091
|
"sql": sql("sql"),
|
|
2092
2092
|
"version": version,
|
|
2093
2093
|
"deployment_method": assign_var("deployment_method", allowed_values={"alter"}),
|
|
2094
|
+
"export_service": export_service, # Deprecated
|
|
2094
2095
|
"export_connection_name": assign_var("export_connection_name"),
|
|
2095
2096
|
"export_schedule": assign_var("export_schedule"),
|
|
2096
2097
|
"export_bucket_uri": assign_var("export_bucket_uri"),
|
|
@@ -52,20 +52,35 @@ def parse_pipe(
|
|
|
52
52
|
if "type" in node:
|
|
53
53
|
node["type"] = node["type"].lower()
|
|
54
54
|
sql = node.get("sql", "")
|
|
55
|
-
|
|
55
|
+
source_sql = sql.strip()
|
|
56
|
+
if not source_sql:
|
|
57
|
+
raise click.ClickException(
|
|
58
|
+
FeedbackManager.error_parsing_node(node=node["name"], pipe=filename, error="Empty query")
|
|
59
|
+
)
|
|
60
|
+
if source_sql.startswith("%"):
|
|
56
61
|
secrets_list: Optional[List[str]] = None
|
|
57
62
|
if secrets:
|
|
58
63
|
secrets_list = list(secrets.keys())
|
|
59
64
|
# Setting test_mode=True to ignore errors on required parameters and
|
|
60
65
|
# secrets_in_test_mode=False to raise errors on missing secrets
|
|
61
66
|
sql, _, variable_warnings = render_sql_template(
|
|
62
|
-
|
|
67
|
+
source_sql[1:],
|
|
63
68
|
name=node["name"],
|
|
64
69
|
secrets=secrets_list,
|
|
65
70
|
test_mode=True,
|
|
66
71
|
secrets_in_test_mode=ignore_secrets,
|
|
67
72
|
)
|
|
68
73
|
doc.warnings = variable_warnings
|
|
74
|
+
if not sql.strip():
|
|
75
|
+
raise click.ClickException(
|
|
76
|
+
FeedbackManager.error_parsing_node(
|
|
77
|
+
node=node["name"],
|
|
78
|
+
pipe=filename,
|
|
79
|
+
error="Template renders to an empty query without parameters",
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
sql = source_sql
|
|
69
84
|
# it'll fail with a ModuleNotFoundError when the toolset is not available but it returns the parsed doc
|
|
70
85
|
from tinybird.sql_toolset import format_sql as toolset_format_sql
|
|
71
86
|
|
|
@@ -79,13 +94,15 @@ def parse_pipe(
|
|
|
79
94
|
)
|
|
80
95
|
)
|
|
81
96
|
except ValueError as e:
|
|
82
|
-
|
|
97
|
+
source_sql = node.get("sql", "")
|
|
98
|
+
source_sql_stripped = source_sql.strip()
|
|
99
|
+
t, template_variables, _ = get_template_and_variables(source_sql, name=node["name"])
|
|
83
100
|
|
|
84
|
-
if
|
|
101
|
+
if not source_sql_stripped.startswith("%") and len(template_variables) > 0:
|
|
85
102
|
raise click.ClickException(FeedbackManager.error_template_start(filename=filename))
|
|
86
103
|
raise click.ClickException(
|
|
87
104
|
FeedbackManager.error_parsing_file(
|
|
88
|
-
filename=filename, lineno="", error=f"{str(e)} + SQL(value error): {
|
|
105
|
+
filename=filename, lineno="", error=f"{str(e)} + SQL(value error): {source_sql}"
|
|
89
106
|
)
|
|
90
107
|
)
|
|
91
108
|
except UnClosedIfError as e:
|
|
@@ -864,6 +864,9 @@ class FeedbackManager:
|
|
|
864
864
|
success_total_rows = success_message("** Total rows in {datasource}: {total_rows}")
|
|
865
865
|
success_appended_datasource = success_message("** Data appended to Data Source '{datasource}' successfully!")
|
|
866
866
|
success_replaced_datasource = success_message("** Data replaced in Data Source '{datasource}' successfully!")
|
|
867
|
+
success_import_job_queued = success_message("** {operation} import job queued: {job_id}")
|
|
868
|
+
success_import_job_completed = success_message("** {operation} import completed: {job_id}")
|
|
869
|
+
info_import_job_status = info_message("Check status: tb job details {job_id}")
|
|
867
870
|
success_auth = success_message(
|
|
868
871
|
"** Auth successful! \n** Configuration written to .tinyb file, consider adding it to .gitignore"
|
|
869
872
|
)
|
|
@@ -1396,7 +1396,7 @@ _namespace = {
|
|
|
1396
1396
|
}
|
|
1397
1397
|
|
|
1398
1398
|
|
|
1399
|
-
reserved_vars =
|
|
1399
|
+
reserved_vars = {"_tt_tmp", "_tt_append", "isinstance", "str", "error", "custom_error", *list(vars(builtins))}
|
|
1400
1400
|
for p in DEFAULT_PARAM_NAMES: # we handle these in an specific manner
|
|
1401
1401
|
reserved_vars.discard(p) # `format` is part of builtins
|
|
1402
1402
|
# Allow 'id' to be used as a template parameter - https://gitlab.com/tinybird/analytics/-/issues/19119
|
|
@@ -643,7 +643,7 @@ def replace_tables(
|
|
|
643
643
|
|
|
644
644
|
if current_replacements:
|
|
645
645
|
# We need to transform the dictionary into something cacheable, so a sorted tuple of tuples it is
|
|
646
|
-
r = tuple(sorted(
|
|
646
|
+
r = tuple(sorted(current_replacements.items()))
|
|
647
647
|
sql = replace_tables_chquery_cached(
|
|
648
648
|
sql,
|
|
649
649
|
r,
|
|
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
|
|
|
4
4
|
__url__ = 'https://www.tinybird.co/docs/forward/commands'
|
|
5
5
|
__author__ = 'Tinybird'
|
|
6
6
|
__author_email__ = 'support@tinybird.co'
|
|
7
|
-
__version__ = '4.6.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '4.6.15.dev0'
|
|
8
|
+
__revision__ = '8311ae9'
|
|
@@ -465,6 +465,7 @@ class TinyB:
|
|
|
465
465
|
sql_condition: Optional[str] = None,
|
|
466
466
|
format: str = "csv",
|
|
467
467
|
replace_options: Optional[Set[str]] = None,
|
|
468
|
+
use_v1: bool = False,
|
|
468
469
|
):
|
|
469
470
|
params = {"name": datasource_name, "mode": mode, "format": format, "debug": "blocks_block_log"}
|
|
470
471
|
|
|
@@ -476,6 +477,30 @@ class TinyB:
|
|
|
476
477
|
|
|
477
478
|
with open(file, "rb") as content:
|
|
478
479
|
file_content = content.read()
|
|
480
|
+
content_types = {
|
|
481
|
+
"csv": "text/csv",
|
|
482
|
+
"ndjson": "application/x-ndjson",
|
|
483
|
+
"parquet": "application/vnd.apache.parquet",
|
|
484
|
+
}
|
|
485
|
+
headers = {"Content-Type": content_types[format]}
|
|
486
|
+
if str(file).endswith(".gz"):
|
|
487
|
+
headers["Content-Encoding"] = "gzip"
|
|
488
|
+
if use_v1:
|
|
489
|
+
v1_params = {"format": format}
|
|
490
|
+
if sql_condition:
|
|
491
|
+
v1_params["replace_condition"] = sql_condition
|
|
492
|
+
if replace_options:
|
|
493
|
+
for option in list(replace_options):
|
|
494
|
+
v1_params[option] = "true"
|
|
495
|
+
res = self._req(
|
|
496
|
+
f"/v1/datasources/{quote(datasource_name, safe='')}/{mode}?{urlencode(v1_params, safe='')}",
|
|
497
|
+
data=file_content,
|
|
498
|
+
headers=headers,
|
|
499
|
+
method="POST",
|
|
500
|
+
)
|
|
501
|
+
if "error" in res:
|
|
502
|
+
raise Exception(res["error"])
|
|
503
|
+
return res
|
|
479
504
|
if format == "csv":
|
|
480
505
|
files = {"csv": ("csv", file_content)}
|
|
481
506
|
else:
|
|
@@ -925,7 +925,8 @@ def push_data(
|
|
|
925
925
|
replace_options=None,
|
|
926
926
|
concurrency: int = 1,
|
|
927
927
|
silent: bool = False,
|
|
928
|
-
|
|
928
|
+
use_v1: bool = False,
|
|
929
|
+
) -> Optional[list[str]]:
|
|
929
930
|
if url and type(url) is tuple:
|
|
930
931
|
url = url[0]
|
|
931
932
|
|
|
@@ -962,6 +963,8 @@ def push_data(
|
|
|
962
963
|
datasource_name: str, url: str, mode: str, sql_condition: Optional[str], replace_options: Optional[Set[str]]
|
|
963
964
|
):
|
|
964
965
|
parsed = urlparse(url)
|
|
966
|
+
if use_v1 and parsed.scheme in ("http", "https"):
|
|
967
|
+
raise CLIException("--experimental=use_v1 only supports local files.")
|
|
965
968
|
# poor man's format detection
|
|
966
969
|
_format = get_format_from_filename_or_url(url)
|
|
967
970
|
if parsed.scheme in ("http", "https"):
|
|
@@ -982,8 +985,15 @@ def push_data(
|
|
|
982
985
|
sql_condition=sql_condition,
|
|
983
986
|
format=_format,
|
|
984
987
|
replace_options=replace_options,
|
|
988
|
+
use_v1=use_v1,
|
|
985
989
|
)
|
|
986
990
|
|
|
991
|
+
if use_v1:
|
|
992
|
+
job_id = res.get("id") or res.get("import_id")
|
|
993
|
+
if not isinstance(job_id, str):
|
|
994
|
+
raise CLIException("The v1 import response did not include a job ID.")
|
|
995
|
+
return job_id
|
|
996
|
+
|
|
987
997
|
datasource_name = res["datasource"]["name"]
|
|
988
998
|
try:
|
|
989
999
|
datasource = client.get_datasource(datasource_name)
|
|
@@ -1014,6 +1024,8 @@ def push_data(
|
|
|
1014
1024
|
try:
|
|
1015
1025
|
tasks = [process_url(datasource_name, url, mode, sql_condition, replace_options) for url in urls]
|
|
1016
1026
|
output = gather_with_concurrency(concurrency, *tasks)
|
|
1027
|
+
if use_v1:
|
|
1028
|
+
return list(output)
|
|
1017
1029
|
parser, total_rows, appended_rows = list(output)[-1]
|
|
1018
1030
|
except AuthNoTokenException:
|
|
1019
1031
|
raise
|
|
@@ -1031,6 +1043,8 @@ def push_data(
|
|
|
1031
1043
|
|
|
1032
1044
|
click.echo(FeedbackManager.success_progress_blocks())
|
|
1033
1045
|
|
|
1046
|
+
return None
|
|
1047
|
+
|
|
1034
1048
|
|
|
1035
1049
|
def sync_data(ctx, datasource_name: str, yes: bool):
|
|
1036
1050
|
client: TinyB = ctx.obj["client"]
|
|
@@ -85,7 +85,7 @@ class CLIConfig:
|
|
|
85
85
|
def to_dict(self) -> Dict[str, Any]:
|
|
86
86
|
"""Helper to ease"""
|
|
87
87
|
result: Dict[str, Any] = {}
|
|
88
|
-
result.update(
|
|
88
|
+
result.update({v.name: deepcopy(v.value) for v in self._values.values()})
|
|
89
89
|
return result
|
|
90
90
|
|
|
91
91
|
def __getitem__(self, key) -> Any:
|
|
@@ -162,12 +162,12 @@ class PipeChecker(unittest.TestCase):
|
|
|
162
162
|
|
|
163
163
|
if self.ignore_order:
|
|
164
164
|
current_data = (
|
|
165
|
-
sorted(normalize_array(current_data), key=itemgetter(*
|
|
165
|
+
sorted(normalize_array(current_data), key=itemgetter(*list(current_data[0].keys())))
|
|
166
166
|
if len(current_data) > 0
|
|
167
167
|
else current_data
|
|
168
168
|
)
|
|
169
169
|
checker_data = (
|
|
170
|
-
sorted(normalize_array(checker_data), key=itemgetter(*
|
|
170
|
+
sorted(normalize_array(checker_data), key=itemgetter(*list(checker_data[0].keys())))
|
|
171
171
|
if len(checker_data) > 0
|
|
172
172
|
else checker_data
|
|
173
173
|
)
|
|
@@ -35,6 +35,7 @@ from tinybird.tb.modules.common import (
|
|
|
35
35
|
get_format_from_filename_or_url,
|
|
36
36
|
normalize_datasource_name,
|
|
37
37
|
push_data,
|
|
38
|
+
wait_job,
|
|
38
39
|
)
|
|
39
40
|
from tinybird.tb.modules.config import CLIConfig
|
|
40
41
|
from tinybird.tb.modules.connection_dynamodb import connection_create_dynamodb, validate_dynamodb_table
|
|
@@ -58,7 +59,7 @@ from tinybird.tb.modules.create import (
|
|
|
58
59
|
generate_gcs_connection_file_with_secrets,
|
|
59
60
|
)
|
|
60
61
|
from tinybird.tb.modules.datafile.fixture import persist_fixture
|
|
61
|
-
from tinybird.tb.modules.exceptions import CLIDatasourceException
|
|
62
|
+
from tinybird.tb.modules.exceptions import CLIDatasourceException, CLIException
|
|
62
63
|
from tinybird.tb.modules.feedback_manager import FeedbackManager, get_cli_name
|
|
63
64
|
from tinybird.tb.modules.llm import LLM
|
|
64
65
|
from tinybird.tb.modules.llm_utils import extract_xml
|
|
@@ -66,6 +67,36 @@ from tinybird.tb.modules.project import Project
|
|
|
66
67
|
from tinybird.tb.modules.secret import save_secret_to_env_file
|
|
67
68
|
from tinybird.tb.modules.telemetry import add_telemetry_event
|
|
68
69
|
|
|
70
|
+
EXPERIMENTAL_FEATURE_USE_V1 = "use_v1"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _echo_v1_import_jobs_queued(job_ids: list[str], operation: str) -> None:
|
|
74
|
+
for job_id in job_ids:
|
|
75
|
+
click.echo(FeedbackManager.success(message=f"✓ {operation} import job queued: {job_id}"))
|
|
76
|
+
click.echo(FeedbackManager.gray(message=f"Check status: tb job details {job_id}"))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _wait_for_v1_import_jobs(client: TinyB, job_ids: list[str], operation: str) -> None:
|
|
80
|
+
for job_id in job_ids:
|
|
81
|
+
try:
|
|
82
|
+
wait_job(client, job_id, f"/v0/jobs/{job_id}", f"{operation} import")
|
|
83
|
+
except CLIException:
|
|
84
|
+
if _echo_v1_import_job_details(client, job_id):
|
|
85
|
+
raise click.exceptions.Exit(1)
|
|
86
|
+
raise
|
|
87
|
+
click.echo(FeedbackManager.success(message=f"✓ {operation} import completed: {job_id}"))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _echo_v1_import_job_details(client: TinyB, job_id: str) -> bool:
|
|
91
|
+
try:
|
|
92
|
+
job = client.job(job_id)
|
|
93
|
+
except Exception:
|
|
94
|
+
return False
|
|
95
|
+
click.echo(FeedbackManager.info_job(job=job_id))
|
|
96
|
+
echo_safe_humanfriendly_tables_format_smart_table([job.values()], column_names=job.keys())
|
|
97
|
+
click.echo("\n")
|
|
98
|
+
return True
|
|
99
|
+
|
|
69
100
|
|
|
70
101
|
def _dynamodb_key_schema_sort_key(key_schema: dict[str, str]) -> int:
|
|
71
102
|
if key_schema.get("key_type") == "HASH":
|
|
@@ -213,7 +244,14 @@ def datasource_ls(ctx: Context, match: Optional[str], format_: str):
|
|
|
213
244
|
@click.option("--url", type=str, help="URL to append data from")
|
|
214
245
|
@click.option("--file", type=str, help="Local file to append data from")
|
|
215
246
|
@click.option("--events", type=str, help="Events to append data from")
|
|
247
|
+
@click.option(
|
|
248
|
+
"--experimental",
|
|
249
|
+
type=click.Choice([EXPERIMENTAL_FEATURE_USE_V1]),
|
|
250
|
+
multiple=True,
|
|
251
|
+
help="Enable an experimental feature. May be specified multiple times.",
|
|
252
|
+
)
|
|
216
253
|
@click.option("--concurrency", help="How many files to submit concurrently", default=1, hidden=True)
|
|
254
|
+
@click.option("--wait", is_flag=True, default=False, help="Wait for a v1 import job to finish.")
|
|
217
255
|
@click.pass_context
|
|
218
256
|
def datasource_append(
|
|
219
257
|
ctx: Context,
|
|
@@ -222,7 +260,9 @@ def datasource_append(
|
|
|
222
260
|
url: str,
|
|
223
261
|
file: str,
|
|
224
262
|
events: str,
|
|
263
|
+
experimental: tuple[str, ...],
|
|
225
264
|
concurrency: int,
|
|
265
|
+
wait: bool,
|
|
226
266
|
):
|
|
227
267
|
"""
|
|
228
268
|
Appends data to an existing data source from URL, local file or a connector
|
|
@@ -237,6 +277,9 @@ def datasource_append(
|
|
|
237
277
|
env: str = ctx.ensure_object(dict)["env"]
|
|
238
278
|
client: TinyB = ctx.obj["client"]
|
|
239
279
|
project: Project = ctx.ensure_object(dict)["project"]
|
|
280
|
+
use_v1 = EXPERIMENTAL_FEATURE_USE_V1 in experimental
|
|
281
|
+
if wait and not use_v1:
|
|
282
|
+
raise CLIDatasourceException("--wait requires --experimental=use_v1.")
|
|
240
283
|
|
|
241
284
|
# If data is passed as argument, we detect if it's a JSON object, a URL or a file
|
|
242
285
|
if data:
|
|
@@ -346,6 +389,8 @@ def datasource_append(
|
|
|
346
389
|
raise CLIDatasourceException(FeedbackManager.error(message="Invalid ingestion option"))
|
|
347
390
|
|
|
348
391
|
if events:
|
|
392
|
+
if use_v1:
|
|
393
|
+
raise CLIDatasourceException("--experimental=use_v1 only supports local files.")
|
|
349
394
|
click.echo(FeedbackManager.highlight(message=f"\n» Sending events to {datasource_name}"))
|
|
350
395
|
events_params = {"name": datasource_name}
|
|
351
396
|
request_from = getattr(client, "request_from", None)
|
|
@@ -382,13 +427,14 @@ def datasource_append(
|
|
|
382
427
|
else:
|
|
383
428
|
click.echo(FeedbackManager.highlight(message=f"\n» Appending data to {datasource_name}"))
|
|
384
429
|
try:
|
|
385
|
-
push_data(
|
|
430
|
+
job_ids = push_data(
|
|
386
431
|
client,
|
|
387
432
|
datasource_name,
|
|
388
433
|
data,
|
|
389
434
|
mode="append",
|
|
390
435
|
concurrency=concurrency,
|
|
391
436
|
silent=True,
|
|
437
|
+
use_v1=use_v1,
|
|
392
438
|
)
|
|
393
439
|
except Exception as e:
|
|
394
440
|
is_quarantined = "quarantine" in str(e)
|
|
@@ -398,7 +444,12 @@ def datasource_append(
|
|
|
398
444
|
return
|
|
399
445
|
else:
|
|
400
446
|
raise e
|
|
401
|
-
|
|
447
|
+
if use_v1:
|
|
448
|
+
_echo_v1_import_jobs_queued(job_ids or [], "Append")
|
|
449
|
+
if wait:
|
|
450
|
+
_wait_for_v1_import_jobs(client, job_ids or [], "Append")
|
|
451
|
+
else:
|
|
452
|
+
click.echo(FeedbackManager.success(message="✓ Rows appended!"))
|
|
402
453
|
|
|
403
454
|
|
|
404
455
|
@datasource.command(name="replace")
|
|
@@ -406,6 +457,13 @@ def datasource_append(
|
|
|
406
457
|
@click.argument("url", nargs=-1, required=True)
|
|
407
458
|
@click.option("--sql-condition", default=None, help="SQL WHERE condition to replace data", hidden=True)
|
|
408
459
|
@click.option("--skip-incompatible-partition-key", is_flag=True, default=False, hidden=True)
|
|
460
|
+
@click.option("--wait", is_flag=True, default=False, help="Wait for a v1 import job to finish.")
|
|
461
|
+
@click.option(
|
|
462
|
+
"--experimental",
|
|
463
|
+
type=click.Choice([EXPERIMENTAL_FEATURE_USE_V1]),
|
|
464
|
+
multiple=True,
|
|
465
|
+
help="Enable an experimental feature. May be specified multiple times.",
|
|
466
|
+
)
|
|
409
467
|
@click.pass_context
|
|
410
468
|
def datasource_replace(
|
|
411
469
|
ctx: Context,
|
|
@@ -413,6 +471,8 @@ def datasource_replace(
|
|
|
413
471
|
url,
|
|
414
472
|
sql_condition,
|
|
415
473
|
skip_incompatible_partition_key,
|
|
474
|
+
experimental: tuple[str, ...],
|
|
475
|
+
wait: bool,
|
|
416
476
|
):
|
|
417
477
|
"""
|
|
418
478
|
Replaces the data in a data source from a URL, local file or a connector
|
|
@@ -426,14 +486,22 @@ def datasource_replace(
|
|
|
426
486
|
if skip_incompatible_partition_key:
|
|
427
487
|
replace_options.add("skip_incompatible_partition_key")
|
|
428
488
|
client: TinyB = ctx.obj["client"]
|
|
429
|
-
|
|
489
|
+
use_v1 = EXPERIMENTAL_FEATURE_USE_V1 in experimental
|
|
490
|
+
if wait and not use_v1:
|
|
491
|
+
raise CLIDatasourceException("--wait requires --experimental=use_v1.")
|
|
492
|
+
job_ids = push_data(
|
|
430
493
|
client,
|
|
431
494
|
datasource_name,
|
|
432
495
|
url,
|
|
433
496
|
mode="replace",
|
|
434
497
|
sql_condition=sql_condition,
|
|
435
498
|
replace_options=replace_options,
|
|
499
|
+
use_v1=use_v1,
|
|
436
500
|
)
|
|
501
|
+
if use_v1:
|
|
502
|
+
_echo_v1_import_jobs_queued(job_ids or [], "Replace")
|
|
503
|
+
if wait:
|
|
504
|
+
_wait_for_v1_import_jobs(client, job_ids or [], "Replace")
|
|
437
505
|
|
|
438
506
|
|
|
439
507
|
@datasource.command(name="analyze")
|
|
@@ -963,7 +963,8 @@ async def push_data(
|
|
|
963
963
|
sql_condition: Optional[str] = None,
|
|
964
964
|
replace_options=None,
|
|
965
965
|
concurrency: int = 1,
|
|
966
|
-
|
|
966
|
+
use_v1: bool = False,
|
|
967
|
+
) -> Optional[list[str]]:
|
|
967
968
|
if url and type(url) is tuple:
|
|
968
969
|
url = url[0]
|
|
969
970
|
client: TinyB = ctx.obj["client"]
|
|
@@ -1000,6 +1001,8 @@ async def push_data(
|
|
|
1000
1001
|
datasource_name: str, url: str, mode: str, sql_condition: Optional[str], replace_options: Optional[Set[str]]
|
|
1001
1002
|
):
|
|
1002
1003
|
parsed = urlparse(url)
|
|
1004
|
+
if use_v1 and parsed.scheme in ("http", "https"):
|
|
1005
|
+
raise CLIException("--experimental=use_v1 only supports local files.")
|
|
1003
1006
|
# poor man's format detection
|
|
1004
1007
|
_format = get_format_from_filename_or_url(url)
|
|
1005
1008
|
if parsed.scheme in ("http", "https"):
|
|
@@ -1020,8 +1023,15 @@ async def push_data(
|
|
|
1020
1023
|
sql_condition=sql_condition,
|
|
1021
1024
|
format=_format,
|
|
1022
1025
|
replace_options=replace_options,
|
|
1026
|
+
use_v1=use_v1,
|
|
1023
1027
|
)
|
|
1024
1028
|
|
|
1029
|
+
if use_v1:
|
|
1030
|
+
job_id = res.get("id") or res.get("import_id")
|
|
1031
|
+
if not isinstance(job_id, str):
|
|
1032
|
+
raise CLIException("The v1 import response did not include a job ID.")
|
|
1033
|
+
return job_id
|
|
1034
|
+
|
|
1025
1035
|
datasource_name = res["datasource"]["name"]
|
|
1026
1036
|
try:
|
|
1027
1037
|
datasource = await client.get_datasource(datasource_name)
|
|
@@ -1052,6 +1062,8 @@ async def push_data(
|
|
|
1052
1062
|
try:
|
|
1053
1063
|
tasks = [process_url(datasource_name, url, mode, sql_condition, replace_options) for url in urls]
|
|
1054
1064
|
output = await gather_with_concurrency(concurrency, *tasks)
|
|
1065
|
+
if use_v1:
|
|
1066
|
+
return list(output)
|
|
1055
1067
|
parser, total_rows, appended_rows = list(output)[-1]
|
|
1056
1068
|
except AuthNoTokenException:
|
|
1057
1069
|
raise
|
|
@@ -1072,6 +1084,8 @@ async def push_data(
|
|
|
1072
1084
|
click.echo(FeedbackManager.success_appended_datasource(datasource=datasource_name))
|
|
1073
1085
|
click.echo(FeedbackManager.info_data_pushed(datasource=datasource_name))
|
|
1074
1086
|
|
|
1087
|
+
return None
|
|
1088
|
+
|
|
1075
1089
|
|
|
1076
1090
|
async def sync_data(ctx, datasource_name: str, yes: bool):
|
|
1077
1091
|
client: TinyB = ctx.obj["client"]
|
|
@@ -88,7 +88,7 @@ class CLIConfig:
|
|
|
88
88
|
def to_dict(self) -> Dict[str, Any]:
|
|
89
89
|
"""Helper to ease"""
|
|
90
90
|
result: Dict[str, Any] = self._parent.to_dict() if self._parent else {}
|
|
91
|
-
result.update(
|
|
91
|
+
result.update({v.name: deepcopy(v.value) for v in self._values.values()})
|
|
92
92
|
return result
|
|
93
93
|
|
|
94
94
|
def __getitem__(self, key) -> Any:
|
|
@@ -15,7 +15,7 @@ from click import Context
|
|
|
15
15
|
|
|
16
16
|
from tinybird.client import AuthNoTokenException, CanNotBeDeletedException, DoesNotExistException, TinyB
|
|
17
17
|
from tinybird.config import get_display_host
|
|
18
|
-
from tinybird.datafile_common import get_name_version
|
|
18
|
+
from tinybird.datafile_common import get_name_version
|
|
19
19
|
from tinybird.feedback_manager import FeedbackManager
|
|
20
20
|
from tinybird.tb_cli_modules.branch import warn_if_in_live
|
|
21
21
|
from tinybird.tb_cli_modules.cli import cli
|
|
@@ -34,9 +34,12 @@ from tinybird.tb_cli_modules.common import (
|
|
|
34
34
|
validate_kafka_auto_offset_reset,
|
|
35
35
|
validate_kafka_group,
|
|
36
36
|
validate_kafka_topic,
|
|
37
|
+
wait_job,
|
|
37
38
|
)
|
|
38
39
|
from tinybird.tb_cli_modules.config import CLIConfig
|
|
39
|
-
from tinybird.tb_cli_modules.exceptions import CLIDatasourceException
|
|
40
|
+
from tinybird.tb_cli_modules.exceptions import CLIDatasourceException, CLIException
|
|
41
|
+
|
|
42
|
+
EXPERIMENTAL_FEATURE_USE_V1 = "use_v1"
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
@cli.group()
|
|
@@ -45,6 +48,34 @@ def datasource(ctx):
|
|
|
45
48
|
"""Data Sources commands"""
|
|
46
49
|
|
|
47
50
|
|
|
51
|
+
def _echo_v1_import_jobs_queued(job_ids: list[str], operation: str) -> None:
|
|
52
|
+
for job_id in job_ids:
|
|
53
|
+
click.echo(FeedbackManager.success_import_job_queued(operation=operation, job_id=job_id))
|
|
54
|
+
click.echo(FeedbackManager.info_import_job_status(job_id=job_id))
|
|
55
|
+
|
|
56
|
+
|
|
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
|
+
|
|
48
79
|
@datasource.command(name="ls")
|
|
49
80
|
@click.option("--match", default=None, help="Retrieve any resources matching the pattern. eg --match _test")
|
|
50
81
|
@click.option(
|
|
@@ -135,6 +166,13 @@ async def datasource_ls(ctx: Context, match: Optional[str], format_: str):
|
|
|
135
166
|
hidden=True,
|
|
136
167
|
)
|
|
137
168
|
@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
|
+
@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.",
|
|
175
|
+
)
|
|
138
176
|
@click.pass_context
|
|
139
177
|
@coro
|
|
140
178
|
async def datasource_append(
|
|
@@ -145,6 +183,8 @@ async def datasource_append(
|
|
|
145
183
|
incremental: Optional[str],
|
|
146
184
|
ignore_empty: bool,
|
|
147
185
|
concurrency: int,
|
|
186
|
+
experimental: tuple[str, ...],
|
|
187
|
+
wait: bool,
|
|
148
188
|
):
|
|
149
189
|
"""
|
|
150
190
|
Appends data to an existing Data Source from URL, local file or a connector
|
|
@@ -157,7 +197,14 @@ async def datasource_append(
|
|
|
157
197
|
|
|
158
198
|
if not url:
|
|
159
199
|
raise CLIDatasourceException(FeedbackManager.error_missing_url(datasource=datasource_name))
|
|
160
|
-
|
|
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")
|
|
161
208
|
|
|
162
209
|
|
|
163
210
|
@datasource.command(name="replace")
|
|
@@ -165,6 +212,13 @@ async def datasource_append(
|
|
|
165
212
|
@click.argument("url", nargs=-1)
|
|
166
213
|
@click.option("--sql-condition", default=None, help="SQL WHERE condition to replace data", hidden=True)
|
|
167
214
|
@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
|
+
@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.",
|
|
221
|
+
)
|
|
168
222
|
@click.pass_context
|
|
169
223
|
@coro
|
|
170
224
|
async def datasource_replace(
|
|
@@ -173,6 +227,8 @@ async def datasource_replace(
|
|
|
173
227
|
url,
|
|
174
228
|
sql_condition,
|
|
175
229
|
skip_incompatible_partition_key,
|
|
230
|
+
experimental: tuple[str, ...],
|
|
231
|
+
wait: bool,
|
|
176
232
|
):
|
|
177
233
|
"""
|
|
178
234
|
Replaces the data in a data source from a URL, local file or a connector
|
|
@@ -189,14 +245,22 @@ async def datasource_replace(
|
|
|
189
245
|
replace_options = set()
|
|
190
246
|
if skip_incompatible_partition_key:
|
|
191
247
|
replace_options.add("skip_incompatible_partition_key")
|
|
192
|
-
|
|
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
|
+
job_ids = await push_data(
|
|
193
252
|
ctx,
|
|
194
253
|
datasource_name,
|
|
195
254
|
url,
|
|
196
255
|
mode="replace",
|
|
197
256
|
sql_condition=sql_condition,
|
|
198
257
|
replace_options=replace_options,
|
|
258
|
+
use_v1=use_v1,
|
|
199
259
|
)
|
|
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")
|
|
200
264
|
|
|
201
265
|
|
|
202
266
|
@datasource.command(name="analyze")
|
|
@@ -916,10 +916,10 @@ def _parse(reader: _TemplateReader, template, in_block=None, in_loop=None):
|
|
|
916
916
|
|
|
917
917
|
# Intermediate ("else", "elif", etc) blocks
|
|
918
918
|
intermediate_blocks = {
|
|
919
|
-
"else":
|
|
920
|
-
"elif":
|
|
921
|
-
"except":
|
|
922
|
-
"finally":
|
|
919
|
+
"else": {"if", "for", "while", "try"},
|
|
920
|
+
"elif": {"if"},
|
|
921
|
+
"except": {"try"},
|
|
922
|
+
"finally": {"try"},
|
|
923
923
|
}
|
|
924
924
|
allowed_parents = intermediate_blocks.get(operator)
|
|
925
925
|
|
|
@@ -997,7 +997,7 @@ def _parse(reader: _TemplateReader, template, in_block=None, in_loop=None):
|
|
|
997
997
|
|
|
998
998
|
elif operator in ("break", "continue"):
|
|
999
999
|
if not in_loop:
|
|
1000
|
-
reader.raise_parse_error("%s outside %s block" % (operator,
|
|
1000
|
+
reader.raise_parse_error("%s outside %s block" % (operator, {"for", "while"}))
|
|
1001
1001
|
body.chunks.append(_Statement(contents, line))
|
|
1002
1002
|
continue
|
|
1003
1003
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybird
|
|
3
|
-
Version: 4.6.
|
|
3
|
+
Version: 4.6.15.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/forward/commands
|
|
6
6
|
Author: Tinybird
|
|
@@ -52,6 +52,11 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
52
52
|
Changelog
|
|
53
53
|
----------
|
|
54
54
|
|
|
55
|
+
4.6.13
|
|
56
|
+
********
|
|
57
|
+
|
|
58
|
+
- `Added` ``tb datasource append`` and ``tb datasource replace`` support the experimental ``--experimental=use_v1`` option for local CSV, NDJSON, and Parquet files. It submits the import through the v1 endpoint and prints the queued job ID; pass ``--wait`` to wait for completion.
|
|
59
|
+
|
|
55
60
|
4.6.12
|
|
56
61
|
********
|
|
57
62
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/build_datasource.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/format_connection.py
RENAMED
|
File without changes
|
{tinybird-4.6.13.dev0 → tinybird-4.6.15.dev0}/tinybird/tb/modules/datafile/format_datasource.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|