tinybird 0.0.1.dev151__tar.gz → 0.0.1.dev153__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.dev151 → tinybird-0.0.1.dev153}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/__cli__.py +2 -2
  3. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/build.py +38 -9
  4. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/cli.py +1 -1
  5. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/create.py +54 -50
  6. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/common.py +8 -0
  7. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/deployment.py +41 -8
  8. tinybird-0.0.1.dev153/tinybird/tb/modules/dev_server.py +257 -0
  9. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/mock.py +58 -30
  10. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/project.py +11 -1
  11. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird.egg-info/PKG-INFO +1 -1
  12. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird.egg-info/SOURCES.txt +1 -0
  13. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/setup.cfg +0 -0
  14. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/__cli__.py +0 -0
  15. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/ch_utils/constants.py +0 -0
  16. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/ch_utils/engine.py +0 -0
  17. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/check_pypi.py +0 -0
  18. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/client.py +0 -0
  19. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/config.py +0 -0
  20. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/connectors.py +0 -0
  21. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/context.py +0 -0
  22. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/datafile.py +0 -0
  23. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/datatypes.py +0 -0
  24. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/feedback_manager.py +0 -0
  25. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/git_settings.py +0 -0
  26. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/prompts.py +0 -0
  27. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/sql.py +0 -0
  28. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/sql_template.py +0 -0
  29. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/sql_template_fmt.py +0 -0
  30. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/sql_toolset.py +0 -0
  31. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/syncasync.py +0 -0
  32. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/check_pypi.py +0 -0
  33. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/cli.py +0 -0
  34. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/client.py +0 -0
  35. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/config.py +0 -0
  36. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/auth.py +0 -0
  37. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/cicd.py +0 -0
  38. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/common.py +0 -0
  39. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/config.py +0 -0
  40. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/connection.py +0 -0
  41. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/copy.py +0 -0
  42. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/build.py +0 -0
  43. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/build_common.py +0 -0
  44. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  45. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  46. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/diff.py +0 -0
  47. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  48. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/fixture.py +0 -0
  49. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/format_common.py +0 -0
  50. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  51. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  52. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  53. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  54. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  55. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/playground.py +0 -0
  56. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datafile/pull.py +0 -0
  57. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/datasource.py +0 -0
  58. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/deprecations.py +0 -0
  59. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/endpoint.py +0 -0
  60. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/exceptions.py +0 -0
  61. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/feedback_manager.py +0 -0
  62. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/infra.py +0 -0
  63. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/job.py +0 -0
  64. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/llm.py +0 -0
  65. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/llm_utils.py +0 -0
  66. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/local.py +0 -0
  67. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/local_common.py +0 -0
  68. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/login.py +0 -0
  69. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/logout.py +0 -0
  70. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/materialization.py +0 -0
  71. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/open.py +0 -0
  72. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/pipe.py +0 -0
  73. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/regions.py +0 -0
  74. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/secret.py +0 -0
  75. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/shell.py +0 -0
  76. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/table.py +0 -0
  77. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/telemetry.py +0 -0
  78. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/test.py +0 -0
  79. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  80. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  81. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/token.py +0 -0
  82. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/watch.py +0 -0
  83. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/workspace.py +0 -0
  84. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb/modules/workspace_members.py +0 -0
  85. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli.py +0 -0
  86. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/auth.py +0 -0
  87. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/branch.py +0 -0
  88. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/cicd.py +0 -0
  89. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/cli.py +0 -0
  90. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/common.py +0 -0
  91. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/config.py +0 -0
  92. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/connection.py +0 -0
  93. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/datasource.py +0 -0
  94. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/exceptions.py +0 -0
  95. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/fmt.py +0 -0
  96. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/job.py +0 -0
  97. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/pipe.py +0 -0
  98. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/regions.py +0 -0
  99. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/tag.py +0 -0
  100. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/telemetry.py +0 -0
  101. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/test.py +0 -0
  102. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  103. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  104. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/workspace.py +0 -0
  105. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  106. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird/tornado_template.py +0 -0
  107. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird.egg-info/dependency_links.txt +0 -0
  108. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird.egg-info/entry_points.txt +0 -0
  109. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird.egg-info/requires.txt +0 -0
  110. {tinybird-0.0.1.dev151 → tinybird-0.0.1.dev153}/tinybird.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev151
3
+ Version: 0.0.1.dev153
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -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.dev151'
8
- __revision__ = 'd9da445'
7
+ __version__ = '0.0.1.dev153'
8
+ __revision__ = '24d7d4a'
@@ -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:
@@ -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:
@@ -1,3 +1,4 @@
1
+ import glob
1
2
  import os
2
3
  import re
3
4
  from pathlib import Path
@@ -5,7 +6,7 @@ from typing import Any, Dict, Optional, Tuple
5
6
 
6
7
  import click
