tinybird 0.0.1.dev76__tar.gz → 0.0.1.dev77__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 (103) hide show
  1. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/PKG-INFO +3 -3
  2. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/client.py +16 -1
  3. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/feedback_manager.py +11 -0
  4. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/__cli__.py +2 -2
  5. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/build.py +0 -1
  6. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/cicd.py +1 -1
  7. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/cli.py +5 -1
  8. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/create.py +18 -5
  9. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/local_common.py +5 -2
  10. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/common.py +131 -7
  11. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/config.py +3 -0
  12. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/workspace.py +49 -6
  13. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird.egg-info/PKG-INFO +3 -3
  14. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird.egg-info/requires.txt +1 -1
  15. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/setup.cfg +0 -0
  16. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/__cli__.py +0 -0
  17. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/ch_utils/constants.py +0 -0
  18. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/ch_utils/engine.py +0 -0
  19. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/check_pypi.py +0 -0
  20. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/config.py +0 -0
  21. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/connectors.py +0 -0
  22. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/context.py +0 -0
  23. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/datafile.py +0 -0
  24. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/datatypes.py +0 -0
  25. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/git_settings.py +0 -0
  26. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/prompts.py +0 -0
  27. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/sql.py +0 -0
  28. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/sql_template.py +0 -0
  29. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/sql_template_fmt.py +0 -0
  30. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/sql_toolset.py +0 -0
  31. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/syncasync.py +0 -0
  32. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/cli.py +0 -0
  33. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/auth.py +0 -0
  34. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/common.py +0 -0
  35. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/config.py +0 -0
  36. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/copy.py +0 -0
  37. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/build.py +0 -0
  38. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/build_common.py +0 -0
  39. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  40. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  41. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/common.py +0 -0
  42. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/diff.py +0 -0
  43. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  44. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/fixture.py +0 -0
  45. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/format_common.py +0 -0
  46. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  47. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  48. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  49. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  50. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  51. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/playground.py +0 -0
  52. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datafile/pull.py +0 -0
  53. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/datasource.py +0 -0
  54. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/deployment.py +0 -0
  55. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/endpoint.py +0 -0
  56. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/exceptions.py +0 -0
  57. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/feedback_manager.py +0 -0
  58. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/fmt.py +0 -0
  59. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/job.py +0 -0
  60. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/llm.py +0 -0
  61. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/llm_utils.py +0 -0
  62. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/local.py +0 -0
  63. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/login.py +0 -0
  64. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/materialization.py +0 -0
  65. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/mock.py +0 -0
  66. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/pipe.py +0 -0
  67. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/playground.py +0 -0
  68. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/project.py +0 -0
  69. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/regions.py +0 -0
  70. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/shell.py +0 -0
  71. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/table.py +0 -0
  72. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/tag.py +0 -0
  73. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/telemetry.py +0 -0
  74. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/test.py +0 -0
  75. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  76. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  77. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/token.py +0 -0
  78. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/watch.py +0 -0
  79. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/workspace.py +0 -0
  80. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb/modules/workspace_members.py +0 -0
  81. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli.py +0 -0
  82. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/auth.py +0 -0
  83. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/branch.py +0 -0
  84. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/cicd.py +0 -0
  85. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/cli.py +0 -0
  86. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/connection.py +0 -0
  87. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/datasource.py +0 -0
  88. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/exceptions.py +0 -0
  89. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/fmt.py +0 -0
  90. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/job.py +0 -0
  91. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/pipe.py +0 -0
  92. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/regions.py +0 -0
  93. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/tag.py +0 -0
  94. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/telemetry.py +0 -0
  95. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/test.py +0 -0
  96. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  97. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  98. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  99. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird/tornado_template.py +0 -0
  100. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird.egg-info/SOURCES.txt +0 -0
  101. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird.egg-info/dependency_links.txt +0 -0
  102. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird.egg-info/entry_points.txt +0 -0
  103. {tinybird-0.0.1.dev76 → tinybird-0.0.1.dev77}/tinybird.egg-info/top_level.txt +0 -0
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev76
3
+ Version: 0.0.1.dev77
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
7
7
  Author-email: support@tinybird.co
8
- Requires-Python: >=3.9, <3.13
8
+ Requires-Python: >=3.9, <3.14
9
9
  Description-Content-Type: text/x-rst
