tinybird 0.0.1.dev148__py3-none-any.whl → 0.0.1.dev152__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.

tinybird/prompts.py CHANGED
@@ -401,7 +401,7 @@ Follow the instructions and generate the following response with no additional t
401
401
  def create_prompt(existing_resources: str, feedback: str = "", history: str = "") -> str:
402
402
  feedback_history = ""
403
403
  if feedback and history:
404
- feedback_history = f"""In case the <feedback> and <history> tags are present and not empty,
404
+ feedback_history = f"""In case the <feedback> and <history> tags are present and not empty,
405
405
  it means there was a previous attempt to generate the resources and the user provided feedback and history about previous responses.
406
406
  Use the following feedback and history to regenerate the response:
407
407
  Feedback to improve the response:
@@ -420,7 +420,9 @@ You are a Tinybird expert. You will be given a prompt to generate new or update
420
420
  {copy_pipe_instructions}
421
421
  {materialized_pipe_instructions}
422
422
  {connection_instructions}
423
- {connection_example}
423
+ {kafka_connection_example}
424
+ {gcs_connection_example}
425
+ {s3_connection_example}
424
426
 
425
427
  {feedback_history}
426
428
 
@@ -443,14 +445,16 @@ Use the following format to generate the response and do not wrap it in any othe
443
445
  copy_pipe_instructions=copy_pipe_instructions,
444
446
  materialized_pipe_instructions=materialized_pipe_instructions,
445
447
  connection_instructions=connection_instructions,
446
- connection_example=connection_example,
448
+ kafka_connection_example=kafka_connection_example,
449
+ gcs_connection_example=gcs_connection_example,
450
+ s3_connection_example=s3_connection_example,
447
451
  feedback_history=feedback_history,
448
452
  )
449
453
 
450
454
 
451
455
  def mock_prompt(rows: int, feedback: str = "") -> str:
452
456
  if feedback:
453
- feedback = f"""In case the <feedback> tag is present and not empty,
457
+ feedback = f"""In case the <feedback> tag is present and not empty,
454
458
  it means there was a previous attempt to generate the resources and the system provided feedback about the previous response.
455
459
  Use the following feedback to regenerate the response:
456
460
  <feedback>{feedback}</feedback>
@@ -515,7 +519,7 @@ SCHEMA >
515
519
  `customer_id` UInt64 `json:$.customer_id`,
516
520
  `order_date` DateTime `json:$.order_date`,
517
521
  `total_amount` Float64 `json:$.total_amount`,
518
- `items` Array(String) `json:$.items[:]` // This is an array field
522
+ `items` Array(String) `json:$.items[:]` // This is an array field
519
523
 
520
524
  ### Desired final output of the query:
