Skip to content

Commit 1029b4f

Browse files
committed
config: add entry-point-based plugin schema discovery
Allow DVC filesystem plugins to declare a REMOTE_CONFIG class attribute on their filesystem class. At import time, _discover_plugin_schemas() iterates over installed dvc.fs entry points and merges any declared config schemas into REMOTE_SCHEMAS, enabling ByUrl to accept custom URL schemes without changes to DVC core. This makes DVC truly extensible for third-party storage backends (e.g. OSF, GitLab packages) that define their own URL schemes. Existing hardcoded schemes are never overwritten. Fixes #10993
1 parent 355939e commit 1029b4f

File tree

2 files changed

+8
-7
lines changed

2 files changed

+8
-7
lines changed

dvc/config_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ def _discover_plugin_schemas():
297297
for ep in entry_points(group="dvc.fs"):
298298
try:
299299
cls = ep.load()
300-
except Exception: # noqa: BLE001
300+
except Exception: # noqa: BLE001,S112
301301
continue
302302

303303
remote_config = getattr(cls, "REMOTE_CONFIG", None)

tests/unit/test_plugin_schema_discovery.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class attribute to register their URL scheme and config options with
55
DVC's config validation, without requiring changes to DVC core.
66
"""
77

8+
from typing import ClassVar
89
from unittest.mock import MagicMock, patch
910

1011
import pytest
@@ -14,7 +15,7 @@ class FakePluginFS:
1415
"""Minimal filesystem class that declares REMOTE_CONFIG."""
1516

1617
protocol = "myplugin"
17-
REMOTE_CONFIG = {
18+
REMOTE_CONFIG: ClassVar[dict] = {
1819
"token": str,
1920
"endpoint_url": str,
2021
}
@@ -30,7 +31,7 @@ class FakePluginMultiProtocol:
3031
"""Filesystem class with tuple protocol."""
3132

3233
protocol = ("myproto", "myprotos")
33-
REMOTE_CONFIG = {
34+
REMOTE_CONFIG: ClassVar[dict] = {
3435
"api_key": str,
3536
}
3637

@@ -48,7 +49,7 @@ class TestDiscoverPluginSchemas:
4849

4950
def test_plugin_schema_registered(self):
5051
"""A plugin with REMOTE_CONFIG gets its scheme added to REMOTE_SCHEMAS."""
51-
from dvc.config_schema import REMOTE_COMMON, REMOTE_SCHEMAS
52+
from dvc.config_schema import REMOTE_SCHEMAS
5253

5354
eps = [_make_entry_point("myplugin", FakePluginFS)]
5455
with patch("dvc.config_schema.entry_points", return_value=eps):
@@ -92,7 +93,7 @@ def test_existing_scheme_not_overwritten(self):
9293

9394
class FakeS3:
9495
protocol = "s3"
95-
REMOTE_CONFIG = {"fake_key": str}
96+
REMOTE_CONFIG: ClassVar[dict] = {"fake_key": str}
9697

9798
eps = [_make_entry_point("s3", FakeS3)]
9899
with patch("dvc.config_schema.entry_points", return_value=eps):
@@ -149,7 +150,7 @@ class TestByUrlWithPlugin:
149150

150151
def test_byurl_validates_plugin_scheme(self):
151152
"""ByUrl should accept a URL with a plugin-registered scheme."""
152-
from dvc.config_schema import ByUrl, REMOTE_COMMON, REMOTE_SCHEMAS
153+
from dvc.config_schema import REMOTE_COMMON, REMOTE_SCHEMAS, ByUrl
153154

154155
# Register a fake scheme
155156
REMOTE_SCHEMAS["testplugin"] = {"token": str, **REMOTE_COMMON}
@@ -167,7 +168,7 @@ def test_byurl_rejects_unknown_scheme(self):
167168
"""ByUrl should reject an unregistered scheme."""
168169
from voluptuous import Invalid as VoluptuousInvalid
169170

170-
from dvc.config_schema import ByUrl, REMOTE_SCHEMAS
171+
from dvc.config_schema import REMOTE_SCHEMAS, ByUrl
171172

172173
validator = ByUrl(REMOTE_SCHEMAS)
173174

0 commit comments

Comments
 (0)