Skip to content

Commit

Permalink
Merge pull request #121 from dalthviz/issue_59_p2
Browse files Browse the repository at this point in the history
Initial napari specific classes for package installer related logic
  • Loading branch information
dalthviz authored Dec 30, 2024
2 parents 5d54882 + b7faa65 commit 05728e0
Show file tree
Hide file tree
Showing 8 changed files with 797 additions and 656 deletions.
41 changes: 36 additions & 5 deletions .github/workflows/test_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ concurrency:

jobs:
test:
name: ${{ matrix.platform }}, py${{ matrix.python-version }}, napari ${{ matrix.napari }}
name: ${{ matrix.platform }}, py${{ matrix.python-version }}, napari ${{ matrix.napari }}, ${{ matrix.tool }}
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, windows-latest, macos-13]
python-version: ["3.9", "3.10", "3.11"]
napari: ["latest", "repo"]
tool: ["pip", "conda"]
exclude:
# TODO: Remove when we have a napari release with the plugin manager changes
- napari: "latest"
Expand All @@ -43,11 +44,19 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
- name: Set up Python ${{ matrix.python-version }} - pip
if: matrix.tool == 'pip'
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Set up Python ${{ matrix.python-version }} - conda
if: matrix.tool == 'conda'
uses: conda-incubator/setup-miniconda@v3
with:
miniforge-version: latest
python-version: ${{ matrix.python-version }}

- uses: tlambert03/setup-qt-libs@v1

# strategy borrowed from vispy for installing opengl libs on windows
Expand All @@ -58,14 +67,36 @@ jobs:
powershell gl-ci-helpers/appveyor/install_opengl.ps1
if (Test-Path -Path "C:\Windows\system32\opengl32.dll" -PathType Leaf) {Exit 0} else {Exit 1}
- name: Install dependencies
- name: Install dependencies without tox-conda
if: matrix.tool == 'pip'
run: |
python -m pip install --upgrade pip
pip install setuptools tox tox-gh-actions
python -m pip install setuptools tox tox-gh-actions
- name: Install dependencies including tox-conda
if: matrix.tool == 'conda'
shell: bash -el {0}
run: |
python -m pip install --upgrade pip
python -m pip install setuptools 'tox<4' tox-gh-actions tox-conda
- name: Test with tox - pip
if: matrix.tool == 'pip'
uses: aganders3/headless-gui@v2
with:
run: python -m tox -vv
env:
PYVISTA_OFF_SCREEN: True # required for opengl on windows
NAPARI: ${{ matrix.napari }}
FORCE_COLOR: 1
# PySide6 only functional with Python 3.10+
TOX_SKIP_ENV: ".*py39-PySide6.*"

- name: Test with tox
- name: Test with tox - conda
if: matrix.tool == 'conda'
uses: aganders3/headless-gui@v2
with:
shell: bash -el {0}
run: python -m tox -vv
env:
PYVISTA_OFF_SCREEN: True # required for opengl on windows
Expand Down
23 changes: 23 additions & 0 deletions napari_plugin_manager/_tests/test_base_installer_process.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import pytest

from napari_plugin_manager.base_qt_package_installer import (
AbstractInstallerTool,
)


def test_not_implemented_methods():
tool = AbstractInstallerTool('install', ['requests'])
with pytest.raises(NotImplementedError):
tool.executable()

with pytest.raises(NotImplementedError):
tool.arguments()

with pytest.raises(NotImplementedError):
tool.environment()

with pytest.raises(NotImplementedError):
tool.constraints()

with pytest.raises(NotImplementedError):
tool.available()
108 changes: 69 additions & 39 deletions napari_plugin_manager/_tests/test_installer_process.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import logging
import re
import sys
import time
from pathlib import Path
from types import MethodType
Expand All @@ -7,13 +9,16 @@
import pytest
from qtpy.QtCore import QProcessEnvironment

