tinybird 4.6.15.dev0__tar.gz → 4.6.16.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.15.dev0 → tinybird-4.6.16.dev0}/PKG-INFO +9 -1
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/client.py +22 -43
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/datafile/parse_pipe.py +8 -1
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/feedback_manager.py +4 -3
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/service_datasources.py +3 -3
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/sql_template.py +50 -1
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/__cli__.py +2 -2
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/client.py +31 -33
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/build_common.py +10 -1
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/common.py +18 -10
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/pipe_checker.py +4 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/pull.py +1 -1
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datasource.py +87 -71
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/feedback_manager.py +0 -3
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/common.py +59 -10
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/datasource.py +13 -53
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/workspace.py +10 -6
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird.egg-info/PKG-INFO +9 -1
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/setup.cfg +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/__cli__.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/ch_utils/engine.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/check_pypi.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/config.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/context.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/datafile/common.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/datafile/exceptions.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/datafile/parse_connection.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/datafile/parse_datasource.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/datatypes.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/git_settings.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/iterating/__init__.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/iterating/data_branch_modes.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/prompts.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/sql.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/sql_toolset.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/syncasync.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/check_pypi.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/cli.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/config.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/branch.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/build.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/cicd.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/cli.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/config.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/connection.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/connection_dynamodb.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/connection_kafka.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/connection_s3.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/copy.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/create.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/build.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/build_common.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/diff.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/fixture.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/format_common.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/format_connection.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/datafile/playground.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/deployment.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/deployment_common.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/deprecations.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/endpoint.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/exceptions.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/fmt.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/info.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/infra.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/job.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/job_common.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/llm.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/llm_utils.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/local.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/local_common.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/local_logs.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/login.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/login_common.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/logout.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/logs.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/materialization.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/open.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/pipe.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/preview.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/project.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/project_commands.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/py_project.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/query_output.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/regions.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/secret.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/secret_common.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/sink.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/table.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/telemetry.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/test.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/test_common.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/token.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/ts_project.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/watch.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/workspace.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb/modules/workspace_members.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/cli.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/config.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/fmt.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/tag.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird/tornado_template.py +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird.egg-info/SOURCES.txt +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird.egg-info/dependency_links.txt +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird.egg-info/entry_points.txt +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.dev0}/tinybird.egg-info/requires.txt +0 -0
- {tinybird-4.6.15.dev0 → tinybird-4.6.16.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.16.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/forward/commands
|
|
6
6
|
Author: Tinybird
|
|
@@ -51,11 +51,17 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
51
51
|
|
|
52
52
|
Changelog
|
|
53
53
|
----------
|
|
54
|
+
4.6.14
|
|
55
|
+
********
|
|
56
|
+
|
|
57
|
+
- `Changed` ``tb datasource append`` and ``tb datasource replace`` submit local CSV, NDJSON, and Parquet files through the v1 import endpoint by default. ``tb datasource append --events`` also submits its request body through v1. These asynchronous imports print the queued job ID; pass ``--wait`` to wait for completion. Remote URL imports continue to use their existing route.
|
|
54
58
|
|
|
55
59
|
4.6.13
|
|
56
60
|
********
|
|
57
61
|
|
|
62
|
+
- `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.
|
|
58
63
|
- `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.
|
|
64
|
+
- `Fixed` Forward builds branch with vendored files without incorrectly detecting drift.
|
|
59
65
|
|
|
60
66
|
4.6.12
|
|
61
67
|
********
|
|
@@ -74,6 +80,7 @@ Changelog
|
|
|
74
80
|
|
|
75
81
|
- `Removed` `tinybird.bi_stats_rt` from `tb logs` sources. The BI Connector `bi_stats` / `bi_stats_rt` service datasources are deprecated.
|
|
76
82
|
|
|
83
|
+
|
|
77
84
|
4.6.9
|
|
78
85
|
*******
|
|
79
86
|
|
|
@@ -82,6 +89,7 @@ Changelog
|
|
|
82
89
|
4.6.8
|
|
83
90
|
*******
|
|
84
91
|
|
|
92
|
+
- `Changed` Upgraded Tornado to 6.5.5.
|
|
85
93
|
- `Fixed` Tinybird Local CLI commands to respect Git branch local workspaces correctly.
|
|
86
94
|
|
|
87
95
|
4.6.7
|
|
@@ -411,7 +411,7 @@ class TinyB:
|
|
|
411
411
|
sql_condition: Optional[str] = None,
|
|
412
412
|
format: str = "csv",
|
|
413
413
|
replace_options: Optional[Set[str]] = None,
|
|
414
|
-
):
|
|
414
|
+
) -> Dict[str, Any]:
|
|
415
415
|
params = {"name": table_name, "url": url, "mode": mode, "debug": "blocks_block_log", "format": format}
|
|
416
416
|
|
|
417
417
|
if sql_condition:
|
|
@@ -437,58 +437,37 @@ class TinyB:
|
|
|
437
437
|
datasource_name: str,
|
|
438
438
|
file: Union[str, Path],
|
|
439
439
|
mode: str = "append",
|
|
440
|
-
status_callback=None,
|
|
440
|
+
status_callback=None, # noqa: ARG002 - kept for v0 caller compatibility
|
|
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,
|
|
445
444
|
):
|
|
446
|
-
|
|
447
|
-
|
|
445
|
+
# V1 returns the import job immediately, so waiting and progress reporting
|
|
446
|
+
# belong to the caller.
|
|
447
|
+
async with aiofiles.open(file, "rb") as content:
|
|
448
|
+
file_content = await content.read()
|
|
449
|
+
v1_params = {"format": format}
|
|
448
450
|
if sql_condition:
|
|
449
|
-
|
|
451
|
+
v1_params["replace_condition"] = sql_condition
|
|
450
452
|
if replace_options:
|
|
451
453
|
for option in list(replace_options):
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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,
|
|
476
|
-
method="POST",
|
|
477
|
-
)
|
|
478
|
-
|
|
479
|
-
if format == "csv":
|
|
480
|
-
files = {"csv": ("csv", file_content)}
|
|
481
|
-
else:
|
|
482
|
-
files = {"ndjson": ("ndjson", file_content)}
|
|
483
|
-
|
|
454
|
+
v1_params[option] = "true"
|
|
455
|
+
content_types = {
|
|
456
|
+
"csv": "text/csv",
|
|
457
|
+
"ndjson": "application/x-ndjson",
|
|
458
|
+
"parquet": "application/vnd.apache.parquet",
|
|
459
|
+
}
|
|
460
|
+
headers = {"Content-Type": content_types[format]}
|
|
461
|
+
if str(file).endswith(".gz"):
|
|
462
|
+
headers["Content-Encoding"] = "gzip"
|
|
484
463
|
res = await self._req(
|
|
485
|
-
f"
|
|
486
|
-
|
|
464
|
+
f"/v1/datasources/{quote(datasource_name, safe='')}/{mode}?{urlencode(v1_params, safe='')}",
|
|
465
|
+
data=file_content,
|
|
466
|
+
headers=headers,
|
|
487
467
|
method="POST",
|
|
488
468
|
)
|
|
489
|
-
if
|
|
490
|
-
|
|
491
|
-
|
|
469
|
+
if not isinstance(res, dict):
|
|
470
|
+
raise RuntimeError("We couldn't confirm that your import started. Please try again.")
|
|
492
471
|
return res
|
|
493
472
|
|
|
494
473
|
async def datasource_truncate(self, datasource_name: str):
|
|
@@ -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
|
|
|
@@ -61,6 +65,9 @@ def parse_pipe(
|
|
|
61
65
|
secrets_list: Optional[List[str]] = None
|
|
62
66
|
if secrets:
|
|
63
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"])
|
|
64
71
|
# Setting test_mode=True to ignore errors on required parameters and
|
|
65
72
|
# secrets_in_test_mode=False to raise errors on missing secrets
|
|
66
73
|
sql, _, variable_warnings = render_sql_template(
|
|
@@ -242,10 +242,11 @@ class FeedbackManager:
|
|
|
242
242
|
error_organization_index = error_message(
|
|
243
243
|
"Error selecting organization '{organization_index}'. Select a valid index or 0 to cancel"
|
|
244
244
|
)
|
|
245
|
-
warning_none_organization = warning_message(
|
|
246
|
-
"Tinybird is now based on organizations. Please, go to the UI ({ui_host}) to follow the migration process. \nYour workspace will be created any way."
|
|
247
|
-
)
|
|
248
245
|
error_while_fetching_orgs = error_message("Error while fetching organizations: {error}")
|
|
246
|
+
error_classic_workspace_creation_deprecated = error_message(
|
|
247
|
+
"The Classic experience is deprecated. New workspaces should be created in Forward. "
|
|
248
|
+
"Migration guide: https://www.tinybird.co/docs/forward/guides/migrate-from-classic"
|
|
249
|
+
)
|
|
249
250
|
error_deleted_include = error_message(
|
|
250
251
|
"Related include file {include_file} was deleted and it's used in {filename}. Delete or remove dependency from {filename}."
|
|
251
252
|
)
|
|
@@ -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"},
|
|
@@ -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:
|
|
@@ -2851,6 +2893,13 @@ def render_sql_template(
|
|
|
2851
2893
|
documentation="/cli/advanced-templates.html",
|
|
2852
2894
|
)
|
|
2853
2895
|
raise SQLTemplateException(str(e), documentation="/cli/advanced-templates.html")
|
|
2896
|
+
except KeyError as e:
|
|
2897
|
+
key = e.args[0] if e.args else "unknown"
|
|
2898
|
+
raise SQLTemplateException(
|
|
2899
|
+
f"Missing key '{key}' while rendering the template. "
|
|
2900
|
+
"Ensure every object accessed in the template has this key.",
|
|
2901
|
+
documentation="/cli/advanced-templates.html",
|
|
2902
|
+
)
|
|
2854
2903
|
except IndexError as e:
|
|
2855
2904
|
# This happens when trying to access string indices on empty strings
|
|
2856
2905
|
if "string index out of range" in str(e):
|
|
@@ -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.16.dev0'
|
|
8
|
+
__revision__ = '532ee82'
|
|
@@ -435,7 +435,7 @@ class TinyB:
|
|
|
435
435
|
sql_condition: Optional[str] = None,
|
|
436
436
|
format: str = "csv",
|
|
437
437
|
replace_options: Optional[Set[str]] = None,
|
|
438
|
-
):
|
|
438
|
+
) -> Dict[str, Any]:
|
|
439
439
|
params = {"name": table_name, "url": url, "mode": mode, "debug": "blocks_block_log", "format": format}
|
|
440
440
|
|
|
441
441
|
if sql_condition:
|
|
@@ -465,16 +465,9 @@ 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,
|
|
469
468
|
):
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if sql_condition:
|
|
473
|
-
params["replace_condition"] = sql_condition
|
|
474
|
-
if replace_options:
|
|
475
|
-
for option in list(replace_options):
|
|
476
|
-
params[option] = "true"
|
|
477
|
-
|
|
469
|
+
# Keep this argument for SDK compatibility; v1 returns before import completion.
|
|
470
|
+
del status_callback
|
|
478
471
|
with open(file, "rb") as content:
|
|
479
472
|
file_content = content.read()
|
|
480
473
|
content_types = {
|
|
@@ -485,29 +478,34 @@ class TinyB:
|
|
|
485
478
|
headers = {"Content-Type": content_types[format]}
|
|
486
479
|
if str(file).endswith(".gz"):
|
|
487
480
|
headers["Content-Encoding"] = "gzip"
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
res = self._req(
|
|
509
|
-
|
|
510
|
-
|
|
481
|
+
v1_params = {"format": format}
|
|
482
|
+
if sql_condition:
|
|
483
|
+
v1_params["replace_condition"] = sql_condition
|
|
484
|
+
if replace_options:
|
|
485
|
+
for option in list(replace_options):
|
|
486
|
+
v1_params[option] = "true"
|
|
487
|
+
res = self._req(
|
|
488
|
+
f"/v1/datasources/{quote(datasource_name, safe='')}/{mode}?{urlencode(v1_params, safe='')}",
|
|
489
|
+
data=file_content,
|
|
490
|
+
headers=headers,
|
|
491
|
+
method="POST",
|
|
492
|
+
)
|
|
493
|
+
if not isinstance(res, dict):
|
|
494
|
+
raise RuntimeError("We couldn't confirm that your import started. Please try again.")
|
|
495
|
+
if "error" in res:
|
|
496
|
+
raise Exception(res["error"])
|
|
497
|
+
return res
|
|
498
|
+
|
|
499
|
+
def datasource_append_events(self, datasource_name: str, events: str) -> dict:
|
|
500
|
+
"""Submit NDJSON event data through the asynchronous v1 append route."""
|
|
501
|
+
res = self._req(
|
|
502
|
+
f"/v1/datasources/{quote(datasource_name, safe='')}/append?format=ndjson",
|
|
503
|
+
data=events.encode(),
|
|
504
|
+
headers={"Content-Type": "application/x-ndjson"},
|
|
505
|
+
method="POST",
|
|
506
|
+
)
|
|
507
|
+
if "error" in res:
|
|
508
|
+
raise Exception(res["error"])
|
|
511
509
|
return res
|
|
512
510
|
|
|
513
511
|
def datasource_truncate(self, datasource_name: str):
|
|
@@ -300,7 +300,8 @@ def build_project_with_deploy_api(
|
|
|
300
300
|
if build_result == "no_changes":
|
|
301
301
|
return False
|
|
302
302
|
if build_result != "success":
|
|
303
|
-
deployment_errors = deployment.get("errors", []) if deployment else result.get("errors", [])
|
|
303
|
+
deployment_errors = list(deployment.get("errors", []) if deployment else result.get("errors", []))
|
|
304
|
+
deployment_errors.extend(item for item in deployment.get("feedback", []) if item.get("level") == "ERROR")
|
|
304
305
|
raise click.ClickException(result.get("error") or format_build_errors(deployment_errors))
|
|
305
306
|
if not deployment:
|
|
306
307
|
raise click.ClickException("Couldn't parse deployment response from server")
|
|
@@ -328,6 +329,7 @@ def get_build_request_files(
|
|
|
328
329
|
project_with_vendors: Optional[Project] = None,
|
|
329
330
|
) -> tuple[list[tuple[str, tuple[str, str, str]]], list[str]]:
|
|
330
331
|
multipart_boundary_data_project = "data_project://"
|
|
332
|
+
multipart_boundary_data_project_vendored = "data_project_vendored://"
|
|
331
333
|
datafile_type_to_content_type = {
|
|
332
334
|
".datasource": "text/plain",
|
|
333
335
|
".pipe": "text/plain",
|
|
@@ -350,6 +352,13 @@ def get_build_request_files(
|
|
|
350
352
|
|
|
351
353
|
files.append((multipart_boundary_data_project, (relative_path, content, content_type)))
|
|
352
354
|
|
|
355
|
+
for file_path in project.get_vendored_files():
|
|
356
|
+
relative_path = Path(file_path).relative_to(project_path).as_posix()
|
|
357
|
+
with open(file_path, "rb") as fd:
|
|
358
|
+
content_type = datafile_type_to_content_type.get(Path(file_path).suffix, "application/unknown")
|
|
359
|
+
content = fd.read().decode("utf-8")
|
|
360
|
+
files.append((multipart_boundary_data_project_vendored, (relative_path, content, content_type)))
|
|
361
|
+
|
|
353
362
|
return files, project_files
|
|
354
363
|
|
|
355
364
|
|
|
@@ -925,7 +925,7 @@ def push_data(
|
|
|
925
925
|
replace_options=None,
|
|
926
926
|
concurrency: int = 1,
|
|
927
927
|
silent: bool = False,
|
|
928
|
-
|
|
928
|
+
wait: bool = True,
|
|
929
929
|
) -> Optional[list[str]]:
|
|
930
930
|
if url and type(url) is tuple:
|
|
931
931
|
url = url[0]
|
|
@@ -963,11 +963,11 @@ def push_data(
|
|
|
963
963
|
datasource_name: str, url: str, mode: str, sql_condition: Optional[str], replace_options: Optional[Set[str]]
|
|
964
964
|
):
|
|
965
965
|
parsed = urlparse(url)
|
|
966
|
-
|
|
967
|
-
|
|
966
|
+
# Remote URL imports retain the v0 API; local files use v1 body imports.
|
|
967
|
+
is_remote_url = parsed.scheme in ("http", "https")
|
|
968
968
|
# poor man's format detection
|
|
969
969
|
_format = get_format_from_filename_or_url(url)
|
|
970
|
-
if
|
|
970
|
+
if is_remote_url:
|
|
971
971
|
res = client.datasource_create_from_url(
|
|
972
972
|
datasource_name,
|
|
973
973
|
url,
|
|
@@ -985,13 +985,12 @@ def push_data(
|
|
|
985
985
|
sql_condition=sql_condition,
|
|
986
986
|
format=_format,
|
|
987
987
|
replace_options=replace_options,
|
|
988
|
-
use_v1=use_v1,
|
|
989
988
|
)
|
|
990
989
|
|
|
991
|
-
if
|
|
990
|
+
if not is_remote_url:
|
|
992
991
|
job_id = res.get("id") or res.get("import_id")
|
|
993
992
|
if not isinstance(job_id, str):
|
|
994
|
-
raise CLIException("
|
|
993
|
+
raise CLIException("We couldn't confirm that your import started. Please try again.")
|
|
995
994
|
return job_id
|
|
996
995
|
|
|
997
996
|
datasource_name = res["datasource"]["name"]
|
|
@@ -1024,8 +1023,17 @@ def push_data(
|
|
|
1024
1023
|
try:
|
|
1025
1024
|
tasks = [process_url(datasource_name, url, mode, sql_condition, replace_options) for url in urls]
|
|
1026
1025
|
output = gather_with_concurrency(concurrency, *tasks)
|
|
1027
|
-
if
|
|
1028
|
-
|
|
1026
|
+
v1_job_ids = [result for result in output if isinstance(result, str)]
|
|
1027
|
+
if v1_job_ids:
|
|
1028
|
+
if not wait:
|
|
1029
|
+
return v1_job_ids
|
|
1030
|
+
for job_id in v1_job_ids:
|
|
1031
|
+
wait_job_no_ui(client, job_id)
|
|
1032
|
+
if not silent:
|
|
1033
|
+
if mode == "replace":
|
|
1034
|
+
click.echo(FeedbackManager.success_replaced_datasource(datasource=datasource_name))
|
|
1035
|
+
click.echo(FeedbackManager.success_progress_blocks())
|
|
1036
|
+
return None
|
|
1029
1037
|
parser, total_rows, appended_rows = list(output)[-1]
|
|
1030
1038
|
except AuthNoTokenException:
|
|
1031
1039
|
raise
|
|
@@ -2380,7 +2388,7 @@ def force_echo(string: str) -> None:
|
|
|
2380
2388
|
click.echo(string, force_output=True) # type: ignore
|
|
2381
2389
|
|
|
2382
2390
|
|
|
2383
|
-
def echo_json(data: Dict[str, Any], indent: Union[
|
|
2391
|
+
def echo_json(data: Dict[str, Any], indent: Union[int, str, None] = None) -> None:
|
|
2384
2392
|
force_echo(json.dumps(data, indent=indent))
|
|
2385
2393
|
|
|
2386
2394
|
|
|
@@ -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'
|
|
@@ -53,7 +53,7 @@ def folder_pull(
|
|
|
53
53
|
if not candidates:
|
|
54
54
|
return None
|
|
55
55
|
|
|
56
|
-
return
|
|
56
|
+
return min(candidates, key=lambda path: (len(path.parent.relative_to(dest_folder).parts), str(path)))
|
|
57
57
|
|
|
58
58
|
def write_files(
|
|
59
59
|
resources: list[dict[str, Any]],
|