tinybird 4.5.2.dev0__py3-none-any.whl → 4.5.3.dev0__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.
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__ = '4.5.2.dev0'
8
- __revision__ = '4cf88c3'
7
+ __version__ = '4.5.3.dev0'
8
+ __revision__ = 'afc5f42'
@@ -75,6 +75,7 @@ def build(ctx: click.Context, watch: bool, with_connections: bool) -> None:
75
75
  tb_client: TinyB = ctx.ensure_object(dict)["client"]
76
76
  config: Dict[str, Any] = ctx.ensure_object(dict)["config"]
77
77
  is_branch = bool(ctx.ensure_object(dict)["branch"])
78
+ use_deployment_api = obj["env"] == "cloud" and is_branch
78
79
 
79
80
  # TODO: Explain that you can use custom branches too once they are open for everyone
80
81
  if obj["env"] == "cloud" and not is_branch:
@@ -100,6 +101,7 @@ def build(ctx: click.Context, watch: bool, with_connections: bool) -> None:
100
101
  config=config,
101
102
  is_branch=is_branch,
102
103
  with_connections=with_connections,
104
+ use_deployment_api=use_deployment_api,
103
105
  )
104
106
  if watch:
105
107
  run_watch(
@@ -113,6 +115,7 @@ def build(ctx: click.Context, watch: bool, with_connections: bool) -> None:
113
115
  config=config,
114
116
  is_branch=is_branch,
115
117
  with_connections=with_connections,
118
+ use_deployment_api=use_deployment_api,
116
119
  ),
117
120
  )
118
121
 
@@ -128,6 +131,7 @@ def dev(ctx: click.Context, with_connections: Optional[bool]) -> None:
128
131
  obj: Dict[str, Any] = ctx.ensure_object(dict)
129
132
  branch: Optional[str] = ctx.ensure_object(dict)["branch"]
130
133
  is_branch = bool(branch)
134
+ use_deployment_api = obj["env"] == "cloud" and is_branch
131
135
 
132
136
  # Default with_connections to True for branches, False otherwise
133
137
  if with_connections is None:
@@ -149,6 +153,7 @@ def dev(ctx: click.Context, with_connections: Optional[bool]) -> None:
149
153
  config=config,
150
154
  is_branch=is_branch,
151
155
  with_connections=with_connections,
156
+ use_deployment_api=use_deployment_api,
152
157
  )
153
158
  run_watch(
154
159
  project=project,
@@ -160,6 +165,7 @@ def dev(ctx: click.Context, with_connections: Optional[bool]) -> None:
160
165
  config=config,
161
166
  is_branch=is_branch,
162
167
  with_connections=with_connections,
168
+ use_deployment_api=use_deployment_api,
163
169
  ),
164
170
  )
165
171
 
@@ -14,6 +14,7 @@ from tinybird.datafile.parse_datasource import parse_datasource
14
14
  from tinybird.tb.client import TinyB
15
15
  from tinybird.tb.modules.common import push_data, sys_exit
16
16
  from tinybird.tb.modules.datafile.fixture import FixtureExtension, get_fixture_dir, persist_fixture
17
+ from tinybird.tb.modules.deployment_common import api_fetch
17
18
  from tinybird.tb.modules.feedback_manager import FeedbackManager
18
19
  from tinybird.tb.modules.local_common import get_local_tokens
19
20
  from tinybird.tb.modules.project import Project
@@ -33,6 +34,7 @@ def process(
33
34
  project_with_vendors: Optional[Project] = None,
34
35
  is_branch: bool = False,
35
36
  with_connections: bool = False,
37
+ use_deployment_api: bool = False,
36
38
  ) -> Optional[str]:
37
39
  time_start = time.time()
38
40
 
@@ -65,6 +67,7 @@ def process(
65
67
  load_fixtures,
66
68
  project_with_vendors=project_with_vendors,
67
69
  with_connections=with_connections,
70
+ use_deployment_api=use_deployment_api,
68
71
  )
69
72
 
70
73
  except click.ClickException as e:
@@ -196,13 +199,8 @@ def build_project(
196
199
  load_fixtures: bool = True,
197
200
  project_with_vendors: Optional[Project] = None,
198
201
  with_connections: bool = False,
202
+ use_deployment_api: bool = False,
199
203
  ) -> Optional[bool]:
