tinybird 0.0.1.dev152__tar.gz → 0.0.1.dev154__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 (110) hide show
  1. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/PKG-INFO +2 -2
  2. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/__cli__.py +3 -3
  3. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/build.py +39 -10
  4. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/cli.py +1 -1
  5. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/deployment.py +41 -8
  6. tinybird-0.0.1.dev154/tinybird/tb/modules/dev_server.py +257 -0
  7. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/local.py +62 -7
  8. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/local_common.py +18 -0
  9. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/project.py +2 -1
  10. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird.egg-info/PKG-INFO +2 -2
  11. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird.egg-info/SOURCES.txt +1 -0
  12. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/setup.cfg +0 -0
  13. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/__cli__.py +0 -0
  14. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/ch_utils/constants.py +0 -0
  15. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/ch_utils/engine.py +0 -0
  16. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/check_pypi.py +0 -0
  17. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/client.py +0 -0
  18. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/config.py +0 -0
  19. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/connectors.py +0 -0
  20. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/context.py +0 -0
  21. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/datafile.py +0 -0
  22. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/datatypes.py +0 -0
  23. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/feedback_manager.py +0 -0
  24. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/git_settings.py +0 -0
  25. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/prompts.py +0 -0
  26. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/sql.py +0 -0
  27. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/sql_template.py +0 -0
  28. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/sql_template_fmt.py +0 -0
  29. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/sql_toolset.py +0 -0
  30. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/syncasync.py +0 -0
  31. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/check_pypi.py +0 -0
  32. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/cli.py +0 -0
  33. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/client.py +0 -0
  34. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/config.py +0 -0
  35. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/auth.py +0 -0
  36. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/cicd.py +0 -0
  37. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/common.py +0 -0
  38. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/config.py +0 -0
  39. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/connection.py +0 -0
  40. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/copy.py +0 -0
  41. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/create.py +0 -0
  42. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/build.py +0 -0
  43. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/build_common.py +0 -0
  44. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  45. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  46. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/common.py +0 -0
  47. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/diff.py +0 -0
  48. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  49. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/fixture.py +0 -0
  50. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/format_common.py +0 -0
  51. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  52. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  53. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  54. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  55. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  56. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/playground.py +0 -0
  57. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datafile/pull.py +0 -0
  58. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/datasource.py +0 -0
  59. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/deprecations.py +0 -0
  60. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/endpoint.py +0 -0
  61. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/exceptions.py +0 -0
  62. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/feedback_manager.py +0 -0
  63. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/infra.py +0 -0
  64. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/job.py +0 -0
  65. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/llm.py +0 -0
  66. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/llm_utils.py +0 -0
  67. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/login.py +0 -0
  68. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/logout.py +0 -0
  69. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/materialization.py +0 -0
  70. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/mock.py +0 -0
  71. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/open.py +0 -0
  72. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/pipe.py +0 -0
  73. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/regions.py +0 -0
  74. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/secret.py +0 -0
  75. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/shell.py +0 -0
  76. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/table.py +0 -0
  77. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/telemetry.py +0 -0
  78. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/test.py +0 -0
  79. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  80. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  81. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/token.py +0 -0
  82. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/watch.py +0 -0
  83. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/workspace.py +0 -0
  84. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb/modules/workspace_members.py +0 -0
  85. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli.py +0 -0
  86. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/auth.py +0 -0
  87. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/branch.py +0 -0
  88. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/cicd.py +0 -0
  89. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/cli.py +0 -0
  90. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/common.py +0 -0
  91. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/config.py +0 -0
  92. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/connection.py +0 -0
  93. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/datasource.py +0 -0
  94. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/exceptions.py +0 -0
  95. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/fmt.py +0 -0
  96. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/job.py +0 -0
  97. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/pipe.py +0 -0
  98. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/regions.py +0 -0
  99. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/tag.py +0 -0
  100. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/telemetry.py +0 -0
  101. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/test.py +0 -0
  102. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  103. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  104. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/workspace.py +0 -0
  105. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  106. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird/tornado_template.py +0 -0
  107. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird.egg-info/dependency_links.txt +0 -0
  108. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird.egg-info/entry_points.txt +0 -0
  109. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird.egg-info/requires.txt +0 -0
  110. {tinybird-0.0.1.dev152 → tinybird-0.0.1.dev154}/tinybird.egg-info/top_level.txt +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev152
