tabsdata-cli 2.0.0__cp312-abi3-win_amd64.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.
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/__init__.py +5 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/_registry.py +78 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/BANNER +5 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/LICENSE +3 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/README-PyPi.md +8 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/RELEASE-NOTES +1 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/THIRD-PARTY +3 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/VERSION +1 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/cli_utils.py +993 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/shell.py +390 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/__init__.py +3 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/auth_group.py +197 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/cli.py +508 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/collection_group.py +458 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/config_utils.py +93 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/connection_group.py +159 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/examples_group.py +206 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/execution_utils.py +429 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/fn_group.py +1109 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/fnrun_group.py +285 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/group_group.py +184 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/metrics_group.py +323 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/permission_group.py +259 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/plan_group.py +324 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/project_group.py +170 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/role_group.py +630 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/settings_group.py +344 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/table_group.py +600 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/trigger_group.py +338 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/trx_group.py +329 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/user_group.py +200 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/worker_group.py +185 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/template.py +781 -0
- tabsdata_cli-2.0.0.data/purelib/tabsdatak/py.typed +0 -0
- tabsdata_cli-2.0.0.dist-info/METADATA +39 -0
- tabsdata_cli-2.0.0.dist-info/RECORD +39 -0
- tabsdata_cli-2.0.0.dist-info/WHEEL +5 -0
- tabsdata_cli-2.0.0.dist-info/licenses/src/main/python/tabsdatak/cli/assets/manifest/LICENSE +3 -0
- tabsdata_cli-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Tabsdata Inc.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
"""Shared helpers for the execution-family command groups
|
|
6
|
+
(``plan``, ``trx``, ``run``, ``worker``)."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from itertools import islice
|
|
12
|
+
from time import sleep
|
|
13
|
+
from typing import TYPE_CHECKING, Callable, List
|
|
14
|
+
|
|
15
|
+
import rich_click as click
|
|
16
|
+
from rich.live import Live
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
|
|
19
|
+
from tabsdatak.cli.cli_utils import (
|
|
20
|
+
beautify_time,
|
|
21
|
+
collection_label,
|
|
22
|
+
is_valid_id,
|
|
23
|
+
logical_prompt,
|
|
24
|
+
make_console,
|
|
25
|
+
output_format,
|
|
26
|
+
render,
|
|
27
|
+
take,
|
|
28
|
+
)
|
|
29
|
+
from tabsdatak.sdk.status_utils.execution import user_execution_status_to_api
|
|
30
|
+
from tabsdatak.sdk.status_utils.function_run import user_function_run_status_to_api
|
|
31
|
+
from tabsdatak.sdk.status_utils.transaction import user_transaction_status_to_api
|
|
32
|
+
from tabsdatak.sdk.status_utils.worker import user_worker_status_to_api
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from tabsdatak.sdk.tabsdata_server import TabsdataServer
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def span_projects(server: TabsdataServer) -> None:
|
|
39
|
+
"""The execution-family commands are id-keyed and their listings always
|
|
40
|
+
span every visible project; a pinned or default project must not narrow
|
|
41
|
+
them (narrowing is done with an explicit ``project:eq:`` filter)."""
|
|
42
|
+
server.connection.project = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _append_at_filter(request_filter: List[str], at: str) -> None:
|
|
46
|
+
# noinspection PyProtectedMember
|
|
47
|
+
from tabsdatak.sdk.tabsdata_server import _top_and_convert_to_timestamp
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
at_timestamp = _top_and_convert_to_timestamp(at)
|
|
51
|
+
request_filter.append(f"triggered_on:le:{at_timestamp}")
|
|
52
|
+
except ValueError as e:
|
|
53
|
+
raise click.ClickException(f"Invalid date-time format for 'at' option: {e}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def obtain_list_exec_filters(
|
|
57
|
+
status: List[str] = None,
|
|
58
|
+
exe_name: str = None,
|
|
59
|
+
fn: str = None,
|
|
60
|
+
collection: str = None,
|
|
61
|
+
at: str = None,
|
|
62
|
+
project: str = None,
|
|
63
|
+
) -> List[str]:
|
|
64
|
+
"""
|
|
65
|
+
Helper function to obtain the filters for listing plans.
|
|
66
|
+
"""
|
|
67
|
+
request_filter = []
|
|
68
|
+
if status:
|
|
69
|
+
request_filter.extend(
|
|
70
|
+
f"status:eq:{user_execution_status_to_api(s)}" for s in status
|
|
71
|
+
)
|
|
72
|
+
if exe_name:
|
|
73
|
+
request_filter.append(f"name:lk:{exe_name}")
|
|
74
|
+
if fn:
|
|
75
|
+
request_filter.append(f"function:eq:{fn}")
|
|
76
|
+
if collection:
|
|
77
|
+
request_filter.append(f"collection:eq:{collection}")
|
|
78
|
+
if at:
|
|
79
|
+
_append_at_filter(request_filter, at)
|
|
80
|
+
if project:
|
|
81
|
+
request_filter.append(f"project:eq:{project}")
|
|
82
|
+
return request_filter
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def obtain_list_trx_filters(
|
|
86
|
+
status: List[str] = None,
|
|
87
|
+
exe_name: str = None,
|
|
88
|
+
execution_id: str = None,
|
|
89
|
+
collection: str = None,
|
|
90
|
+
at: str = None,
|
|
91
|
+
project: str = None,
|
|
92
|
+
) -> List[str]:
|
|
93
|
+
"""
|
|
94
|
+
Helper function to obtain the filters for listing transactions.
|
|
95
|
+
"""
|
|
96
|
+
request_filter = []
|
|
97
|
+
if status:
|
|
98
|
+
request_filter.extend(
|
|
99
|
+
f"status:eq:{user_transaction_status_to_api(s)}" for s in status
|
|
100
|
+
)
|
|
101
|
+
if exe_name:
|
|
102
|
+
request_filter.append(f"execution:lk:{exe_name}")
|
|
103
|
+
if execution_id:
|
|
104
|
+
request_filter.append(f"execution_id:eq:{execution_id}")
|
|
105
|
+
if collection:
|
|
106
|
+
request_filter.append(f"collection:eq:{collection}")
|
|
107
|
+
if at:
|
|
108
|
+
_append_at_filter(request_filter, at)
|
|
109
|
+
if project:
|
|
110
|
+
request_filter.append(f"project:eq:{project}")
|
|
111
|
+
return request_filter
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def obtain_list_fn_run_filters(
|
|
115
|
+
status: List[str] = None,
|
|
116
|
+
exe_name: str = None,
|
|
117
|
+
execution_id: str = None,
|
|
118
|
+
fn: str = None,
|
|
119
|
+
collection: str = None,
|
|
120
|
+
trx: str = None,
|
|
121
|
+
project: str = None,
|
|
122
|
+
) -> List[str]:
|
|
123
|
+
"""
|
|
124
|
+
Helper function to obtain the filters for listing function runs.
|
|
125
|
+
"""
|
|
126
|
+
request_filter = []
|
|
127
|
+
if status:
|
|
128
|
+
request_filter.extend(
|
|
129
|
+
f"status:eq:{user_function_run_status_to_api(s)}" for s in status
|
|
130
|
+
)
|
|
131
|
+
if exe_name:
|
|
132
|
+
request_filter.append(f"execution:lk:{exe_name}")
|
|
133
|
+
if execution_id:
|
|
134
|
+
request_filter.append(f"execution_id:eq:{execution_id}")
|
|
135
|
+
if fn:
|
|
136
|
+
request_filter.append(f"name:eq:{fn}")
|
|
137
|
+
if collection:
|
|
138
|
+
request_filter.append(f"collection:eq:{collection}")
|
|
139
|
+
if trx:
|
|
140
|
+
request_filter.append(f"transaction_id:eq:{trx}")
|
|
141
|
+
if project:
|
|
142
|
+
request_filter.append(f"project:eq:{project}")
|
|
143
|
+
return request_filter
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def obtain_list_worker_filters(
|
|
147
|
+
status: List[str] = None,
|
|
148
|
+
exe_name: str = None,
|
|
149
|
+
execution_id: str = None,
|
|
150
|
+
fn: str = None,
|
|
151
|
+
fnrun: str = None,
|
|
152
|
+
collection: str = None,
|
|
153
|
+
trx: str = None,
|
|
154
|
+
worker_type: str = None,
|
|
155
|
+
project: str = None,
|
|
156
|
+
) -> List[str]:
|
|
157
|
+
"""
|
|
158
|
+
Helper function to obtain the filters for listing workers.
|
|
159
|
+
"""
|
|
160
|
+
request_filter = []
|
|
161
|
+
if status:
|
|
162
|
+
request_filter.extend(
|
|
163
|
+
f"status:eq:{user_worker_status_to_api(s)}" for s in status
|
|
164
|
+
)
|
|
165
|
+
if exe_name:
|
|
166
|
+
request_filter.append(f"execution:lk:{exe_name}")
|
|
167
|
+
if execution_id:
|
|
168
|
+
request_filter.append(f"execution_id:eq:{execution_id}")
|
|
169
|
+
if fn:
|
|
170
|
+
request_filter.append(f"function:eq:{fn}")
|
|
171
|
+
if fnrun:
|
|
172
|
+
request_filter.append(f"logical_function_run_id:eq:{fnrun}")
|
|
173
|
+
if collection:
|
|
174
|
+
request_filter.append(f"collection:eq:{collection}")
|
|
175
|
+
if trx:
|
|
176
|
+
request_filter.append(f"transaction_id:eq:{trx}")
|
|
177
|
+
if worker_type:
|
|
178
|
+
request_filter.append(f"worker_type:eq:{worker_type}")
|
|
179
|
+
if project:
|
|
180
|
+
request_filter.append(f"project:eq:{project}")
|
|
181
|
+
return request_filter
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def obtain_execution_list(
|
|
185
|
+
ctx: click.Context, server: TabsdataServer, request_filter, last: bool
|
|
186
|
+
) -> tuple[list, bool]:
|
|
187
|
+
executions = server.list_executions_generator(
|
|
188
|
+
filter=request_filter,
|
|
189
|
+
order_by="triggered_on-",
|
|
190
|
+
)
|
|
191
|
+
# The listing is newest-first, so --last needs a single row, never a page.
|
|
192
|
+
if last:
|
|
193
|
+
return list(islice(executions, 1)), False
|
|
194
|
+
return take(ctx, executions)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def obtain_transaction_list(
|
|
198
|
+
ctx: click.Context, server: TabsdataServer, request_filter, last: bool
|
|
199
|
+
) -> tuple[list, bool]:
|
|
200
|
+
transactions = server.list_transactions_generator(
|
|
201
|
+
filter=request_filter, order_by="triggered_on-"
|
|
202
|
+
)
|
|
203
|
+
if last:
|
|
204
|
+
return list(islice(transactions, 1)), False
|
|
205
|
+
return take(ctx, transactions)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def echo_plan_status(server: TabsdataServer, plan: str) -> None:
|
|
209
|
+
execution = server.get_execution(plan)
|
|
210
|
+
click.echo(f"The current status of the plan '{plan}' is '{execution.status}'")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def echo_transaction_status(server: TabsdataServer, trx: str) -> None:
|
|
214
|
+
transaction = server.get_transaction(trx)
|
|
215
|
+
click.echo(
|
|
216
|
+
f"The current status of the transaction '{trx}' is '{transaction.status}'"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def echo_function_run_status(server: TabsdataServer, fnrun: str) -> None:
|
|
221
|
+
function_run = server.get_function_run(fnrun)
|
|
222
|
+
click.echo(
|
|
223
|
+
f"The current status of the function run '{fnrun}' is '{function_run.status}'"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def patch_label(item) -> str:
|
|
228
|
+
"""The patch a run/worker executed (a function-version id), or '-' for head
|
|
229
|
+
code. The patch id is surfaced onto the row as `function_version_patch_id`;
|
|
230
|
+
absent/None means unpatched (head)."""
|
|
231
|
+
return item.kwargs.get("function_version_patch_id") or "-"
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _truncate(text: str, width: int = 20) -> str:
|
|
235
|
+
text = text or ""
|
|
236
|
+
return text if len(text) <= width else text[:width] + "..."
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def plans_table(executions, title: str = "Plans") -> Table:
|
|
240
|
+
table = Table(title=title)
|
|
241
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
242
|
+
table.add_column("Name")
|
|
243
|
+
table.add_column("Project")
|
|
244
|
+
table.add_column("Trigger coll / fn", no_wrap=True)
|
|
245
|
+
table.add_column("Triggered by")
|
|
246
|
+
table.add_column("Start time", no_wrap=True)
|
|
247
|
+
table.add_column("End time", no_wrap=True)
|
|
248
|
+
table.add_column("Status", no_wrap=True)
|
|
249
|
+
for execution in executions:
|
|
250
|
+
table.add_row(
|
|
251
|
+
execution.id,
|
|
252
|
+
_truncate(execution.name),
|
|
253
|
+
execution.project or "-",
|
|
254
|
+
f"{collection_label(execution)} / {execution.function.name}",
|
|
255
|
+
execution.triggered_by,
|
|
256
|
+
beautify_time(execution.started_on_str),
|
|
257
|
+
beautify_time(execution.ended_on_str),
|
|
258
|
+
execution.status,
|
|
259
|
+
)
|
|
260
|
+
return table
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def transactions_table(transactions) -> Table:
|
|
264
|
+
table = Table(title="Transactions")
|
|
265
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
266
|
+
table.add_column("Execution plan ID", no_wrap=True)
|
|
267
|
+
table.add_column("Project")
|
|
268
|
+
table.add_column("Collection")
|
|
269
|
+
table.add_column("Triggered by")
|
|
270
|
+
table.add_column("Start time", no_wrap=True)
|
|
271
|
+
table.add_column("End time", no_wrap=True)
|
|
272
|
+
table.add_column("Status", no_wrap=True)
|
|
273
|
+
for transaction in transactions:
|
|
274
|
+
table.add_row(
|
|
275
|
+
transaction.id,
|
|
276
|
+
transaction.execution.id,
|
|
277
|
+
transaction.project or "-",
|
|
278
|
+
collection_label(transaction),
|
|
279
|
+
transaction.triggered_by,
|
|
280
|
+
beautify_time(transaction.started_on_str),
|
|
281
|
+
beautify_time(transaction.ended_on_str),
|
|
282
|
+
transaction.status,
|
|
283
|
+
)
|
|
284
|
+
return table
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def function_runs_table(function_runs, title: str = "Function runs") -> Table:
|
|
288
|
+
table = Table(title=title)
|
|
289
|
+
table.add_column("Function run ID", style="cyan", no_wrap=True)
|
|
290
|
+
table.add_column("Function")
|
|
291
|
+
table.add_column("Execution plan ID", no_wrap=True)
|
|
292
|
+
table.add_column("Transaction ID", no_wrap=True)
|
|
293
|
+
table.add_column("Project")
|
|
294
|
+
table.add_column("Collection")
|
|
295
|
+
table.add_column("Start time", no_wrap=True)
|
|
296
|
+
table.add_column("End time", no_wrap=True)
|
|
297
|
+
table.add_column("Patch")
|
|
298
|
+
table.add_column("Status")
|
|
299
|
+
for fn_run in function_runs:
|
|
300
|
+
table.add_row(
|
|
301
|
+
fn_run.id,
|
|
302
|
+
fn_run.function.name,
|
|
303
|
+
fn_run.execution.id,
|
|
304
|
+
fn_run.transaction.id,
|
|
305
|
+
fn_run.project or "-",
|
|
306
|
+
collection_label(fn_run),
|
|
307
|
+
beautify_time(fn_run.started_on_str),
|
|
308
|
+
beautify_time(fn_run.ended_on_str),
|
|
309
|
+
patch_label(fn_run),
|
|
310
|
+
fn_run.status,
|
|
311
|
+
)
|
|
312
|
+
return table
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def workers_table(workers, title: str = "Workers", indexed: bool = False) -> Table:
|
|
316
|
+
table = Table(title=title)
|
|
317
|
+
if indexed:
|
|
318
|
+
table.add_column("Index")
|
|
319
|
+
table.add_column("Worker ID", style="cyan", no_wrap=True)
|
|
320
|
+
table.add_column("Function run ID", no_wrap=True)
|
|
321
|
+
table.add_column("Function")
|
|
322
|
+
table.add_column("Execution plan ID", no_wrap=True)
|
|
323
|
+
table.add_column("Transaction ID", no_wrap=True)
|
|
324
|
+
table.add_column("Project")
|
|
325
|
+
table.add_column("Collection")
|
|
326
|
+
table.add_column("Start time", no_wrap=True)
|
|
327
|
+
table.add_column("End time", no_wrap=True)
|
|
328
|
+
table.add_column("Patch")
|
|
329
|
+
table.add_column("Status")
|
|
330
|
+
for position, worker in enumerate(workers, start=1):
|
|
331
|
+
row = [str(position)] if indexed else []
|
|
332
|
+
row.extend([
|
|
333
|
+
worker.id,
|
|
334
|
+
worker.kwargs.get("logical_function_run_id") or "-",
|
|
335
|
+
worker.function.name,
|
|
336
|
+
worker.kwargs.get("execution_id") or "-",
|
|
337
|
+
worker.kwargs.get("transaction_id") or "-",
|
|
338
|
+
worker.project or "-",
|
|
339
|
+
collection_label(worker),
|
|
340
|
+
beautify_time(worker.started_on_str),
|
|
341
|
+
beautify_time(worker.ended_on_str),
|
|
342
|
+
patch_label(worker),
|
|
343
|
+
worker.status,
|
|
344
|
+
])
|
|
345
|
+
table.add_row(*row)
|
|
346
|
+
return table
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def live_monitor(items, fetch: Callable, build_table: Callable, is_final: Callable):
|
|
350
|
+
"""Refresh a listing until every item reaches a final status; returns the
|
|
351
|
+
final listing."""
|
|
352
|
+
refresh_rate = 1 # seconds
|
|
353
|
+
with Live(
|
|
354
|
+
build_table(items), refresh_per_second=refresh_rate, console=make_console()
|
|
355
|
+
) as live:
|
|
356
|
+
while not all(is_final(item) for item in items):
|
|
357
|
+
items = fetch()
|
|
358
|
+
live.update(build_table(items))
|
|
359
|
+
sleep(1 / refresh_rate)
|
|
360
|
+
items = fetch()
|
|
361
|
+
live.update(build_table(items))
|
|
362
|
+
return items
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def pick_worker(
|
|
366
|
+
ctx: click.Context, server: TabsdataServer, request_filter: List[str], subject: str
|
|
367
|
+
) -> str:
|
|
368
|
+
"""Resolve a worker to show logs for: the only one directly, or one the
|
|
369
|
+
user picks from the rendered listing when several ran."""
|
|
370
|
+
workers = server.list_workers(filter=request_filter)
|
|
371
|
+
if not workers:
|
|
372
|
+
raise click.ClickException(f"{subject} has no workers yet — nothing has run.")
|
|
373
|
+
if len(workers) == 1:
|
|
374
|
+
return workers[0].id
|
|
375
|
+
if output_format(ctx) == "table":
|
|
376
|
+
click.echo()
|
|
377
|
+
make_console().print(workers_table(workers, indexed=True))
|
|
378
|
+
if output_format(ctx) != "table" or ctx.obj["no_prompt"]:
|
|
379
|
+
raise click.ClickException(
|
|
380
|
+
f"{subject} has {len(workers)} workers. Pick one and run "
|
|
381
|
+
"'tdk worker logs --worker <WORKER-ID>'."
|
|
382
|
+
)
|
|
383
|
+
choice = logical_prompt(ctx, "Index or ID of the worker to show logs for")
|
|
384
|
+
choice = (choice or "").strip()
|
|
385
|
+
if is_valid_id(choice):
|
|
386
|
+
return choice
|
|
387
|
+
try:
|
|
388
|
+
index = int(choice)
|
|
389
|
+
except ValueError:
|
|
390
|
+
raise click.ClickException(
|
|
391
|
+
f"'{choice}' is neither a worker ID nor an index in the list."
|
|
392
|
+
)
|
|
393
|
+
if not 1 <= index <= len(workers):
|
|
394
|
+
raise click.ClickException(f"Index {index} is out of range (1-{len(workers)}).")
|
|
395
|
+
return workers[index - 1].id
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def emit_worker_logs(
|
|
399
|
+
ctx: click.Context, server: TabsdataServer, worker_id: str, file: str
|
|
400
|
+
) -> None:
|
|
401
|
+
generated_logs = (
|
|
402
|
+
f"Worker {worker_id}" + os.linesep + server.get_worker_log(worker_id)
|
|
403
|
+
)
|
|
404
|
+
if file == "-":
|
|
405
|
+
click.echo(generated_logs, nl=False)
|
|
406
|
+
return
|
|
407
|
+
if output_format(ctx) != "table":
|
|
408
|
+
if file:
|
|
409
|
+
with open(file, "w") as f:
|
|
410
|
+
f.write(generated_logs)
|
|
411
|
+
render(ctx, data={"logs": generated_logs}, table=lambda: None)
|
|
412
|
+
return
|
|
413
|
+
if file:
|
|
414
|
+
click.echo(f"Saving logs to file '{file}'")
|
|
415
|
+
with open(file, "w") as f:
|
|
416
|
+
f.write(generated_logs)
|
|
417
|
+
click.echo("Logs saved successfully")
|
|
418
|
+
else:
|
|
419
|
+
click.echo(generated_logs)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def confirm_cancel(ctx: click.Context, confirm: str, subject: str) -> None:
|
|
423
|
+
confirm = confirm or logical_prompt(
|
|
424
|
+
ctx, f"Please type 'cancel' to confirm canceling {subject}"
|
|
425
|
+
)
|
|
426
|
+
if confirm != "cancel":
|
|
427
|
+
raise click.ClickException(
|
|
428
|
+
"Cancellation not confirmed. The confirmation word is 'cancel'."
|
|
429
|
+
)
|