napari-plugin-manager 0.1.0a0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. napari-plugin-manager-0.1.0a0/.github/workflows/test_and_deploy.yml +107 -0
  2. napari-plugin-manager-0.1.0a0/.gitignore +85 -0
  3. napari-plugin-manager-0.1.0a0/.pre-commit-config.yaml +27 -0
  4. napari-plugin-manager-0.1.0a0/LICENSE +29 -0
  5. napari-plugin-manager-0.1.0a0/PKG-INFO +99 -0
  6. napari-plugin-manager-0.1.0a0/README.md +38 -0
  7. napari-plugin-manager-0.1.0a0/napari_plugin_manager/__init__.py +0 -0
  8. napari-plugin-manager-0.1.0a0/napari_plugin_manager/_tests/__init__.py +0 -0
  9. napari-plugin-manager-0.1.0a0/napari_plugin_manager/_tests/conftest.py +18 -0
  10. napari-plugin-manager-0.1.0a0/napari_plugin_manager/_tests/test_installer_process.py +226 -0
  11. napari-plugin-manager-0.1.0a0/napari_plugin_manager/_tests/test_qt_plugin_dialog.py +374 -0
  12. napari-plugin-manager-0.1.0a0/napari_plugin_manager/_version.py +4 -0
  13. napari-plugin-manager-0.1.0a0/napari_plugin_manager/qt_package_installer.py +570 -0
  14. napari-plugin-manager-0.1.0a0/napari_plugin_manager/qt_plugin_dialog.py +1086 -0
  15. napari-plugin-manager-0.1.0a0/napari_plugin_manager.egg-info/PKG-INFO +99 -0
  16. napari-plugin-manager-0.1.0a0/napari_plugin_manager.egg-info/SOURCES.txt +20 -0
  17. napari-plugin-manager-0.1.0a0/napari_plugin_manager.egg-info/dependency_links.txt +1 -0
  18. napari-plugin-manager-0.1.0a0/napari_plugin_manager.egg-info/requires.txt +11 -0
  19. napari-plugin-manager-0.1.0a0/napari_plugin_manager.egg-info/top_level.txt +1 -0
  20. napari-plugin-manager-0.1.0a0/pyproject.toml +188 -0
  21. napari-plugin-manager-0.1.0a0/setup.cfg +4 -0
  22. napari-plugin-manager-0.1.0a0/tox.ini +47 -0
