tinybird 0.0.1.dev87__py3-none-any.whl → 0.0.1.dev89__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.

Potentially problematic release.


This version of tinybird might be problematic. Click here for more details.

@@ -761,6 +761,16 @@ def engine_local_to_replicated(engine: str, database: str, name: str) -> str:
761
761
  return re.sub(r"(.*)MergeTree(\(([^\)]*)\))*(.*)", _replace, engine.strip())
762
762
 
763
763
 
764
+ def ttl_from_engine(engine: str) -> Optional[str]:
765
+ ttl_array = engine.split(" TTL ")
766
+ if len(ttl_array) <= 1:
767
+ return None
768
+ settings_array = engine.split(" SETTINGS ")
769
+ settings = " SETTINGS " + settings_array[1] if len(settings_array) > 1 else None
770
+ ttl = ttl_array[1][: -(len(settings))] if settings else ttl_array[1]
771
+ return ttl
772
+
773
+
764
774
  def ttl_condition_from_engine_full(engine_full: Optional[str]) -> Optional[str]:
765
775
  """
766
776
  >>> ttl_condition_from_engine_full(None)
@@ -808,13 +818,9 @@ def ttl_condition_from_engine_full(engine_full: Optional[str]) -> Optional[str]:
808
818
  return None
809
819
 
810
820
  try:
811
- ttl_array = engine_full.split(" TTL ")
812
- if len(ttl_array) <= 1:
821
+ ttl = ttl_from_engine(engine_full)
822
+ if not ttl:
813
823
  return None
814
- settings_array = engine_full.split(" SETTINGS ")
815
- settings = " SETTINGS " + settings_array[1] if len(settings_array) > 1 else None
816
- ttl = ttl_array[1][: -(len(settings))] if settings else ttl_array[1]
817
-
818
824
  groups = SIMPLE_TTL_DEFINITION.search(ttl)
819
825
  if not groups:
820
826
  return None
tinybird/client.py CHANGED
@@ -871,6 +871,22 @@ class TinyB:
871
871
  async def organization(self, organization_id: str):
872
872
  return await self._req(f"/v0/organizations/{organization_id}")
873
873
 
874
+ async def create_organization(
875
+ self,
876
+ name: str,
877
+ ):
878
+ url = f"/v0/organizations?name={name}"
879
+ return await self._req(url, method="POST", data=b"")
880
+
881
+ async def add_workspaces_to_organization(self, organization_id: str, workspace_ids: List[str]):
882
+ if not workspace_ids:
883
+ return
884
+ return await self._req(
885
+ f"/v0/organizations/{organization_id}/workspaces",
886
+ method="PUT",
887
+ data=json.dumps({"workspace_ids": ",".join(workspace_ids)}),
888
+ )
889
+
874
890
  async def wait_for_job(
875
891
  self,
876
892
  job_id: str,
tinybird/config.py CHANGED
@@ -30,6 +30,7 @@ LEGACY_HOSTS = {
30
30
  "https://api.us-west-2.aws.tinybird.co": "https://app.tinybird.co/aws/us-west-2",
31
31
  "https://api.eu-central-1.aws.tinybird.co": "https://app.tinybird.co/aws/eu-central-1",
32
32
  "https://api.eu-west-1.aws.tinybird.co": "https://app.tinybird.co/aws/eu-west-1",
33
+ "https://api.europe-west2.gcp.tinybird.co": "https://cloud.tinybird.co/gcp/europe-west2",
33
34
  "https://api.ap-east.aws.tinybird.co": "https://app.tinybird.co/aws/ap-east",
34
35
  "https://api.wadus1.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus1",
35
36
  "https://api.wadus2.gcp.tinybird.co": "https://app.wadus.tinybird.co/gcp/wadus2",
@@ -48,6 +49,7 @@ LEGACY_HOSTS = {
48
49
  "https://ui.us-east.aws.tinybird.co": "https://app.tinybird.co/aws/us-east-1",
49
50
  "https://ui.us-west-2.aws.tinybird.co": "https://app.tinybird.co/aws/us-west-2",
50
51
  "https://ui.eu-central-1.aws.tinybird.co": "https://app.tinybird.co/aws/eu-central-1",
52
+ "https://ui.europe-west2.gcp.tinybird.co": "https://cloud.tinybird.co/gcp/europe-west2",
51
53
  "https://ui.ap-east.aws.tinybird.co": "https://app.tinybird.co/aws/ap-east",
52
54
  "https://ui.split.tinybird.co": "https://app.tinybird.co/aws/split-us-east",
53
55
  "https://ui.split.us-west-2.aws.tinybird.co": "https://app.tinybird.co/aws/split-us-west-2",
tinybird/context.py CHANGED
@@ -7,7 +7,7 @@ if TYPE_CHECKING:
7
7
 
8
8
  workspace_id: ContextVar[str] = ContextVar("workspace_id")
9
9
  workspace: ContextVar["User"] = ContextVar("workspace")
10
- datasource_id: ContextVar[str] = ContextVar("datasource_id")
10
+ table_id: ContextVar[str] = ContextVar("table_id")
11
11
  hfi_frequency: ContextVar[float] = ContextVar("hfi_frequency")
12
12
  hfi_frequency_gatherer: ContextVar[float] = ContextVar("hfi_frequency_gatherer")
13
13
  use_gatherer: ContextVar[bool] = ContextVar("use_gatherer")
tinybird/tb/__cli__.py CHANGED
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '0.0.1.dev87'
8
- __revision__ = '029c262'
7
+ __version__ = '0.0.1.dev89'
8
+ __revision__ = '6153af6'
@@ -19,7 +19,7 @@ from enum import Enum
19
19
  from functools import wraps
20
20
  from os import environ, getcwd, getenv
21
21
  from pathlib import Path
22
- from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union
22
+ from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Literal, Optional, Set, Tuple, TypedDict, Union
23
23
  from urllib.parse import urlparse
24
24
 
25
25
  import aiofiles
@@ -683,7 +683,13 @@ async def fork_workspace(client: TinyB, user_client: TinyB, created_workspace):
683
683
 
684
684
 
685
685
  async def create_workspace_non_interactive(
686
- ctx: Context, workspace_name: str, starterkit: str, user_token: str, fork: bool
686
+ ctx: Context,
687
+ workspace_name: str,
688
+ starterkit: Optional[str],
689
+ user_token: str,
690
+ fork: bool,
691
+ organization_id: Optional[str],
692
+ organization_name: Optional[str],
687
693
  ):
688
694
  """Creates a workspace using the provided name and starterkit"""
689
695
  client: TinyB = ctx.ensure_object(dict)["client"]
@@ -692,8 +698,15 @@ async def create_workspace_non_interactive(
692
698
  user_client: TinyB = deepcopy(client)
693
699
  user_client.token = user_token
694
700
 
695
- created_workspace = await user_client.create_workspace(workspace_name, starterkit)
696
- click.echo(FeedbackManager.success_workspace_created(workspace_name=workspace_name))
701
+ created_workspace = await user_client.create_workspace(workspace_name, starterkit, organization_id)
702
+ if organization_id and organization_name:
703
+ click.echo(
704
+ FeedbackManager.success_workspace_created_with_organization(
705
+ workspace_name=workspace_name, organization_name=organization_name, organization_id=organization_id
706
+ )
707
+ )
708
+ else:
709
+ click.echo(FeedbackManager.success_workspace_created(workspace_name=workspace_name))
697
710
 
698
711
  if fork:
699
712
  await fork_workspace(client, user_client, created_workspace)
@@ -703,7 +716,13 @@ async def create_workspace_non_interactive(
703
716
 
704
717
 
705
718
  async def create_workspace_interactive(
706
- ctx: Context, workspace_name: Optional[str], starterkit: Optional[str], user_token: str, fork: bool
719
+ ctx: Context,
720
+ workspace_name: Optional[str],
721
+ starterkit: Optional[str],
722
+ user_token: str,
723
+ fork: bool,
724
+ organization_id: Optional[str],
725
+ organization_name: Optional[str],
707
726
  ):
708
727
  if not starterkit and not is_ci_environment():
709
728
  click.echo("\n")
@@ -721,7 +740,18 @@ async def create_workspace_interactive(
721
740
  default_name = f"new_workspace_{uuid.uuid4().hex[0:4]}"
722
741
  workspace_name = click.prompt("\nWorkspace name", default=default_name, err=True, type=str)
723
742
 
724
- await create_workspace_non_interactive(ctx, workspace_name, starterkit, user_token, fork) # type: ignore
743
+ if not workspace_name:
744
+ raise CLIException(FeedbackManager.error_workspace_name_required())
745
+
746
+ await create_workspace_non_interactive(
747
+ ctx,
748
+ workspace_name,
749
+ starterkit,
750
+ user_token,
751
+ fork,
752
+ organization_id,
753
+ organization_name,
754
+ )
725
755
 
726
756
 
727
757
  async def print_data_branch_summary(client, job_id, response=None):
@@ -1948,3 +1978,163 @@ async def send_batch_events(
1948
1978
  elapsed_time = time_end - time_start
1949
1979
  cols = len(data[0].keys()) if len(data) > 0 else 0
1950
1980
  click.echo(FeedbackManager.highlight(message=f"» {rows} rows x {cols} cols in {elapsed_time:.1f}s"))
1981
+
1982
+
1983
+ async def get_organizations_by_user(config: CLIConfig, user_token: str) -> List[Dict[str, Any]]:
1984
+ """Fetches all organizations by user using the provided user token"""
1985
+ organizations = []
1986
+
1987
+ try:
1988
+ user_client = config.get_client(token=user_token)
1989
+ user_workspaces = await user_client.user_workspaces_with_organization()
1990
+ admin_org_id = user_workspaces.get("organization_id")
1991
+ seen_org_ids = set()
1992
+
1993
+ for workspace in user_workspaces.get("workspaces"):
1994
+ org = workspace.get("organization")
1995
+ if org and org.get("id") not in seen_org_ids:
1996
+ org["is_admin"] = org.get("id") == admin_org_id
1997
+ organizations.append(org)
1998
+ seen_org_ids.add(org.get("id"))
1999
+
2000
+ # Case: user is admin of an organization but not a member of any workspace in it
2001
+ if admin_org_id and admin_org_id not in seen_org_ids:
2002
+ org = await user_client.organization(admin_org_id)
2003
+ org["id"] = admin_org_id
2004
+ org["is_admin"] = True
2005
+ organizations.append(org)
2006
+
2007
+ except Exception as e:
2008
+ raise CLIWorkspaceException(FeedbackManager.error_while_fetching_orgs(error=str(e)))
2009
+ return organizations
2010
+
2011
+
2012
+ OrgType = Literal["tinybird", "domain", "admin", "member"]
2013
+
2014
+
2015
+ class Organization(TypedDict):
2016
+ id: str
2017
+ name: str
2018
+ role: str
2019
+ domains: Optional[List[str]]
2020
+ type: OrgType
2021
+
2022
+
2023
+ def sort_organizations_by_user(organizations: List[Dict[str, Any]], user_email: Optional[str]) -> List[Organization]:
2024
+ """Sort organizations based on type: tinybird > domain > admin > member"""
2025
+ sorted_organizations: List[Organization] = []
2026
+ user_domain = user_email.split("@")[1] if user_email else None
2027
+ is_tinybird_user = user_domain == "tinybird.co"
2028
+
2029
+ for org in organizations:
2030
+ domain = org.get("domain") or ""
2031
+ domains = domain.split(",") if domain else None
2032
+ role: OrgType = "admin" if org.get("is_admin") else "member"
2033
+ type = role
2034
+ if domains and user_domain and user_domain in domains:
2035
+ type = "domain"
2036
+ if org.get("name") == "Tinybird" and is_tinybird_user:
2037
+ type = "tinybird"
2038
+
2039
+ sorted_organizations.append(
2040
+ {
2041
+ "id": org.get("id") or "",
2042
+ "name": org.get("name") or "",
2043
+ "role": role,
2044
+ "domains": [domain.strip() for domain in domains] if domains else None,
2045
+ "type": type,
2046
+ }
2047
+ )
2048
+
2049
+ type_priority: Dict[OrgType, int] = {"tinybird": 0, "domain": 1, "admin": 2, "member": 3}
2050
+
2051
+ sorted_organizations.sort(key=lambda x: type_priority[x["type"]])
2052
+
2053
+ return sorted_organizations
2054
+
2055
+
2056
+ async def ask_for_organization_interactively(organizations: List[Organization]) -> Optional[Organization]:
2057
+ rows = [(index + 1, org["name"], org["role"], org["id"]) for index, org in enumerate(organizations)]
2058
+
2059
+ echo_safe_humanfriendly_tables_format_smart_table(rows, column_names=["Idx", "Name", "Role", "Id"])
2060
+ click.echo("")
2061
+ click.echo(" [0] to cancel")
2062
+
2063
+ org_index = -1
2064
+ while org_index == -1:
2065
+ org_index = click.prompt("\nSelect an organization to include the workspace in", default=1)
2066
+ if org_index < 0 or org_index > len(organizations):
2067
+ click.echo(FeedbackManager.error_organization_index(organization_index=org_index))
2068
+ org_index = -1
2069
+
2070
+ if org_index == 0:
2071
+ click.echo(FeedbackManager.info_cancelled_by_user())
2072
+ return None
2073
+
2074
+ return organizations[org_index - 1]
2075
+
2076
+
2077
+ async def ask_for_organization_name(config: CLIConfig) -> str:
2078
+ user_email = config.get_user_email()
2079
+ default_organization_name = (
2080
+ user_email.split("@")[1].split(".")[0] if user_email else None
2081
+ ) # Example: "jane.doe@tinybird.com" -> "tinybird"
2082
+ # check if domain is a common domain
2083
+ if default_organization_name in ["gmail", "yahoo", "hotmail", "outlook"]:
2084
+ default_organization_name = (
2085
+ user_email.split("@")[0] if user_email else None
2086
+ ) # Example: "jane.doe@gmail.com" -> "jane.doe"
2087
+ return click.prompt(
2088
+ "\nYou need to create an organization to continue. We will include your existing workspaces in it.\nThis operation will only happen once and then your new workspaces will be automatically included in your organization.\n\nEnter organization name",
2089
+ hide_input=False,
2090
+ show_default=True,
2091
+ default=default_organization_name,
2092
+ )
2093
+
2094
+
2095
+ async def create_organization_and_add_workspaces(
2096
+ config: CLIConfig, organization_name: str, user_token: str
2097
+ ) -> Dict[str, Any]:
2098
+ client: TinyB = config.get_client(token=user_token)
2099
+ try:
2100
+ organization = await client.create_organization(organization_name)
2101
+ click.echo(FeedbackManager.success_organization_created(organization_name=organization_name))
2102
+ except Exception as e:
2103
+ raise CLIWorkspaceException(FeedbackManager.error_organization_creation(error=str(e)))
2104
+
2105
+ # Add existing orphan workspaces to the organization - this is only needed for backwards compatibility
2106
+ user_workspaces = await client.user_workspaces_with_organization()
2107
+ workspaces_to_migrate = []
2108
+ for workspace in user_workspaces["workspaces"]:
2109
+ if workspace.get("organization") is None and workspace.get("role") == "admin":
2110
+ workspaces_to_migrate.append(workspace["id"])
2111
+ await client.add_workspaces_to_organization(organization["id"], workspaces_to_migrate)
2112
+
2113
+ return organization
2114
+
2115
+
2116
+ async def get_user_token(config: CLIConfig, user_token: Optional[str] = None) -> str:
2117
+ client = config.get_client()
2118
+ host = config.get_host() or CLIConfig.DEFAULTS["host"]
2119
+ ui_host = get_display_host(host)
2120
+
2121
+ if not user_token:
2122
+ user_token = config.get_user_token()
2123
+ if user_token:
2124
+ try:
2125
+ await check_user_token_with_client(client, user_token)
2126
+ except Exception:
2127
+ user_token = None
2128
+ pass
2129
+ if not user_token:
2130
+ user_token = ask_for_user_token("delete a workspace", ui_host)
2131
+ if not user_token:
2132
+ raise CLIWorkspaceException(
2133
+ FeedbackManager.error_exception(
2134
+ error='Invalid user authentication. Make sure you are using the "user token" instead of the "admin your@email" token.'
2135
+ )
2136
+ )
2137
+
2138
+ await check_user_token_with_client(client, user_token)
2139
+
2140
+ return user_token
@@ -247,6 +247,9 @@ class CLIConfig:
247
247
  def get_user_client(self, host: Optional[str] = None) -> tbc.TinyB:
248
248
  return self.get_client(self.get_user_token(), host)
249
249
 
250
+ def get_user_email(self) -> Optional[str]:
251
+ return self["user_email"]
252
+
250
253
  def set_workspace_token(self, workspace_id: str, token: str) -> None:
251
254
  pass
252
255
 
@@ -191,17 +191,19 @@ def deployment_group() -> None:
191
191
  help="Validate the deployment before creating it. Disabled by default.",
192
192
  )
193
193
  @click.option(
194
- "--allow-remove-datasources/--no-allow-remove-datasources",
194
+ "--allow-destructive-operations/--no-allow-destructive-operations",
195
195
  is_flag=True,
196
196
  default=False,
197
197
  help="Allow removing datasources. Disabled by default.",
198
198
  )
199
199
  @click.pass_context
200
- def deployment_create(ctx: click.Context, wait: bool, auto: bool, check: bool, allow_remove_datasources: bool) -> None:
200
+ def deployment_create(
201
+ ctx: click.Context, wait: bool, auto: bool, check: bool, allow_destructive_operations: bool
202
+ ) -> None:
201
203
  """
