tinybird 0.0.1.dev86__py3-none-any.whl → 0.0.1.dev88__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.

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/sql_template.py CHANGED
@@ -1452,6 +1452,13 @@ def generate(self, **kwargs) -> Tuple[str, TemplateExecutionResults]:
1452
1452
  template_execution_results["activate"] = feature
1453
1453
  return Expression(f"-- activate {feature}\n")
1454
1454
 
1455
+ def set_disable_feature(feature):
1456
+ valid_features = "analyzer"
1457
+ if feature not in valid_features:
1458
+ raise SQLTemplateException(f"'{feature}' is not a valid 'disable' argument")
1459
+ template_execution_results["disable"] = feature
1460
+ return Expression(f"-- disable {feature}\n")
1461
+
1455
1462
  namespace.update(_namespace)
1456
1463
  namespace.update(kwargs)
1457
1464
  namespace.update(
@@ -1466,6 +1473,7 @@ def generate(self, **kwargs) -> Tuple[str, TemplateExecutionResults]:
1466
1473
  "backend_hint": set_backend_hint,
1467
1474
  "cache_ttl": set_cache_ttl,
1468
1475
  "activate": set_activate,
1476
+ "disable": set_disable_feature,
1469
1477
  }
1470
1478
  )
1471
1479
 
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.dev86'
8
- __revision__ = '125dbec'
7
+ __version__ = '0.0.1.dev88'
8
+ __revision__ = 'fd53f21'
@@ -57,6 +57,7 @@ def build_project(project: Project, tb_client: TinyB, file_changed: Optional[str
57
57
  DATAFILE_TYPE_TO_CONTENT_TYPE = {
58
58
  ".datasource": "text/plain",
59
59
  ".pipe": "text/plain",
60
+ ".connection": "text/plain",
60
61
  }
61
62
  TINYBIRD_API_URL = tb_client.host + "/v1/build"
62
63
  logging.debug(TINYBIRD_API_URL)
@@ -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
@@ -79,7 +79,6 @@ class CLIConfig:
79
79
  _projects: Dict[str, "CLIConfig"] = {}
80
80
 
81
81
  def __init__(self, path: Optional[str] = None) -> None:
82
- self.cwd = os.getcwd()
83
82
  self._path = path or self.get_tinyb_file() or os.path.join(os.getcwd(), ".tinyb")
84
83
  self._values: Dict[str, ConfigValue] = {}
85
84
  self._values["version"] = ConfigValue("version", CURRENT_VERSION, ConfigValueOrigin.DEFAULT)
@@ -248,6 +247,9 @@ class CLIConfig:
248
247
  def get_user_client(self, host: Optional[str] = None) -> tbc.TinyB:
249
248
  return self.get_client(self.get_user_token(), host)
250
249
 
250
+ def get_user_email(self) -> Optional[str]:
251
+ return self["user_email"]
252
+
251
253
  def set_workspace_token(self, workspace_id: str, token: str) -> None:
252
254
  pass
253
255
 
@@ -51,6 +51,9 @@ async def create(
51
51
  config.persist_to_file()
52
52
  project.folder = folder
53
53
 
54
+ if cwd := config.get("cwd"):
55
+ click.echo(FeedbackManager.gray(message=f"Using '{cwd.replace(os.getcwd(), '')}' as target folder"))
56
+
54
57
  root_folder = os.getcwd()
55
58
  if config._path:
56
59
  root_folder = os.path.dirname(config._path)
@@ -168,14 +168,18 @@ class UnkownExtensionerror(Exception): ...
168
168
  class DatafileKind(Enum):
169
169
  pipe = "pipe"
170
170
  datasource = "datasource"
171
+ connection = "connection"
171
172
 
172
173
  @classmethod
173
174
  def from_extension(cls, extension: str) -> DatafileKind:
174
- if extension == ".pipe":
175
- return cls.pipe
176
- elif extension == ".datasource":
177
- return cls.datasource
178
- raise UnkownExtensionerror(f"Unknown extension {extension} for data file")
175
+ extension_map = {
176
+ ".pipe": cls.pipe,
177
+ ".datasource": cls.datasource,
178
+ ".connection": cls.connection,
179
+ }
180
+ if extension not in extension_map:
181
+ raise UnkownExtensionerror(f"Unknown extension {extension} for data file")
182
+ return extension_map[extension]
179
183
 
180
184
 
181
185
  class Datafile:
@@ -1099,9 +1103,7 @@ def parse(
1099
1103
  ) -> Datafile:
1100
1104
  """
1101
1105
  Parses `s` string into a document
1102
- >>> d = parse("MAINTAINER 'rambo' #this is me\\nNODE \\"test_01\\"\\n DESCRIPTION this is a node that does whatever\\nSQL >\\n\\n SELECT * from test_00\\n\\n\\nNODE \\"test_02\\"\\n DESCRIPTION this is a node that does whatever\\nSQL >\\n\\n SELECT * from test_01\\n WHERE a > 1\\n GROUP by a\\n")
1103
- >>> d.maintainer
1104
- 'rambo'
1106
+ >>> d = parse("NODE \\"test_01\\"\\n DESCRIPTION this is a node that does whatever\\nSQL >\\n\\n SELECT * from test_00\\n\\n\\nNODE \\"test_02\\"\\n DESCRIPTION this is a node that does whatever\\nSQL >\\n\\n SELECT * from test_01\\n WHERE a > 1\\n GROUP by a\\n")
1105
1107
  >>> len(d.nodes)
1106
1108
  2
1107
1109
  >>> d.nodes[0]
@@ -1377,7 +1379,6 @@ def parse(
1377
1379
 
1378
1380
  cmds = {
1379
1381
  "source": sources,
1380
- "maintainer": assign("maintainer"),
1381
1382
  "schema": schema,
1382
1383
  "indexes": indexes,
1383
1384
  "engine": set_engine,
@@ -1417,6 +1418,7 @@ def parse(
1417
1418
  "kafka_store_binary_headers": assign_var("kafka_store_binary_headers"),
1418
1419
  "kafka_key_avro_deserialization": assign_var("kafka_key_avro_deserialization"),
1419
1420
  "kafka_ssl_ca_pem": assign_var("kafka_ssl_ca_pem"),
1421
+ "kafka_security_protocol": assign_var("kafka_security_protocol"),
1420
1422
  "kafka_sasl_mechanism": assign_var("kafka_sasl_mechanism"),
1421
1423
  "import_service": assign_var("import_service"),
1422
1424
  "import_connection_name": assign_var("import_connection_name"),
@@ -324,6 +324,7 @@ def create_deployment(
324
324
  DATAFILE_TYPE_TO_CONTENT_TYPE = {
325
325
  ".datasource": "text/plain",
326
326
  ".pipe": "text/plain",
327
+ ".connection": "text/plain",
327
328
  }
328
329
  project: Project = ctx.ensure_object(dict)["project"]
329
330
  client = ctx.ensure_object(dict)["client"]
@@ -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,7 +9,7 @@ from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
9
9
 
10
10
 
11
11
  class Project:
12
- extensions = ("datasource", "pipe")
12
+ extensions = ("datasource", "pipe", "connection")
13
13
 
14
14
  def __init__(self, folder: str):
15
15
  self.folder = folder
@@ -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(
@@ -1042,6 +1042,7 @@ VALID_FUNCTION_NAMES = {
1042
1042
  "custom_error",
1043
1043
  "backend_hint",
1044
1044
  "activate",
1045
+ "disable",
1045
1046
  "sql_unescape",
1046
1047
  "max_threads",
1047
1048
  "tb_secret",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev86
3
+ Version: 0.0.1.dev88
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -1,5 +1,5 @@
1
1
  tinybird/__cli__.py,sha256=esPl5QDTzuQgHe5FuxWLm-fURFigGGwjnYLh9GuWUw4,232
2
- tinybird/client.py,sha256=s3YanJI9VBCOZ3QSu11PzWMVXt725qtTgCxBXI9g5vQ,52855
2
+ tinybird/client.py,sha256=Ng4HQHum6ezf7nRZ61PbEOhYvEEPadmXKDYu2SGaL0c,53393
3
3
  tinybird/config.py,sha256=GQSc8v7mT79pmMANvp758i8mGVsTPd1VjJzjJmFi0LM,5885
4
4
  tinybird/connectors.py,sha256=7Gjms7b5MAaBFGi3xytsJurCylprONpFcYrzp4Fw2Rc,15241
5
5
  tinybird/context.py,sha256=VaMhyHruH-uyMypPDfxtuo4scS18b7rxCCdeUVm6ysg,1301
@@ -8,28 +8,28 @@ tinybird/feedback_manager.py,sha256=YSjtFDJvc8y66j2J0iIkb3SVzDdYAJbzFL-JPQ26pak,
8
8
  tinybird/git_settings.py,sha256=Sw_8rGmribEFJ4Z_6idrVytxpFYk7ez8ei0qHULzs3E,3934
9
9
  tinybird/prompts.py,sha256=rDIrkK5S1NJ0bIFjULsCYnGApv9Vc0jmR4tdRuJhM8c,32953
10
10
  tinybird/sql.py,sha256=LBi74GxhNAYTb6m2-KNGpAkguSKh7rcvBbERbE7nalA,46195
11
- tinybird/sql_template.py,sha256=hHz-EbHWlWVMS4e6hTpoieJ1qRAzzZLDq9-fnjT60dw,95732
11
+ tinybird/sql_template.py,sha256=1qO6FaEe9rdLAKIORZsNk53XklLeP6oLnMhlw0PBUcI,96091
12
12
  tinybird/sql_template_fmt.py,sha256=KUHdj5rYCYm_rKKdXYSJAE9vIyXUQLB0YSZnUXHeBlY,10196
13
13
  tinybird/sql_toolset.py,sha256=32SNvxRFKQYWTvYPMJ_u3ukcd1hKZyEqx8T2cv2412w,14697
14
14
  tinybird/syncasync.py,sha256=IPnOx6lMbf9SNddN1eBtssg8vCLHMt76SuZ6YNYm-Yk,27761
15
- tinybird/tornado_template.py,sha256=KmW_VD7y-NVqrc8YZKwIaxoJB0XpCcB2izdmxmtmApM,41944
15
+ tinybird/tornado_template.py,sha256=jjNVDMnkYFWXflmT8KU_Ssbo5vR8KQq3EJMk5vYgXRw,41959
16
16
  tinybird/ch_utils/constants.py,sha256=aYvg2C_WxYWsnqPdZB1ZFoIr8ZY-XjUXYyHKE9Ansj0,3890
17
17
  tinybird/ch_utils/engine.py,sha256=AUAww-KjGOZg9h0IBlKA3FeacJYB4rOtqcTGJhFM-g8,40392
18
- tinybird/tb/__cli__.py,sha256=g7_DbCeC7bCOKoEiJkKaQ89W0974t4itgPG1YR0nK8k,251
18
+ tinybird/tb/__cli__.py,sha256=gO4_tED-nqmsmf_rs3JTBHu2MQsY0Dn0JvbUsi_2ZCE,251
19
19
  tinybird/tb/cli.py,sha256=qon0Lim-v5hHWWRW7TXaOoGo3p2kc5Xwgg3feI4mbMQ,998
20
20
  tinybird/tb/modules/auth.py,sha256=L1IatO2arRSzys3t8px8xVt8uPWUL5EVD0sFzAV_uVU,9022
21
- tinybird/tb/modules/build.py,sha256=46ZTxubughaq8QDV6yq9thatSVVY4yNqyzbfxRBcgDA,10446
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=wIsoIf2yMCT2JQ5f6nZr-WzB7Uuah9EqXf6Glxe_p-Q,11353
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
- tinybird/tb/modules/create.py,sha256=EAPI-ZsY4Nr_FBehUMpM4y1HZRoWLgdtHQtb71WnauE,13137
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=LzxOWjI9hSIXbNxR0BmQfXWBUHhVX2LoZUeChkcmg6c,17528
29
+ tinybird/tb/modules/deployment.py,sha256=XAVt6a_vtH8sJ99Z9Ht1q5_ni7AFPRSeidfgfuJtqLw,17565
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
@@ -42,7 +42,7 @@ tinybird/tb/modules/materialization.py,sha256=r8Q9HXcYEmfrEzP4WpiasCKDJdSkTPaAKJ
42
42
  tinybird/tb/modules/mock.py,sha256=3q4i6CXKcS-zsgevbN_zpAP4AnB9_WIVxmVSJV3FNPQ,3881
43
43
  tinybird/tb/modules/pipe.py,sha256=gcLz0qHgwKDLsWFY3yFLO9a0ETAV1dFbI8YeLHi9460,2429
44
44
  tinybird/tb/modules/playground.py,sha256=CQaz2JqFDdReK2fJY1yZsSwiSY24_jeTb9PKw1WUigA,4848
45
- tinybird/tb/modules/project.py,sha256=vxNEsc5gIQJM7ybXklnT7wna2M-GfbUybj4nKckrZM8,2949
45
+ tinybird/tb/modules/project.py,sha256=ei0TIAuRksdV2g2FJqByuV4DPyivQGrZ42z_eQDNBgI,2963
46
46
  tinybird/tb/modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
47
47
  tinybird/tb/modules/shell.py,sha256=a98W4L4gfrmxEyybtu6S4ENXrBYtgNASB5e_evuXQvI,13936
48
48
  tinybird/tb/modules/table.py,sha256=4XrtjM-N0zfNtxVkbvLDQQazno1EPXnxTyo7llivfXk,11035
@@ -51,13 +51,13 @@ 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
58
58
  tinybird/tb/modules/datafile/build_datasource.py,sha256=VjxaKKLZhPYt3XHOyMmfoqEAWAPI5D78T-8FOaN77MY,17355
59
59
  tinybird/tb/modules/datafile/build_pipe.py,sha256=Tf49kZmXub45qGcePFfqGO7p-FH5eYM46DtVI3AQJEc,11358
60
- tinybird/tb/modules/datafile/common.py,sha256=QhlQkYW5xLNEz_yZmMz1Dl8cnWG274Wu7QHVOIPcooM,79993
60
+ tinybird/tb/modules/datafile/common.py,sha256=J9hXDdi27Quc0AkzoS2ChlEYwDBDePcW2sZ1qruZKdY,80091
61
61
  tinybird/tb/modules/datafile/diff.py,sha256=-0J7PsBO64T7LOZSkZ4ZFHHCPvT7cKItnJkbz2PkndU,6754
62
62
  tinybird/tb/modules/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1wnI,556
63
63
  tinybird/tb/modules/datafile/fixture.py,sha256=si-9LB-LdKQSWDtVW82xDrHtFfko5bgBG1cvjqqrcPU,1064
@@ -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.dev86.dist-info/METADATA,sha256=b7pdCP3rGeK8dWWdfbN-eicQl2u38KD4g1KWUnnLAlA,2585
81
- tinybird-0.0.1.dev86.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
82
- tinybird-0.0.1.dev86.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
83
- tinybird-0.0.1.dev86.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
84
- tinybird-0.0.1.dev86.dist-info/RECORD,,
80
+ tinybird-0.0.1.dev88.dist-info/METADATA,sha256=D04ACuigyiZY-PtN_M1CKoBYBOUC33qeYNcb6pykA-E,2585
81
+ tinybird-0.0.1.dev88.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
82
+ tinybird-0.0.1.dev88.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
83
+ tinybird-0.0.1.dev88.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
84
+ tinybird-0.0.1.dev88.dist-info/RECORD,,