tinybird 0.0.1.dev162__py3-none-any.whl → 0.0.1.dev169__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.

@@ -146,10 +146,9 @@ class TableDetails:
146
146
  def is_replacing_engine(self) -> bool:
147
147
  if self.engine:
148
148
  engine_lower = self.engine.lower()
149
- is_aggregating = "aggregatingmergetree" in engine_lower
150
149
  is_replacing = "replacingmergetree" in engine_lower
151
150
  is_collapsing = "collapsingmergetree" in engine_lower
152
- return is_aggregating or is_replacing or is_collapsing
151
+ return is_replacing or is_collapsing
153
152
  return False
154
153
 
155
154
  def diff_ttl(self, new_ttl: str) -> bool:
@@ -169,6 +168,10 @@ class TableDetails:
169
168
  @property
170
169
  def sorting_key(self) -> Optional[str]:
171
170
  _sorting_key = self.details.get("sorting_key", None)
171
+ # TODO: This should use ENABLED_ENGINES to guess if the sorting key is required or not
172
+ # Also checking this and raising an error in a getter is a bit of an anti-pattern,
173
+ # a data source could have a "wrong" sorting key and we won't be able to even show it in the API.
174
+ # All these checks be performed only on creation time.
172
175
  if self.is_replacing_engine() and not _sorting_key:
173
176
  raise ValueError(f"SORTING_KEY must be defined for the {self.engine} engine")
174
177
  if self.is_mergetree_family():
tinybird/tb/__cli__.py CHANGED
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/forward/commands'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '0.0.1.dev162'
8
- __revision__ = 'ccf549a'
7
+ __version__ = '0.0.1.dev169'
8
+ __revision__ = '0e591e6'
@@ -317,7 +317,23 @@ def __unpatch_click_output():
317
317
 
318
318
 
319
319
  async def create_ctx_client(ctx: Context, config: Dict[str, Any], cloud: bool, staging: bool):
320
- commands_without_ctx_client = ["auth", "check", "local", "login", "logout", "update", "upgrade", "create", "info"]
320
+ commands_without_ctx_client = [
321
+ "auth",
322
+ "check",
323
+ "local",
324
+ "login",
325
+ "logout",
326
+ "update",
327
+ "upgrade",
328
+ "create",
329
+ "info",
330
+ "tag",
331
+ "push",
332
+ "branch",
333
+ "diff",
334
+ "fmt",
335
+ "init",
336
+ ]
321
337
  command = ctx.invoked_subcommand
322
338
  if command in commands_without_ctx_client:
323
339
  return None
@@ -303,7 +303,7 @@ line experience. To opt-out, set TB_CLI_TELEMETRY_OPTOUT to '1' or 'true'."""
303
303
  return rv
304
304
 
305
305
  # Get all available commands
306
- commands: List[str] = self.list_commands(ctx)
306
+ commands: List[str] = list_commands(self, ctx)
307
307
 
308
308
  # Find closest matching command using thefuzz
309
309
  matches = process.extract(cmd_name, commands, limit=1)
@@ -358,6 +358,11 @@ line experience. To opt-out, set TB_CLI_TELEMETRY_OPTOUT to '1' or 'true'."""
358
358
  return None
359
359
 
360
360
 
361
+ def list_commands(self, ctx: Context):
362
+ all_commands = self.list_commands(ctx)
363
+ return [cmd for cmd in all_commands if not self.get_command(ctx, cmd).hidden]
364
+
365
+
361
366
  def load_connector_config(ctx: Context, connector_name: str, debug: bool, check_uninstalled: bool = False):
362
367
  config_file = Path(getcwd()) / f".tinyb_{connector_name}"
363
368
  try:
@@ -156,7 +156,6 @@ INTERNAL_TABLES: Tuple[str, ...] = (
156
156
  )
157
157
 
158
158
  PREVIEW_CONNECTOR_SERVICES = ["s3", "s3_iamrole", "gcs"]
159
- TB_LOCAL_WORKSPACE_NAME = "Tinybird_Local_Testing"
160
159
 
161
160
  pp = pprint.PrettyPrinter()
162
161
 
@@ -419,7 +419,7 @@ def deployment_discard(ctx: click.Context, wait: bool) -> None:
419
419
  discard_deployment(client.host, HEADERS, wait=wait)
420
420
 
421
421
 
