dexverse-cli 0.0.2__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.
@@ -0,0 +1,211 @@
1
+ """Mock backend (doc §9) — simulates the platform so the CLI/SDK can run the
2
+ real flow without a production server.
3
+
4
+ GET /api/download-tasks/{task_id}/manifest (doc §9.3, §12.2)
5
+ POST /api/dev/download-tasks (doc §8.3 — create from files)
6
+
7
+ Env vars (doc §9.2):
8
+
9
+ DEXVERSE_DEV_TOKEN default "dev-token"
10
+ DEXVERSE_S3_ENDPOINT
11
+ DEXVERSE_S3_REGION default "us-east-1"
12
+ DEXVERSE_S3_ACCESS_KEY
13
+ DEXVERSE_S3_SECRET_KEY
14
+ DEXVERSE_S3_SESSION_TOKEN optional
15
+ DEXVERSE_MOCK_TASKS path to tasks json (default ./mock_tasks.json)
16
+
17
+ Run:
18
+
19
+ dexverse-mock-server --port 18080
20
+ # (binds 127.0.0.1 by default — dev endpoints must not be exposed publicly,
21
+ # doc §13.1 item 4. Use --host 0.0.0.0 only inside a trusted intranet.)
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import os
27
+ import threading
28
+ from datetime import datetime, timedelta, timezone
29
+ from pathlib import Path
30
+ from typing import Any, Dict, List, Optional
31
+
32
+ from fastapi import FastAPI, Header, HTTPException
33
+ from pydantic import BaseModel
34
+
35
+ TASKS_PATH = Path(os.getenv("DEXVERSE_MOCK_TASKS", str(Path(__file__).parent / "mock_tasks.json")))
36
+
37
+ _lock = threading.Lock()
38
+ _tasks: Dict[str, Dict[str, Any]] = {}
39
+
40
+
41
+ def _load_tasks() -> Dict[str, Dict[str, Any]]:
42
+ global _tasks
43
+ if not _tasks:
44
+ if TASKS_PATH.exists():
45
+ _tasks = json.loads(TASKS_PATH.read_text(encoding="utf-8"))
46
+ else:
47
+ _tasks = {}
48
+ return _tasks
49
+
50
+
51
+ def _check_auth(authorization: Optional[str]) -> None:
52
+ expected = os.getenv("DEXVERSE_DEV_TOKEN", "dev-token")
53
+ if not authorization:
54
+ raise HTTPException(status_code=401, detail="Missing Authorization header")
55
+ if authorization != f"Bearer {expected}":
56
+ raise HTTPException(status_code=403, detail="Invalid token")
57
+
58
+
59
+ def _truthy(name: str) -> bool:
60
+ return os.getenv(name, "").lower() in ("1", "true", "yes", "on")
61
+
62
+
63
+ def _presign_client():
64
+ """A boto3 client for real presigning, or None if not configured."""
65
+ if not _truthy("DEXVERSE_MOCK_PRESIGN"):
66
+ return None
67
+ import boto3
68
+ from botocore.config import Config
69
+ endpoint = os.getenv("DEXVERSE_S3_ENDPOINT")
70
+ ak = os.getenv("DEXVERSE_S3_ACCESS_KEY")
71
+ sk = os.getenv("DEXVERSE_S3_SECRET_KEY")
72
+ if not (endpoint and ak and sk):
73
+ return None
74
+ return boto3.client(
75
+ "s3", endpoint_url=endpoint,
76
+ region_name=os.getenv("DEXVERSE_S3_REGION", "us-east-1"),
77
+ aws_access_key_id=ak, aws_secret_access_key=sk,
78
+ config=Config(signature_version="s3v4"),
79
+ )
80
+
81
+
82
+ def _build_manifest(task_id: str, task: Dict[str, Any]) -> Dict[str, Any]:
83
+ endpoint = os.getenv("DEXVERSE_S3_ENDPOINT", "https://minio.dexverse.com")
84
+ region = os.getenv("DEXVERSE_S3_REGION", "us-east-1")
85
+ presign_expires = int(os.getenv("DEXVERSE_MOCK_PRESIGN_EXPIRES", "3600"))
86
+ presigner = _presign_client()
87
+ expires_at = datetime.now(timezone.utc) + timedelta(hours=12)
88
+
89
+ files: List[Dict[str, Any]] = []
90
+ for item in task.get("files", []):
91
+ bucket = item["bucket"]
92
+ object_key = item["objectKey"]
93
+ relative = item.get("relativePath") or item.get("originalPath") or object_key
94
+ entry = {
95
+ "fileId": item.get("fileId", object_key),
96
+ "uri": f"s3://{bucket}/{object_key}",
97
+ "bucket": bucket,
98
+ "objectKey": object_key,
99
+ "relativePath": relative,
100
+ "size": item.get("size"),
101
+ "sha256": item.get("sha256", ""),
102
+ }
103
+ # presignedUrl: explicit per-file value, or generated via boto3 if presign is on
104
+ if item.get("presignedUrl"):
105
+ entry["presignedUrl"] = item["presignedUrl"]
106
+ elif presigner is not None:
107
+ entry["presignedUrl"] = presigner.generate_presigned_url(
108
+ "get_object",
109
+ Params={"Bucket": bucket, "Key": object_key},
110
+ ExpiresIn=presign_expires,
111
+ )
112
+ files.append(entry)
113
+
114
+ return {
115
+ "manifestVersion": "1.0",
116
+ "taskId": task_id,
117
+ "createdAt": datetime.now(timezone.utc).isoformat(),
118
+ "expiresAt": expires_at.isoformat(),
119
+ "storage": {
120
+ "type": "s3",
121
+ "endpoint": endpoint,
122
+ "region": region,
123
+ "addressingStyle": "path",
124
+ "credentials": {
125
+ "type": "dev",
126
+ "accessKey": os.getenv("DEXVERSE_S3_ACCESS_KEY"),
127
+ "secretKey": os.getenv("DEXVERSE_S3_SECRET_KEY"),
128
+ "sessionToken": os.getenv("DEXVERSE_S3_SESSION_TOKEN", ""),
129
+ "expiresAt": expires_at.isoformat(),
130
+ },
131
+ },
132
+ "download": {
133
+ "defaultParallel": 16,
134
+ "verify": "size",
135
+ "preserveOriginalPath": True,
136
+ },
137
+ "files": files,
138
+ }
139
+
140
+
141
+ app = FastAPI(title="DexVerse Mock Backend")
142
+
143
+
144
+ @app.get("/api/download-tasks/{task_id}/manifest")
145
+ def get_manifest(task_id: str, authorization: Optional[str] = Header(default=None)) -> Dict[str, Any]:
146
+ _check_auth(authorization)
147
+ with _lock:
148
+ tasks = _load_tasks()
149
+ if task_id not in tasks:
150
+ raise HTTPException(status_code=404, detail="Task not found")
151
+ return _build_manifest(task_id, tasks[task_id])
152
+
153
+
154
+ class DevFile(BaseModel):
155
+ fileId: Optional[str] = None
156
+ bucket: str
157
+ objectKey: str
158
+ relativePath: Optional[str] = None
159
+ size: Optional[int] = None
160
+ sha256: Optional[str] = None
161
+ presignedUrl: Optional[str] = None
162
+
163
+
164
+ class DevTaskRequest(BaseModel):
165
+ files: List[DevFile]
166
+
167
+
168
+ @app.post("/api/dev/download-tasks")
169
+ def create_dev_task(
170
+ req: DevTaskRequest,
171
+ authorization: Optional[str] = Header(default=None),
172
+ ) -> Dict[str, Any]:
173
+ _check_auth(authorization)
174
+ task_id = f"dl_dev_{datetime.now(timezone.utc).strftime('%y%m%d%H%M%S')}"
175
+ with _lock:
176
+ tasks = _load_tasks()
177
+ tasks[task_id] = {
178
+ "files": [f.model_dump(exclude_none=True) for f in req.files],
179
+ }
180
+ return {
181
+ "taskId": task_id,
182
+ "commandLinux": f"dexverse-dl download --task {task_id} --out /data/dexverse",
183
+ "commandWindows": f"dexverse-dl download --task {task_id} --out D:\\DexVerse",
184
+ }
185
+
186
+
187
+ @app.get("/healthz")
188
+ def healthz() -> Dict[str, str]:
189
+ return {"status": "ok"}
190
+
191
+
192
+ def build_parser():
193
+ import argparse
194
+
195
+ parser = argparse.ArgumentParser(description="DexVerse mock backend")
196
+ parser.add_argument("--host", default="127.0.0.1",
197
+ help="bind host (default loopback; doc §13.1 item 4)")
198
+ parser.add_argument("--port", type=int, default=18080)
199
+ return parser
200
+
201
+
202
+ def main(argv=None) -> None:
203
+ args = build_parser().parse_args(argv)
204
+
205
+ import uvicorn
206
+
207
+ uvicorn.run(app, host=args.host, port=args.port)
208
+
209
+
210
+ if __name__ == "__main__":
211
+ main()
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: dexverse-cli
3
+ Version: 0.0.2
4
+ Summary: DexVerse dataset downloader for S3-compatible robot training datasets
5
+ Author: DexVerse
6
+ License: Apache-2.0
7
+ Keywords: dataset,dexverse,downloader,minio,robotics,s3,s5cmd
8
+ Requires-Python: >=3.9
9
+ Requires-Dist: av>=12
10
+ Requires-Dist: h5py>=3.10
11
+ Requires-Dist: httpx>=0.27
12
+ Requires-Dist: numpy>=1.26
13
+ Requires-Dist: pillow>=10
14
+ Requires-Dist: pydantic>=2.0
15
+ Requires-Dist: rich>=13.0
16
+ Requires-Dist: tomlkit>=0.12
17
+ Requires-Dist: typer>=0.12
18
+ Provides-Extra: dev
19
+ Requires-Dist: boto3>=1.34; extra == 'dev'
20
+ Requires-Dist: fastapi>=0.110; extra == 'dev'
21
+ Requires-Dist: httpx>=0.27; extra == 'dev'
22
+ Requires-Dist: moto[s3]>=5.0; extra == 'dev'
23
+ Requires-Dist: pytest>=8.0; extra == 'dev'
24
+ Provides-Extra: mock
25
+ Requires-Dist: fastapi>=0.110; extra == 'mock'
26
+ Requires-Dist: uvicorn>=0.27; extra == 'mock'
27
+ Provides-Extra: s3
28
+ Requires-Dist: boto3>=1.34; extra == 's3'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # DexVerse Downloader (`dexverse`)
32
+
33
+ Presigned-URL batch downloader for robot-training datasets.
34
+
35
+ ```text
36
+ manifest -> dexverse-dl -> httpx -> presigned URLs -> local file landing
37
+ ```
38
+
39
+ The primary input is the DexVerse backend's raw presigned-URL JSON. For those
40
+ payloads, `storage_path` is the remote object key and `file_name` is the local
41
+ landing path under `--out`.
42
+
43
+ Canonical manifests are still supported. Their local paths come from
44
+ `relativePath`, falling back to `originalPath`, then `objectKey`:
45
+
46
+ ```text
47
+ localPath = out / (file.relativePath || file.originalPath || file.objectKey)
48
+ ```
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install "dexverse[s3,mock]" # boto3 + fastapi mock server
54
+ # optional legacy S3 backend:
55
+ # install s5cmd (https://github.com/peak/s5cmd) and put it on PATH
56
+ ```
57
+
58
+ Development:
59
+
60
+ ```bash
61
+ uv venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
62
+ uv pip install -e ".[dev]"
63
+ ```
64
+
65
+ ## Quick Start
66
+
67
+ If your backend issues short-lived presigned URLs per file, save that response
68
+ as `download.json` and run:
69
+
70
+ ```bash
71
+ dexverse-dl download --manifest ./download.json --out D:\DexVerse
72
+ ```
73
+
74
+ No login, no `AWS_*` credentials, no s5cmd, no backend flag, and no TLS flag are
75
+ required for the common internal/self-signed endpoint case. The default backend
76
+ is `http`, and TLS verification is disabled by default for http/presigned
77
+ downloads. Use `--secure` when the endpoint has a valid certificate and you want
78
+ certificate validation.
79
+
80
+ Raw backend output is accepted directly:
81
+
82
+ ```json
83
+ [{
84
+ "download_url": "https://host/bucket/datasets/.../20260429_180359.hdf5?X-Amz-Signature=...",
85
+ "storage_bucket": "dexverse",
86
+ "storage_path": "datasets/.../20260429_180359.hdf5",
87
+ "file_name": "20260429_180359.hdf5",
88
+ "file_size": 14994636,
89
+ "task_instruction": {
90
+ "aaa": "111",
91
+ "aaaa": 222,
92
+ "ewrwrwe": 1.1111111111111112e+54
93
+ }
94
+ }]
95
+ ```
96
+
97
+ Result:
98
+
99
+ ```text
100
+ D:\DexVerse\20260429_180359.hdf5
101
+ ```
102
+
103
+ `task_instruction` is optional and must be a JSON object. If multiple manifest
104
+ items resolve to the same local path (for example, repeated `file_name`
105
+ values), the first item wins and later duplicates are ignored.
106
+
107
+ Include `file_size` (and preferably `sha256`) for large files. The HTTP backend
108
+ also learns the total size from `Content-Length`/`Content-Range`, but manifest
109
+ metadata lets the CLI verify an existing local file before making a request.
110
+
111
+ Transient timeouts and HTTP 408/425/429/5xx responses are retried five times by
112
+ default. Bytes already received stay in a sibling `<file>.part`; retries and
113
+ later CLI runs request the remaining bytes with HTTP Range, while the final
114
+ destination path only appears after the size check succeeds. The object server
115
+ must support Range requests for efficient resume.
116
+
117
+ Presigned URLs expire, often after 1 hour. Have the backend regenerate the
118
+ manifest right before each download; expired URLs surface as per-file `HTTP 403`
119
+ errors. After generating a fresh `download.json`, run the same command with the
120
+ same `--out` path to continue from the retained `.part`.
121
+
122
+ ## CLI
123
+
124
+ ```bash
125
+ dexverse-dl login --server https://dexverse.xxx.com [--token TOKEN]
126
+ dexverse-dl download --manifest ./download.json --out /data/dexverse [--parallel 16] [--secure] [--backend http|python|s5cmd]
127
+ dexverse-dl download --manifest ./download.json --out /data/dexverse [--http-read-timeout 120] [--http-retries 5]
128
+ dexverse-dl download --task dl_xxx --out /data/dexverse [--server URL]
129
+ dexverse-dl verify --manifest PATH --out /data/dexverse [--deep]
130
+ dexverse-dl repair --manifest PATH --out /data/dexverse [--backend http|python|s5cmd]
131
+ ```
132
+
133
+ ### 还原 HDF5 → 可训练数据
134
+
135
+ 下载完成后,把落盘的 `.hdf5` 还原为原始录制结构(逐帧 JPEG + `pose_record_*.json`
136
+ + `metadata.jsonl`)。如果对应 manifest 带有 `task_instruction`,还会自动生成
137
+ `task_instruction.json`:
138
+
139
+ ```bash
140
+ # 单文件
141
+ dexverse-dl convert -i /data/dexverse/20260429_180359.hdf5 -o /data/dexverse/restored
142
+ # 批量(递归处理目录下所有 *.hdf5,保持目录结构)
143
+ dexverse-dl convert --input-dir /data/dexverse -o /data/dexverse/restored
144
+ ```
145
+
146
+ 还原输出形如:
147
+
148
+ ```text
149
+ /data/dexverse/restored/20260429_180359/
150
+ head/left/000000.jpg ...
151
+ head/right/000000.jpg ...
152
+ hand/left/000000.jpg ...
153
+ hand/right/000000.jpg ...
154
+ pose_record_20260429_180359.json
155
+ metadata.jsonl
156
+ task_instruction.json
157
+ ```
158
+
159
+ `task_instruction.json` 的内容就是 manifest 中的对象本身,不增加外层包装。
160
+ 下载器会把转换所需的映射保存在 `.dexverse/task-instructions/`,无需为
161
+ `convert` 增加额外参数。独立来源且没有该隐藏元数据的 HDF5 仍按原行为还原。
162
+
163
+ 该命令依赖 `h5py` / `numpy` / `av` / `pillow`,已列入核心依赖,安装时随包装上。
164
+
165
+ ## Python SDK
166
+
167
+ ```python
168
+ from dexverse import DexVerseClient, download_manifest
169
+
170
+ client = DexVerseClient(server="https://dexverse.xxx.com", token="xxx")
171
+ local_root = client.download_task(task_id="dl_xxx", out="/data/dexverse", parallel=16)
172
+
173
+ # or from a local manifest file
174
+ download_manifest(manifest_path="./download.json", out="/data/dexverse")
175
+ ```
176
+
177
+ ## Layout
178
+
179
+ State files live under `<out>/.dexverse/` and never mix into the dataset tree:
180
+
181
+ ```text
182
+ /data/dexverse/
183
+ .dexverse/task-instructions/
184
+ 20260429_180359.hdf5.json
185
+ .dexverse/tasks/<taskId>/
186
+ manifest.json
187
+ download-state.json
188
+ download.log
189
+ 20260429_180359.hdf5
190
+ ```
191
+
192
+ ## Backends
193
+
194
+ | backend | engine | notes |
195
+ |----------|-------------------|-----------------------------------------|
196
+ | `http` | httpx | default; presigned URLs, no creds/login |
197
+ | `python` | boto3 | legacy S3 fallback |
198
+ | `s5cmd` | peak/s5cmd binary | legacy S3 high-throughput backend |
@@ -0,0 +1,23 @@
1
+ dexverse/__init__.py,sha256=Hf6AqC9YsxJFDCK2BvPpnJ4eKarOPHRb7I6V_n3CzFQ,1365
2
+ dexverse/cli.py,sha256=hWGYEB-tENPXw_CtxJzTJDVwmY7dNHdcmlv30JYd-yw,16802
3
+ dexverse/client.py,sha256=OCchd_x0T20TcegeRIns0SEl_5yb7AQ0POf1D1O3jjc,5073
4
+ dexverse/config.py,sha256=555RAAa5Nyj6E7Ym2m9sU57ndQJU8lanBsJROfTapsU,2368
5
+ dexverse/manifest.py,sha256=uw9V5_L5zf5b8vVa9ihpDmZPYVHepwJkAwiZdjPYz2E,11652
6
+ dexverse/convert/__init__.py,sha256=1O6XCtMzX6ITgXcAQWCntkaJieO2cDwuZzRF7wIDL0g,1001
7
+ dexverse/convert/hdf5_to_raw.py,sha256=GlHGA5C_i5f-hR9B8H_CKCcRfqSm2S20taDq9iXtf0c,19135
8
+ dexverse/convert/image_video_encode.py,sha256=KnYybo3F076HtMUj-3YuwP9kmQED5YBj5DiUxkiOvR4,14283
9
+ dexverse/downloader/__init__.py,sha256=66ATqgVaCd9VDKX7hs_4Bfi_yqLQd3fZjCaoZBuY9Po,723
10
+ dexverse/downloader/base.py,sha256=InH6n264HtmlpArPrlK-F3o2Crd8RDT0KVDPj3a-5pg,3004
11
+ dexverse/downloader/http_backend.py,sha256=ML2Cik4NSgiZQAcuwV1j9nGlthN8sNfekh7PowIczZ0,18666
12
+ dexverse/downloader/manager.py,sha256=ptGWx_f6Ay51FDTodY-jGivWebUazylRtB6VW_eg5k0,14293
13
+ dexverse/downloader/python_s3_backend.py,sha256=-LpvhLn6mer93UP80ugwh21aiKL8r1CWWXPmqwIphaY,5353
14
+ dexverse/downloader/s5cmd_backend.py,sha256=KP28uNRW3DLFGqyEvPlrhh0b8Irm4mCWF5v7sfGKlqs,5897
15
+ dexverse/downloader/state.py,sha256=1iMknv7-SdsAg3rj5_CENKPZACUWv4fS1IPm2Pbiotw,8874
16
+ dexverse/downloader/verify.py,sha256=6M8hZJoUov0Qbpr03B9jUfjObjLqMgE-u_YjWIvqpow,3337
17
+ dexverse/mock/__init__.py,sha256=PfJ4z1g0RHJMeTOAssfiHOIltaktsPIp5J-5pu3EkhA,89
18
+ dexverse/mock/mock_tasks.json,sha256=EyHnM1LOXG5JuP0SYI39R-52LUcXSKx4h34DySg5Iy4,618
19
+ dexverse/mock/server.py,sha256=JLiEEqt8r3DdEbASLn4KIcMQDB1vyBDqezHer3qHBLc,6862
20
+ dexverse_cli-0.0.2.dist-info/METADATA,sha256=0SORmrrWQ07XNPX-Gi-ilU2iiiETAJhPEZPcJy0LLwE,6744
21
+ dexverse_cli-0.0.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
22
+ dexverse_cli-0.0.2.dist-info/entry_points.txt,sha256=OYxj67T5AD2oVdDxcEOEkSPgy371GA7kWFTMtm74nyw,98
23
+ dexverse_cli-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ dexverse-dl = dexverse.cli:app
3
+ dexverse-mock-server = dexverse.mock.server:main