diff --git a/dvc/fs/http.py b/dvc/fs/http.py index a05c4112f..c265db206 100644 --- a/dvc/fs/http.py +++ b/dvc/fs/http.py @@ -31,6 +31,7 @@ class HTTPFileSystem(BaseFileSystem): # pylint:disable=abstract-method PATH_CLS = HTTPURLInfo PARAM_CHECKSUM = "etag" CAN_TRAVERSE = False + REQUIRES = {"requests": "requests"} SESSION_RETRIES = 5 SESSION_BACKOFF_FACTOR = 0.1 diff --git a/dvc/info.py b/dvc/info.py index fdd276c29..87e7b74e7 100644 --- a/dvc/info.py +++ b/dvc/info.py @@ -20,6 +20,11 @@ if PKG is None: else: package = f"({PKG})" +try: + import importlib.metadata as importlib_metadata +except ImportError: # < 3.8 + import importlib_metadata # type: ignore[no-redef] + def get_dvc_info(): info = [ @@ -27,7 +32,7 @@ def get_dvc_info(): "---------------------------------", f"Platform: Python {platform.python_version()} on " f"{platform.platform()}", - f"Supports: {_get_supported_remotes()}", + f"Supports:{_get_supported_remotes()}", ] try: @@ -113,19 +118,23 @@ def _get_linktype_support_info(repo): def _get_supported_remotes(): - supported_remotes = [] for scheme, fs_cls in FS_MAP.items(): if not fs_cls.get_missing_deps(): - supported_remotes.append(scheme) + dependencies = [] + for requirement in fs_cls.REQUIRES: + dependencies.append( + f"{requirement} = " + f"{importlib_metadata.version(requirement)}" + ) - if len(supported_remotes) == len(FS_MAP): - return "All remotes" + remote_info = scheme + if dependencies: + remote_info += " (" + ", ".join(dependencies) + ")" + supported_remotes.append(remote_info) - if len(supported_remotes) == 1: - return supported_remotes - - return ", ".join(supported_remotes) + assert len(supported_remotes) >= 1 + return "\n\t" + ",\n\t".join(supported_remotes) def get_fs_type(path): diff --git a/setup.py b/setup.py index 744eaf816..d09d52da4 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ install_requires = [ "pydot>=1.2.4", "speedcopy>=2.0.1; python_version < '3.8' and sys_platform == 'win32'", "dataclasses==0.7; python_version < '3.7'", + "importlib-metadata==1.4; python_version < '3.8'", "flatten_dict>=0.3.0,<1", "tabulate>=0.8.7", "pygtrie>=2.3.2", diff --git a/tests/func/test_version.py b/tests/func/test_version.py index e08f05ce8..08937d649 100644 --- a/tests/func/test_version.py +++ b/tests/func/test_version.py @@ -1,7 +1,7 @@ import re from dvc.main import main -from tests.unit.test_info import PYTHON_VERSION_REGEX +from tests.unit.test_info import PYTHON_VERSION_REGEX, find_supported_remotes def test_(tmp_dir, dvc, scm, capsys): @@ -10,7 +10,7 @@ def test_(tmp_dir, dvc, scm, capsys): out, _ = capsys.readouterr() assert re.search(r"DVC version: \d+\.\d+\.\d+.*", out) assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", out) - assert re.search(r"Supports: .*", out) + assert find_supported_remotes(out) assert re.search(r"Cache types: .*", out) assert re.search(r"Caches: local", out) assert re.search(r"Remotes: None", out) diff --git a/tests/unit/test_info.py b/tests/unit/test_info.py index 9989e69f1..8f01b2c47 100644 --- a/tests/unit/test_info.py +++ b/tests/unit/test_info.py @@ -13,6 +13,35 @@ from dvc.info import get_dvc_info PYTHON_VERSION_REGEX = r"Python \d\.\d+\.\d+\S*" +def find_supported_remotes(string): + lines = string.splitlines() + index = 0 + + for index, line in enumerate(lines): + if line == "Supports:": + index += 1 + break + else: + return [] + + remotes = {} + for line in lines[index:]: + if not line.startswith("\t"): + break + + remote_name, _, raw_dependencies = ( + line.strip().strip(",").partition(" ") + ) + remotes[remote_name] = { + dependency: version + for dependency, _, version in [ + dependency.partition(" = ") + for dependency in raw_dependencies[1:-1].split(", ") + ] + } + return remotes + + @pytest.mark.parametrize("scm_init", [True, False]) def test_info_in_repo(scm_init, tmp_dir): tmp_dir.init(scm=scm_init, dvc=True) @@ -23,7 +52,7 @@ def test_info_in_repo(scm_init, tmp_dir): assert re.search(r"DVC version: \d+\.\d+\.\d+.*", dvc_info) assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", dvc_info) - assert re.search(r"Supports: .*", dvc_info) + assert find_supported_remotes(dvc_info) assert re.search(r"Cache types: .*", dvc_info) if scm_init: @@ -98,7 +127,7 @@ def test_info_outside_of_repo(tmp_dir, caplog): assert re.search(r"DVC version: \d+\.\d+\.\d+.*", dvc_info) assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", dvc_info) - assert re.search(r"Supports: .*", dvc_info) + assert find_supported_remotes(dvc_info) assert not re.search(r"Cache types: .*", dvc_info) assert "Repo:" not in dvc_info @@ -107,4 +136,14 @@ def test_fs_info_outside_of_repo(tmp_dir, caplog): dvc_info = get_dvc_info() assert re.search(r"DVC version: \d+\.\d+\.\d+.*", dvc_info) assert re.search(f"Platform: {PYTHON_VERSION_REGEX} on .*", dvc_info) - assert re.search(r"Supports: .*", dvc_info) + assert find_supported_remotes(dvc_info) + + +def test_plugin_versions(tmp_dir, dvc): + from dvc.fs import FS_MAP + + dvc_info = get_dvc_info() + remotes = find_supported_remotes(dvc_info) + + for remote, dependencies in remotes.items(): + assert dependencies.keys() == FS_MAP[remote].REQUIRES.keys()