3
+ Version: 0.0.1.dev154
4
4
  Summary: Tinybird Command Line Tool
5
- Home-page: https://www.tinybird.co/docs/cli/introduction.html
5
+ Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
7
7
  Author-email: support@tinybird.co
8
8
  Requires-Python: >=3.9, <3.14
@@ -1,8 +1,8 @@
1
1
 
2
2
  __name__ = 'tinybird'
3
3
  __description__ = 'Tinybird Command Line Tool'
4
- __url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
4
+ __url__ = 'https://www.tinybird.co/docs/forward/commands'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '0.0.1.dev152'
8
- __revision__ = '100f32b'
7
+ __version__ = '0.0.1.dev154'
8
+ __revision__ = 'db74d7e'
@@ -23,6 +23,7 @@ from tinybird.tb.modules.datafile.fixture import FixtureExtension, get_fixture_d
23
23
  from tinybird.tb.modules.datafile.parse_datasource import parse_datasource
24
24
  from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
25
25
  from tinybird.tb.modules.datafile.playground import folder_playground
26
+ from tinybird.tb.modules.dev_server import BuildStatus, start_server
26
27
  from tinybird.tb.modules.feedback_manager import FeedbackManager
27
28
  from tinybird.tb.modules.project import Project
28
29
  from tinybird.tb.modules.shell import Shell, print_table_formatted
@@ -50,16 +51,28 @@ def build(ctx: click.Context, watch: bool) -> None:
50
51
 
51
52
  @cli.command("dev", help="Build the project server side and watch for changes.")
52
53
  @click.option("--data-origin", type=str, default="", help="Data origin: local or cloud")
54
+ @click.option("--ui", is_flag=True, default=False, help="Connect your local project to Tinybird UI")
53
55
  @click.pass_context
54
- def dev(ctx: click.Context, data_origin: str) -> None:
56
+ def dev(ctx: click.Context, data_origin: str, ui: bool) -> None:
55
57
  if data_origin == "cloud":
56
58
  return dev_cloud(ctx)
57
-
58
59
  project: Project = ctx.ensure_object(dict)["project"]
59
60
  tb_client: TinyB = ctx.ensure_object(dict)["client"]
61
+ build_status = BuildStatus()
62
+ if ui:
63
+ server_thread = threading.Thread(
64
+ target=start_server, args=(project, tb_client, process, build_status), daemon=True
65
+ )
66
+ server_thread.start()
67
+ # Wait for the server to start
68
+ time.sleep(0.5)
60
69
  click.echo(FeedbackManager.highlight_building_project())
61
- process(project=project, tb_client=tb_client, watch=True)
62
- run_watch(project=project, tb_client=tb_client, process=partial(process, project=project, tb_client=tb_client))
70
+ process(project=project, tb_client=tb_client, watch=True, build_status=build_status)
71
+ run_watch(
72
+ project=project,
73
+ tb_client=tb_client,
74
+ process=partial(process, project=project, tb_client=tb_client, build_status=build_status),
75
+ )
63
76
 
64
77
 
65
78
  def build_project(project: Project, tb_client: TinyB, file_changed: Optional[str] = None, silent: bool = False) -> None:
