Skip to content

Commit 3a88eee

Browse files
committed
memory store registry
1 parent d44baf6 commit 3a88eee

2 files changed

Lines changed: 82 additions & 2 deletions

File tree

src/zarr/storage/_common.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
)
1818
from zarr.errors import ContainsArrayAndGroupError, ContainsArrayError, ContainsGroupError
1919
from zarr.storage._local import LocalStore
20-
from zarr.storage._memory import MemoryStore
20+
from zarr.storage._memory import MemoryStore, _get_memory_store_from_url
2121
from zarr.storage._utils import normalize_path
2222

2323
_has_fsspec = importlib.util.find_spec("fsspec")
@@ -341,8 +341,20 @@ async def make_store(
341341
return await LocalStore.open(root=store_like, mode=mode, read_only=_read_only)
342342

343343
elif isinstance(store_like, str):
344+
# Check for memory:// URLs first (in-process registry lookup)
345+
if store_like.startswith("memory://"):
346+
memory_store = _get_memory_store_from_url(store_like)
347+
if memory_store is not None:
348+
if _read_only and not memory_store.read_only:
349+
return memory_store.with_read_only(read_only=True)
350+
return memory_store
351+
# Memory store not found in registry - it may have been garbage collected
352+
raise ValueError(
353+
f"Memory store not found for URL '{store_like}'. "
354+
"The store may have been garbage collected."
355+
)
344356
# Either an FSSpec URI or a local filesystem path
345-
if _is_fsspec_uri(store_like):
357+
elif _is_fsspec_uri(store_like):
346358
return FsspecStore.from_url(
347359
store_like, storage_options=storage_options, read_only=_read_only
348360
)
@@ -418,6 +430,24 @@ async def make_store_path(
418430
"'path' was provided but is not used for FSMap store_like objects. Specify the path when creating the FSMap instance instead."
419431
)
420432

433+
elif isinstance(store_like, str) and store_like.startswith("memory://"):
434+
# Handle memory:// URLs specially - extract path from URL
435+
memory_store = _get_memory_store_from_url(store_like)
436+
if memory_store is None:
437+
raise ValueError(
438+
f"Memory store not found for URL '{store_like}'. "
439+
"The store may have been garbage collected."
440+
)
441+
# Extract path from URL: "memory://123456/path/to/node" -> "path/to/node"
442+
url_without_scheme = store_like[len("memory://") :]
443+
parts = url_without_scheme.split("/", 1)
444+
url_path = parts[1] if len(parts) > 1 else ""
445+
# Combine URL path with any additional path argument
446+
combined_path = normalize_path(url_path)
447+
if path_normalized:
448+
combined_path = f"{combined_path}/{path_normalized}" if combined_path else path_normalized
449+
return await StorePath.open(memory_store, path=combined_path, mode=mode)
450+
421451
else:
422452
store = await make_store(store_like, mode=mode, storage_options=storage_options)
423453
return await StorePath.open(store, path=path_normalized, mode=mode)

src/zarr/storage/_memory.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import weakref
34
from logging import getLogger
45
from typing import TYPE_CHECKING, Any, Self
56

@@ -18,6 +19,53 @@
1819
logger = getLogger(__name__)
1920

2021

22+
# -----------------------------------------------------------------------------
23+
# Memory store registry
24+
# -----------------------------------------------------------------------------
25+
# This registry allows memory stores to be looked up by their URL within the
26+
# same process. This enables specs containing memory:// URLs to be re-opened.
27+
# We use weakrefs so that stores can be garbage collected when no longer in use.
28+
29+
_memory_store_registry: weakref.WeakValueDictionary[int, MemoryStore] = weakref.WeakValueDictionary()
30+
31+
32+
def _register_memory_store(store: MemoryStore) -> None:
33+
"""Register a memory store in the registry."""
34+
store_id = id(store._store_dict)
35+
_memory_store_registry[store_id] = store
36+
37+
38+
def _get_memory_store_from_url(url: str) -> MemoryStore | None:
39+
"""
40+
Look up a memory store by its URL.
41+
42+
Parameters
43+
----------
44+
url : str
45+
A URL like "memory://123456" or "memory://123456/path/to/node"
46+
47+
Returns
48+
-------
49+
MemoryStore | None
50+
The store if found in the registry, None otherwise.
51+
"""
52+
if not url.startswith("memory://"):
53+
return None
54+
55+
# Parse the store ID from the URL (handle optional path)
56+
# "memory://123456" -> "123456"
57+
# "memory://123456/path" -> "123456"
58+
url_without_scheme = url[len("memory://") :]
59+
store_id_str = url_without_scheme.split("/")[0]
60+
61+
try:
62+
store_id = int(store_id_str)
63+
except ValueError:
64+
return None
65+
66+
return _memory_store_registry.get(store_id)
67+
68+
2169
class MemoryStore(Store):
2270
"""
2371
Store for local memory.
@@ -52,6 +100,8 @@ def __init__(
52100
if store_dict is None:
53101
store_dict = {}
54102
self._store_dict = store_dict
103+
# Register this store so it can be looked up by URL
104+
_register_memory_store(self)
55105

56106
def with_read_only(self, read_only: bool = False) -> MemoryStore:
57107
# docstring inherited

0 commit comments

Comments
 (0)