tinybird-cli 6.5.5.dev0__py3-none-any.whl → 6.5.7.dev0__py3-none-any.whl
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/__cli__.py +2 -2
- tinybird/client.py +22 -43
- tinybird/datafile_common.py +5 -1
- tinybird/feedback_manager.py +4 -3
- tinybird/sql_template.py +7 -0
- tinybird/tb_cli_modules/common.py +59 -10
- tinybird/tb_cli_modules/datasource.py +13 -53
- tinybird/tb_cli_modules/workspace.py +10 -6
- {tinybird_cli-6.5.5.dev0.dist-info → tinybird_cli-6.5.7.dev0.dist-info}/METADATA +12 -1
- {tinybird_cli-6.5.5.dev0.dist-info → tinybird_cli-6.5.7.dev0.dist-info}/RECORD +13 -13
- {tinybird_cli-6.5.5.dev0.dist-info → tinybird_cli-6.5.7.dev0.dist-info}/WHEEL +0 -0
- {tinybird_cli-6.5.5.dev0.dist-info → tinybird_cli-6.5.7.dev0.dist-info}/entry_points.txt +0 -0
- {tinybird_cli-6.5.5.dev0.dist-info → tinybird_cli-6.5.7.dev0.dist-info}/top_level.txt +0 -0
tinybird/__cli__.py
CHANGED
|
@@ -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.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '6.5.7.dev0'
|
|
8
|
+
__revision__ = '532ee82'
|
tinybird/client.py
CHANGED
|
@@ -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):
|
tinybird/datafile_common.py
CHANGED
|
@@ -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))
|
tinybird/feedback_manager.py
CHANGED
|
@@ -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
|
)
|
tinybird/sql_template.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1005
|
-
|
|
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
|
|
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
|
|
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("
|
|
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
|
|
1066
|
-
|
|
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
|
|
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
|
-
"--
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
201
|
-
if
|
|
202
|
-
|
|
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
|
-
"--
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
220
|
+
wait=wait,
|
|
259
221
|
)
|
|
260
|
-
if
|
|
261
|
-
_echo_v1_import_jobs_queued(job_ids
|
|
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(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
organization_id =
|
|
239
|
-
|
|
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(
|
|
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.
|
|
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
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
tinybird/__cli__.py,sha256=
|
|
1
|
+
tinybird/__cli__.py,sha256=i_-xpNFaP_CRKjaWjkZKj-4VaBC1PpNyvC6YTjFn8XM,236
|
|
2
2
|
tinybird/check_pypi.py,sha256=_4NkharLyR_ELrAdit-ftqIWvOf7jZNPt3i76frlo9g,975
|
|
3
|
-
tinybird/client.py,sha256=
|
|
3
|
+
tinybird/client.py,sha256=av3w9-lKfDWe-rKFE-V05-D4Ytr90kf2BCqu6z7XXH0,52753
|
|
4
4
|
tinybird/config.py,sha256=ltL9qIfY-tnFXeivrLLi7apS_OkzEJT_jRXAJu0SRQg,7164
|
|
5
5
|
tinybird/context.py,sha256=o4yvlXPkMLmdh-XJl3wpmqPAMeRRz5ScKzKlHHKn_I8,1201
|
|
6
|
-
tinybird/datafile_common.py,sha256=
|
|
6
|
+
tinybird/datafile_common.py,sha256=q9rK4MfbfbDTus4jcYNI2Kfq8K3rlN344wM17MDVdCs,237658
|
|
7
7
|
tinybird/datatypes.py,sha256=DRScTI1-lgYjbC7u_4qxOLFleWL-vief_eBg_9WU37U,11304
|
|
8
|
-
tinybird/feedback_manager.py,sha256=
|
|
8
|
+
tinybird/feedback_manager.py,sha256=2n0s6AMbjtFsELJN9pJnG5JjhM3lUueXBgF4mZ0WiCw,68318
|
|
9
9
|
tinybird/git_settings.py,sha256=mqWgeboOlOFsSo97qyv595UCR2R1QCAqT4GTawBNPBg,3935
|
|
10
10
|
tinybird/sql.py,sha256=L3tTmm62iTrf6WQdcV8mmwrCMitLFUvJ3OfDzyRTMGw,47385
|
|
11
|
-
tinybird/sql_template.py,sha256=
|
|
11
|
+
tinybird/sql_template.py,sha256=BLX_qAmnCrRouLAcpqey32-Ctub2ZaXSvpvgyadGHvM,132236
|
|
12
12
|
tinybird/sql_template_fmt.py,sha256=Ma4qcs-2r8ZXQC4GUmrCqYz34DsnGF8k5lE2Jwnr314,10638
|
|
13
13
|
tinybird/sql_toolset.py,sha256=qNKHPGKTR4MrMHKkj1qHqmlB1kPqnbT-SozyzUBeD6s,28498
|
|
14
14
|
tinybird/syncasync.py,sha256=rIPmCvygWSFqfnlVqhZH4N9gVVTvD6DEPsfoxGizYrI,27776
|
|
@@ -20,10 +20,10 @@ tinybird/tb_cli_modules/auth.py,sha256=3xu8STgouOgLkqlBf9LWFg9Oto_NyuDKsUWF95-zG
|
|
|
20
20
|
tinybird/tb_cli_modules/branch.py,sha256=vbjLxIHz5XOqfMGwjyEZ7gji8Jw3PWPKKrPXViWdiIc,39348
|
|
21
21
|
tinybird/tb_cli_modules/cicd.py,sha256=i2Mw8AbmEVNBcEPYdio7liy3PGqh1ezVFZ0OmJ9ww5o,13809
|
|
22
22
|
tinybird/tb_cli_modules/cli.py,sha256=KvgZfdYRvgKG5QAiFmlfLbVGI-xmuxbs-EdkAslqrEQ,60242
|
|
23
|
-
tinybird/tb_cli_modules/common.py,sha256=
|
|
23
|
+
tinybird/tb_cli_modules/common.py,sha256=tgiEmN0dAIrYYVMO-ezwiDAS3mGghCMB0ijD-gcZEtE,79524
|
|
24
24
|
tinybird/tb_cli_modules/config.py,sha256=lukiJeihI09blGeeS0FWeqVbKWhqzZmyoaJDIL0NkyQ,11182
|
|
25
25
|
tinybird/tb_cli_modules/connection.py,sha256=JO2v2xTGoiMeThzCncvxMFUHLghyyGXNF0FcAvi5ZOk,24543
|
|
26
|
-
tinybird/tb_cli_modules/datasource.py,sha256=
|
|
26
|
+
tinybird/tb_cli_modules/datasource.py,sha256=4tkdj17-GrZ7VbsnDQ8KUA761BmSeD3XQOOqbZjUko0,36483
|
|
27
27
|
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
28
28
|
tinybird/tb_cli_modules/fmt.py,sha256=edQap4tAqWMWogSIx5zriT75naLi73XTB3NwatmcrFw,3518
|
|
29
29
|
tinybird/tb_cli_modules/job.py,sha256=BxiqChDYa_4W4tFXHIUz2bOzkx8WMFywynjiPWgDlnE,2936
|
|
@@ -33,11 +33,11 @@ tinybird/tb_cli_modules/tag.py,sha256=S5x8uDW0rsJEI5lKH2mefro7tEzcHyDNWrV_ZWNyS1
|
|
|
33
33
|
tinybird/tb_cli_modules/telemetry.py,sha256=W098H6jmS4kpE7hN3tadaREBTf7oMocel-lkKWN0pU8,10466
|
|
34
34
|
tinybird/tb_cli_modules/test.py,sha256=Vf8oK96V81HdKGsT79y6MUz6oz_VrYIwTbRnzzJs4rQ,4350
|
|
35
35
|
tinybird/tb_cli_modules/token.py,sha256=JXATKTlbXohP9ZDZjlz8E4VYG6zrknKZhuz_wh1zBBc,13793
|
|
36
|
-
tinybird/tb_cli_modules/workspace.py,sha256
|
|
36
|
+
tinybird/tb_cli_modules/workspace.py,sha256=-GW_XZHPc5atVFUkYpXsl1G8t36jZyZ1B0UWAZYwSaA,12709
|
|
37
37
|
tinybird/tb_cli_modules/workspace_members.py,sha256=ksXsjd233y9-sNlz4Qb-meZbX4zn1B84e_bSm2i8rhg,8731
|
|
38
38
|
tinybird/tb_cli_modules/tinyunit/tinyunit.py,sha256=JrZQTb9NA-1kcL2Vm0JuDRNFdH8jcYYOM3ovJDVs28E,11063
|
|
39
|
-
tinybird_cli-6.5.
|
|
40
|
-
tinybird_cli-6.5.
|
|
41
|
-
tinybird_cli-6.5.
|
|
42
|
-
tinybird_cli-6.5.
|
|
43
|
-
tinybird_cli-6.5.
|
|
39
|
+
tinybird_cli-6.5.7.dev0.dist-info/METADATA,sha256=yzq4mC9BBAK4Q_zIsrc0h4kpR-OZzBR8PffM31ydFvQ,84089
|
|
40
|
+
tinybird_cli-6.5.7.dev0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
41
|
+
tinybird_cli-6.5.7.dev0.dist-info/entry_points.txt,sha256=PKPKuPmA4IfJYnCFHHUiw-aAWZuBomFvwCklv1OyCjE,43
|
|
42
|
+
tinybird_cli-6.5.7.dev0.dist-info/top_level.txt,sha256=ZIQJTPCzMqnfDzM_hEGZrJqDSEcKnIK_49T86DGWpyQ,78
|
|
43
|
+
tinybird_cli-6.5.7.dev0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|