From f9e3641ef10afe52a4be79aeb8f3d22416169706 Mon Sep 17 00:00:00 2001 From: Tomaz Vieira Date: Tue, 20 Oct 2026 16:22:27 +0000 Subject: [PATCH 1/2] Fixes parsing of relative file:// URLs --- src/zarr/storage/_utils.py | 16 ++++++++-------- tests/test_store/test_utils.py | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/zarr/storage/_utils.py b/src/zarr/storage/_utils.py index ca10b0679e..545094beed 100644 --- a/src/zarr/storage/_utils.py +++ b/src/zarr/storage/_utils.py @@ -90,16 +90,16 @@ def parse_store_url(url: str) -> ParsedStoreUrl: parsed = urlparse(url) - # netloc is the "host" part (store name for memory://, bucket for s3://, etc.) - name = parsed.netloc or None + if parsed.scheme in ("file", ""): + name = None + path = url.removeprefix("file://") + else: + # netloc is the "host" part (store name for memory://, bucket for s3://, etc.) + name = parsed.netloc or None - # For URLs with a scheme and netloc (like memory://store/path or s3://bucket/key), - # strip the leading slash from the path component. - # For local paths (no scheme), preserve the path as-is. - if parsed.scheme and parsed.netloc: + # For URLs with a non-file scheme and netloc (like memory://store/path or s3://bucket/key), + # strip the leading slash from the path component. path = parsed.path.lstrip("/") - else: - path = parsed.path return ParsedStoreUrl(scheme=parsed.scheme, name=name, path=path, raw=url) diff --git a/tests/test_store/test_utils.py b/tests/test_store/test_utils.py index 291526fab8..26cf760e89 100644 --- a/tests/test_store/test_utils.py +++ b/tests/test_store/test_utils.py @@ -39,9 +39,19 @@ def test_s3_url(self) -> None: scheme="s3", name="bucket", path="key", raw="s3://bucket/key" ) - def test_file_url(self) -> None: - result = parse_store_url("file:///tmp/test") - assert result.scheme == "file" + def test_absolute_file_url(self) -> None: + url = "file:///something/absolute" + result = parse_store_url(url) + assert result == ParsedStoreUrl( + scheme="file", name=None, path="/something/absolute", raw=url + ) + + def test_relative_file_url(self) -> None: + url = "file://something/relative" + result = parse_store_url(url) + assert result == ParsedStoreUrl( + scheme="file", name=None, path="something/relative", raw=url + ) def test_local_absolute_path(self) -> None: result = parse_store_url("/local/path") From 4092d9b1241cca9362e24a0dcf8f9fa0556608fb Mon Sep 17 00:00:00 2001 From: Tomaz Vieira Date: Tue, 20 Oct 2026 16:59:20 +0000 Subject: [PATCH 2/2] Add file to changes/ --- changes/4533.bugfix.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 changes/4533.bugfix.md diff --git a/changes/4533.bugfix.md b/changes/4533.bugfix.md new file mode 100644 index 0000000000..bb5f7cb5a5 --- /dev/null +++ b/changes/4533.bugfix.md @@ -0,0 +1,11 @@ +Properly parses relative `file://` URLs + +URLs with a relative file path like + +`file://a/b` + +were being parsed in `parse_store_url` into something like + +`ParsedStoreUrl(..., path="b")` + +because `a` was being seen as a hostname.