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,600 @@
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
+ MutuallyExclusiveOption,
15
+ beautify_time,
16
+ echo_more_results,
17
+ format_option,
18
+ get_currently_pinned_object,
19
+ hint_common_solutions,
20
+ logical_prompt,
21
+ make_console,
22
+ max_result_option,
23
+ proj_option,
24
+ render,
25
+ take,
26
+ verify_login_or_prompt,
27
+ )
28
+
29
+ if TYPE_CHECKING:
30
+ from tabsdatak.sdk.tabsdata_server import TabsdataServer
31
+
32
+
33
+ _TABLE_STATUS_LABELS = {"A": "Active", "F": "Frozen", "D": "Deleted"}
34
+
35
+
36
+ def _size_mb(data_bytes) -> str:
37
+ if data_bytes is None:
38
+ return "-"
39
+ return f"{int(data_bytes) / (1024 * 1024):.2f}"
40
+
41
+
42
+ @cli_command
43
+ @click.group()
44
+ def table():
45
+ """Table management commands"""
46
+
47
+
48
+ @table.command()
49
+ @proj_option
50
+ @click.option(
51
+ "--name",
52
+ "-n",
53
+ help="Name of the table to be deleted.",
54
+ )
55
+ @click.option(
56
+ "--coll",
57
+ "-c",
58
+ help="Name of the collection to which the table belongs.",
59
+ )
60
+ @click.option(
61
+ "--confirm",
62
+ help="Write 'delete' to confirm deletion. Will be prompted for it if not provided.",
63
+ )
64
+ @click.pass_context
65
+ def delete(ctx: click.Context, name: str, coll: str, confirm: str):
66
+ """Delete a table"""
67
+ verify_login_or_prompt(ctx)
68
+ name = name or logical_prompt(ctx, "Name of the table to be deleted")
69
+ coll = (
70
+ coll
71
+ or get_currently_pinned_object(ctx, "collection")
72
+ or logical_prompt(ctx, "Name of the collection to which the table belongs")
73
+ )
74
+ click.echo(f"Deleting table '{name}' in collection '{coll}'")
75
+ click.echo("-" * 10)
76
+ confirm = confirm or logical_prompt(ctx, "Please type 'delete' to confirm deletion")
77
+ if confirm != "delete":
78
+ raise click.ClickException(
79
+ "Deletion not confirmed. The confirmation word is 'delete'."
80
+ )
81
+ try:
82
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
83
+ server.delete_table(coll, name)
84
+ click.echo("Table deleted successfully")
85
+ except Exception as e:
86
+ hint_common_solutions(ctx, e)
87
+ raise click.ClickException(f"Failed to delete table: {e}")
88
+
89
+
90
+ @table.command()
91
+ @proj_option
92
+ @click.option(
93
+ "--name",
94
+ help="Name of the table.",
95
+ )
96
+ @click.option(
97
+ "--coll",
98
+ "-c",
99
+ help="Name of the collection to which the table belongs.",
100
+ )
101
+ @click.option(
102
+ "--file",
103
+ help=(
104
+ "File in which the table will be stored. Can be an absolute or a relative "
105
+ "path. Any directory that appears must already exist (it will not be created "
106
+ "as part of the download)."
107
+ ),
108
+ )
109
+ @click.option(
110
+ "--at",
111
+ help=(
112
+ "If provided, the table values at the given time will be shown. Must be "
113
+ " either a valid timestamp in the form of a unix timestamp (milliseconds since "
114
+ "epoch without a dot, e.g. '1750074554472') or a valid date-time format. "
115
+ "The valid "
116
+ "formats are 'YYYY-MM-DD', 'YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:MM', "
117
+ "'YYYY-MM-DDTHH:MM:SS', and 'YYYY-MM-DDTHH:MM:SS.sss'. A 'Z' character can be "
118
+ "added at the end to indicate UTC time (e.g., '2023-10-01T12Z' or "
119
+ "'2023-10-01T12:00:00.000Z') or it can be omitted to indicate local "
120
+ "time (e.g., '2023-10-01T12' or '2023-10-01T12:00:00.000')."
121
+ ),
122
+ type=str,
123
+ cls=MutuallyExclusiveOption,
124
+ mutually_exclusive=["at-trx", "version"],
125
+ )
126
+ @click.option(
127
+ "--at-trx",
128
+ help=(
129
+ "ID of a transaction. If provided, the table values at the end of the given "
130
+ "transaction will be shown."
131
+ ),
132
+ type=str,
133
+ cls=MutuallyExclusiveOption,
134
+ mutually_exclusive=["at", "version"],
135
+ )
136
+ @click.option(
137
+ "--version",
138
+ help=(
139
+ "ID of a dataversion of the table. If provided, the table values at that "
140
+ "dataversion will be shown."
141
+ ),
142
+ type=str,
143
+ cls=MutuallyExclusiveOption,
144
+ mutually_exclusive=["at", "at-trx"],
145
+ )
146
+ @click.pass_context
147
+ def download(
148
+ ctx: click.Context,
149
+ coll: str,
150
+ name: str,
151
+ file: str,
152
+ at: str,
153
+ at_trx: str,
154
+ version: str,
155
+ ):
156
+ """Download the table as a parquet file"""
157
+ verify_login_or_prompt(ctx)
158
+ file = file or logical_prompt(ctx, "File in which the table will be stored")
159
+ name = name or logical_prompt(ctx, "Name of the table")
160
+ coll = (
161
+ coll
162
+ or get_currently_pinned_object(ctx, "collection")
163
+ or logical_prompt(ctx, "Name of the collection to which the table belongs")
164
+ )
165
+ click.echo(f"Downloading table to file '{file}'")
166
+ click.echo("-" * 10)
167
+ try:
168
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
169
+ server.download_table(
170
+ coll,
171
+ name,
172
+ file,
173
+ at=at,
174
+ at_trx=at_trx,
175
+ version=version,
176
+ )
177
+
178
+ click.echo("Table downloaded successfully")
179
+
180
+ except Exception as e:
181
+ hint_common_solutions(ctx, e)
182
+ raise click.ClickException(f"Failed to download table: {e}")
183
+
184
+
185
+ @table.command()
186
+ @proj_option
187
+ @click.option(
188
+ "--coll",
189
+ "-c",
190
+ help="Name of the collection to which the tables belong.",
191
+ )
192
+ @click.option(
193
+ "--name",
194
+ "-n",
195
+ help=(
196
+ "A name wildcard to match for the list. "
197
+ "For example, 'my_table*' will match all tables "
198
+ "starting with 'my_table'."
199
+ ),
200
+ )
201
+ @click.option(
202
+ "--at",
203
+ help=(
204
+ "If provided, the table values at the given time will be shown. Must be either"
205
+ " a valid timestamp in the form of a unix timestamp (milliseconds since epoch"
206
+ " without a dot, e.g. '1750074554472') or a valid date-time format. The valid"
207
+ " formats are 'YYYY-MM-DD', 'YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:MM',"
208
+ " 'YYYY-MM-DDTHH:MM:SS', and 'YYYY-MM-DDTHH:MM:SS.sss'. A 'Z' character can be"
209
+ " added at the end to indicate UTC time (e.g., '2023-10-01T12Z' or"
210
+ " '2023-10-01T12:00:00.000Z') or it can be omitted to indicate local time"
211
+ " (e.g., '2023-10-01T12' or '2023-10-01T12:00:00.000')."
212
+ ),
213
+ type=str,
214
+ cls=MutuallyExclusiveOption,
215
+ mutually_exclusive=["at-trx"],
216
+ )
217
+ @click.option(
218
+ "--at-trx",
219
+ help=(
220
+ "ID of a transaction. If provided, the table values at the end of the given "
221
+ "transaction will be shown."
222
+ ),
223
+ type=str,
224
+ cls=MutuallyExclusiveOption,
225
+ mutually_exclusive=["at"],
226
+ )
227
+ @format_option
228
+ @max_result_option
229
+ @click.pass_context
230
+ def list(ctx: click.Context, coll: str, name: str, at: str, at_trx: str):
231
+ """List all tables in a collection"""
232
+ verify_login_or_prompt(ctx)
233
+ coll = (
234
+ coll
235
+ or get_currently_pinned_object(ctx, "collection")
236
+ or logical_prompt(ctx, "Name of the collection to which the tables belong")
237
+ )
238
+ try:
239
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
240
+ request_filter = []
241
+ if name:
242
+ request_filter.append(f"name:lk:{name}")
243
+ list_of_tables, truncated = take(
244
+ ctx,
245
+ server.list_tables_generator(
246
+ coll, filter=request_filter, at=at, at_trx=at_trx
247
+ ),
248
+ )
249
+
250
+ def _table():
251
+ from tabsdatak.sdk.tabsdata_server import _convert_timestamp_to_string
252
+
253
+ cli_table = Table(title="Tables")
254
+ cli_table.add_column("Name", style="cyan", no_wrap=True)
255
+ cli_table.add_column("Function")
256
+ cli_table.add_column("Status")
257
+ cli_table.add_column("Private")
258
+ cli_table.add_column("Created by")
259
+ cli_table.add_column("Created on")
260
+ for table in list_of_tables:
261
+ data = table.kwargs
262
+ defined_on = data.get("defined_on")
263
+ cli_table.add_row(
264
+ table.name,
265
+ table.function.name,
266
+ _TABLE_STATUS_LABELS.get(
267
+ data.get("status"), data.get("status") or "-"
268
+ ),
269
+ "Yes" if data.get("private") else "No",
270
+ data.get("defined_by") or "-",
271
+ _convert_timestamp_to_string(defined_on) if defined_on else "-",
272
+ )
273
+ click.echo()
274
+ make_console().print(cli_table)
275
+ click.echo(
276
+ f"Number of tables in collection '{coll}': {len(list_of_tables)}"
277
+ )
278
+ click.echo()
279
+
280
+ render(
281
+ ctx,
282
+ data=lambda: [t._data for t in list_of_tables],
283
+ table=_table,
284
+ )
285
+ echo_more_results(ctx, truncated)
286
+
287
+ except Exception as e:
288
+ hint_common_solutions(ctx, e)
289
+ raise click.ClickException(f"Failed to list tables: {e}")
290
+
291
+
292
+ @table.command()
293
+ @proj_option
294
+ @click.option(
295
+ "--name",
296
+ help="Name of the table.",
297
+ )
298
+ @click.option(
299
+ "--coll",
300
+ "-c",
301
+ help="Name of the collection to which the table belongs.",
302
+ )
303
+ @click.option(
304
+ "--len",
305
+ type=int,
306
+ help=(
307
+ "Maximum amount of rows to be shown. If not provided, the server default "
308
+ "of 100 rows applies. Cannot be greater than 1000."
309
+ ),
310
+ )
311
+ @click.option(
312
+ "--offset",
313
+ type=int,
314
+ help=(
315
+ "How many rows to skip before starting to read. If not provided, "
316
+ "reading will start at the first row."
317
+ ),
318
+ )
319
+ @click.option(
320
+ "--sql",
321
+ help=(
322
+ "A SQL query to run on the table server-side, before the rows are returned. "
323
+ "The table is the only queryable relation and is referenced by its own name "
324
+ "(for example, 'SELECT a, b FROM my_table WHERE a > 10'); referencing any "
325
+ "other table or a file-reading function is rejected. '--offset' is applied "
326
+ "before the query and '--len' caps its result."
327
+ ),
328
+ )
329
+ @click.option(
330
+ "--at",
331
+ help=(
332
+ "If provided, the table values at the given time will be shown. Must be "
333
+ " either a valid timestamp in the form of a unix timestamp (milliseconds since "
334
+ "epoch without a dot, e.g. '1750074554472') or a valid date-time format. "
335
+ "The valid "
336
+ "formats are 'YYYY-MM-DD', 'YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:MM', "
337
+ "'YYYY-MM-DDTHH:MM:SS', and 'YYYY-MM-DDTHH:MM:SS.sss'. A 'Z' character can be "
338
+ "added at the end to indicate UTC time (e.g., '2023-10-01T12Z' or "
339
+ "'2023-10-01T12:00:00.000Z') or it can be omitted to indicate local "
340
+ "time (e.g., '2023-10-01T12' or '2023-10-01T12:00:00.000')."
341
+ ),
342
+ type=str,
343
+ cls=MutuallyExclusiveOption,
344
+ mutually_exclusive=["at-trx", "version"],
345
+ )
346
+ @click.option(
347
+ "--at-trx",
348
+ help=(
349
+ "ID of a transaction. If provided, the table values at the end of the given "
350
+ "transaction will be shown."
351
+ ),
352
+ type=str,
353
+ cls=MutuallyExclusiveOption,
354
+ mutually_exclusive=["at", "version"],
355
+ )
356
+ @click.option(
357
+ "--version",
358
+ help=(
359
+ "ID of a dataversion of the table. If provided, the table values at that "
360
+ "dataversion will be shown."
361
+ ),
362
+ type=str,
363
+ cls=MutuallyExclusiveOption,
364
+ mutually_exclusive=["at", "at-trx"],
365
+ )
366
+ @click.option(
367
+ "--file",
368
+ help=(
369
+ "File in which the table will be stored as a NDJSON. Can be an absolute or a "
370
+ "relative path. Any directory that appears must already exist (it will not be "
371
+ "created as part of the download)."
372
+ ),
373
+ )
374
+ @format_option
375
+ @click.pass_context
376
+ def data(
377
+ ctx: click.Context,
378
+ coll: str,
379
+ name: str,
380
+ len: int,
381
+ offset: int,
382
+ sql: str,
383
+ file: str,
384
+ at: str,
385
+ at_trx: str,
386
+ version: str,
387
+ ):
388
+ """Show rows of data from the table"""
389
+ verify_login_or_prompt(ctx)
390
+ name = name or logical_prompt(ctx, "Name of the table")
391
+ coll = (
392
+ coll
393
+ or get_currently_pinned_object(ctx, "collection")
394
+ or logical_prompt(ctx, "Name of the collection to which the table belongs")
395
+ )
396
+ try:
397
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
398
+ table_data = server.sample_table(
399
+ coll,
400
+ name,
401
+ at=at,
402
+ at_trx=at_trx,
403
+ version=version,
404
+ len=len,
405
+ offset=offset,
406
+ sql=sql,
407
+ )
408
+
409
+ def _table():
410
+ click.echo()
411
+ click.echo(f"Data of table '{name}'")
412
+ click.echo(table_data)
413
+ click.echo()
414
+
415
+ # table_data is a polars DataFrame -> its rows are the full info.
416
+ render(ctx, data=lambda: table_data.to_dicts(), table=_table)
417
+
418
+ if file:
419
+ # Diagnostics to stderr so json/yaml stdout stays parseable.
420
+ click.echo(f"Saving data to file '{file}'", err=True)
421
+ table_data.write_ndjson(file)
422
+ click.echo("Data saved successfully", err=True)
423
+
424
+ except Exception as e:
425
+ hint_common_solutions(ctx, e)
426
+ raise click.ClickException(f"Failed to obtain table data: {e}")
427
+
428
+
429
+ @table.command()
430
+ @proj_option
431
+ @click.option(
432
+ "--name",
433
+ help="Name of the table.",
434
+ )
435
+ @click.option(
436
+ "--coll",
437
+ "-c",
438
+ help="Name of the collection to which the table belongs.",
439
+ )
440
+ @click.option(
441
+ "--at",
442
+ help=(
443
+ "If provided, the table values at the given time will be shown. Must be "
444
+ " either a valid timestamp in the form of a unix timestamp (milliseconds since "
445
+ "epoch without a dot, e.g. '1750074554472') or a valid date-time format. "
446
+ "The valid "
447
+ "formats are 'YYYY-MM-DD', 'YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:MM', "
448
+ "'YYYY-MM-DDTHH:MM:SS', and 'YYYY-MM-DDTHH:MM:SS.sss'. A 'Z' character can be "
449
+ "added at the end to indicate UTC time (e.g., '2023-10-01T12Z' or "
450
+ "'2023-10-01T12:00:00.000Z') or it can be omitted to indicate local "
451
+ "time (e.g., '2023-10-01T12' or '2023-10-01T12:00:00.000')."
452
+ ),
453
+ type=str,
454
+ cls=MutuallyExclusiveOption,
455
+ mutually_exclusive=["at-trx", "version"],
456
+ )
457
+ @click.option(
458
+ "--at-trx",
459
+ help=(
460
+ "ID of a transaction. If provided, the table values at the end of the given "
461
+ "transaction will be shown."
462
+ ),
463
+ type=str,
464
+ cls=MutuallyExclusiveOption,
465
+ mutually_exclusive=["at", "version"],
466
+ )
467
+ @click.option(
468
+ "--version",
469
+ help=(
470
+ "ID of a dataversion of the table. If provided, the table values at that "
471
+ "dataversion will be shown."
472
+ ),
473
+ type=str,
474
+ cls=MutuallyExclusiveOption,
475
+ mutually_exclusive=["at", "at-trx"],
476
+ )
477
+ @format_option
478
+ @click.pass_context
479
+ def schema(
480
+ ctx: click.Context,
481
+ coll: str,
482
+ name: str,
483
+ at: str,
484
+ at_trx: str,
485
+ version: str,
486
+ ):
487
+ """Show table schema"""
488
+ verify_login_or_prompt(ctx)
489
+ name = name or logical_prompt(ctx, "Name of the table")
490
+ coll = (
491
+ coll
492
+ or get_currently_pinned_object(ctx, "collection")
493
+ or logical_prompt(ctx, "Name of the collection to which the table belongs")
494
+ )
495
+ try:
496
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
497
+ table_schema = server.get_table_schema(
498
+ coll,
499
+ name,
500
+ at=at,
501
+ at_trx=at_trx,
502
+ version=version,
503
+ )
504
+
505
+ def _table():
506
+ table = Table(title="Schema")
507
+ table.add_column("Column", style="cyan", no_wrap=True)
508
+ table.add_column("Type")
509
+ for column in table_schema:
510
+ table.add_row(column["name"], column["type"])
511
+ click.echo()
512
+ make_console().print(table)
513
+ click.echo()
514
+
515
+ # table_schema is already the raw list of column dicts -> full info.
516
+ render(ctx, data=lambda: table_schema, table=_table)
517
+
518
+ except Exception as e:
519
+ hint_common_solutions(ctx, e)
520
+ raise click.ClickException(f"Failed to obtain table schema: {e}")
521
+
522
+
523
+ @table.command()
524
+ @proj_option
525
+ @click.option(
526
+ "--coll",
527
+ "-c",
528
+ help="Name of the collection to which the data belongs.",
529
+ )
530
+ @click.option(
531
+ "--name",
532
+ help="The name of the table to which the data belongs.",
533
+ )
534
+ @format_option
535
+ @max_result_option
536
+ @click.pass_context
537
+ def versions(ctx: click.Context, coll: str, name: str):
538
+ """
539
+ List all versions of the data of a table.
540
+ """
541
+ verify_login_or_prompt(ctx)
542
+ coll = (
543
+ coll
544
+ or get_currently_pinned_object(ctx, "collection")
545
+ or logical_prompt(ctx, "Name of the collection to which the data belongs")
546
+ )
547
+ name = name or logical_prompt(
548
+ ctx, "The name of the table to which the data belongs"
549
+ )
550
+ try:
551
+ server: TabsdataServer = ctx.obj["tabsdataserver"]
552
+ # /data-versions returns all statuses; the CLI lists committed only.
553
+ data_version_list, truncated = take(
554
+ ctx,
555
+ server.list_dataversions_generator(coll, name, filter="status:eq:C"),
556
+ )
557
+
558
+ def _table():
559
+ table = Table(title="Data versions")
560
+ table.add_column("Version", style="cyan", no_wrap=True)
561
+ table.add_column("Function")
562
+ table.add_column("Function version", no_wrap=True)
563
+ table.add_column("Execution plan ID", no_wrap=True)
564
+ table.add_column("Transaction ID", no_wrap=True)
565
+ table.add_column("Created on", no_wrap=True)
566
+ table.add_column("Schema change")
567
+ table.add_column("Data changed")
568
+ table.add_column("Rows")
569
+ table.add_column("Size (MB)")
570
+ table.add_column("Status")
571
+ for data_version in data_version_list:
572
+ data = data_version.kwargs
573
+ row_count = data.get("row_count")
574
+ table.add_row(
575
+ data_version.id,
576
+ data.get("function") or "-",
577
+ data.get("function_version_id") or "-",
578
+ data.get("execution_id") or "-",
579
+ data.get("transaction_id") or "-",
580
+ beautify_time(data_version.created_at_str),
581
+ "Yes" if data.get("schema_data_changed") else "No",
582
+ "Yes" if data.get("data_changed") else "No",
583
+ str(row_count) if row_count is not None else "-",
584
+ _size_mb(data.get("data_bytes")),
585
+ data_version.status,
586
+ )
587
+ click.echo()
588
+ make_console().print(table)
589
+ click.echo(f"Number of data versions: {len(data_version_list)}")
590
+
591
+ render(
592
+ ctx,
593
+ data=lambda: [dv._data for dv in data_version_list],
594
+ table=_table,
595
+ )
596
+ echo_more_results(ctx, truncated)
597
+
598
+ except Exception as e:
599
+ hint_common_solutions(ctx, e)
600
+ raise click.ClickException(f"Failed to list data versions: {e}")