tinybird-cli 6.5.6.dev0__tar.gz → 6.5.7.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.
Files changed (46) hide show
  1. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/PKG-INFO +7 -1
  2. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/__cli__.py +2 -2
  3. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/client.py +22 -43
  4. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/datafile_common.py +5 -1
  5. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/sql_template.py +7 -0
  6. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/common.py +22 -9
  7. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/datasource.py +13 -53
  8. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/PKG-INFO +7 -1
  9. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/setup.cfg +0 -0
  10. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/ch_utils/constants.py +0 -0
  11. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/ch_utils/engine.py +0 -0
  12. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/check_pypi.py +0 -0
  13. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/config.py +0 -0
  14. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/context.py +0 -0
  15. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/datatypes.py +0 -0
  16. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/feedback_manager.py +0 -0
  17. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/git_settings.py +0 -0
  18. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/sql.py +0 -0
  19. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/sql_template_fmt.py +0 -0
  20. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/sql_toolset.py +0 -0
  21. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/syncasync.py +0 -0
  22. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli.py +0 -0
  23. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/auth.py +0 -0
  24. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/branch.py +0 -0
  25. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/cicd.py +0 -0
  26. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/cli.py +0 -0
  27. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/config.py +0 -0
  28. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/connection.py +0 -0
  29. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/exceptions.py +0 -0
  30. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/fmt.py +0 -0
  31. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/job.py +0 -0
  32. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/pipe.py +0 -0
  33. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/regions.py +0 -0
  34. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/tag.py +0 -0
  35. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/telemetry.py +0 -0
  36. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/test.py +0 -0
  37. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  38. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/token.py +0 -0
  39. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/workspace.py +0 -0
  40. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  41. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tornado_template.py +0 -0
  42. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/SOURCES.txt +0 -0
  43. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/dependency_links.txt +0 -0
  44. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/entry_points.txt +0 -0
  45. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/requires.txt +0 -0
  46. {tinybird_cli-6.5.6.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird_cli
3
- Version: 6.5.6.dev0
3
+ Version: 6.5.7.dev0
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli
6
6
  Author: Tinybird
@@ -43,6 +43,12 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
43
43
  Changelog
44
44
  ----------
45
45
 
46
+
47
+ 6.5.6
48
+ ***********
49
+
50
+ - `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. Commands wait for completion by default to preserve the existing output; pass ``--no-wait`` to print the queued job ID instead. Remote URL imports continue to use their existing route.
51
+
46
52
  6.5.5
47
53
  ***********
48
54
 
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/cli'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '6.5.6.dev0'
8
- __revision__ = '5a6cd1d'
7
+ __version__ = '6.5.7.dev0'
8
+ __revision__ = '532ee82'
@@ -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
- params = {"name": datasource_name, "mode": mode, "format": format, "debug": "blocks_block_log"}
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
- params["replace_condition"] = sql_condition
451
+ v1_params["replace_condition"] = sql_condition
450
452
  if replace_options:
451
453
  for option in list(replace_options):
452
- params[option] = "true"
453
-
454
- async with aiofiles.open(file, "rb") as content:
455
- file_content = await content.read()
456
-
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,
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"v0/datasources?{urlencode(params, safe='')}",
486
- files=files,
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 status_callback:
490
- status_callback(res)
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):
@@ -4868,12 +4868,16 @@ async def check_fixtures_data(
4868
4868
  )
4869
4869
  sys.stdout.flush()
4870
4870
  try:
4871
- await client.datasource_append_data(
4871
+ import_job = await client.datasource_append_data(
4872
4872
  datasource_name=resource["params"]["name"],
4873
4873
  file=fixture_path,
4874
4874
  mode=mode,
4875
4875
  format=fixture_path.suffix[1:],
4876
4876
  )
4877
+ job_id = import_job.get("id") or import_job.get("import_id")
4878
+ if not isinstance(job_id, str):
4879
+ raise RuntimeError("We couldn't confirm that your fixture import started. Please try again.")
4880
+ await client.wait_for_job(job_id)
4877
4881
  click.echo(FeedbackManager.success_processing_data())
4878
4882
  except Exception as e:
4879
4883
  raise click.ClickException(FeedbackManager.error_processing_blocks(error=e))
@@ -2893,6 +2893,13 @@ def render_sql_template(
2893
2893
  documentation="/cli/advanced-templates.html",
2894
2894
  )
2895
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
+ )
2896
2903
  except IndexError as e:
2897
2904
  # This happens when trying to access string indices on empty strings
2898
2905
  if "string index out of range" in str(e):
@@ -970,7 +970,7 @@ async def push_data(
970
970
  sql_condition: Optional[str] = None,
971
971
  replace_options=None,
972
972
  concurrency: int = 1,
973
- use_v1: bool = False,
973
+ wait: bool = True,
974
974
  ) -> Optional[list[str]]:
975
975
  if url and type(url) is tuple:
976
976
  url = url[0]
@@ -1008,11 +1008,11 @@ async def push_data(
1008
1008
  datasource_name: str, url: str, mode: str, sql_condition: Optional[str], replace_options: Optional[Set[str]]
1009
1009
  ):
1010
1010
  parsed = urlparse(url)
1011
- if use_v1 and parsed.scheme in ("http", "https"):
1012
- raise CLIException("--experimental=use_v1 only supports local files.")
1011
+ # Remote URL imports retain the v0 API; local files use v1 body imports.
1012
+ is_remote_url = parsed.scheme in ("http", "https")
1013
1013
  # poor man's format detection
1014
1014
  _format = get_format_from_filename_or_url(url)
1015
- if parsed.scheme in ("http", "https"):
1015
+ if is_remote_url:
1016
1016
  res = await client.datasource_create_from_url(
1017
1017
  datasource_name,
1018
1018
  url,
@@ -1030,13 +1030,12 @@ async def push_data(
1030
1030
  sql_condition=sql_condition,
1031
1031
  format=_format,
1032
1032
  replace_options=replace_options,
1033
- use_v1=use_v1,
1034
1033
  )
1035
1034
 
1036
- if use_v1:
1035
+ if not is_remote_url:
1037
1036
  job_id = res.get("id") or res.get("import_id")
1038
1037
  if not isinstance(job_id, str):
1039
- raise CLIException("The v1 import response did not include a job ID.")
1038
+ raise CLIException("We couldn't confirm that your import started. Please try again.")
1040
1039
  return job_id
1041
1040
 
1042
1041
  datasource_name = res["datasource"]["name"]
@@ -1069,8 +1068,22 @@ async def push_data(
1069
1068
  try:
1070
1069
  tasks = [process_url(datasource_name, url, mode, sql_condition, replace_options) for url in urls]
1071
1070
  output = await gather_with_concurrency(concurrency, *tasks)
1072
- if use_v1:
1073
- return list(output)
1071
+ v1_job_ids = [result for result in output if isinstance(result, str)]
1072
+ if v1_job_ids:
1073
+ if not wait:
1074
+ return v1_job_ids
1075
+ for job_id in v1_job_ids:
1076
+ await wait_job_no_ui(client, job_id)
1077
+ datasource = await client.get_datasource(datasource_name)
1078
+ total_rows = (datasource.get("statistics", {}) or {}).get("row_count", 0)
1079
+ click.echo(FeedbackManager.success_total_rows(datasource=datasource_name, total_rows=total_rows))
1080
+ if mode == "replace":
1081
+ click.echo(FeedbackManager.success_replaced_datasource(datasource=datasource_name))
1082
+ else:
1083
+ click.echo(FeedbackManager.success_appended_datasource(datasource=datasource_name))
1084
+ click.echo(FeedbackManager.success_progress_blocks())
1085
+ click.echo(FeedbackManager.info_data_pushed(datasource=datasource_name))
1086
+ return None
1074
1087
  parser, total_rows, appended_rows = list(output)[-1]
1075
1088
  except AuthNoTokenException:
1076
1089
  raise
@@ -37,9 +37,7 @@ from tinybird.tb_cli_modules.common import (
37
37
  wait_job,
38
38
  )
39
39
  from tinybird.tb_cli_modules.config import CLIConfig
40
- from tinybird.tb_cli_modules.exceptions import CLIDatasourceException, CLIException
41
-
42
- EXPERIMENTAL_FEATURE_USE_V1 = "use_v1"
40
+ from tinybird.tb_cli_modules.exceptions import CLIDatasourceException
43
41
 
44
42
 
45
43
  @cli.group()
@@ -54,28 +52,6 @@ def _echo_v1_import_jobs_queued(job_ids: list[str], operation: str) -> None:
54
52
  click.echo(FeedbackManager.info_import_job_status(job_id=job_id))
55
53
 
56
54
 
57
- async def _wait_for_v1_import_jobs(client: TinyB, job_ids: list[str], operation: str) -> None:
58
- for job_id in job_ids:
59
- try:
60
- await wait_job(client, job_id, f"/v0/jobs/{job_id}", f"{operation} import")
61
- except CLIException:
62
- if await _echo_v1_import_job_details(client, job_id):
63
- raise click.exceptions.Exit(1)
64
- raise
65
- click.echo(FeedbackManager.success_import_job_completed(operation=operation, job_id=job_id))
66
-
67
-
68
- async def _echo_v1_import_job_details(client: TinyB, job_id: str) -> bool:
69
- try:
70
- job = await client.job(job_id)
71
- except Exception:
72
- return False
73
- click.echo(FeedbackManager.info_job(job=job_id))
74
- echo_safe_humanfriendly_tables_format_smart_table([job.values()], column_names=job.keys())
75
- click.echo("\n")
76
- return True
77
-
78
-
79
55
  @datasource.command(name="ls")
80
56
  @click.option("--match", default=None, help="Retrieve any resources matching the pattern. eg --match _test")
81
57
  @click.option(
@@ -166,12 +142,10 @@ async def datasource_ls(ctx: Context, match: Optional[str], format_: str):
166
142
  hidden=True,
167
143
  )
168
144
  @click.option("--concurrency", help="How many files to submit concurrently", default=1, hidden=True)
169
- @click.option("--wait", is_flag=True, default=False, help="Wait for a v1 import job to finish.")
170
145
  @click.option(
171
- "--experimental",
172
- type=click.Choice([EXPERIMENTAL_FEATURE_USE_V1]),
173
- multiple=True,
174
- help="Enable an experimental feature. May be specified multiple times.",
146
+ "--wait/--no-wait",
147
+ default=True,
148
+ help="Wait for the import to finish (default); use --no-wait to only queue it.",
175
149
  )
176
150
  @click.pass_context
177
151
  @coro
@@ -183,7 +157,6 @@ async def datasource_append(
183
157
  incremental: Optional[str],
184
158
  ignore_empty: bool,
185
159
  concurrency: int,
186
- experimental: tuple[str, ...],
187
160
  wait: bool,
188
161
  ):
189
162
  """
@@ -197,14 +170,9 @@ async def datasource_append(
197
170
 
198
171
  if not url:
199
172
  raise CLIDatasourceException(FeedbackManager.error_missing_url(datasource=datasource_name))
200
- use_v1 = EXPERIMENTAL_FEATURE_USE_V1 in experimental
201
- if wait and not use_v1:
202
- raise CLIDatasourceException("--wait requires --experimental=use_v1.")
203
- job_ids = await push_data(ctx, datasource_name, url, mode="append", concurrency=concurrency, use_v1=use_v1)
204
- if use_v1:
205
- _echo_v1_import_jobs_queued(job_ids or [], "Append")
206
- if wait:
207
- await _wait_for_v1_import_jobs(ctx.obj["client"], job_ids or [], "Append")
173
+ job_ids = await push_data(ctx, datasource_name, url, mode="append", concurrency=concurrency, wait=wait)
174
+ if job_ids:
175
+ _echo_v1_import_jobs_queued(job_ids, "Append")
208
176
 
209
177
 
210
178
  @datasource.command(name="replace")
@@ -212,12 +180,10 @@ async def datasource_append(
212
180
  @click.argument("url", nargs=-1)
213
181
  @click.option("--sql-condition", default=None, help="SQL WHERE condition to replace data", hidden=True)
214
182
  @click.option("--skip-incompatible-partition-key", is_flag=True, default=False, hidden=True)
215
- @click.option("--wait", is_flag=True, default=False, help="Wait for a v1 import job to finish.")
216
183
  @click.option(
217
- "--experimental",
218
- type=click.Choice([EXPERIMENTAL_FEATURE_USE_V1]),
219
- multiple=True,
220
- help="Enable an experimental feature. May be specified multiple times.",
184
+ "--wait/--no-wait",
185
+ default=True,
186
+ help="Wait for the import to finish (default); use --no-wait to only queue it.",
221
187
  )
222
188
  @click.pass_context
223
189
  @coro
@@ -227,7 +193,6 @@ async def datasource_replace(
227
193
  url,
228
194
  sql_condition,
229
195
  skip_incompatible_partition_key,
230
- experimental: tuple[str, ...],
231
196
  wait: bool,
232
197
  ):
233
198
  """
@@ -245,9 +210,6 @@ async def datasource_replace(
245
210
  replace_options = set()
246
211
  if skip_incompatible_partition_key:
247
212
  replace_options.add("skip_incompatible_partition_key")
248
- use_v1 = EXPERIMENTAL_FEATURE_USE_V1 in experimental
249
- if wait and not use_v1:
250
- raise CLIDatasourceException("--wait requires --experimental=use_v1.")
251
213
  job_ids = await push_data(
252
214
  ctx,
253
215
  datasource_name,
@@ -255,12 +217,10 @@ async def datasource_replace(
255
217
  mode="replace",
256
218
  sql_condition=sql_condition,
257
219
  replace_options=replace_options,
258
- use_v1=use_v1,
220
+ wait=wait,
259
221
  )
260
- if use_v1:
261
- _echo_v1_import_jobs_queued(job_ids or [], "Replace")
262
- if wait:
263
- await _wait_for_v1_import_jobs(ctx.obj["client"], job_ids or [], "Replace")
222
+ if job_ids:
223
+ _echo_v1_import_jobs_queued(job_ids, "Replace")
264
224
 
265
225
 
266
226
  @datasource.command(name="analyze")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird_cli
3
- Version: 6.5.6.dev0
3
+ Version: 6.5.7.dev0
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli
6
6
  Author: Tinybird
@@ -43,6 +43,12 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
43
43
  Changelog
44
44
  ----------
45
45
 
46
+
47
+ 6.5.6
48
+ ***********
49
+
50
+ - `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. Commands wait for completion by default to preserve the existing output; pass ``--no-wait`` to print the queued job ID instead. Remote URL imports continue to use their existing route.
51
+
46
52
  6.5.5
47
53
  ***********
48
54