datavo-cli 0.22.0__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.
- datavo_cli/__init__.py +3 -0
- datavo_cli/client_factory.py +172 -0
- datavo_cli/commands/__init__.py +36 -0
- datavo_cli/commands/auth.py +94 -0
- datavo_cli/commands/config.py +93 -0
- datavo_cli/commands/dataset.py +198 -0
- datavo_cli/commands/events.py +48 -0
- datavo_cli/commands/jobpool.py +50 -0
- datavo_cli/commands/sample.py +29 -0
- datavo_cli/commands/sample_store.py +97 -0
- datavo_cli/commands/source_import.py +38 -0
- datavo_cli/commands/split_set.py +79 -0
- datavo_cli/commands/stats.py +22 -0
- datavo_cli/commands/team.py +191 -0
- datavo_cli/commands/user.py +67 -0
- datavo_cli/config_store.py +200 -0
- datavo_cli/diagnostics.py +77 -0
- datavo_cli/errors.py +49 -0
- datavo_cli/main.py +90 -0
- datavo_cli/parsers.py +411 -0
- datavo_cli/render.py +390 -0
- datavo_cli/utils.py +55 -0
- datavo_cli-0.22.0.dist-info/METADATA +51 -0
- datavo_cli-0.22.0.dist-info/RECORD +28 -0
- datavo_cli-0.22.0.dist-info/WHEEL +5 -0
- datavo_cli-0.22.0.dist-info/entry_points.txt +2 -0
- datavo_cli-0.22.0.dist-info/licenses/LICENSE +201 -0
- datavo_cli-0.22.0.dist-info/top_level.txt +1 -0
datavo_cli/render.py
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from tabulate import tabulate
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _to_payload(value: Any) -> Any:
|
|
11
|
+
if isinstance(value, BaseModel):
|
|
12
|
+
return value.model_dump(mode="json", by_alias=True)
|
|
13
|
+
return value
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _compact_keys(keys: list[str] | None, limit: int = 4) -> str:
|
|
17
|
+
keys = keys or []
|
|
18
|
+
if len(keys) <= limit:
|
|
19
|
+
return ", ".join(keys)
|
|
20
|
+
return ", ".join(keys[:limit]) + f" (+{len(keys) - limit})"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _owner_cell(item: dict) -> str:
|
|
24
|
+
"""The owning team as a name, marking a personal team as such.
|
|
25
|
+
|
|
26
|
+
Prefers the name the server resolved and falls back to the id only when there is
|
|
27
|
+
none — an older server, or a person whose claims were never captured. The
|
|
28
|
+
``personal:`` prefix is never stripped to manufacture a name: the fragment behind it
|
|
29
|
+
is the identity provider's object id, which is exactly what this column existed to
|
|
30
|
+
stop printing.
|
|
31
|
+
"""
|
|
32
|
+
owner = item.get("owner_team_id")
|
|
33
|
+
if not owner:
|
|
34
|
+
return "-"
|
|
35
|
+
shown = item.get("owner_team_display_name") or owner
|
|
36
|
+
return f"{shown} (personal)" if str(owner).startswith("personal:") else str(shown)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def render_output(value: Any, *, output_format: str = "plain") -> None:
|
|
40
|
+
payload = _to_payload(value)
|
|
41
|
+
if output_format == "json":
|
|
42
|
+
print(json.dumps(payload, indent=2))
|
|
43
|
+
return
|
|
44
|
+
_render_plain(payload)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _render_plain(payload: Any) -> None:
|
|
48
|
+
if isinstance(payload, dict):
|
|
49
|
+
if "candidate_count" in payload and "exclusive_group_count" in payload:
|
|
50
|
+
print(f"dataset_name: {payload.get('dataset_name')}")
|
|
51
|
+
print(f"candidate_count: {payload.get('candidate_count')}")
|
|
52
|
+
if payload.get("total_hours") is not None:
|
|
53
|
+
print(f"total_hours: {payload.get('total_hours')}")
|
|
54
|
+
print(f"exclusive_group_count: {payload.get('exclusive_group_count')}")
|
|
55
|
+
rows = [
|
|
56
|
+
[
|
|
57
|
+
item.get("name"),
|
|
58
|
+
item.get("selector_kind"),
|
|
59
|
+
item.get("target_ratio"),
|
|
60
|
+
item.get("assigned_count"),
|
|
61
|
+
round(item["assigned_ratio"], 4) if item.get("assigned_ratio") is not None else None,
|
|
62
|
+
]
|
|
63
|
+
for item in payload.get("splits", [])
|
|
64
|
+
]
|
|
65
|
+
if rows:
|
|
66
|
+
print("splits:")
|
|
67
|
+
print(tabulate(rows, headers=["split", "selector", "target_ratio", "assigned", "assigned_ratio"]))
|
|
68
|
+
for summary in payload.get("rule_summaries", []):
|
|
69
|
+
print(f"rule: {summary}")
|
|
70
|
+
for warning in payload.get("warnings", []):
|
|
71
|
+
print(f"warning: {warning}")
|
|
72
|
+
return
|
|
73
|
+
if "parent_dataset" in payload and "definition" in payload:
|
|
74
|
+
print(f"name: {payload.get('name')}")
|
|
75
|
+
print(f"parent_dataset: {payload.get('parent_dataset')}")
|
|
76
|
+
print(f"lifecycle_state: {payload.get('lifecycle_state')}")
|
|
77
|
+
if payload.get("error_message"):
|
|
78
|
+
print(f"error_message: {payload.get('error_message')}")
|
|
79
|
+
rows = [
|
|
80
|
+
[
|
|
81
|
+
item.get("split_name"),
|
|
82
|
+
item.get("dataset_name"),
|
|
83
|
+
item.get("sample_count"),
|
|
84
|
+
item.get("shard_count"),
|
|
85
|
+
]
|
|
86
|
+
for item in payload.get("datasets", [])
|
|
87
|
+
]
|
|
88
|
+
if rows:
|
|
89
|
+
print("datasets:")
|
|
90
|
+
print(tabulate(rows, headers=["split", "dataset", "samples", "shards"]))
|
|
91
|
+
for summary in payload.get("rule_summaries", []):
|
|
92
|
+
print(f"rule: {summary}")
|
|
93
|
+
return
|
|
94
|
+
if "items" in payload and "dataset_name" in payload and "total" in payload:
|
|
95
|
+
print(f"dataset_name: {payload.get('dataset_name')}")
|
|
96
|
+
print(f"total: {payload.get('total')}")
|
|
97
|
+
rows = [
|
|
98
|
+
[
|
|
99
|
+
item.get("name"),
|
|
100
|
+
item.get("lifecycle_state"),
|
|
101
|
+
",".join(item.get("split_names", [])),
|
|
102
|
+
item.get("sample_count"),
|
|
103
|
+
_owner_cell(item),
|
|
104
|
+
item.get("sharing") or "-",
|
|
105
|
+
]
|
|
106
|
+
for item in payload["items"]
|
|
107
|
+
]
|
|
108
|
+
if rows:
|
|
109
|
+
print(
|
|
110
|
+
tabulate(
|
|
111
|
+
rows,
|
|
112
|
+
headers=["split_set", "state", "splits", "samples", "owner", "sharing"],
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
return
|
|
116
|
+
if (
|
|
117
|
+
"items" in payload
|
|
118
|
+
and "total" in payload
|
|
119
|
+
and "offset" in payload
|
|
120
|
+
and "dataset_name" not in payload
|
|
121
|
+
and "dataset" not in payload
|
|
122
|
+
):
|
|
123
|
+
# DatasetListResponse: items + total + paging, no parent dataset.
|
|
124
|
+
print(f"total: {payload.get('total')}")
|
|
125
|
+
rows = [
|
|
126
|
+
[
|
|
127
|
+
item.get("name"),
|
|
128
|
+
item.get("lifecycle_state"),
|
|
129
|
+
item.get("sample_store"),
|
|
130
|
+
item.get("sample_count"),
|
|
131
|
+
item.get("shard_count"),
|
|
132
|
+
_compact_keys(item.get("keys")),
|
|
133
|
+
_owner_cell(item),
|
|
134
|
+
item.get("sharing") or "-",
|
|
135
|
+
item.get("created_at"),
|
|
136
|
+
]
|
|
137
|
+
for item in payload["items"]
|
|
138
|
+
]
|
|
139
|
+
if rows:
|
|
140
|
+
print(
|
|
141
|
+
tabulate(
|
|
142
|
+
rows,
|
|
143
|
+
headers=[
|
|
144
|
+
"name", "state", "store", "samples", "shards", "keys",
|
|
145
|
+
"owner", "sharing", "created_at",
|
|
146
|
+
],
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
print("datasets: (none)")
|
|
151
|
+
return
|
|
152
|
+
if "operations" in payload and "sample_store" in payload:
|
|
153
|
+
# Store operation log (list_store_operations) or a single-op revert summary.
|
|
154
|
+
print(f"sample_store: {payload.get('sample_store')}")
|
|
155
|
+
rows = [
|
|
156
|
+
[
|
|
157
|
+
op.get("id"),
|
|
158
|
+
op.get("kind"),
|
|
159
|
+
op.get("state"),
|
|
160
|
+
op.get("operation_id"),
|
|
161
|
+
op.get("summary"),
|
|
162
|
+
op.get("created_at"),
|
|
163
|
+
]
|
|
164
|
+
for op in payload.get("operations", [])
|
|
165
|
+
]
|
|
166
|
+
if rows:
|
|
167
|
+
print(tabulate(rows, headers=["id", "kind", "state", "operation_id", "summary", "created_at"]))
|
|
168
|
+
else:
|
|
169
|
+
print("operations: (none)")
|
|
170
|
+
return
|
|
171
|
+
if "split_datasets" in payload:
|
|
172
|
+
if payload.get("parent_dataset") is not None:
|
|
173
|
+
print(f"parent_dataset: {payload.get('parent_dataset')}")
|
|
174
|
+
if payload.get("split_set"):
|
|
175
|
+
print(f"split_set: {payload.get('split_set')}")
|
|
176
|
+
rows = [
|
|
177
|
+
[
|
|
178
|
+
item.get("split_set"),
|
|
179
|
+
item.get("split_name"),
|
|
180
|
+
item.get("dataset_name"),
|
|
181
|
+
item.get("sample_count"),
|
|
182
|
+
item.get("shard_count"),
|
|
183
|
+
]
|
|
184
|
+
for item in payload.get("split_datasets", [])
|
|
185
|
+
]
|
|
186
|
+
if rows:
|
|
187
|
+
print(tabulate(rows, headers=["split_set", "split", "dataset", "samples", "shards"]))
|
|
188
|
+
else:
|
|
189
|
+
print("split_datasets: (none)")
|
|
190
|
+
return
|
|
191
|
+
if "sample_stores" in payload and isinstance(payload.get("sample_stores"), list):
|
|
192
|
+
rows = [
|
|
193
|
+
[
|
|
194
|
+
item.get("name"),
|
|
195
|
+
item.get("sample_count"),
|
|
196
|
+
item.get("catalog_visibility", "visible"),
|
|
197
|
+
_owner_cell(item),
|
|
198
|
+
item.get("sharing") or "-",
|
|
199
|
+
item.get("created_at"),
|
|
200
|
+
]
|
|
201
|
+
for item in payload["sample_stores"]
|
|
202
|
+
]
|
|
203
|
+
# `visibility` and `sharing` are different things and both are shown: the first
|
|
204
|
+
# is the catalog listing hint a store was created with, the second is who can
|
|
205
|
+
# actually read it. Collapsing them would be the drift this design avoids.
|
|
206
|
+
print(
|
|
207
|
+
tabulate(
|
|
208
|
+
rows,
|
|
209
|
+
headers=["name", "sample_count", "visibility", "owner", "sharing", "created_at"],
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
return
|
|
213
|
+
if "state" in payload and "coverage" in payload and "missing_sample_count" in payload:
|
|
214
|
+
print(f"sample_store: {payload.get('sample_store')}")
|
|
215
|
+
print(f"producer_id: {payload.get('producer_id')}")
|
|
216
|
+
print(f"state: {payload.get('state')}")
|
|
217
|
+
print(f"missing_sample_count: {payload.get('missing_sample_count')}")
|
|
218
|
+
if payload.get("workset"):
|
|
219
|
+
workset = payload["workset"]
|
|
220
|
+
print(f"workset_id: {workset.get('workset_id')}")
|
|
221
|
+
print(f"workset_samples: {workset.get('sample_count')}")
|
|
222
|
+
if payload.get("processing_token"):
|
|
223
|
+
token = payload["processing_token"]
|
|
224
|
+
print(f"processing_token_id: {token.get('token_id')}")
|
|
225
|
+
print(f"expires_at: {token.get('expires_at')}")
|
|
226
|
+
return
|
|
227
|
+
if "producers" in payload and "sample_store" in payload:
|
|
228
|
+
print(f"sample_store: {payload.get('sample_store')}")
|
|
229
|
+
rows = [
|
|
230
|
+
[
|
|
231
|
+
item.get("producer_id"),
|
|
232
|
+
item.get("producer_kind"),
|
|
233
|
+
item.get("maker"),
|
|
234
|
+
item.get("producer_run_system"),
|
|
235
|
+
item.get("producer_run_ref"),
|
|
236
|
+
item.get("checkpoint_artifact_id"),
|
|
237
|
+
",".join(item.get("produced_keys", [])),
|
|
238
|
+
]
|
|
239
|
+
for item in payload.get("producers", [])
|
|
240
|
+
]
|
|
241
|
+
if rows:
|
|
242
|
+
print(tabulate(rows, headers=["producer", "kind", "maker", "run_system", "run_ref", "checkpoint", "keys"]))
|
|
243
|
+
else:
|
|
244
|
+
print("producers: (none)")
|
|
245
|
+
return
|
|
246
|
+
if "producer_id" in payload and "produced_keys" in payload and "producer_kind" in payload:
|
|
247
|
+
print(f"sample_store: {payload.get('sample_store')}")
|
|
248
|
+
print(f"producer_id: {payload.get('producer_id')}")
|
|
249
|
+
print(f"producer_kind: {payload.get('producer_kind')}")
|
|
250
|
+
print(f"maker: {payload.get('maker')}")
|
|
251
|
+
if payload.get("producer_run_system") or payload.get("producer_run_ref"):
|
|
252
|
+
print(f"producer_run: {payload.get('producer_run_system')}:{payload.get('producer_run_ref')}")
|
|
253
|
+
if payload.get("checkpoint_artifact_id"):
|
|
254
|
+
print(f"checkpoint_artifact_id: {payload.get('checkpoint_artifact_id')}")
|
|
255
|
+
print(f"producer_fingerprint: {payload.get('producer_fingerprint')}")
|
|
256
|
+
print(f"produced_keys: {', '.join(payload.get('produced_keys', []))}")
|
|
257
|
+
return
|
|
258
|
+
if "keys" in payload and "name" in payload and "sample_count" in payload:
|
|
259
|
+
print(f"name: {payload.get('name')}")
|
|
260
|
+
print(f"sample_count: {payload.get('sample_count')}")
|
|
261
|
+
print(f"catalog_visibility: {payload.get('catalog_visibility', 'visible')}")
|
|
262
|
+
if payload.get("created_at") is not None:
|
|
263
|
+
print(f"created_at: {payload.get('created_at')}")
|
|
264
|
+
rows = [
|
|
265
|
+
[item.get("key"), item.get("sample_count")]
|
|
266
|
+
for item in payload["keys"]
|
|
267
|
+
]
|
|
268
|
+
if rows:
|
|
269
|
+
print("keys:")
|
|
270
|
+
print(tabulate(rows, headers=["key", "sample_count"]))
|
|
271
|
+
return
|
|
272
|
+
if "deleted_counts" in payload and "name" in payload:
|
|
273
|
+
print(f"name: {payload.get('name')}")
|
|
274
|
+
rows = [
|
|
275
|
+
[key, value]
|
|
276
|
+
for key, value in payload.get("deleted_counts", {}).items()
|
|
277
|
+
]
|
|
278
|
+
if rows:
|
|
279
|
+
print("deleted_counts:")
|
|
280
|
+
print(tabulate(rows, headers=["item", "count"]))
|
|
281
|
+
return
|
|
282
|
+
if "available_keys" in payload and "latest_revisions" in payload and "sample_id" in payload:
|
|
283
|
+
print(f"sample_id: {payload.get('sample_id')}")
|
|
284
|
+
print(f"sample_store: {payload.get('sample_store')}")
|
|
285
|
+
if payload.get("tags"):
|
|
286
|
+
print("tags:")
|
|
287
|
+
for key, value in sorted(payload["tags"].items()):
|
|
288
|
+
print(f" {key}: {value}")
|
|
289
|
+
if payload.get("available_keys"):
|
|
290
|
+
print("available_keys:")
|
|
291
|
+
for key in payload["available_keys"]:
|
|
292
|
+
print(f" - {key}")
|
|
293
|
+
rows = [
|
|
294
|
+
[
|
|
295
|
+
item.get("key"),
|
|
296
|
+
item.get("producer_id"),
|
|
297
|
+
item.get("revision_id"),
|
|
298
|
+
"yes" if item.get("is_root") else "",
|
|
299
|
+
item.get("ingested_at"),
|
|
300
|
+
]
|
|
301
|
+
for item in payload["latest_revisions"]
|
|
302
|
+
]
|
|
303
|
+
if rows:
|
|
304
|
+
print("latest_revisions:")
|
|
305
|
+
print(tabulate(rows, headers=["key", "producer", "revision", "root", "ingested_at"]))
|
|
306
|
+
return
|
|
307
|
+
if "shards" in payload and isinstance(payload.get("shards"), list):
|
|
308
|
+
summary = {
|
|
309
|
+
"dataset_name": payload.get("dataset_name"),
|
|
310
|
+
"lifecycle_state": payload.get("lifecycle_state"),
|
|
311
|
+
"sample_count": payload.get("sample_count"),
|
|
312
|
+
"shard_count": payload.get("shard_count"),
|
|
313
|
+
}
|
|
314
|
+
for key, value in summary.items():
|
|
315
|
+
if value is not None:
|
|
316
|
+
print(f"{key}: {value}")
|
|
317
|
+
if payload.get("scalars"):
|
|
318
|
+
print("scalars:")
|
|
319
|
+
for key, value in sorted(payload["scalars"].items()):
|
|
320
|
+
print(f" {key}: {value}")
|
|
321
|
+
rows = [
|
|
322
|
+
[
|
|
323
|
+
item.get("key_group"),
|
|
324
|
+
item.get("shard_index"),
|
|
325
|
+
item.get("content_hash"),
|
|
326
|
+
item.get("size_bytes"),
|
|
327
|
+
]
|
|
328
|
+
for item in payload["shards"]
|
|
329
|
+
]
|
|
330
|
+
if rows:
|
|
331
|
+
print(tabulate(rows, headers=["group", "shard", "hash", "size_bytes"]))
|
|
332
|
+
return
|
|
333
|
+
if "profiles" in payload and isinstance(payload.get("profiles"), list):
|
|
334
|
+
rows = [
|
|
335
|
+
[item.get("name"), item.get("api_base_url"), "yes" if item.get("active") else ""]
|
|
336
|
+
for item in payload["profiles"]
|
|
337
|
+
]
|
|
338
|
+
print(tabulate(rows, headers=["profile", "url", "active"]))
|
|
339
|
+
return
|
|
340
|
+
if "key_groups" in payload and isinstance(payload.get("key_groups"), list):
|
|
341
|
+
summary_keys = [
|
|
342
|
+
"name",
|
|
343
|
+
"sample_store",
|
|
344
|
+
"lifecycle_state",
|
|
345
|
+
"sample_count",
|
|
346
|
+
"shard_count",
|
|
347
|
+
"split_set",
|
|
348
|
+
"split_name",
|
|
349
|
+
]
|
|
350
|
+
for key in summary_keys:
|
|
351
|
+
value = payload.get(key)
|
|
352
|
+
if value is not None:
|
|
353
|
+
print(f"{key}: {value}")
|
|
354
|
+
if payload.get("scalars"):
|
|
355
|
+
print("scalars:")
|
|
356
|
+
for key, value in sorted(payload["scalars"].items()):
|
|
357
|
+
print(f" {key}: {value}")
|
|
358
|
+
rows = [
|
|
359
|
+
[item.get("name"), ",".join(item.get("keys", [])), item.get("manifest_ref")]
|
|
360
|
+
for item in payload["key_groups"]
|
|
361
|
+
]
|
|
362
|
+
if rows:
|
|
363
|
+
print(tabulate(rows, headers=["group", "keys", "manifest"]))
|
|
364
|
+
return
|
|
365
|
+
if "datasets" in payload and isinstance(payload.get("datasets"), list):
|
|
366
|
+
print(f"name: {payload.get('name')}")
|
|
367
|
+
if payload.get("sample_store") is not None:
|
|
368
|
+
print(f"sample_store: {payload.get('sample_store')}")
|
|
369
|
+
if payload.get("created_at") is not None:
|
|
370
|
+
print(f"created_at: {payload.get('created_at')}")
|
|
371
|
+
print("datasets:")
|
|
372
|
+
for name in payload["datasets"]:
|
|
373
|
+
print(f" - {name}")
|
|
374
|
+
return
|
|
375
|
+
for key, value in payload.items():
|
|
376
|
+
if isinstance(value, list) and value and all(isinstance(item, dict) for item in value):
|
|
377
|
+
print(f"{key}:")
|
|
378
|
+
print(tabulate(value, headers="keys"))
|
|
379
|
+
continue
|
|
380
|
+
if isinstance(value, dict):
|
|
381
|
+
print(f"{key}:")
|
|
382
|
+
for nested_key, nested_value in value.items():
|
|
383
|
+
print(f" {nested_key}: {nested_value}")
|
|
384
|
+
continue
|
|
385
|
+
print(f"{key}: {value}")
|
|
386
|
+
return
|
|
387
|
+
if isinstance(payload, list):
|
|
388
|
+
print(tabulate(payload, headers="keys"))
|
|
389
|
+
return
|
|
390
|
+
print(payload)
|
datavo_cli/utils.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, TypeVar
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
ModelT = TypeVar("ModelT", bound=BaseModel)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_key_value_pairs(values: list[str] | None) -> dict[str, str]:
|
|
14
|
+
parsed: dict[str, str] = {}
|
|
15
|
+
for value in values or []:
|
|
16
|
+
if "=" not in value:
|
|
17
|
+
raise RuntimeError(f"Expected KEY=VALUE, got: {value}")
|
|
18
|
+
key, raw_value = value.split("=", 1)
|
|
19
|
+
key = key.strip()
|
|
20
|
+
if not key:
|
|
21
|
+
raise RuntimeError(f"Expected KEY=VALUE, got: {value}")
|
|
22
|
+
parsed[key] = raw_value
|
|
23
|
+
return parsed
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_spec(path: str) -> Any:
|
|
27
|
+
if path == "-":
|
|
28
|
+
import sys
|
|
29
|
+
|
|
30
|
+
raw_text = sys.stdin.read()
|
|
31
|
+
else:
|
|
32
|
+
raw_text = Path(path).read_text(encoding="utf-8")
|
|
33
|
+
if not raw_text.strip():
|
|
34
|
+
raise RuntimeError("Spec input is empty.")
|
|
35
|
+
try:
|
|
36
|
+
return yaml.safe_load(raw_text)
|
|
37
|
+
except yaml.YAMLError as exc:
|
|
38
|
+
raise RuntimeError(f"Failed to parse spec: {exc}") from exc
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def load_model(path: str, model_type: type[ModelT]) -> ModelT:
|
|
42
|
+
payload = load_spec(path)
|
|
43
|
+
return model_type.model_validate(payload)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def write_json(path: str | Path, payload: Any) -> None:
|
|
47
|
+
path = Path(path)
|
|
48
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def model_payload(value: Any) -> Any:
|
|
53
|
+
if isinstance(value, BaseModel):
|
|
54
|
+
return value.model_dump(mode="json", by_alias=True)
|
|
55
|
+
return value
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datavo-cli
|
|
3
|
+
Version: 0.22.0
|
|
4
|
+
Summary: Datavo command line interface
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Documentation, https://altavo.github.io/datavo/
|
|
7
|
+
Project-URL: Homepage, https://altavo.github.io/datavo/
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: argcomplete>=3.4
|
|
15
|
+
Requires-Dist: datavo-sdk[entra]==0.22.0
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Requires-Dist: PyYAML>=6.0
|
|
18
|
+
Requires-Dist: tabulate>=0.9
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# datavo-cli
|
|
22
|
+
|
|
23
|
+
Command-line interface for **[Datavo](https://altavo.github.io/datavo/)**, the
|
|
24
|
+
dataset store. Installs the `datavo` command and the `datavo_sdk` Python client
|
|
25
|
+
together, with Microsoft Entra sign-in.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install datavo-cli
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
datavo auth login # sign in (defaults to https://prod.datavo.io)
|
|
37
|
+
datavo sample-store list # first authorized round-trip
|
|
38
|
+
datavo dataset list # browse datasets (--query, --key to filter)
|
|
39
|
+
datavo dataset pull voice_train_v1 --output-dir ./data # pull a dataset to disk
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Point at another instance with a named profile:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
datavo config set dev --url https://dev.datavo.io
|
|
46
|
+
datavo config use dev
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Documentation
|
|
50
|
+
|
|
51
|
+
Full guide, reference, and runnable examples: **https://altavo.github.io/datavo/**
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
datavo_cli/__init__.py,sha256=3ZANNzjxXIsBXP5B3WP0-uZNGqY7pm32g1LLQDB_AqE,50
|
|
2
|
+
datavo_cli/client_factory.py,sha256=KGc1mmSR8oAYOW55x-S_9EENSVUHV1u12tFBwziP0fA,6257
|
|
3
|
+
datavo_cli/config_store.py,sha256=VI3_M1PS9T3s80DQUx3l4lZ1d0CZfTRSbjSoOCaOiFU,7731
|
|
4
|
+
datavo_cli/diagnostics.py,sha256=_XlXqAPHhUPcVVCr9wJo-jOaAnB8yIBPxcglQHQRw6s,2744
|
|
5
|
+
datavo_cli/errors.py,sha256=qBVQvHSnYSvdG7DEYd6pU3nnvNSI0nAQWvZgHB5vhAQ,1719
|
|
6
|
+
datavo_cli/main.py,sha256=fNFyeBbtHWhvP6p4pdlsk11SsYdD-JJT6rcgBjkZ3oo,2893
|
|
7
|
+
datavo_cli/parsers.py,sha256=pvM6HZ6IczySQI8R8ahH1CTV3n5AdU6W3C7dINZMPlU,24324
|
|
8
|
+
datavo_cli/render.py,sha256=IXERl2KzR3jdGDYitd8CGZeJN9A-zGyv-g0iptLgjoE,16808
|
|
9
|
+
datavo_cli/utils.py,sha256=J87pMyoQLf5u3DjekCoiF-pce5e-hqc71vEkqiCMF1w,1541
|
|
10
|
+
datavo_cli/commands/__init__.py,sha256=49KcaQcSN_6W5EoLnI9BTHabEo6ZqeJ4VgMyagIEeAQ,1159
|
|
11
|
+
datavo_cli/commands/auth.py,sha256=j6R_pqCFpMagH1bSZRGUUd3SbFwyuGCtdIqxKiSj760,3457
|
|
12
|
+
datavo_cli/commands/config.py,sha256=rzi9V0vF04uj-qHMbCplAJR_jbH1W3nyUsOuO-BkdvE,3693
|
|
13
|
+
datavo_cli/commands/dataset.py,sha256=LoZhvNMuH5syPt9Qw5MzYRgQlBKC2_eh0vrzMqLKibw,8816
|
|
14
|
+
datavo_cli/commands/events.py,sha256=dErqXCPoGGSQE1n6_YU-AkyfCnZ2uHQEaHaFsaHh4r0,1735
|
|
15
|
+
datavo_cli/commands/jobpool.py,sha256=4HIgL2wf3Q0vCXl3Gf0t2O8yY8DsVHTZjdGPO_5tMCI,1490
|
|
16
|
+
datavo_cli/commands/sample.py,sha256=veEQqBHBbD2rELWpqNX33EoazgcCxSHxa5CAtVkTBNE,986
|
|
17
|
+
datavo_cli/commands/sample_store.py,sha256=7bYtouhmKAsE6_CdyxdAAKnhEq2CS0HKz2XS1xlatjg,4147
|
|
18
|
+
datavo_cli/commands/source_import.py,sha256=VA_nz41m3RwxGNzkS1p-22MV-lIZ8B8rtWkhJvX9TSk,1916
|
|
19
|
+
datavo_cli/commands/split_set.py,sha256=QKRSFp6it5auQH3Ufd1Y6kIktUYeZK1bEsdvhO5D7Fo,3482
|
|
20
|
+
datavo_cli/commands/stats.py,sha256=AiUQtdGgFa0O9xjMxMhcCQpJaJWEHASr-fi3vNPJTmM,716
|
|
21
|
+
datavo_cli/commands/team.py,sha256=KXWsnPKH2A9WjkYb9qfbSMUMacsbgUdm7p4ZaEj1Kac,7853
|
|
22
|
+
datavo_cli/commands/user.py,sha256=4V7aEdDiDVoeWXAlPuFc-v_aJvWnx4UWYMe70OS-m9Q,2701
|
|
23
|
+
datavo_cli-0.22.0.dist-info/licenses/LICENSE,sha256=5-AUJrHm4TQmwicX7lFZfJu0i7oDs9vvMhDmrenJFeI,11341
|
|
24
|
+
datavo_cli-0.22.0.dist-info/METADATA,sha256=mjpQX8duSlpvpi921B1JvdOHMZnvEseLPOkH9_gFEtk,1541
|
|
25
|
+
datavo_cli-0.22.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
26
|
+
datavo_cli-0.22.0.dist-info/entry_points.txt,sha256=0M-1L1oSSP5B3CyNwmBgulK8X7yrtW19k0wErk7erDQ,48
|
|
27
|
+
datavo_cli-0.22.0.dist-info/top_level.txt,sha256=YTkid9eYLDrFLGj-zDz6fIRREPAm8Vs1VS1YyfnO6uk,11
|
|
28
|
+
datavo_cli-0.22.0.dist-info/RECORD,,
|