@@ -0,0 +1,107 @@
1
+ # This workflow will upload a Python Package using Twine when a release is created
2
+ # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3
+
4
+ name: test and deploy
5
+
6
+ on:
7
+ push:
8
+ branches:
9
+ - main
10
+ tags:
11
+ - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
12
+ pull_request:
13
+ branches:
14
+ - main
15
+ workflow_dispatch:
16
+
17
+ concurrency:
18
+ # Concurrency group that uses the workflow name and PR number if available
19
+ # or commit SHA as a fallback. If a new build is triggered under that
20
+ # concurrency group while a previous build is running it will be canceled.
21
+ # Repeated pushes to a PR will cancel all previous builds, while multiple
22
+ # merges to main will not cancel.
23
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
24
+ cancel-in-progress: true
25
+
26
+ jobs:
27
+ test:
28
+ name: ${{ matrix.platform }}, py${{ matrix.python-version }}, napari ${{ matrix.napari }}
29
+ runs-on: ${{ matrix.platform }}
30
+ strategy:
31
+ fail-fast: false
32
+ matrix:
33
+ platform: [ubuntu-latest, windows-latest, macos-latest]
34
+ python-version: ["3.9", "3.10", "3.11"]
35
+ napari: ["latest", "repo"]
36
+ exclude:
37
+ # TODO: Remove when we have a napari release with the plugin manager changes
38
+ - napari: "latest"
39
+ # TODO: PyQt / PySide wheels missing
40
+ - python-version: "3.11"
41
+ platform: "windows-latest"
42
+
43
+ steps:
44
+ - uses: actions/checkout@v3
45
+
46
+ - name: Set up Python ${{ matrix.python-version }}
47
+ uses: actions/setup-python@v4
48
+ with:
49
+ python-version: ${{ matrix.python-version }}
50
+
51
+ - uses: tlambert03/setup-qt-libs@v1
52
+
53
+ # strategy borrowed from vispy for installing opengl libs on windows
54
+ - name: Install Windows OpenGL
55
+ if: runner.os == 'Windows'
56
+ run: |
57
+ git clone --depth 1 https://github.com/pyvista/gl-ci-helpers.git
58
+ powershell gl-ci-helpers/appveyor/install_opengl.ps1
59
+ if (Test-Path -Path "C:\Windows\system32\opengl32.dll" -PathType Leaf) {Exit 0} else {Exit 1}
60
+
61
+ - name: Install dependencies
62
+ run: |
63
+ python -m pip install --upgrade pip
64
+ pip install setuptools tox tox-gh-actions
65
+
66
+ - name: Test with tox
67
+ uses: aganders3/headless-gui@v1
68
+ with:
69
+ run: python -m tox -vv
70
+ env:
71
+ PYVISTA_OFF_SCREEN: True # required for opengl on windows
72
+ NAPARI: ${{ matrix.napari }}
73
+ FORCE_COLOR: 1
74
+ # PySide6 only functional with Python 3.10+
75
+ TOX_SKIP_ENV: ".*py39-PySide6.*"
76
+
77
+ - name: pre-commit
78
+ uses: pre-commit/action@v3.0.0
79
+
80
+ - name: Coverage
81
+ uses: codecov/codecov-action@v3
82
+
83
+ deploy:
84
+ # this will run when you have tagged a commit, starting with "v*"
85
+ # and requires that you have put your twine API key in your
86
+ # github secrets (see readme for details)
87
+ needs: [test]
88
+ runs-on: ubuntu-latest
89
+ if: contains(github.ref, 'tags')
90
+ steps:
91
+ - uses: actions/checkout@v2
92
+ - name: Set up Python
93
+ uses: actions/setup-python@v2
94
+ with:
95
+ python-version: "3.x"
96
+ - name: Install dependencies
97
+ run: |
98
+ python -m pip install --upgrade pip
99
+ pip install -U setuptools twine build
100
+ - name: Build and publish
101
+ env:
102
+ TWINE_USERNAME: __token__
103
+ TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }}
104
+ run: |
105
+ git tag
106
+ python -m build
107
+ twine upload dist/*
@@ -0,0 +1,85 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ env/
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # PyInstaller
28
+ # Usually these files are written by a python script from a template
29
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .coverage
41
+ .coverage.*
42
+ .cache
43
+ nosetests.xml
44
+ coverage.xml
45
+ *,cover
46
+ .hypothesis/
47
+ .napari_cache
48
+
49
+ # Translations
50
+ *.mo
51
+ *.pot
52
+
53
+ # Django stuff:
54
+ *.log
55
+ local_settings.py
56
+
57
+ # Flask instance folder
58
+ instance/
59
+
60
+ # Sphinx documentation
61
+ docs/_build/
62
+
63
+ # MkDocs documentation
64
+ /site/
65
+
66
+ # PyBuilder
67
+ target/
68
+
69
+ # IPython Notebook
70
+ .ipynb_checkpoints
71
+
72
+ # pyenv
73
+ .python-version
74
+
75
+ # OS
76
+ .DS_Store
77
+
78
+ # written by setuptools_scm
79
+ */_version.py
80
+
81
+ # pycharm stuff
82
+ .idea/
83
+
84
+ # ruff stuff
85
+ .ruff_cache/
@@ -0,0 +1,27 @@
1
+ repos:
2
+ - repo: https://github.com/MarcoGorelli/absolufy-imports
3
+ rev: v0.3.1
4
+ hooks:
5
+ - id: absolufy-imports
6
+ - repo: https://github.com/hadialqattan/pycln
7
+ rev: v2.1.3
8
+ hooks:
9
+ - id: pycln
10
+ - repo: https://github.com/psf/black
11
+ rev: 22.12.0
12
+ hooks:
13
+ - id: black
14
+ pass_filenames: true
15
+ - repo: https://github.com/charliermarsh/ruff-pre-commit
16
+ rev: v0.0.237
17
+ hooks:
18
+ - id: ruff
19
+ - repo: https://github.com/seddonym/import-linter
20
+ rev: v1.7.0
21
+ hooks:
22
+ - id: import-linter
23
+ stages: [manual]
24
+ - repo: https://github.com/python-jsonschema/check-jsonschema
25
+ rev: 0.21.0
26
+ hooks:
27
+ - id: check-github-workflows
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2018, Napari
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.1
2
+ Name: napari-plugin-manager
3
+ Version: 0.1.0a0
4
+ Summary: Install plugins for napari, in napari.
5
+ Author-email: napari team <napari-steering-council@googlegroups.com>
6
+ License: BSD 3-Clause License
7
+
8
+ Copyright (c) 2018, Napari
9
+ All rights reserved.
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are met:
13
+
14
+ * Redistributions of source code must retain the above copyright notice, this
15
+ list of conditions and the following disclaimer.
16
+
17
+ * Redistributions in binary form must reproduce the above copyright notice,
18
+ this list of conditions and the following disclaimer in the documentation
19
+ and/or other materials provided with the distribution.
20
+
21
+ * Neither the name of the copyright holder nor the names of its
22
+ contributors may be used to endorse or promote products derived from
23
+ this software without specific prior written permission.
24
+
25
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
29
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+
36
+ Project-URL: homepage, https://github.com/napari/napari-plugin-manager
37
+ Classifier: Development Status :: 3 - Alpha
38
+ Classifier: Environment :: X11 Applications :: Qt
39
+ Classifier: Intended Audience :: Education
40
+ Classifier: Intended Audience :: Science/Research
41
+ Classifier: License :: OSI Approved :: BSD License
42
+ Classifier: Programming Language :: C
43
+ Classifier: Programming Language :: Python
44
+ Classifier: Programming Language :: Python :: 3 :: Only
45
+ Classifier: Programming Language :: Python :: 3.8
46
+ Classifier: Programming Language :: Python :: 3.9
47
+ Classifier: Programming Language :: Python :: 3.10
48
+ Classifier: Topic :: Scientific/Engineering
49
+ Classifier: Topic :: Scientific/Engineering :: Visualization
50
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
51
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
52
+ Classifier: Topic :: Utilities
53
+ Classifier: Operating System :: Microsoft :: Windows
54
+ Classifier: Operating System :: POSIX
55
+ Classifier: Operating System :: Unix
56
+ Classifier: Operating System :: MacOS
57
+ Requires-Python: >=3.8
58
+ Description-Content-Type: text/markdown
59
+ Provides-Extra: testing
60
+ License-File: LICENSE
61
+
62
+ # napari-plugin-manager
63
+
64
+ > WIP, under active development
65
+
66
+ [![License](https://img.shields.io/pypi/l/napari-plugin-manager.svg?color=green)](https://github.com/napari/napari-plugin-manager/raw/main/LICENSE)
67
+ [![PyPI](https://img.shields.io/pypi/v/napari-plugin-manager.svg?color=green)](https://pypi.org/project/napari-plugin-manager)
68
+ [![Python Version](https://img.shields.io/pypi/pyversions/napari-plugin-manager.svg?color=green)](https://python.org)
69
+ [![tests](https://github.com/napari/napari-plugin-manager/workflows/test_and_deploy/badge.svg)](https://github.com/napari/napari-plugin-manager/actions)
70
+ [![codecov](https://codecov.io/gh/napari/napari-plugin-manager/branch/main/graph/badge.svg)](https://codecov.io/gh/napari/napari-plugin-manager)
71
+
72
+ A plugin that adds a plugin manager to [napari].
73
+
74
+ ----------------------------------
75
+
76
+ ## Installation
77
+
78
+ You can install `napari-plugin-manager` via [pip]:
79
+
80
+ pip install napari-plugin-manager
81
+
82
+ ## License
83
+
84
+ Distributed under the terms of the [BSD-3] license,
85
+ "napari-plugin-manager" is free and open source software
86
+
87
+ ## Issues
88
+
89
+ If you encounter any problems, please [file an issue] along with a detailed description.
90
+
91
+ [napari]: https://github.com/napari/napari
92
+ [Cookiecutter]: https://github.com/audreyr/cookiecutter
93
+ [@napari]: https://github.com/napari
94
+ [BSD-3]: http://opensource.org/licenses/BSD-3-Clause
95
+ [file an issue]: https://github.com/napari/napari-plugin-manager/issues
96
+ [napari]: https://github.com/napari/napari
97
+ [tox]: https://tox.readthedocs.io/en/latest/
98
+ [pip]: https://pypi.org/project/pip/
99
+ [PyPI]: https://pypi.org/
@@ -0,0 +1,38 @@
1
+ # napari-plugin-manager
2
+
3
+ > WIP, under active development
4
+
5
+ [![License](https://img.shields.io/pypi/l/napari-plugin-manager.svg?color=green)](https://github.com/napari/napari-plugin-manager/raw/main/LICENSE)
6
+ [![PyPI](https://img.shields.io/pypi/v/napari-plugin-manager.svg?color=green)](https://pypi.org/project/napari-plugin-manager)
7
+ [![Python Version](https://img.shields.io/pypi/pyversions/napari-plugin-manager.svg?color=green)](https://python.org)
8
+ [![tests](https://github.com/napari/napari-plugin-manager/workflows/test_and_deploy/badge.svg)](https://github.com/napari/napari-plugin-manager/actions)
9
+ [![codecov](https://codecov.io/gh/napari/napari-plugin-manager/branch/main/graph/badge.svg)](https://codecov.io/gh/napari/napari-plugin-manager)
10
+
11
+ A plugin that adds a plugin manager to [napari].
12
+
13
+ ----------------------------------
14
+
15
+ ## Installation
16
+
17
+ You can install `napari-plugin-manager` via [pip]:
18
+
19
+ pip install napari-plugin-manager
20
+
21
+ ## License
22
+
23
+ Distributed under the terms of the [BSD-3] license,
24
+ "napari-plugin-manager" is free and open source software
25
+
26
+ ## Issues
27
+
28
+ If you encounter any problems, please [file an issue] along with a detailed description.
29
+
30
+ [napari]: https://github.com/napari/napari
31
+ [Cookiecutter]: https://github.com/audreyr/cookiecutter
32
+ [@napari]: https://github.com/napari
33
+ [BSD-3]: http://opensource.org/licenses/BSD-3-Clause
34
+ [file an issue]: https://github.com/napari/napari-plugin-manager/issues
35
+ [napari]: https://github.com/napari/napari
36
+ [tox]: https://tox.readthedocs.io/en/latest/
37
+ [pip]: https://pypi.org/project/pip/
38
+ [PyPI]: https://pypi.org/
@@ -0,0 +1,18 @@
1
+ import pytest
2
+ from qtpy.QtWidgets import QDialog, QInputDialog, QMessageBox
3
+
4
+
5
+ @pytest.fixture(autouse=True)
6
+ def _block_message_box(monkeypatch, request):
7
+ def raise_on_call(*_, **__):
8
+ raise RuntimeError("exec_ call") # pragma: no cover
9
+
10
+ monkeypatch.setattr(QMessageBox, "exec_", raise_on_call)
11
+ monkeypatch.setattr(QMessageBox, "critical", raise_on_call)
12
+ monkeypatch.setattr(QMessageBox, "information", raise_on_call)
13
+ monkeypatch.setattr(QMessageBox, "question", raise_on_call)
14
+ monkeypatch.setattr(QMessageBox, "warning", raise_on_call)
15
+ monkeypatch.setattr(QInputDialog, "getText", raise_on_call)
16
+ # QDialogs can be allowed via a marker; only raise if not decorated
17
+ if "enabledialog" not in request.keywords:
18
+ monkeypatch.setattr(QDialog, "exec_", raise_on_call)
@@ -0,0 +1,226 @@
1
+ import re
2
+ import sys
3
+ import time
4
+ from pathlib import Path
5
+ from types import MethodType
6
+ from typing import TYPE_CHECKING
7
+
8
+ import pytest
9
+ from qtpy.QtCore import QProcessEnvironment
10
+
11
+ from napari_plugin_manager.qt_package_installer import (
12
+ AbstractInstallerTool,
13
+ CondaInstallerTool,
14
+ InstallerQueue,
15
+ InstallerTools,
16
+ PipInstallerTool,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from virtualenv.run import Session
21
+
22
+
23
+ @pytest.fixture
24
+ def tmp_virtualenv(tmp_path) -> 'Session':
25
+ virtualenv = pytest.importorskip('virtualenv')
26
+
27
+ cmd = [str(tmp_path), '--no-setuptools', '--no-wheel', '--activators', '']
28
+ return virtualenv.cli_run(cmd)
29
+
30
+
31
+ @pytest.fixture
32
+ def tmp_conda_env(tmp_path):
33
+ import subprocess
34
+
35
+ try:
36
+ subprocess.check_output(
37
+ [
38
+ CondaInstallerTool.executable(),
39
+ 'create',
40
+ '-yq',
41
+ '-p',
42
+ str(tmp_path),
43
+ '--override-channels',
44
+ '-c',
45
+ 'conda-forge',
46
+ f'python={sys.version_info.major}.{sys.version_info.minor}',
47
+ ],
48
+ stderr=subprocess.STDOUT,
49
+ text=True,
50
+ timeout=300,
51
+ )
52
+ except subprocess.CalledProcessError as exc:
53
+ print(exc.output)
54
+ raise
55
+
56
+ return tmp_path
57
+
58
+
59
+ def test_pip_installer_tasks(qtbot, tmp_virtualenv: 'Session', monkeypatch):
60
+ installer = InstallerQueue()
61
+ monkeypatch.setattr(
62
+ PipInstallerTool, "executable", lambda *a: tmp_virtualenv.creator.exe
63
+ )
64
+ with qtbot.waitSignal(installer.allFinished, timeout=20000):
65
+ installer.install(
66
+ tool=InstallerTools.PIP,
67
+ pkgs=['pip-install-test'],
68
+ )
69
+ installer.install(
70
+ tool=InstallerTools.PIP,
71
+ pkgs=['typing-extensions'],
72
+ )
73
+ job_id = installer.install(
74
+ tool=InstallerTools.PIP,
75
+ pkgs=['requests'],
76
+ )
77
+ assert isinstance(job_id, int)
78
+ installer.cancel(job_id)
79
+
80
+ assert not installer.hasJobs()
81
+
82
+ pkgs = 0
83
+ for pth in tmp_virtualenv.creator.libs:
84
+ if (pth / 'pip_install_test').exists():
85
+ pkgs += 1
86
+ if (pth / 'typing_extensions.py').exists():
87
+ pkgs += 1
88
+ if (pth / 'requests').exists():
89
+ raise AssertionError('requests got installed')
90
+
91
+ assert pkgs >= 2, 'package was not installed'
92
+
93
+ with qtbot.waitSignal(installer.allFinished, timeout=10000):
94
+ job_id = installer.uninstall(
95
+ tool=InstallerTools.PIP,
96
+ pkgs=['pip-install-test'],
97
+ )
98
+
99
+ for pth in tmp_virtualenv.creator.libs:
100
+ assert not (
101
+ pth / 'pip_install_test'
102
+ ).exists(), 'pip_install_test still installed'
103
+
104
+ assert not installer.hasJobs()
105
+
106
+
107
+ def _assert_exit_code_not_zero(
108
+ self, exit_code=None, exit_status=None, error=None
109
+ ):
110
+ errors = []
111
+ if exit_code == 0:
112
+ errors.append("- 'exit_code' should have been non-zero!")
113
+ if error is not None:
114
+ errors.append("- 'error' should have been None!")
115
+ if errors:
116
+ raise AssertionError("\n".join(errors))
117
+ return self._on_process_done_original(exit_code, exit_status, error)
118
+
119
+
120
+ class _NonExistingTool(AbstractInstallerTool):
121
+ def executable(self):
122
+ return f"this-tool-does-not-exist-{hash(time.time())}"
123
+
124
+ def arguments(self):
125
+ return ()
126
+
127
+ def environment(self, env=None):
128
+ return QProcessEnvironment.systemEnvironment()
129
+
130
+
131
+ def _assert_error_used(self, exit_code=None, exit_status=None, error=None):
132
+ errors = []
133
+ if error is None:
134
+ errors.append("- 'error' should have been populated!")
135
+ if exit_code is not None:
136
+ errors.append("- 'exit_code' should not have been populated!")
137
+ if errors:
138
+ raise AssertionError("\n".join(errors))
139
+ return self._on_process_done_original(exit_code, exit_status, error)
140
+
141
+
142
+ def test_installer_failures(qtbot, tmp_virtualenv: 'Session', monkeypatch):
143
+ installer = InstallerQueue()
144
+ monkeypatch.setattr(
145
+ PipInstallerTool, "executable", lambda *a: tmp_virtualenv.creator.exe
146
+ )
147
+
148
+ # CHECK 1) Errors should trigger finished and allFinished too
149
+ with qtbot.waitSignal(installer.allFinished, timeout=10000):
150
+ installer.install(
151
+ tool=InstallerTools.PIP,
152
+ pkgs=[f'this-package-does-not-exist-{hash(time.time())}'],
153
+ )
154
+
155
+ # Keep a reference before we monkey patch stuff
156
+ installer._on_process_done_original = installer._on_process_done
157
+
158
+ # CHECK 2) Non-existing packages should return non-zero
159
+ monkeypatch.setattr(
160
+ installer,
161
+ "_on_process_done",
162
+ MethodType(_assert_exit_code_not_zero, installer),
163
+ )
164
+ with qtbot.waitSignal(installer.allFinished, timeout=10000):
165
+ installer.install(
166
+ tool=InstallerTools.PIP,
167
+ pkgs=[f'this-package-does-not-exist-{hash(time.time())}'],
168
+ )
169
+
170
+ # CHECK 3) Non-existing tools should fail to start
171
+ monkeypatch.setattr(
172
+ installer,
173
+ "_on_process_done",
174
+ MethodType(_assert_error_used, installer),
175
+ )
176
+ monkeypatch.setattr(installer, "_get_tool", lambda *a: _NonExistingTool)
177
+ with qtbot.waitSignal(installer.allFinished, timeout=10000):
178
+ installer.install(
179
+ tool=_NonExistingTool,
180
+ pkgs=[f'this-package-does-not-exist-{hash(time.time())}'],
181
+ )
182
+
183
+
184
+ @pytest.mark.skipif(
185
+ not CondaInstallerTool.available(), reason="Conda is not available."
186
+ )
187
+ def test_conda_installer(qtbot, tmp_conda_env: Path):
188
+ installer = InstallerQueue()
189
+
190
+ with qtbot.waitSignal(installer.allFinished, timeout=600_000):
191
+ installer.install(
192
+ tool=InstallerTools.CONDA,
193
+ pkgs=['typing-extensions'],
194
+ prefix=tmp_conda_env,
195
+ )
196
+
197
+ conda_meta = tmp_conda_env / "conda-meta"
198
+ glob_pat = "typing-extensions-*.json"
199
+
200
+ assert not installer.hasJobs()
201
+ assert list(conda_meta.glob(glob_pat))
202
+
203
+ with qtbot.waitSignal(installer.allFinished, timeout=600_000):
204
+ installer.uninstall(
205
+ tool=InstallerTools.CONDA,
206
+ pkgs=['typing-extensions'],
207
+ prefix=tmp_conda_env,
208
+ )
209
+
210
+ assert not installer.hasJobs()
211
+ assert not list(conda_meta.glob(glob_pat))
212
+
213
+
214
+ def test_constraints_are_in_sync():
215
+ conda_constraints = sorted(CondaInstallerTool.constraints())
216
+ pip_constraints = sorted(PipInstallerTool.constraints())
217
+
218
+ assert len(conda_constraints) == len(pip_constraints)
219
+
220
+ name_re = re.compile(r"([a-z0-9_\-]+).*")
221
+ for conda_constraint, pip_constraint in zip(
222
+ conda_constraints, pip_constraints
223
+ ):
224
+ conda_name = name_re.match(conda_constraint).group(1)
225
+ pip_name = name_re.match(pip_constraint).group(1)
226
+ assert conda_name == pip_name