tinybird-cli 6.5.5.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.5.dev0 → tinybird_cli-6.5.7.dev0}/PKG-INFO +12 -1
  2. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/__cli__.py +2 -2
  3. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/client.py +22 -43
  4. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/datafile_common.py +5 -1
  5. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/feedback_manager.py +4 -3
  6. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/sql_template.py +7 -0
  7. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/common.py +59 -10
  8. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/datasource.py +13 -53
  9. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/workspace.py +10 -6
  10. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/PKG-INFO +12 -1
  11. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/setup.cfg +0 -0
  12. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/ch_utils/constants.py +0 -0
  13. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/ch_utils/engine.py +0 -0
  14. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/check_pypi.py +0 -0
  15. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/config.py +0 -0
  16. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/context.py +0 -0
  17. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/datatypes.py +0 -0
  18. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/git_settings.py +0 -0
  19. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/sql.py +0 -0
  20. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/sql_template_fmt.py +0 -0
  21. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/sql_toolset.py +0 -0
  22. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/syncasync.py +0 -0
  23. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli.py +0 -0
  24. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/auth.py +0 -0
  25. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/branch.py +0 -0
  26. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/cicd.py +0 -0
  27. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/cli.py +0 -0
  28. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/config.py +0 -0
  29. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/connection.py +0 -0
  30. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/exceptions.py +0 -0
  31. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/fmt.py +0 -0
  32. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/job.py +0 -0
  33. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/pipe.py +0 -0
  34. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/regions.py +0 -0
  35. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/tag.py +0 -0
  36. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/telemetry.py +0 -0
  37. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/test.py +0 -0
  38. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  39. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/token.py +0 -0
  40. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  41. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird/tornado_template.py +0 -0
  42. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/SOURCES.txt +0 -0
  43. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/dependency_links.txt +0 -0
  44. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/entry_points.txt +0 -0
  45. {tinybird_cli-6.5.5.dev0 → tinybird_cli-6.5.7.dev0}/tinybird_cli.egg-info/requires.txt +0 -0
  46. {tinybird_cli-6.5.5.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.5.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,17 @@ 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
+
52
+ 6.5.5
53
+ ***********
54
+
55
+ - `Changed` `tb workspace create` is now limited to organization in Enterprise plans
56
+
46
57
  6.5.4
47
58
  ***********
48
59
 
@@ -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.5.dev0'
8
- __revision__ = '5d2bbc5'
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))
@@ -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
  )
