Skip to content

Commit 5ca5491

Browse files
Fix storage clients on windows (#20)
* Fix storage clients on windows * remove windows ci for now
1 parent 0d790cf commit 5ca5491

6 files changed

Lines changed: 366 additions & 353 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
hooks:
1010
- id: sync-with-uv
1111
- repo: https://github.com/charliermarsh/ruff-pre-commit
12-
rev: v0.14.3
12+
rev: v0.14.7
1313
hooks:
1414
- id: ruff-check
1515
args: [--fix, --exit-non-zero-on-fix]

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- `tilebox-storage`: Fixed a bug on Windows, where the `CopernicusStorageClient` and `USGSLandsatStorageClient` were
13+
unable to download products due to an incorrect path separator in the underlying S3 paths.
14+
1015
## [0.46.0] - 2025-12-04
1116

1217
### Added

tilebox-storage/tests/test_storage_client.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
UmbraStorageClient,
1818
USGSLandsatStorageClient,
1919
_HttpClient,
20+
list_object_paths,
2021
)
2122
from tilebox.storage.granule import (
2223
ASFStorageGranule,
@@ -149,6 +150,22 @@ async def test_cached_download(httpx_mock: HTTPXMock, tmp_path: Path, granule: A
149150
assert len(httpx_mock.get_requests(url=granule.urls.data)) == 1
150151

151152

153+
@pytest.mark.asyncio
154+
async def test_list_object_paths() -> None:
155+
with TemporaryDirectory() as tmp_path:
156+
store_path = Path(tmp_path) / "store"
157+
store_path.mkdir(exist_ok=True, parents=True)
158+
store = LocalStore(store_path)
159+
160+
await store.put_async("prefix/object1", b"content1")
161+
await store.put_async("prefix/object2", b"content2")
162+
await store.put_async("prefix/subdir/object3", b"content3")
163+
164+
objects = await list_object_paths(store, "prefix")
165+
# we always need a forward slash in our paths, even on windows
166+
assert objects == ["object1", "object2", "subdir/object3"]
167+
168+
152169
@pytest.mark.asyncio
153170
@given(umbra_granules())
154171
@settings(max_examples=1, deadline=timedelta(milliseconds=100))

tilebox-storage/tilebox/storage/aio.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from asyncio import Queue, QueueEmpty
88
from collections.abc import AsyncIterator
99
from pathlib import Path
10+
from pathlib import PurePosixPath as ObjectPath
1011
from typing import Any, TypeAlias
1112

1213
import anyio
@@ -259,8 +260,8 @@ async def destroy_cache(self) -> None:
259260

260261
async def list_object_paths(store: ObjectStore, prefix: str) -> list[str]:
261262
objects = await obs.list(store, prefix).collect_async()
262-
prefix_path = Path(prefix)
263-
return sorted(str(Path(obj["path"]).relative_to(prefix_path)) for obj in objects)
263+
prefix_path = ObjectPath(prefix)
264+
return sorted(str(ObjectPath(obj["path"]).relative_to(prefix_path)) for obj in objects)
264265

265266

266267
async def download_objects( # noqa: PLR0913
@@ -299,7 +300,7 @@ async def _download_worker(
299300
async def _download_object(
300301
store: ObjectStore, prefix: str, obj: str, output_dir: Path, show_progress: bool = True
301302
) -> Path:
302-
key = str(Path(prefix) / obj)
303+
key = str(ObjectPath(prefix) / obj)
303304
output_path = output_dir / obj
304305
if output_path.exists(): # already cached
305306
return output_path
@@ -609,7 +610,7 @@ async def _list_objects(self, datapoint: xr.Dataset | CopernicusStorageGranule)
609610
granule = CopernicusStorageGranule.from_data(datapoint)
610611
# special handling for Sentinel-5P, where the location is not a folder but a single file
611612
if granule.location.endswith(".nc"):
612-
return [Path(granule.granule_name).name]
613+
return [str(ObjectPath(granule.granule_name))]
613614

614615
return await list_object_paths(self._store, _copernicus_s3_prefix(granule))
615616

tilebox-storage/tilebox/storage/granule.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from dataclasses import dataclass
22
from datetime import datetime
3-
from pathlib import Path
3+
from pathlib import PurePosixPath as ObjectPath
44

55
import xarray as xr
66

@@ -103,7 +103,7 @@ def _thumbnail_relative_to_eodata_location(thumbnail_url: str, location: str) ->
103103
url_path = thumbnail_url.split("?path=")[-1]
104104
url_path = url_path.removeprefix("/")
105105
location = location.removeprefix("/eodata/")
106-
return str(Path(url_path).relative_to(location))
106+
return str(ObjectPath(url_path).relative_to(location))
107107

108108

109109
@dataclass

0 commit comments

Comments
 (0)