422
- @cli.command(name="deploy", hidden=True)
422
+ @cli.command(name="deploy")
423
423
  @click.option(
424
424
  "--wait/--no-wait",
425
425
  is_flag=True,
@@ -468,6 +468,19 @@ def create_deployment_cmd(
468
468
  template: Optional[str] = None,
469
469
  ) -> None:
470
470
  if template:
471
+ project = ctx.ensure_object(dict)["project"]
472
+ if project.get_project_files():
473
+ click.echo(
474
+ FeedbackManager.error(
475
+ message="You are trying to deploy a template from a folder that already contains data files. "
476
+ "Please remove the data files from the current folder or use a different folder and try again."
477
+ )
478
+ )
479
+ sys_exit(
480
+ "deployment_error",
481
+ "Deployment using a template is not allowed when the project already contains data files",
482
+ )
483
+
471
484
  click.echo(FeedbackManager.info(message="» Downloading template..."))
472
485
  try:
473
486
  download_github_template(template)
@@ -576,7 +589,10 @@ def create_deployment(
576
589
  sys.exit(0)
577
590
  elif status == "failed":
578
591
  click.echo(FeedbackManager.error(message="Deployment failed"))
579
- sys_exit("deployment_error", "Deployment failed" + str(deployment.get("errors")))
592
+ sys_exit(
593
+ "deployment_error",
594
+ f"Deployment failed. Errors: {str(deployment.get('errors') + deployment.get('feedback'))}",
595
+ )
580
596
  else:
581
597
  click.echo(FeedbackManager.error(message=f"Unknown deployment result {status}"))
582
598
  except Exception as e:
@@ -606,11 +622,18 @@ def create_deployment(
606
622
  if auto:
607
623
  click.echo(FeedbackManager.error(message="Rolling back deployment"))
608
624
  discard_deployment(client.host, HEADERS, wait=wait)
609
- sys_exit("deployment_error", "Deployment failed: " + str(deployment.get("errors")))
625
+ sys_exit(
626
+ "deployment_error",
627
+ f"Deployment failed. Errors: {str(deployment.get('errors') + deployment.get('feedback'))}",
628
+ )
610
629
 
611
630
  if deployment.get("status") == "data_ready":
612
631
  break
613
632
 
633
+ if deployment.get("status") in ["deleting", "deleted"]:
634
+ click.echo(FeedbackManager.error(message="Deployment was deleted by another process"))
635
+ sys_exit("deployment_error", "Deployment was deleted by another process")
636
+
614
637
  time.sleep(5)
615
638
 
616
639
  click.echo(FeedbackManager.info(message="✓ Deployment is ready"))
@@ -1,12 +1,19 @@
1
1
  import hashlib
2
+ import json
2
3
  import logging
3
4
  import os
4
5
  import re
5
6
  import subprocess
6
- from typing import Any, Dict
7
+ import time
8
+ from typing import Any, Dict, Optional
7
9
 
10
+ import boto3
11
+ import click
8
12
  import requests
9
13
 
14
+ import docker
15
+ from docker.client import DockerClient
16
+ from docker.models.containers import Container
10
17
  from tinybird.tb.client import AuthNoTokenException, TinyB
11
18
  from tinybird.tb.modules.config import CLIConfig
12
19
  from tinybird.tb.modules.exceptions import CLILocalException
@@ -18,6 +25,7 @@ TB_CONTAINER_NAME = "tinybird-local"
18
25
  TB_LOCAL_PORT = int(os.getenv("TB_LOCAL_PORT", 7181))
19
26
  TB_LOCAL_HOST = re.sub(r"^https?://", "", os.getenv("TB_LOCAL_HOST", "localhost"))
20
27
  TB_LOCAL_ADDRESS = f"http://{TB_LOCAL_HOST}:{TB_LOCAL_PORT}"
28
+ TB_LOCAL_DEFAULT_WORKSPACE_NAME = "Tinybird_Local_Testing"
21
29
 
22
30
 
23
31
  async def get_tinybird_local_client(
@@ -101,22 +109,328 @@ def get_local_tokens() -> Dict[str, str]:
101
109
  # ruff: noqa: ASYNC210
102
110
  return requests.get(f"{TB_LOCAL_ADDRESS}/tokens").json()
103
111
  except Exception:
112
+ # Check if tinybird-local is running using docker client (some clients use podman and won't have docker cmd)
104
113
  try:
105
- # Check if tinybird-local is running with docker, in case it's a config issue
106
- output = subprocess.check_output(["docker", "ps"], text=True)
107
- header_row = next((line for line in output.splitlines() if "CONTAINER ID" in line), "")
108
- tb_local_row = next(
109
- (line for line in output.splitlines() if TB_CONTAINER_NAME in line),
110
- f"{TB_CONTAINER_NAME} not found in output",
114
+ docker_client = get_docker_client()
115
+ container = get_existing_container_with_matching_env(docker_client, TB_CONTAINER_NAME, {})
116
+
117
+ output = {}
118
+ if container:
119
+ output = container.attrs
120
+ add_telemetry_event(
121
+ "docker_debug",
122
+ data={
123
+ "container_attrs": output,
124
+ },
125
+ )
126
+
127
+ if container and container.status == "running":
128
+ if container.health == "healthy":
129
+ raise CLILocalException(
130
+ FeedbackManager.error(
131
+ message=(
132
+ "Looks like Tinybird Local is running but we are not able to connect to it.\n\n"
133
+ "If you've run it manually using different host or port, please set the environment variables "
134
+ "TB_LOCAL_HOST and TB_LOCAL_PORT to match the ones you're using.\n"
135
+ "If you're not sure about this, please run `tb local restart` and try again."
136
+ )
137
+ )
138
+ )
139
+ raise CLILocalException(
140
+ FeedbackManager.error(
141
+ message=(
142
+ "Tinybird Local is running but it's unhealthy. Please check if it's running and try again.\n"
143
+ "If the problem persists, please run `tb local restart` and try again."
144
+ )
145
+ )
146
+ )
147
+ except CLILocalException as e:
148
+ raise e
149
+ except Exception:
150
+ pass
151
+
152
+ # Check if tinybird-local is running with docker
153
+ try:
154
+ output_str = subprocess.check_output(
155
+ ["docker", "ps", "--filter", f"name={TB_CONTAINER_NAME}", "--format", "json"], text=True
111
156
  )
157
+ output = {}
158
+ if output_str:
159
+ output = json.loads(output_str)
112
160
  add_telemetry_event(
113
161
  "docker_debug",
114
162
  data={
115
- "docker_ps_output": header_row + tb_local_row,
163
+ "docker_ps_output": output,
116
164
  },
117
165
  )
166
+
167
+ if output.get("State", "") == "running":
168
+ if "(healthy)" in output.get("Status", ""):
169
+ raise CLILocalException(
170
+ FeedbackManager.error(
171
+ message=(
172
+ "Looks like Tinybird Local is running but we are not able to connect to it.\n\n"
173
+ "If you've run it manually using different host or port, please set the environment variables "
174
+ "TB_LOCAL_HOST and TB_LOCAL_PORT to match the ones you're using.\n"
175
+ "If you're not sure about this, please run `tb local restart` and try again."
176
+ )
177
+ )
178
+ )
179
+ raise CLILocalException(
180
+ FeedbackManager.error(
181
+ message="Tinybird Local is running but it's unhealthy. Please check if it's running and try again.\n"
182
+ "If the problem persists, please run `tb local restart` and try again."
183
+ )
184
+ )
185
+ except CLILocalException as e:
186
+ raise e
118
187
  except Exception:
119
188
  pass
189
+
190
+ is_ci = (
191
+ os.getenv("GITHUB_ACTIONS")
192
+ or os.getenv("TRAVIS")
193
+ or os.getenv("CIRCLECI")
194
+ or os.getenv("GITLAB_CI")
195
+ or os.getenv("CI")
196
+ or os.getenv("TB_CI")
197
+ )
198
+ if not is_ci:
199
+ yes = click.confirm(
200
+ FeedbackManager.warning(message="Tinybird local is not running. Do you want to start it? [Y/n]"),
201
+ prompt_suffix="",
202
+ show_default=False,
203
+ default=True,
204
+ )
205
+ if yes:
206
+ click.echo(FeedbackManager.highlight(message="» Starting Tinybird Local..."))
207
+ docker_client = get_docker_client()
208
+ start_tinybird_local(docker_client, False)
209
+ click.echo(FeedbackManager.success(message="✓ Tinybird Local is ready!"))
210
+ return get_local_tokens()
211
+
120
212
  raise CLILocalException(
121
213
  FeedbackManager.error(message="Tinybird local is not running. Please run `tb local start` first.")
122
214
  )
215
+
216
+
217
+ def start_tinybird_local(
218
+ docker_client: DockerClient,
219
+ use_aws_creds: bool,
220
+ ) -> None:
221
+ """Start the Tinybird container."""
222
+ pull_show_prompt = False
223
+ pull_required = False
224
+ try:
225
+ local_image = docker_client.images.get(TB_IMAGE_NAME)
226
+ local_image_id = local_image.attrs["RepoDigests"][0].split("@")[1]
227
+ remote_image = docker_client.images.get_registry_data(TB_IMAGE_NAME)
228
+ pull_show_prompt = local_image_id != remote_image.id
229
+ except Exception:
230
+ pull_show_prompt = False
231
+ pull_required = True
232
+
233
+ if pull_show_prompt and click.confirm(
234
+ FeedbackManager.warning(message="△ New version detected, download? [y/N]:"),
235
+ show_default=False,
236
+ prompt_suffix="",
237
+ ):
238
+ click.echo(FeedbackManager.info(message="* Downloading latest version of Tinybird Local..."))
239
+ pull_required = True
240
+
241
+ if pull_required:
242
+ docker_client.images.pull(TB_IMAGE_NAME, platform="linux/amd64")
243
+
244
+ environment = get_use_aws_creds() if use_aws_creds else {}
245
+
246
+ container = get_existing_container_with_matching_env(docker_client, TB_CONTAINER_NAME, environment)
247
+
248
+ if container and not pull_required:
249
+ # Container `start` is idempotent. It's safe to call it even if the container is already running.
250
+ container.start()
251
+ else:
252
+ if container:
253
+ container.remove(force=True)
254
+
255
+ container = docker_client.containers.run(
256
+ TB_IMAGE_NAME,
257
+ name=TB_CONTAINER_NAME,
258
+ detach=True,
259
+ ports={"7181/tcp": TB_LOCAL_PORT},
260
+ remove=False,
261
+ platform="linux/amd64",
262
+ environment=environment,
263
+ )
264
+
265
+ click.echo(FeedbackManager.info(message="* Waiting for Tinybird Local to be ready..."))
266
+ while True:
267
+ container.reload() # Refresh container attributes
268
+ health = container.attrs.get("State", {}).get("Health", {}).get("Status")
269
+ if health == "healthy":
270
+ break
271
+ if health == "unhealthy":
272
+ raise CLILocalException(
273
+ FeedbackManager.error(
274
+ message="Tinybird Local is unhealthy. Try running `tb local restart` in a few seconds."
275
+ )
276
+ )
277
+
278
+ time.sleep(5)
279
+
280
+ # Remove tinybird-local dangling images to avoid running out of disk space
281
+ images = docker_client.images.list(name=re.sub(r":.*$", "", TB_IMAGE_NAME), all=True, filters={"dangling": True})
282
+ for image in images:
283
+ image.remove(force=True)
284
+
285
+
286
+ def get_existing_container_with_matching_env(
287
+ docker_client: DockerClient, container_name: str, required_env: dict[str, str]
288
+ ) -> Optional[Container]:
289
+ """
290
+ Checks if a container with the given name exists and has matching environment variables.
291
+ If it exists but environment doesn't match, it returns None.
292
+
293
+ Args:
294
+ docker_client: The Docker client instance
295
+ container_name: The name of the container to check
296
+ required_env: Dictionary of environment variables that must be present
297
+
298
+ Returns:
299
+ The container if it exists with matching environment, None otherwise
300
+ """
301
+ container = None
302
+ containers = docker_client.containers.list(all=True, filters={"name": container_name})
303
+ if containers:
304
+ container = containers[0]
305
+
306
+ if container and required_env:
307
+ container_info = container.attrs
308
+ container_env = container_info.get("Config", {}).get("Env", [])
309
+ env_missing = False
310
+ for key, value in required_env.items():
311
+ env_var = f"{key}={value}"
312
+ if env_var not in container_env:
313
+ env_missing = True
314
+ break
315
+
316
+ if env_missing:
317
+ container.remove(force=True)
318
+ container = None
319
+
320
+ return container
321
+
322
+
323
+ def get_docker_client() -> DockerClient:
324
+ """Check if Docker is installed and running."""
325
+ try:
326
+ docker_host = os.getenv("DOCKER_HOST")
327
+ if not docker_host:
328
+ # Try to get docker host from docker context
329
+ try:
330
+ try:
331
+ output = subprocess.check_output(["docker", "context", "inspect"], text=True)
332
+ except Exception as e:
333
+ add_telemetry_event(
334
+ "docker_error",
335
+ error=f"docker_context_inspect_error: {str(e)}",
336
+ )
337
+ raise e
338
+ try:
339
+ context = json.loads(output)
340
+ except Exception as e:
341
+ add_telemetry_event(
342
+ "docker_error",
343
+ error=f"docker_context_inspect_parse_output_error: {str(e)}",
344
+ data={
345
+ "docker_context_inspect_output": output,
346
+ },
347
+ )
348
+ raise e
349
+ if context and len(context) > 0:
350
+ try:
351
+ docker_host = context[0].get("Endpoints", {}).get("docker", {}).get("Host")
352
+ if docker_host:
353
+ os.environ["DOCKER_HOST"] = docker_host
354
+ except Exception as e:
355
+ add_telemetry_event(
356
+ "docker_error",
357
+ error=f"docker_context_parse_host_error: {str(e)}",
358
+ data={
359
+ "context": json.dumps(context),
360
+ },
361
+ )
362
+ raise e
363
+ except Exception:
364
+ pass
365
+ try:
366
+ client = docker.from_env() # type: ignore
367
+ except Exception as e:
368
+ add_telemetry_event(
369
+ "docker_error",
370
+ error=f"docker_get_client_from_env_error: {str(e)}",
371
+ )
372
+ raise e
373
+ try:
374
+ client.ping()
375
+ except Exception as e:
376
+ client_dict_non_sensitive = {k: v for k, v in client.api.__dict__.items() if "auth" not in k}
377
+ add_telemetry_event(
378
+ "docker_error",
379
+ error=f"docker_ping_error: {str(e)}",
380
+ data={
381
+ "client": repr(client_dict_non_sensitive),
382
+ },
383
+ )
384
+ raise e
385
+ return client
386
+ except Exception:
387
+ raise CLILocalException(
388
+ FeedbackManager.error(
389
+ message=(
390
+ f"No container runtime is running. Make sure a Docker-compatible runtime is installed and running. "
391
+ f"Trying to connect to Docker-compatible runtime at {docker_host}\n\n"
392
+ "If you're using a custom location, please provide it using the DOCKER_HOST environment variable."
393
+ )
394
+ )
395
+ )
396
+
397
+
398
+ def get_use_aws_creds() -> dict[str, str]:
399
+ credentials: dict[str, str] = {}
400
+ try:
401
+ # Get the boto3 session and credentials
402
+ session = boto3.Session()
403
+ creds = session.get_credentials()
404
+
405
+ if creds:
406
+ # Create environment variables for the container based on boto credentials
407
+ credentials["AWS_ACCESS_KEY_ID"] = creds.access_key
408
+ credentials["AWS_SECRET_ACCESS_KEY"] = creds.secret_key
409
+
410
+ # Add session token if it exists (for temporary credentials)
411
+ if creds.token:
412
+ credentials["AWS_SESSION_TOKEN"] = creds.token
413
+
414
+ # Add region if available
415
+ if session.region_name:
416
+ credentials["AWS_DEFAULT_REGION"] = session.region_name
417
+
418
+ click.echo(
419
+ FeedbackManager.success(
420
+ message=f"✓ AWS credentials found and will be passed to Tinybird Local (region: {session.region_name or 'not set'})"
421
+ )
422
+ )
423
+ else:
424
+ click.echo(
425
+ FeedbackManager.warning(
426
+ message="△ No AWS credentials found. S3 operations will not work in Tinybird Local."
427
+ )
428
+ )
429
+ except Exception as e:
430
+ click.echo(
431
+ FeedbackManager.warning(
432
+ message=f"△ Error retrieving AWS credentials: {str(e)}. S3 operations will not work in Tinybird Local."
433
+ )
434
+ )
435
+
436
+ return credentials
@@ -5,7 +5,7 @@ from tinybird.tb.modules.config import CLIConfig
5
5
  from tinybird.tb.modules.feedback_manager import FeedbackManager
6
6
 
7
7
 
8
- @cli.command(name="logout", hidden=True)
8
+ @cli.command(name="logout")
9
9
  def logout() -> None:
10
10
  """
11
11
  Remove authentication from Tinybird.
@@ -13,7 +13,6 @@ from pathlib import Path
13
13
  from typing import Any, Dict, List, Optional, Tuple
14
14
 
15
15
  import click
16
- import requests
17
16
  import yaml
18
17
  from requests import Response
19
18
 
@@ -22,11 +21,11 @@ from tinybird.tb.client import TinyB
22
21
  from tinybird.tb.modules.build import process as build_project
23
22
  from tinybird.tb.modules.cli import cli
24
23
  from tinybird.tb.modules.config import CLIConfig
25
- from tinybird.tb.modules.exceptions import CLILocalException, CLITestException
24
+ from tinybird.tb.modules.exceptions import CLITestException
26
25
  from tinybird.tb.modules.feedback_manager import FeedbackManager
27
26
  from tinybird.tb.modules.llm import LLM
28
27
  from tinybird.tb.modules.llm_utils import extract_xml, parse_xml
29
- from tinybird.tb.modules.local_common import TB_LOCAL_ADDRESS, get_test_workspace_name
28
+ from tinybird.tb.modules.local_common import get_local_tokens, get_test_workspace_name
30
29
  from tinybird.tb.modules.project import Project
31
30
 
32
31
  yaml.SafeDumper.org_represent_str = yaml.SafeDumper.represent_str # type: ignore[attr-defined]
@@ -314,13 +313,7 @@ def get_pipe_path(name_or_filename: str, folder: str) -> Path:
314
313
 
315
314
  def cleanup_test_workspace(client: TinyB, path: str) -> None:
316
315
  user_client = deepcopy(client)
317
- try:
318
- # ruff: noqa: ASYNC210
319
- tokens = requests.get(f"{TB_LOCAL_ADDRESS}/tokens").json()
320
- except Exception:
321
- raise CLILocalException(
322
- FeedbackManager.error(message="Tinybird local is not running. Please run `tb local start` first.")
323
- )
316
+ tokens = get_local_tokens()
324
317
  try:
325
318
  user_token = tokens["user_token"]
326
319
  user_client.token = user_token
@@ -29,6 +29,7 @@ from tinybird.tb.modules.exceptions import CLIWorkspaceException
29
29
  from tinybird.tb.modules.feedback_manager import FeedbackManager
30
30
  from tinybird.tb.modules.local_common import (
31
31
  TB_LOCAL_ADDRESS,
32
+ TB_LOCAL_DEFAULT_WORKSPACE_NAME,
32
33
  get_local_tokens,
33
34
  )
34
35
 
@@ -183,7 +184,6 @@ async def create_workspace(
183
184
 
184
185
  @workspace.command(name="delete", short_help="Delete a workspace for your Tinybird user")
185
186
  @click.argument("workspace_name_or_id")
186
- @click.option("--user_token", is_flag=False, default=None, help="When passed, tb won't prompt asking for the token")
187
187
  @click.option(
188
188
  "--confirm_hard_delete",
189
189
  default=None,
@@ -194,14 +194,17 @@ async def create_workspace(
194
194
  @click.pass_context
195
195
  @coro
196
196
  async def delete_workspace(
197
- ctx: Context, workspace_name_or_id: str, user_token: Optional[str], confirm_hard_delete: Optional[str], yes: bool
197
+ ctx: Context, workspace_name_or_id: str, confirm_hard_delete: Optional[str], yes: bool
198
198
  ) -> None:
199
199
  """Delete a workspace where you are an admin."""
200
200
 
201
+ is_cloud = ctx.ensure_object(dict)["env"] == "cloud"
201
202
  config = CLIConfig.get_project_config()
202
- client = config.get_client()
203
-
204
- user_token = await get_user_token(config, user_token)
203
+ if is_cloud:
204
+ user_token = await get_user_token(config)
205
+ else:
206
+ user_token = get_local_tokens()["user_token"]
207
+ client: TinyB = ctx.ensure_object(dict)["client"]
205
208
 
206
209
  workspaces = (await client.user_workspaces(version="v1")).get("workspaces", [])
207
210
  workspace_to_delete = next(
@@ -216,6 +219,11 @@ async def delete_workspace(
216
219
  if not workspace_to_delete:
217
220
  raise CLIWorkspaceException(FeedbackManager.error_workspace(workspace=workspace_name_or_id))
218
221
 
222
+ if workspace_to_delete.get("name") == TB_LOCAL_DEFAULT_WORKSPACE_NAME:
223
+ raise CLIWorkspaceException(
224
+ FeedbackManager.error(message="You cannot delete your default Tinybird Local workspace.")
225
+ )
226
+
219
227
  if yes or click.confirm(
220
228
  FeedbackManager.warning_confirm_delete_workspace(workspace_name=workspace_to_delete.get("name"))
221
229
  ):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev162
3
+ Version: 0.0.1.dev169
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -11,22 +11,22 @@ tinybird/sql_toolset.py,sha256=KORVbNAUTfW1qo3U9oe7Z59xQ0QMsFhB0ji3HzY2JVo,15324
11
11
  tinybird/syncasync.py,sha256=IPnOx6lMbf9SNddN1eBtssg8vCLHMt76SuZ6YNYm-Yk,27761
12
12
  tinybird/tornado_template.py,sha256=jjNVDMnkYFWXflmT8KU_Ssbo5vR8KQq3EJMk5vYgXRw,41959
13
13
  tinybird/ch_utils/constants.py,sha256=aYvg2C_WxYWsnqPdZB1ZFoIr8ZY-XjUXYyHKE9Ansj0,3890
14
- tinybird/ch_utils/engine.py,sha256=BZuPM7MFS7vaEKK5tOMR2bwSAgJudPrJt27uVEwZmTY,40512
15
- tinybird/tb/__cli__.py,sha256=P908zolGwSNV24cl38bwFAC73NFhZWghV4jlWXEyxDc,247
14
+ tinybird/ch_utils/engine.py,sha256=X4tE9OrfaUy6kO9cqVEzyI9cDcmOF3IAssRRzsTsfEQ,40781
15
+ tinybird/tb/__cli__.py,sha256=ZH5ERxlrrIxkPa1I9WBJ4oyLFiovbeyQfBGE3VKBdds,247
16
16
  tinybird/tb/check_pypi.py,sha256=rW4QmDRbtgKdUUwJCnBkVjmTjZSZGN-XgZhx7vMkC0w,1009
17
17
  tinybird/tb/cli.py,sha256=u3eGOhX0MHkuT6tiwaZ0_3twqLmqKXDAOxF7yV_Nn9Q,1075
18
18
  tinybird/tb/client.py,sha256=CSBl_JRuioPyY0H8Ac96dJ9wQXDXfrvK2lwqlOxKGoY,55715
19
19
  tinybird/tb/config.py,sha256=jT9xndpeCY_g0HdB5qE2EquC0TFRRnkPnQFWZWd04jo,3998
20
20
  tinybird/tb/modules/build.py,sha256=zakH5812Lop-XHjGmDRdOPeofPtoeyb_2un_T6e50xw,19177
21
21
  tinybird/tb/modules/cicd.py,sha256=MnShTTJzKBYeajswF2jg7p7ZzupaeCgSriAN05MeEdg,7330
22
- tinybird/tb/modules/cli.py,sha256=jJ7vF05vppwqetaHiEyk_x2YFb6S53PS-PmYlNMRcTc,14274
23
- tinybird/tb/modules/common.py,sha256=_mNLBzC7zkveYXgJ02aMJ9L3LrxsAELx84GwEYdWNa0,82955
22
+ tinybird/tb/modules/cli.py,sha256=vADGfvo8lOp9-VIYD_d3KVIlcYfQk0ciJawthn-LFaw,14449
23
+ tinybird/tb/modules/common.py,sha256=12SMmfOlEwQZGBUR22KNVNdTXhEMc0BjvDJ7bbqhZf4,83122
24
24
  tinybird/tb/modules/config.py,sha256=ziqW_t_mRVvWOd85VoB4vKyvgMkEfpXDf9H4v38p2xc,11422
25
25
  tinybird/tb/modules/connection.py,sha256=7oOR7x4PhBcm1ETFFCH2YJ_3oeGXjAbmx1cnZX9_L70,9014
26
26
  tinybird/tb/modules/copy.py,sha256=2Mm4FWKehOG7CoOhiF1m9UZJgJn0W1_cMolqju8ONYg,5805
27
27
  tinybird/tb/modules/create.py,sha256=OHUvuHuvP0iecPPGI4eVOHOgR20qy7a_Sw7sbJKuG8g,17411
28
28
  tinybird/tb/modules/datasource.py,sha256=V314rkpdVxVMjsp5qcSCTqDlmp4Vu--qM07BoWh-aqs,17783
29
- tinybird/tb/modules/deployment.py,sha256=a6CZrYqAM-t6WxGKjgg16ZvncpZBta5gBq7YEBPBoQc,25811
29
+ tinybird/tb/modules/deployment.py,sha256=z2xK8EwKbx_a-rIOxIo3o_IQibcBASOaFfeWWosQFhs,26841
30
30
  tinybird/tb/modules/deprecations.py,sha256=rrszC1f_JJeJ8mUxGoCxckQTJFBCR8wREf4XXXN-PRc,4507
31
31
  tinybird/tb/modules/dev_server.py,sha256=57FCKuWpErwYUYgHspYDkLWEm9F4pbvVOtMrFXX1fVU,10129
32
32
  tinybird/tb/modules/endpoint.py,sha256=XySDt3pk66vxOZ0egUfz4bY8bEk3BjOXkv-L0OIJ3sc,12083
@@ -38,9 +38,9 @@ tinybird/tb/modules/job.py,sha256=n4dSSBgnA8NqD7srGahf2xRj6wxkmX9Vl0J-QJ_a2w0,29
38
38
  tinybird/tb/modules/llm.py,sha256=KfsCYmKeW1VQz0iDZhGKCRkQv_Y3kTHh6JuxvofOguE,1076
39
39
  tinybird/tb/modules/llm_utils.py,sha256=nS9r4FAElJw8yXtmdYrx-rtI2zXR8qXfi1QqUDCfxvg,3469
40
40
  tinybird/tb/modules/local.py,sha256=SUaGWH9TLDFFF9uCw4y7UW4NsKgnXG8uxTcxz1dbkCM,14230
41
- tinybird/tb/modules/local_common.py,sha256=LutxdEG6AR1k5PrUPsDWGVPrr3mA7AbWKhj4Hlewlic,4837
41
+ tinybird/tb/modules/local_common.py,sha256=msAZDNPPVenNyL9Dqfb0Z5uFC_1O809xdAi7j1iKmJA,17066
42
42
  tinybird/tb/modules/login.py,sha256=fmXPSdvJnKPv03chptGuu3_Fm6LhP6kUsUKhrmT8rJc,8269
43
- tinybird/tb/modules/logout.py,sha256=ULooy1cDBD02-r7voZmhV7udA0ML5tVuflJyShrh56Y,1022
43
+ tinybird/tb/modules/logout.py,sha256=sniI4JNxpTrVeRCp0oGJuQ3yRerG4hH5uz6oBmjv724,1009
44
44
  tinybird/tb/modules/materialization.py,sha256=QJX5kCPhhm6IXBO1JsalVfbQdypCe_eOUDZ_WHJZWS8,5478
45
45
  tinybird/tb/modules/mock.py,sha256=IyHweMUM6bUH8IhyiX2tTMpdVpTFUeAJ41lZ5P42-HQ,5303
46
46
  tinybird/tb/modules/open.py,sha256=OuctINN77oexpSjth9uoIZPCelKO4Li-yyVxeSnk1io,1371
@@ -51,16 +51,16 @@ tinybird/tb/modules/secret.py,sha256=WsqzxxLh9W_jkuHL2JofMXdIJy0lT5WEI-7bQSIDgAc
51
51
  tinybird/tb/modules/shell.py,sha256=Zd_4Ak_5tKVX-cw6B4ag36xZeEGHeh-jZpAsIXkoMoE,14116
52
52
  tinybird/tb/modules/table.py,sha256=4XrtjM-N0zfNtxVkbvLDQQazno1EPXnxTyo7llivfXk,11035
53
53
  tinybird/tb/modules/telemetry.py,sha256=X0p5AVkM8BNsK_Rhdcg4p2eIf6OHimHO_VLldBqHQ8o,11386
54
- tinybird/tb/modules/test.py,sha256=Yopg89cRwOQpgRzsb9nvu2Z-UR2as2vBjVa5PF3uiK0,13420
54
+ tinybird/tb/modules/test.py,sha256=891Br7sgRk88Zqqj4UQHWbdIK7aI7QY2wpAaBscPxRw,13134
55
55
  tinybird/tb/modules/token.py,sha256=2fmKwu10_M0pqs6YmJVeILR9ZQB0ejRAET86agASbKM,13488
56
56
  tinybird/tb/modules/watch.py,sha256=H1FieLTVGRqmZ0hR0vELbQJ9l0CThrFCgGCta-MPuAY,8883
57
- tinybird/tb/modules/workspace.py,sha256=v9o4ImrEOxLhCYhVGtJ-slek8DGOW0cJGn7F91DMmj4,10390
57
+ tinybird/tb/modules/workspace.py,sha256=-XUvL2PB5GcviJ8m30h-ZDc5kwJcm1wy1dreYa2l4Ck,10658
58
58
  tinybird/tb/modules/workspace_members.py,sha256=RYLpyPM1ECCasHRg3uvpckzXplX0_KgNFsSPZn_i6qk,8744
59
59
  tinybird/tb/modules/datafile/build.py,sha256=d_h3pRFDPFrDKGhpFx2iejY25GuB2k8yfNouj6s8caw,50973
60
60
  tinybird/tb/modules/datafile/build_common.py,sha256=LU24kAQmxDJIyoIapDaYG-SU3P4FrMG9UBf8m9PgVSI,4565
61
61
  tinybird/tb/modules/datafile/build_datasource.py,sha256=nXEQ0qHdq2ai7jJTv8H2d7eeDPBYzLn8VY7zMtOYb8M,17382
62
62
  tinybird/tb/modules/datafile/build_pipe.py,sha256=6Cwjf3BKEF3-oQ9PipsQfK-Z43nSwtA4qJAUoysI7Uc,11385
63
- tinybird/tb/modules/datafile/common.py,sha256=1xM8R2NVmk8uQpU910Iqp_YGM1SHYoXaLZgjBYIJl54,89771
63
+ tinybird/tb/modules/datafile/common.py,sha256=WOHlXXPLs1RkdPLXA7qLHOGGey7d1Kn8PPdOe-3zVtE,89720
64
64
  tinybird/tb/modules/datafile/diff.py,sha256=MTmj53RYjER4neLgWVjabn-FKVFgh8h8uYiBo55lFQg,6757
65
65
  tinybird/tb/modules/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1wnI,556
66
66
  tinybird/tb/modules/datafile/fixture.py,sha256=DrRWivcvo_1rn7LlVUnHcXccdgx9yVj63mzBkUwCzk8,1420
@@ -80,8 +80,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
80
80
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
81
81
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
82
82
  tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
83
- tinybird-0.0.1.dev162.dist-info/METADATA,sha256=MDkrGU1_u15k7sp6G7ZeL841SKRhXPq6tGYvPd5CfnY,1607
84
- tinybird-0.0.1.dev162.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
85
- tinybird-0.0.1.dev162.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
86
- tinybird-0.0.1.dev162.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
87
- tinybird-0.0.1.dev162.dist-info/RECORD,,
83
+ tinybird-0.0.1.dev169.dist-info/METADATA,sha256=PHcgQ16C8kZsxmtwhnhQMgwRGE5SHb0X8rLHosCD324,1607
84
+ tinybird-0.0.1.dev169.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
85
+ tinybird-0.0.1.dev169.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
86
+ tinybird-0.0.1.dev169.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
87
+ tinybird-0.0.1.dev169.dist-info/RECORD,,