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.
Files changed (39) hide show
  1. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/__init__.py +5 -0
  2. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/_registry.py +78 -0
  3. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/BANNER +5 -0
  4. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/LICENSE +3 -0
  5. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/README-PyPi.md +8 -0
  6. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/RELEASE-NOTES +1 -0
  7. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/THIRD-PARTY +3 -0
  8. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/VERSION +1 -0
  9. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/cli_utils.py +993 -0
  10. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/shell.py +390 -0
  11. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/__init__.py +3 -0
  12. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/auth_group.py +197 -0
  13. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/cli.py +508 -0
  14. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/collection_group.py +458 -0
  15. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/config_utils.py +93 -0
  16. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/connection_group.py +159 -0
  17. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/examples_group.py +206 -0
  18. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/execution_utils.py +429 -0
  19. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/fn_group.py +1109 -0
  20. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/fnrun_group.py +285 -0
  21. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/group_group.py +184 -0
  22. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/metrics_group.py +323 -0
  23. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/permission_group.py +259 -0
  24. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/plan_group.py +324 -0
  25. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/project_group.py +170 -0
  26. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/role_group.py +630 -0
  27. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/settings_group.py +344 -0
  28. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/table_group.py +600 -0
  29. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/trigger_group.py +338 -0
  30. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/trx_group.py +329 -0
  31. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/user_group.py +200 -0
  32. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/worker_group.py +185 -0
  33. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/template.py +781 -0
  34. tabsdata_cli-2.0.0.data/purelib/tabsdatak/py.typed +0 -0
  35. tabsdata_cli-2.0.0.dist-info/METADATA +39 -0
  36. tabsdata_cli-2.0.0.dist-info/RECORD +39 -0
  37. tabsdata_cli-2.0.0.dist-info/WHEEL +5 -0
  38. tabsdata_cli-2.0.0.dist-info/licenses/src/main/python/tabsdatak/cli/assets/manifest/LICENSE +3 -0
  39. tabsdata_cli-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,630 @@