521
525
  {{
@@ -680,19 +684,34 @@ connection_instructions = """
680
684
  - Content cannot be empty.
681
685
  - The connection names must be unique.
682
686
  - No indentation is allowed for property names
683
- - We only support kafka connections for now
687
+ - We support kafka, gcs and s3 connections for now
684
688
  </connection_file_instructions>
685
689
  """
686
690
 
687
- connection_example = """
688
- <connection_content>
691
+ kafka_connection_example = """
692
+ <kafka_connection_content>
689
693
  TYPE kafka
690
694
  KAFKA_BOOTSTRAP_SERVERS {{ tb_secret("PRODUCTION_KAFKA_SERVERS", "localhost:9092") }}
691
695
  KAFKA_SECURITY_PROTOCOL SASL_SSL
692
696
  KAFKA_SASL_MECHANISM PLAIN
693
697
  KAFKA_KEY {{ tb_secret("PRODUCTION_KAFKA_USERNAME", "") }}
694
698
  KAFKA_SECRET {{ tb_secret("PRODUCTION_KAFKA_PASSWORD", "") }}
695
- </connection_content>
699
+ </kafka_connection_content>
700
+ """
701
+
702
+ gcs_connection_example = """
703
+ <gcs_connection_content>
704
+ TYPE gcs
705
+ GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON {{ tb_secret("PRODUCTION_GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON", "") }}
706
+ </gcs_connection_content>
707
+ """
708
+
709
+ s3_connection_example = """
710
+ <s3_connection_content>
711
+ TYPE s3
712
+ S3_REGION {{ tb_secret("PRODUCTION_S3_REGION", "") }}
713
+ S3_ARN {{ tb_secret("PRODUCTION_S3_ARN", "") }}
714
+ </s3_connection_content>
696
715
  """
697
716
 
698
717
  datasource_instructions = """
@@ -703,6 +722,7 @@ datasource_instructions = """
703
722
  - Use MergeTree engine by default.
704
723
  - Use AggregatingMergeTree engine when the datasource is the target of a materialized pipe.
705
724
  - Use always json paths to define the schema. Example: `user_id` String `json:$.user_id`,
725
+ - If the datasource is using an S3 or GCS connection, they need to set IMPORT_CONNECTION_NAME, IMPORT_BUCKET_URI and IMPORT_SCHEDULE (GCS @on-demand only, S3 supports @auto too)
706
726
  </datasource_file_instructions>
707
727
  """
708
728
 
@@ -775,7 +795,7 @@ sql_instructions = """
775
795
  {{%if not defined(end_date)%}}
776
796
  AND now()
777
797
  {{%else%}}
778
- AND {{{{DateTime(end_date)}}}}
798
+ AND {{{{DateTime(end_date)}}}}
779
799
  {{%end%}}
780
800
  </valid_condition_without_now>
781
801
  - Parameters must not be quoted.
@@ -862,7 +882,9 @@ Follow these instructions when creating or updating .pipe files:
862
882
  {copy_pipe_instructions}
863
883
  {materialized_pipe_instructions}
864
884
  {connection_instructions}
865
- {connection_example}
885
+ {kafka_connection_example}
886
+ {gcs_connection_example}
887
+ {s3_connection_example}
866
888
  </pipe_file_instructions>
867
889
  <test_file_instructions>
868
890
  Follow these instructions when creating or updating .yaml files for tests:
@@ -884,7 +906,9 @@ Follow these instructions when evolving a datasource schema:
884
906
  test_instructions=test_instructions,
885
907
  deployment_instructions=deployment_instructions,
886
908
  connection_instructions=connection_instructions,
887
- connection_example=connection_example,
909
+ kafka_connection_example=kafka_connection_example,
910
+ gcs_connection_example=gcs_connection_example,
911
+ s3_connection_example=s3_connection_example,
888
912
  )
889
913
 
890
914
 
@@ -950,7 +974,7 @@ SCHEMA >
950
974
  `payload` String `json:$.payload`
951
975
 
952
976
  FORWARD_QUERY >
953
- select timestamp, toUUID(session_id) as session_id, action, version, payload
977
+ select timestamp, toUUID(session_id) as session_id, action, version, payload
954
978
  </forward_query_example>
955
979
  </deployment_instruction>
956
980
  """
@@ -968,7 +992,7 @@ Current README.md file:
968
992
  - If it is not present in the current readme, generate a new ## Tinybird section with the following content:
969
993
  - ### Overview section:
970
994
  - Explaining the purpose of the project.
971
- - ### Data sources section:
995
+ - ### Data sources section:
972
996
  - Explaining the purpose of each datasource.
973
997
  - Add a snippet of how to ingest data into each datasource like the following (where the payload example matches the datasource schema respecting non-nullable types):
974
998
  curl -X POST "{host}/v0/events?name=events" \
tinybird/tb/__cli__.py CHANGED
@@ -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.dev148'
8
- __revision__ = '7b2789f'
7
+ __version__ = '0.0.1.dev152'
8
+ __revision__ = '100f32b'
@@ -323,16 +323,23 @@ async def create_ctx_client(ctx: Context, config: Dict[str, Any], cloud: bool, s
323
323
  return None
324
324
 
325
325
  commands_always_cloud = ["pull", "infra"]
326
- commands_always_local = ["build", "test", "dev"]
327
- if (cloud or command in commands_always_cloud) and command not in commands_always_local:
326
+ commands_always_local = ["build", "dev"]
327
+ command_always_test = ["test"]
328
+
329
+ if (
330
+ (cloud or command in commands_always_cloud)
331
+ and command not in commands_always_local
332
+ and command not in command_always_test
333
+ ):
328
334
  click.echo(
329
335
  FeedbackManager.gray(message=f"Running against Tinybird Cloud: Workspace {config.get('name', 'default')}")
330
336
  )
331
337
  return _get_tb_client(config.get("token", None), config["host"], staging=staging)
332
338
  local = command in commands_always_local
339
+ test = command in command_always_test
333
340
  if not local and command not in commands_always_local and command:
334
341
  click.echo(FeedbackManager.gray(message="Running against Tinybird Local"))
335
- return await get_tinybird_local_client(config, staging=staging)
342
+ return await get_tinybird_local_client(config, test=test, staging=staging)
336
343
 
337
344
 
338
345
  def get_target_env(cloud: bool) -> str:
@@ -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,14 +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
- ]
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()]
243
244
  resources_xml = "\n".join(
244
245
  [
245
246
  f"<resource><type>{resource_type}</type><name>{resource_name}</name><content>{resource_content}</content></resource>"
@@ -254,6 +255,14 @@ TYPE ENDPOINT
254
255
  )
255
256
  for pipe in pipes_paths
256
257
  ]
258
+ + [
259
+ (
260
+ "connection",
261
+ conn.stem,
262
+ conn.read_text(),
263
+ )
264
+ for conn in connections_paths
265
+ ]
257
266
  ]