10
10
  Requires-Dist: aiofiles==24.1.0
11
11
  Requires-Dist: anthropic==0.42.0
12
12
  Requires-Dist: click<8.2,>=8.1.6
13
- Requires-Dist: clickhouse-toolset==0.33.dev0
13
+ Requires-Dist: clickhouse-toolset==0.34.dev0
14
14
  Requires-Dist: colorama==0.4.6
15
15
  Requires-Dist: cryptography~=41.0.0
16
16
  Requires-Dist: croniter==1.3.8
@@ -709,6 +709,11 @@ class TinyB:
709
709
  async def user_workspaces_and_branches(self):
710
710
  return await self._req("/v0/user/workspaces/?with_environments=true")
711
711
 
712
+ async def user_workspaces_with_organization(self):
713
+ return await self._req(
714
+ "/v0/user/workspaces/?with_environments=false&with_organization=true&with_members_and_owner=false"
715
+ )
716
+
712
717
  async def user_workspace_branches(self):
713
718
  return await self._req("/v0/user/workspaces/?with_environments=true&only_environments=true")
714
719
 
@@ -718,10 +723,17 @@ class TinyB:
718
723
  async def releases(self, workspace_id):
719
724
  return await self._req(f"/v0/workspaces/{workspace_id}/releases")
720
725
 
721
- async def create_workspace(self, name: str, template: Optional[str]):
726
+ async def create_workspace(
727
+ self,
728
+ name: str,
729
+ template: Optional[str],
730
+ assign_to_organization_id: Optional[str] = None,
731
+ ):
722
732
  url = f"/v0/workspaces?name={name}"
723
733
  if template:
724
734
  url += f"&starter_kit={template}"
735
+ if assign_to_organization_id:
736
+ url += f"&assign_to_organization_id={assign_to_organization_id}"
725
737
  return await self._req(url, method="POST", data=b"")
726
738
 
