dexverse-cli 0.1.1__tar.gz → 0.1.3__tar.gz

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 (21) hide show
  1. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/PKG-INFO +10 -1
  2. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/pyproject.toml +1 -1
  3. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/README.md +9 -0
  4. dexverse_cli-0.1.3/src/dexverse_cli/__init__.py +1 -0
  5. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/download.py +8 -14
  6. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/manifest.py +34 -10
  7. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/uv.lock +1 -1
  8. dexverse_cli-0.1.1/src/dexverse_cli/__init__.py +0 -1
  9. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/.gitignore +0 -0
  10. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/.python-version +0 -0
  11. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/alignment.py +0 -0
  12. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/cli.py +0 -0
  13. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/hdf5_schema.py +0 -0
  14. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/http.py +0 -0
  15. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/jetson_encoder.py +0 -0
  16. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/pack.py +0 -0
  17. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/raw_schema.py +0 -0
  18. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/state.py +0 -0
  19. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/transaction.py +0 -0
  20. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/unpack.py +0 -0
  21. {dexverse_cli-0.1.1 → dexverse_cli-0.1.3}/src/dexverse_cli/unversioned_hdf5.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dexverse-cli
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Download, pack, and unpack DexVerse datasets
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: av<17,>=14
@@ -49,6 +49,15 @@ corresponding HDF5 file is unpacked; only top-level `data_protocol` is removed
49
49
  from that algorithm-facing projection.
50
50
 
51
51
  Downloads use bounded parallelism, retries, and compatible partial-file resume.
52
+ Within a manifest, the first occurrence of a target path keeps its name; later
53
+ occurrences receive `-1`, `-2`, etc. before the final extension (for example,
54
+ `episode1.hdf5`, `episode1-1.hdf5`, `episode1-2.hdf5`). Numbering skips names and
55
+ paths already claimed by other entries, including partial files and metadata.
56
+ Different relative directories remain independent. Names are assigned in manifest
57
+ order, with Windows case-insensitive matching, before any downloads start.
58
+ Rerunning the same manifest preserves the assigned names for skipping and resume;
59
+ existing files on disk do not trigger new suffixes. This is not cross-batch name
60
+ allocation: changing the manifest entries or their order can change the mapping.
52
61
  When `sha256` is present it is always checked before the final file is replaced.
53
62
  TLS certificate verification is disabled by default for internal endpoints;
54
63
  pass `--secure` to enable certificate verification.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dexverse-cli"
7
- version = "0.1.1"
7
+ version = "0.1.3"
8
8
  description = "Download, pack, and unpack DexVerse datasets"
9
9
  readme = "src/dexverse_cli/README.md"
10
10
  requires-python = ">=3.10"
@@ -33,6 +33,15 @@ corresponding HDF5 file is unpacked; only top-level `data_protocol` is removed
33
33
  from that algorithm-facing projection.
34
34
 
35
35
  Downloads use bounded parallelism, retries, and compatible partial-file resume.
36
+ Within a manifest, the first occurrence of a target path keeps its name; later
37
+ occurrences receive `-1`, `-2`, etc. before the final extension (for example,
38
+ `episode1.hdf5`, `episode1-1.hdf5`, `episode1-2.hdf5`). Numbering skips names and
39
+ paths already claimed by other entries, including partial files and metadata.
40
+ Different relative directories remain independent. Names are assigned in manifest
41
+ order, with Windows case-insensitive matching, before any downloads start.
42
+ Rerunning the same manifest preserves the assigned names for skipping and resume;
43
+ existing files on disk do not trigger new suffixes. This is not cross-batch name
44
+ allocation: changing the manifest entries or their order can change the mapping.
36
45
  When `sha256` is present it is always checked before the final file is replaced.
37
46
  TLS certificate verification is disabled by default for internal endpoints;
38
47
  pass `--secure` to enable certificate verification.
@@ -0,0 +1 @@
1
+ __version__ = "0.1.3"
@@ -74,7 +74,6 @@ class OutputRootPlan:
74
74
  ):
75
75
  raise UnsafeOutputPathError("unsafe output path")
76
76
  check_claim(path, self.root)
77
- reject_existing_aliases(self.claims)
78
77
 
79
78
 
80
79
  def safe_lstat(path: Path) -> os.stat_result | None:
@@ -140,24 +139,19 @@ def check_claim(path: Path, root: Path) -> None:
140
139
 
141
140
  def reject_existing_aliases(claims: tuple[Path, ...]) -> None:
142
141
  resolved: set[str] = set()
143
- existing: list[Path] = []
142
+ identities: set[tuple[int, int]] = set()
144
143
  for claim in claims:
145
144
  key = os.path.normcase(os.path.normpath(str(claim.resolve(strict=False))))
146
145
  if key in resolved:
147
146
  raise UnsafeOutputPathError("unsafe output path")
148
147
  resolved.add(key)