@@ -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):
@@ -624,7 +624,14 @@ async def create_workspace_non_interactive(
624
624
  await fork_workspace(client, user_client, created_workspace)
625
625
 
626
626
  except Exception as e:
627
- raise CLIWorkspaceException(FeedbackManager.error_exception(error=str(e)))
627
+ message = str(e)
628
+ # The server (POST /v0/workspaces) rejects Classic workspace creation for
629
+ # non-enterprise organizations with this deprecation error. The API is the
630
+ # source of truth; we only reformat its message so it isn't buried behind a
631
+ # generic "Forbidden:" prefix. Matched on the stable migration-guide URL.
632
+ if "migrate-from-classic" in message:
633
+ raise CLIWorkspaceException(FeedbackManager.error_classic_workspace_creation_deprecated())
634
+ raise CLIWorkspaceException(FeedbackManager.error_exception(error=message))
628
635
 
629
636
 
630
637
  async def create_workspace_interactive(
@@ -963,7 +970,7 @@ async def push_data(
963
970
  sql_condition: Optional[str] = None,
964
971
  replace_options=None,
965
972
  concurrency: int = 1,
966
- use_v1: bool = False,
973
+ wait: bool = True,
967
974
  ) -> Optional[list[str]]:
968
975
  if url and type(url) is tuple:
969
976
  url = url[0]
@@ -1001,11 +1008,11 @@ async def push_data(
1001
1008
  datasource_name: str, url: str, mode: str, sql_condition: Optional[str], replace_options: Optional[Set[str]]
1002
1009
  ):
1003
1010
  parsed = urlparse(url)
1004
- if use_v1 and parsed.scheme in ("http", "https"):
1005
- 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")
1006
1013
  # poor man's format detection
1007
1014
  _format = get_format_from_filename_or_url(url)
1008
- if parsed.scheme in ("http", "https"):
1015
+ if is_remote_url:
1009
1016
  res = await client.datasource_create_from_url(
1010
1017
  datasource_name,
1011
1018
  url,
@@ -1023,13 +1030,12 @@ async def push_data(
1023
1030
  sql_condition=sql_condition,
1024
1031
  format=_format,
1025
1032
  replace_options=replace_options,
1026
- use_v1=use_v1,
1027
1033
  )
1028
1034
 
1029
- if use_v1:
1035
+ if not is_remote_url:
1030
1036
  job_id = res.get("id") or res.get("import_id")
1031
1037
  if not isinstance(job_id, str):
1032
- 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.")
1033
1039
  return job_id
1034
1040
 
1035
1041
  datasource_name = res["datasource"]["name"]
@@ -1062,8 +1068,22 @@ async def push_data(
1062
1068
  try:
1063
1069
  tasks = [process_url(datasource_name, url, mode, sql_condition, replace_options) for url in urls]
1064
1070
  output = await gather_with_concurrency(concurrency, *tasks)
1065
- if use_v1:
1066
- 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
1067
1087
  parser, total_rows, appended_rows = list(output)[-1]
1068
1088
  except AuthNoTokenException:
1069
1089
  raise
@@ -2014,6 +2034,35 @@ async def get_organizations_by_user(ctx: Context, user_token: str) -> List[Dict[
2014
2034
  return organizations
2015
2035
 
2016
2036
 
2037
+ CLASSIC_WORKSPACE_CREATION_ALLOWED_ORGANIZATION_BILLING_PLANS = {
2038
+ "infrastructure_usage",
2039
+ "shared_infrastructure_usage_commitment",
2040
+ "mixed_infrastructure_usage",
2041
+ }
2042
+
2043
+
2044
+ def get_organization_billing_plan(organization: Dict[str, Any]) -> Optional[str]:
2045
+ plan = organization.get("plan")
2046
+ if isinstance(plan, dict):
2047
+ billing = plan.get("billing")
2048
+ return billing if isinstance(billing, str) else None
2049
+
2050
+ for field in ("billing", "commitment_billing"):
2051
+ billing = organization.get(field)
2052
+ if isinstance(billing, str):
2053
+ return billing
2054
+
2055
+ return None
2056
+
2057
+
2058
+ def get_classic_workspace_creation_organizations(organizations: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
2059
+ return [
2060
+ organization
2061
+ for organization in organizations
2062
+ if get_organization_billing_plan(organization) in CLASSIC_WORKSPACE_CREATION_ALLOWED_ORGANIZATION_BILLING_PLANS
2063
+ ]
2064
+
2065
+
2017
2066
  OrgType = Literal["tinybird", "domain", "admin", "member"]
2018
2067
 
2019
2068
 
@@ -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")
@@ -22,6 +22,7 @@ from tinybird.tb_cli_modules.common import (
22
22
  create_workspace_interactive,
23
23
  create_workspace_non_interactive,
24
24
  echo_safe_humanfriendly_tables_format_smart_table,
25
+ get_classic_workspace_creation_organizations,
25
26
  get_current_main_workspace,
26
27
  get_organizations_by_user,
27
28
  is_valid_starterkit,
@@ -226,19 +227,22 @@ async def create_workspace(
226
227
 
227
228
  organization_name = None
228
229
  organizations = await get_organizations_by_user(ctx, user_token)
230
+ selectable_organizations = get_classic_workspace_creation_organizations(organizations)
229
231
 
230
232
  if organization_id:
231
233
  organization = next((org for org in organizations if org.get("id") == organization_id), None)
232
234
  if not organization:
233
235
  raise CLIWorkspaceException(FeedbackManager.error_organization_not_found(organization_id=organization_id))
234
236
  organization_name = organization.get("name")
235
- elif len(organizations) == 0:
236
- click.echo(FeedbackManager.warning_none_organization(ui_host=ui_host))
237
- elif len(organizations) == 1:
238
- organization_id = organizations[0].get("id")
239
- organization_name = organizations[0].get("name")
237
+ elif len(selectable_organizations) == 0:
238
+ # Let the API return the Classic deprecation error without printing the
239
+ # old organizations-migration warning that claimed creation would proceed.
240
+ organization_id = None
241
+ elif len(selectable_organizations) == 1:
242
+ organization_id = selectable_organizations[0].get("id")
243
+ organization_name = selectable_organizations[0].get("name")
240
244
  else:
241
- sorted_organizations = sort_organizations_by_user(organizations, user_email=config.get_user_email())
245
+ sorted_organizations = sort_organizations_by_user(selectable_organizations, user_email=config.get_user_email())
242
246
  current_organization = await ask_for_organization_interactively(sorted_organizations)
243
247
  if current_organization:
244
248
  organization_id = current_organization.get("id")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird_cli
3
- Version: 6.5.5.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,17 @@ 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
+
52
+ 6.5.5
53
+ ***********
54
+
55
+ - `Changed` `tb workspace create` is now limited to organization in Enterprise plans
56
+
46
57
  6.5.4
47
58
  ***********
48
59