tinybird 0.0.1.dev164__tar.gz → 0.0.1.dev166__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (111) hide show
  1. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/__cli__.py +2 -2
  3. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/common.py +6 -1
  4. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/deployment.py +12 -2
  5. tinybird-0.0.1.dev166/tinybird/tb/modules/local_common.py +373 -0
  6. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/test.py +3 -10
  7. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird.egg-info/PKG-INFO +1 -1
  8. tinybird-0.0.1.dev164/tinybird/tb/modules/local_common.py +0 -123
  9. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/setup.cfg +0 -0
  10. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/__cli__.py +0 -0
  11. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/ch_utils/constants.py +0 -0
  12. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/ch_utils/engine.py +0 -0
  13. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/check_pypi.py +0 -0
  14. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/client.py +0 -0
  15. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/config.py +0 -0
  16. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/connectors.py +0 -0
  17. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/context.py +0 -0
  18. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/datafile.py +0 -0
  19. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/datatypes.py +0 -0
  20. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/feedback_manager.py +0 -0
  21. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/git_settings.py +0 -0
  22. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/prompts.py +0 -0
  23. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/sql.py +0 -0
  24. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/sql_template.py +0 -0
  25. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/sql_template_fmt.py +0 -0
  26. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/sql_toolset.py +0 -0
  27. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/syncasync.py +0 -0
  28. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/check_pypi.py +0 -0
  29. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/cli.py +0 -0
  30. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/client.py +0 -0
  31. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/config.py +0 -0
  32. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/build.py +0 -0
  33. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/cicd.py +0 -0
  34. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/cli.py +0 -0
  35. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/config.py +0 -0
  36. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/connection.py +0 -0
  37. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/copy.py +0 -0
  38. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/create.py +0 -0
  39. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/build.py +0 -0
  40. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/build_common.py +0 -0
  41. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  42. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  43. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/common.py +0 -0
  44. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/diff.py +0 -0
  45. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  46. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/fixture.py +0 -0
  47. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/format_common.py +0 -0
  48. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  49. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  50. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  51. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  52. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  53. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/playground.py +0 -0
  54. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datafile/pull.py +0 -0
  55. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/datasource.py +0 -0
  56. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/deprecations.py +0 -0
  57. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/dev_server.py +0 -0
  58. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/endpoint.py +0 -0
  59. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/exceptions.py +0 -0
  60. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/feedback_manager.py +0 -0
  61. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/info.py +0 -0
  62. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/infra.py +0 -0
  63. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/job.py +0 -0
  64. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/llm.py +0 -0
  65. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/llm_utils.py +0 -0
  66. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/local.py +0 -0
  67. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/login.py +0 -0
  68. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/logout.py +0 -0
  69. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/materialization.py +0 -0
  70. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/mock.py +0 -0
  71. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/open.py +0 -0
  72. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/pipe.py +0 -0
  73. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/project.py +0 -0
  74. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/regions.py +0 -0
  75. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/secret.py +0 -0
  76. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/shell.py +0 -0
  77. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/table.py +0 -0
  78. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/telemetry.py +0 -0
  79. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  80. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  81. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/token.py +0 -0
  82. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/watch.py +0 -0
  83. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/workspace.py +0 -0
  84. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb/modules/workspace_members.py +0 -0
  85. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli.py +0 -0
  86. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/auth.py +0 -0
  87. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/branch.py +0 -0
  88. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/cicd.py +0 -0
  89. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/cli.py +0 -0
  90. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/common.py +0 -0
  91. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/config.py +0 -0
  92. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/connection.py +0 -0
  93. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/datasource.py +0 -0
  94. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/exceptions.py +0 -0
  95. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/fmt.py +0 -0
  96. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/job.py +0 -0
  97. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/pipe.py +0 -0
  98. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/regions.py +0 -0
  99. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/tag.py +0 -0
  100. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/telemetry.py +0 -0
  101. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/test.py +0 -0
  102. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  103. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  104. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/workspace.py +0 -0
  105. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  106. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird/tornado_template.py +0 -0
  107. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird.egg-info/SOURCES.txt +0 -0
  108. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird.egg-info/dependency_links.txt +0 -0
  109. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird.egg-info/entry_points.txt +0 -0
  110. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird.egg-info/requires.txt +0 -0
  111. {tinybird-0.0.1.dev164 → tinybird-0.0.1.dev166}/tinybird.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev164