149
- if safe_lstat(claim) is not None:
150
- for other in existing:
151
- try:
152
- if os.path.samefile(claim, other):
153
- raise UnsafeOutputPathError("unsafe output path")
154
- except FileNotFoundError:
155
- continue
156
- except UnsafeOutputPathError:
157
- raise
158
- except OSError as exc:
159
- raise UnsafeOutputPathError("unsafe output path") from exc
160
- existing.append(claim)
148
+ value = safe_lstat(claim)
149
+ if value is None:
150
+ continue
151
+ identity = value.st_dev, value.st_ino
152
+ if identity in identities:
153
+ raise UnsafeOutputPathError("unsafe output path")
154
+ identities.add(identity)
161
155
 
162
156
 
163
157
  def build_output_plan(files: tuple[ManifestFile, ...], out: Path) -> OutputRootPlan:
@@ -4,7 +4,7 @@ import json
4
4
  import os
5
5
  import re
6
6
  import sys
7
- from dataclasses import dataclass
7
+ from dataclasses import dataclass, replace
8
8
  from pathlib import Path
9
9
  from typing import Any
10
10
  from urllib.parse import urlsplit, urlunsplit
@@ -199,18 +199,17 @@ def parse_manifest(text: str) -> tuple[ManifestFile, ...]:
199
199
  raise ManifestError(f"files[{index}].file_name must be a non-empty string")
200
200
  safe_name = safe_file_name(file_name, index=index)
201
201
  duplicate_key = os.path.normcase(safe_name)
202
- if duplicate_key in destinations:
203
- raise ManifestError(f"files[{index}].file_name resolves to a duplicate target")
204
- destinations.add(duplicate_key)
205
202
  final_claim = claim_parts(safe_name)
206
203
  part_claim = (*final_claim[:-1], f"{final_claim[-1]}.part")
207
204
  instruction_claim = (*final_claim[:-1], f"{final_claim[-1]}.json")
208
- if any(
209
- paths_conflict(claim, existing) for claim in (final_claim, part_claim) for existing in output_claims
210
- ) or any(paths_conflict(instruction_claim, existing) for existing in metadata_claims):
211
- raise ManifestError(f"files[{index}].file_name creates an ambiguous target collision")
212
- output_claims.extend((final_claim, part_claim))
213
- metadata_claims.append(instruction_claim)
205
+ if duplicate_key not in destinations:
206
+ if any(
207
+ paths_conflict(claim, existing) for claim in (final_claim, part_claim) for existing in output_claims
208
+ ) or any(paths_conflict(instruction_claim, existing) for existing in metadata_claims):
209
+ raise ManifestError(f"files[{index}].file_name creates an ambiguous target collision")
210
+ destinations.add(duplicate_key)
211
+ output_claims.extend((final_claim, part_claim))
212
+ metadata_claims.append(instruction_claim)
214
213
  files.append(
215
214
  ManifestFile(
216
215
  download_url=download_url,
@@ -225,6 +224,31 @@ def parse_manifest(text: str) -> tuple[ManifestFile, ...]:
225
224
  meta_data=metadata(item, index=index),
226
225
  )
227
226
  )
227
+ # Reserve every original name before allocating suffixes, independent of disk
228
+ # contents and worker order so the same manifest can resume at the same paths.
229
+ next_suffix: dict[str, int] = {}
230
+ for index, file in enumerate(files):
231
+ key = os.path.normcase(file.file_name)
232
+ if key not in next_suffix:
233
+ next_suffix[key] = 1
234
+ continue
235
+ stem, extension = os.path.splitext(file.file_name)
236
+ suffix = next_suffix[key]
237
+ while True:
238
+ candidate = f"{stem}-{suffix}{extension}"
239
+ suffix += 1
240
+ final_claim = claim_parts(candidate)
241
+ part_claim = (*final_claim[:-1], f"{final_claim[-1]}.part")
242
+ instruction_claim = (*final_claim[:-1], f"{final_claim[-1]}.json")
243
+ if any(
244
+ paths_conflict(claim, existing) for claim in (final_claim, part_claim) for existing in output_claims
245
+ ) or any(paths_conflict(instruction_claim, existing) for existing in metadata_claims):
246
+ continue
247
+ output_claims.extend((final_claim, part_claim))
248
+ metadata_claims.append(instruction_claim)
249
+ files[index] = replace(file, file_name=candidate)
250
+ next_suffix[key] = suffix
251
+ break
228
252
  return tuple(files)
229
253
 
230
254
 
@@ -108,7 +108,7 @@ wheels = [
108
108
 
109
109
  [[package]]
110
110
  name = "dexverse-cli"
111
- version = "0.1.1"
111
+ version = "0.1.3"
112
112
  source = { editable = "." }
113
113
  dependencies = [
114
114
  { name = "av" },
@@ -1 +0,0 @@
1
- __version__ = "0.1.1"
File without changes