202
204
  Validate and deploy the project server side.
203
205
  """
204
- create_deployment(ctx, wait, auto, check, allow_remove_datasources)
206
+ create_deployment(ctx, wait, auto, check, allow_destructive_operations)
205
207
 
206
208
 
207
209
  @deployment_group.command(name="ls")
@@ -298,17 +300,17 @@ def deployment_rollback(ctx: click.Context, wait: bool) -> None:
298
300
  help="Validate the deployment before creating it. Disabled by default.",
299
301
  )
300
302
  @click.option(
301
- "--allow-remove-datasources/--no-allow-remove-datasources",
303
+ "--allow-destructive-operations/--no-allow-destructive-operations",
302
304
  is_flag=True,
303
305
  default=False,
304
306
  help="Allow removing datasources. Disabled by default.",
305
307
  )
306
308
  @click.pass_context
307
- def deploy(ctx: click.Context, wait: bool, auto: bool, check: bool, allow_remove_datasources: bool) -> None:
309
+ def deploy(ctx: click.Context, wait: bool, auto: bool, check: bool, allow_destructive_operations: bool) -> None:
308
310
  """
309
311
  Deploy the project.
310
312
  """
311
- create_deployment(ctx, wait, auto, check, allow_remove_datasources)
313
+ create_deployment(ctx, wait, auto, check, allow_destructive_operations)
312
314
 
313
315
 
314
316
  def create_deployment(
@@ -316,7 +318,7 @@ def create_deployment(
316
318
  wait: bool,
317
319
  auto: bool,
318
320
  check: Optional[bool] = None,
319
- allow_remove_datasources: Optional[bool] = None,
321
+ allow_destructive_operations: Optional[bool] = None,
320
322
  ) -> None:
321
323
  # TODO: This code is duplicated in build_server.py
322
324
  # Should be refactored to be shared
@@ -350,8 +352,8 @@ def create_deployment(
350
352
  if check:
351
353
  click.echo(FeedbackManager.highlight(message="\n» Validating deployment...\n"))
352
354
  params["check"] = "true"
353
- if allow_remove_datasources:
354
- params["allow_remove_datasources"] = "true"
355
+ if allow_destructive_operations:
356
+ params["allow_destructive_operations"] = "true"
355
357
  r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS, params=params)
356
358
  result = r.json()
357
359
  logging.debug(json.dumps(result, indent=2))
@@ -257,6 +257,16 @@ class FeedbackManager:
257
257
  error_connection_invalid_ca_pem = error_message("Invalid CA certificate in PEM format")
258
258
  error_connection_ca_pem_not_found = error_message("CA certificate in PEM format not found at {ca_pem}")
259
259
  error_workspace = error_message("Workspace {workspace} not found. use 'tb workspace ls' to list your workspaces")
260
+ error_workspace_name_required = error_message("Workspace name is required")
261
+ error_organization_not_found = error_message("Organization with id '{organization_id}' not found")
262
+ error_organization_index = error_message(
263
+ "Error selecting organization '{organization_index}'. Select a valid index or 0 to cancel"
264
+ )
265
+ error_organization_creation = error_message("Error creating organization: {error}")
266
+ warning_none_organization = warning_message(
267
+ "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."
268
+ )
269
+ error_while_fetching_orgs = error_message("Error while fetching organizations: {error}")
260
270
  error_deleted_include = error_message(
261
271
  "Related include file {include_file} was deleted and it's used in {filename}. Delete or remove dependency from {filename}."
262
272
  )
@@ -991,6 +1001,10 @@ Ready? """
991
1001
  success_connection_using = success_message("** Using connection '{connection_name}'")
