From 8abe33c8ce085f389c4c09f2c4f092fb8dc43502 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 28 Nov 2022 23:41:31 -0500 Subject: [PATCH] Fix tests --- .github/workflows/tests.yml | 8 +- constructor-manager-cli/README.md | 7 + .../src/constructor_manager_cli/actions.py | 14 +- .../src/constructor_manager_cli/installer.py | 160 +++++++++++------- .../src/constructor_manager_cli/main.py | 2 - .../{utils/log.py => tests/test_actions.py} | 0 .../tests/test_installer.py | 63 +++++++ .../constructor_manager_cli/utils/anaconda.py | 29 +--- .../constructor_manager_cli/utils/conda.py | 69 +------- .../src/constructor_manager_cli/utils/io.py | 72 +++++--- .../constructor_manager_cli/utils/packages.py | 24 --- .../src/constructor_manager_cli/utils/pypi.py | 44 ----- .../constructor_manager_cli/utils/request.py | 29 +++- .../utils/tests/test_anaconda.py | 57 +++++++ .../utils/tests/test_conda.py | 32 ++++ .../utils/tests/test_packages.py | 23 +++ .../utils/tests/test_request.py | 25 +++ .../utils/tests/test_versions.py | 35 +++- 18 files changed, 435 insertions(+), 258 deletions(-) rename constructor-manager-cli/src/constructor_manager_cli/{utils/log.py => tests/test_actions.py} (100%) delete mode 100644 constructor-manager-cli/src/constructor_manager_cli/utils/pypi.py create mode 100644 constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_anaconda.py create mode 100644 constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_conda.py create mode 100644 constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_packages.py create mode 100644 constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_request.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 78da8a1c..7368d6d9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,9 @@ jobs: test: name: ${{ matrix.platform }} py${{ matrix.python-version }} runs-on: ${{ matrix.platform }} + defaults: + run: + shell: bash -el {0} strategy: matrix: platform: [ubuntu-latest, windows-latest, macos-latest] @@ -25,9 +28,10 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: conda-incubator/setup-miniconda@v2 with: - python-version: ${{ matrix.python-version }} + activate-environment: "" + auto-activate-base: true - name: Install dependencies constructor-manager-cli run: | diff --git a/constructor-manager-cli/README.md b/constructor-manager-cli/README.md index af5f0ab7..48193777 100644 --- a/constructor-manager-cli/README.md +++ b/constructor-manager-cli/README.md @@ -399,3 +399,10 @@ This command will install a specified on a fresh environment, deleting the old e ```bash constructor-manager restore "napari=0.4.16=*pyside*" -c conda-forge ``` + +### Run tests + +```bash +cd constructor-manager-cli/src +pytest constructor_manager_cli --cov=constructor_manager_cli --cov-report term-missing +``` diff --git a/constructor-manager-cli/src/constructor_manager_cli/actions.py b/constructor-manager-cli/src/constructor_manager_cli/actions.py index 46f40cc5..d5ff6b52 100644 --- a/constructor-manager-cli/src/constructor_manager_cli/actions.py +++ b/constructor-manager-cli/src/constructor_manager_cli/actions.py @@ -347,7 +347,16 @@ def lock_environment( ): """Lock the environment, using conda lock. - TODO + Parameters + ---------- + package : str + Package specification. + channels : tuple of str, optional + Check for available versions on these channels. + Default is ``('conda-forge', )``. + platforms : tuple of str, optional + Platforms to lock for. If not provided conda is queried for the + current platform. """ installer = CondaInstaller(channels=channels) yaml_spec = {"dependencies": [package]} @@ -394,6 +403,3 @@ def lock_environment( if str(lockfile) != filepath and fh.read() == data: lockfile.unlink() break - - # return installer._exit_codes[id] - return "1" diff --git a/constructor-manager-cli/src/constructor_manager_cli/installer.py b/constructor-manager-cli/src/constructor_manager_cli/installer.py index e5b371e6..74a7b36e 100644 --- a/constructor-manager-cli/src/constructor_manager_cli/installer.py +++ b/constructor-manager-cli/src/constructor_manager_cli/installer.py @@ -46,7 +46,11 @@ def __init__(self) -> None: # -------------------------- Public API ------------------------------ def install( - self, pkg_list: Sequence[str], *, prefix: Optional[str] = None + self, + pkg_list: Sequence[str], + *, + prefix: Optional[str] = None, + block: bool = False, ) -> job_id: """Install packages in `pkg_list` into `prefix`. @@ -140,6 +144,13 @@ def _process_queue(self): self._processes[job_id] = popen if block: stdout, stderr = popen.communicate() + + if isinstance(stdout, bytes): + stdout = stdout.decode() + + if isinstance(stderr, bytes): + stderr = stderr.decode() + # TODO: Write to file for logging and querying status return_code = popen.returncode self._on_process_finished(job_id, return_code, 0) @@ -149,17 +160,27 @@ def _process_queue(self): res = {"stdout": stdout} return res else: - for line in popen.stdout: - self._on_output(line) - # print(line, end='') + # for line in popen.stdout: + # self._on_output(line) + # # print(line, end='') + + # for line in popen.stderr: + # self._on_output(line) + # # print(line, end='') - for line in popen.stderr: - self._on_output(line) - # print(line, end='') + # popen.stdout.close() + # popen.stderr.close() + # return_code = popen.wait() + + while True: + output = popen.stdout.readline() + if isinstance(output, bytes): + output = output.decode() - popen.stdout.close() - popen.stderr.close() - return_code = popen.wait() + if output == '' and popen.poll() is not None: + break + + return_code = popen.poll() self._on_process_finished(job_id, return_code, 0) @@ -244,7 +265,10 @@ def _get_args(self, arg0, pkg_list: Sequence[str], prefix: Optional[str]): for channel in self._channels: cmd.extend(["-c", channel]) - return tuple(cmd + list(pkg_list)) + if pkg_list: + cmd.extend(pkg_list) + + return tuple(cmd) # -------------------------- Public API ---------------------------------- def info(self) -> Dict[Any, Any]: @@ -253,120 +277,128 @@ def info(self) -> Dict[Any, Any]: res = cast(dict, self._queue_args(args, block=True)) return res - def create( - self, pkg_list: Sequence[str], *, prefix: Optional[str] = None - ) -> job_id: - """Create a new conda environment with `pkg_list` in `prefix`. + def list(self, prefix: str, block=False) -> job_id: + """List packages for `prefix`. Parameters ---------- - pkg_list : Sequence[str] - List of packages to install on new environment. - prefix : str, optional - Optional prefix for new environment. + prefix : str + Prefix from which to list packages. Returns ------- job_id : int ID that can be used to cancel the process. """ - return self._queue_args(self._get_create_args(pkg_list, prefix)) + return self._queue_args( + ("list", "--prefix", str(prefix), "--json"), block=block + ) - def remove(self, prefix) -> job_id: - """Remove a conda environment in `prefix`. + def lock( + self, + env_path: str, + platforms: Optional[Tuple[str, ...]] = None, + lockfile: Optional[str] = None, + block: bool = False, + ) -> job_id: + """List packages for `prefix`. Parameters ---------- - prefix : str, optional - Optional prefix for new environment. + prefix : str + Prefix from which to list packages. Returns ------- job_id : int ID that can be used to cancel the process. """ - return self._queue_args(self._get_remove_args(prefix)) + args = ["-f", env_path] + if platforms: + for platform in platforms: + args.extend(["-p", platform]) - def install( - self, pkg_list: Sequence[str], *, prefix: Optional[str] = None + if lockfile: + args.extend(["--lockfile", lockfile]) + + return self._queue_args(args, bin="conda-lock", block=block) + + def create( + self, prefix: str, *, pkg_list: Sequence[str] = (), block: bool = False ) -> job_id: - """Install packages in `pkg_list` into `prefix`. + """Create a new conda environment with `pkg_list` in `prefix`. Parameters ---------- pkg_list : Sequence[str] - List of packages to install. - prefix : Optional[str], optional - Optional prefix to install packages into. + List of packages to install on new environment. + prefix : str, optional + Optional prefix for new environment. Returns ------- job_id : int ID that can be used to cancel the process. """ - return self._queue_args(self._get_install_args(pkg_list, prefix)) + return self._queue_args(self._get_create_args(pkg_list, prefix), block=block) - def uninstall( - self, pkg_list: Sequence[str], *, prefix: Optional[str] = None - ) -> job_id: - """Uninstall packages in `pkg_list` from `prefix`. + def remove(self, prefix: str, block: bool = False) -> job_id: + """Remove a conda environment in `prefix`. Parameters ---------- - pkg_list : Sequence[str] - List of packages to uninstall. - prefix : Optional[str], optional - Optional prefix from which to uninstall packages. + prefix : str, optional + Optional prefix for new environment. Returns ------- job_id : int ID that can be used to cancel the process. """ - return self._queue_args(self._get_uninstall_args(pkg_list, prefix)) + return self._queue_args(self._get_remove_args(prefix), block=block) - def list(self, prefix: str, block=True) -> job_id: - """List packages for `prefix`. + def install( + self, + pkg_list: Sequence[str], + *, + prefix: Optional[str] = None, + block: bool = False, + ) -> job_id: + """Install packages in `pkg_list` into `prefix`. Parameters ---------- - prefix : str - Prefix from which to list packages. + pkg_list : Sequence[str] + List of packages to install. + prefix : Optional[str], optional + Optional prefix to install packages into. Returns ------- job_id : int ID that can be used to cancel the process. """ - return self._queue_args( - ("list", "--prefix", str(prefix), "--json"), block=block - ) + return self._queue_args(self._get_install_args(pkg_list, prefix), block=block) - def lock( + def uninstall( self, - env_path: str, - platforms: Optional[Tuple[str, ...]] = None, - lockfile: Optional[str] = None, + pkg_list: Sequence[str], + *, + prefix: Optional[str] = None, block: bool = False, ) -> job_id: - """List packages for `prefix`. + """Uninstall packages in `pkg_list` from `prefix`. Parameters ---------- - prefix : str - Prefix from which to list packages. + pkg_list : Sequence[str] + List of packages to uninstall. + prefix : Optional[str], optional + Optional prefix from which to uninstall packages. Returns ------- job_id : int ID that can be used to cancel the process. """ - args = ["-f", env_path] - if platforms: - for platform in platforms: - args.extend(["-p", platform]) - - if lockfile: - args.extend(["--lockfile", lockfile]) - - return self._queue_args(args, bin="conda-lock", block=block) + return self._queue_args(self._get_uninstall_args(pkg_list, prefix), block=block) diff --git a/constructor-manager-cli/src/constructor_manager_cli/main.py b/constructor-manager-cli/src/constructor_manager_cli/main.py index d62faf60..4c147f54 100644 --- a/constructor-manager-cli/src/constructor_manager_cli/main.py +++ b/constructor-manager-cli/src/constructor_manager_cli/main.py @@ -231,8 +231,6 @@ def _handle_excecute(args, lock, lock_created=None): lock_created: bool, optional Whether the lock was created or not, by default ``None``. """ - _execute(args, lock, lock_created) - return try: _execute(args, lock, lock_created) except Exception as e: diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/log.py b/constructor-manager-cli/src/constructor_manager_cli/tests/test_actions.py similarity index 100% rename from constructor-manager-cli/src/constructor_manager_cli/utils/log.py rename to constructor-manager-cli/src/constructor_manager_cli/tests/test_actions.py diff --git a/constructor-manager-cli/src/constructor_manager_cli/tests/test_installer.py b/constructor-manager-cli/src/constructor_manager_cli/tests/test_installer.py index e69de29b..5a806ec8 100644 --- a/constructor-manager-cli/src/constructor_manager_cli/tests/test_installer.py +++ b/constructor-manager-cli/src/constructor_manager_cli/tests/test_installer.py @@ -0,0 +1,63 @@ +import shutil +import random + +import pytest + +from constructor_manager_cli.installer import CondaInstaller +from constructor_manager_cli.utils.conda import get_base_prefix, get_prefix_by_name + + +def test_conda_installer_info(): + data = CondaInstaller().info() + assert data["conda_prefix"] == str(get_base_prefix()) + + +def test_conda_installer_list(): + pkgs = CondaInstaller().list(get_base_prefix()) + pkg_names = [pkg["name"] for pkg in pkgs] + assert pkgs + assert "conda" in pkg_names + assert "mamba" in pkg_names + + +@pytest.mark.parametrize("use_mamba", [True, False]) +def test_conda_installer_create_remove(use_mamba): + prefix = get_prefix_by_name( + f"test-constructor-manager-{random.randint(1000, 9999)}" + ) + if prefix.exists() and prefix.is_dir(): + shutil.rmtree(prefix) + shutil.rmtree(prefix, ignore_errors=True) + + installer = CondaInstaller(use_mamba=use_mamba) + # Create + _ = installer.create(prefix=prefix, block=True) + assert prefix.exists() + # assert installer._exit_codes[job_id] == 0 + + # Remove + _ = installer.remove(prefix=prefix, block=True) + assert not prefix.exists() + # assert installer._exit_codes[job_id] == 0 + + +@pytest.mark.parametrize("use_mamba", [True, False]) +def test_conda_installer_install_uninstall(use_mamba): + prefix = get_base_prefix() + pkg = "loghub" + installer = CondaInstaller(use_mamba=use_mamba) + + # Install + _ = installer.install([pkg], prefix=prefix, block=True) + + pkgs = installer.list(prefix=prefix) + print("HELLO", pkgs) + pkg_names = [pkg["name"] for pkg in pkgs] + assert pkg in pkg_names + + # Uninstall + _ = installer.uninstall([pkg], prefix=prefix, block=True) + pkgs = installer.list(prefix=prefix) + print("HELLO 2", pkgs) + pkg_names = [pkg["name"] for pkg in pkgs] + assert pkg not in pkg_names diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/anaconda.py b/constructor-manager-cli/src/constructor_manager_cli/utils/anaconda.py index f45b8ab6..ba03910d 100644 --- a/constructor-manager-cli/src/constructor_manager_cli/utils/anaconda.py +++ b/constructor-manager-cli/src/constructor_manager_cli/utils/anaconda.py @@ -2,10 +2,9 @@ import re from functools import lru_cache -from typing import List +from typing import List, Optional from constructor_manager_cli.defaults import DEFAULT_CHANNEL -from constructor_manager_cli.utils.packages import normalized_name from constructor_manager_cli.utils.request import get_request from constructor_manager_cli.utils.versions import sort_versions @@ -37,7 +36,7 @@ def conda_package_data( @lru_cache def conda_package_versions( package_name: str, - build: str, + build: Optional[str] = None, channels: List[str] = [DEFAULT_CHANNEL], reverse: bool = False, ) -> List[str]: @@ -85,27 +84,3 @@ def conda_package_versions( ) return sort_versions(set(versions), reverse=reverse) - - -@lru_cache -def plugin_versions( - url: str, -) -> List[str]: - """Return information on package plugins from endpoint in json. - - Parameters - ---------- - url : str - Url to json endpoint. - - Returns - ------- - list of str - Package versions. - """ - response = get_request(url) - plugins = [] - for key in response.json(): - plugins.append(normalized_name(key)) - - return list(sorted(plugins)) diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/conda.py b/constructor-manager-cli/src/constructor_manager_cli/utils/conda.py index 328ac9ce..8e349ba6 100644 --- a/constructor-manager-cli/src/constructor_manager_cli/utils/conda.py +++ b/constructor-manager-cli/src/constructor_manager_cli/utils/conda.py @@ -2,11 +2,9 @@ import sys from pathlib import Path -from typing import List, Optional, Tuple, Union +from typing import Optional, Tuple from conda.models.match_spec import MatchSpec # type: ignore -from constructor_manager_cli.installer import CondaInstaller -from constructor_manager_cli.utils.packages import sentinel_file_name def parse_conda_version_spec(package: str) -> Tuple[str, str, str]: @@ -41,35 +39,6 @@ def parse_conda_version_spec(package: str) -> Tuple[str, str, str]: return package_name, version, build_string -def check_if_constructor_app(package_name, path=None) -> bool: - """FIXME:""" - if path is None: - path = Path(sys.prefix) - - return (path.parent.parent / sentinel_file_name(package_name)).exists() - - -def check_if_conda_environment( - path: Union[Optional[Path], Optional[str]] = None -) -> bool: - """Check if path is a conda environment. - - Parameters - ---------- - path : str, optional - If `None` then check if current process is running in a conda - environment. - - Returns - ------- - bool - """ - if path is None: - path = Path(sys.prefix) - - return (Path(path) / "conda-meta" / "history").exists() - - def get_base_prefix() -> Path: """Get base conda prefix. @@ -110,39 +79,3 @@ def get_prefix_by_name(name: Optional[str] = None) -> Path: return base_prefix else: return base_prefix / "envs" / name - - -def list_packages2(prefix: str, plugins: Optional[List] = None): - """List packages in a conda environment. - - Optionally filter by plugin list. - - Parameters - ---------- - prefix : str - The conda environment prefix. - plugins : list, optional - List of plugins to filter by. - - Returns - ------- - list - List of packages in the environment. - """ - packages = [] - for path in (Path(prefix) / "conda-meta").iterdir(): - if path.is_file() and path.name.endswith(".json"): - parts = path.name.rsplit("-") - b, v, name = parts[-1], parts[-2], "-".join(parts[:-2]) - b = b.replace(".json", "") - packages.append((name, v, b)) - - if plugins is not None: - packages = [pkg for pkg in packages if pkg[0] in plugins] - - return packages - - -def list_packages(): - installer = CondaInstaller() - print("hello", installer.list(sys.prefix)) diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/io.py b/constructor-manager-cli/src/constructor_manager_cli/utils/io.py index 1f720d54..b592c197 100644 --- a/constructor-manager-cli/src/constructor_manager_cli/utils/io.py +++ b/constructor-manager-cli/src/constructor_manager_cli/utils/io.py @@ -2,9 +2,8 @@ import json import os -import sys from pathlib import Path -from typing import List, Tuple +from typing import List, Tuple, Union from constructor_manager_cli.utils.conda import get_prefix_by_name from constructor_manager_cli.utils.packages import ( @@ -14,12 +13,12 @@ def get_broken_envs(package_name: str) -> List[Path]: - """TODO + """Find broken conda application environments. Parameters ---------- - package_name : str - Name of the package. + package_name : str + Name of the package. Returns ------- @@ -49,7 +48,7 @@ def get_broken_envs(package_name: str) -> List[Path]: def get_installed_versions(package_name: str) -> List[Tuple[str, ...]]: - """Check the current conda prefix for installed versions. + """Check the current conda installation for installed versions in environments. Parameters ---------- @@ -87,30 +86,55 @@ def get_installed_versions(package_name: str) -> List[Tuple[str, ...]]: return versions -def check_if_constructor_app(package_name, path=None) -> bool: - """""" - if path is None: - path = Path(sys.prefix) - - return (path.parent.parent / sentinel_file_name(package_name)).exists() +def get_sentinel_path(prefix: Union[Path, str], package_name: str) -> Path: + """Sentinel file path for a given environment. + Parameters + ---------- + prefix : Path or str + Path to the environment. + package_name : str + Name of the package. -def get_sentinel_path(prefix, package_name): - """""" + Returns + ------- + Path + Path to the sentinel file. + """ + prefix = Path(prefix) return prefix / "conda-meta" / sentinel_file_name(package_name) -def create_sentinel_file(package_name, version): - """""" +def create_sentinel_file(package_name: str, version: str): + """Create a sentinel file in the corresponding environment for a given + package and version. + + Parameters + ---------- + package_name : str + Name of the package. + version : str + Version of the package. + """ package_name = normalized_name(package_name) env_name = f"{package_name}-{version}" prefix = get_prefix_by_name(env_name) + with open(get_sentinel_path(prefix, package_name), "w") as f: f.write("") -def remove_sentinel_file(package_name, version): - """""" +def remove_sentinel_file(package_name: str, version: str): + """Remove a sentinel file in the corresponding environment for a given + package and version. + + Parameters + ---------- + package_name : str + Name of the package. + version : str + Version of the package. + """ package_name = normalized_name(package_name) env_name = f"{package_name}-{version}" prefix = get_prefix_by_name(env_name) @@ -141,14 +165,20 @@ def get_env_path() -> Path: return get_config_path() / "env" -def save_state_file(application, packages, channel, dev, plugins): +def save_state_file( + application: str, + packages: List[str], + channels: List[str], + dev: bool, + plugins: List[str], +): """""" base_path = get_state_path() base_path.mkdir(parents=True, exist_ok=True) data = { "application": application, "packages": packages, - "channel": channel, + "channels": channels, "dev": dev, "plugins": plugins, } @@ -156,7 +186,7 @@ def save_state_file(application, packages, channel, dev, plugins): f.write(json.dumps(data, indent=4)) -def load_state_file(application): +def load_state_file(application: str): """""" path = get_state_path() / f"{application}.json" data = {} diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/packages.py b/constructor-manager-cli/src/constructor_manager_cli/utils/packages.py index 12e3957e..a9bc73ac 100644 --- a/constructor-manager-cli/src/constructor_manager_cli/utils/packages.py +++ b/constructor-manager-cli/src/constructor_manager_cli/utils/packages.py @@ -41,27 +41,3 @@ def normalized_name(name: str) -> str: The normalized package name. """ return re.sub(r"[-_.]+", "-", name).lower() - - -def get_package_spec(package, version, build): - """Return the package spec for a package. - - Parameters - ---------- - package : str - The name of the package. - version : str - The version of the package. - build : str - The build string of the package. - - Returns - ------- - str - The package spec. - """ - spec = f"{package}=={version}" - if build: - spec = spec + f"=*{build}*" - - return spec diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/pypi.py b/constructor-manager-cli/src/constructor_manager_cli/utils/pypi.py deleted file mode 100644 index 73fea3f8..00000000 --- a/constructor-manager-cli/src/constructor_manager_cli/utils/pypi.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Convenience functions for searching packages in `pypi.org`.""" - -import re -from functools import lru_cache -from typing import List - -from constructor_manager_cli.utils.request import get_request - - -@lru_cache -def pypi_package_data(package_name: str) -> dict: - """Return package information on package. - - Parameters - ---------- - package_name : str - Name of package. - - Returns - ------- - dict - Package information. - """ - url = f"https://pypi.org/pypi/{package_name}/json" - return get_request(url).json() - - -@lru_cache -def pypi_package_versions(package_name: str) -> List[str]: - """Get available versions of a package on pypi. - - Parameters - ---------- - package_name : str - Name of the package. - - Returns - ------- - list - Versions available on pypi. - """ - url = f"https://pypi.org/simple/{package_name}" - html = get_request(url).text - return re.findall(f">{package_name}-(.+).tar", html) diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/request.py b/constructor-manager-cli/src/constructor_manager_cli/utils/request.py index b6a0a4c0..c50ba223 100644 --- a/constructor-manager-cli/src/constructor_manager_cli/utils/request.py +++ b/constructor-manager-cli/src/constructor_manager_cli/utils/request.py @@ -1,8 +1,11 @@ from functools import lru_cache +from typing import List import requests + from constructor_manager_cli import __version__ from constructor_manager_cli.defaults import DEFAULT_TIMEOUT +from constructor_manager_cli.utils.packages import normalized_name @lru_cache @@ -14,7 +17,7 @@ def _user_agent() -> str: str User agent string. """ - return f"constructor-updater-{__version__}" + return f"constructor-manager-{__version__}" def get_request(url: str) -> requests.Response: @@ -33,3 +36,27 @@ def get_request(url: str) -> requests.Response: session = requests.Session() session.headers.update({"user-agent": _user_agent()}) return session.get(url, timeout=DEFAULT_TIMEOUT) + + +@lru_cache +def plugin_versions( + url: str, +) -> List[str]: + """Return information on package plugins from endpoint in json. + + Parameters + ---------- + url : str + Url to json endpoint. + + Returns + ------- + list of str + Package versions. + """ + response = get_request(url) + plugins = [] + for key in response.json(): + plugins.append(normalized_name(key)) + + return list(sorted(plugins)) diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_anaconda.py b/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_anaconda.py new file mode 100644 index 00000000..947bd75c --- /dev/null +++ b/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_anaconda.py @@ -0,0 +1,57 @@ +from constructor_manager_cli.utils.anaconda import ( + conda_package_data, + conda_package_versions, +) + + +def test_conda_package_data(): + package = "napari" + data = conda_package_data(package, channel="conda-forge") + expected_keys = [ + "app_entry", + "conda_platforms", + "full_name", + "owner", + "home", + "source_git_url", + "source_git_tag", + "app_type", + "upvoted", + "id", + "app_summary", + "public", + "revision", + "files", + "package_types", + "description", + "releases", + "html_url", + "builds", + "watchers", + "dev_url", + "name", + "license", + "versions", + "url", + "created_at", + "modified_at", + "latest_version", + "summary", + "license_url", + "doc_url", + ] + assert data["name"] == package + for key in expected_keys: + assert key in data + + +def test_conda_package_versions(): + versions = conda_package_versions("napari", channels=("conda-forge", "napari")) + assert versions[0] == "0.2.12" + + +def test_conda_package_versions_build(): + versions = conda_package_versions( + "napari", "*pyside*", channels=("conda-forge", "napari") + ) + assert "0.2.12" not in versions diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_conda.py b/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_conda.py new file mode 100644 index 00000000..1599a5e8 --- /dev/null +++ b/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_conda.py @@ -0,0 +1,32 @@ +import sys + +import pytest + +from constructor_manager_cli.utils.conda import ( + get_base_prefix, + get_prefix_by_name, + parse_conda_version_spec, +) + + +def test_get_base_prefix(): + assert str(get_base_prefix()) == sys.prefix + + +def test_get_prefix_by_name(): + assert get_prefix_by_name() == get_base_prefix() + assert get_prefix_by_name("base") == get_base_prefix() + assert get_prefix_by_name("foo") == get_base_prefix() / "envs" / "foo" + + +@pytest.mark.parametrize( + "test_input,expected", + [ + ("napari=0.4.1=*pyside*", ("napari", "0.4.1", "*pyside*")), + ("napari=0.4.1", ("napari", "0.4.1", "")), + ("napari=*=*pyside*", ("napari", "", "*pyside*")), + ("napari", ("napari", "", "")), + ], +) +def test_parse_conda_version_spec(test_input, expected): + assert parse_conda_version_spec(test_input) == expected diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_packages.py b/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_packages.py new file mode 100644 index 00000000..b0977fe2 --- /dev/null +++ b/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_packages.py @@ -0,0 +1,23 @@ +import pytest + +from constructor_manager_cli.utils.packages import ( + sentinel_file_name, + normalized_name, +) + + +def test_sentinel_file_name(): + assert sentinel_file_name("foo") == ".foo_is_bundled_constructor" + + +@pytest.mark.parametrize( + "test_input,expected", + [ + ("napari_svg", "napari-svg"), + ("napari.svg", "napari-svg"), + ("napari-SVG", "napari-svg"), + ("napari_SVG.2", "napari-svg-2"), + ], +) +def test_normalized_name(test_input, expected): + assert normalized_name(test_input) == expected diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_request.py b/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_request.py new file mode 100644 index 00000000..49ec05bc --- /dev/null +++ b/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_request.py @@ -0,0 +1,25 @@ +from requests import Response + + +from constructor_manager_cli.utils.request import ( + _user_agent, + get_request, + plugin_versions, +) + + +def test_user_agent(): + from constructor_manager_cli import __version__ + + assert _user_agent() == f"constructor-manager-{__version__}" + + +def test_get_request(): + r = get_request("https://google.com") + assert isinstance(r, Response) + + +def test_plugin_versions(): + data = plugin_versions("https://api.napari-hub.org/plugins") + assert len(data) > 0 + assert "napari-svg" in data diff --git a/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_versions.py b/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_versions.py index 86247030..4fd877f9 100644 --- a/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_versions.py +++ b/constructor-manager-cli/src/constructor_manager_cli/utils/tests/test_versions.py @@ -1,5 +1,9 @@ import pytest # type: ignore -from constructor_manager_cli.utils.versions import is_stable_version +from constructor_manager_cli.utils.versions import ( + is_stable_version, + parse_version, + sort_versions, +) @pytest.mark.parametrize( @@ -19,3 +23,32 @@ ) def test_is_stable_version(test_input, expected): assert is_stable_version(test_input) == expected + + +@pytest.mark.parametrize( + "test_input", + [ + "0.4", + "0.4.15", + "0.4.15rc1", + "0.4.15dev0", + "0.4.15beta", + "0.4.15alfa", + "whatever", + ], +) +def test_parse_version(test_input): + str(parse_version(test_input)) == test_input + + +@pytest.mark.parametrize( + "test_input,expected", + [ + ( + ["0.4.1", "0.4.1dev1", "0.1.1", "0.4.1dev0", "0.4.1a1"], + ["0.1.1", "0.4.1dev0", "0.4.1dev1", "0.4.1a1", "0.4.1"], + ), + ], +) +def test_sort_versions(test_input, expected): + assert sort_versions(test_input) == expected