Repository navigation
Description
Summary
ZEP 8 (zarr-developers/zeps#48) proposes a standardized URL syntax for specifying Zarr nodes across multiple implementations. Implementing this in zarr-python would enable more seamless interoperability between different Zarr tools, making it easier to share dataset locations across different backends.
Motivation
- A standardized URL syntax ensures consistent dataset access across different implementations.
- The proposed syntax allows for nested storage mechanisms, including ZIP files and hierarchical Zarr groups.
- Improved integration with libraries like
xarray, which could leverage this for seamless dataset loading.
Proposed Implementation
I would encourage us to handle this in two phases.
Phase 1
Implement handling of ZEP8 URLs for stores implemented in Zarr-Python. Practically, this requires parsing URLs into Store, Path, and ZarrFormat components.
Phase 2
Implement an entrypoint mechanism for 3rd party stores (e.g. Icechunk). This would allow external Store developers to provide a function to generate a Store given a URL.
Example Usage in xarray
With ZEP 8 URL syntax, an xarray.Dataset could be opened as follows:
import xarray as xr
dataset = xr.open_dataset(
"gs://my-bucket/my-archive.zip|zip:inner-dir|zarr3:subgroup",
engine="zarr"
)This URL specifies:
- A ZipStore (
my-archive.zip) stored in Google Cloud Storage (gs://my-bucket/) - A nested hierarchy inside the Zarr store (
subgroup)
Another example:
dataset = xr.open_dataset(
"https://foobar.com/my-archive|icechunk|zarr3",
engine="zarr"
)This URL specifies:
- An Icechunk Store available over https
Additional Considerations
- Backward compatibility with existing
fsspecURLs.
cc @jbms
Activity
d-v-b commentedon Apr 1, 2025
this is a great idea!
maxrjones commentedon Aug 4, 2025
Nice idea! Could this be used to specify whether to use fsspec, obstore, or some other Store implementations (e.g., a Store that's coupled to a caching store)?
jhamman commentedon Jul 26, 2026
Hi all 👋 — I'd like to take another run at closing this issue, and I want to share the plan before PRs start landing.
Some history
My first attempt (#3369, continued by @ianhi in #3567) stalled largely under its own weight: a single ~4.8k-line PR, a fragile scheme-detection heuristic,
storage_optionsthat didn't thread through the pipeline path, andzarr2:/zarr3:segments that were parsed and then silently dropped. Meanwhile the spec itself matured and moved out of the ZEP process: @jbms now maintains it as an implementation-independent standard at https://github.com/jbms/url-pipeline, with a formal ABNF grammar and per-scheme specs. The new implementation will target that spec (including a few small schema changes), and I'll use the spec's name for the feature: URL pipelines.Why this is much easier now than a year ago
Several improvements have landed in zarr-python over the past year do most of the infrastructural work the old PR had to hand-roll:
make_store()— store creation was refactored into a single central dispatch point (zarr.storage._common.make_store), so the pipeline hook is one small branch in one function instead of special cases scattered throughmake_store_path.parse_store_url()— string URLs are now parsed once, up front, with Windows drive-letter handling already solved. The old PR re-solved this twice with regexes.Registry.lazy_load(use_entrypoint_name=True)— the entry-point registry pattern used by chunk-key encodings is basically the exact same thing a URL-adapter registry needs; the old PR's custom registry subclass can now be done in ~10 lines of code.memory://handling (ManagedMemoryStore) and the cleaned-up fsspec path semantics mean the builtin schemes need no adapters at all — non-piped URLs take exactly the same path they take today.The result: the review-critical core is roughly 350 source lines, and routing is dead simple — a URL goes through the pipeline machinery only if it contains a
|(or its scheme has a registered adapter). No scheme denylists, no fsspec special-casing, and zero downstream-specific code in zarr core.The plan: 5 PRs, opened one at a time
I'll open each PR only after the previous one merges, so review load stays small and each step is independently useful. I've had Claude estimate the approximate size of each of these PRs just to make sure they reviewable in a single sitting.
ZipStorefile-like supportZipStoreaccepts an open binary file object (enables zip-over-anything later; useful on its own)URLPipelineAdapterABC,zarr.url_adaptersentry-point group,make_store/make_store_pathhookszip:adapterzarr:/zarr2:/zarr3:format segmentszarr_formatraiseWhat works at the end of PR 4:
Downstream validation
Phase 2 of this issue (the plugin mechanism) is only proven once we have a real third-party store using it, so alongside PR 2 I'll open a companion PR to Icechunk (adapter registered via
zarr.url_adapters). I have working end-to-end implementations of this already — including the xarray examples above running against real repos — which is how the adapter contract in PR 2 was validated before proposing it here. The key contract points that came out of that exercise: adapters receive the preceding pipeline both parsed and as an exact string (so a native backend like Icechunk can consumes3://…with its own I/O stack), and they return a store plus a residual path, which is what keeps icechunk-specific logic out of zarr core this time.A note on process
I'm using AI tooling for part of the implementation work. To be clear about what that means for review: I personally review, test, and sign off on every change before requesting review. The design itself (adapter contract, registry shape, PR split) was worked out against the failure modes of #3369.
jbms commentedon Jul 26, 2026
I'd love to see this move forward, I think a common cross-implementation url syntax for nested storage formats will be very valuable to the data science community.
A few comments:
d-v-b commentedon Jul 26, 2026
we may need a conformance test suite.
jbms commentedon Jul 26, 2026
Do you have some ideas for how to do that? The spec has test code that ensures the examples fully cover the grammar, but that just covers allowed syntax, includes no negative examples, and doesn't address semantics.
Possibly there could be a test suite of operations against a generic python interface that would be easy to support for any implementation.
d-v-b commentedon Jul 27, 2026
we could have an oracle create a storage state parametrized over a root scheme and some number of adapter schemes. for example, some known array is written to zarr in zip on s3. The oracle supplies the zep 8 url pointing to this resource to the program under test, and that program attempts to read the known value out. I think testing the memory root scheme will require the oracle and test code to operate in the same process.