258
267
  )
259
268
  llm = LLM(user_token=user_token, host=tb_client.host)
@@ -287,19 +296,19 @@ TYPE ENDPOINT
287
296
  data=None,
288
297
  _format="ndjson",
289
298
  force=True,
290
- folder=folder,
299
+ folder=project.folder,
291
300
  )
292
301
  created_any_resource = True
293
302
 
294
303
  for pipe in pipes:
295
304
  content = pipe["content"].replace("```", "")
296
- generate_pipe_file(pipe["name"], content, folder)
305
+ generate_pipe_file(pipe["name"], content, project.folder)
297
306
  created_any_resource = True
298
307
 
299
308
  for conn in connections:
300
309
  content = conn["content"].replace("```", "")
301
310
  filename = f"{conn['name']}.connection"
302
- generate_connection_file(conn["name"], content, folder)
311
+ generate_connection_file(conn["name"], content, project.folder)
303
312
  created_any_resource = True
304
313
 
305
314
  return result, created_any_resource
@@ -335,21 +344,25 @@ def generate_pipe_file(name: str, content: str, folder: str) -> Path:
335
344
  def is_endpoint(content: str) -> bool:
336
345
  return re.search(r"TYPE endpoint", content, re.IGNORECASE) is not None
337
346
 
338
- if is_copy(content):
339
- pathname = "copies"
340
- elif is_materialization(content):
341
- pathname = "materializations"
342
- elif is_sink(content):
343
- pathname = "sinks"
344
- elif is_endpoint(content):
345
- pathname = "endpoints"
347
+ already_exists = glob.glob(f"{folder}/**/{name}.pipe")
348
+ if already_exists:
349
+ f = Path(already_exists[0])
346
350
  else:
347
- 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"
348
361
 
349
- base = Path(folder) / pathname
350
- if not base.exists():
351
- base.mkdir()
352
- f = base / (f"{name}.pipe")
362
+ base = Path(folder) / pathname
363
+ if not base.exists():
364
+ base.mkdir()
365
+ f = base / (f"{name}.pipe")
353
366
  with open(f"{f}", "w") as file:
354
367
  file.write(content)
355
368
  click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder)))
@@ -357,10 +370,14 @@ def generate_pipe_file(name: str, content: str, folder: str) -> Path:
357
370
 
358
371
 
359
372
  def generate_connection_file(name: str, content: str, folder: str, skip_feedback: bool = False) -> Path:
360
- base = Path(folder) / "connections"
361
- if not base.exists():
362
- base.mkdir()
363
- 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")
364
381
  with open(f"{f}", "w") as file:
365
382
  file.write(content)
366
383
  if not skip_feedback:
@@ -846,6 +846,15 @@ 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
+ )
857
+
849
858
  try:
850
859
  # Imported in the body to be compatible with the CLI
851
860
  from chtoolset.query import check_compatible_types
@@ -9,6 +9,7 @@ from tinybird.tb.modules.feedback_manager import FeedbackManager
9
9
  context_settings=dict(
10
10
  ignore_unknown_options=True,
11
11
  ),
12
+ hidden=True,
12
13
  )
13
14
  @click.argument("args", nargs=-1, type=click.UNPROCESSED)
14
15
  def branch(args) -> None:
@@ -30,6 +31,7 @@ def branch(args) -> None:
30
31
  context_settings=dict(
31
32
  ignore_unknown_options=True,
32
33
  ),
34
+ hidden=True,
33
35
  )
34
36
  @click.argument("args", nargs=-1, type=click.UNPROCESSED)
35
37
  def check(args) -> None:
@@ -47,6 +49,7 @@ def check(args) -> None:
47
49
  context_settings=dict(
48
50
  ignore_unknown_options=True,
49
51
  ),
52
+ hidden=True,
50
53
  )
51
54
  @click.argument("args", nargs=-1, type=click.UNPROCESSED)
52
55
  def diff(args) -> None:
@@ -64,6 +67,7 @@ def diff(args) -> None:
64
67
  context_settings=dict(
65
68
  ignore_unknown_options=True,
66
69
  ),
70
+ hidden=True,
67
71
  )
68
72
  @click.argument("args", nargs=-1, type=click.UNPROCESSED)
69
73
  def fmt(args) -> None:
@@ -81,6 +85,7 @@ def fmt(args) -> None:
81
85
  context_settings=dict(
82
86
  ignore_unknown_options=True,
83
87
  ),
88
+ hidden=True,
84
89
  )
85
90
  @click.argument("args", nargs=-1, type=click.UNPROCESSED)
86
91
  def init(args) -> None:
