tinybird 0.0.1.dev291__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/ch_utils/constants.py +5 -0
- tinybird/connectors.py +1 -7
- tinybird/context.py +3 -3
- tinybird/datafile/common.py +10 -8
- tinybird/datafile/parse_pipe.py +2 -2
- tinybird/feedback_manager.py +3 -0
- tinybird/prompts.py +1 -0
- tinybird/service_datasources.py +223 -0
- tinybird/sql_template.py +26 -11
- tinybird/sql_template_fmt.py +14 -4
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/cli.py +1 -0
- tinybird/tb/client.py +104 -26
- tinybird/tb/config.py +24 -0
- tinybird/tb/modules/agent/agent.py +103 -67
- tinybird/tb/modules/agent/banner.py +15 -15
- tinybird/tb/modules/agent/explore_agent.py +5 -0
- tinybird/tb/modules/agent/mock_agent.py +5 -1
- tinybird/tb/modules/agent/models.py +6 -2
- tinybird/tb/modules/agent/prompts.py +49 -2
- tinybird/tb/modules/agent/tools/deploy.py +1 -1
- tinybird/tb/modules/agent/tools/execute_query.py +15 -18
- tinybird/tb/modules/agent/tools/request_endpoint.py +1 -1
- tinybird/tb/modules/agent/tools/run_command.py +9 -0
- tinybird/tb/modules/agent/utils.py +38 -48
- tinybird/tb/modules/branch.py +150 -0
- tinybird/tb/modules/build.py +58 -13
- tinybird/tb/modules/build_common.py +209 -25
- tinybird/tb/modules/cli.py +129 -16
- tinybird/tb/modules/common.py +172 -146
- 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 +83 -150
- tinybird/tb/modules/datafile/build.py +27 -38
- tinybird/tb/modules/datafile/build_datasource.py +21 -25
- tinybird/tb/modules/datafile/diff.py +1 -1
- tinybird/tb/modules/datafile/format_pipe.py +46 -7
- tinybird/tb/modules/datafile/playground.py +59 -68
- tinybird/tb/modules/datafile/pull.py +2 -3
- tinybird/tb/modules/datasource.py +477 -308
- tinybird/tb/modules/deployment.py +2 -0
- tinybird/tb/modules/deployment_common.py +84 -44
- tinybird/tb/modules/deprecations.py +4 -4
- tinybird/tb/modules/dev_server.py +33 -12
- tinybird/tb/modules/exceptions.py +14 -0
- tinybird/tb/modules/feedback_manager.py +1 -1
- tinybird/tb/modules/info.py +69 -12
- tinybird/tb/modules/infra.py +4 -5
- tinybird/tb/modules/job_common.py +15 -0
- tinybird/tb/modules/local.py +143 -23
- tinybird/tb/modules/local_common.py +347 -19
- tinybird/tb/modules/local_logs.py +209 -0
- tinybird/tb/modules/login.py +21 -2
- tinybird/tb/modules/login_common.py +254 -12
- tinybird/tb/modules/mock.py +5 -54
- tinybird/tb/modules/mock_common.py +0 -54
- tinybird/tb/modules/open.py +10 -5
- tinybird/tb/modules/project.py +14 -5
- tinybird/tb/modules/shell.py +15 -7
- tinybird/tb/modules/sink.py +3 -1
- tinybird/tb/modules/telemetry.py +11 -3
- tinybird/tb/modules/test.py +13 -9
- tinybird/tb/modules/test_common.py +13 -87
- tinybird/tb/modules/tinyunit/tinyunit.py +0 -14
- tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -6
- tinybird/tb/modules/watch.py +5 -3
- tinybird/tb_cli_modules/common.py +2 -2
- tinybird/tb_cli_modules/telemetry.py +1 -1
- tinybird/tornado_template.py +6 -7
- {tinybird-0.0.1.dev291.dist-info → tinybird-1.0.5.dist-info}/METADATA +32 -6
- tinybird-1.0.5.dist-info/RECORD +132 -0
- {tinybird-0.0.1.dev291.dist-info → tinybird-1.0.5.dist-info}/WHEEL +1 -1
- tinybird-0.0.1.dev291.dist-info/RECORD +0 -128
- {tinybird-0.0.1.dev291.dist-info → tinybird-1.0.5.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev291.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"))
|
|
183
|
-
|
|
184
|
-
TINYBIRD_API_URL = f"{host}/v1/deployments/{previous_deployment.get('id')}/set-live"
|
|
185
|
-
result = api_post(TINYBIRD_API_URL, headers=headers)
|
|
186
|
-
|
|
187
|
-
click.echo(FeedbackManager.success(message="Removing current 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']}"))
|
|
188
189
|
|
|
189
|
-
TINYBIRD_API_URL = f"{host}/v1/deployments/{
|
|
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:
|
|
@@ -352,26 +376,33 @@ def create_deployment(
|
|
|
352
376
|
if not deployment:
|
|
353
377
|
click.echo(FeedbackManager.error(message="Error parsing deployment from response"))
|
|
354
378
|
sys_exit("deployment_error", "Error parsing deployment from response")
|
|
355
|
-
|
|
356
|
-
click.echo(FeedbackManager.error(message="Deployment failed"))
|
|
357
|
-
deploy_errors = deployment.get("errors")
|
|
358
|
-
for deploy_error in deploy_errors:
|
|
359
|
-
click.echo(FeedbackManager.error(message=f"* {deploy_error}"))
|
|
360
|
-
|
|
361
|
-
if auto:
|
|
362
|
-
click.echo(FeedbackManager.error(message="Rolling back deployment"))
|
|
363
|
-
discard_deployment(client.host, HEADERS, wait=wait)
|
|
364
|
-
sys_exit(
|
|
365
|
-
"deployment_error",
|
|
366
|
-
f"Deployment failed. Errors: {str(deployment.get('errors') + deployment.get('feedback', []))}",
|
|
367
|
-
)
|
|
379
|
+
return
|
|
368
380
|
|
|
369
|
-
|
|
381
|
+
status = deployment.get("status")
|
|
382
|
+
errors = deployment.get("errors")
|
|
383
|
+
feedback = deployment.get("feedback")
|
|
384
|
+
|
|
385
|
+
if status == "failed":
|
|
386
|
+
# Just wait until we poll again and see deleting or deleted to report errors
|
|
387
|
+
pass
|
|
388
|
+
|
|
389
|
+
if status == "data_ready":
|
|
370
390
|
break
|
|
371
391
|
|
|
372
|
-
if
|
|
373
|
-
|
|
374
|
-
|
|
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
|
+
)
|
|
375
406
|
|
|
376
407
|
time.sleep(5)
|
|
377
408
|
|
|
@@ -379,6 +410,15 @@ def create_deployment(
|
|
|
379
410
|
|
|
380
411
|
if auto:
|
|
381
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)
|
|
382
422
|
|
|
383
423
|
|
|
384
424
|
def _build_data_movement_message(kind: str, source_mv_name: Optional[str]) -> str:
|
|
@@ -390,7 +430,7 @@ def _build_data_movement_message(kind: str, source_mv_name: Optional[str]) -> st
|
|
|
390
430
|
return ""
|
|
391
431
|
|
|
392
432
|
|
|
393
|
-
def print_changes(result: dict, project: Project) -> None:
|
|
433
|
+
def print_changes(result: dict, project: Project, output: Optional[str] = "human") -> None:
|
|
394
434
|
deployment = result.get("deployment", {})
|
|
395
435
|
resources_columns = ["status", "name", "type", "path"]
|
|
396
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
|
|
@@ -127,6 +127,13 @@ class CLIMockException(CLIException):
|
|
|
127
127
|
super().__init__(message, "mock_error", **kw_telemetry_event_data)
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
class CLIAgentException(CLIException):
|
|
131
|
+
"""Exceptions generated by the agent commands"""
|
|
132
|
+
|
|
133
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
134
|
+
super().__init__(message, "agent_error", **kw_telemetry_event_data)
|
|
135
|
+
|
|
136
|
+
|
|
130
137
|
class CLILoginException(CLIException):
|
|
131
138
|
"""Exceptions generated by the login commands"""
|
|
132
139
|
|
|
@@ -146,3 +153,10 @@ class CLISecretException(CLIException):
|
|
|
146
153
|
|
|
147
154
|
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
148
155
|
super().__init__(message, "secret_error", **kw_telemetry_event_data)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class CLIChException(CLIException):
|
|
159
|
+
"""Exceptions generated by the ch commands"""
|
|
160
|
+
|
|
161
|
+
def __init__(self, message: str, **kw_telemetry_event_data: Any) -> None:
|
|
162
|
+
super().__init__(message, "ch_error", **kw_telemetry_event_data)
|
|
@@ -1209,7 +1209,7 @@ STEP 3: ADD KEY TO SERVICE ACCOUNT
|
|
|
1209
1209
|
|
|
1210
1210
|
debug_running_file = print_message("** Running {file}", bcolors.CGREY)
|
|
1211
1211
|
|
|
1212
|
-
highlight_building_project = info_highlight_message("
|
|
1212
|
+
highlight_building_project = info_highlight_message("» Building project...")
|
|
1213
1213
|
|
|
1214
1214
|
success = success_message("{message}")
|
|
1215
1215
|
info = info_message("{message}")
|
tinybird/tb/modules/info.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple
|
|
|
4
4
|
import click
|
|
5
5
|
|
|
6
6
|
from tinybird.tb.client import TinyB
|
|
7
|
-
from tinybird.tb.config import get_display_cloud_host
|
|
7
|
+
from tinybird.tb.config import get_clickhouse_host, get_display_cloud_host
|
|
8
8
|
from tinybird.tb.modules.cli import CLIConfig, cli
|
|
9
9
|
from tinybird.tb.modules.common import echo_json, force_echo, format_robust_table
|
|
10
10
|
from tinybird.tb.modules.exceptions import CLILocalException
|
|
@@ -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]]:
|
|
@@ -54,9 +73,10 @@ def get_cloud_info(ctx_config: Dict[str, Any]) -> Tuple[Iterable[Any], List[str]
|
|
|
54
73
|
token = config.get_token() or "No workspace token found"
|
|
55
74
|
api_host = config.get("host") or "No API host found"
|
|
56
75
|
ui_host = get_display_cloud_host(api_host)
|
|
76
|
+
ch_host = get_clickhouse_host(api_host)
|
|
57
77
|
user_email = config.get("user_email") or "No user email found"
|
|
58
78
|
user_token = config.get_user_token() or "No user token found"
|
|
59
|
-
return get_env_info(client, ctx_config, user_email, token, user_token, api_host, ui_host)
|
|
79
|
+
return get_env_info(client, ctx_config, user_email, token, user_token, api_host, ui_host, ch_host)
|
|
60
80
|
except Exception:
|
|
61
81
|
click.echo(
|
|
62
82
|
FeedbackManager.warning(
|
|
@@ -66,6 +86,20 @@ def get_cloud_info(ctx_config: Dict[str, Any]) -> Tuple[Iterable[Any], List[str]
|
|
|
66
86
|
return [], []
|
|
67
87
|
|
|
68
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
|
+
|
|
69
103
|
def get_local_info(config: Dict[str, Any]) -> Tuple[Iterable[Any], List[str]]:
|
|
70
104
|
try:
|
|
71
105
|
local_config = get_tinybird_local_config(config, test=False, silent=False)
|
|
@@ -75,7 +109,10 @@ def get_local_info(config: Dict[str, Any]) -> Tuple[Iterable[Any], List[str]]:
|
|
|
75
109
|
user_token = local_config.get_user_token() or "No user token found"
|
|
76
110
|
api_host = TB_LOCAL_ADDRESS
|
|
77
111
|
ui_host = get_display_cloud_host(api_host)
|
|
78
|
-
|
|
112
|
+
ch_host = get_clickhouse_host(api_host)
|
|
113
|
+
return get_env_info(
|
|
114
|
+
local_client, config, user_email, token, user_token, api_host, ui_host, ch_host, is_local=True
|
|
115
|
+
)
|
|
79
116
|
except CLILocalException as e:
|
|
80
117
|
raise e
|
|
81
118
|
except Exception as e:
|
|
@@ -95,6 +132,7 @@ def get_env_info(
|
|
|
95
132
|
user_token: str,
|
|
96
133
|
api_host: str,
|
|
97
134
|
ui_host: str,
|
|
135
|
+
ch_host: str,
|
|
98
136
|
is_local=False,
|
|
99
137
|
) -> Tuple[List[Any], List[str]]:
|
|
100
138
|
user_workspaces = client.user_workspaces(version="v1")
|
|
@@ -115,11 +153,20 @@ def get_env_info(
|
|
|
115
153
|
|
|
116
154
|
assert isinstance(current_main_workspace, dict)
|
|
117
155
|
|
|
118
|
-
columns = ["user", "workspace_name", "workspace_id", "token", "user_token", "api", "ui"]
|
|
156
|
+
columns = ["user", "workspace_name", "workspace_id", "token", "user_token", "api", "ui", "clickhouse"]
|
|
119
157
|
if current_main_workspace["name"]:
|
|
120
158
|
ui_host += f"/{current_main_workspace['name']}"
|
|
121
159
|
table = [
|
|
122
|
-
(
|
|
160
|
+
(
|
|
161
|
+
user_email,
|
|
162
|
+
current_main_workspace["name"],
|
|
163
|
+
current_main_workspace["id"],
|
|
164
|
+
token,
|
|
165
|
+
user_token,
|
|
166
|
+
api_host,
|
|
167
|
+
ui_host,
|
|
168
|
+
ch_host,
|
|
169
|
+
)
|
|
123
170
|
]
|
|
124
171
|
|
|
125
172
|
if is_local and (
|
|
@@ -155,3 +202,13 @@ def get_project_info(project_path: Optional[str] = None) -> Tuple[Iterable[Any],
|
|
|
155
202
|
table: Iterable[Any] = [(current_path, tinyb_path, project_path)]
|
|
156
203
|
click.echo(format_robust_table(table, column_names=columns))
|
|
157
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 []
|
tinybird/tb/modules/infra.py
CHANGED
|
@@ -626,12 +626,11 @@ def infra_init(
|
|
|
626
626
|
else:
|
|
627
627
|
click.echo("Failed to get diff for Kubernetes configuration:")
|
|
628
628
|
click.echo(diff_result.stderr)
|
|
629
|
+
elif diff_result.stdout:
|
|
630
|
+
click.echo("\nChanges that will be applied:")
|
|
631
|
+
click.echo(diff_result.stdout)
|
|
629
632
|
else:
|
|
630
|
-
|
|
631
|
-
click.echo("\nChanges that will be applied:")
|
|
632
|
-
click.echo(diff_result.stdout)
|
|
633
|
-
else:
|
|
634
|
-
click.echo("\nNo changes detected or resources don't exist yet.")
|
|
633
|
+
click.echo("\nNo changes detected or resources don't exist yet.")
|
|
635
634
|
|
|
636
635
|
# Now apply the configuration
|
|
637
636
|
command = f"kubectl --context {selected_context} apply -f {str(yaml_path)}"
|
|
@@ -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
|
+
)
|