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 +2 -2
- tinybird/tb/modules/build.py +6 -0
- tinybird/tb/modules/build_common.py +233 -97
- {tinybird-4.5.2.dev0.dist-info → tinybird-4.5.3.dev0.dist-info}/METADATA +6 -1
- {tinybird-4.5.2.dev0.dist-info → tinybird-4.5.3.dev0.dist-info}/RECORD +8 -8
- {tinybird-4.5.2.dev0.dist-info → tinybird-4.5.3.dev0.dist-info}/WHEEL +0 -0
- {tinybird-4.5.2.dev0.dist-info → tinybird-4.5.3.dev0.dist-info}/entry_points.txt +0 -0
- {tinybird-4.5.2.dev0.dist-info → tinybird-4.5.3.dev0.dist-info}/top_level.txt +0 -0
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.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '4.5.3.dev0'
|
|
8
|
+
__revision__ = 'afc5f42'
|
tinybird/tb/modules/build.py
CHANGED
|
@@ -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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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=
|
|
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=
|
|
27
|
-
tinybird/tb/modules/build_common.py,sha256=
|
|
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.
|
|
101
|
-
tinybird-4.5.
|
|
102
|
-
tinybird-4.5.
|
|
103
|
-
tinybird-4.5.
|
|
104
|
-
tinybird-4.5.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|