tinybird 0.0.1.dev242__py3-none-any.whl → 0.0.1.dev245__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tinybird might be problematic. Click here for more details.

@@ -1698,11 +1698,36 @@ def parse(
1698
1698
  def version(*args: str, **kwargs: Any) -> None:
1699
1699
  pass # whatever, it's deprecated
1700
1700
 
1701
- @not_supported_yet()
1702
1701
  def shared_with(*args: str, **kwargs: Any) -> None:
1702
+ # Count total workspaces collected
1703
+ total_workspaces = 0
1704
+
1703
1705
  for entries in args:
1704
- # In case they specify multiple workspaces
1705
- doc.shared_with += [workspace.strip() for workspace in entries.splitlines()]
1706
+ # In case they specify multiple workspaces, handle both line-separated and comma-separated values
1707
+ lines = entries.splitlines()
1708
+ for line in lines:
1709
+ # Split by comma and strip whitespace from each workspace name
1710
+ workspaces = [workspace.strip().rstrip(",") for workspace in line.split(",") if workspace.strip()]
1711
+ doc.shared_with += workspaces
1712
+ total_workspaces += len(workspaces)
1713
+
1714
+ # Add warning once if any workspaces were found
1715
+ if total_workspaces > 0:
1716
+ warnings.append(
1717
+ DatafileParseWarning(
1718
+ message=(
1719
+ f"{kwargs['cmd'].upper()} is not fully implemented in Forward and will be ignored while this warning is present."
1720
+ )
1721
+ )
1722
+ )
1723
+
1724
+ # Validate that at least one workspace was provided
1725
+ if total_workspaces == 0:
1726
+ raise DatafileSyntaxError(
1727
+ "SHARED_WITH requires at least one workspace name",
1728
+ lineno=kwargs["lineno"],
1729
+ pos=1,
1730
+ )
1706
1731
 
1707
1732
  def __init_engine(v: str):
1708
1733
  if not parser_state.current_node:
tinybird/tb/__cli__.py CHANGED
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
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.dev242'
8
- __revision__ = '62ad714'
7
+ __version__ = '0.0.1.dev245'
8
+ __revision__ = 'f0c439a'
@@ -1,5 +1,8 @@
1
1
  import sys
2
+ import uuid
2
3
  from datetime import datetime
4
+ from functools import partial
5
+ from typing import Any
3
6
 
4
7
  import click
5
8
  from prompt_toolkit import prompt
@@ -26,20 +29,29 @@ from tinybird.tb.modules.agent.animations import ThinkingAnimation
26
29
  from tinybird.tb.modules.agent.banner import display_banner
27
30
  from tinybird.tb.modules.agent.memory import clear_history, load_history
28
31
  from tinybird.tb.modules.agent.models import create_model
29
- from tinybird.tb.modules.agent.prompts import datafile_instructions, plan_instructions, sql_instructions
32
+ from tinybird.tb.modules.agent.prompts import (
33
+ datafile_instructions,
34
+ plan_instructions,
35
+ resources_prompt,
36
+ sql_instructions,
37
+ )
30
38
  from tinybird.tb.modules.agent.tools.create_datafile import create_datafile
31
39
  from tinybird.tb.modules.agent.tools.explore import explore_data
32
40
  from tinybird.tb.modules.agent.tools.plan import plan
33
41
  from tinybird.tb.modules.agent.tools.preview_datafile import preview_datafile
34
42
  from tinybird.tb.modules.agent.utils import TinybirdAgentContext
43
+ from tinybird.tb.modules.build_common import process as build_process
44
+ from tinybird.tb.modules.exceptions import CLIBuildException
35
45
  from tinybird.tb.modules.feedback_manager import FeedbackManager
46
+ from tinybird.tb.modules.local_common import get_tinybird_local_client
47
+ from tinybird.tb.modules.project import Project
36
48
 
37
49
 
38
50
  class TinybirdAgent:
39
- def __init__(self, token: str, host: str, folder: str):
51
+ def __init__(self, token: str, host: str, project: Project):
40
52
  self.token = token
41
53
  self.host = host
42
- self.folder = folder
54
+ self.project = project
43
55
  self.messages: list[ModelMessage] = []
44
56
  self.agent = Agent(
45
57
  model=create_model(token, host),
@@ -83,9 +95,10 @@ You have access to the following tools:
83
95
  4. Without asking, use the `create_datafile` tool to create the datafile, because it will ask for confirmation before creating the file.
84
96
  5. Check the result of the `create_datafile` tool to see if the datafile was created successfully.
85
97
  6. If the datafile was created successfully, report the result to the user.
86
- 7. If the datafile was not created successfully, finish the process and just wait for a new user prompt.
98
+ 7. If the datafile was not created, finish the process and just wait for a new user prompt.
99
+ 8. If the datafile was created successfully, but the built failed, try to fix the error and repeat the process.
87
100
 
88
- IMPORTANT: If the user cancels some of the steps or there is an error, DO NOT continue with the plan. Stop the process and wait for the user before using any other tool.
101
+ IMPORTANT: If the user cancels some of the steps or there is an error in file creation, DO NOT continue with the plan. Stop the process and wait for the user before using any other tool.
89
102
 
90
103
  # When planning the creation or update of resources:
91
104
  {plan_instructions}
@@ -134,9 +147,10 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
134
147
  """Keep only the last 5 messages to manage token usage."""
135
148
  return self.messages[-5:] if len(self.messages) > 5 else self.messages
136
149
 
137
- def run(self, user_prompt: str) -> None:
150
+ def run(self, user_prompt: str, project: Project) -> None:
151
+ user_prompt = f"{user_prompt}\n\n# Existing resources in the project:\n{resources_prompt(project)}"
138
152
  client = TinyB(token=self.token, host=self.host)
139
-
153
+ folder = self.project.folder
140
154
  thinking_animation = ThinkingAnimation(message="Chirping", delay=0.15)
141
155
  thinking_animation.start()
142
156
 
@@ -145,7 +159,9 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
145
159
  deps=TinybirdAgentContext(
146
160
  # context does not support the whole client, so we need to pass only the functions we need
147
161
  explore_data=client.explore_data,
148
- folder=self.folder,
162
+ build_project=partial(build_project, folder=folder),
163
+ get_project_files=project.get_project_files,
164
+ folder=folder,
149
165
  thinking_animation=thinking_animation,
150
166
  ),
151
167
  message_history=self.messages,
@@ -158,11 +174,13 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
158
174
  click.echo("\n")
159
175
 
160
176
 
161
- def run_agent(token: str, host: str, folder: str):
177
+ def run_agent(config: dict[str, Any], project: Project):
162
178
  display_banner()
163
179
 
164
180
  try:
165
- agent = TinybirdAgent(token, host, folder)
181
+ token = config["token"]
182
+ host = config["host"]
183
+ agent = TinybirdAgent(token, host, project)
166
184
  click.echo()
167
185
  click.echo(FeedbackManager.success(message="Welcome to Tinybird Code"))
168
186
  click.echo(FeedbackManager.info(message="Describe what you want to create and I'll help you build it"))
@@ -207,7 +225,7 @@ def run_agent(token: str, host: str, folder: str):
207
225
  elif user_input.strip() == "":
208
226
  continue
209
227
  else:
210
- agent.run(user_input)
228
+ agent.run(user_input, project)
211
229
 
212
230
  except KeyboardInterrupt:
213
231
  click.echo(FeedbackManager.info(message="Goodbye!"))
@@ -219,3 +237,12 @@ def run_agent(token: str, host: str, folder: str):
219
237
  except Exception as e:
220
238
  click.echo(FeedbackManager.error(message=f"Error: {e}"))
221
239
  sys.exit(1)
240
+
241
+
242
+ def build_project(folder: str) -> None:
243
+ workspace_name = f"tmp_workspace_{uuid.uuid4()}"
244
+ project = Project(folder, workspace_name=workspace_name)
245
+ local_client = get_tinybird_local_client({"path": folder, "name": workspace_name}, test=True, silent=True)
246
+ build_error = build_process(project=project, tb_client=local_client, watch=False, silent=True, exit_on_error=False)
247
+ if build_error:
248
+ raise CLIBuildException(build_error)
@@ -1,3 +1,7 @@
1
+ from pathlib import Path
2
+
3
+ from tinybird.tb.modules.project import Project
4
+
1
5
  plan_instructions = """
2
6
  When asked to create a plan, you MUST respond with this EXACT format and NOTHING ELSE:
3
7
 
@@ -81,3 +85,35 @@ datafile_instructions = """
81
85
  - Datasource files will be created under the `/datasources` folder.
82
86
  </datafile_instructions>
83
87
  """
88
+
89
+
90
+ def resources_prompt(project: Project) -> str:
91
+ files = project.get_project_files()
92
+
93
+ if not files:
94
+ return "No resources found"
95
+
96
+ paths = [Path(file_path) for file_path in files]
97
+
98
+ def get_resource_type(path: Path) -> str:
99
+ if path.suffix.lower() == ".pipe":
100
+ return Project.get_pipe_type(str(path))
101
+ elif path.suffix.lower() == ".datasource":
102
+ return "datasource"
103
+ elif path.suffix.lower() == ".connection":
104
+ return "connection"
105
+ return "unknown"
106
+
107
+ return "\n".join(
108
+ [
109
+ f"""
110
+ <resource>
111
+ <path>{file_path.relative_to(project.folder)}</path>
112
+ <type>{get_resource_type(file_path)}</type>
113
+ <name>{file_path.stem}</name>
114
+ <content>{file_path.read_text()}</content>
115
+ </resource>
116
+ """
117
+ for file_path in paths
118
+ ]
119
+ )
@@ -4,13 +4,16 @@ import click
4
4
  from pydantic_ai import RunContext
5
5
 
6
6
  from tinybird.tb.modules.agent.utils import Datafile, TinybirdAgentContext, show_options
7
+ from tinybird.tb.modules.exceptions import CLIBuildException
8
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
7
9
 
8
10
 
9
- def get_resource_confirmation(resource: Datafile) -> bool:
11
+ def get_resource_confirmation(resource: Datafile, exists: bool) -> bool:
10
12
  """Get user confirmation for creating a resource"""
11
13
  while True:
14
+ action = "create" if not exists else "update"
12
15
  result = show_options(
13
- options=[f"Yes, create {resource.type} '{resource.name}'", "No, and tell Tinybird Code what to do"],
16
+ options=[f"Yes, {action} {resource.type} '{resource.name}'", "No, and tell Tinybird Code what to do"],
14
17
  title=f"What would you like to do with {resource.type} '{resource.name}'?",
15
18
  )
16
19
 
@@ -36,29 +39,28 @@ def create_datafile(ctx: RunContext[TinybirdAgentContext], resource: Datafile) -
36
39
  """
37
40
  try:
38
41
  ctx.deps.thinking_animation.stop()
39
- click.echo()
40
42
  click.echo(resource.content)
41
- confirmation = get_resource_confirmation(resource)
43
+ resource.pathname = resource.pathname.removeprefix("/")
44
+ path = Path(ctx.deps.folder) / resource.pathname
45
+ exists = str(path) in ctx.deps.get_project_files()
46
+ confirmation = get_resource_confirmation(resource, exists)
42
47
  ctx.deps.thinking_animation.start()
43
48
 
44
49
  if not confirmation:
45
50
  return f"Resource {resource.pathname} was not created. User cancelled creation."
46
51
 
47
- resource.pathname = resource.pathname.removeprefix("/")
48
-
49
- path = Path(ctx.deps.folder) / resource.pathname
50
-
51
52
  folder_path = path.parent
52
-
53
- if not folder_path.exists():
54
- folder_path.mkdir()
55
-
56
- if not path.exists():
57
- path.touch()
53
+ folder_path.mkdir(parents=True, exist_ok=True)
54
+ path.touch(exist_ok=True)
58
55
 
59
56
  path.write_text(resource.content)
60
-
57
+ ctx.deps.build_project()
61
58
  return f"Created {resource.pathname}"
62
59
 
60
+ except CLIBuildException as e:
61
+ ctx.deps.thinking_animation.stop()
62
+ click.echo(FeedbackManager.error(message=e))
63
+ ctx.deps.thinking_animation.start()
64
+ return f"Error building project: {e}"
63
65
  except Exception as e:
64
66
  return f"Error creating {resource.pathname}: {e}"
@@ -34,7 +34,6 @@ def plan(ctx: RunContext[TinybirdAgentContext], plan: str) -> str:
34
34
  """
35
35
  try:
36
36
  ctx.deps.thinking_animation.stop()
37
- click.echo()
38
37
  click.echo(plan)
39
38
  confirmation = get_plan_confirmation()
40
39
  ctx.deps.thinking_animation.start()
@@ -20,7 +20,9 @@ from pydantic import BaseModel, Field
20
20
  class TinybirdAgentContext(BaseModel):
21
21
  explore_data: Callable[[str], str]
22
22
  folder: str
23
+ build_project: Callable[[], None]
23
24
  thinking_animation: Any
25
+ get_project_files: Callable[[], List[str]]
24
26
 
25
27
 
26
28
  default_style = Style.from_dict(
@@ -1,30 +1,26 @@
1
- import json
2
- import logging
3
1
  import threading
4
2
  import time
5
3
  from copy import deepcopy
6
4
  from functools import partial
7
5
  from pathlib import Path
8
- from typing import Callable, List, Optional
6
+ from typing import Callable, List
9
7
  from urllib.parse import urlencode
10
8
 
11
9
  import click
12
- import requests
13
10
 
14
11
  import tinybird.context as context
15
12
  from tinybird.datafile.exceptions import ParseException
16
13
  from tinybird.datafile.parse_datasource import parse_datasource
17
14
  from tinybird.datafile.parse_pipe import parse_pipe
18
15
  from tinybird.tb.client import TinyB
16
+ from tinybird.tb.modules.build_common import process
19
17
  from tinybird.tb.modules.cli import cli
20
- from tinybird.tb.modules.common import push_data, sys_exit
21
18
  from tinybird.tb.modules.config import CLIConfig
22
- from tinybird.tb.modules.datafile.fixture import FixtureExtension, get_fixture_dir, persist_fixture
23
19
  from tinybird.tb.modules.datafile.playground import folder_playground
24
20
  from tinybird.tb.modules.dev_server import BuildStatus, start_server
25
21
  from tinybird.tb.modules.feedback_manager import FeedbackManager
26
22
  from tinybird.tb.modules.project import Project
27
- from tinybird.tb.modules.secret import load_secrets
23
+ from tinybird.tb.modules.secret_common import load_secrets
28
24
  from tinybird.tb.modules.shell import Shell, print_table_formatted
29
25
  from tinybird.tb.modules.watch import watch_files, watch_project
30
26
 
@@ -87,275 +83,6 @@ def dev(ctx: click.Context, data_origin: str, ui: bool) -> None:
87
83
  )
88
84
 
89
85
 
90
- def build_project(project: Project, tb_client: TinyB, silent: bool = False) -> Optional[bool]:
91
- MULTIPART_BOUNDARY_DATA_PROJECT = "data_project://"
92
- DATAFILE_TYPE_TO_CONTENT_TYPE = {
93
- ".datasource": "text/plain",
94
- ".pipe": "text/plain",
95
- ".connection": "text/plain",
96
- }
97
- TINYBIRD_API_URL = tb_client.host + "/v1/build"
98
- logging.debug(TINYBIRD_API_URL)
99
- TINYBIRD_API_KEY = tb_client.token
100
- error: Optional[str] = None
101
- try:
102
- files = [
103
- ("context://", ("cli-version", "1.0.0", "text/plain")),
104
- ]
105
- project_path = project.path
106
- project_files = project.get_project_files()
107
-
108
- if not project_files:
109
- return False
110
-
111
- for file_path in project_files:
112
- relative_path = Path(file_path).relative_to(project_path).as_posix()
113
- with open(file_path, "rb") as fd:
114
- content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
115
- files.append(
116
- (MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd.read().decode("utf-8"), content_type))
117
- )
118
- HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
119
-
120
- r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS)
121
- try:
122
- result = r.json()
123
- except Exception as e:
124
- logging.debug(e, exc_info=True)
125
- click.echo(FeedbackManager.error(message="Couldn't parse response from server"))
126
- sys_exit("build_error", str(e))
127
-
128
- logging.debug(json.dumps(result, indent=2))
129
-
130
- build_result = result.get("result")
131
- if build_result == "success":
132
- build = result.get("build")
133
- new_datasources = build.get("new_datasource_names", [])
134
- new_pipes = build.get("new_pipe_names", [])
135
- new_connections = build.get("new_data_connector_names", [])
136
- changed_datasources = build.get("changed_datasource_names", [])
137
- changed_pipes = build.get("changed_pipe_names", [])
138
- changed_connections = build.get("changed_data_connector_names", [])
139
- deleted_datasources = build.get("deleted_datasource_names", [])
140
- deleted_pipes = build.get("deleted_pipe_names", [])
141
- deleted_connections = build.get("deleted_data_connector_names", [])
142
-
143
- no_changes = (
144
- not new_datasources
145
- and not changed_datasources
146
- and not new_pipes
147
- and not changed_pipes
148
- and not new_connections
149
- and not changed_connections
150
- and not deleted_datasources
151
- and not deleted_pipes
152
- and not deleted_connections
153
- )
154
- if no_changes:
155
- return False
156
- else:
157
- if not silent:
158
- echo_changes(project, new_datasources, ".datasource", "created")
159
- echo_changes(project, changed_datasources, ".datasource", "changed")
160
- echo_changes(project, deleted_datasources, ".datasource", "deleted")
161
- echo_changes(project, new_pipes, ".pipe", "created")
162
- echo_changes(project, changed_pipes, ".pipe", "changed")
163
- echo_changes(project, deleted_pipes, ".pipe", "deleted")
164
- echo_changes(project, new_connections, ".connection", "created")
165
- echo_changes(project, changed_connections, ".connection", "changed")
166
- echo_changes(project, deleted_connections, ".connection", "deleted")
167
- try:
168
- for filename in project_files:
169
- if filename.endswith(".datasource"):
170
- ds_path = Path(filename)
171
- ds_name = ds_path.stem
172
- fixture_folder = get_fixture_dir(project.folder)
173
- fixture_extensions = [FixtureExtension.NDJSON, FixtureExtension.CSV]
174
- fixture_path = next(
175
- (
176
- fixture_folder / f"{ds_name}{ext}"
177
- for ext in fixture_extensions
178
- if (fixture_folder / f"{ds_name}{ext}").exists()
179
- ),
180
- None,
181
- )
182
- if not fixture_path:
183
- sql_path = fixture_folder / f"{ds_name}.sql"
184
- if sql_path.exists():
185
- fixture_path = rebuild_fixture_sql(project, tb_client, str(sql_path))
186
-
187
- if fixture_path:
188
- append_fixture(tb_client, ds_name, str(fixture_path))
189
-
190
- except Exception as e:
191
- click.echo(FeedbackManager.error_exception(error=f"Error appending fixtures for '{ds_name}': {e}"))
192
-
193
- feedback = build.get("feedback", [])
194
- for f in feedback:
195
- click.echo(
196
- FeedbackManager.warning(message=f"△ {f.get('level')}: {f.get('resource')}: {f.get('message')}")
197
- )
198
- elif build_result == "failed":
199
- build_errors = result.get("errors")
200
- full_error_msg = ""
201
- for build_error in build_errors:
202
- filename_bit = build_error.get("filename", build_error.get("resource", ""))
203
- error_bit = build_error.get("error") or build_error.get("message") or ""
204
- error_msg = ((filename_bit + "\n") if filename_bit else "") + error_bit
205
- full_error_msg += error_msg + "\n\n"
206
- error = full_error_msg.strip("\n") or "Unknown build error"
207
- else:
208
- error = f"Unknown build result. Error: {result.get('error')}"
209
- except Exception as e:
210
- error = str(e)
211
-
212
- if error:
213
- raise click.ClickException(error)
214
-
215
- return build_result
216
-
217
-
218
- def append_fixture(
219
- tb_client: TinyB,
220
- datasource_name: str,
221
- url: str,
222
- ):
223
- # Append fixtures only if the datasource is empty
224
- data = tb_client._req(f"/v0/datasources/{datasource_name}")
225
- if data.get("statistics", {}).get("row_count", 0) > 0:
226
- return
227
-
228
- push_data(
229
- tb_client,
230
- datasource_name,
231
- url,
232
- mode="append",
233
- concurrency=1,
234
- silent=True,
235
- )
236
-
237
-
238
- def rebuild_fixture(project: Project, tb_client: TinyB, fixture: str) -> None:
239
- try:
240
- fixture_path = Path(fixture)
241
- datasources_path = Path(project.folder) / "datasources"
242
- ds_name = fixture_path.stem
243
-
244
- if ds_name not in project.datasources:
245
- try:
246
- ds_name = "_".join(fixture_path.stem.split("_")[:-1])
247
- except Exception:
248
- pass
249
-
250
- ds_path = datasources_path / f"{ds_name}.datasource"
251
-
252
- if ds_path.exists():
253
- tb_client.datasource_truncate(ds_name)
254
- append_fixture(tb_client, ds_name, str(fixture_path))
255
- except Exception as e:
256
- click.echo(FeedbackManager.error_exception(error=e))
257
-
258
-
259
- def show_data(tb_client: TinyB, filename: str, diff: Optional[str] = None):
260
- table_name = diff
261
- resource_path = Path(filename)
262
- resource_name = resource_path.stem
263
-
264
- pipeline = resource_name if filename.endswith(".pipe") else None
265
-
266
- if not table_name:
267
- table_name = resource_name
268
-
269
- sql = f"SELECT * FROM {table_name} FORMAT JSON"
270
-
271
- res = tb_client.query(sql, pipeline=pipeline)
272
- print_table_formatted(res, table_name)
273
- if Project.get_pipe_type(filename) == "endpoint":
274
- example_params = {
275
- "format": "json",
276
- "pipe": resource_name,
277
- "q": "",
278
- "token": tb_client.token,
279
- }
280
- endpoint_url = tb_client._req(f"/examples/query.http?{urlencode(example_params)}")
281
- if endpoint_url:
282
- endpoint_url = endpoint_url.replace("http://localhost:8001", tb_client.host)
283
- click.echo(FeedbackManager.gray(message="\nTest endpoint at ") + FeedbackManager.info(message=endpoint_url))
284
-
285
-
286
- def process(
287
- project: Project,
288
- tb_client: TinyB,
289
- watch: bool,
290
- file_changed: Optional[str] = None,
291
- diff: Optional[str] = None,
292
- silent: bool = False,
293
- error: bool = False,
294
- build_status: Optional[BuildStatus] = None,
295
- ) -> Optional[str]:
296
- time_start = time.time()
297
- build_failed = False
298
- build_error: Optional[str] = None
299
- build_result: Optional[bool] = None
300
- if build_status:
301
- if build_status.building:
302
- return build_status.error
303
- else:
304
- build_status.building = True
305
- if file_changed and (file_changed.endswith(FixtureExtension.NDJSON) or file_changed.endswith(FixtureExtension.CSV)):
306
- rebuild_fixture(project, tb_client, file_changed)
307
- if build_status:
308
- build_status.building = False
309
- build_status.error = None
310
- elif file_changed and file_changed.endswith(".sql"):
311
- rebuild_fixture_sql(project, tb_client, file_changed)
312
- if build_status:
313
- build_status.building = False
314
- build_status.error = None
315
- elif file_changed and (file_changed.endswith(".env.local") or file_changed.endswith(".env")):
316
- load_secrets(project, tb_client)
317
- if build_status:
318
- build_status.building = False
319
- build_status.error = None
320
- else:
321
- try:
322
- build_result = build_project(project, tb_client, silent)
323
- if build_status:
324
- build_status.building = False
325
- build_status.error = None
326
- except click.ClickException as e:
327
- click.echo(FeedbackManager.info(message=str(e)))
328
- build_error = str(e)
329
- build_failed = True
330
- try:
331
- if file_changed and not build_failed and not build_status:
332
- show_data(tb_client, file_changed, diff)
333
- except Exception:
334
- pass
335
-
336
- time_end = time.time()
337
- elapsed_time = time_end - time_start
338
-
339
- rebuild_str = "Rebuild" if watch and file_changed else "Build"
340
- if build_failed:
341
- click.echo(FeedbackManager.error(message=f"✗ {rebuild_str} failed"))
342
- if not watch:
343
- sys_exit("build_error", build_error or "Unknown error")
344
- build_error = build_error or "Unknown error"
345
- if build_status:
346
- build_status.error = build_error
347
- build_status.building = False
348
- return build_error
349
- else:
350
- if not silent:
351
- if build_result == False: # noqa: E712
352
- click.echo(FeedbackManager.info(message="No changes. Build skipped."))
353
- else:
354
- click.echo(FeedbackManager.success(message=f"\n✓ {rebuild_str} completed in {elapsed_time:.1f}s"))
355
-
356
- return None
357
-
358
-
359
86
  def run_watch(project: Project, tb_client: TinyB, process: Callable) -> None:
360
87
  shell = Shell(project=project, tb_client=tb_client, playground=False)
361
88
  click.echo(FeedbackManager.gray(message="\nWatching for changes..."))
@@ -368,27 +95,6 @@ def run_watch(project: Project, tb_client: TinyB, process: Callable) -> None:
368
95
  shell.run()
369
96
 
370
97
 
371
- def rebuild_fixture_sql(project: Project, tb_client: TinyB, sql_file: str) -> Path:
372
- sql_path = Path(sql_file)
373
- datasource_name = sql_path.stem
374
- valid_extensions = [FixtureExtension.NDJSON, FixtureExtension.CSV]
375
- fixtures_path = get_fixture_dir(project.folder)
376
- current_fixture_path = next(
377
- (
378
- fixtures_path / f"{datasource_name}{extension}"
379
- for extension in valid_extensions
380
- if (fixtures_path / f"{datasource_name}{extension}").exists()
381
- ),
382
- None,
383
- )
384
- fixture_format = current_fixture_path.suffix.lstrip(".") if current_fixture_path else "ndjson"
385
- sql = sql_path.read_text()
386
- sql_format = "CSV" if fixture_format == "csv" else "JSON"
387
- result = tb_client.query(f"{sql} FORMAT {sql_format}")
388
- data = result.get("data", [])
389
- return persist_fixture(datasource_name, data, project.folder, format=fixture_format)
390
-
391
-
392
98
  def is_vendor(f: Path) -> bool:
393
99
  return f.parts[0] == "vendor"
394
100
 
@@ -497,13 +203,3 @@ def build_and_print_resource(config: CLIConfig, tb_client: TinyB, filename: str)
497
203
  node_sql = last_node["sql"]
498
204
  res = tb_client.query(f"{node_sql} FORMAT JSON", playground=playground_id)
499
205
  print_table_formatted(res, name)
500
-
501
-
502
- def echo_changes(project: Project, changes: List[str], extension: str, status: str):
503
- for resource in changes:
504
- path_str = next(
505
- (p for p in project.get_project_files() if p.endswith(resource + extension)), resource + extension
506
- )
507
- if path_str:
508
- path_str = path_str.replace(f"{project.folder}/", "")
509
- click.echo(FeedbackManager.info(message=f"✓ {path_str} {status}"))
@@ -0,0 +1,321 @@
1
+ import json
2
+ import logging
3
+ import time
4
+ from pathlib import Path
5
+ from typing import Optional
6
+ from urllib.parse import urlencode
7
+
8
+ import click
9
+ import requests
10
+
11
+ from tinybird.tb.client import TinyB
12
+ from tinybird.tb.modules.common import push_data, sys_exit
13
+ from tinybird.tb.modules.datafile.fixture import FixtureExtension, get_fixture_dir, persist_fixture
14
+ from tinybird.tb.modules.dev_server import BuildStatus
15
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
16
+ from tinybird.tb.modules.project import Project
17
+ from tinybird.tb.modules.secret_common import load_secrets
18
+ from tinybird.tb.modules.shell import print_table_formatted
19
+
20
+
21
+ def process(
22
+ project: Project,
23
+ tb_client: TinyB,
24
+ watch: bool,
25
+ file_changed: Optional[str] = None,
26
+ diff: Optional[str] = None,
27
+ silent: bool = False,
28
+ error: bool = False,
29
+ build_status: Optional[BuildStatus] = None,
30
+ exit_on_error: bool = True,
31
+ ) -> Optional[str]:
32
+ time_start = time.time()
33
+ build_failed = False
34
+ build_error: Optional[str] = None
35
+ build_result: Optional[bool] = None
36
+ if build_status:
37
+ if build_status.building:
38
+ return build_status.error
39
+ else:
40
+ build_status.building = True
41
+ if file_changed and (file_changed.endswith(FixtureExtension.NDJSON) or file_changed.endswith(FixtureExtension.CSV)):
42
+ rebuild_fixture(project, tb_client, file_changed)
43
+ if build_status:
44
+ build_status.building = False
45
+ build_status.error = None
46
+ elif file_changed and file_changed.endswith(".sql"):
47
+ rebuild_fixture_sql(project, tb_client, file_changed)
48
+ if build_status:
49
+ build_status.building = False
50
+ build_status.error = None
51
+ elif file_changed and (file_changed.endswith(".env.local") or file_changed.endswith(".env")):
52
+ load_secrets(project, tb_client)
53
+ if build_status:
54
+ build_status.building = False
55
+ build_status.error = None
56
+ else:
57
+ try:
58
+ build_result = build_project(project, tb_client, silent)
59
+ if build_status:
60
+ build_status.building = False
61
+ build_status.error = None
62
+ except click.ClickException as e:
63
+ if not silent:
64
+ click.echo(FeedbackManager.info(message=str(e)))
65
+ build_error = str(e)
66
+ build_failed = True
67
+ try:
68
+ if file_changed and not build_failed and not build_status:
69
+ show_data(tb_client, file_changed, diff)
70
+ except Exception:
71
+ pass
72
+
73
+ time_end = time.time()
74
+ elapsed_time = time_end - time_start
75
+
76
+ rebuild_str = "Rebuild" if watch and file_changed else "Build"
77
+ if build_failed:
78
+ if not silent:
79
+ click.echo(FeedbackManager.error(message=f"✗ {rebuild_str} failed"))
80
+ if not watch and exit_on_error:
81
+ sys_exit("build_error", build_error or "Unknown error")
82
+ build_error = build_error or "Unknown error"
83
+ if build_status:
84
+ build_status.error = build_error
85
+ build_status.building = False
86
+ return build_error
87
+ else:
88
+ if not silent:
89
+ if build_result == False: # noqa: E712
90
+ click.echo(FeedbackManager.info(message="No changes. Build skipped."))
91
+ else:
92
+ click.echo(FeedbackManager.success(message=f"\n✓ {rebuild_str} completed in {elapsed_time:.1f}s"))
93
+
94
+ return None
95
+
96
+
97
+ def rebuild_fixture(project: Project, tb_client: TinyB, fixture: str) -> None:
98
+ try:
99
+ fixture_path = Path(fixture)
100
+ datasources_path = Path(project.folder) / "datasources"
101
+ ds_name = fixture_path.stem
102
+
103
+ if ds_name not in project.datasources:
104
+ try:
105
+ ds_name = "_".join(fixture_path.stem.split("_")[:-1])
106
+ except Exception:
107
+ pass
108
+
109
+ ds_path = datasources_path / f"{ds_name}.datasource"
110
+
111
+ if ds_path.exists():
112
+ tb_client.datasource_truncate(ds_name)
113
+ append_fixture(tb_client, ds_name, str(fixture_path))
114
+ except Exception as e:
115
+ click.echo(FeedbackManager.error_exception(error=e))
116
+
117
+
118
+ def rebuild_fixture_sql(project: Project, tb_client: TinyB, sql_file: str) -> Path:
119
+ sql_path = Path(sql_file)
120
+ datasource_name = sql_path.stem
121
+ valid_extensions = [FixtureExtension.NDJSON, FixtureExtension.CSV]
122
+ fixtures_path = get_fixture_dir(project.folder)
123
+ current_fixture_path = next(
124
+ (
125
+ fixtures_path / f"{datasource_name}{extension}"
126
+ for extension in valid_extensions
127
+ if (fixtures_path / f"{datasource_name}{extension}").exists()
128
+ ),
129
+ None,
130
+ )
131
+ fixture_format = current_fixture_path.suffix.lstrip(".") if current_fixture_path else "ndjson"
132
+ sql = sql_path.read_text()
133
+ sql_format = "CSV" if fixture_format == "csv" else "JSON"
134
+ result = tb_client.query(f"{sql} FORMAT {sql_format}")
135
+ data = result.get("data", [])
136
+ return persist_fixture(datasource_name, data, project.folder, format=fixture_format)
137
+
138
+
139
+ def append_fixture(
140
+ tb_client: TinyB,
141
+ datasource_name: str,
142
+ url: str,
143
+ ):
144
+ # Append fixtures only if the datasource is empty
145
+ data = tb_client._req(f"/v0/datasources/{datasource_name}")
146
+ if data.get("statistics", {}).get("row_count", 0) > 0:
147
+ return
148
+
149
+ push_data(
150
+ tb_client,
151
+ datasource_name,
152
+ url,
153
+ mode="append",
154
+ concurrency=1,
155
+ silent=True,
156
+ )
157
+
158
+
159
+ def show_data(tb_client: TinyB, filename: str, diff: Optional[str] = None):
160
+ table_name = diff
161
+ resource_path = Path(filename)
162
+ resource_name = resource_path.stem
163
+
164
+ pipeline = resource_name if filename.endswith(".pipe") else None
165
+
166
+ if not table_name:
167
+ table_name = resource_name
168
+
169
+ sql = f"SELECT * FROM {table_name} FORMAT JSON"
170
+
171
+ res = tb_client.query(sql, pipeline=pipeline)
172
+ print_table_formatted(res, table_name)
173
+ if Project.get_pipe_type(filename) == "endpoint":
174
+ example_params = {
175
+ "format": "json",
176
+ "pipe": resource_name,
177
+ "q": "",
178
+ "token": tb_client.token,
179
+ }
180
+ endpoint_url = tb_client._req(f"/examples/query.http?{urlencode(example_params)}")
181
+ if endpoint_url:
182
+ endpoint_url = endpoint_url.replace("http://localhost:8001", tb_client.host)
183
+ click.echo(FeedbackManager.gray(message="\nTest endpoint at ") + FeedbackManager.info(message=endpoint_url))
184
+
185
+
186
+ def build_project(project: Project, tb_client: TinyB, silent: bool = False) -> Optional[bool]:
187
+ MULTIPART_BOUNDARY_DATA_PROJECT = "data_project://"
188
+ DATAFILE_TYPE_TO_CONTENT_TYPE = {
189
+ ".datasource": "text/plain",
190
+ ".pipe": "text/plain",
191
+ ".connection": "text/plain",
192
+ }
193
+ TINYBIRD_API_URL = tb_client.host + "/v1/build"
194
+ logging.debug(TINYBIRD_API_URL)
195
+ TINYBIRD_API_KEY = tb_client.token
196
+ error: Optional[str] = None
197
+ try:
198
+ files = [
199
+ ("context://", ("cli-version", "1.0.0", "text/plain")),
200
+ ]
201
+ project_path = project.path
202
+ project_files = project.get_project_files()
203
+
204
+ if not project_files:
205
+ return False
206
+
207
+ for file_path in project_files:
208
+ relative_path = Path(file_path).relative_to(project_path).as_posix()
209
+ with open(file_path, "rb") as fd:
210
+ content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
211
+ files.append(
212
+ (MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, fd.read().decode("utf-8"), content_type))
213
+ )
214
+ HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
215
+
216
+ r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS)
217
+ try:
218
+ result = r.json()
219
+ except Exception as e:
220
+ logging.debug(e, exc_info=True)
221
+ click.echo(FeedbackManager.error(message="Couldn't parse response from server"))
222
+ sys_exit("build_error", str(e))
223
+
224
+ logging.debug(json.dumps(result, indent=2))
225
+
226
+ build_result = result.get("result")
227
+ if build_result == "success":
228
+ build = result.get("build")
229
+ new_datasources = build.get("new_datasource_names", [])
230
+ new_pipes = build.get("new_pipe_names", [])
231
+ new_connections = build.get("new_data_connector_names", [])
232
+ changed_datasources = build.get("changed_datasource_names", [])
233
+ changed_pipes = build.get("changed_pipe_names", [])
234
+ changed_connections = build.get("changed_data_connector_names", [])
235
+ deleted_datasources = build.get("deleted_datasource_names", [])
236
+ deleted_pipes = build.get("deleted_pipe_names", [])
237
+ deleted_connections = build.get("deleted_data_connector_names", [])
238
+
239
+ no_changes = (
240
+ not new_datasources
241
+ and not changed_datasources
242
+ and not new_pipes
243
+ and not changed_pipes
244
+ and not new_connections
245
+ and not changed_connections
246
+ and not deleted_datasources
247
+ and not deleted_pipes
248
+ and not deleted_connections
249
+ )
250
+ if no_changes:
251
+ return False
252
+ else:
253
+ if not silent:
254
+ echo_changes(project, new_datasources, ".datasource", "created")
255
+ echo_changes(project, changed_datasources, ".datasource", "changed")
256
+ echo_changes(project, deleted_datasources, ".datasource", "deleted")
257
+ echo_changes(project, new_pipes, ".pipe", "created")
258
+ echo_changes(project, changed_pipes, ".pipe", "changed")
259
+ echo_changes(project, deleted_pipes, ".pipe", "deleted")
260
+ echo_changes(project, new_connections, ".connection", "created")
261
+ echo_changes(project, changed_connections, ".connection", "changed")
262
+ echo_changes(project, deleted_connections, ".connection", "deleted")
263
+ try:
264
+ for filename in project_files:
265
+ if filename.endswith(".datasource"):
266
+ ds_path = Path(filename)
267
+ ds_name = ds_path.stem
268
+ fixture_folder = get_fixture_dir(project.folder)
269
+ fixture_extensions = [FixtureExtension.NDJSON, FixtureExtension.CSV]
270
+ fixture_path = next(
271
+ (
272
+ fixture_folder / f"{ds_name}{ext}"
273
+ for ext in fixture_extensions
274
+ if (fixture_folder / f"{ds_name}{ext}").exists()
275
+ ),
276
+ None,
277
+ )
278
+ if not fixture_path:
279
+ sql_path = fixture_folder / f"{ds_name}.sql"
280
+ if sql_path.exists():
281
+ fixture_path = rebuild_fixture_sql(project, tb_client, str(sql_path))
282
+
283
+ if fixture_path:
284
+ append_fixture(tb_client, ds_name, str(fixture_path))
285
+
286
+ except Exception as e:
287
+ click.echo(FeedbackManager.error_exception(error=f"Error appending fixtures for '{ds_name}': {e}"))
288
+
289
+ feedback = build.get("feedback", [])
290
+ for f in feedback:
291
+ click.echo(
292
+ FeedbackManager.warning(message=f"△ {f.get('level')}: {f.get('resource')}: {f.get('message')}")
293
+ )
294
+ elif build_result == "failed":
295
+ build_errors = result.get("errors")
296
+ full_error_msg = ""
297
+ for build_error in build_errors:
298
+ filename_bit = build_error.get("filename", build_error.get("resource", ""))
299
+ error_bit = build_error.get("error") or build_error.get("message") or ""
300
+ error_msg = ((filename_bit + "\n") if filename_bit else "") + error_bit
301
+ full_error_msg += error_msg + "\n\n"
302
+ error = full_error_msg.strip("\n") or "Unknown build error"
303
+ else:
304
+ error = f"Unknown build result. Error: {result.get('error')}"
305
+ except Exception as e:
306
+ error = str(e)
307
+
308
+ if error:
309
+ raise click.ClickException(error)
310
+
311
+ return build_result
312
+
313
+
314
+ def echo_changes(project: Project, changes: list[str], extension: str, status: str):
315
+ for resource in changes:
316
+ path_str = next(
317
+ (p for p in project.get_project_files() if p.endswith(resource + extension)), resource + extension
318
+ )
319
+ if path_str:
320
+ path_str = path_str.replace(f"{project.folder}/", "")
321
+ click.echo(FeedbackManager.info(message=f"✓ {path_str} {status}"))
@@ -196,7 +196,7 @@ def cli(
196
196
  is_agent_mode = agent_mode_flag and ctx.invoked_subcommand is None
197
197
 
198
198
  if is_agent_mode:
199
- run_agent(config.get("token", None), config["host"], str(project.path))
199
+ run_agent(config, project)
200
200
 
201
201
 
202
202
  @cli.command(hidden=True)
@@ -1,8 +1,8 @@
1
1
  import re
2
- from typing import Dict, Optional
2
+ from typing import Optional
3
3
 
4
4
  import click
5
- from dotenv import dotenv_values, set_key
5
+ from dotenv import set_key
6
6
 
7
7
  from tinybird.tb.client import TinyB
8
8
  from tinybird.tb.modules.cli import cli
@@ -97,54 +97,6 @@ def secret_rm(ctx: click.Context, name: str):
97
97
  click.echo(FeedbackManager.error(message=f"✗ Error: {e}"))
98
98
 
99
99
 
100
- def load_secrets(project: Project, client: TinyB):
101
- try:
102
- env_vars: Dict[str, str] = {}
103
-
104
- # Load secrets from .env file
105
- env_file = ".env"
106
- env_path = project.path / env_file
107
-
108
- if env_path.exists():
109
- env_values = dotenv_values(env_path)
110
- if env_values:
111
- env_vars.update({k: v for k, v in env_values.items() if v is not None})
112
-
113
- # Load secrets from .env.local file
114
- env_file = ".env.local"
115
- env_path = project.path / env_file
116
-
117
- if env_path.exists():
118
- env_values = dotenv_values(env_path)
119
- if env_values:
120
- env_vars.update({k: v for k, v in env_values.items() if v is not None})
121
-
122
- if len(env_vars.keys()) == 0:
123
- return
124
-
125
- click.echo(FeedbackManager.highlight(message="\n» Loading secrets from .env files..."))
126
-
127
- for name, value in env_vars.items():
128
- if not value:
129
- continue
130
-
131
- try:
132
- existing_secret = client.get_secret(name)
133
- except Exception:
134
- existing_secret = None
135
- try:
136
- if existing_secret:
137
- client.update_secret(name, value)
138
- else:
139
- client.create_secret(name, value)
140
- except Exception as e:
141
- click.echo(FeedbackManager.error(message=f"✗ Error setting secret '{name}': {e}"))
142
-
143
- click.echo(FeedbackManager.success(message="✓ Secrets loaded!"))
144
- except Exception as e:
145
- click.echo(FeedbackManager.error(message=f"✗ Error: {e}"))
146
-
147
-
148
100
  def save_secret_to_env_file(project: Project, name: str, value: str):
149
101
  env_path = project.path / ".env.local"
150
102
 
@@ -0,0 +1,56 @@
1
+ from typing import Dict
2
+
3
+ import click
4
+ from dotenv import dotenv_values
5
+
6
+ from tinybird.tb.client import TinyB
7
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
8
+ from tinybird.tb.modules.project import Project
9
+
10
+
11
+ def load_secrets(project: Project, client: TinyB):
12
+ try:
13
+ env_vars: Dict[str, str] = {}
14
+
15
+ # Load secrets from .env file
16
+ env_file = ".env"
17
+ env_path = project.path / env_file
18
+
19
+ if env_path.exists():
20
+ env_values = dotenv_values(env_path)
21
+ if env_values:
22
+ env_vars.update({k: v for k, v in env_values.items() if v is not None})
23
+
24
+ # Load secrets from .env.local file
25
+ env_file = ".env.local"
26
+ env_path = project.path / env_file
27
+
28
+ if env_path.exists():
29
+ env_values = dotenv_values(env_path)
30
+ if env_values:
31
+ env_vars.update({k: v for k, v in env_values.items() if v is not None})
32
+
33
+ if len(env_vars.keys()) == 0:
34
+ return
35
+
36
+ click.echo(FeedbackManager.highlight(message="\n» Loading secrets from .env files..."))
37
+
38
+ for name, value in env_vars.items():
39
+ if not value:
40
+ continue
41
+
42
+ try:
43
+ existing_secret = client.get_secret(name)
44
+ except Exception:
45
+ existing_secret = None
46
+ try:
47
+ if existing_secret:
48
+ client.update_secret(name, value)
49
+ else:
50
+ client.create_secret(name, value)
51
+ except Exception as e:
52
+ click.echo(FeedbackManager.error(message=f"✗ Error setting secret '{name}': {e}"))
53
+
54
+ click.echo(FeedbackManager.success(message="✓ Secrets loaded!"))
55
+ except Exception as e:
56
+ click.echo(FeedbackManager.error(message=f"✗ Error: {e}"))
@@ -26,7 +26,7 @@ from tinybird.tb.modules.llm import LLM
26
26
  from tinybird.tb.modules.llm_utils import extract_xml, parse_xml
27
27
  from tinybird.tb.modules.local_common import get_local_tokens, get_test_workspace_name
28
28
  from tinybird.tb.modules.project import Project
29
- from tinybird.tb.modules.secret import load_secrets
29
+ from tinybird.tb.modules.secret_common import load_secrets
30
30
 
31
31
  yaml.SafeDumper.org_represent_str = yaml.SafeDumper.represent_str # type: ignore[attr-defined]
32
32
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev242
3
+ Version: 0.0.1.dev245
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -12,19 +12,20 @@ tinybird/syncasync.py,sha256=IPnOx6lMbf9SNddN1eBtssg8vCLHMt76SuZ6YNYm-Yk,27761
12
12
  tinybird/tornado_template.py,sha256=jjNVDMnkYFWXflmT8KU_Ssbo5vR8KQq3EJMk5vYgXRw,41959
13
13
  tinybird/ch_utils/constants.py,sha256=yEKR11gLCL-irEXXF9QwShaR0JLXiBTlaxfolcCIoqY,4097
14
14
  tinybird/ch_utils/engine.py,sha256=X4tE9OrfaUy6kO9cqVEzyI9cDcmOF3IAssRRzsTsfEQ,40781
15
- tinybird/datafile/common.py,sha256=q-enN3RGK6W-8Yqab7Al_BSjRwF8dXiEyk_zHykBM6s,97391
15
+ tinybird/datafile/common.py,sha256=J0Oydru3Nh3oCx66EEEHGxm2-r5_NfxvZxiFhBjgAH0,98428
16
16
  tinybird/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1wnI,556
17
17
  tinybird/datafile/parse_connection.py,sha256=tRyn2Rpr1TeWet5BXmMoQgaotbGdYep1qiTak_OqC5E,1825
18
18
  tinybird/datafile/parse_datasource.py,sha256=ssW8QeFSgglVFi3sDZj_HgkJiTJ2069v2JgqnH3CkDE,1825
19
19
  tinybird/datafile/parse_pipe.py,sha256=xf4m0Tw44QWJzHzAm7Z7FwUoUUtr7noMYjU1NiWnX0k,3880
20
- tinybird/tb/__cli__.py,sha256=WaMYFAckfNWnMzyIA-CatGh7AXWIKDHBGMB-QqtF2rw,247
20
+ tinybird/tb/__cli__.py,sha256=lO7Woz0azArhNCFKAWijw8uJo4jrQDbI-rUCxK_YeO0,247
21
21
  tinybird/tb/check_pypi.py,sha256=Gp0HkHHDFMSDL6nxKlOY51z7z1Uv-2LRexNTZSHHGmM,552
22
22
  tinybird/tb/cli.py,sha256=FdDFEIayjmsZEVsVSSvRiVYn_FHOVg_zWQzchnzfWho,1008
23
23
  tinybird/tb/client.py,sha256=pJbdkWMXGAqKseNAvdsRRnl_c7I-DCMB0dWCQnG82nU,54146
24
24
  tinybird/tb/config.py,sha256=mhMTGnMB5KcxGoh3dewIr2Jjsa6pHE183gCPAQWyp6o,3973
25
- tinybird/tb/modules/build.py,sha256=MxGQY-KII4lYut-yOWGBIHFpg-m_cfHgpE7lwOzZCeQ,19871
25
+ tinybird/tb/modules/build.py,sha256=2ijDOQDpb64v7zLRfl38UdJATkehoeunFfi2sozSjog,7609
26
+ tinybird/tb/modules/build_common.py,sha256=3umtAVUir9Xdcu7auY5kpcA_RdQ6UoijwWXHoZEmpeg,12867
26
27
  tinybird/tb/modules/cicd.py,sha256=0KLKccha9IP749QvlXBmzdWv1On3mFwMY4DUcJlBxiE,7326
27
- tinybird/tb/modules/cli.py,sha256=_-kj4PSN_OPYk__WYgFfzLsDJfy4cQfveZg6tTOc220,16389
28
+ tinybird/tb/modules/cli.py,sha256=c1r7Z7NZUIUt9O2c5IRbt4uBKiV8LGOXM4c8kOx_vyY,16344
28
29
  tinybird/tb/modules/common.py,sha256=jTTaDDHrZREt--032XhP6GkbfFwC79YJ5aH1Sl7bmbo,81925
29
30
  tinybird/tb/modules/config.py,sha256=gK7rgaWTDd4ZKCrNEg_Uemr26EQjqWt6TjyQKujxOws,11462
30
31
  tinybird/tb/modules/connection.py,sha256=-MY56NUAai6EMC4-wpi7bT0_nz_SA8QzTmHkV7HB1IQ,17810
@@ -52,28 +53,29 @@ tinybird/tb/modules/open.py,sha256=LYiuO8Z1I9O_v6pv58qpUCWFD6BT00BdeO21fRa4I4Y,1
52
53
  tinybird/tb/modules/pipe.py,sha256=xPKtezhnWZ6k_g82r4XpgKslofhuIxb_PvynH4gdUzI,2393
53
54
  tinybird/tb/modules/project.py,sha256=pOcvtgsR0ibPi0sNu-6GuAI4WS2DORRALezisjN3xY8,5662
54
55
  tinybird/tb/modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
55
- tinybird/tb/modules/secret.py,sha256=fSOwsG-swZC65IesYKCEuCmtRCqIvXGT3DscyYTxMms,5149
56
+ tinybird/tb/modules/secret.py,sha256=9BIdh2PZDAbY2wRbf4ZDvkEltygztz1RMxgDmY1D0LI,3521
57
+ tinybird/tb/modules/secret_common.py,sha256=HyCLAI9WniDLwfK6SAb7ZUWorWjtf8j_GghlaTaos_I,1829
56
58
  tinybird/tb/modules/shell.py,sha256=_9PaKkkh6ZjkixVtKNAtoCPqXMXMn1aQJM_Xzirn7ZM,13621
57
59
  tinybird/tb/modules/sink.py,sha256=dK2s__my0ePIUYrqBzhPSgdWN9rbpvP1G4dT7DJzz80,3865
58
60
  tinybird/tb/modules/table.py,sha256=4XrtjM-N0zfNtxVkbvLDQQazno1EPXnxTyo7llivfXk,11035
59
61
  tinybird/tb/modules/telemetry.py,sha256=T9gtsQffWqG_4hRBaUJPzOfMkPwz7mH-R6Bn1XRYViA,11482
60
- tinybird/tb/modules/test.py,sha256=DH3ZnKEJfa_7OTO803ejDNAOV98bJV1-K6-mWKfJ-sA,13138
62
+ tinybird/tb/modules/test.py,sha256=ma6wLSf4PPr1vywSBafU80oR3nJLo3ZSK-_judTiiiE,13145
61
63
  tinybird/tb/modules/token.py,sha256=DkXW9FNCLGBisXewfk195jTJ6B1Iz7zq3cEEac48aAs,12731
62
64
  tinybird/tb/modules/watch.py,sha256=No0bK1M1_3CYuMaIgylxf7vYFJ72lTJe3brz6xQ-mJo,8819
63
65
  tinybird/tb/modules/workspace.py,sha256=Q_8HcxMsNg8QG9aBlwcWS2umrDP5IkTIHqqz3sfmGuc,11341
64
66
  tinybird/tb/modules/workspace_members.py,sha256=5JdkJgfuEwbq-t6vxkBhYwgsiTDxF790wsa6Xfif9nk,8608
65
67
  tinybird/tb/modules/agent/__init__.py,sha256=i3oe3vDIWWPaicdCM0zs7D7BJ1W0k7th93ooskHAV00,54
66
- tinybird/tb/modules/agent/agent.py,sha256=TWHU9gEPH7EZQguno1P_HZ5jO7v6QLj9UE1btgaAOxg,10570
68
+ tinybird/tb/modules/agent/agent.py,sha256=h0va31JtVzwxaQ6kD1V8cwclNhpbzJeJbtKOps5MGIg,11864
67
69
  tinybird/tb/modules/agent/animations.py,sha256=z0MNLf8TnUO8qAjgYvth_wc9a9283pNVz1Z4jl15Ggs,2558
68
70
  tinybird/tb/modules/agent/banner.py,sha256=KX_e467uiy1gWOZ4ofTZt0GCFGQqHQ_8Ob27XLQqda0,3053
69
71
  tinybird/tb/modules/agent/memory.py,sha256=H6SJK--2L5C87B7AJd_jMqsq3sCvFvZwZXmajuT0GBE,1171
70
72
  tinybird/tb/modules/agent/models.py,sha256=mf8dRCdof6uEFZWh5xQ_D_FStk7eDds7qWRNSbDklUM,589
71
- tinybird/tb/modules/agent/prompts.py,sha256=j7nkx1E2BnPP6Ra718FvSDGRWucyFgSX07V9QAiaiGg,4864
72
- tinybird/tb/modules/agent/utils.py,sha256=brW-2StkAy-X1LGDbjQYhOXMSifGwWJCSkPDDmo7SUk,13069
73
+ tinybird/tb/modules/agent/prompts.py,sha256=rAbcqkrw7BKVuV2sNRGpbLXxfo95suKWRLPwgNx7fdM,5784
74
+ tinybird/tb/modules/agent/utils.py,sha256=SSfAmPTO-pv4UtRrsAbERHjjeqZ3Mkx6Y_eRD5QXr9M,13154
73
75
  tinybird/tb/modules/agent/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
- tinybird/tb/modules/agent/tools/create_datafile.py,sha256=V5VMFvpne-LW1XzQY7AnSMiOkMALSRspEjtBqK1Glnk,1858
76
+ tinybird/tb/modules/agent/tools/create_datafile.py,sha256=e7xMaPziVw5JPaQ8rmeEnNz6SCkR3MP1ZBvvXb6QOwE,2314
75
77
  tinybird/tb/modules/agent/tools/explore.py,sha256=ihALc_kBcsjrKT3hZyicqyIowB0g_K3AtNNi-5uz9-8,412
76
- tinybird/tb/modules/agent/tools/plan.py,sha256=K9mXqCgLXbwXtyFFjPvrrwRwEm2rNnIMn73qazDTQts,1345
78
+ tinybird/tb/modules/agent/tools/plan.py,sha256=CMSGrqjdVyhsJ0U1M5B2eRFLZXE7HqJ4K8tl1Ile0f0,1324
77
79
  tinybird/tb/modules/agent/tools/preview_datafile.py,sha256=e9q5fR0afApcrntzFrnuHmd10ex7MG_GM6T0Pwc9bRI,850
78
80
  tinybird/tb/modules/datafile/build.py,sha256=NFKBrusFLU0WJNCXePAFWiEDuTaXpwc0lHlOQWEJ43s,51117
79
81
  tinybird/tb/modules/datafile/build_common.py,sha256=2yNdxe49IMA9wNvl25NemY2Iaz8L66snjOdT64dm1is,4511
@@ -95,8 +97,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
95
97
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
96
98
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
97
99
  tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
98
- tinybird-0.0.1.dev242.dist-info/METADATA,sha256=jcAeg7hLOB_0X5tNRd6Jchij6S62DjqZ3I4vHblBchk,1733
99
- tinybird-0.0.1.dev242.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
100
- tinybird-0.0.1.dev242.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
101
- tinybird-0.0.1.dev242.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
102
- tinybird-0.0.1.dev242.dist-info/RECORD,,
100
+ tinybird-0.0.1.dev245.dist-info/METADATA,sha256=1Qqly3uB1KyENHgniVShFyw6tTeJRz7UZWntvH9AR24,1733
101
+ tinybird-0.0.1.dev245.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
102
+ tinybird-0.0.1.dev245.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
103
+ tinybird-0.0.1.dev245.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
104
+ tinybird-0.0.1.dev245.dist-info/RECORD,,