Skip to content

Commit b8de1d8

Browse files
kushalbakshiclaude
andcommitted
feat: delegate plugin protocols in get_store_spec()
When a store config uses an unrecognised protocol, settings.py now queries the adapter registry before raising an error. Registered plugin adapters are validated and default keys are applied; unknown protocols surface a clear message directing users to install a plugin package. Includes new test class TestGetStoreSpecPluginDelegation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3caee31 commit b8de1d8

File tree

2 files changed

+64
-3
lines changed

2 files changed

+64
-3
lines changed

src/datajoint/settings.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,10 +434,31 @@ def get_store_spec(self, store: str | None = None, *, use_filepath_default: bool
434434
protocol = spec.get("protocol", "").lower()
435435
supported_protocols = ("file", "s3", "gcs", "azure")
436436
if protocol not in supported_protocols:
437-
raise DataJointError(
438-
f'Missing or invalid protocol in config.stores["{store}"]. '
439-
f"Supported protocols: {', '.join(supported_protocols)}"
437+
from .storage_adapter import get_storage_adapter
438+
439+
adapter = get_storage_adapter(protocol)
440+
if adapter is None:
441+
raise DataJointError(
442+
f'Unknown protocol "{protocol}" in config.stores["{store}"]. '
443+
f"Built-in: {', '.join(supported_protocols)}. "
444+
f"Install a plugin package for additional protocols."
445+
)
446+
# Apply common defaults for plugin protocols
447+
spec.setdefault("subfolding", None)
448+
spec.setdefault("partition_pattern", None)
449+
spec.setdefault("token_length", 8)
450+
spec.setdefault("hash_prefix", "_hash")
451+
spec.setdefault("schema_prefix", "_schema")
452+
spec.setdefault("filepath_prefix", None)
453+
spec.setdefault("location", "")
454+
adapter.validate_spec(spec)
455+
self._validate_prefix_separation(
456+
store_name=store,
457+
hash_prefix=spec.get("hash_prefix"),
458+
schema_prefix=spec.get("schema_prefix"),
459+
filepath_prefix=spec.get("filepath_prefix"),
440460
)
461+
return spec
441462

442463
# Set protocol-specific defaults
443464
if protocol == "s3":

tests/unit/test_storage_adapter.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,43 @@ def setup_method(self):
103103

104104
def test_default_url_format(self):
105105
assert self.adapter.get_url({}, "data/file.dat") == "dummy://data/file.dat"
106+
107+
108+
import datajoint as dj
109+
110+
111+
class TestGetStoreSpecPluginDelegation:
112+
"""Tests for plugin protocol handling in Config.get_store_spec()."""
113+
114+
def setup_method(self):
115+
import datajoint.storage_adapter as sa_mod
116+
117+
sa_mod._adapter_registry["dummy"] = _DummyAdapter()
118+
self._original_stores = dj.config.stores.copy()
119+
120+
def teardown_method(self):
121+
import datajoint.storage_adapter as sa_mod
122+
123+
sa_mod._adapter_registry.pop("dummy", None)
124+
dj.config.stores = self._original_stores
125+
126+
def test_plugin_protocol_accepted(self):
127+
"""Plugin protocol passes validation via adapter."""
128+
dj.config.stores["test_store"] = {
129+
"protocol": "dummy",
130+
"endpoint": "https://example.com",
131+
"location": "",
132+
"hash_prefix": "_hash",
133+
"schema_prefix": "_schema",
134+
}
135+
spec = dj.config.get_store_spec("test_store")
136+
assert spec["protocol"] == "dummy"
137+
138+
def test_unknown_protocol_error_message(self):
139+
"""Unknown protocol gives clear error mentioning plugin installation."""
140+
dj.config.stores["bad_store"] = {
141+
"protocol": "nonexistent_xyz",
142+
"location": "",
143+
}
144+
with pytest.raises(DataJointError, match="Install a plugin"):
145+
dj.config.get_store_spec("bad_store")

0 commit comments

Comments
 (0)