@@ -98,6 +103,7 @@ def init(args) -> None:
98
103
  context_settings=dict(
99
104
  ignore_unknown_options=True,
100
105
  ),
106
+ hidden=True,
101
107
  )
102
108
  @click.argument("args", nargs=-1, type=click.UNPROCESSED)
103
109
  def push(args) -> None:
@@ -115,6 +121,7 @@ def push(args) -> None:
115
121
  context_settings=dict(
116
122
  ignore_unknown_options=True,
117
123
  ),
124
+ hidden=True,
118
125
  )
119
126
  @click.argument("args", nargs=-1, type=click.UNPROCESSED)
120
127
  def tag(args) -> None:
@@ -18,14 +18,14 @@ TB_LOCAL_HOST = re.sub(r"^https?://", "", os.getenv("TB_LOCAL_HOST", "localhost"
18
18
  TB_LOCAL_ADDRESS = f"http://{TB_LOCAL_HOST}:{TB_LOCAL_PORT}"
19
19
 
20
20
 
21
- async def get_tinybird_local_client(config_obj: Dict[str, Any], staging: bool = False) -> TinyB:
21
+ async def get_tinybird_local_client(config_obj: Dict[str, Any], test: bool = False, staging: bool = False) -> TinyB:
22
22
  """Get a Tinybird client connected to the local environment."""
23
23
 
24
- config = await get_tinybird_local_config(config_obj)
24
+ config = await get_tinybird_local_config(config_obj, test=test)
25
25
  return config.get_client(host=TB_LOCAL_ADDRESS, staging=staging)
26
26
 
27
27
 
28
- async def get_tinybird_local_config(config_obj: Dict[str, Any]) -> CLIConfig:
28
+ async def get_tinybird_local_config(config_obj: Dict[str, Any], test: bool = False) -> CLIConfig:
29
29
  """Craft a client config with a workspace name based on the path of the project files
30
30
 
31
31
  It uses the tokens from tinybird local
@@ -47,7 +47,10 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any]) -> CLIConfig:
47
47
  # Create a new workspace if path is provided. This is used to isolate the build in a different workspace.
48
48
  if path:
49
49
  user_client = config.get_client(host=TB_LOCAL_ADDRESS, token=user_token)
50
- ws_name = config.get("name") or config_obj.get("name") or get_build_workspace_name(path)
50
+ if test:
51
+ ws_name = get_test_workspace_name(path)
52
+ else:
53
+ ws_name = config.get("name") or config_obj.get("name") or get_build_workspace_name(path)
51
54
  if not ws_name:
52
55
  raise AuthNoTokenException()
53
56
 
@@ -61,6 +64,13 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any]) -> CLIConfig:
61
64
 
62
65
  ws = next((ws for ws in local_workspaces if ws["name"] == ws_name), None)
63
66
 
67
+ # If we are running a test, we need to delete the workspace if it already exists
68
+ if test and ws:
69
+ requests.delete(
70
+ f"{TB_LOCAL_ADDRESS}/v1/workspaces/{ws['id']}?token={user_token}&hard_delete_confirmation=yes"
71
+ )
72
+ ws = None
73
+
64
74
  if not ws:
65
75
  await user_client.create_workspace(
66
76
  ws_name, template=None, assign_to_organization_id=user_org_id, version="v1"
@@ -71,7 +81,6 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any]) -> CLIConfig:
71
81
  raise AuthNoTokenException()
72
82
 
73
83
  ws_token = ws["token"]
74
-
75
84
  config.set_token(ws_token)
76
85
  config.set_token_for_host(TB_LOCAL_ADDRESS, ws_token)
77
86
  config.set_host(TB_LOCAL_ADDRESS)
@@ -86,3 +95,8 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any]) -> CLIConfig:
86
95
  def get_build_workspace_name(path: str) -> str:
87
96
  folder_hash = hashlib.sha256(path.encode()).hexdigest()
88
97
  return f"Tinybird_Local_Build_{folder_hash}"
98
+
99
+
100
+ def get_test_workspace_name(path: str) -> str:
101
+ folder_hash = hashlib.sha256(path.encode()).hexdigest()
102
+ return f"Tinybird_Local_Test_{folder_hash}"
@@ -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
@@ -55,6 +55,15 @@ class Project:
55
55
  def connections(self) -> List[str]:
56
56
  return sorted([Path(f).stem for f in glob.glob(f"{self.path}/**/*.connection", recursive=False)])
57
57
 
58
+ def get_datasource_files(self) -> List[str]:
59
+ return glob.glob(f"{self.path}/**/*.datasource", recursive=False)
60
+
61
+ def get_pipe_files(self) -> List[str]:
62
+ return glob.glob(f"{self.path}/**/*.pipe", recursive=False)
63
+
64
+ def get_connection_files(self) -> List[str]:
65
+ return glob.glob(f"{self.path}/**/*.connection", recursive=False)
66
+
58
67
  def get_pipe_datafile(self, filename: str) -> Optional[Datafile]:
59
68
  try:
60
69
  return parse_pipe(filename).datafile
@@ -8,22 +8,25 @@ import difflib
8
8
  import glob
9
9
  import sys
10
10
  import urllib.parse
11
+ from copy import deepcopy
11
12
  from pathlib import Path
12
13
  from typing import Any, Dict, List, Optional, Tuple
13
14
 
14
15
  import click
16
+ import requests
15
17
  import yaml
16
18
  from requests import Response
17
19
 
18
20
  from tinybird.prompts import test_create_prompt
19
21
  from tinybird.tb.client import TinyB
20
- from tinybird.tb.modules.build import process
22
+ from tinybird.tb.modules.build import process as build_project
21
23
  from tinybird.tb.modules.cli import cli
22
24
  from tinybird.tb.modules.config import CLIConfig
23
- from tinybird.tb.modules.exceptions import CLITestException
25
+ from tinybird.tb.modules.exceptions import CLILocalException, CLITestException
24
26
  from tinybird.tb.modules.feedback_manager import FeedbackManager
25
27
  from tinybird.tb.modules.llm import LLM
26
28
  from tinybird.tb.modules.llm_utils import extract_xml, parse_xml
29
+ from tinybird.tb.modules.local_common import TB_LOCAL_ADDRESS, get_test_workspace_name
27
30
  from tinybird.tb.modules.project import Project
28
31
 
29
32
  yaml.SafeDumper.org_represent_str = yaml.SafeDumper.represent_str # type: ignore[attr-defined]
@@ -77,7 +80,7 @@ def test_create(ctx: click.Context, name_or_filename: str, prompt: str) -> None:
77
80
  project: Project = ctx.ensure_object(dict)["project"]
78
81
  client: TinyB = ctx.ensure_object(dict)["client"]
79
82
  click.echo(FeedbackManager.highlight(message="\n» Building project"))
80
- process(project=project, tb_client=client, watch=False, silent=True)
83
+ build_project(project=project, tb_client=client, watch=False, silent=True)
81
84
  click.echo(FeedbackManager.info(message="✓ Done!\n"))
82
85
  config = CLIConfig.get_project_config()
83
86
  folder = project.folder
@@ -140,6 +143,8 @@ def test_create(ctx: click.Context, name_or_filename: str, prompt: str) -> None:
140
143
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
141
144
  except Exception as e:
142
145
  raise CLITestException(FeedbackManager.error(message=str(e)))
146
+ finally:
147
+ cleanup_test_workspace(client, project.folder)
143
148
 
144
149
 
145
150
  @test.command(
@@ -154,7 +159,7 @@ def test_update(ctx: click.Context, pipe: str) -> None:
154
159
  project: Project = ctx.ensure_object(dict)["project"]
155
160
  folder = project.folder
156
161
  click.echo(FeedbackManager.highlight(message="\n» Building project"))
157
- process(project=project, tb_client=client, watch=False, silent=True)
162
+ build_project(project=project, tb_client=client, watch=False, silent=True)
158
163
  click.echo(FeedbackManager.info(message="✓ Done!"))
159
164
  pipe_tests_path = get_pipe_path(pipe, folder)
160
165
  pipe_name = pipe_tests_path.stem
@@ -191,6 +196,8 @@ def test_update(ctx: click.Context, pipe: str) -> None:
191
196
  click.echo(FeedbackManager.success(message="✓ Done!\n"))
192
197
  except Exception as e:
193
198
  raise CLITestException(FeedbackManager.error(message=str(e)))
199
+ finally:
200
+ cleanup_test_workspace(client, project.folder)
194
201
 
195
202
 
196
203
  @test.command(
@@ -204,7 +211,7 @@ def run_tests(ctx: click.Context, name: Tuple[str, ...]) -> None:
204
211
  client: TinyB = ctx.ensure_object(dict)["client"]
205
212
  project: Project = ctx.ensure_object(dict)["project"]
206
213
  click.echo(FeedbackManager.highlight(message="\n» Building project"))
207
- process(project=project, tb_client=client, watch=False, silent=True)
214
+ build_project(project=project, tb_client=client, watch=False, silent=True)
208
215
  click.echo(FeedbackManager.info(message="✓ Done!"))
209
216
 
210
217
  click.echo(FeedbackManager.highlight(message="\n» Running tests"))
@@ -268,6 +275,8 @@ def run_tests(ctx: click.Context, name: Tuple[str, ...]) -> None:
268
275
  click.echo(FeedbackManager.success(message=f"\n✓ {test_count}/{test_count} passed"))
269
276
  except Exception as e:
270
277
  raise CLITestException(FeedbackManager.error(message=str(e)))
278
+ finally:
279
+ cleanup_test_workspace(client, project.folder)
271
280
 
272
281
 
273
282
  async def get_pipe_data(client: TinyB, pipe_name: str, test_params: str) -> Response:
@@ -301,3 +310,22 @@ def get_pipe_path(name_or_filename: str, folder: str) -> Path:
301
310
  raise Exception(f"Pipe {name_or_filename} not found")
302
311
 
303
312
  return pipe_path
313
+
314
+
315
+ def cleanup_test_workspace(client: TinyB, path: str) -> None:
316
+ user_client = deepcopy(client)
317
+ try:
318
+ # ruff: noqa: ASYNC210
319
+ tokens = requests.get(f"{TB_LOCAL_ADDRESS}/tokens").json()
320
+ except Exception:
321
+ raise CLILocalException(
322
+ FeedbackManager.error(message="Tinybird local is not running. Please run `tb local start` first.")
323
+ )
324
+ try:
325
+ user_token = tokens["user_token"]
326
+ user_client.token = user_token
327
+ asyncio.run(
328
+ user_client.delete_workspace(get_test_workspace_name(path), hard_delete_confirmation="yes", version="v1")
329
+ )
330
+ except Exception:
331
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev148
3
+ Version: 0.0.1.dev152
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -3,7 +3,7 @@ tinybird/context.py,sha256=FfqYfrGX_I7PKGTQo93utaKPDNVYWelg4Hsp3evX5wM,1291
3
3
  tinybird/datatypes.py,sha256=r4WCvspmrXTJHiPjjyOTiZyZl31FO3Ynkwq4LQsYm6E,11059
4
4
  tinybird/feedback_manager.py,sha256=a_ZhFX2zcB7vRknIcmHKMdQbb0c7TqlTBQ_5hPuWh88,69267
5
5
  tinybird/git_settings.py,sha256=Sw_8rGmribEFJ4Z_6idrVytxpFYk7ez8ei0qHULzs3E,3934
6
- tinybird/prompts.py,sha256=6J-dE0gOSOaIwdEwJUhEQS1Ztllz_qZnpnrv-tDoJWA,35511
6
+ tinybird/prompts.py,sha256=yLXzHB7_P0zjriIvI9UsfnIRwmcwLSEA1tcJMzYJqjw,36456
7
7
  tinybird/sql.py,sha256=C_B81wwv3BsqyXGhF5oTk9DcTUkrp7NwIFqSzd3Dmjc,47854
8
8
  tinybird/sql_template.py,sha256=mK0yeRFctbXTAu0VNMjIzoFBJoh9PoniAVgEatA5SG4,99832
9
9
  tinybird/sql_template_fmt.py,sha256=KUHdj5rYCYm_rKKdXYSJAE9vIyXUQLB0YSZnUXHeBlY,10196
@@ -12,7 +12,7 @@ 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=aYvg2C_WxYWsnqPdZB1ZFoIr8ZY-XjUXYyHKE9Ansj0,3890
14
14
  tinybird/ch_utils/engine.py,sha256=BZuPM7MFS7vaEKK5tOMR2bwSAgJudPrJt27uVEwZmTY,40512
15
- tinybird/tb/__cli__.py,sha256=ND-paC6OJCufE_OWkvnADMob08wWCEmSeglAIwgaUro,252
15
+ tinybird/tb/__cli__.py,sha256=GgehmhHmo8VV_VXA3EVctUEaD_qi0Fd6UJAECARm81o,252
16
16
  tinybird/tb/check_pypi.py,sha256=rW4QmDRbtgKdUUwJCnBkVjmTjZSZGN-XgZhx7vMkC0w,1009
17
17
  tinybird/tb/cli.py,sha256=uPV6pvi4aYVfaiGs0DQO-eoi1g9dHlrgvhutkXkdJko,1075
18
18
  tinybird/tb/client.py,sha256=aaPKq5C77e72kR7IMv9WrvnvNki8mKMOTi9EsCp0eUc,55962
@@ -20,15 +20,15 @@ tinybird/tb/config.py,sha256=jT9xndpeCY_g0HdB5qE2EquC0TFRRnkPnQFWZWd04jo,3998
20
20
  tinybird/tb/modules/auth.py,sha256=_OeYnmTH83lnqCgQEdS6K0bx1KBUeRmZk2M7JnRmWpk,9037
21
21
  tinybird/tb/modules/build.py,sha256=5SX59ssq0KaRrgzk21KmYUnB6xOdQ_O59n7rxFBQQ1Y,17393
22
22
  tinybird/tb/modules/cicd.py,sha256=Cn1DyE28w2maQgIJF4IjZU7J7ULPvMG11DjN2OH-cqE,7161
23
- tinybird/tb/modules/cli.py,sha256=uXGkK0lrbUMzLRtRuE77khwl0nmfnE6TQuuQFVy8Ug0,14075
23
+ tinybird/tb/modules/cli.py,sha256=AJooz6uiluC7eMO2ZlXluWL-NNLwTKb9kqkv3yqg6S4,14227
24
24
  tinybird/tb/modules/common.py,sha256=WzF_zRtAl3FKqbzK6yi_IjeeGIjQNu6SKnP-PX-o3Kk,85196
25
25
  tinybird/tb/modules/config.py,sha256=ziqW_t_mRVvWOd85VoB4vKyvgMkEfpXDf9H4v38p2xc,11422
26
26
  tinybird/tb/modules/connection.py,sha256=7oOR7x4PhBcm1ETFFCH2YJ_3oeGXjAbmx1cnZX9_L70,9014
27
27
  tinybird/tb/modules/copy.py,sha256=2Mm4FWKehOG7CoOhiF1m9UZJgJn0W1_cMolqju8ONYg,5805
28
- tinybird/tb/modules/create.py,sha256=lj7vugRfTo6jNb02sJ9wmV6Y0ASGEbxVAd1FrmaVr58,16943
28
+ tinybird/tb/modules/create.py,sha256=OHUvuHuvP0iecPPGI4eVOHOgR20qy7a_Sw7sbJKuG8g,17411
29
29
  tinybird/tb/modules/datasource.py,sha256=V314rkpdVxVMjsp5qcSCTqDlmp4Vu--qM07BoWh-aqs,17783
30
30
  tinybird/tb/modules/deployment.py,sha256=T0lwhAT8FwZZcRnA-JDMfPUrj5MOXcQ8blxYIK2igbc,24363
31
- tinybird/tb/modules/deprecations.py,sha256=kugrWKWLnUL0qHXghObmzEvNIPs5F5O-kTXtYHjaSrI,3632
31
+ tinybird/tb/modules/deprecations.py,sha256=nI_VPCB0Seyhlk2Foomoj3oUnx6GV_8Q5DMYVDi9L-o,3751
32
32
  tinybird/tb/modules/endpoint.py,sha256=XySDt3pk66vxOZ0egUfz4bY8bEk3BjOXkv-L0OIJ3sc,12083
33
33
  tinybird/tb/modules/exceptions.py,sha256=5jK91w1LPmtqIUfDpHe_Op5OxGz8-p1BPgtLREMIni0,5217
34
34
  tinybird/tb/modules/feedback_manager.py,sha256=NoflMdTcUs6JF2IUZS8TatItxWDNCfwaIJnKtbY8e3w,77447
@@ -37,20 +37,20 @@ tinybird/tb/modules/job.py,sha256=n4dSSBgnA8NqD7srGahf2xRj6wxkmX9Vl0J-QJ_a2w0,29
37
37
  tinybird/tb/modules/llm.py,sha256=KfsCYmKeW1VQz0iDZhGKCRkQv_Y3kTHh6JuxvofOguE,1076
38
38
  tinybird/tb/modules/llm_utils.py,sha256=nS9r4FAElJw8yXtmdYrx-rtI2zXR8qXfi1QqUDCfxvg,3469
39
39
  tinybird/tb/modules/local.py,sha256=kxdF8NXZYYwtUwlFHxHKq_PA9CpMrG-rXOg_i_hlvnY,12158
40
- tinybird/tb/modules/local_common.py,sha256=3nxGcjdxA5Uq0PakhbkiPDVzd66OUwPNW4gNqyYPCe8,3381
40
+ tinybird/tb/modules/local_common.py,sha256=jNbVG_nk5sNduPo3JDSNtop1H2X1Tdd5j4q3xXUR6W8,3965
41
41
  tinybird/tb/modules/login.py,sha256=fmXPSdvJnKPv03chptGuu3_Fm6LhP6kUsUKhrmT8rJc,8269
42
42
  tinybird/tb/modules/logout.py,sha256=ULooy1cDBD02-r7voZmhV7udA0ML5tVuflJyShrh56Y,1022
43
43
  tinybird/tb/modules/materialization.py,sha256=QJX5kCPhhm6IXBO1JsalVfbQdypCe_eOUDZ_WHJZWS8,5478
44
- tinybird/tb/modules/mock.py,sha256=tyhs6izqVGmDzMHdW5yHiprNPjeJDa2I-cLwrLRDC_U,4816
44
+ tinybird/tb/modules/mock.py,sha256=z_mYNVAu5aeq-5qGB0kSkliOX4j62fgUuLi1-ZcTamA,5304
45
45
  tinybird/tb/modules/open.py,sha256=OuctINN77oexpSjth9uoIZPCelKO4Li-yyVxeSnk1io,1371
46
46
  tinybird/tb/modules/pipe.py,sha256=AQKEDagO6e3psPVjJkS_MDbn8aK-apAiLp26k7jgAV0,2432
47
- tinybird/tb/modules/project.py,sha256=RdVOzJiuFPqkCzPhzGetbgcFeiMEn9IMviFCG58XKgI,3142
47
+ tinybird/tb/modules/project.py,sha256=vjw9RB3XcRWqj0izWbYSiTKqzP2n9QdIDXTu8QoRcK0,3502
48
48
  tinybird/tb/modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
49
49
  tinybird/tb/modules/secret.py,sha256=WsqzxxLh9W_jkuHL2JofMXdIJy0lT5WEI-7bQSIDgAc,2921
50
50
  tinybird/tb/modules/shell.py,sha256=Zd_4Ak_5tKVX-cw6B4ag36xZeEGHeh-jZpAsIXkoMoE,14116
51
51
  tinybird/tb/modules/table.py,sha256=4XrtjM-N0zfNtxVkbvLDQQazno1EPXnxTyo7llivfXk,11035
52
52
  tinybird/tb/modules/telemetry.py,sha256=IHHks8-jnrkyQIY_Y6ZmU8hdVHMGSS0pkSUOgCPcYY8,11346
53
- tinybird/tb/modules/test.py,sha256=WmPoZBSHBORiM66xp54KxfY3s0YwjFMN_ZTT8xObFO4,12377
53
+ tinybird/tb/modules/test.py,sha256=Yopg89cRwOQpgRzsb9nvu2Z-UR2as2vBjVa5PF3uiK0,13420
54
54
  tinybird/tb/modules/token.py,sha256=2fmKwu10_M0pqs6YmJVeILR9ZQB0ejRAET86agASbKM,13488
55
55
  tinybird/tb/modules/watch.py,sha256=_92co0BjTikQuy7MbHr4TDu9A75QdHsrAO8v7DlYLg4,8898
56
56
  tinybird/tb/modules/workspace.py,sha256=SlK08psp0tu5t_URHyIczm696buW6KD6FPs9Lg1aNRE,6614
@@ -59,7 +59,7 @@ tinybird/tb/modules/datafile/build.py,sha256=d_h3pRFDPFrDKGhpFx2iejY25GuB2k8yfNo
59
59
  tinybird/tb/modules/datafile/build_common.py,sha256=LU24kAQmxDJIyoIapDaYG-SU3P4FrMG9UBf8m9PgVSI,4565
60
60
  tinybird/tb/modules/datafile/build_datasource.py,sha256=nXEQ0qHdq2ai7jJTv8H2d7eeDPBYzLn8VY7zMtOYb8M,17382
61
61
  tinybird/tb/modules/datafile/build_pipe.py,sha256=6Cwjf3BKEF3-oQ9PipsQfK-Z43nSwtA4qJAUoysI7Uc,11385
62
- tinybird/tb/modules/datafile/common.py,sha256=NIqXpsj7u8uaeSKcUZM-85Kxg35pd08DVAdbVuHmvTU,89400
62
+ tinybird/tb/modules/datafile/common.py,sha256=1xM8R2NVmk8uQpU910Iqp_YGM1SHYoXaLZgjBYIJl54,89771
63
63
  tinybird/tb/modules/datafile/diff.py,sha256=MTmj53RYjER4neLgWVjabn-FKVFgh8h8uYiBo55lFQg,6757
64
64
  tinybird/tb/modules/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1wnI,556
65
65
  tinybird/tb/modules/datafile/fixture.py,sha256=DrRWivcvo_1rn7LlVUnHcXccdgx9yVj63mzBkUwCzk8,1420
@@ -79,8 +79,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
79
79
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
80
80
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
81
81
  tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
82
- tinybird-0.0.1.dev148.dist-info/METADATA,sha256=a78Hx_MhAQknWEIA9ss3ykmRIw40G6o3cXiu2bGbXSE,1612
83
- tinybird-0.0.1.dev148.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
84
- tinybird-0.0.1.dev148.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
85
- tinybird-0.0.1.dev148.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
86
- tinybird-0.0.1.dev148.dist-info/RECORD,,
82
+ tinybird-0.0.1.dev152.dist-info/METADATA,sha256=vHFPvsfjueEX_1WXokHN42243Qf31_7kRfUZqrwS2IA,1612
83
+ tinybird-0.0.1.dev152.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
84
+ tinybird-0.0.1.dev152.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
85
+ tinybird-0.0.1.dev152.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
86
+ tinybird-0.0.1.dev152.dist-info/RECORD,,