3
+ Version: 0.0.1.dev166
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -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.dev164'
8
- __revision__ = 'f0254a2'
7
+ __version__ = '0.0.1.dev166'
8
+ __revision__ = 'dfe3726'
@@ -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:
@@ -576,7 +576,10 @@ def create_deployment(
576
576
  sys.exit(0)
577
577
  elif status == "failed":
578
578
  click.echo(FeedbackManager.error(message="Deployment failed"))
579
- sys_exit("deployment_error", "Deployment failed" + str(deployment.get("errors")))
579
+ sys_exit(
580
+ "deployment_error",
581
+ f"Deployment failed. Errors: {str(deployment.get('errors') + deployment.get('feedback'))}",
582
+ )
580
583
  else:
581
584
  click.echo(FeedbackManager.error(message=f"Unknown deployment result {status}"))
582
585
  except Exception as e:
@@ -606,11 +609,18 @@ def create_deployment(
606
609
  if auto:
607
610
  click.echo(FeedbackManager.error(message="Rolling back deployment"))
608
611
  discard_deployment(client.host, HEADERS, wait=wait)
609
- sys_exit("deployment_error", "Deployment failed: " + str(deployment.get("errors")))
612
+ sys_exit(
613
+ "deployment_error",
614
+ f"Deployment failed. Errors: {str(deployment.get('errors') + deployment.get('feedback'))}",
615
+ )
610
616
 
611
617
  if deployment.get("status") == "data_ready":
612
618
  break
613
619
 
620
+ if deployment.get("status") in ["deleting", "deleted"]:
621
+ click.echo(FeedbackManager.error(message="Deployment was deleted by another process"))
622
+ sys_exit("deployment_error", "Deployment was deleted by another process")
623
+
614
624
  time.sleep(5)
615
625
 
616
626
  click.echo(FeedbackManager.info(message="✓ Deployment is ready"))
@@ -0,0 +1,373 @@
1
+ import hashlib
2
+ import json
3
+ import logging
4
+ import os
5
+ import re
6
+ import subprocess
7
+ import time
8
+ from typing import Any, Dict, Optional
9
+
10
+ import boto3
11
+ import click
12
+ import requests
13
+
14
+ import docker
15
+ from docker.client import DockerClient
16
+ from docker.models.containers import Container
17
+ from tinybird.tb.client import AuthNoTokenException, TinyB
18
+ from tinybird.tb.modules.config import CLIConfig
19
+ from tinybird.tb.modules.exceptions import CLILocalException
20
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
21
+ from tinybird.tb.modules.telemetry import add_telemetry_event
22
+
23
+ TB_IMAGE_NAME = "tinybirdco/tinybird-local:latest"
24
+ TB_CONTAINER_NAME = "tinybird-local"
25
+ TB_LOCAL_PORT = int(os.getenv("TB_LOCAL_PORT", 7181))
26
+ TB_LOCAL_HOST = re.sub(r"^https?://", "", os.getenv("TB_LOCAL_HOST", "localhost"))
27
+ TB_LOCAL_ADDRESS = f"http://{TB_LOCAL_HOST}:{TB_LOCAL_PORT}"
28
+ TB_LOCAL_DEFAULT_WORKSPACE_NAME = "Tinybird_Local_Testing"
29
+
30
+
31
+ async def get_tinybird_local_client(
32
+ config_obj: Dict[str, Any], test: bool = False, staging: bool = False, silent: bool = False
33
+ ) -> TinyB:
34
+ """Get a Tinybird client connected to the local environment."""
35
+
36
+ config = await get_tinybird_local_config(config_obj, test=test, silent=silent)
37
+ return config.get_client(host=TB_LOCAL_ADDRESS, staging=staging)
38
+
39
+
40
+ async def get_tinybird_local_config(config_obj: Dict[str, Any], test: bool = False, silent: bool = False) -> CLIConfig:
41
+ """Craft a client config with a workspace name based on the path of the project files
42
+
43
+ It uses the tokens from tinybird local
44
+ """
45
+ path = config_obj.get("path")
46
+ config = CLIConfig.get_project_config()
47
+ tokens = get_local_tokens()
48
+ user_token = tokens["user_token"]
49
+ admin_token = tokens["admin_token"]
50
+ default_token = tokens["workspace_admin_token"]
51
+ # Create a new workspace if path is provided. This is used to isolate the build in a different workspace.
52
+ if path:
53
+ user_client = config.get_client(host=TB_LOCAL_ADDRESS, token=user_token)
54
+ if test:
55
+ ws_name = get_test_workspace_name(path)
56
+ else:
57
+ ws_name = config.get("name") or config_obj.get("name") or get_build_workspace_name(path)
58
+ if not ws_name:
59
+ raise AuthNoTokenException()
60
+
61
+ logging.debug(f"Workspace used for build: {ws_name}")
62
+
63
+ user_workspaces = requests.get(
64
+ f"{TB_LOCAL_ADDRESS}/v1/user/workspaces?with_organization=true&token={admin_token}"
65
+ ).json()
66
+ user_org_id = user_workspaces.get("organization_id", {})
67
+ local_workspaces = user_workspaces.get("workspaces", [])
68
+
69
+ ws = next((ws for ws in local_workspaces if ws["name"] == ws_name), None)
70
+
71
+ # If we are running a test, we need to delete the workspace if it already exists
72
+ if test and ws:
73
+ requests.delete(
74
+ f"{TB_LOCAL_ADDRESS}/v1/workspaces/{ws['id']}?token={user_token}&hard_delete_confirmation=yes"
75
+ )
76
+ ws = None
77
+
78
+ if not ws:
79
+ await user_client.create_workspace(ws_name, assign_to_organization_id=user_org_id, version="v1")
80
+ user_workspaces = requests.get(f"{TB_LOCAL_ADDRESS}/v1/user/workspaces?token={admin_token}").json()
81
+ ws = next((ws for ws in user_workspaces["workspaces"] if ws["name"] == ws_name), None)
82
+ if not ws:
83
+ raise AuthNoTokenException()
84
+
85
+ ws_token = ws["token"]
86
+ config.set_token(ws_token)
87
+ config.set_token_for_host(TB_LOCAL_ADDRESS, ws_token)
88
+ config.set_host(TB_LOCAL_ADDRESS)
89
+ else:
90
+ config.set_token(default_token)
91
+ config.set_token_for_host(TB_LOCAL_ADDRESS, default_token)
92
+
93
+ config.set_user_token(user_token)
94
+ return config
95
+
96
+
97
+ def get_build_workspace_name(path: str) -> str:
98
+ folder_hash = hashlib.sha256(path.encode()).hexdigest()
99
+ return f"Tinybird_Local_Build_{folder_hash}"
100
+
101
+
102
+ def get_test_workspace_name(path: str) -> str:
103
+ folder_hash = hashlib.sha256(path.encode()).hexdigest()
104
+ return f"Tinybird_Local_Test_{folder_hash}"
105
+
106
+
107
+ def get_local_tokens() -> Dict[str, str]:
108
+ try:
109
+ # ruff: noqa: ASYNC210
110
+ return requests.get(f"{TB_LOCAL_ADDRESS}/tokens").json()
111
+ except Exception:
112
+ try:
113
+ # Check if tinybird-local is running with docker, in case it's a config issue
114
+ output = subprocess.check_output(["docker", "ps"], text=True)
115
+ header_row = next((line for line in output.splitlines() if "CONTAINER ID" in line), "")
116
+ tb_local_row = next(
117
+ (line for line in output.splitlines() if TB_CONTAINER_NAME in line),
118
+ f"{TB_CONTAINER_NAME} not found in output",
119
+ )
120
+ add_telemetry_event(
121
+ "docker_debug",
122
+ data={
123
+ "docker_ps_output": header_row + tb_local_row,
124
+ },
125
+ )
126
+ except Exception:
127
+ pass
128
+ is_ci = (
129
+ os.getenv("GITHUB_ACTIONS")
130
+ or os.getenv("TRAVIS")
131
+ or os.getenv("CIRCLECI")
132
+ or os.getenv("GITLAB_CI")
133
+ or os.getenv("CI")
134
+ or os.getenv("TB_CI")
135
+ )
136
+ if not is_ci:
137
+ yes = click.confirm(
138
+ FeedbackManager.warning(message="Tinybird local is not running. Do you want to start it? [Y/n]"),
139
+ prompt_suffix="",
140
+ show_default=False,
141
+ default=True,
142
+ )
143
+ if yes:
144
+ click.echo(FeedbackManager.highlight(message="» Starting Tinybird Local..."))
145
+ docker_client = get_docker_client()
146
+ start_tinybird_local(docker_client, False)
147
+ click.echo(FeedbackManager.success(message="✓ Tinybird Local is ready!"))
148
+ return get_local_tokens()
149
+ raise CLILocalException(
150
+ FeedbackManager.error(message="Tinybird local is not running. Please run `tb local start` first.")
151
+ )
152
+
153
+
154
+ def start_tinybird_local(
155
+ docker_client: DockerClient,
156
+ use_aws_creds: bool,
157
+ ) -> None:
158
+ """Start the Tinybird container."""
159
+ pull_show_prompt = False
160
+ pull_required = False
161
+ try:
162
+ local_image = docker_client.images.get(TB_IMAGE_NAME)
163
+ local_image_id = local_image.attrs["RepoDigests"][0].split("@")[1]
164
+ remote_image = docker_client.images.get_registry_data(TB_IMAGE_NAME)
165
+ pull_show_prompt = local_image_id != remote_image.id
166
+ except Exception:
167
+ pull_show_prompt = False
168
+ pull_required = True
169
+
170
+ if pull_show_prompt and click.confirm(
171
+ FeedbackManager.warning(message="△ New version detected, download? [y/N]:"),
172
+ show_default=False,
173
+ prompt_suffix="",
174
+ ):
175
+ click.echo(FeedbackManager.info(message="* Downloading latest version of Tinybird Local..."))
176
+ pull_required = True
177
+
178
+ if pull_required:
179
+ docker_client.images.pull(TB_IMAGE_NAME, platform="linux/amd64")
180
+
181
+ environment = get_use_aws_creds() if use_aws_creds else {}
182
+
183
+ container = get_existing_container_with_matching_env(docker_client, TB_CONTAINER_NAME, environment)
184
+
185
+ if container and not pull_required:
186
+ # Container `start` is idempotent. It's safe to call it even if the container is already running.
187
+ container.start()
188
+ else:
189
+ if container:
190
+ container.remove(force=True)
191
+
192
+ container = docker_client.containers.run(
193
+ TB_IMAGE_NAME,
194
+ name=TB_CONTAINER_NAME,
195
+ detach=True,
196
+ ports={"7181/tcp": TB_LOCAL_PORT},
197
+ remove=False,
198
+ platform="linux/amd64",
199
+ environment=environment,
200
+ )
201
+
202
+ click.echo(FeedbackManager.info(message="* Waiting for Tinybird Local to be ready..."))
203
+ while True:
204
+ container.reload() # Refresh container attributes
205
+ health = container.attrs.get("State", {}).get("Health", {}).get("Status")
206
+ if health == "healthy":
207
+ break
208
+ if health == "unhealthy":
209
+ raise CLILocalException(
210
+ FeedbackManager.error(
211
+ message="Tinybird Local is unhealthy. Try running `tb local restart` in a few seconds."
212
+ )
213
+ )
214
+
215
+ time.sleep(5)
216
+
217
+ # Remove tinybird-local dangling images to avoid running out of disk space
218
+ images = docker_client.images.list(name=re.sub(r":.*$", "", TB_IMAGE_NAME), all=True, filters={"dangling": True})
219
+ for image in images:
220
+ image.remove(force=True)
221
+
222
+
223
+ def get_existing_container_with_matching_env(
224
+ docker_client: DockerClient, container_name: str, required_env: dict[str, str]
225
+ ) -> Optional[Container]:
226
+ """
227
+ Checks if a container with the given name exists and has matching environment variables.
228
+ If it exists but environment doesn't match, it returns None.
229
+
230
+ Args:
231
+ docker_client: The Docker client instance
232
+ container_name: The name of the container to check
233
+ required_env: Dictionary of environment variables that must be present
234
+
235
+ Returns:
236
+ The container if it exists with matching environment, None otherwise
237
+ """
238
+ container = None
239
+ containers = docker_client.containers.list(all=True, filters={"name": container_name})
240
+ if containers:
241
+ container = containers[0]
242
+
243
+ if container and required_env:
244
+ container_info = container.attrs
245
+ container_env = container_info.get("Config", {}).get("Env", [])
246
+ env_missing = False
247
+ for key, value in required_env.items():
248
+ env_var = f"{key}={value}"
249
+ if env_var not in container_env:
250
+ env_missing = True
251
+ break
252
+
253
+ if env_missing:
254
+ container.remove(force=True)
255
+ container = None
256
+
257
+ return container
258
+
259
+
260
+ def get_docker_client() -> DockerClient:
261
+ """Check if Docker is installed and running."""
262
+ try:
263
+ docker_host = os.getenv("DOCKER_HOST")
264
+ if not docker_host:
265
+ # Try to get docker host from docker context
266
+ try:
267
+ try:
268
+ output = subprocess.check_output(["docker", "context", "inspect"], text=True)
269
+ except Exception as e:
270
+ add_telemetry_event(
271
+ "docker_error",
272
+ error=f"docker_context_inspect_error: {str(e)}",
273
+ )
274
+ raise e
275
+ try:
276
+ context = json.loads(output)
277
+ except Exception as e:
278
+ add_telemetry_event(
279
+ "docker_error",
280
+ error=f"docker_context_inspect_parse_output_error: {str(e)}",
281
+ data={
282
+ "docker_context_inspect_output": output,
283
+ },
284
+ )
285
+ raise e
286
+ if context and len(context) > 0:
287
+ try:
288
+ docker_host = context[0].get("Endpoints", {}).get("docker", {}).get("Host")
289
+ if docker_host:
290
+ os.environ["DOCKER_HOST"] = docker_host
291
+ except Exception as e:
292
+ add_telemetry_event(
293
+ "docker_error",
294
+ error=f"docker_context_parse_host_error: {str(e)}",
295
+ data={
296
+ "context": json.dumps(context),
297
+ },
298
+ )
299
+ raise e
300
+ except Exception:
301
+ pass
302
+ try:
303
+ client = docker.from_env() # type: ignore
304
+ except Exception as e:
305
+ add_telemetry_event(
306
+ "docker_error",
307
+ error=f"docker_get_client_from_env_error: {str(e)}",
308
+ )
309
+ raise e
310
+ try:
311
+ client.ping()
312
+ except Exception as e:
313
+ client_dict_non_sensitive = {k: v for k, v in client.api.__dict__.items() if "auth" not in k}
314
+ add_telemetry_event(
315
+ "docker_error",
316
+ error=f"docker_ping_error: {str(e)}",
317
+ data={
318
+ "client": repr(client_dict_non_sensitive),
319
+ },
320
+ )
321
+ raise e
322
+ return client
323
+ except Exception:
324
+ raise CLILocalException(
325
+ FeedbackManager.error(
326
+ message=(
327
+ f"No container runtime is running. Make sure a Docker-compatible runtime is installed and running. "
328
+ f"Trying to connect to Docker-compatible runtime at {docker_host}\n\n"
329
+ "If you're using a custom location, please provide it using the DOCKER_HOST environment variable."
330
+ )
331
+ )
332
+ )
333
+
334
+
335
+ def get_use_aws_creds() -> dict[str, str]:
336
+ credentials: dict[str, str] = {}
337
+ try:
338
+ # Get the boto3 session and credentials
339
+ session = boto3.Session()
340
+ creds = session.get_credentials()
341
+
342
+ if creds:
343
+ # Create environment variables for the container based on boto credentials
344
+ credentials["AWS_ACCESS_KEY_ID"] = creds.access_key
345
+ credentials["AWS_SECRET_ACCESS_KEY"] = creds.secret_key
346
+
347
+ # Add session token if it exists (for temporary credentials)
348
+ if creds.token:
349
+ credentials["AWS_SESSION_TOKEN"] = creds.token
350
+
351
+ # Add region if available
352
+ if session.region_name:
353
+ credentials["AWS_DEFAULT_REGION"] = session.region_name
354
+
355
+ click.echo(
356
+ FeedbackManager.success(
357
+ message=f"✓ AWS credentials found and will be passed to Tinybird Local (region: {session.region_name or 'not set'})"
358
+ )
359
+ )
360
+ else:
361
+ click.echo(
362
+ FeedbackManager.warning(
363
+ message="△ No AWS credentials found. S3 operations will not work in Tinybird Local."
364
+ )
365
+ )
366
+ except Exception as e:
367
+ click.echo(
368
+ FeedbackManager.warning(
369
+ message=f"△ Error retrieving AWS credentials: {str(e)}. S3 operations will not work in Tinybird Local."
370
+ )
371
+ )
372
+
373
+ return credentials
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev164
3
+ Version: 0.0.1.dev166
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -1,123 +0,0 @@
1
- import hashlib
2
- import logging
3
- import os
4
- import re
5
- import subprocess
6
- from typing import Any, Dict
7
-
8
- import requests
9
-
10
- from tinybird.tb.client import AuthNoTokenException, TinyB
11
- from tinybird.tb.modules.config import CLIConfig
12
- from tinybird.tb.modules.exceptions import CLILocalException
13
- from tinybird.tb.modules.feedback_manager import FeedbackManager
14
- from tinybird.tb.modules.telemetry import add_telemetry_event
15
-
16
- TB_IMAGE_NAME = "tinybirdco/tinybird-local:latest"
17
- TB_CONTAINER_NAME = "tinybird-local"
18
- TB_LOCAL_PORT = int(os.getenv("TB_LOCAL_PORT", 7181))
19
- TB_LOCAL_HOST = re.sub(r"^https?://", "", os.getenv("TB_LOCAL_HOST", "localhost"))
20
- TB_LOCAL_ADDRESS = f"http://{TB_LOCAL_HOST}:{TB_LOCAL_PORT}"
21
- TB_LOCAL_DEFAULT_WORKSPACE_NAME = "Tinybird_Local_Testing"
22
-
23
-
24
- async def get_tinybird_local_client(
25
- config_obj: Dict[str, Any], test: bool = False, staging: bool = False, silent: bool = False
26
- ) -> TinyB:
27
- """Get a Tinybird client connected to the local environment."""
28
-
29
- config = await get_tinybird_local_config(config_obj, test=test, silent=silent)
30
- return config.get_client(host=TB_LOCAL_ADDRESS, staging=staging)
31
-
32
-
33
- async def get_tinybird_local_config(config_obj: Dict[str, Any], test: bool = False, silent: bool = False) -> CLIConfig:
34
- """Craft a client config with a workspace name based on the path of the project files
35
-
36
- It uses the tokens from tinybird local
37
- """
38
- path = config_obj.get("path")
39
- config = CLIConfig.get_project_config()
40
- tokens = get_local_tokens()
41
- user_token = tokens["user_token"]
42
- admin_token = tokens["admin_token"]
43
- default_token = tokens["workspace_admin_token"]
44
- # Create a new workspace if path is provided. This is used to isolate the build in a different workspace.
45
- if path:
46
- user_client = config.get_client(host=TB_LOCAL_ADDRESS, token=user_token)
47
- if test:
48
- ws_name = get_test_workspace_name(path)
49
- else:
50
- ws_name = config.get("name") or config_obj.get("name") or get_build_workspace_name(path)
51
- if not ws_name:
52
- raise AuthNoTokenException()
53
-
54
- logging.debug(f"Workspace used for build: {ws_name}")
55
-
56
- user_workspaces = requests.get(
57
- f"{TB_LOCAL_ADDRESS}/v1/user/workspaces?with_organization=true&token={admin_token}"
58
- ).json()
59
- user_org_id = user_workspaces.get("organization_id", {})
60
- local_workspaces = user_workspaces.get("workspaces", [])
61
-
62
- ws = next((ws for ws in local_workspaces if ws["name"] == ws_name), None)
63
-
64
- # If we are running a test, we need to delete the workspace if it already exists
65
- if test and ws:
66
- requests.delete(
67
- f"{TB_LOCAL_ADDRESS}/v1/workspaces/{ws['id']}?token={user_token}&hard_delete_confirmation=yes"
68
- )
69
- ws = None
70
-
71
- if not ws:
72
- await user_client.create_workspace(ws_name, assign_to_organization_id=user_org_id, version="v1")
73
- user_workspaces = requests.get(f"{TB_LOCAL_ADDRESS}/v1/user/workspaces?token={admin_token}").json()
74
- ws = next((ws for ws in user_workspaces["workspaces"] if ws["name"] == ws_name), None)
75
- if not ws:
76
- raise AuthNoTokenException()
77
-
78
- ws_token = ws["token"]
79
- config.set_token(ws_token)
80
- config.set_token_for_host(TB_LOCAL_ADDRESS, ws_token)
81
- config.set_host(TB_LOCAL_ADDRESS)
82
- else:
83
- config.set_token(default_token)
84
- config.set_token_for_host(TB_LOCAL_ADDRESS, default_token)
85
-
86
- config.set_user_token(user_token)
87
- return config
88
-
89
-
90
- def get_build_workspace_name(path: str) -> str:
91
- folder_hash = hashlib.sha256(path.encode()).hexdigest()
92
- return f"Tinybird_Local_Build_{folder_hash}"
93
-
94
-
95
- def get_test_workspace_name(path: str) -> str:
96
- folder_hash = hashlib.sha256(path.encode()).hexdigest()
97
- return f"Tinybird_Local_Test_{folder_hash}"
98
-
99
-
100
- def get_local_tokens() -> Dict[str, str]:
101
- try:
102
- # ruff: noqa: ASYNC210
103
- return requests.get(f"{TB_LOCAL_ADDRESS}/tokens").json()
104
- except Exception:
105
- try:
106
- # Check if tinybird-local is running with docker, in case it's a config issue
107
- output = subprocess.check_output(["docker", "ps"], text=True)
108
- header_row = next((line for line in output.splitlines() if "CONTAINER ID" in line), "")
109
- tb_local_row = next(
110
- (line for line in output.splitlines() if TB_CONTAINER_NAME in line),
111
- f"{TB_CONTAINER_NAME} not found in output",
112
- )
113
- add_telemetry_event(
114
- "docker_debug",
115
- data={
116
- "docker_ps_output": header_row + tb_local_row,
117
- },
118
- )
119
- except Exception:
120
- pass
121
- raise CLILocalException(
122
- FeedbackManager.error(message="Tinybird local is not running. Please run `tb local start` first.")
123
- )