from napari_plugin_manager.qt_package_installer import (
import napari_plugin_manager.base_qt_package_installer as bqpi
from napari_plugin_manager.base_qt_package_installer import (
AbstractInstallerTool,
CondaInstallerTool,
InstallerActions,
InstallerQueue,
InstallerTools,
PipInstallerTool,
)
from napari_plugin_manager.qt_package_installer import (
NapariCondaInstallerTool,
NapariInstallerQueue,
NapariPipInstallerTool,
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -55,25 +60,20 @@ def environment(self, env=None):
return QProcessEnvironment.systemEnvironment()


def test_not_implemented_methods():
tool = AbstractInstallerTool('install', ['requests'])
with pytest.raises(NotImplementedError):
tool.executable()

with pytest.raises(NotImplementedError):
tool.arguments()

with pytest.raises(NotImplementedError):
tool.environment()

with pytest.raises(NotImplementedError):
tool.available()


def test_pip_installer_tasks(qtbot, tmp_virtualenv: 'Session', monkeypatch):
installer = InstallerQueue()
def test_pip_installer_tasks(
qtbot, tmp_virtualenv: 'Session', monkeypatch, caplog
):
caplog.set_level(logging.DEBUG, logger=bqpi.__name__)
installer = NapariInstallerQueue()
monkeypatch.setattr(
PipInstallerTool, "executable", lambda *a: tmp_virtualenv.creator.exe
NapariPipInstallerTool,
"executable",
lambda *a: tmp_virtualenv.creator.exe,
)
monkeypatch.setattr(
NapariPipInstallerTool,
"origins",
("https://pypi.org/simple",),
)
with qtbot.waitSignal(installer.allFinished, timeout=20000):
installer.install(
Expand Down Expand Up @@ -138,10 +138,34 @@ def test_pip_installer_tasks(qtbot, tmp_virtualenv: 'Session', monkeypatch):
)


def test_pip_installer_invalid_action(tmp_virtualenv: 'Session', monkeypatch):
installer = NapariInstallerQueue()
monkeypatch.setattr(
NapariPipInstallerTool,
"executable",
lambda *a: tmp_virtualenv.creator.exe,
)
invalid_action = 'Invalid Action'
with pytest.raises(
ValueError, match=f"Action '{invalid_action}' not supported!"
):
item = installer._build_queue_item(
tool=InstallerTools.PIP,
action=invalid_action,
pkgs=['pip-install-test'],
prefix=None,
origins=(),
process=installer._create_process(),
)
installer._queue_item(item)


def test_installer_failures(qtbot, tmp_virtualenv: 'Session', monkeypatch):
installer = InstallerQueue()
installer = NapariInstallerQueue()
monkeypatch.setattr(
PipInstallerTool, "executable", lambda *a: tmp_virtualenv.creator.exe
NapariPipInstallerTool,
"executable",
lambda *a: tmp_virtualenv.creator.exe,
)

# CHECK 1) Errors should trigger finished and allFinished too
Expand Down Expand Up @@ -181,7 +205,7 @@ def test_installer_failures(qtbot, tmp_virtualenv: 'Session', monkeypatch):


def test_cancel_incorrect_job_id(qtbot, tmp_virtualenv: 'Session'):
installer = InstallerQueue()
installer = NapariInstallerQueue()
with qtbot.waitSignal(installer.allFinished, timeout=20000):
job_id = installer.install(
tool=InstallerTools.PIP,
Expand All @@ -192,13 +216,17 @@ def test_cancel_incorrect_job_id(qtbot, tmp_virtualenv: 'Session'):


@pytest.mark.skipif(
not CondaInstallerTool.available(), reason="Conda is not available."
not NapariCondaInstallerTool.available(), reason="Conda is not available."
)
def test_conda_installer(qtbot, tmp_conda_env: Path):
def test_conda_installer(qtbot, caplog, monkeypatch, tmp_conda_env: Path):
if sys.platform == "darwin":
# check handled for `PYTHONEXECUTABLE` env definition on macOS
monkeypatch.setenv("PYTHONEXECUTABLE", sys.executable)
caplog.set_level(logging.DEBUG, logger=bqpi.__name__)
conda_meta = tmp_conda_env / "conda-meta"
glob_pat = "typing-extensions-*.json"
glob_pat_2 = "pyzenhub-*.json"
installer = InstallerQueue()
installer = NapariInstallerQueue()

with qtbot.waitSignal(installer.allFinished, timeout=600_000):
installer.install(
Expand Down Expand Up @@ -277,9 +305,11 @@ def test_conda_installer(qtbot, tmp_conda_env: Path):


def test_installer_error(qtbot, tmp_virtualenv: 'Session', monkeypatch):
installer = InstallerQueue()
installer = NapariInstallerQueue()
monkeypatch.setattr(
PipInstallerTool, "executable", lambda *a: 'not-a-real-executable'
NapariPipInstallerTool,
"executable",
lambda *a: 'not-a-real-executable',
)
with qtbot.waitSignal(installer.allFinished, timeout=600_000):
installer.install(
Expand All @@ -289,10 +319,10 @@ def test_installer_error(qtbot, tmp_virtualenv: 'Session', monkeypatch):


@pytest.mark.skipif(
not CondaInstallerTool.available(), reason="Conda is not available."
not NapariCondaInstallerTool.available(), reason="Conda is not available."
)
def test_conda_installer_wait_for_finished(qtbot, tmp_conda_env: Path):
installer = InstallerQueue()
installer = NapariInstallerQueue()

with qtbot.waitSignal(installer.allFinished, timeout=600_000):
installer.install(
Expand All @@ -309,8 +339,8 @@ def test_conda_installer_wait_for_finished(qtbot, tmp_conda_env: Path):


def test_constraints_are_in_sync():
conda_constraints = sorted(CondaInstallerTool.constraints())
pip_constraints = sorted(PipInstallerTool.constraints())
conda_constraints = sorted(NapariCondaInstallerTool.constraints())
pip_constraints = sorted(NapariPipInstallerTool.constraints())

assert len(conda_constraints) == len(pip_constraints)

Expand All @@ -324,15 +354,15 @@ def test_constraints_are_in_sync():


def test_executables():
assert CondaInstallerTool.executable()
assert PipInstallerTool.executable()
assert NapariCondaInstallerTool.executable()
assert NapariPipInstallerTool.executable()


def test_available():
assert str(CondaInstallerTool.available())
assert PipInstallerTool.available()
assert str(NapariCondaInstallerTool.available())
assert NapariPipInstallerTool.available()


def test_unrecognized_tool():
with pytest.raises(ValueError):
InstallerQueue().install(tool='shrug', pkgs=[])
NapariInstallerQueue().install(tool='shrug', pkgs=[])
3 changes: 2 additions & 1 deletion napari_plugin_manager/_tests/test_qt_plugin_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
)

from napari_plugin_manager import qt_plugin_dialog
from napari_plugin_manager.qt_package_installer import InstallerActions
from napari_plugin_manager.base_qt_package_installer import InstallerActions

N_MOCKED_PLUGINS = 2

Expand Down Expand Up @@ -509,6 +509,7 @@ def test_install_pypi_constructor(
plugin_dialog.installer.processFinished, timeout=60_000
):
widget.action_button.click()
qtbot.wait(5000)
else:
widget.action_button.click()
assert mock.called
Expand Down
Loading

0 comments on commit 05728e0

Please sign in to comment.