@@ -171,7 +184,7 @@ def build_project(project: Project, tb_client: TinyB, file_changed: Optional[str
171
184
  build_errors = result.get("errors")
172
185
  full_error_msg = ""
173
186
  for build_error in build_errors:
174
- filename_bit = build_error.get("filename", "")
187
+ filename_bit = build_error.get("filename", build_error.get("resource", ""))
175
188
  error_bit = build_error.get("error") or build_error.get("message") or ""
176
189
  error_msg = ((filename_bit + "\n") if filename_bit else "") + error_bit
177
190
  full_error_msg += error_msg + "\n\n"
@@ -266,10 +279,17 @@ def process(
266
279
  file_changed: Optional[str] = None,
267
280
  diff: Optional[str] = None,
268
281
  silent: bool = False,
269
- ) -> None:
282
+ error: bool = False,
283
+ build_status: Optional[BuildStatus] = None,
284
+ ) -> Optional[str]:
270
285
  time_start = time.time()
271
286
  build_failed = False
272
287
  build_error: Optional[str] = None
288
+ if build_status:
289
+ if build_status.building:
290
+ return build_status.error
291
+ else:
292
+ build_status.building = True
273
293
  if file_changed and (file_changed.endswith(FixtureExtension.NDJSON) or file_changed.endswith(FixtureExtension.CSV)):
274
294
  rebuild_fixture(project, tb_client, file_changed)
275
295
  elif file_changed and file_changed.endswith(".sql"):
@@ -277,6 +297,9 @@ def process(
277
297
  else:
278
298
  try:
279
299
  build_project(project, tb_client, file_changed, silent)
300
+ if build_status:
301
+ build_status.building = False
302
+ build_status.error = None
280
303
  except click.ClickException as e:
281
304
  click.echo(FeedbackManager.info(message=str(e)))
282
305
  build_error = str(e)
@@ -284,7 +307,8 @@ def process(
284
307
  try:
285
308
  if file_changed and not build_failed:
286
309
  asyncio.run(folder_build(project, tb_client, filenames=[file_changed]))
287
- show_data(tb_client, file_changed, diff)
310
+ if not build_status:
311
+ show_data(tb_client, file_changed, diff)
288
312
  except Exception:
289
313
  pass
290
314
 
@@ -296,14 +320,19 @@ def process(
296
320
  click.echo(FeedbackManager.error(message=f"✗ {rebuild_str} failed"))
297
321
  if not watch:
298
322
  sys_exit("build_error", build_error or "Unknown error")
323
+ build_error = build_error or "Unknown error"
324
+ if build_status:
325
+ build_status.error = build_error
326
+ build_status.building = False
327
+ return build_error
299
328
  else:
300
329
  if not silent:
301
330
  click.echo(FeedbackManager.success(message=f"\n✓ {rebuild_str} completed in {elapsed_time:.1f}s"))
302
331
 
332
+ return None
303
333
 
304
- def run_watch(
305
- project: Project, tb_client: TinyB, process: Callable[[bool, Optional[str], Optional[str]], None]
306
- ) -> None:
334
+
335
+ def run_watch(project: Project, tb_client: TinyB, process: Callable) -> None:
307
336
  shell = Shell(project=project, tb_client=tb_client, playground=False)
308
337
  click.echo(FeedbackManager.gray(message="\nWatching for changes..."))
309
338
  watcher_thread = threading.Thread(
@@ -141,7 +141,7 @@ async def cli(
141
141
  config = await get_config(host, token, user_token=user_token, config_file=config_temp._path)
142
142
  client = _get_tb_client(config.get("token", None), config["host"])
143
143
  folder = os.path.join(config_temp._path.replace(".tinyb", ""), config.get("cwd", os.getcwd()))
144
- project = Project(folder=folder)
144
+ project = Project(folder=folder, workspace_name=config.get("name", ""))
145
145
  config["path"] = str(project.path)
146
146
  # If they have passed a token or host as parameter and it's different that record in .tinyb, refresh the workspace id
147
147
  if token or host:
@@ -68,7 +68,6 @@ def download_github_template(url: str) -> Optional[Path]:
68
68
  """
69
69
  # Parse GitHub URL components
70
70
  # From: https://github.com/owner/repo/tree/branch/path
71
- # To: api.github.com/repos/owner/repo/contents/path?ref=branch
72
71
  parts = url.replace("https://github.com/", "").split("/")
73
72
  if len(parts) < 5 or "tree" not in parts:
74
73
  click.echo(
@@ -83,16 +82,50 @@ def download_github_template(url: str) -> Optional[Path]:
83
82
  branch = parts[parts.index("tree") + 1]
84
83
  path = "/".join(parts[parts.index("tree") + 2 :])
85
84
 
86
- api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref={branch}"
87
-
88
- # Create temporary directory for template
85
+ try:
86
+ import shutil
87
+ import subprocess
88
+ import tempfile
89
+
90
+ # Create a temporary directory for cloning
91
+ with tempfile.TemporaryDirectory() as temp_dir:
92
+ # Clone the specific branch with minimum depth
93
+ repo_url = f"https://github.com/{owner}/{repo}.git"
94
+ subprocess.run(
95
+ ["git", "clone", "--depth", "1", "--branch", branch, repo_url, temp_dir],
96
+ check=True,
97
+ capture_output=True,
98
+ )
89
99
 
90
- dir = Path(".")
100
+ # Copy the specific path to current directory
101
+ source_path = Path(temp_dir) / path
102
+ if not source_path.exists():
103
+ click.echo(FeedbackManager.error(message=f"Path {path} not found in repository"))
104
+ return None
105
+
106
+ dir = Path(".")
107
+ if source_path.is_dir():
108
+ # Copy directory contents
109
+ for item in source_path.iterdir():
110
+ dest = dir / item.name
111
+ if item.is_dir():
112
+ shutil.copytree(item, dest)
113
+ else:
114
+ shutil.copy2(item, dest)
115
+ click.echo(FeedbackManager.info(message=f"Downloaded {item.name}"))
116
+ else:
117
+ # Copy single file
118
+ shutil.copy2(source_path, dir / source_path.name)
119
+ click.echo(FeedbackManager.info(message=f"Downloaded {source_path.name}"))
91
120
 
92
- # Recursively download all files
93
- download_github_contents(api_url, dir)
121
+ return dir
94
122
 
95
- return dir
123
+ except subprocess.CalledProcessError as e:
124
+ click.echo(FeedbackManager.error(message=f"Git clone failed: {e.stderr.decode()}"))
125
+ return None
126
+ except Exception as e:
127
+ click.echo(FeedbackManager.error(message=f"Error downloading template: {str(e)}"))
128
+ return None
96
129
 
97
130
 
98
131
  # TODO(eclbg): This should eventually end up in client.py, but we're not using it here yet.
@@ -0,0 +1,257 @@
1
+ import glob
2
+ import http.server
3
+ import json
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Callable, Optional
7
+
8
+ import click
9
+
10
+ from tinybird.tb.client import TinyB
11
+ from tinybird.tb.modules.common import sys_exit
12
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
13
+ from tinybird.tb.modules.local_common import TB_LOCAL_PORT
14
+ from tinybird.tb.modules.project import Project
15
+
16
+
17
+ class BuildStatus:
18
+ def __init__(self):
19
+ self.last_build_time = 0
20
+ self.building = False
21
+ self.error: Optional[str] = None
22
+ self.result: Optional[str] = None
23
+
24
+
25
+ class DevServer(http.server.HTTPServer):
26
+ project: Project
27
+ tb_client: TinyB
28
+ process: Callable
29
+
30
+ def __init__(
31
+ self,
32
+ process: Callable,
33
+ project: Project,
34
+ build_status: BuildStatus,
35
+ tb_client: TinyB,
36
+ ):
37
+ port = 49161
38
+ self.project = project
39
+ self.tb_client = tb_client
40
+ self.process = process
41
+ self.build_status = build_status
42
+ self.port = port
43
+
44
+ try:
45
+ super().__init__(("", port), DevHandler)
46
+ click.echo(FeedbackManager.success(message=f"✓ Dev server running on http://localhost:{port}"))
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
+ )
52
+ except OSError as e:
53
+ if e.errno == 48: # Address already in use
54
+ click.echo(FeedbackManager.error(message=f"Port {port} is already in use. Try a different port."))
55
+ sys_exit("port_in_use", f"Port {port} is already in use")
56
+ else:
57
+ click.echo(FeedbackManager.error_exception(error=e))
58
+ sys_exit("server_error", str(e))
59
+
60
+
61
+ class DevHandler(http.server.SimpleHTTPRequestHandler):
62
+ server: DevServer
63
+
64
+ def send_cors_headers(self):
65
+ self.send_header("Access-Control-Allow-Origin", "*")
66
+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
67
+ self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
68
+
69
+ def do_OPTIONS(self):
70
+ self.send_response(200)
71
+ self.send_cors_headers()
72
+ self.end_headers()
73
+
74
+ def do_GET(self):
75
+ try:
76
+ project: Project = self.server.project
77
+
78
+ if self.path == "/":
79
+ self.send_response(200)
80
+ self.send_header("Content-Type", "text/html")
81
+ self.send_cors_headers()
82
+ self.end_headers()
83
+
84
+ html = f"""
85
+ <!DOCTYPE html>
86
+ <html>
87
+ <head>
88
+ <title>Tinybird Dev Server</title>
89
+ <style>
90
+ body {{ font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }}
91
+ h1 {{ color: #333; }}
92
+ ul {{ list-style-type: none; padding: 0; }}
93
+ li {{ margin-bottom: 10px; }}
94
+ a {{ color: #0066cc; text-decoration: none; }}
95
+ a:hover {{ text-decoration: underline; }}
96
+ </style>
97
+ </head>
98
+ <body>
99
+ <h1>Tinybird Dev Server</h1>
100
+ <p>Server running for project: {project.path}</p>
101
+ <p>Access your local environment at <a href="https://cloud.tinybird.co/local/{TB_LOCAL_PORT}/{project.workspace_name}/project">https://cloud.tinybird.co/local/{TB_LOCAL_PORT}/{project.workspace_name}/project</a> and edit your project.</p>
102
+ <h2>API Endpoints:</h2>
103
+ <ul>
104
+ <li><a href="/files">/files</a> - List all project files</li>
105
+ </ul>
106
+ </body>
107
+ </html>
108
+ """
109
+ self.wfile.write(html.encode())
110
+ return
111
+
112
+ elif "/files/" in self.path:
113
+ file_path = self.path.split("/files/")[1].split("?")[0]
114
+ project_files = project.get_project_files()
115
+ project_file = next((Path(f) for f in project_files if f.endswith(file_path)), None)
116
+ if not project_file:
117
+ self.send_response(404)
118
+ self.send_header("Content-Type", "application/json")
119
+ self.send_cors_headers()
120
+ self.end_headers()
121
+ self.wfile.write(json.dumps({"status": "error", "error": "Resource not found"}).encode())
122
+ return
123
+
124
+ self.send_response(200)
125
+ self.send_header("Content-Type", "application/json")
126
+ self.send_cors_headers()
127
+ self.end_headers()
128
+ self.wfile.write(json.dumps({"path": str(project_file), "content": project_file.read_text()}).encode())
129
+ return
130
+
131
+ elif "/files" in self.path:
132
+ project_files = project.get_project_files()
133
+ self.send_response(200)
134
+ self.send_header("Content-Type", "application/json")
135
+ self.send_cors_headers()
136
+ self.end_headers()
137
+ self.wfile.write(
138
+ json.dumps(
139
+ {
140
+ "files": [f.replace(f"{project.folder}/", "") for f in project_files],
141
+ "root": project.folder,
142
+ "workspace_name": project.workspace_name,
143
+ }
144
+ ).encode()
145
+ )
146
+ return
147
+
148
+ # Handle all other paths
149
+ self.send_response(404)
150
+ self.send_header("Content-Type", "application/json")
151
+ self.send_cors_headers()
152
+ self.end_headers()
153
+ self.wfile.write(json.dumps({"status": "error", "message": "Not found"}).encode())
154
+
155
+ except Exception as e:
156
+ self.send_response(400)
157
+ self.send_header("Content-Type", "application/json")
158
+ self.send_cors_headers()
159
+ self.end_headers()
160
+ self.wfile.write(json.dumps({"status": "error", "message": str(e)}).encode())
161
+
162
+ def do_POST(self):
163
+ try:
164
+ # Parse the request body
165
+ content_length = int(self.headers["Content-Length"])
166
+ body = self.rfile.read(content_length)
167
+ data = json.loads(body)
168
+
169
+ # Get the file path from the request body
170
+ file_path = data.get("path")
171
+ if not file_path:
172
+ self.send_response(400)
173
+ self.send_header("Content-Type", "application/json")
174
+ self.send_cors_headers()
175
+ self.end_headers()
176
+ self.wfile.write(json.dumps({"status": "error", "message": "No file path provided"}).encode())
177
+ return
178
+
179
+ exists = glob.glob(f"{self.server.project.folder}/**/{file_path}", recursive=True)
180
+ if not exists:
181
+ self.send_response(400)
182
+ self.send_header("Content-Type", "application/json")
183
+ self.send_cors_headers()
184
+ self.end_headers()
185
+ self.wfile.write(json.dumps({"status": "error", "message": "File does not exist"}).encode())
186
+ return
187
+
188
+ # Get the content from the request body
189
+ content = data.get("content")
190
+ if not content:
191
+ self.send_response(400)
192
+ self.send_header("Content-Type", "application/json")
193
+ self.send_cors_headers()
194
+ self.end_headers()
195
+ self.wfile.write(json.dumps({"status": "error", "message": "No content provided"}).encode())
196
+ return
197
+
198
+ # Write the content to the file
199
+ file_path = exists[0]
200
+ with open(file_path, "w") as f:
201
+ f.write(content)
202
+ # sleep for 0.2 seconds to ensure the file is written
203
+ time.sleep(0.2)
204
+
205
+ def check_build_status(attempts: int = 0):
206
+ if attempts > 10:
207
+ return "Build timeout. Check the console for more details."
208
+ if self.server.build_status.building:
209
+ time.sleep(0.5)
210
+ return check_build_status(attempts + 1)
211
+ else:
212
+ return self.server.build_status.error
213
+
214
+ build_error = check_build_status()
215
+ if build_error:
216
+ raise Exception(build_error)
217
+
218
+ self.send_response(200)
219
+ self.send_header("Content-Type", "application/json")
220
+ self.send_cors_headers()
221
+ self.end_headers()
222
+ self.wfile.write(json.dumps({"status": "success", "message": "File written successfully"}).encode())
223
+
224
+ except Exception as e:
225
+ self.send_response(400)
226
+ self.send_header("Content-Type", "application/json")
227
+ self.send_cors_headers()
228
+ self.end_headers()
229
+ self.wfile.write(json.dumps({"status": "error", "error": str(e)}).encode())
230
+
231
+ def log_message(self, format, *args):
232
+ pass
233
+
234
+
235
+ def start_server(project: Project, tb_client: TinyB, process: Callable, build_status: BuildStatus):
236
+ """Start a development server for the project.
237
+
238
+ Args:
239
+ project: The project instance to serve.
240
+ """
241
+
242
+ try:
243
+ click.echo(FeedbackManager.highlight(message="\n» Starting Tinybird dev server...\n"))
244
+
245
+ # Create and start the server
246
+ server = DevServer(process, project, build_status, tb_client)
247
+ server.serve_forever()
248
+
249
+ # Run the server in the main thread
250
+ click.echo(FeedbackManager.gray(message="\nWatching for changes...\n"))
251
+ click.echo(FeedbackManager.highlight(message="Press Ctrl+C to stop the server\n"))
252
+
253
+ except KeyboardInterrupt:
254
+ click.echo(FeedbackManager.highlight(message="\n» Stopping Tinybird dev server...\n"))
255
+ except Exception as e:
256
+ click.echo(FeedbackManager.error_exception(error=e))
257
+ sys_exit("server_error", str(e))
@@ -16,6 +16,7 @@ from tinybird.tb.modules.common import coro
16
16
  from tinybird.tb.modules.exceptions import CLIException, CLILocalException
17
17
  from tinybird.tb.modules.feedback_manager import FeedbackManager
18
18
  from tinybird.tb.modules.local_common import TB_CONTAINER_NAME, TB_IMAGE_NAME, TB_LOCAL_PORT
19
+ from tinybird.tb.modules.telemetry import add_telemetry_event
19
20
 
20
21
 
21
22
  def start_tinybird_local(
@@ -131,16 +132,70 @@ def get_docker_client() -> DockerClient:
131
132
  if not docker_host:
132
133
  # Try to get docker host from docker context
133
134
  try:
134
- output = subprocess.check_output(["docker", "context", "inspect"], text=True)
135
- context = json.loads(output)
135
+ try:
136
+ output = subprocess.check_output(["docker", "context", "inspect"], text=True)
137
+ except Exception as e:
138
+ add_telemetry_event(
139
+ "docker_error",
140
+ data={
141
+ "error_kind": "docker_context_inspect_error",
142
+ "exception": str(e),
143
+ },
144
+ )
145
+ raise e
146
+ try:
147
+ context = json.loads(output)
148
+ except Exception as e:
149
+ add_telemetry_event(
150
+ "docker_error",
151
+ data={
152
+ "error_kind": "docker_context_inspect_parse_output_error",
153
+ "exception": str(e),
154
+ "docker_context_inspect_output": output,
155
+ },
156
+ )
157
+ raise e
136
158
  if context and len(context) > 0:
137
- docker_host = context[0].get("Endpoints", {}).get("docker", {}).get("Host")
138
- if docker_host:
139
- os.environ["DOCKER_HOST"] = docker_host
159
+ try:
160
+ docker_host = context[0].get("Endpoints", {}).get("docker", {}).get("Host")
161
+ if docker_host:
162
+ os.environ["DOCKER_HOST"] = docker_host
163
+ except Exception as e:
164
+ add_telemetry_event(
165
+ "docker_error",
166
+ data={
167
+ "error_kind": "docker_context_parse_host_error",
168
+ "exception": str(e),
169
+ "context": json.dumps(context),
170
+ },
171
+ )
172
+ raise e
140
173
  except Exception:
141
174
  pass
142
- client = docker.from_env() # type: ignore
143
- client.ping()
175
+ try:
176
+ client = docker.from_env() # type: ignore
177
+ except Exception as e:
178
+ add_telemetry_event(
179
+ "docker_error",
180
+ data={
181
+ "error_kind": "docker_get_client_from_env_error",
182
+ "exception": str(e),
183
+ },
184
+ )
185
+ raise e
186
+ try:
187
+ client.ping()
188
+ except Exception as e:
189
+ client_dict_non_sensitive = {k: v for k, v in client.api.__dict__.items() if "auth" not in k}
190
+ add_telemetry_event(
191
+ "docker_error",
192
+ data={
193
+ "error_kind": "docker_ping_error",
194
+ "exception": str(e),
195
+ "client": repr(client_dict_non_sensitive),
196
+ },
197
+ )
198
+ raise e
144
199
  return client
145
200
  except Exception:
146
201
  raise CLILocalException(
@@ -2,6 +2,7 @@ import hashlib
2
2
  import logging
3
3
  import os
4
4
  import re
5
+ import subprocess
5
6
  from typing import Any, Dict
6
7
 
7
8
  import requests
@@ -10,6 +11,7 @@ from tinybird.tb.client import AuthNoTokenException, TinyB
10
11
  from tinybird.tb.modules.config import CLIConfig
11
12
  from tinybird.tb.modules.exceptions import CLILocalException
12
13
  from tinybird.tb.modules.feedback_manager import FeedbackManager
14
+ from tinybird.tb.modules.telemetry import add_telemetry_event
13
15
 
14
16
  TB_IMAGE_NAME = "tinybirdco/tinybird-local:latest"
15
17
  TB_CONTAINER_NAME = "tinybird-local"
@@ -37,6 +39,22 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any], test: bool = Fal
37
39
  # ruff: noqa: ASYNC210
38
40
  tokens = requests.get(f"{TB_LOCAL_ADDRESS}/tokens").json()
39
41
  except Exception:
42
+ try:
43
+ # Check if tinybird-local is running with docker, in case it's a config issue
44
+ output = subprocess.check_output(["docker", "ps"], text=True) # noqa: ASYNC221
45
+ header_row = next((line for line in output.splitlines() if "CONTAINER ID" in line), "")
46
+ tb_local_row = next(
47
+ (line for line in output.splitlines() if TB_CONTAINER_NAME in line),
48
+ f"{TB_CONTAINER_NAME} not found in output",
49
+ )
50
+ add_telemetry_event(
51
+ "docker_debug",
52
+ data={
53
+ "docker_ps_output": header_row + tb_local_row,
54
+ },
55
+ )
56
+ except Exception:
57
+ pass
40
58
  raise CLILocalException(
41
59
  FeedbackManager.error(message="Tinybird local is not running. Please run `tb local start` first.")
42
60
  )
@@ -11,8 +11,9 @@ from tinybird.tb.modules.datafile.parse_pipe import parse_pipe
11
11
  class Project:
12
12
  extensions = ("datasource", "pipe", "connection")
13
13
 
14
- def __init__(self, folder: str):
14
+ def __init__(self, folder: str, workspace_name: str):
15
15
  self.folder = folder
16
+ self.workspace_name = workspace_name
16
17
 
17
18
  @property
18
19
  def path(self) -> Path:
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev152
3
+ Version: 0.0.1.dev154
4
4
  Summary: Tinybird Command Line Tool
5
- Home-page: https://www.tinybird.co/docs/cli/introduction.html
5
+ Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
7
7
  Author-email: support@tinybird.co
8
8
  Requires-Python: >=3.9, <3.14
@@ -41,6 +41,7 @@ tinybird/tb/modules/create.py
41
41
  tinybird/tb/modules/datasource.py
42
42
  tinybird/tb/modules/deployment.py
43
43
  tinybird/tb/modules/deprecations.py
44
+ tinybird/tb/modules/dev_server.py
44
45
  tinybird/tb/modules/endpoint.py
45
46
  tinybird/tb/modules/exceptions.py
46
47
  tinybird/tb/modules/feedback_manager.py