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.
- napari-plugin-manager-0.1.0a0/.github/workflows/test_and_deploy.yml +107 -0
- napari-plugin-manager-0.1.0a0/.gitignore +85 -0
- napari-plugin-manager-0.1.0a0/.pre-commit-config.yaml +27 -0
- napari-plugin-manager-0.1.0a0/LICENSE +29 -0
- napari-plugin-manager-0.1.0a0/PKG-INFO +99 -0
- napari-plugin-manager-0.1.0a0/README.md +38 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager/__init__.py +0 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager/_tests/__init__.py +0 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager/_tests/conftest.py +18 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager/_tests/test_installer_process.py +226 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager/_tests/test_qt_plugin_dialog.py +374 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager/_version.py +4 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager/qt_package_installer.py +570 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager/qt_plugin_dialog.py +1086 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager.egg-info/PKG-INFO +99 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager.egg-info/SOURCES.txt +20 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager.egg-info/dependency_links.txt +1 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager.egg-info/requires.txt +11 -0
- napari-plugin-manager-0.1.0a0/napari_plugin_manager.egg-info/top_level.txt +1 -0
- napari-plugin-manager-0.1.0a0/pyproject.toml +188 -0
- napari-plugin-manager-0.1.0a0/setup.cfg +4 -0
- 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
|
+
[](https://github.com/napari/napari-plugin-manager/raw/main/LICENSE)
|
|
67
|
+
[](https://pypi.org/project/napari-plugin-manager)
|
|
68
|
+
[](https://python.org)
|
|
69
|
+
[](https://github.com/napari/napari-plugin-manager/actions)
|
|
70
|
+
[](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
|
+
[](https://github.com/napari/napari-plugin-manager/raw/main/LICENSE)
|
|
6
|
+
[](https://pypi.org/project/napari-plugin-manager)
|
|
7
|
+
[](https://python.org)
|
|
8
|
+
[](https://github.com/napari/napari-plugin-manager/actions)
|
|
9
|
+
[](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/
|
|
File without changes
|
|
File without changes
|
|
@@ -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
|