1
+ #
2
+ # Copyright 2026 Tabsdata Inc.
3
+ #
4
+
5
+
6
+ import rich_click as click
7
+ from rich.table import Table
8
+
9
+ from tabsdatak.cli._registry import cli_command
10
+ from tabsdatak.cli.cli_utils import (
11
+ MutuallyExclusiveOption,
12
+ echo_more_results,
13
+ get_currently_pinned_object,
14
+ get_pin,
15
+ hint_common_solutions,
16
+ logical_prompt,
17
+ make_console,
18
+ max_result_option,
19
+ set_pin,
20
+ store_cli_options,
21
+ take,
22
+ verify_login_or_prompt,
23
+ )
24
+ from tabsdatak.sdk.tabsdata_server import (
25
+ PERMISSION_TYPES_WITH_ENTITY,
26
+ VALID_PERMISSION_TYPES,
27
+ Role,
28
+ TabsdataServer,
29
+ )
30
+
31
+ ALL_ENTITIES_REPRESENTATION = "<ALL>"
32
+
33
+
34
+ @cli_command
35
+ @click.group()
36
+ def role():
37
+ """Role management commands"""
38
+
39
+
40
+ @role.command()
41
+ @click.option(
42
+ "--name",
43
+ "-n",
44
+ help="Name of the role to which the permissions will be added.",
45
+ )
46
+ @click.option(
47
+ "--perm",
48
+ help=(
49
+ "Permission to add. Will "
50
+ "be prompted for it if not provided. Valid values are "
51
+ f"{', '.join(str(p) for p in VALID_PERMISSION_TYPES)}"
52
+ ),
53
+ )
54
+ @click.option(
55
+ "--proj",
56
+ "project",
57
+ help=(
58
+ "Project to which the permission will apply. If '"
59
+ f"{ALL_ENTITIES_REPRESENTATION}' is provided, it will "
60
+ "apply to all projects. This option is only allowed for "
61
+ "permissions that require an entity, which are "
62
+ f"{', '.join(str(p) for p in PERMISSION_TYPES_WITH_ENTITY)}."
63
+ ),
64
+ )
65
+ @click.pass_context
66
+ def add_perm(ctx: click.Context, name: str, perm: str, project: str):
67
+ """Add a permission to a role"""
68
+ verify_login_or_prompt(ctx)
69
+ name = (
70
+ name
71
+ or get_currently_pinned_object(ctx, "role")
72
+ or logical_prompt(ctx, "Name of the role to add the permission to")
73
+ )
74
+ perm = perm or logical_prompt(
75
+ ctx,
76
+ "Permission to add. Valid values are "
77
+ f"'{', '.join(str(p) for p in VALID_PERMISSION_TYPES)}'",
78
+ )
79
+ flattened_list = [item for tup in PERMISSION_TYPES_WITH_ENTITY for item in tup]
80
+ requires_entity = perm in flattened_list
81
+ if project:
82
+ if not requires_entity:
83
+ raise click.ClickException(
84
+ f"Permission '{perm}' does not require an entity. The permissions "
85
+ "that require an entity are: "
86
+ f"{', '.join(str(p) for p in PERMISSION_TYPES_WITH_ENTITY)}. "
87
+ "Please remove the --proj option."
88
+ )
89
+ if project == ALL_ENTITIES_REPRESENTATION:
90
+ project = None
91
+ else:
92
+ if requires_entity:
93
+ project = logical_prompt(
94
+ ctx,
95
+ "Project to which the permission will apply. If "
96
+ f"'{ALL_ENTITIES_REPRESENTATION}' is provided, it will apply "
97
+ "to all projects.",
98
+ default_value=ALL_ENTITIES_REPRESENTATION,
99
+ )
100
+ if project == ALL_ENTITIES_REPRESENTATION:
101
+ project = None
102
+ try:
103
+ click.echo(f"Adding permission of type '{perm}' to role '{name}'")
104
+ click.echo("-" * 10)
105
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
106
+ role = server.get_role(name)
107
+ permission = role.create_permission(perm, project)
108
+
109
+ click.echo("Permission added successfully")
110
+ click.echo(f"Permission ID: {permission.id}")
111
+ except Exception as e:
112
+ hint_common_solutions(ctx, e)
113
+ raise click.ClickException(f"Failed to add permission: {e}")
114
+
115
+
116
+ @role.command()
117
+ @click.option(
118
+ "--name",
119
+ "-n",
120
+ help="Name of the role to which the user will be added.",
121
+ )
122
+ @click.option(
123
+ "--user",
124
+ "-u",
125
+ help="User that will be added to the role.",
126
+ )
127
+ @click.pass_context
128
+ def add_user(ctx: click.Context, name: str, user: str):
129
+ """Add a user to a role"""
130
+ verify_login_or_prompt(ctx)
131
+ name = (
132
+ name
133
+ or get_currently_pinned_object(ctx, "role")
134
+ or logical_prompt(ctx, "Name of the role to the user to")
135
+ )
136
+ user = user or logical_prompt(ctx, "User that will be added to the role")
137
+ try:
138
+ click.echo(f"Adding user '{user}' to role '{name}'")
139
+ click.echo("-" * 10)
140
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
141
+ role = server.get_role(name)
142
+ role.add_user(user)
143
+
144
+ click.echo("User added successfully")
145
+ except Exception as e:
146
+ hint_common_solutions(ctx, e)
147
+ raise click.ClickException(f"Failed to add user to role: {e}")
148
+
149
+
150
+ @role.command()
151
+ @click.option("--name", "-n", help="Name of the role to create.")
152
+ @click.option("--description", help="Description of the role.")
153
+ @click.pass_context
154
+ def create(
155
+ ctx: click.Context,
156
+ name: str,
157
+ description: str,
158
+ ):
159
+ """Create a new role"""
160
+ verify_login_or_prompt(ctx)
161
+ name = (
162
+ name
163
+ or get_currently_pinned_object(ctx, "role")
164
+ or logical_prompt(ctx, "Name of the role to create")
165
+ )
166
+ description = description or ""
167
+ click.echo("Creating a new role")
168
+ click.echo("-" * 10)
169
+ try:
170
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
171
+ server.create_role(name, description)
172
+ click.echo("Role created successfully")
173
+ except Exception as e:
174
+ hint_common_solutions(ctx, e)
175
+ raise click.ClickException(f"Failed to create role: {e}")
176
+
177
+
178
+ @role.command()
179
+ @click.option("--name", "-n", help="Name of the role to delete.")
180
+ @click.option(
181
+ "--confirm",
182
+ help="Write 'delete' to confirm deletion. Will be prompted for it if not provided.",
183
+ )
184
+ @click.pass_context
185
+ def delete(ctx: click.Context, name: str, confirm: str):
186
+ """Delete a role by name"""
187
+ verify_login_or_prompt(ctx)
188
+ name = (
189
+ name
190
+ or get_currently_pinned_object(ctx, "role")
191
+ or logical_prompt(ctx, "Name of the role to delete")
192
+ )
193
+ click.echo(f"Deleting role: {name}")
194
+ click.echo("-" * 10)
195
+ confirm = confirm or logical_prompt(ctx, "Please type 'delete' to confirm deletion")
196
+ if confirm != "delete":
197
+ raise click.ClickException(
198
+ "Deletion not confirmed. The confirmation word is 'delete'."
199
+ )
200
+ try:
201
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
202
+ server.delete_role(name)
203
+ click.echo("Role deleted successfully")
204
+ except Exception as e:
205
+ hint_common_solutions(ctx, e)
206
+ raise click.ClickException(f"Failed to delete role: {e}")
207
+
208
+
209
+ @role.command()
210
+ @click.option(
211
+ "--name",
212
+ "-n",
213
+ help="Name of the role from which the permissions will be deleted.",
214
+ )
215
+ @click.option(
216
+ "--id",
217
+ help="ID of the permission to delete.",
218
+ cls=MutuallyExclusiveOption,
219
+ mutually_exclusive=["perm", "project"],
220
+ )
221
+ @click.option(
222
+ "--perm",
223
+ help=(
224
+ "Permission to delete. Valid values are "
225
+ f"{', '.join(str(p) for p in VALID_PERMISSION_TYPES)}"
226
+ ),
227
+ cls=MutuallyExclusiveOption,
228
+ mutually_exclusive=["id"],
229
+ )
230
+ @click.option(
231
+ "--proj",
232
+ "project",
233
+ help=(
234
+ "Project to which the permission applies. "
235
+ "This option is only allowed for permissions that require an entity, which are "
236
+ f"{', '.join(str(p) for p in PERMISSION_TYPES_WITH_ENTITY)}."
237
+ ),
238
+ cls=MutuallyExclusiveOption,
239
+ mutually_exclusive=["id"],
240
+ )
241
+ @click.pass_context
242
+ def delete_perm(ctx: click.Context, name: str, id: str, perm: str, project: str):
243
+ """Delete a permission from a role"""
244
+ verify_login_or_prompt(ctx)
245
+ name = (
246
+ name
247
+ or get_currently_pinned_object(ctx, "role")
248
+ or logical_prompt(
249
+ ctx, "Name of the role from which the permission will be deleted"
250
+ )
251
+ )
252
+ if not perm:
253
+ id = id or logical_prompt(ctx, "ID of the permission to delete")
254
+ else:
255
+ project = _obtain_project_for_perm(perm, project, ctx)
256
+ try:
257
+ if id:
258
+ click.echo(f"Deleting permission from role '{name}' with ID '{id}'")
259
+ else:
260
+ click.echo(f"Deleting permission '{perm}' from role '{name}'")
261
+ click.echo("-" * 10)
262
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
263
+ role = server.get_role(name)
264
+ if id:
265
+ _delete_permission_by_id(role, id, name)
266
+ else:
267
+ _delete_permission_by_type_and_entity(perm, project, role, name)
268
+
269
+ click.echo("Permission deleted successfully")
270
+ except Exception as e:
271
+ hint_common_solutions(ctx, e)
272
+ raise click.ClickException(f"Failed to delete permission: {e}")
273
+
274
+
275
+ def _delete_permission_by_id(role: Role, id: str, name: str):
276
+ permissions = role.list_permissions(filter=f"id:eq:{id}")
277
+ try:
278
+ permission = permissions[0]
279
+ except IndexError:
280
+ raise click.ClickException(
281
+ f"No permission found for role '{name}' with ID '{id}'. The"
282
+ " existing permissions are:"
283
+ f" {', '.join(p.id for p in role.permissions)}"
284
+ )
285
+ role.delete_permission(permission)
286
+
287
+
288
+ def _obtain_project_for_perm(perm: str, project: str, ctx: click.Context):
289
+ flattened_list = [item for tup in PERMISSION_TYPES_WITH_ENTITY for item in tup]
290
+ requires_entity = perm in flattened_list
291
+ if project:
292
+ if not requires_entity:
293
+ raise click.ClickException(
294
+ f"Permission '{perm}' does not require an entity. The permissions "
295
+ "that require an entity are: "
296
+ f"{', '.join(str(p) for p in PERMISSION_TYPES_WITH_ENTITY)}. "
297
+ "Please remove the --proj option."
298
+ )
299
+ if project == ALL_ENTITIES_REPRESENTATION:
300
+ project = None
301
+ else:
302
+ if requires_entity:
303
+ project = logical_prompt(
304
+ ctx,
305
+ "Project to which the permission that will be deleted "
306
+ "currently applies. Provide "
307
+ f"'{ALL_ENTITIES_REPRESENTATION}' if it applies "
308
+ "to all projects.",
309
+ default_value=ALL_ENTITIES_REPRESENTATION,
310
+ )
311
+ if project == ALL_ENTITIES_REPRESENTATION:
312
+ project = None
313
+ return project
314
+
315
+
316
+ def _delete_permission_by_type_and_entity(
317
+ perm: str, project: str, role: Role, name: str
318
+ ):
319
+ for n, value in VALID_PERMISSION_TYPES:
320
+ if perm.lower() == n or perm.lower() == value:
321
+ permission_type = value
322
+ break
323
+ else:
324
+ raise click.ClickException(
325
+ f"'{perm}' is an invalid permission type. "
326
+ "The valid values are: "
327
+ f"{', '.join(str(p) for p in VALID_PERMISSION_TYPES)}."
328
+ )
329
+ permissions = role.list_permissions(
330
+ filter=f"permission_type:eq:{permission_type}",
331
+ )
332
+ for p in permissions:
333
+ if p.entity == project:
334
+ role.delete_permission(p)
335
+ break
336
+ else:
337
+ flattened_list = [item for tup in PERMISSION_TYPES_WITH_ENTITY for item in tup]
338
+ list_of_permissions = []
339
+ for p in role.permissions:
340
+ entity = p.entity
341
+ if not entity:
342
+ entity = (
343
+ ALL_ENTITIES_REPRESENTATION if p.type in flattened_list else "-"
344
+ )
345
+ list_of_permissions.append((p.type, entity))
346
+ raise click.ClickException(
347
+ f"No permission found for role '{name}' with type '{perm}' and "
348
+ "applying to the provided entity. The existing "
349
+ "permissions are: "
350
+ f"{', '.join(str(p) for p in list_of_permissions)}"
351
+ )
352
+
353
+
354
+ @role.command()
355
+ @click.option(
356
+ "--name",
357
+ "-n",
358
+ help="Name of the role from which the user will be deleted.",
359
+ )
360
+ @click.option(
361
+ "--user",
362
+ "-u",
363
+ help="User to delete from the role.",
364
+ )
365
+ @click.pass_context
366
+ def delete_user(ctx: click.Context, name: str, user: str):
367
+ """Delete a user from a role"""
368
+ verify_login_or_prompt(ctx)
369
+ name = (
370
+ name
371
+ or get_currently_pinned_object(ctx, "role")
372
+ or logical_prompt(ctx, "Name of the role from which the user will be deleted")
373
+ )
374
+ user = user or logical_prompt(ctx, "User to delete from the role")
375
+ try:
376
+ click.echo(f"Deleting user '{user}' from role '{name}'")
377
+ click.echo("-" * 10)
378
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
379
+ role = server.get_role(name)
380
+ users = role.list_users(filter=f"user:eq:{user}")
381
+ try:
382
+ user = users[0]
383
+ except IndexError:
384
+ raise click.ClickException(
385
+ f"No users found for role '{name}' with name '{user}'. The existing "
386
+ f"users are: {', '.join(u.name for u in role.users)}"
387
+ )
388
+ role.delete_user(user)
389
+
390
+ click.echo("User deleted from role successfully")
391
+ except Exception as e:
392
+ hint_common_solutions(ctx, e)
393
+ raise click.ClickException(f"Failed to delete user from role: {e}")
394
+
395
+
396
+ @role.command()
397
+ @click.option("--name", "-n", help="Name of the role to display.")
398
+ @click.pass_context
399
+ def info(ctx: click.Context, name: str):
400
+ """Display a role by name"""
401
+ verify_login_or_prompt(ctx)
402
+ name = (
403
+ name
404
+ or get_currently_pinned_object(ctx, "role")
405
+ or logical_prompt(ctx, "Name of the role to display")
406
+ )
407
+ try:
408
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
409
+ role = server.get_role(name)
410
+
411
+ table = Table(title="Role")
412
+ table.add_column("Name", style="cyan", no_wrap=True)
413
+ table.add_column("ID", no_wrap=True)
414
+ table.add_column("Description")
415
+ table.add_column("Created on")
416
+ table.add_column("Created by")
417
+
418
+ table.add_row(
419
+ role.name,
420
+ role.id,
421
+ role.description,
422
+ role.created_on_str,
423
+ role.created_by,
424
+ )
425
+
426
+ click.echo()
427
+ console = make_console()
428
+ console.print(table)
429
+ click.echo()
430
+ except Exception as e:
431
+ hint_common_solutions(ctx, e)
432
+ raise click.ClickException(f"Failed to display role: {e}")
433
+
434
+
435
+ @role.command()
436
+ @max_result_option
437
+ @click.pass_context
438
+ def list(ctx: click.Context):
439
+ """List all roles"""
440
+ verify_login_or_prompt(ctx)
441
+ try:
442
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
443
+ list_of_roles, truncated = take(ctx, server.list_roles_generator())
444
+
445
+ table = Table(title="Roles")
446
+ table.add_column("Name", style="cyan", no_wrap=True)
447
+ table.add_column("ID", no_wrap=True)
448
+ table.add_column("Description")
449
+ table.add_column("Created on")
450
+ table.add_column("Created by")
451
+
452
+ for role in list_of_roles:
453
+ table.add_row(
454
+ role.name,
455
+ role.id,
456
+ role.description,
457
+ role.created_on_str,
458
+ role.created_by,
459
+ )
460
+
461
+ click.echo()
462
+ console = make_console()
463
+ console.print(table)
464
+ click.echo(f"Number of roles: {len(list_of_roles)}")
465
+ echo_more_results(ctx, truncated)
466
+ click.echo()
467
+ except Exception as e:
468
+ hint_common_solutions(ctx, e)
469
+ raise click.ClickException(f"Failed to list roles: {e}")
470
+
471
+
472
+ @role.command()
473
+ @click.option("--name", "-n", help="Name of the role to which the permissions belong.")
474
+ @max_result_option
475
+ @click.pass_context
476
+ def list_perm(ctx: click.Context, name: str):
477
+ """List all permissions of a role"""
478
+ verify_login_or_prompt(ctx)
479
+ name = (
480
+ name
481
+ or get_currently_pinned_object(ctx, "role")
482
+ or logical_prompt(ctx, "Name of the role to which the permissions belong")
483
+ )
484
+ try:
485
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
486
+ role = server.get_role(name)
487
+ list_of_permissions, truncated = take(ctx, role.list_permissions_generator())
488
+
489
+ table = Table(title="Permissions")
490
+ table.add_column("ID", style="cyan", no_wrap=True)
491
+ table.add_column("Permission type")
492
+ table.add_column("Entity name")
493
+ table.add_column("Granted on")
494
+ table.add_column("Granted by")
495
+
496
+ for permission in list_of_permissions:
497
+ permission_type = permission.type
498
+ flattened_list = [
499
+ item for tup in PERMISSION_TYPES_WITH_ENTITY for item in tup
500
+ ]
501
+ if permission_type not in flattened_list:
502
+ entity = "-"
503
+ else:
504
+ entity = (
505
+ permission.entity
506
+ if permission.entity
507
+ else ALL_ENTITIES_REPRESENTATION
508
+ )
509
+ table.add_row(
510
+ permission.id,
511
+ permission_type,
512
+ entity,
513
+ permission.granted_on_str,
514
+ permission.granted_by,
515
+ )
516
+
517
+ click.echo()
518
+ console = make_console()
519
+ console.print(table)
520
+ click.echo(f"Number of permissions: {len(list_of_permissions)}")
521
+ echo_more_results(ctx, truncated)
522
+ click.echo()
523
+ except Exception as e:
524
+ hint_common_solutions(ctx, e)
525
+ raise click.ClickException(f"Failed to list permissions: {e}")
526
+
527
+
528
+ @role.command()
529
+ @click.option("--name", "-n", help="Name of the role to which the users belong.")
530
+ @max_result_option
531
+ @click.pass_context
532
+ def list_user(ctx: click.Context, name: str):
533
+ """List all users of a role"""
534
+ verify_login_or_prompt(ctx)
535
+ name = (
536
+ name
537
+ or get_currently_pinned_object(ctx, "role")
538
+ or logical_prompt(ctx, "Name of the role to which the users belong")
539
+ )
540
+ try:
541
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
542
+ role = server.get_role(name)
543
+ list_of_users, truncated = take(ctx, role.list_users_generator())
544
+
545
+ table = Table(title="Users")
546
+ table.add_column("Name", style="cyan", no_wrap=True)
547
+ table.add_column("Full name")
548
+ table.add_column("Email")
549
+ table.add_column("Enabled")
550
+
551
+ for user in list_of_users:
552
+ table.add_row(user.name, user.full_name, user.email, str(user.enabled))
553
+
554
+ click.echo()
555
+ console = make_console()
556
+ console.print(table)
557
+ click.echo(f"Number of users: {len(list_of_users)}")
558
+ echo_more_results(ctx, truncated)
559
+ click.echo()
560
+ except Exception as e:
561
+ hint_common_solutions(ctx, e)
562
+ raise click.ClickException(f"Failed to list users: {e}")
563
+
564
+
565
+ @role.command()
566
+ @click.option("--name", "-n", help="Name of the role to pin.")
567
+ @click.pass_context
568
+ def pin(ctx: click.Context, name: str):
569
+ """Pin a role by name"""
570
+ name = name or logical_prompt(ctx, "Name of the role to be pinned")
571
+ click.echo(f"Pinning role: {name}")
572
+ click.echo("-" * 10)
573
+ try:
574
+ previously_pinned = get_pin(ctx, "role")
575
+ if previously_pinned:
576
+ click.echo(f"Unpinning previously pinned role: {previously_pinned}")
577
+ set_pin(ctx, "role", name)
578
+ store_cli_options(ctx)
579
+ click.echo("Role pinned successfully")
580
+ except Exception as e:
581
+ hint_common_solutions(ctx, e)
582
+ raise click.ClickException(f"Failed to pin role: {e}")
583
+
584
+
585
+ @role.command()
586
+ @click.pass_context
587
+ def unpin(ctx: click.Context):
588
+ """Unpin the currently pinned role"""
589
+ click.echo("Unpinning role")
590
+ click.echo("-" * 10)
591
+ try:
592
+ previously_pinned = get_pin(ctx, "role")
593
+ if not previously_pinned:
594
+ click.echo("No previously pinned role to unpin.")
595
+ else:
596
+ set_pin(ctx, "role", None)
597
+ store_cli_options(ctx)
598
+ click.echo("Role unpinned successfully")
599
+ except Exception as e:
600
+ hint_common_solutions(ctx, e)
601
+ raise click.ClickException(f"Failed to unpin role: {e}")
602
+
603
+
604
+ @role.command()
605
+ @click.option("--name", "-n", help="Name of the role to update.")
606
+ @click.option("--new-name", help="New name for the role.")
607
+ @click.option("--description", help="New description for the role.")
608
+ @click.pass_context
609
+ def update(
610
+ ctx: click.Context,
611
+ name: str,
612
+ new_name: str,
613
+ description: str,
614
+ ):
615
+ """Update a role by name"""
616
+ verify_login_or_prompt(ctx)
617
+ name = (
618
+ name
619
+ or get_currently_pinned_object(ctx, "role")
620
+ or logical_prompt(ctx, "Name of the role to update")
621
+ )
622
+ click.echo(f"Updating role: {name}")
623
+ click.echo("-" * 10)
624
+ try:
625
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
626
+ server.update_role(name, new_name=new_name, new_description=description)
627
+ click.echo("Role updated successfully")
628
+ except Exception as e:
629
+ hint_common_solutions(ctx, e)
630
+ raise click.ClickException(f"Failed to update role: {e}")