tinybird 0.0.1.dev42__py3-none-any.whl → 0.0.1.dev44__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.

@@ -18,6 +18,7 @@ from tinybird.tb.modules.feedback_manager import FeedbackManager
18
18
  from tinybird.tb.modules.llm import LLM
19
19
  from tinybird.tb.modules.llm_utils import extract_xml, parse_xml
20
20
  from tinybird.tb.modules.local_common import get_tinybird_local_client
21
+ from tinybird.tb.modules.project import Project
21
22
 
22
23
 
23
24
  @cli.command()
@@ -41,8 +42,10 @@ from tinybird.tb.modules.local_common import get_tinybird_local_client
41
42
  )
42
43
  @click.option("--rows", type=int, default=10, help="Number of events to send")
43
44
  @click.option("--cursor", default=False, is_flag=True, help="Create .cursorrules file with Tinybird rules")
45
+ @click.pass_context
44
46
  @coro
45
47
  async def create(
48
+ ctx: click.Context,
46
49
  data: Optional[str],
47
50
  prompt: Optional[str],
48
51
  folder: Optional[str],
@@ -50,13 +53,14 @@ async def create(
50
53
  cursor: bool,
51
54
  ) -> None:
52
55
  """Initialize a new project."""
56
+ project: Project = ctx.ensure_object(dict)["project"]
53
57
  folder = folder or getcwd()
54
58
  folder_path = Path(folder)
55
59
  if not folder_path.exists():
56
60
  folder_path.mkdir()
57
61
 
58
62
  try:
59
- config = CLIConfig.get_project_config(folder)
63
+ config = CLIConfig.get_project_config(str(project.path))
60
64
  tb_client = config.get_client()
61
65
  user_token: Optional[str] = None
62
66
  if prompt:
@@ -80,7 +84,7 @@ async def create(
80
84
  click.echo(FeedbackManager.success(message="✓ Scaffolding completed!\n"))
81
85
 
82
86
  click.echo(FeedbackManager.highlight(message="\n» Creating resources..."))
83
- datasources_created = await create_resources(local_client, tb_client, user_token, data, prompt, folder)
87
+ result = await create_resources(local_client, tb_client, user_token, data, prompt, folder)
84
88
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
85
89
 
86
90
  if not already_has_cicd(folder):
@@ -89,7 +93,7 @@ async def create(
89
93
  await init_cicd(data_project_dir=os.path.relpath(folder))
90
94
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
91
95
 
92
- if validate_fixtures(folder) and datasources_created:
96
+ if should_generate_fixtures(result):
93
97
  click.echo(FeedbackManager.highlight(message="\n» Generating fixtures..."))
94
98
 
95
99
  if data:
@@ -110,7 +114,7 @@ async def create(
110
114
  datasource_content = datasource_path.read_text()
111
115
  has_json_path = "`json:" in datasource_content
112
116
  if has_json_path:
113
- prompt = f"<datasource_schema>{datasource_content}</datasource_schema>\n<user_input>{prompt}</user_input>"
117
+ prompt = f"<datasource_schema>{datasource_content}</datasource_schema>"
114
118
  response = llm.ask(system_prompt=mock_prompt(rows), prompt=prompt)
115
119
  sql = extract_xml(response, "sql")
116
120
  sql = sql.split("FORMAT")[0]
@@ -139,9 +143,10 @@ def validate_project_structure(folder: str) -> bool:
139
143
  return all((Path(folder) / path).exists() for path in PROJECT_PATHS)
140
144
 
141
145
 
142
- def validate_fixtures(folder: str) -> bool:
143
- datasource_files = [f for f in os.listdir(Path(folder) / "datasources") if f.endswith(".datasource")]
144
- return len(datasource_files) > 0
146
+ def should_generate_fixtures(result: str) -> bool:
147
+ if not result:
148
+ return False
149
+ return "<type>datasource</type>" in result
145
150
 
146
151
 
147
152
  def already_has_cicd(folder: str) -> bool:
@@ -168,12 +173,12 @@ async def create_resources(
168
173
  prompt: Optional[str],
169
174
  folder: str,
170
175
  ):
171
- force = True
176
+ result = ""
172
177
  folder_path = Path(folder)
173
178
  if data:
174
179
  path = folder_path / data
175
180
  format = path.suffix.lstrip(".")
176
- await _generate_datafile(str(path), local_client, format=format, force=force)
181
+ await _generate_datafile(str(path), local_client, format=format, force=True)
177
182
  name = data.split(".")[0]
178
183
  generate_pipe_file(
179
184
  f"{name}_endpoint",
@@ -185,7 +190,10 @@ TYPE ENDPOINT
185
190
  """,
186
191
  folder,
187
192
  )
188
- return True
193
+ result = (
194
+ f"<response><resource><type>datasource</type><name>{name}</name><content></content></resource></response>"
195
+ )
196
+
189
197
  elif prompt and user_token:
190
198
  datasource_paths = [
191
199
  Path(folder) / "datasources" / f
@@ -212,41 +220,74 @@ TYPE ENDPOINT
212
220
  ]
213
221
  )
214
222
  llm = LLM(user_token=user_token, host=tb_client.host)
215
- result = llm.ask(system_prompt=create_prompt(resources_xml), prompt=prompt)
216
- result = extract_xml(result, "response")
217
- resources = parse_xml(result, "resource")
218
- datasources = []
219
- pipes = []
220
- for resource_xml in resources:
221
- resource_type = extract_xml(resource_xml, "type")
222
- name = extract_xml(resource_xml, "name")
223
- content = extract_xml(resource_xml, "content")
224
- resource = {
225
- "name": name,
226
- "content": content,
227
- }
228
- if resource_type.lower() == "datasource":
229
- datasources.append(resource)
230
- elif resource_type.lower() == "pipe":
231
- pipes.append(resource)
232
-
233
- for ds in datasources:
234
- content = ds["content"].replace("```", "")
235
- filename = f"{ds['name']}.datasource"
236
- generate_datafile(
237
- content,
238
- filename=filename,
239
- data=None,
240
- _format="ndjson",
241
- force=force,
242
- folder=folder,
243
- )
244
-
245
- for pipe in pipes:
246
- content = pipe["content"].replace("```", "")
247
- generate_pipe_file(pipe["name"], content, folder)
248
-
249
- return len(datasources) > 0
223
+ result = ""
224
+ iterations = 0
225
+ history = ""
226
+ generated_paths: list[Path] = []
227
+
228
+ while iterations < 10:
229
+ feedback = ""
230
+ if iterations > 0:
231
+ feedback = click.prompt("\nFollow-up instructions or continue", default="continue")
232
+ if iterations > 0 and (not feedback or feedback in ("continue", "ok", "exit", "quit", "q")):
233
+ break
234
+ else:
235
+ if iterations > 0:
236
+ click.echo(FeedbackManager.highlight(message="\n» Creating resources..."))
237
+ for path in generated_paths:
238
+ path.unlink()
239
+ generated_paths = []
240
+
241
+ save_context(prompt, feedback)
242
+ result = llm.ask(system_prompt=create_prompt(resources_xml, feedback, history), prompt=prompt)
243
+ result = extract_xml(result, "response")
244
+ history = (
245
+ history
246
+ + f"""
247
+ <result_iteration_{iterations}>
248
+ {result}
249
+ </result_iteration_{iterations}>
250
+ """
251
+ )
252
+ resources = parse_xml(result, "resource")
253
+ datasources = []
254
+ pipes = []
255
+ for resource_xml in resources:
256
+ resource_type = extract_xml(resource_xml, "type")
257
+ name = extract_xml(resource_xml, "name")
258
+ content = extract_xml(resource_xml, "content")
259
+ resource = {
260
+ "name": name,
261
+ "content": content,
262
+ }
263
+ if resource_type.lower() == "datasource":
264
+ datasources.append(resource)
265
+ elif resource_type.lower() == "pipe":
266
+ pipes.append(resource)
267
+
268
+ for ds in datasources:
269
+ content = ds["content"].replace("```", "")
270
+ filename = f"{ds['name']}.datasource"
271
+ datasource_path = generate_datafile(
272
+ content,
273
+ filename=filename,
274
+ data=None,
275
+ _format="ndjson",
276
+ force=True,
277
+ folder=folder,
278
+ )
279
+ generated_paths.append(datasource_path)
280
+ for pipe in pipes:
281
+ content = pipe["content"].replace("```", "")
282
+ pipe_path = generate_pipe_file(pipe["name"], content, folder)
283
+ generated_paths.append(pipe_path)
284
+
285
+ iterations += 1
286
+
287
+ if iterations == 10:
288
+ click.echo(FeedbackManager.info(message="Too many iterations. Change the prompt and try again."))
289
+
290
+ return result
250
291
 
251
292
 
252
293
  def init_git(folder: str):
@@ -266,7 +307,7 @@ def init_git(folder: str):
266
307
  raise CLIException(f"Error initializing Git: {e}")
267
308
 
268
309
 
269
- def generate_pipe_file(name: str, content: str, folder: str):
310
+ def generate_pipe_file(name: str, content: str, folder: str) -> Path:
270
311
  def is_copy(content: str) -> bool:
271
312
  return re.search(r"TYPE copy", content, re.IGNORECASE) is not None
272
313
 
@@ -292,6 +333,7 @@ def generate_pipe_file(name: str, content: str, folder: str):
292
333
  with open(f"{f}", "w") as file:
293
334
  file.write(content)
294
335
  click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder)))
336
+ return f.relative_to(folder)
295
337
 
296
338
 
297
339
  def already_has_cursorrules(folder: str) -> bool:
@@ -301,3 +343,20 @@ def already_has_cursorrules(folder: str) -> bool:
301
343
  def create_cursorrules(folder: str):
302
344
  cursorrules_file = Path(folder) / ".cursorrules"
303
345
  cursorrules_file.write_text(cursorrules_prompt)
346
+
347
+
348
+ def get_context_file() -> Path:
349
+ context_file = Path(os.path.expanduser("~/.tb_create_context"))
350
+ if not context_file.exists():
351
+ context_file.touch()
352
+ return context_file
353
+
354
+
355
+ def get_context() -> str:
356
+ context_file = get_context_file()
357
+ return context_file.read_text()
358
+
359
+
360
+ def save_context(prompt: str, feedback: str):
361
+ context_file = get_context_file()
362
+ context_file.write_text(f"- {prompt}\n{feedback}")