992
1002
  success_using_host = success_message("** Using host: {host} ({name})")
993
1003
  success_workspace_created = success_message("** Workspace '{workspace_name}' has been created")
1004
+ success_organization_created = success_message("** Organization '{organization_name}' has been created")
1005
+ success_workspace_created_with_organization = success_message(
1006
+ "** Workspace '{workspace_name}' has been created in Organization '{organization_name}' (id: '{organization_id}')"
1007
+ )
994
1008
  success_workspace_branch_created = success_message(
995
1009
  "** Branch '{branch_name}' from '{workspace_name}' has been created"
996
1010
  )
@@ -9,19 +9,22 @@ import click
9
9
  from click import Context
10
10
 
11
11
  from tinybird.client import TinyB
12
- from tinybird.config import get_display_host
13
12
  from tinybird.tb.modules.cli import cli
14
13
  from tinybird.tb.modules.common import (
15
14
  _get_workspace_plan_name,
16
- ask_for_user_token,
17
- check_user_token,
15
+ ask_for_organization_interactively,
16
+ ask_for_organization_name,
18
17
  coro,
18
+ create_organization_and_add_workspaces,
19
19
  create_workspace_interactive,
20
20
  create_workspace_non_interactive,
21
21
  echo_safe_humanfriendly_tables_format_smart_table,
22
22
  get_current_main_workspace,
23
+ get_organizations_by_user,
24
+ get_user_token,
23
25
  is_valid_starterkit,
24
26
  print_current_workspace,
27
+ sort_organizations_by_user,
25
28
  switch_workspace,
26
29
  )
