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,324 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Tabsdata Inc.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, List
|
|
8
|
+
|
|
9
|
+
import rich_click as click
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from tabsdatak.cli._registry import cli_command
|
|
13
|
+
from tabsdatak.cli.cli_utils import (
|
|
14
|
+
beautify_time,
|
|
15
|
+
collection_label,
|
|
16
|
+
echo_more_results,
|
|
17
|
+
format_option,
|
|
18
|
+
hint_common_solutions,
|
|
19
|
+
make_console,
|
|
20
|
+
max_result_option,
|
|
21
|
+
output_format,
|
|
22
|
+
render,
|
|
23
|
+
show_hint,
|
|
24
|
+
verify_login_or_prompt,
|
|
25
|
+
)
|
|
26
|
+
from tabsdatak.cli.td.execution_utils import (
|
|
27
|
+
confirm_cancel,
|
|
28
|
+
emit_worker_logs,
|
|
29
|
+
function_runs_table,
|
|
30
|
+
live_monitor,
|
|
31
|
+
obtain_execution_list,
|
|
32
|
+
obtain_list_exec_filters,
|
|
33
|
+
pick_worker,
|
|
34
|
+
plans_table,
|
|
35
|
+
span_projects,
|
|
36
|
+
workers_table,
|
|
37
|
+
)
|
|
38
|
+
from tabsdatak.sdk.status_utils.execution import (
|
|
39
|
+
EXECUTION_FINAL_STATUSES,
|
|
40
|
+
EXECUTION_SUCCESSFUL_FINAL_STATUSES,
|
|
41
|
+
EXECUTION_VALID_USER_PROVIDED_STATUSES,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from tabsdatak.sdk.tabsdata_server import TabsdataServer
|
|
46
|
+
|
|
47
|
+
AT_OPTION_HELP = (
|
|
48
|
+
"If provided, only plans started before that time will be shown. Must be "
|
|
49
|
+
"either a valid timestamp in the form of a unix timestamp (milliseconds since "
|
|
50
|
+
"epoch without a dot, e.g. '1750074554472') or a valid date-time format. "
|
|
51
|
+
"The valid "
|
|
52
|
+
"formats are 'YYYY-MM-DD', 'YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:MM', "
|
|
53
|
+
"'YYYY-MM-DDTHH:MM:SS', and 'YYYY-MM-DDTHH:MM:SS.sss'. A 'Z' character can be "
|
|
54
|
+
"added at the end to indicate UTC time (e.g., '2023-10-01T12Z' or "
|
|
55
|
+
"'2023-10-01T12:00:00.000Z') or it can be omitted to indicate local "
|
|
56
|
+
"time (e.g., '2023-10-01T12' or '2023-10-01T12:00:00.000')."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@cli_command
|
|
61
|
+
@click.group()
|
|
62
|
+
def plan():
|
|
63
|
+
"""Plan (execution) management commands"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@plan.command(name="list")
|
|
67
|
+
@click.option("--proj", type=str, help="Name of the project to filter plans by.")
|
|
68
|
+
@click.option(
|
|
69
|
+
"--status",
|
|
70
|
+
type=str,
|
|
71
|
+
multiple=True,
|
|
72
|
+
help=(
|
|
73
|
+
"Filter plans by status. The possible statuses are "
|
|
74
|
+
f"'{EXECUTION_VALID_USER_PROVIDED_STATUSES}'. This field is "
|
|
75
|
+
"case-insensitive. "
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
@click.option("--fn", type=str, help="Name of the function to filter plans by.")
|
|
79
|
+
@click.option("--coll", type=str, help="Name of the collection to filter plans by.")
|
|
80
|
+
@click.option(
|
|
81
|
+
"--name",
|
|
82
|
+
"-n",
|
|
83
|
+
help=(
|
|
84
|
+
"A plan name wildcard to match for the list. "
|
|
85
|
+
"For example, 'my_plan*' will match all plans "
|
|
86
|
+
"with names starting with 'my_plan'."
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
@click.option(
|
|
90
|
+
"--last",
|
|
91
|
+
is_flag=True,
|
|
92
|
+
help="If set, only the last plan of the list will be shown.",
|
|
93
|
+
)
|
|
94
|
+
@click.option("--at", help=AT_OPTION_HELP, type=str)
|
|
95
|
+
@click.option(
|
|
96
|
+
"--monitor",
|
|
97
|
+
is_flag=True,
|
|
98
|
+
help=(
|
|
99
|
+
"If set, the command will monitor the listed plans until they all reach "
|
|
100
|
+
"a final state."
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
@format_option
|
|
104
|
+
@max_result_option
|
|
105
|
+
@click.pass_context
|
|
106
|
+
def list_plans(
|
|
107
|
+
ctx: click.Context,
|
|
108
|
+
proj: str,
|
|
109
|
+
status: List[str],
|
|
110
|
+
fn: str,
|
|
111
|
+
coll: str,
|
|
112
|
+
name: str,
|
|
113
|
+
last: bool,
|
|
114
|
+
at: str,
|
|
115
|
+
monitor: bool,
|
|
116
|
+
):
|
|
117
|
+
"""List plans across every project you can view."""
|
|
118
|
+
verify_login_or_prompt(ctx)
|
|
119
|
+
request_filter = obtain_list_exec_filters(
|
|
120
|
+
status=status,
|
|
121
|
+
exe_name=name,
|
|
122
|
+
fn=fn,
|
|
123
|
+
collection=coll,
|
|
124
|
+
at=at,
|
|
125
|
+
project=proj,
|
|
126
|
+
)
|
|
127
|
+
try:
|
|
128
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
129
|
+
span_projects(server)
|
|
130
|
+
list_of_executions, truncated = obtain_execution_list(
|
|
131
|
+
ctx, server, request_filter, last
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if output_format(ctx) != "table":
|
|
135
|
+
render(
|
|
136
|
+
ctx,
|
|
137
|
+
data=[e._data for e in list_of_executions],
|
|
138
|
+
table=lambda: None,
|
|
139
|
+
)
|
|
140
|
+
echo_more_results(ctx, truncated)
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
if monitor:
|
|
144
|
+
click.echo("Waiting for the plans to finish...")
|
|
145
|
+
list_of_executions = live_monitor(
|
|
146
|
+
list_of_executions,
|
|
147
|
+
lambda: obtain_execution_list(ctx, server, request_filter, last)[0],
|
|
148
|
+
plans_table,
|
|
149
|
+
lambda e: e.status in EXECUTION_FINAL_STATUSES,
|
|
150
|
+
)
|
|
151
|
+
click.echo("Plans finished.")
|
|
152
|
+
else:
|
|
153
|
+
click.echo()
|
|
154
|
+
console = make_console()
|
|
155
|
+
console.print(plans_table(list_of_executions))
|
|
156
|
+
echo_more_results(ctx, truncated)
|
|
157
|
+
click.echo()
|
|
158
|
+
if not all([
|
|
159
|
+
aux_exe.status in EXECUTION_SUCCESSFUL_FINAL_STATUSES
|
|
160
|
+
for aux_exe in list_of_executions
|
|
161
|
+
]):
|
|
162
|
+
show_hint(
|
|
163
|
+
ctx,
|
|
164
|
+
"If you want to explore why a plan failed, run "
|
|
165
|
+
"'tdk plan logs --plan <PLAN-ID>' to pick a worker and read its "
|
|
166
|
+
"logs, or 'tdk worker list --plan <PLAN-ID>' to see the workers.",
|
|
167
|
+
)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
hint_common_solutions(ctx, e)
|
|
170
|
+
raise click.ClickException(f"Failed to list plans: {e}")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@plan.command()
|
|
174
|
+
@click.option("--plan", required=True, help="ID of the plan to display.")
|
|
175
|
+
@format_option
|
|
176
|
+
@click.pass_context
|
|
177
|
+
def get(ctx: click.Context, plan: str):
|
|
178
|
+
"""
|
|
179
|
+
Display a plan: its details plus its function runs and workers.
|
|
180
|
+
"""
|
|
181
|
+
verify_login_or_prompt(ctx)
|
|
182
|
+
try:
|
|
183
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
184
|
+
span_projects(server)
|
|
185
|
+
execution = server.get_execution(plan)
|
|
186
|
+
function_runs = server.list_function_runs(filter=f"execution_id:eq:{plan}")
|
|
187
|
+
workers = server.list_workers(filter=f"execution_id:eq:{plan}")
|
|
188
|
+
|
|
189
|
+
if output_format(ctx) != "table":
|
|
190
|
+
render(
|
|
191
|
+
ctx,
|
|
192
|
+
data={
|
|
193
|
+
"plan": execution._data,
|
|
194
|
+
"function_runs": [r._data for r in function_runs],
|
|
195
|
+
"workers": [w._data for w in workers],
|
|
196
|
+
},
|
|
197
|
+
table=lambda: None,
|
|
198
|
+
)
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
table = Table(title="Plan")
|
|
202
|
+
table.add_column("Name")
|
|
203
|
+
table.add_column("Project")
|
|
204
|
+
table.add_column("Collection")
|
|
205
|
+
table.add_column("Function")
|
|
206
|
+
table.add_column("Triggered on")
|
|
207
|
+
table.add_column("Started on")
|
|
208
|
+
table.add_column("Ended on")
|
|
209
|
+
table.add_column("Status", no_wrap=True)
|
|
210
|
+
table.add_row(
|
|
211
|
+
execution.name,
|
|
212
|
+
execution.project or "-",
|
|
213
|
+
collection_label(execution),
|
|
214
|
+
execution.function.name,
|
|
215
|
+
beautify_time(execution.triggered_on_str),
|
|
216
|
+
beautify_time(execution.started_on_str),
|
|
217
|
+
beautify_time(execution.ended_on_str),
|
|
218
|
+
execution.status,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
click.echo()
|
|
222
|
+
console = make_console()
|
|
223
|
+
console.print(table)
|
|
224
|
+
console.print(function_runs_table(function_runs))
|
|
225
|
+
console.print(workers_table(workers))
|
|
226
|
+
click.echo()
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
hint_common_solutions(ctx, e)
|
|
230
|
+
raise click.ClickException(f"Failed to display plan: {e}")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@plan.command()
|
|
234
|
+
@click.option("--plan", required=True, help="ID of the plan to cancel.")
|
|
235
|
+
@click.option(
|
|
236
|
+
"--confirm",
|
|
237
|
+
help="Write 'cancel' to confirm cancellation. Will be prompted for it if "
|
|
238
|
+
"not provided.",
|
|
239
|
+
)
|
|
240
|
+
@click.pass_context
|
|
241
|
+
def cancel(ctx: click.Context, plan: str, confirm: str):
|
|
242
|
+
"""
|
|
243
|
+
Cancel a plan: every non-committed transaction in it is canceled.
|
|
244
|
+
Canceled runs cannot be recovered.
|
|
245
|
+
"""
|
|
246
|
+
verify_login_or_prompt(ctx)
|
|
247
|
+
click.echo(f"Canceling plan with ID '{plan}'")
|
|
248
|
+
click.echo("-" * 10)
|
|
249
|
+
confirm_cancel(ctx, confirm, f"plan '{plan}'")
|
|
250
|
+
try:
|
|
251
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
252
|
+
server.cancel_execution(plan)
|
|
253
|
+
click.echo("Plan canceled successfully")
|
|
254
|
+
except Exception as e:
|
|
255
|
+
hint_common_solutions(ctx, e)
|
|
256
|
+
raise click.ClickException(f"Failed to cancel plan: {e}")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@plan.command()
|
|
260
|
+
@click.option("--plan", required=True, help="ID of the plan to recover.")
|
|
261
|
+
@click.pass_context
|
|
262
|
+
def recover(ctx: click.Context, plan: str):
|
|
263
|
+
"""
|
|
264
|
+
Recover a plan: its failed or on-hold function runs are re-scheduled.
|
|
265
|
+
"""
|
|
266
|
+
verify_login_or_prompt(ctx)
|
|
267
|
+
click.echo(f"Recovering plan with ID '{plan}'")
|
|
268
|
+
click.echo("-" * 10)
|
|
269
|
+
try:
|
|
270
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
271
|
+
server.recover_execution(plan)
|
|
272
|
+
click.echo("Plan recovered successfully")
|
|
273
|
+
except Exception as e:
|
|
274
|
+
hint_common_solutions(ctx, e)
|
|
275
|
+
raise click.ClickException(f"Failed to recover plan: {e}")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@plan.command()
|
|
279
|
+
@click.option("--plan", required=True, help="ID of the plan to monitor.")
|
|
280
|
+
@click.pass_context
|
|
281
|
+
def monitor(ctx: click.Context, plan: str):
|
|
282
|
+
"""
|
|
283
|
+
Monitor the execution of a plan.
|
|
284
|
+
"""
|
|
285
|
+
verify_login_or_prompt(ctx)
|
|
286
|
+
try:
|
|
287
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
288
|
+
span_projects(server)
|
|
289
|
+
execution = server.get_execution(plan)
|
|
290
|
+
from tabsdatak.cli.td.fn_group import _monitor_execution_or_transaction
|
|
291
|
+
|
|
292
|
+
_monitor_execution_or_transaction(ctx, execution=execution)
|
|
293
|
+
except Exception as e:
|
|
294
|
+
hint_common_solutions(ctx, e)
|
|
295
|
+
raise click.ClickException(f"Failed to monitor plan: {e}")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@plan.command()
|
|
299
|
+
@click.option("--plan", required=True, help="ID of the plan whose logs to show.")
|
|
300
|
+
@click.option(
|
|
301
|
+
"--file",
|
|
302
|
+
help=(
|
|
303
|
+
"Path of the file where the logs will be saved, or '-' to write them to "
|
|
304
|
+
"stdout. Any folders provided before the file must already exist."
|
|
305
|
+
),
|
|
306
|
+
)
|
|
307
|
+
@format_option
|
|
308
|
+
@click.pass_context
|
|
309
|
+
def logs(ctx: click.Context, plan: str, file: str):
|
|
310
|
+
"""
|
|
311
|
+
Show logs of one of the plan's workers; when several ran, pick one from
|
|
312
|
+
the listing.
|
|
313
|
+
"""
|
|
314
|
+
verify_login_or_prompt(ctx)
|
|
315
|
+
try:
|
|
316
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
317
|
+
span_projects(server)
|
|
318
|
+
target = pick_worker(ctx, server, [f"execution_id:eq:{plan}"], f"Plan '{plan}'")
|
|
319
|
+
emit_worker_logs(ctx, server, target, file)
|
|
320
|
+
except click.exceptions.Exit:
|
|
321
|
+
raise
|
|
322
|
+
except Exception as e:
|
|
323
|
+
hint_common_solutions(ctx, e)
|
|
324
|
+
raise click.ClickException(f"Failed to show logs: {e}")
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Tabsdata Inc.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
import rich_click as click
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from tabsdatak.cli._registry import cli_command
|
|
13
|
+
from tabsdatak.cli.cli_utils import (
|
|
14
|
+
echo_more_results,
|
|
15
|
+
format_option,
|
|
16
|
+
hint_common_solutions,
|
|
17
|
+
logical_prompt,
|
|
18
|
+
make_console,
|
|
19
|
+
max_result_option,
|
|
20
|
+
render,
|
|
21
|
+
take,
|
|
22
|
+
verify_login_or_prompt,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from tabsdatak.sdk.tabsdata_server import TabsdataServer
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@cli_command
|
|
30
|
+
@click.group()
|
|
31
|
+
def project():
|
|
32
|
+
"""Project management commands"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@project.command()
|
|
36
|
+
@click.option("--name", "-n", help="Name of the project to create.")
|
|
37
|
+
@click.option("--description", help="Description of the project.")
|
|
38
|
+
@click.pass_context
|
|
39
|
+
def create(ctx: click.Context, name: str, description: str):
|
|
40
|
+
"""Create a new project"""
|
|
41
|
+
verify_login_or_prompt(ctx)
|
|
42
|
+
name = name or logical_prompt(ctx, "Name of the project to create")
|
|
43
|
+
description = description or ""
|
|
44
|
+
click.echo("Creating a new project")
|
|
45
|
+
click.echo("-" * 10)
|
|
46
|
+
try:
|
|
47
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
48
|
+
server.create_project(name, description)
|
|
49
|
+
click.echo("Project created successfully")
|
|
50
|
+
except Exception as e:
|
|
51
|
+
hint_common_solutions(ctx, e)
|
|
52
|
+
raise click.ClickException(f"Failed to create project: {e}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@project.command()
|
|
56
|
+
@click.option("--name", "-n", help="Name of the project to delete.")
|
|
57
|
+
@click.option(
|
|
58
|
+
"--confirm",
|
|
59
|
+
help="Write 'delete' to confirm deletion. Will be prompted for it if not provided.",
|
|
60
|
+
)
|
|
61
|
+
@click.pass_context
|
|
62
|
+
def delete(ctx: click.Context, name: str, confirm: str):
|
|
63
|
+
"""Delete a project by name."""
|
|
64
|
+
verify_login_or_prompt(ctx)
|
|
65
|
+
name = name or logical_prompt(ctx, "Name of the project to delete")
|
|
66
|
+
click.echo(f"Deleting project: {name}")
|
|
67
|
+
click.echo("-" * 10)
|
|
68
|
+
confirm = confirm or logical_prompt(ctx, "Please type 'delete' to confirm deletion")
|
|
69
|
+
if confirm != "delete":
|
|
70
|
+
raise click.ClickException(
|
|
71
|
+
"Deletion not confirmed. The confirmation word is 'delete'."
|
|
72
|
+
)
|
|
73
|
+
try:
|
|
74
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
75
|
+
server.delete_project(name)
|
|
76
|
+
click.echo("Project deleted successfully")
|
|
77
|
+
except Exception as e:
|
|
78
|
+
hint_common_solutions(ctx, e)
|
|
79
|
+
raise click.ClickException(f"Failed to delete project: {e}")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@project.command()
|
|
83
|
+
@format_option
|
|
84
|
+
@click.option("--name", "-n", help="Name of the project to display.")
|
|
85
|
+
@click.pass_context
|
|
86
|
+
def get(ctx: click.Context, name: str):
|
|
87
|
+
"""Display a project by name"""
|
|
88
|
+
verify_login_or_prompt(ctx)
|
|
89
|
+
name = name or logical_prompt(ctx, "Name of the project to display")
|
|
90
|
+
try:
|
|
91
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
92
|
+
prj = server.get_project(name)
|
|
93
|
+
|
|
94
|
+
def _table():
|
|
95
|
+
table = Table(title="Project", show_header=False, show_lines=True)
|
|
96
|
+
table.add_column("Field", style="cyan", no_wrap=True)
|
|
97
|
+
table.add_column("Value")
|
|
98
|
+
table.add_row("Name:", prj.name)
|
|
99
|
+
table.add_row("Description:", prj.description or "")
|
|
100
|
+
table.add_row("Created by:", prj.created_by or "-")
|
|
101
|
+
table.add_row("Created on:", prj.created_on_str or "-")
|
|
102
|
+
click.echo()
|
|
103
|
+
make_console(emoji=False, markup=False).print(table)
|
|
104
|
+
click.echo()
|
|
105
|
+
|
|
106
|
+
render(ctx, data=lambda: prj._data, table=_table)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
hint_common_solutions(ctx, e)
|
|
109
|
+
raise click.ClickException(f"Failed to display project: {e}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@project.command()
|
|
113
|
+
@format_option
|
|
114
|
+
@max_result_option
|
|
115
|
+
@click.pass_context
|
|
116
|
+
def list(ctx: click.Context):
|
|
117
|
+
"""List all projects"""
|
|
118
|
+
verify_login_or_prompt(ctx)
|
|
119
|
+
try:
|
|
120
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
121
|
+
list_of_projects, truncated = take(ctx, server.list_projects_generator())
|
|
122
|
+
|
|
123
|
+
def _table():
|
|
124
|
+
table = Table(title="Projects")
|
|
125
|
+
table.add_column("Name", style="cyan", no_wrap=True)
|
|
126
|
+
table.add_column("Description")
|
|
127
|
+
table.add_column("Created by")
|
|
128
|
+
table.add_column("Created on")
|
|
129
|
+
for prj in list_of_projects:
|
|
130
|
+
table.add_row(
|
|
131
|
+
prj.name,
|
|
132
|
+
prj.description,
|
|
133
|
+
prj.created_by,
|
|
134
|
+
prj.created_on_str,
|
|
135
|
+
)
|
|
136
|
+
click.echo()
|
|
137
|
+
make_console().print(table)
|
|
138
|
+
click.echo(f"Number of projects: {len(list_of_projects)}")
|
|
139
|
+
click.echo()
|
|
140
|
+
|
|
141
|
+
render(ctx, data=lambda: [p._data for p in list_of_projects], table=_table)
|
|
142
|
+
echo_more_results(ctx, truncated)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
hint_common_solutions(ctx, e)
|
|
145
|
+
raise click.ClickException(f"Failed to list projects: {e}")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@project.command()
|
|
149
|
+
@click.option("--name", "-n", help="Name of the project to update.")
|
|
150
|
+
@click.option("--new-name", help="New name for the project.")
|
|
151
|
+
@click.option("--description", help="New description for the project.")
|
|
152
|
+
@click.pass_context
|
|
153
|
+
def update(
|
|
154
|
+
ctx: click.Context,
|
|
155
|
+
name: str,
|
|
156
|
+
new_name: str,
|
|
157
|
+
description: str,
|
|
158
|
+
):
|
|
159
|
+
"""Update a project by name"""
|
|
160
|
+
verify_login_or_prompt(ctx)
|
|
161
|
+
name = name or logical_prompt(ctx, "Name of the project to update")
|
|
162
|
+
click.echo(f"Updating project: {name}")
|
|
163
|
+
click.echo("-" * 10)
|
|
164
|
+
try:
|
|
165
|
+
server: TabsdataServer = ctx.obj["tabsdataserver"]
|
|
166
|
+
server.update_project(name, new_name=new_name, new_description=description)
|
|
167
|
+
click.echo("Project updated successfully")
|
|
168
|
+
except Exception as e:
|
|
169
|
+
hint_common_solutions(ctx, e)
|
|
170
|
+
raise click.ClickException(f"Failed to update project: {e}")
|