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.
Files changed (45) hide show
  1. tinybird/datafile/common.py +4 -1
  2. tinybird/feedback_manager.py +3 -0
  3. tinybird/service_datasources.py +57 -8
  4. tinybird/sql_template.py +1 -1
  5. tinybird/sql_template_fmt.py +14 -4
  6. tinybird/tb/__cli__.py +2 -2
  7. tinybird/tb/cli.py +1 -0
  8. tinybird/tb/client.py +104 -22
  9. tinybird/tb/modules/agent/tools/execute_query.py +1 -1
  10. tinybird/tb/modules/agent/tools/request_endpoint.py +1 -1
  11. tinybird/tb/modules/branch.py +150 -0
  12. tinybird/tb/modules/build.py +51 -10
  13. tinybird/tb/modules/build_common.py +4 -2
  14. tinybird/tb/modules/cli.py +32 -10
  15. tinybird/tb/modules/common.py +161 -134
  16. tinybird/tb/modules/connection.py +125 -194
  17. tinybird/tb/modules/connection_kafka.py +382 -0
  18. tinybird/tb/modules/copy.py +3 -1
  19. tinybird/tb/modules/create.py +11 -0
  20. tinybird/tb/modules/datafile/build.py +1 -1
  21. tinybird/tb/modules/datafile/format_pipe.py +44 -5
  22. tinybird/tb/modules/datafile/playground.py +1 -1
  23. tinybird/tb/modules/datasource.py +475 -324
  24. tinybird/tb/modules/deployment.py +2 -0
  25. tinybird/tb/modules/deployment_common.py +81 -43
  26. tinybird/tb/modules/deprecations.py +4 -4
  27. tinybird/tb/modules/dev_server.py +33 -12
  28. tinybird/tb/modules/info.py +50 -7
  29. tinybird/tb/modules/job_common.py +15 -0
  30. tinybird/tb/modules/local.py +91 -21
  31. tinybird/tb/modules/local_common.py +320 -13
  32. tinybird/tb/modules/local_logs.py +209 -0
  33. tinybird/tb/modules/login.py +3 -2
  34. tinybird/tb/modules/login_common.py +252 -9
  35. tinybird/tb/modules/open.py +10 -5
  36. tinybird/tb/modules/project.py +14 -5
  37. tinybird/tb/modules/shell.py +14 -6
  38. tinybird/tb/modules/sink.py +3 -1
  39. tinybird/tb/modules/telemetry.py +7 -3
  40. tinybird/tb_cli_modules/telemetry.py +1 -1
  41. {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/METADATA +29 -4
  42. {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/RECORD +45 -41
  43. {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/WHEEL +1 -1
  44. {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/entry_points.txt +0 -0
  45. {tinybird-0.0.1.dev306.dist-info → tinybird-1.0.5.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ import time
3
3
  from copy import deepcopy
4
4
  from functools import partial
5
5
  from pathlib import Path
6
- from typing import Any, Callable, Dict, List
6
+ from typing import Any, Callable, Dict, List, Optional
7
7
  from urllib.parse import urlencode
8
8
 
9
9
  import click
@@ -35,7 +35,10 @@ def build(ctx: click.Context, watch: bool) -> None:
35
35
  project: Project = ctx.ensure_object(dict)["project"]
36
36
  tb_client: TinyB = ctx.ensure_object(dict)["client"]
37
37
  config: Dict[str, Any] = ctx.ensure_object(dict)["config"]
38
- if obj["env"] == "cloud":
38
+ is_branch = bool(ctx.ensure_object(dict)["branch"])
39
+
40
+ # TODO: Explain that you can use custom branches too once they are open for everyone
41
+ if obj["env"] == "cloud" and not is_branch:
39
42
  raise click.ClickException(FeedbackManager.error_build_only_supported_in_local())
40
43
 
41
44
  if project.has_deeper_level():
@@ -48,47 +51,85 @@ def build(ctx: click.Context, watch: bool) -> None:
48
51
  )
49
52
 
50
53
  click.echo(FeedbackManager.highlight_building_project())
51
- process(project=project, tb_client=tb_client, watch=False, config=config)
54
+ process(project=project, tb_client=tb_client, watch=False, config=config, is_branch=is_branch)
52
55
  if watch:
53
56
  run_watch(
54
57
  project=project,
55
58
  tb_client=tb_client,
56
59
  config=config,
57
- process=partial(process, project=project, tb_client=tb_client, watch=True, config=config),
60
+ process=partial(
61
+ process,
62
+ project=project,
63
+ tb_client=tb_client,
64
+ watch=True,
65
+ config=config,
66
+ is_branch=is_branch,
67
+ ),
58
68
  )
59
69
 
60
70
 
61
71
  @cli.command("dev", help="Build the project server side and watch for changes.")
62
72
  @click.option("--data-origin", type=str, default="", help="Data origin: local or cloud")
63
- @click.option("--ui", is_flag=True, default=False, help="Connect your local project to Tinybird UI")
73
+ @click.option("--ui/--skip-ui", is_flag=True, default=True, help="Connect your local project to Tinybird UI")
64
74
  @click.pass_context
65
75
  def dev(ctx: click.Context, data_origin: str, ui: bool) -> None:
76
+ obj: Dict[str, Any] = ctx.ensure_object(dict)
77
+ branch: Optional[str] = ctx.ensure_object(dict)["branch"]
78
+ is_branch = bool(branch)
79
+
80
+ if obj["env"] == "cloud" and not is_branch:
81
+ raise click.ClickException(FeedbackManager.error_build_only_supported_in_local())
82
+
66
83
  if data_origin == "cloud":
84
+ click.echo(
85
+ FeedbackManager.warning(
86
+ message="--data-origin=cloud is deprecated and will be removed in a future version. Create an branch and use `tb --branch <branch_name> dev`"
87
+ )
88
+ )
67
89
  return dev_cloud(ctx)
90
+
68
91
  project: Project = ctx.ensure_object(dict)["project"]
69
92
  tb_client: TinyB = ctx.ensure_object(dict)["client"]
70
93
  config: Dict[str, Any] = ctx.ensure_object(dict)["config"]
94
+
71
95
  build_status = BuildStatus()
72
96
  if ui:
73
97
  server_thread = threading.Thread(
74
- target=start_server, args=(project, tb_client, process, build_status), daemon=True
98
+ target=start_server, args=(project, tb_client, process, build_status, branch), daemon=True
75
99
  )
76
100
  server_thread.start()
77
101
  # Wait for the server to start
78
102
  time.sleep(0.5)
79
103
 
80
104
  click.echo(FeedbackManager.highlight_building_project())
81
- process(project=project, tb_client=tb_client, watch=True, config=config, build_status=build_status)
105
+ process(
106
+ project=project,
107
+ tb_client=tb_client,
108
+ watch=True,
109
+ config=config,
110
+ build_status=build_status,
111
+ is_branch=is_branch,
112
+ )
82
113
  run_watch(
83
114
  project=project,
84
115
  tb_client=tb_client,
85
116
  config=config,
86
- process=partial(process, project=project, tb_client=tb_client, build_status=build_status, config=config),
117
+ branch=branch,
118
+ process=partial(
119
+ process,
120
+ project=project,
121
+ tb_client=tb_client,
122
+ build_status=build_status,
123
+ config=config,
124
+ is_branch=is_branch,
125
+ ),
87
126
  )
88
127
 
89
128
 
90
- def run_watch(project: Project, tb_client: TinyB, process: Callable, config: dict[str, Any]) -> None:
91
- shell = Shell(project=project, tb_client=tb_client, playground=False)
129
+ def run_watch(
130
+ project: Project, tb_client: TinyB, process: Callable, config: dict[str, Any], branch: Optional[str] = None
131
+ ) -> None:
132
+ shell = Shell(project=project, tb_client=tb_client, branch=branch)
92
133
  click.echo(FeedbackManager.gray(message="\nWatching for changes..."))
93
134
  watcher_thread = threading.Thread(
94
135
  target=watch_project,
@@ -33,15 +33,17 @@ def process(
33
33
  exit_on_error: bool = True,
34
34
  load_fixtures: bool = True,
35
35
  project_with_vendors: Optional[Project] = None,
36
+ is_branch: bool = False,
36
37
  ) -> Optional[str]:
37
38
  time_start = time.time()
38
39
 
39
40
  # Build vendored workspaces before build
40
- if not project_with_vendors:
41
+ if not project_with_vendors and not is_branch:
41
42
  build_vendored_workspaces(project=project, tb_client=tb_client, config=config)
42
43
 
43
44
  # Ensure SHARED_WITH workspaces exist before build
44
- build_shared_with_workspaces(project=project, tb_client=tb_client, config=config)
45
+ if not is_branch:
46
+ build_shared_with_workspaces(project=project, tb_client=tb_client, config=config)
45
47
 
46
48
  build_failed = False
47
49
  build_error: Optional[str] = None
@@ -42,6 +42,7 @@ from tinybird.tb.modules.datafile.pull import folder_pull
42
42
  from tinybird.tb.modules.exceptions import CLIChException
43
43
  from tinybird.tb.modules.feedback_manager import FeedbackManager
44
44
  from tinybird.tb.modules.local_common import get_tinybird_local_client
45
+ from tinybird.tb.modules.login_common import check_current_folder_in_sessions
45
46
  from tinybird.tb.modules.project import Project
46
47
 
47
48
  __old_click_echo = click.echo
@@ -76,6 +77,7 @@ VERSION = f"{__cli__.__version__} (rev {__cli__.__revision__})"
76
77
  )
77
78
  @click.option("--show-tokens", is_flag=True, default=False, help="Enable the output of tokens.")
78
79
  @click.option("--cloud/--local", is_flag=True, default=False, help="Run against cloud or local.")
80
+ @click.option("--branch", help="Run against a branch.")
79
81
  @click.option("--staging", is_flag=True, default=False, help="Run against a staging deployment.")
80
82
  @click.option(
81
83
  "--output", type=click.Choice(["human", "json", "csv"], case_sensitive=False), default="human", help="Output format"
@@ -103,6 +105,7 @@ def cli(
103
105
  version_warning: bool,
104
106
  show_tokens: bool,
105
107
  cloud: bool,
108
+ branch: Optional[str],
106
109
  staging: bool,
107
110
  output: str,
108
111
  max_depth: int,
@@ -200,21 +203,33 @@ def cli(
200
203
  return
201
204
 
202
205
  ctx.ensure_object(dict)["project"] = project
203
- client = create_ctx_client(ctx, config, cloud, staging, project=project, show_warnings=version_warning)
206
+ client = create_ctx_client(
207
+ ctx,
208
+ config,
209
+ cloud or bool(branch),
210
+ staging,
211
+ project=project,
212
+ show_warnings=version_warning,
213
+ branch=branch,
214
+ )
204
215
 
205
216
  if client:
206
217
  ctx.ensure_object(dict)["client"] = client
207
218
 
208
- ctx.ensure_object(dict)["env"] = get_target_env(cloud)
219
+ ctx.ensure_object(dict)["env"] = get_target_env(cloud, branch)
220
+ ctx.ensure_object(dict)["branch"] = branch
209
221
  ctx.ensure_object(dict)["output"] = output
210
222
 
223
+ # Check if current folder is tracked from previous sessions
224
+ check_current_folder_in_sessions(ctx)
225
+
211
226
  is_prompt_mode = prompt is not None
212
227
 
213
228
  if is_agent_mode or is_prompt_mode:
214
- if any(arg in sys.argv for arg in ["--cloud", "--local"]):
229
+ if any(arg in sys.argv for arg in ["--cloud", "--local", "--branch"]):
215
230
  raise CLIException(
216
231
  FeedbackManager.error(
217
- message="Tinybird Code does not support --cloud or --local flags. It will choose the correct environment based on your prompts."
232
+ message="Tinybird Code does not support --cloud, --local or --branch flags. It will choose the correct environment based on your prompts."
218
233
  )
219
234
  )
220
235
 
@@ -484,7 +499,13 @@ def __hide_click_output() -> None:
484
499
 
485
500
 
486
501
  def create_ctx_client(
487
- ctx: Context, config: Dict[str, Any], cloud: bool, staging: bool, project: Project, show_warnings: bool = True
502
+ ctx: Context,
503
+ config: Dict[str, Any],
504
+ cloud: bool,
505
+ staging: bool,
506
+ project: Project,
507
+ show_warnings: bool = True,
508
+ branch: Optional[str] = None,
488
509
  ):
489
510
  commands_without_ctx_client = [
490
511
  "auth",
@@ -498,6 +519,7 @@ def create_ctx_client(
498
519
  "tag",
499
520
  "push",
500
521
  "branch",
522
+ "environment",
501
523
  "diff",
502
524
  "fmt",
503
525
  "init",
@@ -506,8 +528,8 @@ def create_ctx_client(
506
528
  if not command or command in commands_without_ctx_client:
507
529
  return None
508
530
 
509
- commands_always_cloud = ["infra"]
510
- commands_always_local = ["build", "dev", "create"]
531
+ commands_always_cloud = ["infra", "branch", "environment"]
532
+ commands_always_local = ["create"]
511
533
  command_always_test = ["test"]
512
534
 
513
535
  if (
@@ -530,7 +552,7 @@ def create_ctx_client(
530
552
  if method and show_warnings:
531
553
  click.echo(FeedbackManager.gray(message=f"Authentication method: {method}"))
532
554
 
533
- return _get_tb_client(config.get("token", ""), config["host"], staging=staging)
555
+ return _get_tb_client(config.get("token", ""), config["host"], staging=staging, branch=branch)
534
556
  local = command in commands_always_local
535
557
  test = command in command_always_test
536
558
  if show_warnings and not local and command not in commands_always_local and command:
@@ -538,8 +560,8 @@ def create_ctx_client(
538
560
  return get_tinybird_local_client(config, test=test, staging=staging)
539
561
 
540
562
 
541
- def get_target_env(cloud: bool) -> str:
542
- if cloud:
563
+ def get_target_env(cloud: bool, branch: Optional[str]) -> str:
564
+ if cloud or bool(branch):
543
565
  return "cloud"
544
566
  return "local"
545
567
 
@@ -30,7 +30,6 @@ import requests
30
30
  from click import Context
31
31
  from click._termui_impl import ProgressBar
32
32
  from humanfriendly.tables import format_pretty_table
33
- from packaging.version import Version
34
33
  from thefuzz import process
35
34
 
36
35
  from tinybird.tb.client import (
@@ -61,7 +60,6 @@ from tinybird.tb.modules.exceptions import (
61
60
  CLIAuthException,
62
61
  CLIConnectionException,
63
62
  CLIException,
64
- CLIReleaseException,
65
63
  CLIWorkspaceException,
66
64
  )
67
65
  from tinybird.tb.modules.feedback_manager import FeedbackManager, warning_message
@@ -378,9 +376,33 @@ def getenv_bool(key: str, default: bool) -> bool:
378
376
  return v.lower() == "true" or v == "1"
379
377
 
380
378
 
381
- def _get_tb_client(token: str, host: str, staging: bool = False) -> TinyB:
379
+ def _get_tb_client(token: str, host: str, staging: bool = False, branch: Optional[str] = None) -> TinyB:
382
380
  disable_ssl: bool = getenv_bool("TB_DISABLE_SSL_CHECKS", False)
383
- return TinyB(token, host, version=VERSION, disable_ssl_checks=disable_ssl, send_telemetry=True, staging=staging)
381
+ cloud_client = TinyB(
382
+ token,
383
+ host,
384
+ version=VERSION,
385
+ disable_ssl_checks=disable_ssl,
386
+ send_telemetry=True,
387
+ staging=staging,
388
+ )
389
+
390
+ if not branch:
391
+ return cloud_client
392
+
393
+ workspaces = cloud_client.user_workspaces_and_branches(version="v1")
394
+ workspace = next((w for w in workspaces.get("workspaces", []) if w.get("name") == branch), None)
395
+ if not workspace:
396
+ raise CLIException(FeedbackManager.error_exception(error=f"Branch {branch} not found"))
397
+
398
+ return TinyB(
399
+ workspace.get("token", ""),
400
+ host,
401
+ version=VERSION,
402
+ disable_ssl_checks=disable_ssl,
403
+ send_telemetry=True,
404
+ staging=staging,
405
+ )
384
406
 
385
407
 
386
408
  def create_tb_client(ctx: Context) -> TinyB:
@@ -414,7 +436,16 @@ def analyze_file(filename: str, client: TinyB, format: str):
414
436
  meta, data = _analyze(filename, client, format)
415
437
  schema = meta["analysis"]["schema"]
416
438
  schema = schema.replace(", ", ",\n ")
417
- content = f"""DESCRIPTION >\n Generated from {filename}\n\nSCHEMA >\n {schema}"""
439
+ content = f"""DESCRIPTION >
440
+ Generated from {filename}
441
+
442
+ SCHEMA >
443
+ {schema}
444
+
445
+ ENGINE "MergeTree"
446
+ # ENGINE_SORTING_KEY "user_id, timestamp"
447
+ # ENGINE_TTL "timestamp + toIntervalDay(60)"
448
+ # Learn more at https://www.tinybird.co/docs/forward/dev-reference/datafiles/datasource-files"""
418
449
  return content
419
450
 
420
451
 
@@ -429,7 +460,16 @@ def _generate_datafile(
429
460
  meta, data = _analyze(filename, client, format, connector=connector)
430
461
  schema = meta["analysis"]["schema"]
431
462
  schema = schema.replace(", ", ",\n ")
432
- datafile = f"""DESCRIPTION >\n Generated from {filename}\n\nSCHEMA >\n {schema}"""
463
+ datafile = f"""DESCRIPTION >
464
+ Generated from {filename}
465
+
466
+ SCHEMA >
467
+ {schema}
468
+
469
+ ENGINE "MergeTree"
470
+ # ENGINE_SORTING_KEY "user_id, timestamp"
471
+ # ENGINE_TTL "timestamp + toIntervalDay(60)"
472
+ # Learn more at https://www.tinybird.co/docs/forward/dev-reference/datafiles/datasource-files"""
433
473
  return generate_datafile(datafile, filename, data, force, _format=format, folder=folder)
434
474
 
435
475
 
@@ -965,7 +1005,10 @@ def push_data(
965
1005
  cb.prev_done = 0 # type: ignore[attr-defined]
966
1006
 
967
1007
  if not silent:
968
- click.echo(FeedbackManager.highlight(message=f"\n» Appending data to {datasource_name}..."))
1008
+ if mode == "replace":
1009
+ click.echo(FeedbackManager.highlight(message=f"\n» Replacing data in {datasource_name}..."))
1010
+ else:
1011
+ click.echo(FeedbackManager.highlight(message=f"\n» Appending data to {datasource_name}..."))
969
1012
 
970
1013
  if isinstance(url, list):
971
1014
  urls = url
@@ -1165,10 +1208,26 @@ def _get_setting_value(connection, setting, sensitive_settings):
1165
1208
  return connection.get(setting, "")
1166
1209
 
1167
1210
 
1168
- def switch_workspace(config: CLIConfig, workspace_name_or_id: str) -> None:
1211
+ def get_current_workspace_branches(config: CLIConfig) -> List[Dict[str, Any]]:
1212
+ current_main_workspace: Optional[Dict[str, Any]] = get_current_main_workspace(config)
1213
+ if not current_main_workspace:
1214
+ raise CLIException(FeedbackManager.error_unable_to_identify_main_workspace())
1215
+
1216
+ client = config.get_client()
1217
+ user_branches: List[Dict[str, Any]] = (client.user_workspace_branches("v1")).get("workspaces", [])
1218
+ all_branches: List[Dict[str, Any]] = (client.branches()).get("environments", [])
1219
+ branches = user_branches + [branch for branch in all_branches if branch not in user_branches]
1220
+
1221
+ return [branch for branch in branches if branch.get("main") == current_main_workspace["id"]]
1222
+
1223
+
1224
+ def switch_workspace(config: CLIConfig, workspace_name_or_id: str, only_environments: bool = False) -> None:
1169
1225
  try:
1170
- response = config.get_client().user_workspaces(version="v1")
1171
- workspaces = response["workspaces"]
1226
+ if only_environments:
1227
+ workspaces = get_current_workspace_branches(config)
1228
+ else:
1229
+ response = config.get_client().user_workspaces(version="v1")
1230
+ workspaces = response["workspaces"]
1172
1231
 
1173
1232
  workspace = next(
1174
1233
  (
@@ -1563,130 +1622,6 @@ def _get_current_main_workspace_common(
1563
1622
  return current
1564
1623
 
1565
1624
 
1566
- def is_post_semver(new_version: Version, current_version: Version) -> bool:
1567
- """
1568
- Check if only the post part of the semantic version has changed.
1569
-
1570
- Args:
1571
- new_version (Version): The new version to check.
1572
- current_version (Version): The current version to compare with.
1573
-
1574
- Returns:
1575
- bool: True if only the post part of the version has changed, False otherwise.
1576
-
1577
- Examples:
1578
- >>> is_post_semver(Version("0.0.0-2"), Version("0.0.0-1"))
1579
- True
1580
- >>> is_post_semver(Version("0.0.0-1"), Version("0.0.0-1"))
1581
- False
1582
- >>> is_post_semver(Version("0.0.1-1"), Version("0.0.0-1"))
1583
- False
1584
- >>> is_post_semver(Version("0.1.0-1"), Version("0.0.0-1"))
1585
- False
1586
- >>> is_post_semver(Version("1.0.0-1"), Version("0.0.0-1"))
1587
- False
1588
- >>> is_post_semver(Version("0.0.1-1"), Version("0.0.0"))
1589
- False
1590
- """
1591
- if (
1592
- new_version.major == current_version.major
1593
- and new_version.minor == current_version.minor
1594
- and new_version.micro == current_version.micro
1595
- ):
1596
- return new_version.post is not None and new_version.post != current_version.post
1597
-
1598
- return False
1599
-
1600
-
1601
- def is_major_semver(new_version: Version, current_version: Version) -> bool:
1602
- """
1603
- Check if only the major part of the semantic version has changed.
1604
-
1605
- Args:
1606
- new_version (Version): The new version to check.
1607
- current_version (Version): The current version to compare with.
1608
-
1609
- Returns:
1610
- bool: True if only the major part of the version has changed, False otherwise.
1611
-
1612
- Examples:
1613
- >>> is_major_semver(Version("1.0.0"), Version("0.0.0"))
1614
- True
1615
- >>> is_major_semver(Version("0.0.0"), Version("0.0.0"))
1616
- False
1617
- >>> is_major_semver(Version("1.0.1"), Version("1.0.0"))
1618
- False
1619
- >>> is_major_semver(Version("2.0.0-1"), Version("1.0.1-2"))
1620
- True
1621
- """
1622
-
1623
- return new_version.major != current_version.major
1624
-
1625
-
1626
- def print_release_summary(config: CLIConfig, semver: Optional[str], info: bool = False, dry_run=False):
1627
- if not semver:
1628
- click.echo(FeedbackManager.info_release_no_rollback())
1629
- return
1630
- try:
1631
- client = config.get_client()
1632
- response = client.release_rm(config["id"], semver, confirmation=config["name"], dry_run=True)
1633
- except Exception as e:
1634
- raise CLIReleaseException(FeedbackManager.error_exception(error=str(e)))
1635
- else:
1636
- columns = ["name", "id", "notes"]
1637
- if not response:
1638
- click.echo(FeedbackManager.info_release_no_rollback())
1639
- return
1640
-
1641
- if len(response["datasources"]) or len(response["pipes"]):
1642
- semver = response.get("semver", semver)
1643
- if info:
1644
- if dry_run:
1645
- click.echo(FeedbackManager.info_release_rm_resources_dry_run(semver=semver))
1646
- else:
1647
- click.echo(FeedbackManager.info_release_rm_resources())
1648
- else:
1649
- click.echo(FeedbackManager.info_release_rollback(semver=semver))
1650
-
1651
- if len(response["datasources"]):
1652
- click.echo("\nDatasources:")
1653
- rows = [
1654
- [ds, response["datasources"][ds], response["notes"].get(response["datasources"][ds], "")]
1655
- for ds in response["datasources"]
1656
- ]
1657
- echo_safe_humanfriendly_tables_format_smart_table(rows, column_names=columns)
1658
-
1659
- if len(response["pipes"]):
1660
- click.echo("\nPipes:")
1661
- rows = [
1662
- [pipe, response["pipes"][pipe], response["notes"].get(response["pipes"][pipe], "")]
1663
- for pipe in response["pipes"]
1664
- ]
1665
- echo_safe_humanfriendly_tables_format_smart_table(rows, column_names=columns)
1666
-
1667
-
1668
- def get_oldest_rollback(config: CLIConfig, client: TinyB) -> Optional[str]:
1669
- oldest_rollback_response = client.release_oldest_rollback(config["id"])
1670
- return oldest_rollback_response.get("semver") if oldest_rollback_response else None
1671
-
1672
-
1673
- def remove_release(
1674
- dry_run: bool, config: CLIConfig, semver: Optional[str], client: TinyB, force: bool, show_print=True
1675
- ):
1676
- if semver == OLDEST_ROLLBACK:
1677
- semver = get_oldest_rollback(config, client)
1678
- if show_print:
1679
- print_release_summary(config, semver, info=True, dry_run=True)
1680
- if not dry_run:
1681
- if semver:
1682
- response = client.release_rm(
1683
- config["id"], semver, confirmation=config["name"], dry_run=dry_run, force=force
1684
- )
1685
- click.echo(FeedbackManager.success_release_delete(semver=response.get("semver")))
1686
- else:
1687
- click.echo(FeedbackManager.info_no_release_deleted())
1688
-
1689
-
1690
1625
  def run_aws_iamrole_connection_flow(
1691
1626
  client: TinyB,
1692
1627
  service: str,
@@ -2261,3 +2196,95 @@ def update_cli() -> None:
2261
2196
  click.echo(FeedbackManager.success(message="✓ Tinybird CLI updated"))
2262
2197
  else:
2263
2198
  click.echo(FeedbackManager.info(message="✓ Tinybird CLI is already up-to-date"))
2199
+
2200
+
2201
+ def create_workspace_branch(
2202
+ branch_name: Optional[str],
2203
+ last_partition: bool,
2204
+ all: bool,
2205
+ ignore_datasources: Optional[List[str]],
2206
+ wait: Optional[bool],
2207
+ ) -> None:
2208
+ """
2209
+ Creates a workspace branch
2210
+ """
2211
+ config = CLIConfig.get_project_config()
2212
+ _ = try_update_config_with_remote(config)
2213
+
2214
+ try:
2215
+ workspace = get_current_workspace(config)
2216
+ if not workspace:
2217
+ raise CLIWorkspaceException(FeedbackManager.error_workspace())
2218
+
2219
+ if not branch_name:
2220
+ click.echo(FeedbackManager.info_workspace_branch_create_greeting())
2221
+ default_name = f"{workspace['name']}_{uuid.uuid4().hex[0:4]}"
2222
+ branch_name = click.prompt("\Branch name", default=default_name, err=True, type=str)
2223
+ assert isinstance(branch_name, str)
2224
+
2225
+ response = config.get_client().create_workspace_branch(
2226
+ branch_name,
2227
+ last_partition,
2228
+ all,
2229
+ ignore_datasources,
2230
+ )
2231
+ assert isinstance(response, dict)
2232
+
2233
+ is_job: bool = "job" in response
2234
+ is_summary: bool = "partitions" in response
2235
+
2236
+ if not is_job and not is_summary:
2237
+ raise CLIException(str(response))
2238
+
2239
+ if all and not is_job:
2240
+ raise CLIException(str(response))
2241
+
2242
+ click.echo(
2243
+ FeedbackManager.success_workspace_branch_created(workspace_name=workspace["name"], branch_name=branch_name)
2244
+ )
2245
+
2246
+ job_id: Optional[str] = None
2247
+
2248
+ if is_job:
2249
+ job_id = response["job"]["job_id"]
2250
+ job_url = response["job"]["job_url"]
2251
+ click.echo(FeedbackManager.info_data_branch_job_url(url=job_url))
2252
+
2253
+ if wait and is_job:
2254
+ assert isinstance(job_id, str)
2255
+
2256
+ # Await the job to finish and get the result dict
2257
+ job_response = wait_job(config.get_client(), job_id, job_url, "Environment creation")
2258
+ if job_response is None:
2259
+ raise CLIException(f"Empty job API response (job_id: {job_id}, job_url: {job_url})")
2260
+ else:
2261
+ response = job_response.get("result", {})
2262
+ is_summary = "partitions" in response
2263
+
2264
+ except Exception as e:
2265
+ raise CLIException(FeedbackManager.error_exception(error=str(e)))
2266
+
2267
+
2268
+ async def print_current_branch(config: CLIConfig) -> None:
2269
+ _ = try_update_config_with_remote(config, only_if_needed=True)
2270
+
2271
+ response = config.get_client().user_workspaces_and_branches("v1")
2272
+
2273
+ columns = ["name", "id", "workspace"]
2274
+ table = []
2275
+
2276
+ for workspace in response["workspaces"]:
2277
+ if config["id"] == workspace["id"]:
2278
+ click.echo(FeedbackManager.info_current_branch())
2279
+ if workspace.get("is_branch"):
2280
+ name = workspace["name"]
2281
+ main_workspace = get_current_main_workspace(config)
2282
+ assert isinstance(main_workspace, dict)
2283
+ main_name = main_workspace["name"]
2284
+ else:
2285
+ name = MAIN_BRANCH
2286
+ main_name = workspace["name"]
2287
+ table.append([name, workspace["id"], main_name])
2288
+ break
2289
+
2290
+ echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)