Skip to content

Commit 06472a2

Browse files
committed
fix(datasets): name WebDataset shards as file:// URLs so Windows can open them
webdataset routes every shard string through gopen, which dispatches on the scheme urlparse reads off it. A POSIX path parses with an empty scheme and reaches the local-file branch; a Windows path does not, because C:\shards\train-000000.tar parses as scheme "c" and no handler is registered for it. Every streaming test failed on both windows-latest jobs with "no gopen handler defined" while the same tests passed on ubuntu and macos. Naming the shards as file:// URLs picks the branch that resolves back to a local path on both platforms, and Path.as_uri() percent-encodes a directory containing spaces or other reserved characters, which url2pathname reverses on the way back in.
1 parent 2a56582 commit 06472a2

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

src/rfdetr/datasets/webdataset_io.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,22 @@ def _decode(self, sample: dict[str, Any]) -> tuple[Any, dict[str, Any]]:
730730
image, target = self._transforms(image, target)
731731
return image, target
732732

733+
def _shard_urls(self) -> list[str]:
734+
"""Return this split's shards as ``file://`` URLs.
735+
736+
``webdataset`` hands every shard string to ``gopen``, which dispatches on the scheme
737+
``urllib.parse.urlparse`` reads off it. A plain filesystem path only reaches the local-file branch when that
738+
scheme comes back empty, which is true of a POSIX path and false of a Windows one: ``C:\\shards\\train.tar``
739+
parses as scheme ``c``, no handler is registered for it, and the read fails with ``no gopen handler
740+
defined``. Naming the shards as ``file://`` URLs instead picks the one branch that resolves back to a local
741+
path on both platforms, and it is the spelling :meth:`pathlib.Path.as_uri` already percent-encodes for a
742+
directory containing spaces or other reserved characters.
743+
744+
Returns:
745+
One ``file://`` URL per shard, in index order.
746+
"""
747+
return [(self._shard_dir / shard).resolve().as_uri() for shard in self.index.shards]
748+
733749
def __iter__(self) -> Iterator[tuple[Any, Any]]:
734750
"""Iterate this worker's share of the split.
735751
@@ -738,7 +754,7 @@ def __iter__(self) -> Iterator[tuple[Any, Any]]:
738754
"""
739755
wds = _require_webdataset()
740756
shard_seed, buffer_seed = self._epoch_seeds()
741-
urls = [str(self._shard_dir / shard) for shard in self.index.shards]
757+
urls = self._shard_urls()
742758
pipeline = wds.WebDataset(
743759
urls,
744760
# A split with fewer shards than workers legitimately leaves some workers with nothing to read;

tests/datasets/test_webdataset_io.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from dataclasses import replace
2121
from pathlib import Path
2222
from typing import Any
23+
from urllib.parse import urlparse
24+
from urllib.request import url2pathname
2325

2426
import numpy as np
2527
import pytest
@@ -353,6 +355,16 @@ def _require_webdataset(self) -> None:
353355
"""
354356
pytest.importorskip("webdataset")
355357

358+
def test_shard_urls_carry_the_file_scheme(self, tmp_path: Path) -> None:
359+
dataset = WebDatasetDetection(_pack(tmp_path, count=4), "train", transforms=None)
360+
assert {urlparse(url).scheme for url in dataset._shard_urls()} == {"file"}
361+
362+
def test_shard_urls_resolve_back_to_the_shards_they_name(self, tmp_path: Path) -> None:
363+
shard_dir = _pack(tmp_path / "shard dir", count=4)
364+
dataset = WebDatasetDetection(shard_dir, "train", transforms=None)
365+
opened = [Path(url2pathname(urlparse(url).path)) for url in dataset._shard_urls()]
366+
assert opened == [(shard_dir / name).resolve() for name in dataset.index.shards]
367+
356368
def test_every_sample_is_streamed_once(self, tmp_path: Path) -> None:
357369
dataset = WebDatasetDetection(_pack(tmp_path, count=12), "train", transforms=None)
358370
image_ids = [int(target["image_id"]) for _, target in dataset]

0 commit comments

Comments
 (0)