tinybird 0.0.1.dev306__py3-none-any.whl → 1.0.5__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.
- tinybird/datafile/common.py +4 -1
- tinybird/feedback_manager.py +3 -0
- tinybird/service_datasources.py +57 -8
- tinybird/sql_template.py +1 -1
- tinybird/sql_template_fmt.py +14 -4
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/cli.py +1 -0
- tinybird/tb/client.py +104 -22
- tinybird/tb/modules/agent/tools/execute_query.py +1 -1
- tinybird/tb/modules/agent/tools/request_endpoint.py +1 -1
- tinybird/tb/modules/branch.py +150 -0
- tinybird/tb/modules/build.py +51 -10
- tinybird/tb/modules/build_common.py +4 -2
- tinybird/tb/modules/cli.py +32 -10
- tinybird/tb/modules/common.py +161 -134
- tinybird/tb/modules/connection.py +125 -194
- tinybird/tb/modules/connection_kafka.py +382 -0
- tinybird/tb/modules/copy.py +3 -1
- tinybird/tb/modules/create.py +11 -0
- tinybird/tb/modules/datafile/build.py +1 -1
- tinybird/tb/modules/datafile/format_pipe.py +44 -5
- tinybird/tb/modules/datafile/playground.py +1 -1
- tinybird/tb/modules/datasource.py +475 -324
- tinybird/tb/modules/deployment.py +2 -0
- tinybird/tb/modules/deployment_common.py +81 -43
- tinybird/tb/modules/deprecations.py +4 -4
- tinybird/tb/modules/dev_server.py +33 -12
- tinybird/tb/modules/info.py +50 -7
- tinybird/tb/modules/job_common.py +15 -0
- tinybird/tb/modules/local.py +91 -21
- tinybird/tb/modules/local_common.py +320 -13
- tinybird/tb/modules/local_logs.py +209 -0
- tinybird/tb/modules/login.py +3 -2
- tinybird/tb/modules/login_common.py +252 -9
- tinybird/tb/modules/open.py +10 -5
- tinybird/tb/modules/project.py +14 -5
- tinybird/tb/modules/shell.py +14 -6
- tinybird/tb/modules/sink.py +3 -1
- tinybird/tb/modules/telemetry.py +7 -3
- tinybird/tb_cli_modules/telemetry.py +1 -1
- {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/METADATA +29 -4
- {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/RECORD +45 -41
- {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/WHEEL +1 -1
- {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/top_level.txt +0 -0
|
@@ -382,6 +382,7 @@ def create_deployment_cmd(
|
|
|
382
382
|
template: Optional[str] = None,
|
|
383
383
|
verbose: bool = False,
|
|
384
384
|
) -> None:
|
|
385
|
+
output = ctx.ensure_object(dict)["output"]
|
|
385
386
|
project: Project = ctx.ensure_object(dict)["project"]
|
|
386
387
|
if template:
|
|
387
388
|
if project.get_project_files():
|
|
@@ -416,6 +417,7 @@ def create_deployment_cmd(
|
|
|
416
417
|
check,
|
|
417
418
|
allow_destructive_operations,
|
|
418
419
|
ingest_hint=not is_web_analytics_starter_kit,
|
|
420
|
+
output=output,
|
|
419
421
|
)
|
|
420
422
|
show_web_analytics_starter_kit_hints(client, is_web_analytics_starter_kit)
|
|
421
423
|
|
|
@@ -10,11 +10,13 @@ import requests
|
|
|
10
10
|
|
|
11
11
|
from tinybird.tb.client import TinyB
|
|
12
12
|
from tinybird.tb.modules.common import (
|
|
13
|
+
echo_json,
|
|
13
14
|
echo_safe_humanfriendly_tables_format_smart_table,
|
|
14
15
|
get_display_cloud_host,
|
|
15
16
|
sys_exit,
|
|
16
17
|
)
|
|
17
18
|
from tinybird.tb.modules.feedback_manager import FeedbackManager, bcolors
|
|
19
|
+
from tinybird.tb.modules.job_common import echo_job_url
|
|
18
20
|
from tinybird.tb.modules.project import Project
|
|
19
21
|
|
|
20
22
|
|
|
@@ -59,9 +61,14 @@ def api_post(
|
|
|
59
61
|
params: Optional[dict] = None,
|
|
60
62
|
) -> dict:
|
|
61
63
|
r = requests.post(url, headers=headers, files=files, params=params)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
try:
|
|
65
|
+
if r.status_code < 300:
|
|
66
|
+
logging.debug(json.dumps(r.json(), indent=2))
|
|
67
|
+
return r.json()
|
|
68
|
+
except json.JSONDecodeError:
|
|
69
|
+
message = "Error parsing response from API"
|
|
70
|
+
click.echo(FeedbackManager.error(message=message))
|
|
71
|
+
sys_exit("deployment_error", message)
|
|
65
72
|
|
|
66
73
|
# Try to parse and print the error from the response
|
|
67
74
|
try:
|
|
@@ -152,8 +159,6 @@ def promote_deployment(host: Optional[str], headers: dict, wait: bool, ingest_hi
|
|
|
152
159
|
)
|
|
153
160
|
|
|
154
161
|
|
|
155
|
-
# TODO(eclbg): This logic should be in the server, and there should be a dedicated endpoint for discarding a
|
|
156
|
-
# deployment
|
|
157
162
|
def discard_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
158
163
|
TINYBIRD_API_URL = f"{host}/v1/deployments"
|
|
159
164
|
result = api_fetch(TINYBIRD_API_URL, headers=headers)
|
|
@@ -164,29 +169,25 @@ def discard_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
|
164
169
|
return
|
|
165
170
|
|
|
166
171
|
if len(deployments) < 2:
|
|
167
|
-
click.echo(FeedbackManager.error(message="Only one deployment found"))
|
|
172
|
+
click.echo(FeedbackManager.error(message="Only one deployment found. Cannot discard the only deployment."))
|
|
168
173
|
return
|
|
169
174
|
|
|
170
|
-
|
|
175
|
+
current_deployment, deployment_to_discard = deployments[0], deployments[1]
|
|
171
176
|
|
|
172
|
-
|
|
177
|
+
# NOTE(eclbg): we never get here. We wrote this code when we though we'd enable promoting back and forth between
|
|
178
|
+
# staging and live, but the current CLI commands don't allow getting in that state
|
|
179
|
+
if current_deployment.get("status") != "data_ready":
|
|
173
180
|
click.echo(FeedbackManager.error(message="Previous deployment is not ready"))
|
|
174
|
-
deploy_errors =
|
|
181
|
+
deploy_errors = current_deployment.get("errors", [])
|
|
175
182
|
for deploy_error in deploy_errors:
|
|
176
183
|
click.echo(FeedbackManager.error(message=f"* {deploy_error}"))
|
|
177
184
|
return
|
|
178
185
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
click.echo(FeedbackManager.success(message="Promoting previous deployment"))
|
|
186
|
+
to_discard_status = deployment_to_discard.get("status")
|
|
187
|
+
verb = "Canceling" if to_discard_status in {"calculating", "creating_schema", "schema_ready"} else "Removing"
|
|
188
|
+
click.echo(FeedbackManager.success(message=f"{verb} deployment {deployment_to_discard['id']}"))
|
|
183
189
|
|
|
184
|
-
|
|
185
|
-
result = api_post(TINYBIRD_API_URL, headers=headers)
|
|
186
|
-
|
|
187
|
-
click.echo(FeedbackManager.success(message="Removing current deployment"))
|
|
188
|
-
|
|
189
|
-
TINYBIRD_API_URL = f"{host}/v1/deployments/{current_deployment.get('id')}"
|
|
190
|
+
TINYBIRD_API_URL = f"{host}/v1/deployments/{deployment_to_discard.get('id')}"
|
|
190
191
|
r = requests.delete(TINYBIRD_API_URL, headers=headers)
|
|
191
192
|
result = r.json()
|
|
192
193
|
logging.debug(json.dumps(result, indent=2))
|
|
@@ -194,15 +195,16 @@ def discard_deployment(host: Optional[str], headers: dict, wait: bool) -> None:
|
|
|
194
195
|
click.echo(FeedbackManager.error(message=result.get("error")))
|
|
195
196
|
sys_exit("deployment_error", result.get("error", "Unknown error"))
|
|
196
197
|
|
|
198
|
+
deployment_to_discard = deployments[1]
|
|
197
199
|
click.echo(FeedbackManager.success(message="Discard process successfully started"))
|
|
198
200
|
|
|
199
201
|
if wait:
|
|
200
202
|
while True:
|
|
201
|
-
TINYBIRD_API_URL = f"{host}/v1/deployments/{
|
|
203
|
+
TINYBIRD_API_URL = f"{host}/v1/deployments/{deployment_to_discard.get('id')}"
|
|
202
204
|
result = api_fetch(TINYBIRD_API_URL, headers)
|
|
203
205
|
|
|
204
|
-
|
|
205
|
-
if
|
|
206
|
+
deployment_to_discard = result.get("deployment")
|
|
207
|
+
if deployment_to_discard and deployment_to_discard.get("status") == "deleted":
|
|
206
208
|
click.echo(FeedbackManager.success(message="Discard process successfully completed"))
|
|
207
209
|
break
|
|
208
210
|
time.sleep(5)
|
|
@@ -218,6 +220,8 @@ def create_deployment(
|
|
|
218
220
|
check: Optional[bool] = None,
|
|
219
221
|
allow_destructive_operations: Optional[bool] = None,
|
|
220
222
|
ingest_hint: Optional[bool] = True,
|
|
223
|
+
output: Optional[str] = "human",
|
|
224
|
+
env: Optional[str] = "cloud",
|
|
221
225
|
) -> None:
|
|
222
226
|
# TODO: This code is duplicated in build_server.py
|
|
223
227
|
# Should be refactored to be shared
|
|
@@ -269,7 +273,10 @@ def create_deployment(
|
|
|
269
273
|
|
|
270
274
|
result = api_post(TINYBIRD_API_URL, headers=HEADERS, files=files, params=params)
|
|
271
275
|
|
|
272
|
-
print_changes(result, project)
|
|
276
|
+
print_changes(result, project, output)
|
|
277
|
+
|
|
278
|
+
if output == "json" and check:
|
|
279
|
+
echo_json(result.get("deployment", {}), 8)
|
|
273
280
|
|
|
274
281
|
deployment = result.get("deployment", {})
|
|
275
282
|
feedback = deployment.get("feedback", [])
|
|
@@ -315,13 +322,30 @@ def create_deployment(
|
|
|
315
322
|
)
|
|
316
323
|
|
|
317
324
|
status = result.get("result")
|
|
318
|
-
|
|
319
|
-
|
|
325
|
+
host = get_display_cloud_host(client.host)
|
|
326
|
+
if status in ["success", "failed"]:
|
|
320
327
|
click.echo(
|
|
321
|
-
FeedbackManager.
|
|
328
|
+
FeedbackManager.gray(message="Deployment URL: ")
|
|
322
329
|
+ f"{bcolors.UNDERLINE}{host}/{config.get('name')}/deployments/{deployment.get('id')}{bcolors.ENDC}"
|
|
323
330
|
)
|
|
331
|
+
jobs = client.jobs()
|
|
332
|
+
deployment_job = next(
|
|
333
|
+
(
|
|
334
|
+
job
|
|
335
|
+
for job in jobs
|
|
336
|
+
if job.get("kind") == "deployment" and job.get("deployment_id") == deployment.get("id")
|
|
337
|
+
),
|
|
338
|
+
None,
|
|
339
|
+
)
|
|
340
|
+
if deployment_job:
|
|
341
|
+
echo_job_url(
|
|
342
|
+
token=client.token,
|
|
343
|
+
host=client.host,
|
|
344
|
+
workspace_name=config.get("name") or "",
|
|
345
|
+
job_url=deployment_job.get("job_url") or "",
|
|
346
|
+
)
|
|
324
347
|
|
|
348
|
+
if status == "success":
|
|
325
349
|
if wait:
|
|
326
350
|
click.echo(FeedbackManager.info(message="\n* Deployment submitted"))
|
|
327
351
|
else:
|
|
@@ -354,26 +378,31 @@ def create_deployment(
|
|
|
354
378
|
sys_exit("deployment_error", "Error parsing deployment from response")
|
|
355
379
|
return
|
|
356
380
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
for deploy_error in deploy_errors:
|
|
361
|
-
click.echo(FeedbackManager.error(message=f"* {deploy_error}"))
|
|
381
|
+
status = deployment.get("status")
|
|
382
|
+
errors = deployment.get("errors")
|
|
383
|
+
feedback = deployment.get("feedback")
|
|
362
384
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
sys_exit(
|
|
367
|
-
"deployment_error",
|
|
368
|
-
f"Deployment failed. Errors: {str(deployment.get('errors') + deployment.get('feedback', []))}",
|
|
369
|
-
)
|
|
385
|
+
if status == "failed":
|
|
386
|
+
# Just wait until we poll again and see deleting or deleted to report errors
|
|
387
|
+
pass
|
|
370
388
|
|
|
371
|
-
if
|
|
389
|
+
if status == "data_ready":
|
|
372
390
|
break
|
|
373
391
|
|
|
374
|
-
if
|
|
375
|
-
|
|
376
|
-
|
|
392
|
+
if status in ["deleting", "deleted"]:
|
|
393
|
+
errors = deployment.get("errors")
|
|
394
|
+
if errors:
|
|
395
|
+
verb = "is being" if status == "deleting" else "was"
|
|
396
|
+
click.echo(
|
|
397
|
+
FeedbackManager.error(
|
|
398
|
+
message=f"Deployment failed and {verb} deleted automatically. Deployment errors:"
|
|
399
|
+
)
|
|
400
|
+
)
|
|
401
|
+
for error in errors:
|
|
402
|
+
click.echo(FeedbackManager.error(message=f"* {error}"))
|
|
403
|
+
sys_exit(
|
|
404
|
+
"deployment_error", f"Deployment deleted after failure. Errors: {str(errors + (feedback or []))}"
|
|
405
|
+
)
|
|
377
406
|
|
|
378
407
|
time.sleep(5)
|
|
379
408
|
|
|
@@ -381,6 +410,15 @@ def create_deployment(
|
|
|
381
410
|
|
|
382
411
|
if auto:
|
|
383
412
|
promote_deployment(client.host, HEADERS, wait=wait, ingest_hint=ingest_hint)
|
|
413
|
+
# Fetch the final deployment state after promotion for JSON output
|
|
414
|
+
if output == "json":
|
|
415
|
+
url = f"{client.host}/v1/deployments/{deployment.get('id')}"
|
|
416
|
+
res = api_fetch(url, HEADERS)
|
|
417
|
+
deployment = res.get("deployment")
|
|
418
|
+
|
|
419
|
+
# Output JSON at the appropriate time based on the execution path
|
|
420
|
+
if output == "json" and deployment:
|
|
421
|
+
echo_json(deployment, 8)
|
|
384
422
|
|
|
385
423
|
|
|
386
424
|
def _build_data_movement_message(kind: str, source_mv_name: Optional[str]) -> str:
|
|
@@ -392,7 +430,7 @@ def _build_data_movement_message(kind: str, source_mv_name: Optional[str]) -> st
|
|
|
392
430
|
return ""
|
|
393
431
|
|
|
394
432
|
|
|
395
|
-
def print_changes(result: dict, project: Project) -> None:
|
|
433
|
+
def print_changes(result: dict, project: Project, output: Optional[str] = "human") -> None:
|
|
396
434
|
deployment = result.get("deployment", {})
|
|
397
435
|
resources_columns = ["status", "name", "type", "path"]
|
|
398
436
|
resources: list[list[Union[str, None]]] = []
|
|
@@ -29,20 +29,20 @@ def auth(args) -> None:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
@cli.command(
|
|
32
|
-
name="
|
|
32
|
+
name="environment",
|
|
33
33
|
context_settings=dict(
|
|
34
34
|
ignore_unknown_options=True,
|
|
35
35
|
),
|
|
36
36
|
hidden=True,
|
|
37
37
|
)
|
|
38
38
|
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
39
|
-
def
|
|
39
|
+
def environment(args) -> None:
|
|
40
40
|
"""
|
|
41
|
-
`tb
|
|
41
|
+
`tb environment` has been renamed to `tb branch`.
|
|
42
42
|
"""
|
|
43
43
|
click.echo(
|
|
44
44
|
FeedbackManager.warning(
|
|
45
|
-
message="
|
|
45
|
+
message=f"`tb environment` has been renamed to `tb branch`. Please use `tb branch {args[0]}` instead."
|
|
46
46
|
)
|
|
47
47
|
)
|
|
48
48
|
click.echo(
|
|
@@ -6,8 +6,10 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Callable, Optional
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
|
+
import requests
|
|
9
10
|
|
|
10
11
|
from tinybird.tb.client import TinyB
|
|
12
|
+
from tinybird.tb.config import get_display_cloud_host
|
|
11
13
|
from tinybird.tb.modules.common import sys_exit
|
|
12
14
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
13
15
|
from tinybird.tb.modules.local_common import TB_LOCAL_PORT
|
|
@@ -33,6 +35,7 @@ class DevServer(http.server.HTTPServer):
|
|
|
33
35
|
project: Project,
|
|
34
36
|
build_status: BuildStatus,
|
|
35
37
|
tb_client: TinyB,
|
|
38
|
+
branch: Optional[str] = None,
|
|
36
39
|
):
|
|
37
40
|
port = 49161
|
|
38
41
|
self.project = project
|
|
@@ -40,19 +43,32 @@ class DevServer(http.server.HTTPServer):
|
|
|
40
43
|
self.process = process
|
|
41
44
|
self.build_status = build_status
|
|
42
45
|
self.port = port
|
|
46
|
+
base_ui_url = get_display_cloud_host(tb_client.host)
|
|
47
|
+
if branch:
|
|
48
|
+
ui_url = f"{base_ui_url}/{project.workspace_name}~{branch}/project"
|
|
49
|
+
else:
|
|
50
|
+
ui_url = f"{base_ui_url}/{project.workspace_name}/project"
|
|
43
51
|
|
|
44
52
|
try:
|
|
45
53
|
super().__init__(("", port), DevHandler)
|
|
46
|
-
click.echo(FeedbackManager.
|
|
47
|
-
click.echo(
|
|
48
|
-
FeedbackManager.info(
|
|
49
|
-
message=f"* Access your project at https://cloud.tinybird.co/local/{TB_LOCAL_PORT}/{project.workspace_name}/project"
|
|
50
|
-
)
|
|
51
|
-
)
|
|
54
|
+
click.echo(FeedbackManager.info(message=f"✓ Access your project at {ui_url}\n"))
|
|
52
55
|
except OSError as e:
|
|
53
56
|
if e.errno == 48: # Address already in use
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
dev_server_already_running = False
|
|
58
|
+
try:
|
|
59
|
+
response = requests.get(f"http://localhost:{port}")
|
|
60
|
+
if response.status_code == 200 and "Tinybird Dev Server" in response.text:
|
|
61
|
+
dev_server_already_running = True
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
if dev_server_already_running:
|
|
65
|
+
message = f"Dev server is already running on http://localhost:{port}. Skipping..."
|
|
66
|
+
click.echo(FeedbackManager.warning(message=message))
|
|
67
|
+
sys_exit("dev_server_already_running", message)
|
|
68
|
+
else:
|
|
69
|
+
message = f"Port {port} is already in use. Check if another instance of the server is running or release the port."
|
|
70
|
+
click.echo(FeedbackManager.error(message=message))
|
|
71
|
+
sys_exit("port_in_use", message)
|
|
56
72
|
else:
|
|
57
73
|
click.echo(FeedbackManager.error_exception(error=e))
|
|
58
74
|
sys_exit("server_error", str(e))
|
|
@@ -137,7 +153,10 @@ class DevHandler(http.server.SimpleHTTPRequestHandler):
|
|
|
137
153
|
self.wfile.write(
|
|
138
154
|
json.dumps(
|
|
139
155
|
{
|
|
140
|
-
"files": [
|
|
156
|
+
"files": [
|
|
157
|
+
{"path": f.replace(f"{project.folder}/", ""), "content": Path(f).read_text()}
|
|
158
|
+
for f in project_files
|
|
159
|
+
],
|
|
141
160
|
"root": project.folder,
|
|
142
161
|
"workspace_name": project.workspace_name,
|
|
143
162
|
}
|
|
@@ -232,7 +251,9 @@ class DevHandler(http.server.SimpleHTTPRequestHandler):
|
|
|
232
251
|
pass
|
|
233
252
|
|
|
234
253
|
|
|
235
|
-
def start_server(
|
|
254
|
+
def start_server(
|
|
255
|
+
project: Project, tb_client: TinyB, process: Callable, build_status: BuildStatus, branch: Optional[str] = None
|
|
256
|
+
):
|
|
236
257
|
"""Start a development server for the project.
|
|
237
258
|
|
|
238
259
|
Args:
|
|
@@ -240,10 +261,10 @@ def start_server(project: Project, tb_client: TinyB, process: Callable, build_st
|
|
|
240
261
|
"""
|
|
241
262
|
|
|
242
263
|
try:
|
|
243
|
-
click.echo(FeedbackManager.highlight(message="
|
|
264
|
+
click.echo(FeedbackManager.highlight(message="» Exposing your project to Tinybird UI..."))
|
|
244
265
|
|
|
245
266
|
# Create and start the server
|
|
246
|
-
server = DevServer(process, project, build_status, tb_client)
|
|
267
|
+
server = DevServer(process, project, build_status, tb_client, branch)
|
|
247
268
|
server.serve_forever()
|
|
248
269
|
|
|
249
270
|
# Run the server in the main thread
|
tinybird/tb/modules/info.py
CHANGED
|
@@ -14,8 +14,9 @@ from tinybird.tb.modules.project import Project
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@cli.command(name="info")
|
|
17
|
+
@click.option("--skip-local", is_flag=True, default=False, help="Skip local info")
|
|
17
18
|
@click.pass_context
|
|
18
|
-
def info(ctx: click.Context) -> None:
|
|
19
|
+
def info(ctx: click.Context, skip_local: bool) -> None:
|
|
19
20
|
"""Get information about the project that is currently being used"""
|
|
20
21
|
ctx_config = ctx.ensure_object(dict)["config"]
|
|
21
22
|
project: Project = ctx.ensure_object(dict)["project"]
|
|
@@ -27,23 +28,41 @@ def info(ctx: click.Context) -> None:
|
|
|
27
28
|
|
|
28
29
|
click.echo(FeedbackManager.highlight(message="» Tinybird Cloud:"))
|
|
29
30
|
cloud_table, cloud_columns = get_cloud_info(ctx_config)
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
if not skip_local:
|
|
33
|
+
click.echo(FeedbackManager.highlight(message="\n» Tinybird Local:"))
|
|
34
|
+
local_table, local_columns = get_local_info(ctx_config)
|
|
35
|
+
|
|
32
36
|
click.echo(FeedbackManager.highlight(message="\n» Project:"))
|
|
33
37
|
project_table, project_columns = get_project_info(project.folder)
|
|
38
|
+
|
|
34
39
|
if output == "json":
|
|
40
|
+
response: dict[str, Any] = {}
|
|
41
|
+
|
|
35
42
|
cloud_data = {}
|
|
36
43
|
if cloud_columns and cloud_table and isinstance(cloud_table, list) and len(cloud_table) > 0:
|
|
37
44
|
cloud_data = {column: cloud_table[0][i] for i, column in enumerate(cloud_columns)}
|
|
45
|
+
response["cloud"] = cloud_data
|
|
38
46
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
if not skip_local:
|
|
48
|
+
local_data = {}
|
|
49
|
+
if local_columns and local_table and isinstance(local_table, list) and len(local_table) > 0:
|
|
50
|
+
local_data = {column: local_table[0][i] for i, column in enumerate(local_columns)}
|
|
51
|
+
response["local"] = local_data
|
|
42
52
|
|
|
43
53
|
project_data = {}
|
|
44
54
|
if project_columns and project_table and isinstance(project_table, list) and len(project_table) > 0:
|
|
45
55
|
project_data = {column: project_table[0][i] for i, column in enumerate(project_columns)}
|
|
46
|
-
|
|
56
|
+
response["project"] = project_data
|
|
57
|
+
|
|
58
|
+
branches = get_branches(ctx_config)
|
|
59
|
+
if branches:
|
|
60
|
+
branch_data: dict[str, dict[str, str]] = {}
|
|
61
|
+
for branch in branches:
|
|
62
|
+
branch_data[branch["name"]] = get_branch_info(branch)
|
|
63
|
+
response["branches"] = branch_data
|
|
64
|
+
|
|
65
|
+
echo_json(response)
|
|
47
66
|
|
|
48
67
|
|
|
49
68
|
def get_cloud_info(ctx_config: Dict[str, Any]) -> Tuple[Iterable[Any], List[str]]:
|
|
@@ -67,6 +86,20 @@ def get_cloud_info(ctx_config: Dict[str, Any]) -> Tuple[Iterable[Any], List[str]
|
|
|
67
86
|
return [], []
|
|
68
87
|
|
|
69
88
|
|
|
89
|
+
def get_branch_info(branch: Dict[str, Any]) -> Dict[str, Any]:
|
|
90
|
+
try:
|
|
91
|
+
token = branch.get("token") or "No token found"
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"token": token,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
except Exception:
|
|
98
|
+
return {
|
|
99
|
+
"token": "No token found",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
70
103
|
def get_local_info(config: Dict[str, Any]) -> Tuple[Iterable[Any], List[str]]:
|
|
71
104
|
try:
|
|
72
105
|
local_config = get_tinybird_local_config(config, test=False, silent=False)
|
|
@@ -169,3 +202,13 @@ def get_project_info(project_path: Optional[str] = None) -> Tuple[Iterable[Any],
|
|
|
169
202
|
table: Iterable[Any] = [(current_path, tinyb_path, project_path)]
|
|
170
203
|
click.echo(format_robust_table(table, column_names=columns))
|
|
171
204
|
return table, columns
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def get_branches(ctx_config: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
208
|
+
try:
|
|
209
|
+
config = CLIConfig.get_project_config()
|
|
210
|
+
client = config.get_client()
|
|
211
|
+
response = client.branches()
|
|
212
|
+
return response["environments"]
|
|
213
|
+
except Exception:
|
|
214
|
+
return []
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from tinybird.tb.config import get_display_cloud_host
|
|
4
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
5
|
+
from tinybird.tb.modules.local_common import TB_LOCAL_ADDRESS
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def echo_job_url(token: str, host: str, workspace_name: str, job_url: str):
|
|
9
|
+
if "localhost" in host:
|
|
10
|
+
job_url = f"{job_url.replace('http://localhost:8001', TB_LOCAL_ADDRESS)}?token={token}"
|
|
11
|
+
click.echo(FeedbackManager.gray(message="Job API URL: ") + FeedbackManager.info(message=f"{job_url}"))
|
|
12
|
+
ui_host = get_display_cloud_host(host)
|
|
13
|
+
click.echo(
|
|
14
|
+
FeedbackManager.gray(message="Jobs URL: ") + FeedbackManager.info(message=f"{ui_host}/{workspace_name}/jobs")
|
|
15
|
+
)
|
tinybird/tb/modules/local.py
CHANGED
|
@@ -10,7 +10,9 @@ from docker.client import DockerClient
|
|
|
10
10
|
|
|
11
11
|
from tinybird.tb.modules.cli import cli
|
|
12
12
|
from tinybird.tb.modules.common import echo_json, update_cli
|
|
13
|
+
from tinybird.tb.modules.exceptions import CLILocalException
|
|
13
14
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
15
|
+
from tinybird.tb.modules.info import get_local_info
|
|
14
16
|
from tinybird.tb.modules.local_common import (
|
|
15
17
|
TB_CONTAINER_NAME,
|
|
16
18
|
TB_LOCAL_ADDRESS,
|
|
@@ -18,6 +20,19 @@ from tinybird.tb.modules.local_common import (
|
|
|
18
20
|
get_existing_container_with_matching_env,
|
|
19
21
|
start_tinybird_local,
|
|
20
22
|
)
|
|
23
|
+
from tinybird.tb.modules.local_logs import (
|
|
24
|
+
check_memory_sufficient,
|
|
25
|
+
clickhouse_is_ready,
|
|
26
|
+
container_is_ready,
|
|
27
|
+
container_is_starting,
|
|
28
|
+
container_is_stopping,
|
|
29
|
+
container_is_unhealthy,
|
|
30
|
+
container_stats,
|
|
31
|
+
events_is_ready,
|
|
32
|
+
local_authentication_is_ready,
|
|
33
|
+
redis_is_ready,
|
|
34
|
+
server_is_ready,
|
|
35
|
+
)
|
|
21
36
|
|
|
22
37
|
|
|
23
38
|
def stop_tinybird_local(docker_client: DockerClient) -> None:
|
|
@@ -76,30 +91,69 @@ def stop() -> None:
|
|
|
76
91
|
@click.pass_context
|
|
77
92
|
def status(ctx: click.Context) -> None:
|
|
78
93
|
"""Check status of Tinybird Local"""
|
|
94
|
+
|
|
95
|
+
click.echo(FeedbackManager.highlight(message="» Checking status..."))
|
|
79
96
|
docker_client = get_docker_client()
|
|
80
97
|
container = get_existing_container_with_matching_env(docker_client, TB_CONTAINER_NAME, {})
|
|
81
98
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
try:
|
|
100
|
+
if container:
|
|
101
|
+
if container_is_ready(container):
|
|
102
|
+
stats = container_stats(container, docker_client)
|
|
103
|
+
click.echo(FeedbackManager.info(message=f"✓ Tinybird Local container ({stats})"))
|
|
104
|
+
|
|
105
|
+
# Check memory sufficiency
|
|
106
|
+
is_sufficient, warning_msg = check_memory_sufficient(container, docker_client)
|
|
107
|
+
if not is_sufficient and warning_msg:
|
|
108
|
+
click.echo(FeedbackManager.warning(message=f"△ {warning_msg}"))
|
|
109
|
+
|
|
110
|
+
if not clickhouse_is_ready(container):
|
|
111
|
+
raise Exception("Clickhouse is not ready.")
|
|
112
|
+
click.echo(FeedbackManager.info(message="✓ Clickhouse"))
|
|
113
|
+
|
|
114
|
+
if not redis_is_ready(container):
|
|
115
|
+
raise Exception("Redis is not ready.")
|
|
116
|
+
click.echo(FeedbackManager.info(message="✓ Redis"))
|
|
117
|
+
|
|
118
|
+
if not server_is_ready(container):
|
|
119
|
+
raise Exception("Server is not ready.")
|
|
120
|
+
click.echo(FeedbackManager.info(message="✓ Server"))
|
|
121
|
+
|
|
122
|
+
if not events_is_ready(container):
|
|
123
|
+
raise Exception("Events is not ready.")
|
|
124
|
+
click.echo(FeedbackManager.info(message="✓ Events"))
|
|
125
|
+
|
|
126
|
+
if not local_authentication_is_ready(container):
|
|
127
|
+
raise Exception("Tinybird Local authentication is not ready.")
|
|
128
|
+
click.echo(FeedbackManager.info(message="✓ Tinybird Local authentication"))
|
|
129
|
+
|
|
130
|
+
click.echo(FeedbackManager.success(message="✓ Tinybird Local is ready!"))
|
|
131
|
+
click.echo(FeedbackManager.highlight(message="\n» Tinybird Local:"))
|
|
132
|
+
config = ctx.ensure_object(dict).get("config", {})
|
|
133
|
+
get_local_info(config)
|
|
134
|
+
elif container_is_starting(container):
|
|
135
|
+
click.echo(FeedbackManager.highlight(message="* Tinybird Local is starting..."))
|
|
136
|
+
elif container_is_stopping(container):
|
|
137
|
+
click.echo(FeedbackManager.highlight(message="* Tinybird Local is stopping..."))
|
|
138
|
+
elif container_is_unhealthy(container):
|
|
139
|
+
is_sufficient, warning_msg = check_memory_sufficient(container, docker_client)
|
|
140
|
+
if not is_sufficient and warning_msg:
|
|
141
|
+
click.echo(FeedbackManager.warning(message=f"△ {warning_msg}"))
|
|
142
|
+
click.echo(
|
|
143
|
+
FeedbackManager.error(
|
|
144
|
+
message="* Tinybird Local is unhealthy. Try running `tb local restart` in a few seconds."
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
else:
|
|
148
|
+
click.echo(
|
|
149
|
+
FeedbackManager.error(message="✗ Tinybird Local is not running. Run 'tb local start' to start it")
|
|
150
|
+
)
|
|
97
151
|
else:
|
|
98
152
|
click.echo(
|
|
99
|
-
FeedbackManager.
|
|
153
|
+
FeedbackManager.error(message="✗ Tinybird Local is not running. Run 'tb local start' to start it")
|
|
100
154
|
)
|
|
101
|
-
|
|
102
|
-
|
|
155
|
+
except Exception as e:
|
|
156
|
+
raise CLILocalException(FeedbackManager.error(message=f"Tinybird Local is not ready. Reason: {e}"))
|
|
103
157
|
|
|
104
158
|
|
|
105
159
|
@local.command()
|
|
@@ -141,8 +195,20 @@ def remove() -> None:
|
|
|
141
195
|
envvar="TB_LOCAL_WORKSPACE_TOKEN",
|
|
142
196
|
help="Workspace token to use for the Tinybird Local container.",
|
|
143
197
|
)
|
|
198
|
+
@click.option(
|
|
199
|
+
"--daemon/--watch",
|
|
200
|
+
"-d/-w",
|
|
201
|
+
default=False,
|
|
202
|
+
is_flag=True,
|
|
203
|
+
help="Run Tinybird Local in the background.",
|
|
204
|
+
)
|
|
144
205
|
def start(
|
|
145
|
-
use_aws_creds: bool,
|
|
206
|
+
use_aws_creds: bool,
|
|
207
|
+
volumes_path: str,
|
|
208
|
+
skip_new_version: bool,
|
|
209
|
+
user_token: str,
|
|
210
|
+
workspace_token: str,
|
|
211
|
+
daemon: bool,
|
|
146
212
|
) -> None:
|
|
147
213
|
"""Start Tinybird Local"""
|
|
148
214
|
if volumes_path is not None:
|
|
@@ -152,8 +218,12 @@ def start(
|
|
|
152
218
|
|
|
153
219
|
click.echo(FeedbackManager.highlight(message="» Starting Tinybird Local..."))
|
|
154
220
|
docker_client = get_docker_client()
|
|
155
|
-
|
|
156
|
-
|
|
221
|
+
watch = not daemon
|
|
222
|
+
start_tinybird_local(
|
|
223
|
+
docker_client, use_aws_creds, volumes_path, skip_new_version, user_token, workspace_token, watch=watch
|
|
224
|
+
)
|
|
225
|
+
if daemon:
|
|
226
|
+
click.echo(FeedbackManager.success(message="✓ Tinybird Local is ready!"))
|
|
157
227
|
|
|
158
228
|
|
|
159
229
|
@local.command()
|