tinybird 4.6.12.dev0__tar.gz → 4.6.13__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.12.dev0 → tinybird-4.6.13}/PKG-INFO +16 -1
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/client.py +34 -10
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/datafile/common.py +20 -10
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/datafile/parse_pipe.py +30 -6
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/feedback_manager.py +3 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/service_datasources.py +3 -3
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/sql_template.py +83 -4
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/sql_toolset.py +26 -7
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/__cli__.py +2 -2
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/client.py +25 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/common.py +15 -1
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/config.py +1 -1
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/build.py +1 -1
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/pipe_checker.py +6 -2
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/playground.py +1 -1
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datasource.py +72 -4
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/common.py +15 -1
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/config.py +1 -1
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/datasource.py +68 -4
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tornado_template.py +5 -5
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird.egg-info/PKG-INFO +16 -1
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/setup.cfg +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/__cli__.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/ch_utils/engine.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/check_pypi.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/config.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/context.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/datafile/exceptions.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/datafile/parse_connection.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/datafile/parse_datasource.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/datatypes.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/git_settings.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/iterating/__init__.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/iterating/data_branch_modes.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/prompts.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/sql.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/syncasync.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/check_pypi.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/cli.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/config.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/branch.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/build.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/build_common.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/cicd.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/cli.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/connection.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/connection_dynamodb.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/connection_kafka.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/connection_s3.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/copy.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/create.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/build_common.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/diff.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/fixture.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/format_common.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/format_connection.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/datafile/pull.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/deployment.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/deployment_common.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/deprecations.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/endpoint.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/exceptions.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/feedback_manager.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/fmt.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/info.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/infra.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/job.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/job_common.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/llm.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/llm_utils.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/local.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/local_common.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/local_logs.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/login.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/login_common.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/logout.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/logs.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/materialization.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/open.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/pipe.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/preview.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/project.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/project_commands.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/py_project.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/query_output.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/regions.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/secret.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/secret_common.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/sink.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/table.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/telemetry.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/test.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/test_common.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/token.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/ts_project.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/watch.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/workspace.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb/modules/workspace_members.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/cli.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/fmt.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/tag.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird.egg-info/SOURCES.txt +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird.egg-info/dependency_links.txt +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird.egg-info/entry_points.txt +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/tinybird.egg-info/requires.txt +0 -0
- {tinybird-4.6.12.dev0 → tinybird-4.6.13}/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.13
|
|
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,19 @@ 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
|
+
- `Fixed` `tb deploy` and `tb build` now reject pipes that declare a parameter without a valid name (e.g. a numeric-literal cast like ``{{Float32(1682892000.0)}}``), instead of creating an unusable parameter.
|
|
59
|
+
- `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.
|
|
60
|
+
|
|
61
|
+
4.6.12
|
|
62
|
+
********
|
|
63
|
+
|
|
64
|
+
- `Added` Forward sink pipes support ``{{ tb_secret(...) }}`` in ``EXPORT_*`` metadata (for example ``EXPORT_BUCKET_URI``). Secrets are resolved at deploy time and baked into the sink settings; changing a secret requires a redeploy.
|
|
65
|
+
- `Fixed` Forward deploy now raises an error when a secret used in sink metadata resolves to an empty value.
|
|
66
|
+
- `Fixed` Forward deploy now rejects ``{{ tb_secret(...) }}`` in ``EXPORT_SCHEDULE`` and ``COPY_SCHEDULE``. Use a literal cron expression (for example ``0 * * * *``) or ``@on-demand``.
|
|
67
|
+
|
|
55
68
|
4.6.11
|
|
56
69
|
********
|
|
57
70
|
|
|
@@ -62,6 +75,7 @@ Changelog
|
|
|
62
75
|
|
|
63
76
|
- `Removed` `tinybird.bi_stats_rt` from `tb logs` sources. The BI Connector `bi_stats` / `bi_stats_rt` service datasources are deprecated.
|
|
64
77
|
|
|
78
|
+
|
|
65
79
|
4.6.9
|
|
66
80
|
*******
|
|
67
81
|
|
|
@@ -70,6 +84,7 @@ Changelog
|
|
|
70
84
|
4.6.8
|
|
71
85
|
*******
|
|
72
86
|
|
|
87
|
+
- `Changed` Upgraded Tornado to 6.5.5.
|
|
73
88
|
- `Fixed` Tinybird Local CLI commands to respect Git branch local workspaces correctly.
|
|
74
89
|
|
|
75
90
|
4.6.7
|
|
@@ -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="")
|
|
@@ -401,12 +401,15 @@ class Datafile:
|
|
|
401
401
|
if node.get("mode") and node["mode"] not in ["append", "replace"]:
|
|
402
402
|
raise DatafileValidationError("COPY node mode must be append or replace")
|
|
403
403
|
# copy schedule must be @on-demand or a cron-expression
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
and
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
404
|
+
copy_schedule = node.get("copy_schedule")
|
|
405
|
+
if copy_schedule and copy_schedule != ON_DEMAND:
|
|
406
|
+
if isinstance(copy_schedule, str) and "tb_secret" in copy_schedule:
|
|
407
|
+
raise DatafileValidationError(
|
|
408
|
+
"COPY_SCHEDULE does not support { tb_secret(...) }. "
|
|
409
|
+
'Use a literal cron expression (for example "0 * * * *") or @on-demand.'
|
|
410
|
+
)
|
|
411
|
+
if not croniter.is_valid(copy_schedule):
|
|
412
|
+
raise DatafileValidationError("COPY node schedule must be @on-demand or a valid cron expression.")
|
|
410
413
|
for key in node.keys():
|
|
411
414
|
if key not in CopyParameters.valid_params():
|
|
412
415
|
raise DatafileValidationError(
|
|
@@ -503,10 +506,16 @@ class Datafile:
|
|
|
503
506
|
|
|
504
507
|
# Validate schedule format (common for both Kafka and S3/GCS)
|
|
505
508
|
export_schedule = node.get("export_schedule")
|
|
506
|
-
if export_schedule and export_schedule != ON_DEMAND
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
509
|
+
if export_schedule and export_schedule != ON_DEMAND:
|
|
510
|
+
if isinstance(export_schedule, str) and "tb_secret" in export_schedule:
|
|
511
|
+
raise DatafileValidationError(
|
|
512
|
+
f"Sink node {repr(node['name'])}: EXPORT_SCHEDULE does not support {{ tb_secret(...) }}. "
|
|
513
|
+
'Use a literal cron expression (for example "0 * * * *") or @on-demand.'
|
|
514
|
+
)
|
|
515
|
+
if not croniter.is_valid(export_schedule):
|
|
516
|
+
raise DatafileValidationError(
|
|
517
|
+
f"Sink node {repr(node['name'])} has invalid export_schedule '{export_schedule}'. Must be @on-demand or a valid cron expression."
|
|
518
|
+
)
|
|
510
519
|
|
|
511
520
|
def validate(self):
|
|
512
521
|
if self.kind == DatafileKind.pipe:
|
|
@@ -2082,6 +2091,7 @@ def parse(
|
|
|
2082
2091
|
"sql": sql("sql"),
|
|
2083
2092
|
"version": version,
|
|
2084
2093
|
"deployment_method": assign_var("deployment_method", allowed_values={"alter"}),
|
|
2094
|
+
"export_service": export_service, # Deprecated
|
|
2085
2095
|
"export_connection_name": assign_var("export_connection_name"),
|
|
2086
2096
|
"export_schedule": assign_var("export_schedule"),
|
|
2087
2097
|
"export_bucket_uri": assign_var("export_bucket_uri"),
|
|
@@ -11,7 +11,11 @@ from tinybird.datafile.common import (
|
|
|
11
11
|
parse,
|
|
12
12
|
)
|
|
13
13
|
from tinybird.datafile.exceptions import IncludeFileNotFoundException, ParseException
|
|
14
|
-
from tinybird.sql_template import
|
|
14
|
+
from tinybird.sql_template import (
|
|
15
|
+
get_template_and_variables,
|
|
16
|
+
render_sql_template,
|
|
17
|
+
validate_template_parameter_names,
|
|
18
|
+
)
|
|
15
19
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
16
20
|
from tinybird.tornado_template import UnClosedIfError
|
|
17
21
|
|
|
@@ -52,20 +56,38 @@ def parse_pipe(
|
|
|
52
56
|
if "type" in node:
|
|
53
57
|
node["type"] = node["type"].lower()
|
|
54
58
|
sql = node.get("sql", "")
|
|
55
|
-
|
|
59
|
+
source_sql = sql.strip()
|
|
60
|
+
if not source_sql:
|
|
61
|
+
raise click.ClickException(
|
|
62
|
+
FeedbackManager.error_parsing_node(node=node["name"], pipe=filename, error="Empty query")
|
|
63
|
+
)
|
|
64
|
+
if source_sql.startswith("%"):
|
|
56
65
|
secrets_list: Optional[List[str]] = None
|
|
57
66
|
if secrets:
|
|
58
67
|
secrets_list = list(secrets.keys())
|
|
68
|
+
# Reject parameters declared without a usable name (e.g. a numeric
|
|
69
|
+
# literal cast like `{{Float32(1682892000.0)}}`) before rendering.
|
|
70
|
+
validate_template_parameter_names(sql[1:], node_id=node["name"])
|
|
59
71
|
# Setting test_mode=True to ignore errors on required parameters and
|
|
60
72
|
# secrets_in_test_mode=False to raise errors on missing secrets
|
|
61
73
|
sql, _, variable_warnings = render_sql_template(
|
|
62
|
-
|
|
74
|
+
source_sql[1:],
|
|
63
75
|
name=node["name"],
|
|
64
76
|
secrets=secrets_list,
|
|
65
77
|
test_mode=True,
|
|
66
78
|
secrets_in_test_mode=ignore_secrets,
|
|
67
79
|
)
|
|
68
80
|
doc.warnings = variable_warnings
|
|
81
|
+
if not sql.strip():
|
|
82
|
+
raise click.ClickException(
|
|
83
|
+
FeedbackManager.error_parsing_node(
|
|
84
|
+
node=node["name"],
|
|
85
|
+
pipe=filename,
|
|
86
|
+
error="Template renders to an empty query without parameters",
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
sql = source_sql
|
|
69
91
|
# it'll fail with a ModuleNotFoundError when the toolset is not available but it returns the parsed doc
|
|
70
92
|
from tinybird.sql_toolset import format_sql as toolset_format_sql
|
|
71
93
|
|
|
@@ -79,13 +101,15 @@ def parse_pipe(
|
|
|
79
101
|
)
|
|
80
102
|
)
|
|
81
103
|
except ValueError as e:
|
|
82
|
-
|
|
104
|
+
source_sql = node.get("sql", "")
|
|
105
|
+
source_sql_stripped = source_sql.strip()
|
|
106
|
+
t, template_variables, _ = get_template_and_variables(source_sql, name=node["name"])
|
|
83
107
|
|
|
84
|
-
if
|
|
108
|
+
if not source_sql_stripped.startswith("%") and len(template_variables) > 0:
|
|
85
109
|
raise click.ClickException(FeedbackManager.error_template_start(filename=filename))
|
|
86
110
|
raise click.ClickException(
|
|
87
111
|
FeedbackManager.error_parsing_file(
|
|
88
|
-
filename=filename, lineno="", error=f"{str(e)} + SQL(value error): {
|
|
112
|
+
filename=filename, lineno="", error=f"{str(e)} + SQL(value error): {source_sql}"
|
|
89
113
|
)
|
|
90
114
|
)
|
|
91
115
|
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
|
)
|
|
@@ -19,12 +19,12 @@ def get_tinybird_service_datasources() -> List[Dict[str, Any]]:
|
|
|
19
19
|
return [
|
|
20
20
|
{
|
|
21
21
|
"name": "tinybird.pipe_stats_rt",
|
|
22
|
-
"description": "Contains information about all requests made to your API endpoints in real time. This data source has a TTL of
|
|
22
|
+
"description": "Contains information about all requests made to your API endpoints in real time. This data source has a TTL of 3 months. If you need to query data older than 3 months you must use the aggregated by day data available at tinybird.pipe_stats.",
|
|
23
23
|
"dateColumn": "start_datetime",
|
|
24
24
|
"engine": {
|
|
25
25
|
"engine": "MergeTree",
|
|
26
26
|
"sorting_key": "pipe_id, start_datetime",
|
|
27
|
-
"ttl": "start_datetime +
|
|
27
|
+
"ttl": "start_datetime + toIntervalMonth(3)",
|
|
28
28
|
},
|
|
29
29
|
"columns": [
|
|
30
30
|
{"name": "start_datetime", "type": "DateTime"},
|
|
@@ -659,7 +659,7 @@ def get_organization_service_datasources() -> List[Dict[str, Any]]:
|
|
|
659
659
|
"engine": {
|
|
660
660
|
"engine": "MergeTree",
|
|
661
661
|
"sorting_key": "workspace_id, pipe_id, start_datetime",
|
|
662
|
-
"ttl": "start_datetime +
|
|
662
|
+
"ttl": "start_datetime + toIntervalMonth(3)",
|
|
663
663
|
},
|
|
664
664
|
"columns": [
|
|
665
665
|
{"name": "workspace_id", "type": "String"},
|
|
@@ -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
|
|
@@ -2263,7 +2263,12 @@ def get_var_names_and_types(t: Template, node_id: Optional[str] = None) -> List[
|
|
|
2263
2263
|
vars_out.extend(
|
|
2264
2264
|
parse_statement_code_if_new_vars(statement_code, skip_names=typed_names | statement_expr_names)
|
|
2265
2265
|
)
|
|
2266
|
-
|
|
2266
|
+
# Inside a {% %} statement a type function with a literal first arg
|
|
2267
|
+
# (e.g. {% set x = Float32(1.5) %}) is a plain value cast: the
|
|
2268
|
+
# assigned variable is the parameter, the literal is not. Drop
|
|
2269
|
+
# these artifacts so they don't surface as bogus params nor get
|
|
2270
|
+
# rejected by validate_template_parameter_names.
|
|
2271
|
+
return [vd for vd in vars_out if isinstance(vd["name"], str) and vd["name"]]
|
|
2267
2272
|
|
|
2268
2273
|
def _n(chunks: list, vars_out: list[dict[str, Any]]) -> None:
|
|
2269
2274
|
for x in chunks:
|
|
@@ -2310,6 +2315,43 @@ def get_var_names_and_types_cached(t: Template):
|
|
|
2310
2315
|
return get_var_names_and_types(t)
|
|
2311
2316
|
|
|
2312
2317
|
|
|
2318
|
+
def validate_template_parameter_names(sql: str, node_id: Optional[str] = None) -> None:
|
|
2319
|
+
"""Reject template parameters that don't have a usable name.
|
|
2320
|
+
|
|
2321
|
+
Type functions such as ``{{Float32(x)}}`` double as casts, so a literal
|
|
2322
|
+
first argument (e.g. ``{{Float32(1682892000.0)}}`` or ``{{String('')}}``)
|
|
2323
|
+
is parsed as a parameter whose name is a number or an empty string instead
|
|
2324
|
+
of an identifier. Such a "parameter" can never be supplied by a caller and
|
|
2325
|
+
breaks parameter metadata consumers, so it must be rejected at write time.
|
|
2326
|
+
|
|
2327
|
+
Raises ``ValueError`` with the same wording used elsewhere for invalid names.
|
|
2328
|
+
|
|
2329
|
+
Templates that fail to parse are left untouched: there are no parameters to
|
|
2330
|
+
validate, and reporting/deferring those errors is the job of the existing
|
|
2331
|
+
render/validation flow (some paths, e.g. ``ignore_sql_errors``, tolerate
|
|
2332
|
+
unparseable SQL on purpose). This keeps the check strictly additive. The
|
|
2333
|
+
only exception is the invalid-name ``ValueError`` that extraction itself
|
|
2334
|
+
raises (e.g. for a list literal used as first argument), which is exactly
|
|
2335
|
+
what this validator exists to report.
|
|
2336
|
+
"""
|
|
2337
|
+
try:
|
|
2338
|
+
params = get_var_names_and_types(Template(sql), node_id=node_id)
|
|
2339
|
+
except SQLTemplateException:
|
|
2340
|
+
# Template-level problem (e.g. a SecurityException converted by
|
|
2341
|
+
# get_var_names_and_types), not an invalid parameter name.
|
|
2342
|
+
return
|
|
2343
|
+
except ValueError:
|
|
2344
|
+
# Extraction itself rejects some invalid names (e.g. a list literal
|
|
2345
|
+
# as first argument) with the same ValueError this function raises.
|
|
2346
|
+
raise
|
|
2347
|
+
except Exception:
|
|
2348
|
+
return
|
|
2349
|
+
for param in params:
|
|
2350
|
+
name = param.get("name")
|
|
2351
|
+
if not isinstance(name, str) or not name:
|
|
2352
|
+
raise ValueError(f'"{name}" can not be used as a variable name')
|
|
2353
|
+
|
|
2354
|
+
|
|
2313
2355
|
def wrap_vars(t, escape_arrays: bool = False):
|
|
2314
2356
|
def _n(chunks, v):
|
|
2315
2357
|
for x in chunks:
|
|
@@ -2911,13 +2953,21 @@ def extract_variables_from_sql(sql: str, params: List[Dict[str, Any]]) -> Dict[s
|
|
|
2911
2953
|
return defaults
|
|
2912
2954
|
|
|
2913
2955
|
|
|
2914
|
-
def render_template_with_secrets(
|
|
2956
|
+
def render_template_with_secrets(
|
|
2957
|
+
name: str,
|
|
2958
|
+
content: str,
|
|
2959
|
+
secrets: Optional[Dict[str, str]] = None,
|
|
2960
|
+
empty_secret_raises: bool = False,
|
|
2961
|
+
) -> str:
|
|
2915
2962
|
"""Renders a template with secrets, allowing for default values.
|
|
2916
2963
|
|
|
2917
2964
|
Args:
|
|
2918
2965
|
name: The name of the template
|
|
2919
2966
|
content: The template content
|
|
2920
2967
|
secrets: A dictionary mapping secret names to their values
|
|
2968
|
+
empty_secret_raises: When True, empty secret values or empty defaults raise
|
|
2969
|
+
instead of returning '""'. Used for sink metadata (Forward deploy) where
|
|
2970
|
+
an empty bucket path must not be baked as s3://"".
|
|
2921
2971
|
|
|
2922
2972
|
Returns:
|
|
2923
2973
|
The rendered template
|
|
@@ -2959,6 +3009,26 @@ def render_template_with_secrets(name: str, content: str, secrets: Optional[Dict
|
|
|
2959
3009
|
Traceback (most recent call last):
|
|
2960
3010
|
...
|
|
2961
3011
|
tinybird.sql_template.SQLTemplateException: Template Syntax Error: Cannot access secret 'MISSING_SECRET'. Check the secret exists in the Workspace and the token has the required scope.
|
|
3012
|
+
|
|
3013
|
+
>>> render_template_with_secrets(
|
|
3014
|
+
... "sink_bucket",
|
|
3015
|
+
... "s3://{{ tb_secret('MISSING_SECRET', '') }}",
|
|
3016
|
+
... secrets = {},
|
|
3017
|
+
... empty_secret_raises=True,
|
|
3018
|
+
... )
|
|
3019
|
+
Traceback (most recent call last):
|
|
3020
|
+
...
|
|
3021
|
+
tinybird.sql_template.SQLTemplateException: Template Syntax Error: Secret 'MISSING_SECRET' resolves to an empty value and cannot be used in sink metadata.
|
|
3022
|
+
|
|
3023
|
+
>>> render_template_with_secrets(
|
|
3024
|
+
... "sink_bucket",
|
|
3025
|
+
... "s3://{{ tb_secret('EMPTY_SECRET', 'fallback') }}",
|
|
3026
|
+
... secrets = {'EMPTY_SECRET': ''},
|
|
3027
|
+
... empty_secret_raises=True,
|
|
3028
|
+
... )
|
|
3029
|
+
Traceback (most recent call last):
|
|
3030
|
+
...
|
|
3031
|
+
tinybird.sql_template.SQLTemplateException: Template Syntax Error: Secret 'EMPTY_SECRET' resolves to an empty value and cannot be used in sink metadata.
|
|
2962
3032
|
"""
|
|
2963
3033
|
if not secrets:
|
|
2964
3034
|
secrets = {}
|
|
@@ -2974,15 +3044,24 @@ def render_template_with_secrets(name: str, content: str, secrets: Optional[Dict
|
|
|
2974
3044
|
The secret value or default
|
|
2975
3045
|
|
|
2976
3046
|
Raises:
|
|
2977
|
-
SQLTemplateException: If the secret is not found and no default is provided
|
|
3047
|
+
SQLTemplateException: If the secret is not found and no default is provided,
|
|
3048
|
+
or if empty_secret_raises is True and the resolved value is empty
|
|
2978
3049
|
"""
|
|
2979
3050
|
if secret_name in secrets:
|
|
2980
3051
|
value = secrets[secret_name]
|
|
2981
3052
|
if isinstance(value, str) and len(value) == 0:
|
|
3053
|
+
if empty_secret_raises:
|
|
3054
|
+
raise SQLTemplateException(
|
|
3055
|
+
f"Secret '{secret_name}' resolves to an empty value and cannot be used in sink metadata."
|
|
3056
|
+
)
|
|
2982
3057
|
return '""'
|
|
2983
3058
|
return value
|
|
2984
3059
|
elif default is not None:
|
|
2985
3060
|
if isinstance(default, str) and len(default) == 0:
|
|
3061
|
+
if empty_secret_raises:
|
|
3062
|
+
raise SQLTemplateException(
|
|
3063
|
+
f"Secret '{secret_name}' resolves to an empty value and cannot be used in sink metadata."
|
|
3064
|
+
)
|
|
2986
3065
|
return '""'
|
|
2987
3066
|
return default
|
|
2988
3067
|
else:
|
|
@@ -158,7 +158,20 @@ def has_unoptimized_join(sql: str, left_table: Optional[Union[Tuple[str, str], T
|
|
|
158
158
|
raise UnoptimizedJoinException(sql)
|
|
159
159
|
|
|
160
160
|
|
|
161
|
-
def
|
|
161
|
+
def _format_fragment_for_mutation_command(fragment: str) -> str:
|
|
162
|
+
"""Normalize a SQL fragment the way CH stores it in the
|
|
163
|
+
`system.mutations.command` column: format the expression with CH's own
|
|
164
|
+
formatter, then escape it as it appears inside the serialized command
|
|
165
|
+
(string literals keep backslash-escaped quotes)."""
|
|
166
|
+
formatted = chquery.format(f"""SELECT {fragment}""").split("SELECT ")[1]
|
|
167
|
+
formatted = formatted.replace("\\", "\\\\").replace("'", "''")
|
|
168
|
+
quoted = chquery.format(f"SELECT '{formatted}'").split("SELECT ")[1]
|
|
169
|
+
return quoted[1:-1]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def format_where_for_mutation_command(
|
|
173
|
+
where_clause: str, lightweight: bool = False, partition: Optional[str] = None
|
|
174
|
+
) -> str:
|
|
162
175
|
"""
|
|
163
176
|
>>> format_where_for_mutation_command("numnights = 99")
|
|
164
177
|
'DELETE WHERE numnights = 99'
|
|
@@ -172,12 +185,18 @@ def format_where_for_mutation_command(where_clause: str, lightweight: bool = Fal
|
|
|
172
185
|
"DELETE WHERE reservationid = \\\\'\\\\\\\\\\\\'foo\\\\'"
|
|
173
186
|
>>> format_where_for_mutation_command("number < 3", lightweight=True)
|
|
174
187
|
'UPDATE _row_exists = 0 WHERE number < 3'
|
|
188
|
+
>>> format_where_for_mutation_command("number < 3", lightweight=True, partition="201901")
|
|
189
|
+
'UPDATE _row_exists = 0 IN PARTITION 201901 WHERE number < 3'
|
|
190
|
+
>>> format_where_for_mutation_command("number < 3", lightweight=True, partition="'2019-01-01'")
|
|
191
|
+
"UPDATE _row_exists = 0 IN PARTITION \\\\'2019-01-01\\\\' WHERE number < 3"
|
|
175
192
|
"""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
193
|
+
quoted_condition = _format_fragment_for_mutation_command(where_clause)
|
|
194
|
+
prefix = "UPDATE _row_exists = 0" if lightweight else "DELETE"
|
|
195
|
+
# Partition-scoped mutations store the partition expression in the
|
|
196
|
+
# command (`… IN PARTITION <expr> WHERE <cond>`), normalized by the same
|
|
197
|
+
# CH formatter, so it must be part of the match.
|
|
198
|
+
partition_clause = f" IN PARTITION {_format_fragment_for_mutation_command(partition)}" if partition else ""
|
|
199
|
+
return f"{prefix}{partition_clause} WHERE {quoted_condition}"
|
|
181
200
|
|
|
182
201
|
|
|
183
202
|
# Functions that take table/dictionary names as string literal arguments.
|
|
@@ -624,7 +643,7 @@ def replace_tables(
|
|
|
624
643
|
|
|
625
644
|
if current_replacements:
|
|
626
645
|
# We need to transform the dictionary into something cacheable, so a sorted tuple of tuples it is
|
|
627
|
-
r = tuple(sorted(
|
|
646
|
+
r = tuple(sorted(current_replacements.items()))
|
|
628
647
|
sql = replace_tables_chquery_cached(
|
|
629
648
|
sql,
|
|
630
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.13'
|
|
8
|
+
__revision__ = '5d2bbc5'
|
|
@@ -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
|
)
|
|
@@ -282,6 +282,8 @@ class PipeCheckerRunner:
|
|
|
282
282
|
FROM {pipe_stats_rt}
|
|
283
283
|
WHERE
|
|
284
284
|
pipe_name = '{self.pipe_name}'
|
|
285
|
+
-- pipe_stats_rt retains 3 months; recent requests are enough for checks
|
|
286
|
+
AND start_datetime >= now() - INTERVAL 7 DAY
|
|
285
287
|
AND url IS NOT NULL
|
|
286
288
|
AND extractURLParameter(assumeNotNull(url), 'from') <> 'ui'
|
|
287
289
|
AND extractURLParameter(assumeNotNull(url), 'pipe_checker') <> 'true'
|
|
@@ -309,6 +311,8 @@ class PipeCheckerRunner:
|
|
|
309
311
|
FROM {pipe_stats_rt}
|
|
310
312
|
WHERE
|
|
311
313
|
pipe_name = '{self.pipe_name}'
|
|
314
|
+
-- pipe_stats_rt retains 3 months; recent requests are enough for checks
|
|
315
|
+
AND start_datetime >= now() - INTERVAL 7 DAY
|
|
312
316
|
AND url IS NOT NULL
|
|
313
317
|
AND extractURLParameter(assumeNotNull(url), 'from') <> 'ui'
|
|
314
318
|
AND extractURLParameter(assumeNotNull(url), 'pipe_checker') <> 'true'
|