27
30
  from tinybird.tb.modules.config import CLIConfig
@@ -98,29 +101,65 @@ async def workspace_current():
98
101
  default=False,
99
102
  help="When enabled, tb will share all data sources from the current workspace with the new one",
100
103
  )
104
+ @click.option(
105
+ "--organization-id",
106
+ "organization_id",
107
+ type=str,
108
+ required=False,
109
+ help="When passed, the workspace will be created in the specified organization",
110
+ )
101
111
  @click.pass_context
102
112
  @coro
103
113
  async def create_workspace(
104
- ctx: Context, workspace_name: str, starter_kit: str, user_token: Optional[str], fork: bool
114
+ ctx: Context,
115
+ workspace_name: str,
116
+ starter_kit: str,
117
+ user_token: Optional[str],
118
+ fork: bool,
119
+ organization_id: Optional[str],
105
120
  ) -> None:
106
121
  if starter_kit and not await is_valid_starterkit(ctx, starter_kit):
107
122
  raise CLIWorkspaceException(FeedbackManager.error_starterkit_name(starterkit_name=starter_kit))
108
123
 
109
- if not user_token:
110
- config = CLIConfig.get_project_config()
111
- host = config.get_host() or CLIConfig.DEFAULTS["host"]
112
- ui_host = get_display_host(host)
113
- user_token = ask_for_user_token("create a new workspace", ui_host)
114
- if not user_token:
115
- return
116
- await check_user_token(ctx, user_token)
124
+ config = CLIConfig.get_project_config()
125
+
126
+ user_token = await get_user_token(config, user_token)
127
+
128
+ organization_name = None
129
+ organizations = await get_organizations_by_user(config, user_token)
130
+
131
+ if organization_id:
132
+ organization = next((org for org in organizations if org.get("id") == organization_id), None)
133
+ if not organization:
134
+ raise CLIWorkspaceException(FeedbackManager.error_organization_not_found(organization_id=organization_id))
135
+ organization_name = organization.get("name")
136
+ else:
137
+ if len(organizations) == 0:
138
+ organization_name = await ask_for_organization_name(config)
139
+ organization = await create_organization_and_add_workspaces(config, organization_name, user_token)
140
+ organization_id = organization.get("id")
141
+ elif len(organizations) == 1:
142
+ organization_id = organizations[0].get("id")
143
+ organization_name = organizations[0].get("name")
144
+ else:
145
+ sorted_organizations = sort_organizations_by_user(organizations, user_email=config.get_user_email())
146
+ current_organization = await ask_for_organization_interactively(sorted_organizations)
147
+ if current_organization:
148
+ organization_id = current_organization.get("id")
149
+ organization_name = current_organization.get("name")
150
+ else:
151
+ return
117
152
 