727
739
  async def create_workspace_branch(
@@ -856,6 +868,9 @@ class TinyB:
856
868
  async def workspace_info(self) -> Dict[str, Any]:
857
869
  return await self._req("/v0/workspace")
858
870
 
871
+ async def organization(self, organization_id: str):
872
+ return await self._req(f"/v0/organizations/{organization_id}")
873
+
859
874
  async def wait_for_job(
860
875
  self,
861
876
  job_id: str,
@@ -247,6 +247,14 @@ class FeedbackManager:
247
247
  error_connection_invalid_ca_pem = error_message("Invalid CA certificate in PEM format")
248
248
  error_connection_ca_pem_not_found = error_message("CA certificate in PEM format not found at {ca_pem}")
249
249
  error_workspace = error_message("Workspace {workspace} not found. use 'tb workspace ls' to list your workspaces")
250
+ error_organization_not_found = error_message("Organization with id '{organization_id}' not found")
251
+ error_organization_index = error_message(
252
+ "Error selecting organization '{organization_index}'. Select a valid index or 0 to cancel"
253
+ )
254
+ warning_none_organization = warning_message(
255
+ "Tinybird is now based on organizations. Please, go to the UI ({ui_host}) to follow the migration process. \nYour workspace will be created any way."
256
+ )
257
+ error_while_fetching_orgs = error_message("Error while fetching organizations: {error}")
250
258
  error_deleted_include = error_message(
251
259
  "Related include file {include_file} was deleted and it's used in {filename}. Delete or remove dependency from {filename}."
252
260
  )
@@ -981,6 +989,9 @@ Ready? """
981
989
  success_connection_using = success_message("** Using connection '{connection_name}'")
982
990
  success_using_host = success_message("** Using host: {host} ({name})")
983
991
  success_workspace_created = success_message("** Workspace '{workspace_name}' has been created")
992
+ success_workspace_created_with_organization = success_message(
993
+ "** Workspace '{workspace_name}' has been created in Organization '{organization_name}' (id: '{organization_id}')"
994
+ )
984
995
  success_workspace_branch_created = success_message(
985
996
  "** Branch '{branch_name}' from '{workspace_name}' has been created"
986
997
  )
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '0.0.1.dev76'
8
- __revision__ = '5ecf681'
7
+ __version__ = '0.0.1.dev77'
8
+ __revision__ = '44da91f'
@@ -132,7 +132,6 @@ def build_project(project: Project, tb_client: TinyB, file_changed: Optional[str
132
132
  click.echo(
133
133
  FeedbackManager.warning(message=f"△ {f.get('level')}: {f.get('resource')}: {f.get('message')}")
134
134
  )
135
-
136
135
  elif build_result == "failed":
137
136
  build_errors = result.get("errors")
138
137
  error_msg = None
@@ -25,7 +25,7 @@ on:
25
25
  branches:
26
26
  - main
27
27
  - master
28
- types: [opened, reopened, labeled, unlabeled, synchronize, closed]{% if data_project_dir != '.' %}
28
+ types: [opened, reopened, labeled, unlabeled, synchronize]{% if data_project_dir != '.' %}
29
29
  paths:
30
30
  - '{{ data_project_dir }}/**'{% end %}
31
31
 
@@ -6,6 +6,7 @@
6
6
  import json
7
7
  import logging
8
8
  import os
9
+ import sys
9
10
  from os import getcwd
10
11
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
11
12
 
@@ -123,6 +124,9 @@ async def cli(
123
124
 
124
125
  logging.debug("debug enabled")
125
126
 
127
+ if "--help" in sys.argv or "-h" in sys.argv:
128
+ return
129
+
126
130
  client = await create_ctx_client(ctx, config, cloud, build, staging)
127
131
 
128
132
  if client:
@@ -399,8 +403,8 @@ async def create_ctx_client(ctx: Context, config: Dict[str, Any], cloud: bool, b
399
403
  commands_always_local = ["create"]
400
404
  if (
401
405
  (cloud or command in commands_always_cloud)
402
- and command not in commands_always_build
403
406
  and command not in commands_always_local
407
+ and command not in commands_always_build
404
408
  ):
405
409
  click.echo(
406
410
  FeedbackManager.gray(message=f"Running against Tinybird Cloud: Workspace {config.get('name', 'default')}")
@@ -50,6 +50,7 @@ async def create(ctx: click.Context, data: Optional[str], prompt: Optional[str],
50
50
  config = CLIConfig.get_project_config(str(project.path))
51
51
  tb_client = config.get_client()
52
52
  user_token: Optional[str] = None
53
+ created = False
53
54
  if prompt:
54
55
  try:
55
56
  user_token = config.get_user_token()
@@ -68,24 +69,24 @@ async def create(ctx: click.Context, data: Optional[str], prompt: Optional[str],
68
69
  click.echo(FeedbackManager.highlight(message="\n» Creating new project structure..."))
69
70
  create_project_structure(folder)
70
71
  click.echo(FeedbackManager.success(message="✓ Scaffolding completed!\n"))
71
-
72
+ created = True
72
73
  result = ""
73
74
  if data or prompt:
74
75
  click.echo(FeedbackManager.highlight(message="\n» Creating resources..."))
75
76
  result = await create_resources(local_client, tb_client, user_token, data, prompt, folder)
76
77
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
77
-
78
+ created = True
78
79
  if not already_has_cicd(folder):
79
80
  click.echo(FeedbackManager.highlight(message="\n» Creating CI/CD files for GitHub and GitLab..."))
80
81
  init_git(folder)
81
82
  await init_cicd(data_project_dir=os.path.relpath(folder))
82
83
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
83
-
84
+ created = True
84
85
  if not already_has_cursor_rules(folder):
85
86
  click.echo(FeedbackManager.highlight(message="\n» Creating .cursorrules..."))
86
87
  create_rules(folder, source, "cursor")
87
88
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
88
-
89
+ created = True
89
90
  if should_generate_fixtures(result):
90
91
  click.echo(FeedbackManager.highlight(message="\n» Generating fixtures..."))
91
92
 
@@ -95,6 +96,7 @@ async def create(ctx: click.Context, data: Optional[str], prompt: Optional[str],
95
96
  datasource_path = Path(folder) / "datasources" / f"{ds_name}.datasource"
96
97
  click.echo(FeedbackManager.info(message=f"✓ /fixtures/{ds_name}"))
97
98
  persist_fixture(ds_name, data_content, folder)
99
+ created = True
98
100
  elif prompt and user_token:
99
101
  datasource_files = [f for f in os.listdir(Path(folder) / "datasources") if f.endswith(".datasource")]
100
102
  for datasource_file in datasource_files:
@@ -113,6 +115,9 @@ async def create(ctx: click.Context, data: Optional[str], prompt: Optional[str],
113
115
  if data:
114
116
  persist_fixture(datasource_name, data, folder)
115
117
  click.echo(FeedbackManager.info(message=f"✓ /fixtures/{datasource_name}"))
118
+ created = True
119
+ if not created:
120
+ click.echo(FeedbackManager.warning(message="△ No resources created\n"))
116
121
  except Exception as e:
117
122
  click.echo(FeedbackManager.error(message=f"Error: {str(e)}"))
118
123
 
@@ -121,7 +126,15 @@ PROJECT_PATHS = ("datasources", "endpoints", "materializations", "copies", "pipe
121
126
 
122
127
 
123
128
  def validate_project_structure(folder: str) -> bool:
124
- return all((Path(folder) / path).exists() for path in PROJECT_PATHS)
129
+ some_folder_created = any((Path(folder) / path).exists() for path in PROJECT_PATHS)
130
+ if some_folder_created:
131
+ return True
132
+
133
+ folder_path = Path(folder)
134
+ datasources = list(folder_path.glob("**/*.datasource"))
135
+ pipes = list(folder_path.glob("**/*.pipe"))
136
+
137
+ return len(datasources) > 0 or len(pipes) > 0
125
138
 
126
139
 
127
140
  def should_generate_fixtures(result: str) -> bool:
@@ -48,13 +48,16 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any], build: bool = Fa
48
48
 
49
49
  logging.debug(f"Workspace used for build: {ws_name}")
50
50
 
51
- user_workspaces = requests.get(f"{TB_LOCAL_HOST}/v0/user/workspaces?token={admin_token}").json()
51
+ user_workspaces = requests.get(
52
+ f"{TB_LOCAL_HOST}/v0/user/workspaces?with_organization=true&token={admin_token}"
53
+ ).json()
54
+ user_org_id = user_workspaces.get("organization_id", {})
52
55
  local_workspaces = user_workspaces.get("workspaces", [])
53
56
 
54
57
  ws = next((ws for ws in local_workspaces if ws["name"] == ws_name), None)
55
58
 
56
59
  if not ws:
57
- await user_client.create_workspace(ws_name, template=None)
60
+ await user_client.create_workspace(ws_name, template=None, assign_to_organization_id=user_org_id)
58
61
  user_workspaces = requests.get(f"{TB_LOCAL_HOST}/v0/user/workspaces?token={admin_token}").json()
59
62
  ws = next((ws for ws in user_workspaces["workspaces"] if ws["name"] == ws_name), None)
60
63
  if not ws:
@@ -18,7 +18,7 @@ from enum import Enum
18
18
  from functools import wraps
19
19
  from os import chmod, environ, getcwd, getenv
20
20
  from pathlib import Path
21
- from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union
21
+ from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Literal, Optional, Set, Tuple, TypedDict, Union
22
22
  from urllib.parse import urlparse
23
23
 
24
24
  import aiofiles
@@ -707,7 +707,13 @@ async def fork_workspace(client: TinyB, user_client: TinyB, created_workspace):
707
707
 
708
708
 
709
709
  async def create_workspace_non_interactive(
710
- ctx: Context, workspace_name: str, starterkit: str, user_token: str, fork: bool
710
+ ctx: Context,
711
+ workspace_name: str,
712
+ starterkit: Optional[str],
713
+ user_token: str,
714
+ fork: bool,
715
+ organization_id: Optional[str],
716
+ organization_name: Optional[str],
711
717
  ):
712
718
  """Creates a workspace using the provided name and starterkit"""
713
719
  client: TinyB = ctx.ensure_object(dict)["client"]
@@ -716,8 +722,15 @@ async def create_workspace_non_interactive(
716
722
  user_client: TinyB = deepcopy(client)
717
723
  user_client.token = user_token
718
724
 
719
- created_workspace = await user_client.create_workspace(workspace_name, starterkit)
720
- click.echo(FeedbackManager.success_workspace_created(workspace_name=workspace_name))
725
+ created_workspace = await user_client.create_workspace(workspace_name, starterkit, organization_id)
726
+ if organization_id and organization_name:
727
+ click.echo(
728
+ FeedbackManager.success_workspace_created_with_organization(
729
+ workspace_name=workspace_name, organization_name=organization_name, organization_id=organization_id
730
+ )
731
+ )
732
+ else:
733
+ click.echo(FeedbackManager.success_workspace_created(workspace_name=workspace_name))
721
734
 
722
735
  if fork:
723
736
  await fork_workspace(client, user_client, created_workspace)
@@ -727,7 +740,13 @@ async def create_workspace_non_interactive(
727
740
 
728
741
 
729
742
  async def create_workspace_interactive(
730
- ctx: Context, workspace_name: Optional[str], starterkit: Optional[str], user_token: str, fork: bool
743
+ ctx: Context,
744
+ workspace_name: Optional[str],
745
+ starterkit: Optional[str],
746
+ user_token: str,
747
+ fork: bool,
748
+ organization_id: Optional[str],
749
+ organization_name: Optional[str],
731
750
  ):
732
751
  if not starterkit and not is_ci_environment():
733
752
  click.echo("\n")
@@ -744,8 +763,17 @@ async def create_workspace_interactive(
744
763
  click.echo(FeedbackManager.info_workspace_create_greeting())
745
764
  default_name = f"new_workspace_{uuid.uuid4().hex[0:4]}"
746
765
  workspace_name = click.prompt("\nWorkspace name", default=default_name, err=True, type=str)
747
-
748
- await create_workspace_non_interactive(ctx, workspace_name, starterkit, user_token, fork) # type: ignore
766
+ assert isinstance(workspace_name, str)
767
+
768
+ await create_workspace_non_interactive(
769
+ ctx,
770
+ workspace_name,
771
+ starterkit,
772
+ user_token,
773
+ fork,
774
+ organization_id=organization_id,
775
+ organization_name=organization_name,
776
+ )
749
777
 
750
778
 
751
779
  async def create_workspace_branch(
@@ -2080,3 +2108,99 @@ def get_ca_pem_content(ca_pem: Optional[str], filename: Optional[str] = None) ->
2080
2108
  raise CLIConnectionException(FeedbackManager.error_connection_invalid_ca_pem())
2081
2109
 
2082
2110
  return ca_pem_content
2111
+
2112
+
2113
+ async def get_organizations_by_user(ctx: Context, user_token: str) -> List[Dict[str, Any]]:
2114
+ """Fetches all organizations by user using the provided user token"""
2115
+ client: TinyB = ctx.ensure_object(dict)["client"]
2116
+ organizations = []
2117
+
2118
+ try:
2119
+ user_client: TinyB = deepcopy(client)
2120
+ user_client.token = user_token
2121
+ user_workspaces = await user_client.user_workspaces_with_organization()
2122
+ admin_org_id = user_workspaces.get("organization_id")
2123
+ seen_org_ids = set()
2124
+
2125
+ for workspace in user_workspaces.get("workspaces"):
2126
+ org = workspace.get("organization")
2127
+ if org and org.get("id") not in seen_org_ids:
2128
+ org["is_admin"] = org.get("id") == admin_org_id
2129
+ organizations.append(org)
2130
+ seen_org_ids.add(org.get("id"))
2131
+
2132
+ # Case: user is admin of an organization but not a member of any workspace in it
2133
+ if admin_org_id and admin_org_id not in seen_org_ids:
2134
+ org = await user_client.organization(admin_org_id)
2135
+ org["id"] = admin_org_id
2136
+ org["is_admin"] = True
2137
+ organizations.append(org)
2138
+
2139
+ except Exception as e:
2140
+ raise CLIWorkspaceException(FeedbackManager.error_while_fetching_orgs(error=str(e)))
2141
+ return organizations
2142
+
2143
+
2144
+ OrgType = Literal["tinybird", "domain", "admin", "member"]
2145
+
2146
+
2147
+ class Organization(TypedDict):
2148
+ id: str
2149
+ name: str
2150
+ role: str
2151
+ domains: Optional[List[str]]
2152
+ type: OrgType
2153
+
2154
+
2155
+ def sort_organizations_by_user(organizations: List[Dict[str, Any]], user_email: Optional[str]) -> List[Organization]:
2156
+ """Sort organizations based on type: tinybird > domain > admin > member"""
2157
+ sorted_organizations: List[Organization] = []
2158
+ user_domain = user_email.split("@")[1] if user_email else None
2159
+ is_tinybird_user = user_domain == "tinybird.co"
2160
+
2161
+ for org in organizations:
2162
+ domain = org.get("domain") or ""
2163
+ domains = domain.split(",") if domain else None
2164
+ role: OrgType = "admin" if org.get("is_admin") else "member"
2165
+ type = role
2166
+ if domains and user_domain and user_domain in domains:
2167
+ type = "domain"
2168
+ if org.get("name") == "Tinybird" and is_tinybird_user:
2169
+ type = "tinybird"
2170
+
2171
+ sorted_organizations.append(
2172
+ {
2173
+ "id": org.get("id") or "",
2174
+ "name": org.get("name") or "",
2175
+ "role": role,
2176
+ "domains": [domain.strip() for domain in domains] if domains else None,
2177
+ "type": type,
2178
+ }
2179
+ )
2180
+
2181
+ type_priority: Dict[OrgType, int] = {"tinybird": 0, "domain": 1, "admin": 2, "member": 3}
2182
+
2183
+ sorted_organizations.sort(key=lambda x: type_priority[x["type"]])
2184
+
2185
+ return sorted_organizations
2186
+
2187
+
2188
+ async def ask_for_organization_interactively(organizations: List[Organization]) -> Optional[Organization]:
2189
+ rows = [(index + 1, org["name"], org["role"], org["id"]) for index, org in enumerate(organizations)]
2190
+
2191
+ echo_safe_humanfriendly_tables_format_smart_table(rows, column_names=["Idx", "Name", "Role", "Id"])
2192
+ click.echo("")
2193
+ click.echo(" [0] to cancel")
2194
+
2195
+ org_index = -1
2196
+ while org_index == -1:
2197
+ org_index = click.prompt("\nSelect an organization to include the workspace in", default=1)
2198
+ if org_index < 0 or org_index > len(organizations):
2199
+ click.echo(FeedbackManager.error_organization_index(organization_index=org_index))
2200
+ org_index = -1
2201
+
2202
+ if org_index == 0:
2203
+ click.echo(FeedbackManager.info_cancelled_by_user())
2204
+ return None
2205
+
2206
+ return organizations[org_index - 1]
@@ -259,6 +259,9 @@ class CLIConfig:
259
259
  def get_user_client(self, host: Optional[str] = None) -> tbc.TinyB:
260
260
  return self.get_client(self.get_user_token(), host)
261
261
 
262
+ def get_user_email(self) -> Optional[str]:
263
+ return self["user_email"]
264
+
262
265
  def set_workspace_token(self, workspace_id: str, token: str) -> None:
263
266
  pass
264
267
 
@@ -15,6 +15,7 @@ from tinybird.feedback_manager import FeedbackManager
15
15
  from tinybird.tb_cli_modules.cli import cli
16
16
  from tinybird.tb_cli_modules.common import (
17
17
  _get_workspace_plan_name,
18
+ ask_for_organization_interactively,
18
19
  ask_for_user_token,
19
20
  check_user_token,
20
21
  coro,
@@ -22,8 +23,10 @@ from tinybird.tb_cli_modules.common import (
22
23
  create_workspace_non_interactive,
23
24
  echo_safe_humanfriendly_tables_format_smart_table,
24
25
  get_current_main_workspace,
26
+ get_organizations_by_user,
25
27
  is_valid_starterkit,
26
28
  print_current_workspace,
29
+ sort_organizations_by_user,
27
30
  switch_workspace,
28
31
  )
29
32
  from tinybird.tb_cli_modules.config import CLIConfig
@@ -193,29 +196,69 @@ async def clear_workspace(ctx: Context, yes: bool, dry_run: bool) -> None:
193
196
  default=False,
194
197
  help="When enabled, we will share all datasource from the current workspace to the new created one",
195
198
  )
199
+ @click.option(
200
+ "--organization-id",
201
+ "organization_id",
202
+ type=str,
203
+ required=False,
204
+ help="When passed, the workspace will be created in the specified organization",
205
+ )
196
206
  @click.pass_context
197
207
  @coro
198
208
  async def create_workspace(
199
- ctx: Context, workspace_name: str, starter_kit: str, user_token: Optional[str], fork: bool
209
+ ctx: Context,
210
+ workspace_name: str,
211
+ starter_kit: str,
212
+ user_token: Optional[str],
213
+ fork: bool,
214
+ organization_id: Optional[str],
200
215
  ) -> None:
201
216
  if starter_kit and not await is_valid_starterkit(ctx, starter_kit):
202
217
  raise CLIWorkspaceException(FeedbackManager.error_starterkit_name(starterkit_name=starter_kit))
203
218
 
219
+ config = CLIConfig.get_project_config()
220
+ host = config.get_host() or CLIConfig.DEFAULTS["host"]
221
+ ui_host = get_display_host(host)
222
+
204
223
  if not user_token:
205
- config = CLIConfig.get_project_config()
206
- host = config.get_host() or CLIConfig.DEFAULTS["host"]
207
- ui_host = get_display_host(host)
208
224
  user_token = ask_for_user_token("create a new workspace", ui_host)
209
225
  if not user_token:
210
226
  return
211
227
  await check_user_token(ctx, user_token)
212
228
 
229
+ organization_name = None
230
+ organizations = await get_organizations_by_user(ctx, user_token)
231
+
232
+ if organization_id:
233
+ organization = next((org for org in organizations if org.get("id") == organization_id), None)
234
+ if not organization:
235
+ raise CLIWorkspaceException(FeedbackManager.error_organization_not_found(organization_id=organization_id))
236
+ organization_name = organization.get("name")
237
+ else:
238
+ if len(organizations) == 0:
239
+ click.echo(FeedbackManager.warning_none_organization(ui_host=ui_host))
240
+ elif len(organizations) == 1:
241
+ organization_id = organizations[0].get("id")
242
+ organization_name = organizations[0].get("name")
243
+ else:
244
+ sorted_organizations = sort_organizations_by_user(organizations, user_email=config.get_user_email())
245
+ current_organization = await ask_for_organization_interactively(sorted_organizations)
246
+ if current_organization:
247
+ organization_id = current_organization.get("id")
248
+ organization_name = current_organization.get("name")
249
+ else:
250
+ return
251
+
213
252
  # If we have at least workspace_name, we start the non interactive
214
253
  # process, creating an empty workspace
215
254
  if workspace_name:
216
- await create_workspace_non_interactive(ctx, workspace_name, starter_kit, user_token, fork)
255
+ await create_workspace_non_interactive(
256
+ ctx, workspace_name, starter_kit, user_token, fork, organization_id, organization_name
257
+ )
217
258
  else:
218
- await create_workspace_interactive(ctx, workspace_name, starter_kit, user_token, fork)
259
+ await create_workspace_interactive(
260
+ ctx, workspace_name, starter_kit, user_token, fork, organization_id, organization_name
261
+ )
219
262
 
220
263
 
221
264
  @workspace.command(name="delete", short_help="Delete a Workspace for your Tinybird user")
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev76
3
+ Version: 0.0.1.dev77
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
7
7
  Author-email: support@tinybird.co
8
- Requires-Python: >=3.9, <3.13
8
+ Requires-Python: >=3.9, <3.14
9
9
  Description-Content-Type: text/x-rst
10
10
  Requires-Dist: aiofiles==24.1.0
11
11
  Requires-Dist: anthropic==0.42.0
12
12
  Requires-Dist: click<8.2,>=8.1.6
13
- Requires-Dist: clickhouse-toolset==0.33.dev0
13
+ Requires-Dist: clickhouse-toolset==0.34.dev0
14
14
  Requires-Dist: colorama==0.4.6
15
15
  Requires-Dist: cryptography~=41.0.0
16
16
  Requires-Dist: croniter==1.3.8
@@ -1,7 +1,7 @@
1
1
  aiofiles==24.1.0
2
2
  anthropic==0.42.0
3
3
  click<8.2,>=8.1.6
4
- clickhouse-toolset==0.33.dev0
4
+ clickhouse-toolset==0.34.dev0
5
5
  colorama==0.4.6
6
6
  cryptography~=41.0.0
7
7
  croniter==1.3.8
File without changes