200
- MULTIPART_BOUNDARY_DATA_PROJECT = "data_project://"
201
- DATAFILE_TYPE_TO_CONTENT_TYPE = {
202
- ".datasource": "text/plain",
203
- ".pipe": "text/plain",
204
- ".connection": "text/plain",
205
- }
206
204
  build_url = "/v1/build"
207
205
  if with_connections:
208
206
  build_url = f"{build_url}?with_connections=true"
@@ -213,28 +211,20 @@ def build_project(
213
211
  error: Optional[str] = None
214
212
 
215
213
  try:
216
- files = [
217
- ("context://", ("cli-version", "1.0.0", "text/plain")),
218
- ]
219
- project_path = project.path
220
- project_files = project.get_project_files()
214
+ if use_deployment_api:
215
+ return build_project_with_deploy_api(
216
+ project=project,
217
+ tb_client=tb_client,
218
+ silent=silent,
219
+ load_fixtures=load_fixtures,
220
+ project_with_vendors=project_with_vendors,
221
+ )
222
+
223
+ files, project_files = get_build_request_files(project, project_with_vendors)
221
224
 
222
225
  if not project_files:
223
226
  return False
224
227
 
225
- for file_path in project_files:
226
- relative_path = Path(file_path).relative_to(project_path).as_posix()
227
- with open(file_path, "rb") as fd:
228
- content_type = DATAFILE_TYPE_TO_CONTENT_TYPE.get(Path(file_path).suffix, "application/unknown")
229
- content = fd.read().decode("utf-8")
230
- if project_with_vendors:
231
- # Replace 'SHARED_WITH' and everything that comes after, including new lines, with 'SHARED_WITH Tinybird_Local_Test_'
232
- content = replace_shared_with(
233
- content,
234
- [project_with_vendors.workspace_name],
235
- )
236
-
237
- files.append((MULTIPART_BOUNDARY_DATA_PROJECT, (relative_path, content, content_type)))
238
228
  HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
239
229
  params = {"from": request_from} if request_from else None
240
230
  r = requests.post(TINYBIRD_API_URL, files=files, headers=HEADERS, params=params)
@@ -249,90 +239,236 @@ def build_project(
249
239
 
250
240
  build_result = result.get("result")
251
241
  if build_result == "success":
252
- build = result.get("build")
253
- new_datasources = build.get("new_datasource_names", [])
254
- new_pipes = build.get("new_pipe_names", [])
255
- new_connections = build.get("new_data_connector_names", [])
256
- changed_datasources = build.get("changed_datasource_names", [])
257
- changed_pipes = build.get("changed_pipe_names", [])
258
- changed_connections = build.get("changed_data_connector_names", [])
259
- deleted_datasources = build.get("deleted_datasource_names", [])
260
- deleted_pipes = build.get("deleted_pipe_names", [])
261
- deleted_connections = build.get("deleted_data_connector_names", [])
262
-
263
- no_changes = (
264
- not new_datasources
265
- and not changed_datasources
266
- and not new_pipes
267
- and not changed_pipes
268
- and not new_connections
269
- and not changed_connections
270
- and not deleted_datasources
271
- and not deleted_pipes
272
- and not deleted_connections
273
- )
274
- if no_changes:
242
+ build = result.get("build") or {}
243
+ changes = get_build_changes(build)
244
+ if not has_build_changes(changes):
275
245
  return False
276
- if not silent:
277
- echo_changes(project, new_datasources, ".datasource", "created")
278
- echo_changes(project, changed_datasources, ".datasource", "changed")
279
- echo_changes(project, deleted_datasources, ".datasource", "deleted")
280
- echo_changes(project, new_pipes, ".pipe", "created")
281
- echo_changes(project, changed_pipes, ".pipe", "changed")
282
- echo_changes(project, deleted_pipes, ".pipe", "deleted")
283
- echo_changes(project, new_connections, ".connection", "created")
284
- echo_changes(project, changed_connections, ".connection", "changed")
285
- echo_changes(project, deleted_connections, ".connection", "deleted")
246
+ echo_build_changes(project, changes, silent)
286
247
  if load_fixtures:
287
- try:
288
- for filename in project_files:
289
- if filename.endswith(".datasource"):
290
- ds_path = Path(filename)
291
- ds_name = ds_path.stem
292
- fixture_folder = get_fixture_dir(project.folder)
293
- fixture_extensions = [FixtureExtension.NDJSON, FixtureExtension.CSV]
294
- fixture_path = next(
295
- (
296
- fixture_folder / f"{ds_name}{ext}"
297
- for ext in fixture_extensions
298
- if (fixture_folder / f"{ds_name}{ext}").exists()
299
- ),
300
- None,
301
- )
302
- if not fixture_path:
303
- sql_path = fixture_folder / f"{ds_name}.sql"
304
- if sql_path.exists():
305
- fixture_path = rebuild_fixture_sql(project, tb_client, str(sql_path))
306
-
307
- if fixture_path:
308
- append_fixture(tb_client, ds_name, str(fixture_path))
309
-
310
- except Exception as e:
311
- click.echo(FeedbackManager.error_exception(error=f"Error appending fixtures for '{ds_name}': {e}"))
312
-
313
- feedback = build.get("feedback", [])
314
- for f in feedback:
315
- click.echo(
316
- FeedbackManager.warning(message=f"△ {f.get('level')}: {f.get('resource')}: {f.get('message')}")
317
- )
248
+ append_project_fixtures(project, tb_client, project_files)
249
+ echo_build_feedback(build.get("feedback", []))
250
+ return True
318
251
  elif build_result == "failed":
319
- build_errors = result.get("errors")
320
- full_error_msg = ""
321
- for build_error in build_errors:
322
- filename_bit = build_error.get("filename", build_error.get("resource", ""))
323
- error_bit = build_error.get("error") or build_error.get("message") or ""
324
- error_msg = ((filename_bit + "\n") if filename_bit else "") + error_bit
325
- full_error_msg += error_msg + "\n\n"
326
- error = full_error_msg.strip("\n") or "Unknown build error"
252
+ error = format_build_errors(result.get("errors", []))
327
253
  else:
328
254
  error = f"Unknown build result. Error: {result.get('error')}"
255
+ except click.ClickException:
256
+ raise
329
257
  except Exception as e:
330
258
  error = str(e)
331
259
 
332
260
  if error:
333
261
  raise click.ClickException(error)
334
262
 
335
- return build_result
263
+ return False
264
+
265
+
266
+ def build_project_with_deploy_api(
267
+ project: Project,
268
+ tb_client: TinyB,
269
+ silent: bool = False,
270
+ load_fixtures: bool = True,
271
+ project_with_vendors: Optional[Project] = None,
272
+ ) -> Optional[bool]:
273
+ deploy_url = urljoin(tb_client.host, "/v1/deploy")
274
+ logging.debug(deploy_url)
275
+ request_from = getattr(tb_client, "request_from", None)
276
+ files, project_files = get_build_request_files(project, project_with_vendors)
277
+
278
+ if not project_files:
279
+ return False
280
+
281
+ headers = {"Authorization": f"Bearer {tb_client.token}"}
282
+ params: dict[str, str] = {"auto_promote": "true"}
283
+ if request_from:
284
+ params["from"] = request_from
285
+
286
+ response = requests.post(deploy_url, files=files, headers=headers, params=params)
287
+ try:
288
+ result = response.json()
289
+ except Exception as e:
290
+ logging.debug(e, exc_info=True)
291
+ click.echo(FeedbackManager.error(message="Couldn't parse response from server"))
292
+ sys_exit("build_error", str(e))
293
+
294
+ logging.debug(json.dumps(result, indent=2))
295
+
296
+ build_result = result.get("result")
297
+ deployment = result.get("deployment") or {}
298
+ if build_result == "no_changes":
299
+ return False
300
+ if build_result != "success":
301
+ deployment_errors = deployment.get("errors", []) if deployment else result.get("errors", [])
302
+ raise click.ClickException(result.get("error") or format_build_errors(deployment_errors))
303
+ if not deployment:
304
+ raise click.ClickException("Couldn't parse deployment response from server")
305
+
306
+ deployment = wait_for_build_deployment_to_be_live(
307
+ tb_client=tb_client,
308
+ headers=headers,
309
+ deployment=deployment,
310
+ request_from=request_from,
311
+ silent=silent,
312
+ )
313
+ changes = get_build_changes(deployment)
314
+ if not has_build_changes(changes):
315
+ return False
316
+
317
+ echo_build_changes(project, changes, silent)
318
+ if load_fixtures:
319
+ append_project_fixtures(project, tb_client, project_files)
320
+ echo_build_feedback(deployment.get("feedback", []))
321
+ return True
322
+
323
+
324
+ def get_build_request_files(
325
+ project: Project,
326
+ project_with_vendors: Optional[Project] = None,
327
+ ) -> tuple[list[tuple[str, tuple[str, str, str]]], list[str]]:
328
+ multipart_boundary_data_project = "data_project://"
329
+ datafile_type_to_content_type = {
330
+ ".datasource": "text/plain",
331
+ ".pipe": "text/plain",
332
+ ".connection": "text/plain",
333
+ }
334
+ files: list[tuple[str, tuple[str, str, str]]] = [
335
+ ("context://", ("cli-version", "1.0.0", "text/plain")),
336
+ ]
337
+ project_path = project.path
338
+ project_files = project.get_project_files()
339
+
340
+ for file_path in project_files:
341
+ relative_path = Path(file_path).relative_to(project_path).as_posix()
342
+ with open(file_path, "rb") as fd:
343
+ content_type = datafile_type_to_content_type.get(Path(file_path).suffix, "application/unknown")
344
+ content = fd.read().decode("utf-8")
345
+ if project_with_vendors:
346
+ # Replace SHARED_WITH targets when building vendored workspaces against the main project.
347
+ content = replace_shared_with(content, [project_with_vendors.workspace_name])
348
+
349
+ files.append((multipart_boundary_data_project, (relative_path, content, content_type)))
350
+
351
+ return files, project_files
352
+
353
+
354
+ def wait_for_build_deployment_to_be_live(
355
+ tb_client: TinyB,
356
+ headers: dict[str, str],
357
+ deployment: dict[str, Any],
358
+ request_from: Optional[str],
359
+ silent: bool,
360
+ ) -> dict[str, Any]:
361
+ if not silent:
362
+ click.echo(FeedbackManager.highlight(message="» Waiting for deployment to be ready..."))
363
+
364
+ poll_interval = 5
365
+ times_seen_failed = 0
366
+ while True:
367
+ url = f"{tb_client.host}/v1/deployments/{deployment.get('id')}"
368
+ result = api_fetch(url, headers, request_from=request_from)
369
+ deployment = result.get("deployment", {})
370
+ if not deployment:
371
+ raise click.ClickException("Error parsing deployment from response")
372
+
373
+ status = deployment.get("status")
374
+ if status == "failed":
375
+ times_seen_failed += 1
376
+ if times_seen_failed > 60:
377
+ raise click.ClickException("Deployment failed and wasn't deleted automatically")
378
+ time.sleep(poll_interval)
379
+ continue
380
+
381
+ if status in ("deleting", "deleted"):
382
+ errors = deployment.get("errors", [])
383
+ raise click.ClickException(f"Deployment deleted after failure. Errors: {errors}")
384
+
385
+ if deployment.get("live"):
386
+ return deployment
387
+
388
+ time.sleep(poll_interval)
389
+
390
+
391
+ def get_build_changes(result: dict[str, Any]) -> dict[str, list[str]]:
392
+ return {
393
+ "new_datasources": result.get("new_datasource_names", []),
394
+ "changed_datasources": result.get("changed_datasource_names", []),
395
+ "deleted_datasources": result.get("deleted_datasource_names", []),
396
+ "new_pipes": result.get("new_pipe_names", []),
397
+ "changed_pipes": result.get("changed_pipe_names", []),
398
+ "deleted_pipes": result.get("deleted_pipe_names", []),
399
+ "new_connections": result.get("new_data_connector_names", []),
400
+ "changed_connections": result.get("changed_data_connector_names", []),
401
+ "deleted_connections": result.get("deleted_data_connector_names", []),
402
+ }
403
+
404
+
405
+ def has_build_changes(changes: dict[str, list[str]]) -> bool:
406
+ return any(changes.values())
407
+
408
+
409
+ def echo_build_changes(project: Project, changes: dict[str, list[str]], silent: bool) -> None:
410
+ if silent:
411
+ return
412
+
413
+ echo_changes(project, changes["new_datasources"], ".datasource", "created")
414
+ echo_changes(project, changes["changed_datasources"], ".datasource", "changed")
415
+ echo_changes(project, changes["deleted_datasources"], ".datasource", "deleted")
416
+ echo_changes(project, changes["new_pipes"], ".pipe", "created")
417
+ echo_changes(project, changes["changed_pipes"], ".pipe", "changed")
418
+ echo_changes(project, changes["deleted_pipes"], ".pipe", "deleted")
419
+ echo_changes(project, changes["new_connections"], ".connection", "created")
420
+ echo_changes(project, changes["changed_connections"], ".connection", "changed")
421
+ echo_changes(project, changes["deleted_connections"], ".connection", "deleted")
422
+
423
+
424
+ def append_project_fixtures(
425
+ project: Project,
426
+ tb_client: TinyB,
427
+ project_files: list[str],
428
+ ) -> None:
429
+ ds_name = ""
430
+ try:
431
+ for filename in project_files:
432
+ if not filename.endswith(".datasource"):
433
+ continue
434
+
435
+ ds_name = Path(filename).stem
436
+ fixture_folder = get_fixture_dir(project.folder)
437
+ fixture_extensions = [FixtureExtension.NDJSON, FixtureExtension.CSV]
438
+ fixture_path = next(
439
+ (
440
+ fixture_folder / f"{ds_name}{ext}"
441
+ for ext in fixture_extensions
442
+ if (fixture_folder / f"{ds_name}{ext}").exists()
443
+ ),
444
+ None,
445
+ )
446
+ if not fixture_path:
447
+ sql_path = fixture_folder / f"{ds_name}.sql"
448
+ if sql_path.exists():
449
+ fixture_path = rebuild_fixture_sql(project, tb_client, str(sql_path))
450
+
451
+ if fixture_path:
452
+ append_fixture(tb_client, ds_name, str(fixture_path))
453
+ except Exception as e:
454
+ click.echo(FeedbackManager.error_exception(error=f"Error appending fixtures for '{ds_name}': {e}"))
455
+
456
+
457
+ def echo_build_feedback(feedback: list[dict[str, Any]]) -> None:
458
+ for item in feedback:
459
+ click.echo(
460
+ FeedbackManager.warning(message=f"△ {item.get('level')}: {item.get('resource')}: {item.get('message')}")
461
+ )
462
+
463
+
464
+ def format_build_errors(build_errors: list[dict[str, Any]]) -> str:
465
+ full_error_msg = ""
466
+ for build_error in build_errors:
467
+ filename_bit = build_error.get("filename", build_error.get("resource", ""))
468
+ error_bit = build_error.get("error") or build_error.get("message") or ""
469
+ error_msg = ((filename_bit + "\n") if filename_bit else "") + error_bit
470
+ full_error_msg += error_msg + "\n\n"
471
+ return full_error_msg.strip("\n") or "Unknown build error"
336
472
 
337
473
 
338
474
  def echo_changes(project: Project, changes: list[str], extension: str, status: str):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 4.5.2.dev0
3
+ Version: 4.5.3.dev0
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -52,6 +52,11 @@ The Tinybird command-line tool allows you to use all the Tinybird functionality
52
52
  Changelog
53
53
  ----------
54
54
 
55
+ 4.5.2
56
+ ***********
57
+
58
+ - `Fixed` `tb build` on branches now runs as an asynchronous job, avoiding timeouts on long-running executions.
59
+
55
60
  4.5.1
56
61
  *******
57
62
 
@@ -17,14 +17,14 @@ tinybird/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1w
17
17
  tinybird/datafile/parse_connection.py,sha256=GxmGp_XnWbDZPDbh_PBxitlIMqZRYfDwxMBw-JQBp1g,1890
18
18
  tinybird/datafile/parse_datasource.py,sha256=yd58HrUF4yNJXLn6OsvKGpZJpvrcjLGAeJG1lgBe_zk,1891
19
19
  tinybird/datafile/parse_pipe.py,sha256=-9bbgVuiWRyDYydrLVflDBt8GstZotMy6dklsrc6MUY,3859
20
- tinybird/tb/__cli__.py,sha256=7WU6bivcBQ_tYlueRvNPz-IFZshSMj5WJx3OmMpSBLU,245
20
+ tinybird/tb/__cli__.py,sha256=fEPZtz5yQ0OXcYpjwKowucqLmPf8le00OegJ776lpAQ,245
21
21
  tinybird/tb/check_pypi.py,sha256=Gp0HkHHDFMSDL6nxKlOY51z7z1Uv-2LRexNTZSHHGmM,552
22
22
  tinybird/tb/cli.py,sha256=IjiGfNIpxSxi1odK1kMj9s8lEhx3sAUgGA263XdmyR0,1119
23
23
  tinybird/tb/client.py,sha256=RM8VQwPZFonfvejuTf3oT09XbJmq7pD5VLzFt4zoBGM,53910
24
24
  tinybird/tb/config.py,sha256=CGCfBbCMlmVcBHQ0IMGc2IE4O2-1tZEPyD564JZoTbw,5659
25
25
  tinybird/tb/modules/branch.py,sha256=Ik6KPkWcraqnBCxfZpQlMLadH_OSqGVSGdNE0eMUHp0,9318
26
- tinybird/tb/modules/build.py,sha256=Sj2UobSvEj29byeN6LwFDY9fyFQotd6SE8vrW3XPAqs,10098
27
- tinybird/tb/modules/build_common.py,sha256=Ok1HIlDpVTCKMu2fYOdhgmrAEb0GdAW307-A1_UzzVg,19717
26
+ tinybird/tb/modules/build.py,sha256=bGFoFppR_UbGUMDWFGDzfJ4nT3CGFwzCzl2o4OpwR2o,10420
27
+ tinybird/tb/modules/build_common.py,sha256=KxAE_HCq4QupIghW4n6IVokznwgA3Lv-KZ_mE9YAUPk,23709
28
28
  tinybird/tb/modules/cicd.py,sha256=xGa1zop2Io0Y3YcvOea7IAboBSM_qm2zZdeBvl93KIs,8235
29
29
  tinybird/tb/modules/cli.py,sha256=WVk7zTQS5a2ROGXALQSM57Gpy8QCKy3fJ5tDtFfT1Ic,40701
30
30
  tinybird/tb/modules/common.py,sha256=j7fg_8rIQFPIzzDfs9Xuse5jU-QaomuzY-6aqXb9izo,91937
@@ -97,8 +97,8 @@ tinybird/tb_cli_modules/config.py,sha256=0kFDmsDcjKon32rgFGMHHKSbv4j5dOrXtVOlyuA
97
97
  tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
98
98
  tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
99
99
  tinybird/tb_cli_modules/telemetry.py,sha256=W098H6jmS4kpE7hN3tadaREBTf7oMocel-lkKWN0pU8,10466
100
- tinybird-4.5.2.dev0.dist-info/METADATA,sha256=ObQb41Q6V0SnnAeZYrCoRvJSITyJ1yhT_yLF_mSwqlI,9880
101
- tinybird-4.5.2.dev0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
102
- tinybird-4.5.2.dev0.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
103
- tinybird-4.5.2.dev0.dist-info/top_level.txt,sha256=ZIQJTPCzMqnfDzM_hEGZrJqDSEcKnIK_49T86DGWpyQ,78
104
- tinybird-4.5.2.dev0.dist-info/RECORD,,
100
+ tinybird-4.5.3.dev0.dist-info/METADATA,sha256=3fpTA7h3b5AZYOU74U4xT0CGMET3aG6Mx3_ajbBabEk,10012
101
+ tinybird-4.5.3.dev0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
102
+ tinybird-4.5.3.dev0.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
103
+ tinybird-4.5.3.dev0.dist-info/top_level.txt,sha256=ZIQJTPCzMqnfDzM_hEGZrJqDSEcKnIK_49T86DGWpyQ,78
104
+ tinybird-4.5.3.dev0.dist-info/RECORD,,