From 3484416224093c449ac90673425cb907a4770e64 Mon Sep 17 00:00:00 2001 From: guarin <43336610+guarin@users.noreply.github.com> Date: Wed, 15 May 2024 13:11:16 +0200 Subject: [PATCH] Validate input and lightly mount (#1537) * Validate input and lightly mount --- lightly/api/serve.py | 42 +++++++++++++++++++++++++++ lightly/cli/serve_cli.py | 18 ++++++++++-- tests/api/test_serve.py | 62 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/lightly/api/serve.py b/lightly/api/serve.py index 58aeea192..371df2902 100644 --- a/lightly/api/serve.py +++ b/lightly/api/serve.py @@ -3,6 +3,8 @@ from typing import Sequence from urllib import parse +from lightly.data import _helpers + def get_server( paths: Sequence[Path], @@ -50,6 +52,46 @@ def send_response_only(self, code, message=None): return HTTPServer((host, port), _LocalDatasourceRequestHandler) +def validate_input_mount(input_mount: Path) -> None: + """Validates that the input mount is a directory and contains files.""" + input_mount = input_mount.resolve() + if not input_mount.exists(): + raise ValueError( + f"Path for 'input_mount' argument '{input_mount}' does not exist." + ) + if not input_mount.is_dir(): + raise ValueError( + f"Path for 'input_mount' argument '{input_mount}' is not a directory." + ) + if not _dir_contains_image_or_video(path=input_mount): + raise ValueError( + f"Path for 'input_mount' argument '{input_mount}' does not contain any " + "images or videos. Please verify that this is the correct directory. See " + "our docs on lightly-serve for more information: " + "https://docs.lightly.ai/docs/local-storage#optional-after-run-view-local-data-in-lightly-platform" + ) + + +def validate_lightly_mount(lightly_mount: Path) -> None: + lightly_mount = lightly_mount.resolve() + """Validates that the Lightly mount is a directory.""" + if not lightly_mount.exists(): + raise ValueError( + f"Path for 'lightly_mount' argument '{lightly_mount}' does not exist." + ) + if not lightly_mount.is_dir(): + raise ValueError( + f"Path for 'lightly_mount' argument '{lightly_mount}' is not a directory." + ) + + +def _dir_contains_image_or_video(path: Path) -> bool: + extensions = set(_helpers.IMG_EXTENSIONS + _helpers.VIDEO_EXTENSIONS) + return any( + p for p in path.rglob("**/*") if p.is_file() and p.suffix.lower() in extensions + ) + + def _translate_path(path: str, directories: Sequence[Path]) -> str: """Translates a relative path to a file in the local datasource. diff --git a/lightly/cli/serve_cli.py b/lightly/cli/serve_cli.py index 887045e86..2845c65f8 100644 --- a/lightly/cli/serve_cli.py +++ b/lightly/cli/serve_cli.py @@ -4,6 +4,7 @@ import hydra from lightly.api import serve +from lightly.api.serve import validate_input_mount, validate_lightly_mount from lightly.cli._helpers import fix_hydra_arguments from lightly.utils.hipify import bcolors @@ -28,15 +29,26 @@ def lightly_serve(cfg): """ if not cfg.input_mount: - print("Please provide a valid input mount. Use --help for more information.") + print( + "Please provide a valid 'input_mount' argument. Use --help for more " + "information." + ) sys.exit(1) if not cfg.lightly_mount: - print("Please provide a valid Lightly mount. Use --help for more information.") + print( + "Please provide a valid 'lightly_mount' argument. Use --help for more " + "information." + ) sys.exit(1) + input_mount = Path(cfg.input_mount) + validate_input_mount(input_mount=input_mount) + lightly_mount = Path(cfg.lightly_mount) + validate_lightly_mount(lightly_mount=lightly_mount) + httpd = serve.get_server( - paths=[Path(cfg.input_mount), Path(cfg.lightly_mount)], + paths=[input_mount, lightly_mount], host=cfg.host, port=cfg.port, ) diff --git a/tests/api/test_serve.py b/tests/api/test_serve.py index d5b91438b..1e10fc8e7 100644 --- a/tests/api/test_serve.py +++ b/tests/api/test_serve.py @@ -1,8 +1,70 @@ from pathlib import Path +import pytest + from lightly.api import serve +def test_validate_input_mount(tmp_path: Path) -> None: + (tmp_path / "image.png").touch() + serve.validate_input_mount(input_mount=tmp_path) + + +def test_validate_input_mount__not_exist(tmp_path: Path) -> None: + with pytest.raises( + ValueError, + match=f"Path for 'input_mount' argument '{tmp_path}/not-existant' does not exist.", + ): + serve.validate_input_mount(input_mount=tmp_path / "not-existant") + + +def test_validate_input_mount__not_directory(tmp_path: Path) -> None: + (tmp_path / "file.txt").touch() + with pytest.raises( + ValueError, + match=f"Path for 'input_mount' argument '{tmp_path}/file.txt' is not a directory.", + ): + serve.validate_input_mount(input_mount=tmp_path / "file.txt") + + +def test_validate_input_mount__no_files(tmp_path: Path) -> None: + with pytest.raises( + ValueError, + match=( + f"Path for 'input_mount' argument '{tmp_path}' does not contain any images " + "or videos" + ), + ): + serve.validate_input_mount(input_mount=tmp_path) + + +def test_validate_lightly_mount(tmp_path: Path) -> None: + serve.validate_lightly_mount(lightly_mount=tmp_path) + + +def test_validate_lightly_mount__not_exist(tmp_path: Path) -> None: + with pytest.raises( + ValueError, + match=( + f"Path for 'lightly_mount' argument '{tmp_path}/not-existant' does not " + "exist." + ), + ): + serve.validate_lightly_mount(lightly_mount=tmp_path / "not-existant") + + +def test_validate_lightly_mount__not_directory(tmp_path: Path) -> None: + (tmp_path / "file.txt").touch() + with pytest.raises( + ValueError, + match=( + f"Path for 'lightly_mount' argument '{tmp_path}/file.txt' is not a " + "directory." + ), + ): + serve.validate_lightly_mount(lightly_mount=tmp_path / "file.txt") + + def test__translate_path(tmp_path: Path) -> None: tmp_file = tmp_path / "hello/world.txt" assert serve._translate_path(path="/hello/world.txt", directories=[]) == ""