tinybird-cli 5.15.2.dev1__tar.gz → 5.16.1.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_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/PKG-INFO +10 -5
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/__cli__.py +2 -2
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/client.py +11 -1
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/feedback_manager.py +13 -2
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/sql_template.py +18 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/common.py +131 -7
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/config.py +3 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/workspace.py +49 -6
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird_cli.egg-info/PKG-INFO +10 -5
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird_cli.egg-info/requires.txt +2 -2
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/setup.cfg +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/ch_utils/constants.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/ch_utils/engine.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/check_pypi.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/config.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/connectors.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/context.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/datafile.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/datatypes.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/git_settings.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/sql.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/sql_template_fmt.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/sql_toolset.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/syncasync.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/cli.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/datasource.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/fmt.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/tag.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/token.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tornado_template.py +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird_cli.egg-info/SOURCES.txt +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird_cli.egg-info/dependency_links.txt +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird_cli.egg-info/entry_points.txt +0 -0
- {tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird_cli.egg-info/top_level.txt +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybird_cli
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.16.1.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli
|
|
6
6
|
Author: Tinybird
|
|
7
7
|
Author-email: support@tinybird.co
|
|
8
|
-
Requires-Python: >=3.9, <3.
|
|
8
|
+
Requires-Python: >=3.9, <3.14
|
|
9
9
|
Description-Content-Type: text/x-rst
|
|
10
10
|
Requires-Dist: aiofiles==24.1.0
|
|
11
|
-
Requires-Dist:
|
|
12
|
-
Requires-Dist:
|
|
11
|
+
Requires-Dist: clickhouse-toolset==0.34.dev0
|
|
12
|
+
Requires-Dist: click<8.2,>=8.1.8
|
|
13
13
|
Requires-Dist: colorama==0.4.6
|
|
14
14
|
Requires-Dist: cryptography~=41.0.0
|
|
15
15
|
Requires-Dist: croniter==1.3.15
|
|
@@ -61,11 +61,16 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
61
61
|
Changelog
|
|
62
62
|
----------
|
|
63
63
|
|
|
64
|
-
5.
|
|
64
|
+
5.16.0
|
|
65
65
|
***********
|
|
66
66
|
|
|
67
|
+
- `Added` support for organizations on `tb workspace create`
|
|
68
|
+
- `Added` support for Python 3.13
|
|
67
69
|
- `Updated` dependencies:
|
|
68
70
|
- croniter: from 1.3.8 to 1.3.15
|
|
71
|
+
- clickhouse-driver: from 0.2.7 to 0.2.9
|
|
72
|
+
- clickhouse-toolset: from 0.33.dev0 to 0.34.dev0
|
|
73
|
+
- click: from 8.1.7 to 8.1.8
|
|
69
74
|
|
|
70
75
|
5.15.1
|
|
71
76
|
***********
|
|
@@ -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__ = '5.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '5.16.1.dev0'
|
|
8
|
+
__revision__ = '24d8376'
|
|
@@ -667,6 +667,11 @@ class TinyB:
|
|
|
667
667
|
async def user_workspaces_and_branches(self):
|
|
668
668
|
return await self._req("/v0/user/workspaces/?with_environments=true")
|
|
669
669
|
|
|
670
|
+
async def user_workspaces_with_organization(self):
|
|
671
|
+
return await self._req(
|
|
672
|
+
"/v0/user/workspaces/?with_environments=false&with_organization=true&with_members_and_owner=false"
|
|
673
|
+
)
|
|
674
|
+
|
|
670
675
|
async def user_workspace_branches(self):
|
|
671
676
|
return await self._req("/v0/user/workspaces/?with_environments=true&only_environments=true")
|
|
672
677
|
|
|
@@ -676,10 +681,12 @@ class TinyB:
|
|
|
676
681
|
async def releases(self, workspace_id):
|
|
677
682
|
return await self._req(f"/v0/workspaces/{workspace_id}/releases")
|
|
678
683
|
|
|
679
|
-
async def create_workspace(self, name: str, template: Optional[str]):
|
|
684
|
+
async def create_workspace(self, name: str, template: Optional[str], organization_id: Optional[str]):
|
|
680
685
|
url = f"/v0/workspaces?name={name}"
|
|
681
686
|
if template:
|
|
682
687
|
url += f"&starter_kit={template}"
|
|
688
|
+
if organization_id:
|
|
689
|
+
url += f"&assign_to_organization_id={organization_id}"
|
|
683
690
|
return await self._req(url, method="POST", data=b"")
|
|
684
691
|
|
|
685
692
|
async def create_workspace_branch(
|
|
@@ -814,6 +821,9 @@ class TinyB:
|
|
|
814
821
|
async def workspace_info(self):
|
|
815
822
|
return await self._req("/v0/workspace")
|
|
816
823
|
|
|
824
|
+
async def organization(self, organization_id: str):
|
|
825
|
+
return await self._req(f"/v0/organizations/{organization_id}")
|
|
826
|
+
|
|
817
827
|
async def wait_for_job(
|
|
818
828
|
self,
|
|
819
829
|
job_id: str,
|
|
@@ -247,6 +247,14 @@ class FeedbackManager:
|
|
|
247
247
|
error_connection_invalid_ca_pem = error_message("Invalid CA certificate in PEM format")
|
|
248
248
|
error_connection_ca_pem_not_found = error_message("CA certificate in PEM format not found at {ca_pem}")
|
|
249
249
|
error_workspace = error_message("Workspace {workspace} not found. use 'tb workspace ls' to list your workspaces")
|
|
250
|
+
error_organization_not_found = error_message("Organization with id '{organization_id}' not found")
|
|
251
|
+
error_organization_index = error_message(
|
|
252
|
+
"Error selecting organization '{organization_index}'. Select a valid index or 0 to cancel"
|
|
253
|
+
)
|
|
254
|
+
warning_none_organization = warning_message(
|
|
255
|
+
"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."
|
|
256
|
+
)
|
|
257
|
+
error_while_fetching_orgs = error_message("Error while fetching organizations: {error}")
|
|
250
258
|
error_deleted_include = error_message(
|
|
251
259
|
"Related include file {include_file} was deleted and it's used in {filename}. Delete or remove dependency from {filename}."
|
|
252
260
|
)
|
|
@@ -458,7 +466,7 @@ class FeedbackManager:
|
|
|
458
466
|
info_releases_detected = info_message("** Live Release: {current_semver} => New Release: {semver}")
|
|
459
467
|
info_dry_releases_detected = info_message("** [DRY RUN] Live Release: {current_semver} => New Release: {semver}")
|
|
460
468
|
info_pre_prompt_auth_login_user_token = info_message(
|
|
461
|
-
"ℹ️ In order to log into Tinybird, you need your user token. Please, go to {host}/tokens/ and paste your User Token here."
|
|
469
|
+
"ℹ️ In order to log into Tinybird, you need your user token. Please, go to {host}/tokens/ and paste your User Token here." # noqa: RUF001
|
|
462
470
|
)
|
|
463
471
|
prompt_auth_login_user_token = prompt_message("❓ User token:")
|
|
464
472
|
|
|
@@ -609,7 +617,7 @@ Ready? """
|
|
|
609
617
|
)
|
|
610
618
|
warning_datasource_sync = warning_message("** Warning: Do you want to sync the Data Source {datasource}?")
|
|
611
619
|
warning_datasource_sync_bucket = warning_message(
|
|
612
|
-
"** Warning: Do you want to ingest all the selected files (new and previous) from the {datasource} bucket? Be aware that data might be duplicated if
|
|
620
|
+
"** Warning: Do you want to ingest all the selected files (new and previous) from the {datasource} bucket? Be aware that data might be duplicated if you have previously ingested those files.\n"
|
|
613
621
|
)
|
|
614
622
|
warning_users_dont_exist = warning_message(
|
|
615
623
|
"** Warning: The following users do not exist in the workspace {workspace}: {users}"
|
|
@@ -981,6 +989,9 @@ Ready? """
|
|
|
981
989
|
success_connection_using = success_message("** Using connection '{connection_name}'")
|
|
982
990
|
success_using_host = success_message("** Using host: {host} ({name})")
|
|
983
991
|
success_workspace_created = success_message("** Workspace '{workspace_name}' has been created")
|
|
992
|
+
success_workspace_created_with_organization = success_message(
|
|
993
|
+
"** Workspace '{workspace_name}' has been created in Organization '{organization_name}' (id: '{organization_id}')"
|
|
994
|
+
)
|
|
984
995
|
success_workspace_branch_created = success_message(
|
|
985
996
|
"** Branch '{branch_name}' from '{workspace_name}' has been created"
|
|
986
997
|
)
|
|
@@ -2256,6 +2256,16 @@ def render_sql_template(
|
|
|
2256
2256
|
Traceback (most recent call last):
|
|
2257
2257
|
...
|
|
2258
2258
|
tinybird.sql_template.SQLTemplateException: Template Syntax Error: Required parameter is not defined. Check the parameters test. Please provide a value or set a default value in the pipe code.
|
|
2259
|
+
>>> render_sql_template("SELECT * FROM test WHERE {% for item in JSON(filters, '[{}]') %} {{item.get('operand')}} {% end %}")
|
|
2260
|
+
Traceback (most recent call last):
|
|
2261
|
+
...
|
|
2262
|
+
tinybird.sql_template.SQLTemplateException: Template Syntax Error: expression "item.get('operand')" evaluated to null
|
|
2263
|
+
>>> render_sql_template("SELECT * FROM test WHERE {% for item in JSON(filters, '[{\\\"operand\\\":\\\"test\\\"}]') %} {{item.get('operand')}} {% end %}")
|
|
2264
|
+
("SELECT * FROM test WHERE 'test' ", {}, [])
|
|
2265
|
+
>>> render_sql_template("SELECT * FROM test WHERE {% for item in JSON(filters, '[\\\"test\\\"]') %} {{item.get('operator')}} {% end %}")
|
|
2266
|
+
Traceback (most recent call last):
|
|
2267
|
+
...
|
|
2268
|
+
tinybird.sql_template.SQLTemplateException: Template Syntax Error: 'str' object has no attribute 'get'. Make sure you're using an object/dictionary where trying to use .get()
|
|
2259
2269
|
"""
|
|
2260
2270
|
escape_split_to_array = ff_split_to_array_escape.get(False)
|
|
2261
2271
|
|
|
@@ -2323,6 +2333,14 @@ def render_sql_template(
|
|
|
2323
2333
|
except SQLTemplateException as e:
|
|
2324
2334
|
format_SQLTemplateException_message(e, vars_and_types=template_variables_with_types)
|
|
2325
2335
|
raise
|
|
2336
|
+
except AttributeError as e:
|
|
2337
|
+
# This happens when trying to use `get` on a string or when the object is None
|
|
2338
|
+
if "'str' object has no attribute 'get'" in str(e):
|
|
2339
|
+
raise SQLTemplateException(
|
|
2340
|
+
"'str' object has no attribute 'get'. Make sure you're using an object/dictionary where trying to use .get()",
|
|
2341
|
+
documentation="/cli/advanced-templates.html",
|
|
2342
|
+
)
|
|
2343
|
+
raise SQLTemplateException(str(e), documentation="/cli/advanced-templates.html")
|
|
2326
2344
|
except Exception as e:
|
|
2327
2345
|
# errors might vary here, we need to support as much as possible
|
|
2328
2346
|
# https://gitlab.com/tinybird/analytics/-/issues/943
|
|
@@ -18,7 +18,7 @@ from enum import Enum
|
|
|
18
18
|
from functools import wraps
|
|
19
19
|
from os import chmod, environ, getcwd, getenv
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Literal, Optional, Set, Tuple, TypedDict, Union
|
|
22
22
|
from urllib.parse import urlparse
|
|
23
23
|
|
|
24
24
|
import aiofiles
|
|
@@ -707,7 +707,13 @@ async def fork_workspace(client: TinyB, user_client: TinyB, created_workspace):
|
|
|
707
707
|
|
|
708
708
|
|
|
709
709
|
async def create_workspace_non_interactive(
|
|
710
|
-
ctx: Context,
|
|
710
|
+
ctx: Context,
|
|
711
|
+
workspace_name: str,
|
|
712
|
+
starterkit: Optional[str],
|
|
713
|
+
user_token: str,
|
|
714
|
+
fork: bool,
|
|
715
|
+
organization_id: Optional[str],
|
|
716
|
+
organization_name: Optional[str],
|
|
711
717
|
):
|
|
712
718
|
"""Creates a workspace using the provided name and starterkit"""
|
|
713
719
|
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
@@ -716,8 +722,15 @@ async def create_workspace_non_interactive(
|
|
|
716
722
|
user_client: TinyB = deepcopy(client)
|
|
717
723
|
user_client.token = user_token
|
|
718
724
|
|
|
719
|
-
created_workspace = await user_client.create_workspace(workspace_name, starterkit)
|
|
720
|
-
|
|
725
|
+
created_workspace = await user_client.create_workspace(workspace_name, starterkit, organization_id)
|
|
726
|
+
if organization_id and organization_name:
|
|
727
|
+
click.echo(
|
|
728
|
+
FeedbackManager.success_workspace_created_with_organization(
|
|
729
|
+
workspace_name=workspace_name, organization_name=organization_name, organization_id=organization_id
|
|
730
|
+
)
|
|
731
|
+
)
|
|
732
|
+
else:
|
|
733
|
+
click.echo(FeedbackManager.success_workspace_created(workspace_name=workspace_name))
|
|
721
734
|
|
|
722
735
|
if fork:
|
|
723
736
|
await fork_workspace(client, user_client, created_workspace)
|
|
@@ -727,7 +740,13 @@ async def create_workspace_non_interactive(
|
|
|
727
740
|
|
|
728
741
|
|
|
729
742
|
async def create_workspace_interactive(
|
|
730
|
-
ctx: Context,
|
|
743
|
+
ctx: Context,
|
|
744
|
+
workspace_name: Optional[str],
|
|
745
|
+
starterkit: Optional[str],
|
|
746
|
+
user_token: str,
|
|
747
|
+
fork: bool,
|
|
748
|
+
organization_id: Optional[str],
|
|
749
|
+
organization_name: Optional[str],
|
|
731
750
|
):
|
|
732
751
|
if not starterkit and not is_ci_environment():
|
|
733
752
|
click.echo("\n")
|
|
@@ -744,8 +763,17 @@ async def create_workspace_interactive(
|
|
|
744
763
|
click.echo(FeedbackManager.info_workspace_create_greeting())
|
|
745
764
|
default_name = f"new_workspace_{uuid.uuid4().hex[0:4]}"
|
|
746
765
|
workspace_name = click.prompt("\nWorkspace name", default=default_name, err=True, type=str)
|
|
747
|
-
|
|
748
|
-
|
|
766
|
+
assert isinstance(workspace_name, str)
|
|
767
|
+
|
|
768
|
+
await create_workspace_non_interactive(
|
|
769
|
+
ctx,
|
|
770
|
+
workspace_name,
|
|
771
|
+
starterkit,
|
|
772
|
+
user_token,
|
|
773
|
+
fork,
|
|
774
|
+
organization_id=organization_id,
|
|
775
|
+
organization_name=organization_name,
|
|
776
|
+
)
|
|
749
777
|
|
|
750
778
|
|
|
751
779
|
async def create_workspace_branch(
|
|
@@ -2080,3 +2108,99 @@ def get_ca_pem_content(ca_pem: Optional[str], filename: Optional[str] = None) ->
|
|
|
2080
2108
|
raise CLIConnectionException(FeedbackManager.error_connection_invalid_ca_pem())
|
|
2081
2109
|
|
|
2082
2110
|
return ca_pem_content
|
|
2111
|
+
|
|
2112
|
+
|
|
2113
|
+
async def get_organizations_by_user(ctx: Context, user_token: str) -> List[Dict[str, Any]]:
|
|
2114
|
+
"""Fetches all organizations by user using the provided user token"""
|
|
2115
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
2116
|
+
organizations = []
|
|
2117
|
+
|
|
2118
|
+
try:
|
|
2119
|
+
user_client: TinyB = deepcopy(client)
|
|
2120
|
+
user_client.token = user_token
|
|
2121
|
+
user_workspaces = await user_client.user_workspaces_with_organization()
|
|
2122
|
+
admin_org_id = user_workspaces.get("organization_id")
|
|
2123
|
+
seen_org_ids = set()
|
|
2124
|
+
|
|
2125
|
+
for workspace in user_workspaces.get("workspaces"):
|
|
2126
|
+
org = workspace.get("organization")
|
|
2127
|
+
if org and org.get("id") not in seen_org_ids:
|
|
2128
|
+
org["is_admin"] = org.get("id") == admin_org_id
|
|
2129
|
+
organizations.append(org)
|
|
2130
|
+
seen_org_ids.add(org.get("id"))
|
|
2131
|
+
|
|
2132
|
+
# Case: user is admin of an organization but not a member of any workspace in it
|
|
2133
|
+
if admin_org_id and admin_org_id not in seen_org_ids:
|
|
2134
|
+
org = await user_client.organization(admin_org_id)
|
|
2135
|
+
org["id"] = admin_org_id
|
|
2136
|
+
org["is_admin"] = True
|
|
2137
|
+
organizations.append(org)
|
|
2138
|
+
|
|
2139
|
+
except Exception as e:
|
|
2140
|
+
raise CLIWorkspaceException(FeedbackManager.error_while_fetching_orgs(error=str(e)))
|
|
2141
|
+
return organizations
|
|
2142
|
+
|
|
2143
|
+
|
|
2144
|
+
OrgType = Literal["tinybird", "domain", "admin", "member"]
|
|
2145
|
+
|
|
2146
|
+
|
|
2147
|
+
class Organization(TypedDict):
|
|
2148
|
+
id: str
|
|
2149
|
+
name: str
|
|
2150
|
+
role: str
|
|
2151
|
+
domains: Optional[List[str]]
|
|
2152
|
+
type: OrgType
|
|
2153
|
+
|
|
2154
|
+
|
|
2155
|
+
def sort_organizations_by_user(organizations: List[Dict[str, Any]], user_email: Optional[str]) -> List[Organization]:
|
|
2156
|
+
"""Sort organizations based on type: tinybird > domain > admin > member"""
|
|
2157
|
+
sorted_organizations: List[Organization] = []
|
|
2158
|
+
user_domain = user_email.split("@")[1] if user_email else None
|
|
2159
|
+
is_tinybird_user = user_domain == "tinybird.co"
|
|
2160
|
+
|
|
2161
|
+
for org in organizations:
|
|
2162
|
+
domain = org.get("domain") or ""
|
|
2163
|
+
domains = domain.split(",") if domain else None
|
|
2164
|
+
role: OrgType = "admin" if org.get("is_admin") else "member"
|
|
2165
|
+
type = role
|
|
2166
|
+
if domains and user_domain and user_domain in domains:
|
|
2167
|
+
type = "domain"
|
|
2168
|
+
if org.get("name") == "Tinybird" and is_tinybird_user:
|
|
2169
|
+
type = "tinybird"
|
|
2170
|
+
|
|
2171
|
+
sorted_organizations.append(
|
|
2172
|
+
{
|
|
2173
|
+
"id": org.get("id") or "",
|
|
2174
|
+
"name": org.get("name") or "",
|
|
2175
|
+
"role": role,
|
|
2176
|
+
"domains": [domain.strip() for domain in domains] if domains else None,
|
|
2177
|
+
"type": type,
|
|
2178
|
+
}
|
|
2179
|
+
)
|
|
2180
|
+
|
|
2181
|
+
type_priority: Dict[OrgType, int] = {"tinybird": 0, "domain": 1, "admin": 2, "member": 3}
|
|
2182
|
+
|
|
2183
|
+
sorted_organizations.sort(key=lambda x: type_priority[x["type"]])
|
|
2184
|
+
|
|
2185
|
+
return sorted_organizations
|
|
2186
|
+
|
|
2187
|
+
|
|
2188
|
+
async def ask_for_organization_interactively(organizations: List[Organization]) -> Optional[Organization]:
|
|
2189
|
+
rows = [(index + 1, org["name"], org["role"], org["id"]) for index, org in enumerate(organizations)]
|
|
2190
|
+
|
|
2191
|
+
echo_safe_humanfriendly_tables_format_smart_table(rows, column_names=["Idx", "Name", "Role", "Id"])
|
|
2192
|
+
click.echo("")
|
|
2193
|
+
click.echo(" [0] to cancel")
|
|
2194
|
+
|
|
2195
|
+
org_index = -1
|
|
2196
|
+
while org_index == -1:
|
|
2197
|
+
org_index = click.prompt("\nSelect an organization to include the workspace in", default=1)
|
|
2198
|
+
if org_index < 0 or org_index > len(organizations):
|
|
2199
|
+
click.echo(FeedbackManager.error_organization_index(organization_index=org_index))
|
|
2200
|
+
org_index = -1
|
|
2201
|
+
|
|
2202
|
+
if org_index == 0:
|
|
2203
|
+
click.echo(FeedbackManager.info_cancelled_by_user())
|
|
2204
|
+
return None
|
|
2205
|
+
|
|
2206
|
+
return organizations[org_index - 1]
|
|
@@ -259,6 +259,9 @@ class CLIConfig:
|
|
|
259
259
|
def get_user_client(self, host: Optional[str] = None) -> tbc.TinyB:
|
|
260
260
|
return self.get_client(self.get_user_token(), host)
|
|
261
261
|
|
|
262
|
+
def get_user_email(self) -> Optional[str]:
|
|
263
|
+
return self["user_email"]
|
|
264
|
+
|
|
262
265
|
def set_workspace_token(self, workspace_id: str, token: str) -> None:
|
|
263
266
|
pass
|
|
264
267
|
|
|
@@ -15,6 +15,7 @@ from tinybird.feedback_manager import FeedbackManager
|
|
|
15
15
|
from tinybird.tb_cli_modules.cli import cli
|
|
16
16
|
from tinybird.tb_cli_modules.common import (
|
|
17
17
|
_get_workspace_plan_name,
|
|
18
|
+
ask_for_organization_interactively,
|
|
18
19
|
ask_for_user_token,
|
|
19
20
|
check_user_token,
|
|
20
21
|
coro,
|
|
@@ -22,8 +23,10 @@ from tinybird.tb_cli_modules.common import (
|
|
|
22
23
|
create_workspace_non_interactive,
|
|
23
24
|
echo_safe_humanfriendly_tables_format_smart_table,
|
|
24
25
|
get_current_main_workspace,
|
|
26
|
+
get_organizations_by_user,
|
|
25
27
|
is_valid_starterkit,
|
|
26
28
|
print_current_workspace,
|
|
29
|
+
sort_organizations_by_user,
|
|
27
30
|
switch_workspace,
|
|
28
31
|
)
|
|
29
32
|
from tinybird.tb_cli_modules.config import CLIConfig
|
|
@@ -193,29 +196,69 @@ async def clear_workspace(ctx: Context, yes: bool, dry_run: bool) -> None:
|
|
|
193
196
|
default=False,
|
|
194
197
|
help="When enabled, we will share all datasource from the current workspace to the new created one",
|
|
195
198
|
)
|
|
199
|
+
@click.option(
|
|
200
|
+
"--organization-id",
|
|
201
|
+
"organization_id",
|
|
202
|
+
type=str,
|
|
203
|
+
required=False,
|
|
204
|
+
help="When passed, the workspace will be created in the specified organization",
|
|
205
|
+
)
|
|
196
206
|
@click.pass_context
|
|
197
207
|
@coro
|
|
198
208
|
async def create_workspace(
|
|
199
|
-
ctx: Context,
|
|
209
|
+
ctx: Context,
|
|
210
|
+
workspace_name: str,
|
|
211
|
+
starter_kit: str,
|
|
212
|
+
user_token: Optional[str],
|
|
213
|
+
fork: bool,
|
|
214
|
+
organization_id: Optional[str],
|
|
200
215
|
) -> None:
|
|
201
216
|
if starter_kit and not await is_valid_starterkit(ctx, starter_kit):
|
|
202
217
|
raise CLIWorkspaceException(FeedbackManager.error_starterkit_name(starterkit_name=starter_kit))
|
|
203
218
|
|
|
219
|
+
config = CLIConfig.get_project_config()
|
|
220
|
+
host = config.get_host() or CLIConfig.DEFAULTS["host"]
|
|
221
|
+
ui_host = get_display_host(host)
|
|
222
|
+
|
|
204
223
|
if not user_token:
|
|
205
|
-
config = CLIConfig.get_project_config()
|
|
206
|
-
host = config.get_host() or CLIConfig.DEFAULTS["host"]
|
|
207
|
-
ui_host = get_display_host(host)
|
|
208
224
|
user_token = ask_for_user_token("create a new workspace", ui_host)
|
|
209
225
|
if not user_token:
|
|
210
226
|
return
|
|
211
227
|
await check_user_token(ctx, user_token)
|
|
212
228
|
|
|
229
|
+
organization_name = None
|
|
230
|
+
organizations = await get_organizations_by_user(ctx, user_token)
|
|
231
|
+
|
|
232
|
+
if organization_id:
|
|
233
|
+
organization = next((org for org in organizations if org.get("id") == organization_id), None)
|
|
234
|
+
if not organization:
|
|
235
|
+
raise CLIWorkspaceException(FeedbackManager.error_organization_not_found(organization_id=organization_id))
|
|
236
|
+
organization_name = organization.get("name")
|
|
237
|
+
else:
|
|
238
|
+
if len(organizations) == 0:
|
|
239
|
+
click.echo(FeedbackManager.warning_none_organization(ui_host=ui_host))
|
|
240
|
+
elif len(organizations) == 1:
|
|
241
|
+
organization_id = organizations[0].get("id")
|
|
242
|
+
organization_name = organizations[0].get("name")
|
|
243
|
+
else:
|
|
244
|
+
sorted_organizations = sort_organizations_by_user(organizations, user_email=config.get_user_email())
|
|
245
|
+
current_organization = await ask_for_organization_interactively(sorted_organizations)
|
|
246
|
+
if current_organization:
|
|
247
|
+
organization_id = current_organization.get("id")
|
|
248
|
+
organization_name = current_organization.get("name")
|
|
249
|
+
else:
|
|
250
|
+
return
|
|
251
|
+
|
|
213
252
|
# If we have at least workspace_name, we start the non interactive
|
|
214
253
|
# process, creating an empty workspace
|
|
215
254
|
if workspace_name:
|
|
216
|
-
await create_workspace_non_interactive(
|
|
255
|
+
await create_workspace_non_interactive(
|
|
256
|
+
ctx, workspace_name, starter_kit, user_token, fork, organization_id, organization_name
|
|
257
|
+
)
|
|
217
258
|
else:
|
|
218
|
-
await create_workspace_interactive(
|
|
259
|
+
await create_workspace_interactive(
|
|
260
|
+
ctx, workspace_name, starter_kit, user_token, fork, organization_id, organization_name
|
|
261
|
+
)
|
|
219
262
|
|
|
220
263
|
|
|
221
264
|
@workspace.command(name="delete", short_help="Delete a Workspace for your Tinybird user")
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tinybird_cli
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.16.1.dev0
|
|
4
4
|
Summary: Tinybird Command Line Tool
|
|
5
5
|
Home-page: https://www.tinybird.co/docs/cli
|
|
6
6
|
Author: Tinybird
|
|
7
7
|
Author-email: support@tinybird.co
|
|
8
|
-
Requires-Python: >=3.9, <3.
|
|
8
|
+
Requires-Python: >=3.9, <3.14
|
|
9
9
|
Description-Content-Type: text/x-rst
|
|
10
10
|
Requires-Dist: aiofiles==24.1.0
|
|
11
|
-
Requires-Dist:
|
|
12
|
-
Requires-Dist:
|
|
11
|
+
Requires-Dist: clickhouse-toolset==0.34.dev0
|
|
12
|
+
Requires-Dist: click<8.2,>=8.1.8
|
|
13
13
|
Requires-Dist: colorama==0.4.6
|
|
14
14
|
Requires-Dist: cryptography~=41.0.0
|
|
15
15
|
Requires-Dist: croniter==1.3.15
|
|
@@ -61,11 +61,16 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
|
|
|
61
61
|
Changelog
|
|
62
62
|
----------
|
|
63
63
|
|
|
64
|
-
5.
|
|
64
|
+
5.16.0
|
|
65
65
|
***********
|
|
66
66
|
|
|
67
|
+
- `Added` support for organizations on `tb workspace create`
|
|
68
|
+
- `Added` support for Python 3.13
|
|
67
69
|
- `Updated` dependencies:
|
|
68
70
|
- croniter: from 1.3.8 to 1.3.15
|
|
71
|
+
- clickhouse-driver: from 0.2.7 to 0.2.9
|
|
72
|
+
- clickhouse-toolset: from 0.33.dev0 to 0.34.dev0
|
|
73
|
+
- click: from 8.1.7 to 8.1.8
|
|
69
74
|
|
|
70
75
|
5.15.1
|
|
71
76
|
***********
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/tinyunit/tinyunit.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird/tb_cli_modules/workspace_members.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{tinybird_cli-5.15.2.dev1 → tinybird_cli-5.16.1.dev0}/tinybird_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|