7
8
 
8
- from tinybird.prompts import create_prompt, mock_prompt, readme_prompt, rules_prompt
9
+ from tinybird.prompts import create_prompt, readme_prompt, rules_prompt
9
10
  from tinybird.tb.client import TinyB
10
11
  from tinybird.tb.modules.cicd import init_cicd
11
12
  from tinybird.tb.modules.cli import cli
@@ -17,6 +18,7 @@ from tinybird.tb.modules.feedback_manager import FeedbackManager
17
18
  from tinybird.tb.modules.llm import LLM
18
19
  from tinybird.tb.modules.llm_utils import extract_xml, parse_xml
19
20
  from tinybird.tb.modules.local_common import get_tinybird_local_client
21
+ from tinybird.tb.modules.mock import create_mock_data
20
22
  from tinybird.tb.modules.project import Project
21
23
 
22
24
 
@@ -83,7 +85,7 @@ async def create(
83
85
 
84
86
  if data or (prompt and user_token):
85
87
  click.echo(FeedbackManager.highlight(message="\n» Creating resources..."))
86
- result, created_something = await create_resources(tb_client, user_token, data, prompt, folder, ctx_config)
88
+ result, created_something = await create_resources(tb_client, user_token, data, prompt, project, ctx_config)
87
89
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
88
90
  if prompt:
89
91
  readme_path = Path(root_folder) / "README.md"
@@ -132,22 +134,26 @@ async def create(
132
134
  persist_fixture(ds_name, data_content, folder, format=data_format)
133
135
  created_something = True
134
136
  elif prompt and user_token:
135
- datasource_files = [f for f in os.listdir(Path(folder) / "datasources") if f.endswith(".datasource")]
136
- for datasource_file in datasource_files:
137
- datasource_path = Path(folder) / "datasources" / datasource_file
138
- llm = LLM(user_token=user_token, host=tb_client.host)
137
+ for datasource_file in project.get_datasource_files():
138
+ datasource_path = Path(datasource_file)
139
139
  datasource_name = datasource_path.stem
140
140
  datasource_content = datasource_path.read_text()
141
141
  has_json_path = "`json:" in datasource_content
142
142
  if has_json_path:
143
- prompt = f"<datasource_schema>{datasource_content}</datasource_schema>"
144
- response = llm.ask(system_prompt=mock_prompt(rows), prompt=prompt)
145
- sql = extract_xml(response, "sql")
146
- sql = sql.split("FORMAT")[0]
147
- query_result = await tb_client.query(f"{sql} FORMAT JSON")
148
- data = query_result.get("data", [])
149
- if data:
150
- persist_fixture(datasource_name, data, folder, format="ndjson")
143
+ mock_data = await create_mock_data(
144
+ datasource_name,
145
+ datasource_content,
146
+ rows,
147
+ prompt,
148
+ config,
149
+ ctx_config,
150
+ user_token,
151
+ tb_client,
152
+ format_="ndjson",
153
+ folder=project.folder,
154
+ )
155
+ if mock_data:
156
+ persist_fixture(datasource_name, mock_data, folder, format="ndjson")
151
157
  click.echo(FeedbackManager.info(message=f"✓ /fixtures/{datasource_name}"))
152
158
  created_something = True
153
159
 
@@ -204,12 +210,12 @@ async def create_resources(
204
210
  user_token: Optional[str],
205
211
  data: Optional[str],
206
212
  prompt: Optional[str],
207
- folder: str,
213
+ project: Project,
208
214
  config: Dict[str, Any],
209
215
  ) -> Tuple[str, bool]:
210
216
  result = ""
211
217
  created_any_resource = False
212
- folder_path = Path(folder)
218
+ folder_path = project.path
213
219
  if data:
214
220
  local_client = await get_tinybird_local_client(config)
215
221
  path = folder_path / data
@@ -224,7 +230,7 @@ SQL >
224
230
  SELECT * from {name}
225
231
  TYPE ENDPOINT
226
232
  """,
227
- folder,
233
+ project.folder,
228
234
  )
229
235
  result = (
230
236
  f"<response><resource><type>datasource</type><name>{name}</name><content></content></resource></response>"
@@ -232,19 +238,9 @@ TYPE ENDPOINT
232
238
  created_any_resource = True
233
239
 
234
240
  elif prompt and user_token:
235
- datasource_paths = [
236
- Path(folder) / "datasources" / f
237
- for f in os.listdir(Path(folder) / "datasources")
238
- if f.endswith(".datasource")
239
- ]
240
- pipes_paths = [
241
- Path(folder) / "endpoints" / f for f in os.listdir(Path(folder) / "endpoints") if f.endswith(".pipe")
242
- ]
243
- connections_paths = [
244
- Path(folder) / "connections" / f
245
- for f in os.listdir(Path(folder) / "connections")
246
- if f.endswith(".connection")
247
- ]
241
+ datasource_paths = [Path(ds_file) for ds_file in project.get_datasource_files()]
242
+ pipes_paths = [Path(pipe_file) for pipe_file in project.get_pipe_files()]
243
+ connections_paths = [Path(conn_file) for conn_file in project.get_connection_files()]
248
244
  resources_xml = "\n".join(
249
245
  [
250
246
  f"<resource><type>{resource_type}</type><name>{resource_name}</name><content>{resource_content}</content></resource>"
@@ -300,19 +296,19 @@ TYPE ENDPOINT
300
296
  data=None,
301
297
  _format="ndjson",
302
298
  force=True,
303
- folder=folder,
299
+ folder=project.folder,
304
300
  )
305
301
  created_any_resource = True
306
302
 
307
303
  for pipe in pipes:
308
304
  content = pipe["content"].replace("```", "")
309
- generate_pipe_file(pipe["name"], content, folder)
305
+ generate_pipe_file(pipe["name"], content, project.folder)
310
306
  created_any_resource = True
311
307
 
312
308
  for conn in connections:
313
309
  content = conn["content"].replace("```", "")
314
310
  filename = f"{conn['name']}.connection"
315
- generate_connection_file(conn["name"], content, folder)
311
+ generate_connection_file(conn["name"], content, project.folder)
316
312
  created_any_resource = True
317
313
 
318
314
  return result, created_any_resource
@@ -348,21 +344,25 @@ def generate_pipe_file(name: str, content: str, folder: str) -> Path:
348
344
  def is_endpoint(content: str) -> bool:
349
345
  return re.search(r"TYPE endpoint", content, re.IGNORECASE) is not None
350
346
 
351
- if is_copy(content):
352
- pathname = "copies"
353
- elif is_materialization(content):
354
- pathname = "materializations"
355
- elif is_sink(content):
356
- pathname = "sinks"
357
- elif is_endpoint(content):
358
- pathname = "endpoints"
347
+ already_exists = glob.glob(f"{folder}/**/{name}.pipe")
348
+ if already_exists:
349
+ f = Path(already_exists[0])
359
350
  else:
360
- pathname = "pipes"
351
+ if is_copy(content):
352
+ pathname = "copies"
353
+ elif is_materialization(content):
354
+ pathname = "materializations"
355
+ elif is_sink(content):
356
+ pathname = "sinks"
357
+ elif is_endpoint(content):
358
+ pathname = "endpoints"
359
+ else:
360
+ pathname = "pipes"
361
361
 
362
- base = Path(folder) / pathname
363
- if not base.exists():
364
- base.mkdir()
365
- f = base / (f"{name}.pipe")
362
+ base = Path(folder) / pathname
363
+ if not base.exists():
364
+ base.mkdir()
365
+ f = base / (f"{name}.pipe")
366
366
  with open(f"{f}", "w") as file:
367
367
  file.write(content)
368
368
  click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder)))
@@ -370,10 +370,14 @@ def generate_pipe_file(name: str, content: str, folder: str) -> Path:
370
370
 
371
371
 
372
372
  def generate_connection_file(name: str, content: str, folder: str, skip_feedback: bool = False) -> Path:
373
- base = Path(folder) / "connections"
374
- if not base.exists():
375
- base.mkdir()
376
- f = base / (f"{name}.connection")
373
+ already_exists = glob.glob(f"{folder}/**/{name}.connection")
374
+ if already_exists:
375
+ f = Path(already_exists[0])
376
+ else:
377
+ base = Path(folder) / "connections"
378
+ if not base.exists():
379
+ base.mkdir()
380
+ f = base / (f"{name}.connection")
377
381
  with open(f"{f}", "w") as file:
378
382
  file.write(content)
379
383
  if not skip_feedback:
@@ -846,6 +846,14 @@ def _parse_table_structure(schema: str) -> List[Dict[str, Any]]:
846
846
  lineno=line,
847
847
  pos=pos,
848
848
  )
849
+ if detected_type in ("Int", "UInt"):
850
+ t = detected_type
851
+ raise SchemaSyntaxError(
852
+ message=f"Precision is mandatory for {t} types",
853
+ hint=f"Hint: use one of {t}8, {t}16, {t}32, {t}64, {t}128, {t}256",
854
+ lineno=line,
855
+ pos=pos - len(t),
856
+ )
849
857
 
850
858
  try:
851
859
  # Imported in the body to be compatible with the CLI
@@ -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))
@@ -1,5 +1,6 @@
1
1
  import glob
2
2
  from pathlib import Path
3
+ from typing import Any, Dict, List
3
4
 
4
5
  import click
5
6
 
@@ -65,41 +66,23 @@ async def mock(ctx: click.Context, datasource: str, rows: int, prompt: str, form
65
66
 
66
67
  datasource_content = datasource_path.read_text()
67
68
  config = CLIConfig.get_project_config()
68
- user_client = config.get_client(token=ctx_config.get("token"), host=ctx_config.get("host"))
69
69
  user_token = ctx_config.get("user_token")
70
70
 
71
71
  if not user_token:
72
72
  raise Exception("This action requires authentication. Run 'tb login' first.")
73
73
 
74
- llm = LLM(user_token=user_token, host=user_client.host)
75
- prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
76
- sql = ""
77
- attempts = 0
78
- data = []
79
- error = ""
80
- sql_path = None
81
- while True:
82
- try:
83
- response = llm.ask(system_prompt=mock_prompt(rows, error), prompt=prompt)
84
- sql = extract_xml(response, "sql")
85
- sql_path = persist_fixture_sql(datasource_name, sql, folder)
86
- sql_format = "JSON" if format_ == "ndjson" else "CSV"
87
- result = await tb_client.query(f"SELECT * FROM ({sql}) LIMIT {rows} FORMAT {sql_format}")
88
- if sql_format == "JSON":
89
- data = result.get("data", [])[:rows]
90
- error_response = result.get("error", None)
91
- if error_response:
92
- raise Exception(error_response)
93
- else:
94
- data = result
95
- break
96
- except Exception as e:
97
- error = str(e)
98
- attempts += 1
99
- if attempts > 5:
100
- raise Exception(f"Failed to generate a valid solution. Check {str(sql_path)} and try again.")
101
- else:
102
- continue
74
+ data = await create_mock_data(
75
+ datasource_name,
76
+ datasource_content,
77
+ rows,
78
+ prompt,
79
+ config,
80
+ ctx_config,
81
+ user_token,
82
+ tb_client,
83
+ format_,
84
+ folder,
85
+ )
103
86
 
104
87
  fixture_path = persist_fixture(datasource_name, data, folder, format=format_)
105
88
  click.echo(FeedbackManager.info(message=f"✓ /fixtures/{datasource_name}.{format_} created"))
@@ -125,3 +108,48 @@ async def append_fixture(
125
108
  concurrency=1,
126
109
  silent=True,
127
110
  )
111
+
112
+
113
+ async def create_mock_data(
114
+ datasource_name: str,
115
+ datasource_content: str,
116
+ rows: int,
117
+ prompt: str,
118
+ config: CLIConfig,
119
+ ctx_config: Dict[str, Any],
120
+ user_token: str,
121
+ tb_client: TinyB,
122
+ format_: str,
123
+ folder: str,
124
+ ) -> List[Dict[str, Any]]:
125
+ user_client = config.get_client(token=ctx_config.get("token"), host=ctx_config.get("host"))
126
+ llm = LLM(user_token=user_token, host=user_client.host)
127
+ prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
128
+ sql = ""
129
+ attempts = 0
130
+ data = []
131
+ error = ""
132
+ sql_path = None
133
+ while True:
134
+ try:
135
+ response = llm.ask(system_prompt=mock_prompt(rows, error), prompt=prompt)
136
+ sql = extract_xml(response, "sql")
137
+ sql_path = persist_fixture_sql(datasource_name, sql, folder)
138
+ sql_format = "JSON" if format_ == "ndjson" else "CSV"
139
+ result = await tb_client.query(f"SELECT * FROM ({sql}) LIMIT {rows} FORMAT {sql_format}")
140
+ if sql_format == "JSON":
141
+ data = result.get("data", [])[:rows]
142
+ error_response = result.get("error", None)
143
+ if error_response:
144
+ raise Exception(error_response)
145
+ else:
146
+ data = result
147
+ break
148
+ except Exception as e:
149
+ error = str(e)
150
+ attempts += 1
151
+ if attempts > 5:
152
+ raise Exception(f"Failed to generate a valid solution. Check {str(sql_path)} and try again.")
153
+ else:
154
+ continue
155
+ return data
@@ -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:
@@ -55,6 +56,15 @@ class Project:
55
56
  def connections(self) -> List[str]:
56
57
  return sorted([Path(f).stem for f in glob.glob(f"{self.path}/**/*.connection", recursive=False)])
57
58
 
59
+ def get_datasource_files(self) -> List[str]:
60
+ return glob.glob(f"{self.path}/**/*.datasource", recursive=False)
61
+
62
+ def get_pipe_files(self) -> List[str]:
63
+ return glob.glob(f"{self.path}/**/*.pipe", recursive=False)
64
+
65
+ def get_connection_files(self) -> List[str]:
66
+ return glob.glob(f"{self.path}/**/*.connection", recursive=False)
67
+
58
68
  def get_pipe_datafile(self, filename: str) -> Optional[Datafile]:
59
69
  try:
60
70
  return parse_pipe(filename).datafile
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev151
3
+ Version: 0.0.1.dev153
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -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