118
153
  # If we have at least workspace_name, we start the non interactive
119
154
  # process, creating an empty workspace
120
155
  if workspace_name:
121
- await create_workspace_non_interactive(ctx, workspace_name, starter_kit, user_token, fork)
156
+ await create_workspace_non_interactive(
157
+ ctx, workspace_name, starter_kit, user_token, fork, organization_id, organization_name
158
+ )
122
159
  else:
123
- await create_workspace_interactive(ctx, workspace_name, starter_kit, user_token, fork)
160
+ await create_workspace_interactive(
161
+ ctx, workspace_name, starter_kit, user_token, fork, organization_id, organization_name
162
+ )
124
163
 
125
164
 
126
165
  @workspace.command(name="delete", short_help="Delete a workspace for your Tinybird user")
@@ -142,12 +181,8 @@ async def delete_workspace(
142
181
 
143
182
  config = CLIConfig.get_project_config()
144
183
  client = config.get_client()
145
- host = config.get_host() or CLIConfig.DEFAULTS["host"]
146
- ui_host = get_display_host(host)
147
184
 
148
- if not user_token:
149
- user_token = ask_for_user_token("delete a workspace", ui_host)
150
- await check_user_token(ctx, user_token)
185
+ user_token = await get_user_token(config, user_token)
151
186
 
152
187
  workspaces = (await client.user_workspaces()).get("workspaces", [])
153
188
  workspace_to_delete = next(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev87
3
+ Version: 0.0.1.dev89
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -1,8 +1,8 @@
1
1
  tinybird/__cli__.py,sha256=esPl5QDTzuQgHe5FuxWLm-fURFigGGwjnYLh9GuWUw4,232
2
- tinybird/client.py,sha256=s3YanJI9VBCOZ3QSu11PzWMVXt725qtTgCxBXI9g5vQ,52855
3
- tinybird/config.py,sha256=GQSc8v7mT79pmMANvp758i8mGVsTPd1VjJzjJmFi0LM,5885
2
+ tinybird/client.py,sha256=Ng4HQHum6ezf7nRZ61PbEOhYvEEPadmXKDYu2SGaL0c,53393
3
+ tinybird/config.py,sha256=5UP_UZ2Qtlm5aOH5W7SbtN8r7X-8u3-r853joKqU5zs,6072
4
4
  tinybird/connectors.py,sha256=7Gjms7b5MAaBFGi3xytsJurCylprONpFcYrzp4Fw2Rc,15241
5
- tinybird/context.py,sha256=VaMhyHruH-uyMypPDfxtuo4scS18b7rxCCdeUVm6ysg,1301
5
+ tinybird/context.py,sha256=FfqYfrGX_I7PKGTQo93utaKPDNVYWelg4Hsp3evX5wM,1291
6
6
  tinybird/datatypes.py,sha256=XNypumfqNjsvLJ5iNXnbVHRvAJe0aQwI3lS6Cxox-e0,10979
7
7
  tinybird/feedback_manager.py,sha256=YSjtFDJvc8y66j2J0iIkb3SVzDdYAJbzFL-JPQ26pak,68761
8
8
  tinybird/git_settings.py,sha256=Sw_8rGmribEFJ4Z_6idrVytxpFYk7ez8ei0qHULzs3E,3934
@@ -14,22 +14,22 @@ tinybird/sql_toolset.py,sha256=32SNvxRFKQYWTvYPMJ_u3ukcd1hKZyEqx8T2cv2412w,14697
14
14
  tinybird/syncasync.py,sha256=IPnOx6lMbf9SNddN1eBtssg8vCLHMt76SuZ6YNYm-Yk,27761
15
15
  tinybird/tornado_template.py,sha256=jjNVDMnkYFWXflmT8KU_Ssbo5vR8KQq3EJMk5vYgXRw,41959
16
16
  tinybird/ch_utils/constants.py,sha256=aYvg2C_WxYWsnqPdZB1ZFoIr8ZY-XjUXYyHKE9Ansj0,3890
17
- tinybird/ch_utils/engine.py,sha256=AUAww-KjGOZg9h0IBlKA3FeacJYB4rOtqcTGJhFM-g8,40392
18
- tinybird/tb/__cli__.py,sha256=LqvlRqPpw46Nvopi4aHBuiUk4LaKNklSwNZJhp6LJWs,251
17
+ tinybird/ch_utils/engine.py,sha256=BZuPM7MFS7vaEKK5tOMR2bwSAgJudPrJt27uVEwZmTY,40512
18
+ tinybird/tb/__cli__.py,sha256=xWmtpX_BrBWY-ALVqm-M8PCmUyEnwSYABEqNIkELu8M,251
19
19
  tinybird/tb/cli.py,sha256=qon0Lim-v5hHWWRW7TXaOoGo3p2kc5Xwgg3feI4mbMQ,998
20
20
  tinybird/tb/modules/auth.py,sha256=L1IatO2arRSzys3t8px8xVt8uPWUL5EVD0sFzAV_uVU,9022
21
21
  tinybird/tb/modules/build.py,sha256=-lRGBxKtuipmyl3pmiGcfp67fH1Ed-COfHAZKdgLIWo,10483
22
22
  tinybird/tb/modules/cicd.py,sha256=T0lb9u_bDdTUVe8TwNNb1qQ5KnSPHMVjqPfKF4BBNBw,5347
23
23
  tinybird/tb/modules/cli.py,sha256=rE-0PtttuXvigKP1BkdgolP833pEhe-daK6l86bnaEw,16468
24
- tinybird/tb/modules/common.py,sha256=f5yKjb1dgUzR-SrC4UHnpHXYIu6INcTHwNO96jW8A9w,73201
25
- tinybird/tb/modules/config.py,sha256=RWDrhH1OM9OWMixOyQfM6RVLBLb7Ul2Ar1tR8K46tOs,11322
24
+ tinybird/tb/modules/common.py,sha256=xWdxukkqTdK0YFHSUYxHx8_cJO165h2hdrE75aLHa7g,80383
25
+ tinybird/tb/modules/config.py,sha256=BVZg-4f_R3vJTwCChXY2AXaH67SRk62xoP_IymquosI,11404
26
26
  tinybird/tb/modules/copy.py,sha256=MAVqKip8_QhOYq99U_XuqSO6hCLJEh5sFtbhcXtI3SI,5802
27
27
  tinybird/tb/modules/create.py,sha256=KjotVfIQLfcPyQBykTHnPLn4ikrm6qqeMcbRE1d-6Jo,13280
28
28
  tinybird/tb/modules/datasource.py,sha256=dNCK9iCR2xPLfwqqwg2ixyE6NuoVEiJU2mBZBmOYrVY,16906
29
- tinybird/tb/modules/deployment.py,sha256=XAVt6a_vtH8sJ99Z9Ht1q5_ni7AFPRSeidfgfuJtqLw,17565
29
+ tinybird/tb/modules/deployment.py,sha256=oSRnQnz2Wkvp1y0yt_tVa9g0aCYyFxNAsUyxoZTIcpY,17615
30
30
  tinybird/tb/modules/endpoint.py,sha256=EhVoGAXsFz-83Fiwj1gI-I73iRRvL49d0W81un7hvPE,12080
31
31
  tinybird/tb/modules/exceptions.py,sha256=4A2sSjCEqKUMqpP3WI00zouCWW4uLaghXXLZBSw04mY,3363
32
- tinybird/tb/modules/feedback_manager.py,sha256=Lv5MBGWNbIFTIy4nNSi0c61bmiQRv5J0nHRiiXUqDuc,68478
32
+ tinybird/tb/modules/feedback_manager.py,sha256=7nNiOx7OMebiheLED1r0d75SbuXCNxyBmF4e20rCBNc,69511
33
33
  tinybird/tb/modules/fmt.py,sha256=qpf9APqKTKL2uphNgdbj4OMVyLkAxZn6dn4eHF99L5g,3553
34
34
  tinybird/tb/modules/job.py,sha256=956Pj8BEEsiD2GZsV9RKKVM3I_CveOLgS82lykO5ukk,2963
35
35
  tinybird/tb/modules/llm.py,sha256=AC0VSphTOM2t-v1_3NLvNN_FIbgMo4dTyMqIv5nniPo,835
@@ -51,7 +51,7 @@ tinybird/tb/modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09
51
51
  tinybird/tb/modules/test.py,sha256=oa6_ApTCT526W8-hngpZwNyl2AbIpOztOsxf0iwlSwc,11547
52
52
  tinybird/tb/modules/token.py,sha256=OhqLFpCHVfYeBCxJ0n7n2qoho9E9eGcUfHgL7R1MUVQ,13485
53
53
  tinybird/tb/modules/watch.py,sha256=qMQhewRSso1AFSEFLuyeyGFA8Lxf9ccYJxmVdPU1BgM,8808
54
- tinybird/tb/modules/workspace.py,sha256=fF0W6M5_qdwbbXpCUfuhon7CR0NhOOA0XOv5jD2l1JI,6401
54
+ tinybird/tb/modules/workspace.py,sha256=SYkEULv_Gg8FhnAnZspengzyT5N4w0wjsvWWZ3vy3Ho,7753
55
55
  tinybird/tb/modules/workspace_members.py,sha256=Vb5XEaKmkfONyfg2MS5EcpwolMvv7GLwFS5m2EuobT8,8726
56
56
  tinybird/tb/modules/datafile/build.py,sha256=seGFSvmgyRrAM1-icsKBkuog3WccfGUYFTPT-xoA5W8,50940
57
57
  tinybird/tb/modules/datafile/build_common.py,sha256=rT7VJ5mnQ68R_8US91DAtkusfvjWuG_NObOzNgtN_ko,4562
@@ -77,8 +77,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
77
77
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
78
78
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
79
79
  tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
80
- tinybird-0.0.1.dev87.dist-info/METADATA,sha256=B58e0OeSZ-M9Z70rKj58zzouvl9WpGwl7pQy9tvjzyg,2585
81
- tinybird-0.0.1.dev87.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
82
- tinybird-0.0.1.dev87.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
83
- tinybird-0.0.1.dev87.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
84
- tinybird-0.0.1.dev87.dist-info/RECORD,,
80
+ tinybird-0.0.1.dev89.dist-info/METADATA,sha256=pKJ86pTylK2iqAqdOcWyaaApey6C8eiBM_NpLv-ZzWA,2585
81
+ tinybird-0.0.1.dev89.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
82
+ tinybird-0.0.1.dev89.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
83
+ tinybird-0.0.1.dev89.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
84
+ tinybird-0.0.1.dev89.dist-info/RECORD,,