From 28a176b000269e4b243107bd1313b310349005a3 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Wed, 5 Jul 2023 13:01:51 +0100 Subject: [PATCH 01/15] purge changelog see https://github.com/paskino/qt-elements/releases instead --- CHANGELOG.md | 66 ---------------------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index ee2eb77..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,66 +0,0 @@ - -## v0.7.0 -- Adds `MainWindowWithProgressDialogs` a base class for a main window, with a menu bar, and ability to create ProgressTimerDialogs. -- Renames `SessionMainWindow` to `MainWindowWithSessionManagement` -- Removed addToMenu method from `MainWindowWithProgressDialogs` (and therefore `MainWindowWithSessionManagement` which inherits from it) as it didn't do anything, and the user can add their own method and call it if needed. -- Check if session folder loaded from QSettings exists before writing to it. - -## v0.6.0 -- Use pip install in the conda recipe, instead of setup.py install -- Adds the following new methods to UIFormWidget, FormWidget, FormDialog and FormDockWidget: - - `saveAllWidgetStates` - Saves the state of all widgets in the form. This can be used to restore the state of the widgets using the restoreAllSavedWidgetStates method. - - - `restoreAllSavedWidgetStates` - Restores the state of all widgets in the form to the state saved by the saveAllWidgetStates method. If the saveAllWidgetStates method has not been called, this method will do nothing. - - - `getAllWidgetStates` - Returns a dictionary of the state of all widgets in the form. - - - `getWidgetState` - Returns the state of the widget. - - - `applyWidgetState` - Applies the given state to the widget with the given name. - - - `applyWidgetStates` - Applies the given state to the widgets in the form given by the keys of the state dictionary. - -- Adds an example of a FormDialog: `dialog_save_state_example.py` where all of the widgets are saved and restored if you press "Ok", whereas the previous values of the dialog are restored if you press "Cancel". -- Adds unit tests to cover: `saveAllWidgetStates`, `restoreAllSavedWidgetStates`, `getAllWidgetStates`, `getWidgetState`, `applyWidgetState`, `applyWidgetStates` -- setup.py: - - Always normalise the version from git describe to pep440 -- Adds `SessionsMainWindow.py` - which is a base class for our apps which create a session folder where any files generated in the app are saved, and provides the ability to permanently save and reload sessions. -- Adds `SessionsMainWindow_example.py` - an example of using the SessionsMainWindow - you can run this example, change the state of widgets in the form, save the session, reload the session and see the state of the widgets be restored. -- Adds `SessionsDialogs.py` - the dialogs used by the SessionsMainWindow.py -- Adds `io.py` - contains method for zipping a directory, used by SessionsMainWindow.py -- Adds unit tests to cover `SessionsDialogs.py`, `io.py`, and a large proportion of `SessionsMainWindow.py` - -## v0.5.0 -* Add getWidgets method to FormWidget, FormDockWidget and FormDialog -* Add setWidgetVisibility method to FormWidget, FormDockWidget and FormDialog - -## v0.4.0 -* Add ReOrderableListWidget and ReOrderableListDockWidget -* Add example of using the ReOrderableListWidget -* Add getWidget method to FormWidget, FormDockWidget and FormDialog - -## v0.3.0 -* Add ProgressTimerDialog and example. -* Delete ErrorObserver, as this is relevant to VTK, not Qt, so it has been moved to the CILViewer repo. - -## v0.2.2 -* By default, automatically number the tab titles in a StackedWidget - -## v0.2.1 -* Adds methods to add titles and separators to the FormWidget -* Adds UIMultiStepWidget -* Adds version string from git describe -* Added unit tests and a way to disable interactive tests if run on conda build - -## v0.2.0 -* Adds methods to add titles and separators to the FormWidget -* Adds version string from git describe -* Added unit tests and a way to disable interactive tests if run on conda build -* Adds UIMultiStepWidget - -## v0.1.0 -* Fixes FormDockWidget by setting a FormWidget as the DockWidget's widget -* Allows a widget to be added to a FormWidget, which spans the width of the form -* Adds a factory for creating a StackedWidget -* Adds UISliderWidget - From 13ce71f03467b16a09e878e241f8a131f85b7a07 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Wed, 5 Jul 2023 13:05:15 +0100 Subject: [PATCH 02/15] minify licen[cs]e --- LICENSE | 208 +++----------------------------------------------------- 1 file changed, 10 insertions(+), 198 deletions(-) diff --git a/LICENSE b/LICENSE index 261eeb9..655d8f2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,13 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Copyright 2020 Edoardo Pasca - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - 1. Definitions. + http://www.apache.org/licenses/LICENSE-2.0 - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. From a57a443b10e0ebc3b2d80a5f8002c89f69b274d9 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Wed, 5 Jul 2023 16:56:16 +0100 Subject: [PATCH 03/15] migrate setup.py => pyproject.toml --- .gitignore | 7 +- eqt/__init__.py | 11 ++- ...MainWindowWithSessionManagement_example.py | 13 ++- pyproject.toml | 59 +++++++++++++ setup.py | 88 ------------------- test/test_basic.py | 9 +- 6 files changed, 80 insertions(+), 107 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 4fe082b..a7ee797 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -eqt/version.py +*.py[cod] +__pycache__/ +/eqt/_dist_ver.py +/*.egg*/ +/build/ +/dist/ diff --git a/eqt/__init__.py b/eqt/__init__.py index d33d233..812265a 100644 --- a/eqt/__init__.py +++ b/eqt/__init__.py @@ -1,3 +1,10 @@ -from eqt import version as dversion +# version detector. Precedence: installed dist, git, 'UNKNOWN' +try: + from ._dist_ver import __version__ +except ImportError: + try: + from setuptools_scm import get_version -__version__ = dversion.version + __version__ = get_version(root="..", relative_to=__file__) + except (ImportError, LookupError): + __version__ = "UNKNOWN" diff --git a/examples/MainWindowWithSessionManagement_example.py b/examples/MainWindowWithSessionManagement_example.py index 2c22c17..fe9e322 100644 --- a/examples/MainWindowWithSessionManagement_example.py +++ b/examples/MainWindowWithSessionManagement_example.py @@ -6,17 +6,14 @@ from eqt.ui.MainWindowWithSessionManagement import MainWindowWithSessionManagement from eqt.ui.UIFormWidget import FormWidget from eqt.ui.UISliderWidget import UISliderWidget - -from eqt.version import version - -__version__ = version +from eqt import __version__ class ExampleMainWindowWithSessionManagement(MainWindowWithSessionManagement): ''' This is an example of a MainWindowWithSessionManagement. It is used to show how to use the MainWindowWithSessionManagement class. - + To test out this example, change the state of the widgets and save the session. Then load the session again and check if the state of the widgets is the same as before. ''' @@ -37,7 +34,7 @@ def getSessionConfig(self): ''' This function is called when the user wants to save the current session. We need to add the state of the widgets to the config. - + Returns ------- config : dict @@ -87,8 +84,8 @@ def add_every_widget_to_form(form): def create_main_window(): window = ExampleMainWindowWithSessionManagement( - "Example Session Main Window{}".format(__version__), "Example0", settings_name="Example0") - + f"Example Session Main Window{__version__}", "Example0", settings_name="Example0") + return window diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b4c4e31 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,59 @@ +[build-system] +requires = ["setuptools>=42", "setuptools_scm[toml]>=3.4"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +write_to = "eqt/_dist_ver.py" +write_to_template = "__version__ = '{version}'\n" + +[tool.setuptools.packages.find] +exclude = ["test"] + +[project.urls] +documentation = "https://github.com/paskino/qt-elements#readme" +repository = "https://github.com/paskino/qt-elements" +changelog = "https://github.com/paskino/qt-elements/releases" + +[project] +name = "eqt" +dynamic = ["version"] +authors = [{name = "Edoardo Pasca", email = "edoardo.pasca@stfc.ac.uk"}] +maintainers = [{name = "Casper da Costa-Luis", email = "casper.dcl@physics.org"}] +description = "A number of templates and tools to develop Qt GUIs with Python effectively" +readme = "README.rst" +requires-python = ">=3.7" +license = {text = "Apache-2.0"} +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3 :: Only"] +dependencies = ["pyside2"] + +[tool.flake8] +max_line_length = 99 +exclude = [".git", "__pycache__", "build", "dist", ".eggs"] + +[tool.yapf] +spaces_before_comment = [15, 20] +arithmetic_precedence_indication = true +allow_split_before_dict_value = false +coalesce_brackets = true +column_limit = 99 +each_dict_entry_on_separate_line = false +space_between_ending_comma_and_closing_bracket = false +split_before_named_assigns = false +split_before_closing_bracket = false +blank_line_before_nested_class_or_def = false + +[tool.isort] +profile = "black" +line_length = 99 +multi_line_output = 4 +known_first_party = ["eqt", "test"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 37d6a08..0000000 --- a/setup.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python3 - -from setuptools import setup -import os -import subprocess - -try: - from sphinx.setup_command import BuildDoc - sphinx_available = False - cmdclass = {'build_sphinx': BuildDoc} - -except ImportError as ie: - sphinx_available = False - cmdclass = {} - -def version2pep440(version): - '''normalises the version from git describe to pep440 - - https://www.python.org/dev/peps/pep-0440/#id29 - ''' - if version[0] == 'v': - version = version[1:] - - if u'-' in version: - v = version.split('-') - v_pep440 = "{}.dev{}".format(v[0], v[1]) - else: - v_pep440 = version - - return v_pep440 - - -git_version_string = subprocess.check_output('git describe', shell=True).decode("utf-8").rstrip()[1:] - - -#with open("README.rst", "r") as fh: -# long_description = fh.read() -long_description = 'A number of templates and tools to develop Qt GUIs with Python effectively.' - -if os.environ.get('CONDA_BUILD', 0) == '1': - # if it is a conda build requirements are going to be satisfied by conda - install_requires = [] - cwd = os.path.join(os.environ.get('RECIPE_DIR'),'..') -else: - install_requires = [ - - 'sphinx', - 'pyside2' - - ] - cwd = os.getcwd() - -version = version2pep440(git_version_string) - - -print ('version {}'.format(version)) -print(cwd) -fname = os.path.join(cwd, 'eqt', 'version.py') -print("write version at: ", fname) - -if os.path.exists(fname): - os.remove(fname) -with open(fname, 'w') as f: - f.write("version = \"{}\"".format(version)) -name = "eqt" - -setup(name=name, - version = version, - description = 'A number of templates and tools to develop Qt GUIs with Python effectively', - long_description = long_description, - author = 'Edoardo Pasca', - author_email = 'edoardo.pasca@stfc.ac.uk', - url = '', - packages = ['eqt', 'eqt.threading', 'eqt.ui'], - license = 'Apache v2.0', - install_requires=install_requires, - classifiers = [ - "Development Status :: 4 - Beta", - "Programming Language :: Python :: 3", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - ], - command_options={ - 'build_sphinx': { - 'project': ('setup.py', name), - 'version': ('setup.py', version), - 'source_dir': ('setup.py', 'doc')}}, - ) diff --git a/test/test_basic.py b/test/test_basic.py index 418fc31..5cc7837 100644 --- a/test/test_basic.py +++ b/test/test_basic.py @@ -6,11 +6,4 @@ from dialog_example_2_test import MainUI, DialogTest class TestModuleBase(unittest.TestCase): - def test_version(self): - try: - from eqt import version as dversion - a = dversion.version - print ("version", a) - self.assertTrue(isinstance(a, str)) - except ImportError as ie: - self.assertFalse(True, str(ie)) + pass From 7e5d3bc8b92b218ab992d84d45cff8c1bc3a7219 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Wed, 5 Jul 2023 17:06:26 +0100 Subject: [PATCH 04/15] ci: update PyPI workflow --- .github/workflows/README.md | 2 +- .github/workflows/pypi_publish.yml | 35 ------------------------------ .github/workflows/test.yml | 29 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 36 deletions(-) delete mode 100644 .github/workflows/pypi_publish.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 71a0d8f..079e884 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -9,7 +9,7 @@ When making an [annotated](https://git-scm.com/book/en/v2/Git-Basics-Tagging) ta When opening or modifying a pull request to main, a single variant is built and tested, but not published. This variant is `python=3.7` and `numpy=1.18`. -## Building the PyPi Package: [pypi_publish.yml](https://github.com/paskino/qt-elements/blob/main/.github/workflows/pypi_publish.yml) +## Building the PyPi Package: [test.yml](./test.yml) This github action builds the pypi package, by using the [deploy-pypi action](https://github.com/casperdcl/deploy-pypi). When pushing to main it is built and checked. diff --git a/.github/workflows/pypi_publish.yml b/.github/workflows/pypi_publish.yml deleted file mode 100644 index a0df473..0000000 --- a/.github/workflows/pypi_publish.yml +++ /dev/null @@ -1,35 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: publish_pypi - -# Controls when the action will run. -on: - # Triggers the workflow on push or pull request events but only for the main branch - push: - branches: [ main ] - tags: - - '**' - pull_request: - branches: [ main ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v1 - - uses: actions/setup-python@v2 - - uses: casperdcl/deploy-pypi@v2 - with: - password: ${{ secrets.EQT_SECRET_TOKEN }} - pip: wheel -w dist/ --no-deps . - # only upload if a tag is pushed (otherwise just build & check) - upload: ${{ github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..bf54ad2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: Test +on: + push: + pull_request: +jobs: + deploy: + name: PyPI Deploy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + - id: dist + uses: casperdcl/deploy-pypi@v2 + with: + build: true + password: ${{ secrets.EQT_SECRET_TOKEN }} + upload: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} + - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + name: Release + run: | + changelog=$(git log --pretty='format:%d%n- %s%n%b---' $(git tag --sort=v:refname | tail -n2 | head -n1)..HEAD) + tag="${GITHUB_REF#refs/tags/}" + gh release create --title "Version $tag" --draft --notes "$changelog" "$tag" dist/${{ steps.dist.outputs.whl }} dist/${{ steps.dist.outputs.targz }} + env: + GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} From 0a356eb75e0e27a645f6ee7613540e77d7ee3b92 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Wed, 5 Jul 2023 17:13:05 +0100 Subject: [PATCH 05/15] deps: add qdarkstyle --- examples/reorderable_list_widget_example.py | 25 +++++++++------------ pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/examples/reorderable_list_widget_example.py b/examples/reorderable_list_widget_example.py index c9f3e5a..4050771 100644 --- a/examples/reorderable_list_widget_example.py +++ b/examples/reorderable_list_widget_example.py @@ -1,12 +1,8 @@ from eqt.ui.ReOrderableListWidget import ReOrderableListWidget from PySide2 import QtWidgets import sys -try: - import qdarkstyle - from qdarkstyle.dark.palette import DarkPalette - HAS_QDARKSTYLE = True -except: - HAS_QDARKSTYLE = False +import qdarkstyle +from qdarkstyle.dark.palette import DarkPalette class MainUI(QtWidgets.QMainWindow): @@ -14,23 +10,22 @@ def __init__(self, parent = None, title=None): QtWidgets.QMainWindow.__init__(self, parent) self.setWindowTitle(title) - + qlist = ReOrderableListWidget() qlist.addItems(["Transmission to Absorption", "Centre of Rotation Correction"]) qlist.addItem("FBP") - + layout = QtWidgets.QHBoxLayout() layout.addWidget(qlist) layout.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize) - + widg = QtWidgets.QWidget() widg.setLayout(layout) self.setCentralWidget(widg) - if HAS_QDARKSTYLE: - style = qdarkstyle.load_stylesheet(palette=DarkPalette) - self.setStyleSheet(style) + style = qdarkstyle.load_stylesheet(palette=DarkPalette) + self.setStyleSheet(style) self.show() @@ -39,8 +34,8 @@ def __init__(self, parent = None, title=None): if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) - + window = MainUI(title="ReOrderableListWidget") window.resize(400, 150) - - sys.exit(app.exec_()) \ No newline at end of file + + sys.exit(app.exec_()) diff --git a/pyproject.toml b/pyproject.toml index b4c4e31..97ac51c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3 :: Only"] -dependencies = ["pyside2"] +dependencies = ["pyside2", "qdarkstyle"] [tool.flake8] max_line_length = 99 From df36485b6ed4584782955403dc3367bfa488c165 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Wed, 5 Jul 2023 17:28:04 +0100 Subject: [PATCH 06/15] build: purge conda packaging --- .github/workflows/README.md | 22 +++++++----- .github/workflows/conda_build_and_publish.yml | 27 -------------- conda/bld.bat | 4 --- conda/build.sh | 8 ----- conda/conda_build_config.yaml | 3 -- conda/meta.yaml | 36 ------------------- 6 files changed, 13 insertions(+), 87 deletions(-) delete mode 100644 .github/workflows/conda_build_and_publish.yml delete mode 100644 conda/bld.bat delete mode 100644 conda/build.sh delete mode 100644 conda/conda_build_config.yaml delete mode 100644 conda/meta.yaml diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 079e884..68e9e5a 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,17 +1,21 @@ # GitHub Actions -## Building the Conda Package: [conda_build_and_publish.yml](https://github.com/paskino/qt-elements/blob/main/.github/workflows/conda_build_and_publish.yml) -This github action builds and tests the conda package, by using the [conda-package-publish-action](https://github.com/paskino/conda-package-publish-action) +Runs automatically on every commit via [test.yml](./test.yml). -When pushing to main *all* variants are built and tested. +## Testing -When making an [annotated](https://git-scm.com/book/en/v2/Git-Basics-Tagging) tag, *all* variants are built, tested and published to the [paskino conda channel for qt-elements](https://anaconda.org/paskino/eqt/files). This package is noarch. +Runs `unittest` suite from the `test` directory. -When opening or modifying a pull request to main, a single variant is built and tested, but not published. This variant is `python=3.7` and `numpy=1.18`. +## Building -## Building the PyPi Package: [test.yml](./test.yml) -This github action builds the pypi package, by using the [deploy-pypi action](https://github.com/casperdcl/deploy-pypi). +Runs automatically after tests (above) succeed. -When pushing to main it is built and checked. +Builds binary (`*.whl`) & source (`*.tar.gz`) distributions. -When making an [annotated](https://git-scm.com/book/en/v2/Git-Basics-Tagging) tag, it is built and published to the [PyPi](https://pypi.org/project/eqt/#description). +## Releasing + +Runs automatically -- when a [tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) is pushed -- after builds (above) succeed. + +Publishes to [PyPI](https://pypi.org/project/eqt) and drafts changelog (release notes) at . + +:warning: The draft notes above need to be manually tidied & approved by a maintainer. diff --git a/.github/workflows/conda_build_and_publish.yml b/.github/workflows/conda_build_and_publish.yml deleted file mode 100644 index 8612ca3..0000000 --- a/.github/workflows/conda_build_and_publish.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: publish_conda - -on: - release: - types: [published] - push: - branches: [ main ] - tags: - - '**' - pull_request: - branches: [ main ] - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: publish-to-conda - uses: paskino/conda-package-publish-action@v1.4.3 - with: - subDir: 'conda' - channels: 'conda-forge' - AnacondaToken: ${{ secrets.ANACONDA_TOKEN }} - publish: ${{ github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') }} - test_all: ${{(github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')) || (github.ref == 'refs/heads/main')}} - convert_win: false - convert_osx: false diff --git a/conda/bld.bat b/conda/bld.bat deleted file mode 100644 index 5950f51..0000000 --- a/conda/bld.bat +++ /dev/null @@ -1,4 +0,0 @@ - -cd %RECIPE_DIR%\.. - -pip install . diff --git a/conda/build.sh b/conda/build.sh deleted file mode 100644 index e68ca1a..0000000 --- a/conda/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# install using pip from the whl file -# pip install https://files.pythonhosted.org/packages/98/7b/98fcb18998153977ed3cb41f9e231ae7a8ceaa1fbda74a4c8e642b69e676/qtelements-1.0.1-py3-none-any.whl - -#$PYTHON -m pip install eqt --no-deps -cd $RECIPE_DIR/.. -pip install . diff --git a/conda/conda_build_config.yaml b/conda/conda_build_config.yaml deleted file mode 100644 index d06cf07..0000000 --- a/conda/conda_build_config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -python: - - 3.6 - - 3.7 diff --git a/conda/meta.yaml b/conda/meta.yaml deleted file mode 100644 index 7c5fa90..0000000 --- a/conda/meta.yaml +++ /dev/null @@ -1,36 +0,0 @@ -package: - name: eqt - version: {{ environ.get('GIT_DESCRIBE_TAG','v')[1:] }} - -source: - path: .. - -build: - skip: True # [py==38 and np==115] - preserve_egg_dir: False - number: {{ environ.get('GIT_DESCRIBE_NUMBER', 0)}} - noarch: python - -test: - source_files: - - ./test - - commands: - - python -c "import os; print ('TESTING IN THIS DIRECTORY' , os.getcwd())" - - python -m unittest discover -s test -v - - -requirements: - build: - - python - - pip - - sphinx - run: - - python - - pyside2 - - qdarkstyle - -about: - home: https://github.com/paskino/qt-elements - license: Apache 2.0 - summary: A number of templates and tools to develop Qt GUI's with Python effectively. From 28ee6bce6b950da58f853e5ea5b41dbe364d2301 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Thu, 6 Jul 2023 10:53:52 +0100 Subject: [PATCH 07/15] tests: migrate to pytest --- .github/workflows/README.md | 2 +- .gitignore | 3 + eqt/io.py | 8 +- test/__init__.py | 81 ++++++++ test/common.py | 63 ------- test/conftest.py | 0 test/dialog_example_2_test.py | 23 +-- test/test_MainWindowWithSessionManagement.py | 43 ++--- test/test_SessionDialogs.py | 44 ++--- test/test__formUI_status_test.py | 187 ++++++++----------- test/test_basic.py | 9 - test/test_io.py | 67 +++---- 12 files changed, 224 insertions(+), 306 deletions(-) delete mode 100644 test/common.py create mode 100644 test/conftest.py delete mode 100644 test/test_basic.py diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 68e9e5a..586b491 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -4,7 +4,7 @@ Runs automatically on every commit via [test.yml](./test.yml). ## Testing -Runs `unittest` suite from the `test` directory. +Runs `pytest`. ## Building diff --git a/.gitignore b/.gitignore index a7ee797..091f8a9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ __pycache__/ /*.egg*/ /build/ /dist/ +/.coverage* +/coverage.xml +/.pytest_cache/ diff --git a/eqt/io.py b/eqt/io.py index cc369be..2a2c1da 100644 --- a/eqt/io.py +++ b/eqt/io.py @@ -6,7 +6,7 @@ def zip_directory(directory, compress=True, **kwargs): ''' Zips a directory, optionally compressing it. - + Parameters ---------- directory : str @@ -14,8 +14,8 @@ def zip_directory(directory, compress=True, **kwargs): compress : bool Whether to compress the directory. ''' - - zipper = zipfile.ZipFile(directory + '.zip', 'a') + + zipper = zipfile.ZipFile(f'{directory}.zip', 'a') if compress: compress_type = zipfile.ZIP_DEFLATED @@ -27,4 +27,4 @@ def zip_directory(directory, compress=True, **kwargs): filepath = os.path.join(r, _file) arcname = os.path.relpath(filepath, directory) zipper.write(filepath, arcname, compress_type=compress_type) - zipper.close() \ No newline at end of file + zipper.close() diff --git a/test/__init__.py b/test/__init__.py index e69de29..9e48711 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -0,0 +1,81 @@ +import os + +from pytest import skip + +from eqt.ui import UIFormFactory, FormDialog +from PySide2 import QtWidgets + +if any(os.getenv(var, '0').lower() in ('1', 'true') for var in ('CONDA_BUILD', 'CI')): + def skip_ci(_): + def inner(*_, **__): + skip("Running in CI (no GUI)") + return inner +else: + def skip_ci(fn): + return fn + +@skip_ci +def test_import(): + from dialog_example_2_test import MainUI, DialogTest + + +class MainUI(QtWidgets.QMainWindow): + + def __init__(self, parent=None): + QtWidgets.QMainWindow.__init__(self, parent) + + pb = QtWidgets.QPushButton(self) + pb.setText("Open Dialog with form layout") + pb.clicked.connect(lambda: self.openFormDialog()) + + layout = QtWidgets.QHBoxLayout() + layout.addWidget(pb) + widg = QtWidgets.QWidget() + widg.setLayout(layout) + self.push_button = pb + + self.setCentralWidget(widg) + + self.show() + + def openFormDialog(self): + + dialog = FormDialog(parent=self, title='Example') + dialog.Ok.clicked.connect(lambda: self.accepted()) + dialog.Cancel.clicked.connect(lambda: self.rejected()) + + # Example on how to add elements to the + # add input 1 as QLineEdit + qlabel = QtWidgets.QLabel(dialog.groupBox) + qlabel.setText("Input 1: ") + qwidget = QtWidgets.QLineEdit(dialog.groupBox) + qwidget.setClearButtonEnabled(True) + # finally add to the form widget + dialog.addWidget(qwidget, qlabel, 'input1', layout='form') + + # add input 2 as QComboBox + qlabel = QtWidgets.QLabel(dialog.groupBox) + qlabel.setText("Input 2: ") + qwidget = QtWidgets.QComboBox(dialog.groupBox) + qwidget.addItem("option 1") + qwidget.addItem("option 2") + qwidget.setCurrentIndex(0) + qwidget.setEnabled(True) + # finally add to the form widget + dialog.addWidget(qwidget, qlabel, 'input2') + + # store a reference + self.dialog = dialog + + dialog.exec() + + def accepted(self): + print("accepted") + print(self.dialog.widgets['input1_field'].text()) + print(self.dialog.widgets['input2_field'].currentText()) + + self.dialog.close() + + def rejected(self): + print("rejected") + self.dialog.close() diff --git a/test/common.py b/test/common.py deleted file mode 100644 index 6142dfc..0000000 --- a/test/common.py +++ /dev/null @@ -1,63 +0,0 @@ -from eqt.ui import UIFormFactory, FormDialog -from PySide2 import QtWidgets - -class MainUI(QtWidgets.QMainWindow): - - def __init__(self, parent=None): - QtWidgets.QMainWindow.__init__(self, parent) - - pb = QtWidgets.QPushButton(self) - pb.setText("Open Dialog with form layout") - pb.clicked.connect(lambda: self.openFormDialog()) - - layout = QtWidgets.QHBoxLayout() - layout.addWidget(pb) - widg = QtWidgets.QWidget() - widg.setLayout(layout) - self.push_button = pb - - self.setCentralWidget(widg) - - self.show() - - def openFormDialog(self): - - dialog = FormDialog(parent=self, title='Example') - dialog.Ok.clicked.connect(lambda: self.accepted()) - dialog.Cancel.clicked.connect(lambda: self.rejected()) - - # Example on how to add elements to the - # add input 1 as QLineEdit - qlabel = QtWidgets.QLabel(dialog.groupBox) - qlabel.setText("Input 1: ") - qwidget = QtWidgets.QLineEdit(dialog.groupBox) - qwidget.setClearButtonEnabled(True) - # finally add to the form widget - dialog.addWidget(qwidget, qlabel, 'input1', layout='form') - - # add input 2 as QComboBox - qlabel = QtWidgets.QLabel(dialog.groupBox) - qlabel.setText("Input 2: ") - qwidget = QtWidgets.QComboBox(dialog.groupBox) - qwidget.addItem("option 1") - qwidget.addItem("option 2") - qwidget.setCurrentIndex(0) - qwidget.setEnabled(True) - # finally add to the form widget - dialog.addWidget(qwidget, qlabel, 'input2') - - # store a reference - self.dialog = dialog - - dialog.exec() - - def accepted(self): - print("accepted") - print(self.dialog.widgets['input1_field'].text()) - print(self.dialog.widgets['input2_field'].currentText()) - - self.dialog.close() - - def rejected(self): - print("rejected") - self.dialog.close() \ No newline at end of file diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..e69de29 diff --git a/test/dialog_example_2_test.py b/test/dialog_example_2_test.py index 09cb43d..48331d7 100644 --- a/test/dialog_example_2_test.py +++ b/test/dialog_example_2_test.py @@ -1,22 +1,13 @@ from PySide2 import QtCore, QtWidgets, QtTest from PySide2.QtTest import QTest from PySide2.QtCore import Qt -import glob import sys -import os -from eqt.ui import UIFormFactory, FormDialog +from eqt.ui import FormDialog import unittest +from . import skip_ci -# skip the tests on GitHub actions -if os.environ.get('CONDA_BUILD', '0') == '1': - skip_as_conda_build = True -else: - skip_as_conda_build = False - -print ("skip_as_conda_build is set to ", skip_as_conda_build) class MainUI(QtWidgets.QMainWindow): - def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) @@ -35,7 +26,6 @@ def __init__(self, parent=None): self.show() def openFormDialog(self): - dialog = FormDialog(parent=self, title='Example') dialog.Ok.clicked.connect(lambda: self.accepted()) dialog.Cancel.clicked.connect(lambda: self.rejected()) @@ -83,7 +73,7 @@ def rejected(self): class DialogTest(unittest.TestCase): '''Test the margarita mixer GUI''' - @unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") + @skip_ci def setUp(self): '''Create the GUI''' super(DialogTest, self).setUp() @@ -99,24 +89,23 @@ def setUp(self): # QTest.mouseClick(self.window.push_button, Qt.LeftButton) # self.dialog = window.dialog - @unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") + @skip_ci def tearDown(self): del self.app super(DialogTest, self).tearDown() - @unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") + @skip_ci def test_close(self): self.window.close() self.assertTrue(True) - @unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") + @skip_ci def test_openclose_dialog(self): QTest.mouseClick(self.window.push_button, Qt.LeftButton) dialog = self.window.dialog print(dialog) dialog.close() - @unittest.skipUnless(skip_as_conda_build, "On conda builds do not do any test with interfaces") def stest_defaults(self): '''Test the GUI in its default state''' diff --git a/test/test_MainWindowWithSessionManagement.py b/test/test_MainWindowWithSessionManagement.py index 7726675..304e8aa 100644 --- a/test/test_MainWindowWithSessionManagement.py +++ b/test/test_MainWindowWithSessionManagement.py @@ -1,37 +1,22 @@ import json import os import shutil -import sys import unittest from datetime import datetime from unittest import mock from unittest.mock import patch from PySide2.QtCore import QSettings, QThreadPool -from PySide2.QtWidgets import QApplication, QMenu, QMenuBar +from PySide2.QtWidgets import QMenu, QMenuBar import eqt from eqt.io import zip_directory from eqt.ui.MainWindowWithSessionManagement import MainWindowWithSessionManagement -# skip the tests on GitHub actions -if os.environ.get('CONDA_BUILD', '0') == '1': - skip_as_conda_build = True -else: - skip_as_conda_build = False +from . import skip_ci -print("skip_as_conda_build is set to ", skip_as_conda_build) -if not skip_as_conda_build: - if not QApplication.instance(): - app = QApplication(sys.argv) - else: - app = QApplication.instance() -else: - skip_test = True - - -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestMainWindowWithSessionManagementInit(unittest.TestCase): ''' Tests the init method of the MainWindowWithSessionManagement class @@ -94,7 +79,7 @@ def test_init_calls_setUpSession(self, mock_setupSession): smw.setupSession.assert_called_once() -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestMainWindowWithSessionManagementMenuBar(unittest.TestCase): ''' Tests the expected menu bar is created @@ -140,7 +125,7 @@ def test_settings_menu_has_expected_actions(self): 1].text(), "Set Session Directory") -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestMainWindowWithSessionManagementSetupSession(unittest.TestCase): ''' Tests the setupSession method of the MainWindowWithSessionManagement class @@ -201,7 +186,7 @@ def test_setupSession_when_sessions_folder_setting_is_not_None_and_session_folde self.smw.createSessionSelector.assert_not_called() -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestMainWindowWithSessionManagementCreateSessionSelector(unittest.TestCase): ''' Tests the createSessionSelector method of the MainWindowWithSessionManagement class @@ -253,7 +238,7 @@ def tearDown(self): shutil.rmtree("Test Folder") -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestMainWindowWithSessionManagementCreateLoadSessionDialog(unittest.TestCase): ''' Tests the createLoadSessionDialog method of the MainWindowWithSessionManagement class @@ -299,7 +284,7 @@ def test_createLoadSessionDialog_connections(self): self.smw.loadSessionNew.assert_called_once() -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestSelectLoadSessionsDirectorySelectedInSessionSelector(unittest.TestCase): ''' Tests the selectLoadSessionsDirectorySelectedInSessionSelector method of the MainWindowWithSessionManagement class @@ -324,7 +309,7 @@ def test_selectLoadSessionsDirectorySelectedInSessionSelector(self): new_session=True) -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestCreateSessionFolder(unittest.TestCase): ''' Tests the createSessionFolder method of the MainWindowWithSessionManagement class @@ -355,7 +340,7 @@ def tearDown(self): shutil.rmtree("Test Folder") -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestLoadSessionConfig(unittest.TestCase): ''' Tests the loadSessionConfig method of the MainWindowWithSessionManagement class @@ -402,7 +387,7 @@ def tearDown(self): shutil.rmtree("Test Folder") -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestSaveSession(unittest.TestCase): ''' Tests: @@ -493,7 +478,7 @@ def test_saveSessionConfigToJson(self): shutil.rmtree("Test Folder") -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestRemoveTempMethods(unittest.TestCase): ''' Tests: @@ -534,7 +519,3 @@ def test_removeTempAndClose(self): self.smw.removeTemp.assert_called_once() self.smw.finishProcess.assert_called_once_with(process_name) self.smw.close.assert_called_once() - - -if __name__ == "__main__": - unittest.main() diff --git a/test/test_SessionDialogs.py b/test/test_SessionDialogs.py index 1a2505b..1ecbac6 100644 --- a/test/test_SessionDialogs.py +++ b/test/test_SessionDialogs.py @@ -1,28 +1,14 @@ from eqt.ui.SessionDialogs import WarningDialog, ErrorDialog, SaveSessionDialog, SessionDirectorySelectionDialog, LoadSessionDialog, AppSettingsDialog import os import unittest -from PySide2.QtWidgets import QApplication, QFileDialog -import sys +from pathlib import Path +from PySide2.QtWidgets import QFileDialog from unittest.mock import patch -# skip the tests on GitHub actions -if os.environ.get('CONDA_BUILD', '0') == '1': - skip_as_conda_build = True -else: - skip_as_conda_build = False +from . import skip_ci -print("skip_as_conda_build is set to ", skip_as_conda_build) -if not skip_as_conda_build: - if not QApplication.instance(): - app = QApplication(sys.argv) - else: - app = QApplication.instance() -else: - skip_test = True - - -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestWarningDialog(unittest.TestCase): def test_init(self): wd = WarningDialog() @@ -40,7 +26,7 @@ def test_init_with_params(self): self.assertEqual(wd.windowTitle(), window_title) -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestErrorDialog(unittest.TestCase): def test_init(self): ed = ErrorDialog() @@ -58,7 +44,7 @@ def test_init_with_params(self): self.assertEqual(ed.windowTitle(), window_title) -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestSaveSessionDialog(unittest.TestCase): def test_init(self): ssd = SaveSessionDialog() @@ -70,7 +56,7 @@ def test_init_with_title_param(self): self.assertEqual(ssd.windowTitle(), title) -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestSessionDirectorySelectionDialog(unittest.TestCase): def test_init(self): sdsd = SessionDirectorySelectionDialog() @@ -120,7 +106,7 @@ def test_browse_dialog_updates_selected_dir_attribute(self): self.assertEqual(sdsd.selected_dir, example_dir) -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestLoadSessionDialog(unittest.TestCase): def test_init(self): lsd = LoadSessionDialog() @@ -132,20 +118,14 @@ def test_init_with_title_param(self): self.assertEqual(lsd.windowTitle(), title) def test_init_with_location_of_session_files_param(self): - location_of_session_files = "C:\\Users\\test_user\\Documents\\test_dir" - lsd = LoadSessionDialog( - location_of_session_files=location_of_session_files) + location_of_session_files = Path("~").expanduser() / "some" / "test_dir" + lsd = LoadSessionDialog(location_of_session_files=location_of_session_files) self.assertEqual(lsd.getWidget("sessions_directory").text( - ), "Currently loading sessions from: C:\\Users\\test_user\\Documents\\test_dir") + ), f"Currently loading sessions from: {location_of_session_files}") -@unittest.skipIf(skip_as_conda_build, "On conda builds do not do any test with interfaces") +@skip_ci class TestAppSettingsDialog(unittest.TestCase): - def test_init(self): asd = AppSettingsDialog() assert asd is not None - - -if __name__ == "__main__": - unittest.main() diff --git a/test/test__formUI_status_test.py b/test/test__formUI_status_test.py index 5e4235e..564688e 100644 --- a/test/test__formUI_status_test.py +++ b/test/test__formUI_status_test.py @@ -1,31 +1,12 @@ -import os -import sys import unittest from unittest import mock from eqt.ui.FormDialog import FormDialog from eqt.ui.UIFormWidget import FormWidget, FormDockWidget from PySide2 import QtWidgets -from PySide2.QtWidgets import QApplication from eqt.ui.UISliderWidget import UISliderWidget - -# skip the tests on GitHub actions -if os.environ.get('CONDA_BUILD', '0') == '1': - skip_test = True -else: - skip_test = False - -print("skip_test is set to ", skip_test) - - -if not skip_test: - if not QApplication.instance(): - app = QApplication(sys.argv) - else: - app = QApplication.instance() -else: - skip_test = True +from . import skip_ci def add_every_widget_to_form(form): @@ -70,17 +51,17 @@ def add_two_widgets_to_form(form): 'test checkbox'), 'CheckBox: ', 'checkBox') -@unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") +@skip_ci class FormDialogStatusTest(unittest.TestCase): - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def setUp(self): self.form = FormDialog() add_every_widget_to_form(self.form) self.simple_form = FormDialog() add_two_widgets_to_form(self.simple_form) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_visibility(self): # Check that the visibility of the widget is saved to the state # Have to use magic mock as we can't set the visibility of the QLabel @@ -99,7 +80,7 @@ def test_getWidgetState_returns_visibility(self): self.assertEqual(self.form.getWidgetState('label_field')[ 'visible'], final_label_visibility) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_enabled_state(self): # Check that the enabled state of the widget is saved to the state @@ -116,7 +97,7 @@ def test_getWidgetState_returns_enabled_state(self): # Test value is saved for all widget types ---------------------------------------------------------------- - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QLabel_value(self): # Check that the value of the QLabel is saved to the state @@ -131,7 +112,7 @@ def test_getWidgetState_returns_QLabel_value(self): self.assertEqual(self.form.getWidgetState( 'label_label')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_value_using_role_parameter_field(self): # Check that the value of the QLabel is saved to the state @@ -146,7 +127,7 @@ def test_getWidgetState_returns_value_using_role_parameter_field(self): self.assertEqual(self.form.getWidgetState( 'label', 'field')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_value_using_role_parameter_label(self): # Check that the value of the QLabel is saved to the state @@ -161,7 +142,7 @@ def test_getWidgetState_returns_value_using_role_parameter_label(self): self.assertEqual(self.form.getWidgetState( 'label', 'field')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_value_using_default_role_parameter(self): # Check that the value of the QLabel is saved to the state initial_label_value = 'test label' @@ -176,7 +157,7 @@ def test_getWidgetState_returns_value_using_default_role_parameter(self): self.assertEqual(self.form.getWidgetState( 'label')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QCheckBox_value(self): # Check that the value of the QCheckBox is saved to the state @@ -191,7 +172,7 @@ def test_getWidgetState_returns_QCheckBox_value(self): self.assertEqual(self.form.getWidgetState( 'checkBox_field')['value'], final_checkbox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QComboBox_value(self): # Check that the value of the QComboBox is saved to the state @@ -209,7 +190,7 @@ def test_getWidgetState_returns_QComboBox_value(self): self.assertEqual(self.form.getWidgetState( 'comboBox_field')['value'], final_combobox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QDoubleSpinBox_value(self): # Check that the value of the QDoubleSpinBox is saved to the state @@ -225,7 +206,7 @@ def test_getWidgetState_returns_QDoubleSpinBox_value(self): self.assertEqual(self.form.getWidgetState('doubleSpinBox_field')[ 'value'], final_doubleSpinBox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QSpinBox_value(self): # Check that the value of the QSpinBox is saved to the state @@ -240,7 +221,7 @@ def test_getWidgetState_returns_QSpinBox_value(self): self.assertEqual(self.form.getWidgetState( 'spinBox_field')['value'], final_spinBox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QSlider_value(self): # Check that the value of the QSlider is saved to the state @@ -255,7 +236,7 @@ def test_getWidgetState_returns_QSlider_value(self): self.assertEqual(self.form.getWidgetState( 'slider_field')['value'], final_slider_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_UISliderWidget_value(self): # Check that the value of the UISliderWidget is returned in the state @@ -270,7 +251,7 @@ def test_getWidgetState_returns_UISliderWidget_value(self): self.assertEqual(self.form.getWidgetState( 'uiSliderWidget_field')['value'], final_slider_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QLineEdit_value(self): # Check that the value of the QLineEdit is saved to the state @@ -286,7 +267,7 @@ def test_getWidgetState_returns_QLineEdit_value(self): self.assertEqual(self.form.getWidgetState( 'lineEdit_field')['value'], final_lineEdit_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QTextEdit_value(self): # Check that the value of the QTextEdit is saved to the state @@ -302,7 +283,7 @@ def test_getWidgetState_returns_QTextEdit_value(self): self.assertEqual(self.form.getWidgetState( 'textEdit_field')['value'], final_textEdit_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QPlainTextEdit_value(self): # Check that the value of the QPlainTextEdit is saved to the state @@ -320,7 +301,7 @@ def test_getWidgetState_returns_QPlainTextEdit_value(self): self.assertEqual(self.form.getWidgetState('plainTextEdit_field')[ 'value'], final_plainTextEdit_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QPushButton_value(self): # Check that the value of the QPushButton is saved to the state @@ -336,7 +317,7 @@ def test_getWidgetState_returns_QPushButton_value(self): self.assertEqual(self.form.getWidgetState( 'button_field')['value'], final_button_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QRadioButton_value(self): # Check that the value of the QRadioButton is saved to the state @@ -351,7 +332,7 @@ def test_getWidgetState_returns_QRadioButton_value(self): self.assertEqual(self.form.getWidgetState( 'radioButton_field')['value'], final_radio_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetStates(self): state_to_set = { 'checkBox_field': {'value': True, 'enabled': False, 'visible': False}, @@ -365,14 +346,14 @@ def test_applyWidgetStates(self): self.assertEqual(self.simple_form.getWidgetState( 'label_field'), state_to_set['label_field']) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState('checkBox_field', state_to_set) self.assertEqual(self.simple_form.getWidgetState( 'checkBox_field'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState_using_role_parameter_field(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState( @@ -380,7 +361,7 @@ def test_applyWidgetState_using_role_parameter_field(self): self.assertEqual(self.simple_form.getWidgetState( 'checkBox', 'field'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState_using_role_parameter_label(self): state_to_set = {'value': 'test the checkbox:', 'enabled': False, 'visible': False} @@ -389,14 +370,14 @@ def test_applyWidgetState_using_role_parameter_label(self): self.assertEqual(self.simple_form.getWidgetState( 'checkBox', 'label'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState_using_role_parameter_default(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState('checkBox', state_to_set) self.assertEqual(self.simple_form.getWidgetState( 'checkBox'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getAllWidgetStates(self): # Check that the state of all widgets is returned @@ -409,7 +390,7 @@ def test_getAllWidgetStates(self): self.assertEqual(self.simple_form.getAllWidgetStates(), expected_state) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_saveAllWidgetStates(self): # Check that the state of all widgets is saved to the state variable @@ -425,7 +406,7 @@ def test_saveAllWidgetStates(self): self.assertEqual( self.simple_form.formWidget.widget_states, expected_state) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_restoreAllSavedWidgetStates(self): # Check that the state of all widgets is restored from the state variable @@ -466,17 +447,17 @@ def test_restoreAllSavedWidgetStates(self): 'label', 'label').isVisible(), state_to_restore['label_label']['visible']) -@unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") +@skip_ci class FormWidgetStateTest(unittest.TestCase): - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def setUp(self): self.form = FormWidget() add_every_widget_to_form(self.form) self.simple_form = FormWidget() add_two_widgets_to_form(self.simple_form) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_visibility(self): # Check that the visibility of the widget is saved to the state # Have to use magic mock as we can't set the visibility of the QLabel @@ -495,7 +476,7 @@ def test_getWidgetState_returns_visibility(self): self.assertEqual(self.form.getWidgetState('label_field')[ 'visible'], final_label_visibility) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_enabled_state(self): # Check that the enabled state of the widget is saved to the state @@ -512,7 +493,7 @@ def test_getWidgetState_returns_enabled_state(self): # Test value is saved for all widget types ---------------------------------------------------------------- - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_value_using_role_parameter_field(self): # Check that the value of the QLabel is saved to the state @@ -527,7 +508,7 @@ def test_getWidgetState_returns_value_using_role_parameter_field(self): self.assertEqual(self.form.getWidgetState( 'label', 'field')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_value_using_role_parameter_label(self): # Check that the value of the QLabel is saved to the state @@ -542,7 +523,7 @@ def test_getWidgetState_returns_value_using_role_parameter_label(self): self.assertEqual(self.form.getWidgetState( 'label', 'field')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_value_using_default_role_parameter(self): # Check that the value of the QLabel is saved to the state initial_label_value = 'test label' @@ -557,7 +538,7 @@ def test_getWidgetState_returns_value_using_default_role_parameter(self): self.assertEqual(self.form.getWidgetState( 'label')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QLabel_value(self): # Check that the value of the QLabel is saved to the state @@ -572,7 +553,7 @@ def test_getWidgetState_returns_QLabel_value(self): self.assertEqual(self.form.getWidgetState( 'label_field')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QCheckBox_value(self): # Check that the value of the QCheckBox is saved to the state @@ -587,7 +568,7 @@ def test_getWidgetState_returns_QCheckBox_value(self): self.assertEqual(self.form.getWidgetState( 'checkBox_field')['value'], final_checkbox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QComboBox_value(self): # Check that the value of the QComboBox is saved to the state @@ -605,7 +586,7 @@ def test_getWidgetState_returns_QComboBox_value(self): self.assertEqual(self.form.getWidgetState( 'comboBox_field')['value'], final_combobox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QDoubleSpinBox_value(self): # Check that the value of the QDoubleSpinBox is saved to the state @@ -621,7 +602,7 @@ def test_getWidgetState_returns_QDoubleSpinBox_value(self): self.assertEqual(self.form.getWidgetState('doubleSpinBox_field')[ 'value'], final_doubleSpinBox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QSpinBox_value(self): # Check that the value of the QSpinBox is saved to the state @@ -636,7 +617,7 @@ def test_getWidgetState_returns_QSpinBox_value(self): self.assertEqual(self.form.getWidgetState( 'spinBox_field')['value'], final_spinBox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QSlider_value(self): # Check that the value of the QSlider is saved to the state @@ -651,7 +632,7 @@ def test_getWidgetState_returns_QSlider_value(self): self.assertEqual(self.form.getWidgetState( 'slider_field')['value'], final_slider_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_UISliderWidget_value(self): # Check that the value of the UISliderWidget is returned in the state @@ -666,7 +647,7 @@ def test_getWidgetState_returns_UISliderWidget_value(self): self.assertEqual(self.form.getWidgetState( 'uiSliderWidget_field')['value'], final_slider_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QLineEdit_value(self): # Check that the value of the QLineEdit is saved to the state @@ -682,7 +663,7 @@ def test_getWidgetState_returns_QLineEdit_value(self): self.assertEqual(self.form.getWidgetState( 'lineEdit_field')['value'], final_lineEdit_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QTextEdit_value(self): # Check that the value of the QTextEdit is saved to the state @@ -698,7 +679,7 @@ def test_getWidgetState_returns_QTextEdit_value(self): self.assertEqual(self.form.getWidgetState( 'textEdit_field')['value'], final_textEdit_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QPlainTextEdit_value(self): # Check that the value of the QPlainTextEdit is saved to the state @@ -716,7 +697,7 @@ def test_getWidgetState_returns_QPlainTextEdit_value(self): self.assertEqual(self.form.getWidgetState('plainTextEdit_field')[ 'value'], final_plainTextEdit_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QPushButton_value(self): # Check that the value of the QPushButton is saved to the state @@ -732,7 +713,7 @@ def test_getWidgetState_returns_QPushButton_value(self): self.assertEqual(self.form.getWidgetState( 'button_field')['value'], final_button_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QRadioButton_value(self): # Check that the value of the QRadioButton is saved to the state @@ -747,7 +728,7 @@ def test_getWidgetState_returns_QRadioButton_value(self): self.assertEqual(self.form.getWidgetState( 'radioButton_field')['value'], final_radio_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetStates(self): state_to_set = { 'checkBox_field': {'value': True, 'enabled': False, 'visible': False}, @@ -761,14 +742,14 @@ def test_applyWidgetStates(self): self.assertEqual(self.simple_form.getWidgetState( 'label_field'), state_to_set['label_field']) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState('checkBox_field', state_to_set) self.assertEqual(self.simple_form.getWidgetState( 'checkBox_field'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState_using_role_parameter_field(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState( @@ -776,7 +757,7 @@ def test_applyWidgetState_using_role_parameter_field(self): self.assertEqual(self.simple_form.getWidgetState( 'checkBox', 'field'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState_using_role_parameter_label(self): state_to_set = {'value': 'test the checkbox:', 'enabled': False, 'visible': False} @@ -785,14 +766,14 @@ def test_applyWidgetState_using_role_parameter_label(self): self.assertEqual(self.simple_form.getWidgetState( 'checkBox', 'label'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState_using_role_parameter_default(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState('checkBox', state_to_set) self.assertEqual(self.simple_form.getWidgetState( 'checkBox'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getAllWidgetStates(self): # Check that the state of all widgets is returned @@ -805,7 +786,7 @@ def test_getAllWidgetStates(self): self.assertEqual(self.simple_form.getAllWidgetStates(), expected_state) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_saveAllWidgetStates(self): # Check that the state of all widgets is saved to the state variable @@ -820,7 +801,7 @@ def test_saveAllWidgetStates(self): self.assertEqual(self.simple_form.widget_states, expected_state) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_restoreAllSavedWidgetStates(self): # Check that the state of all widgets is restored from the state variable @@ -861,17 +842,17 @@ def test_restoreAllSavedWidgetStates(self): 'label', 'label').isVisible(), state_to_restore['label_label']['visible']) -@unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") +@skip_ci class FormDockWidgetStateTest(unittest.TestCase): - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def setUp(self): self.form = FormDockWidget() add_every_widget_to_form(self.form) self.simple_form = FormDockWidget() add_two_widgets_to_form(self.simple_form) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_visibility(self): # Check that the visibility of the widget is saved to the state # Have to use magic mock as we can't set the visibility of the QLabel @@ -890,7 +871,7 @@ def test_getWidgetState_returns_visibility(self): self.assertEqual(self.form.getWidgetState('label_field')[ 'visible'], final_label_visibility) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_enabled_state(self): # Check that the enabled state of the widget is saved to the state @@ -905,7 +886,7 @@ def test_getWidgetState_returns_enabled_state(self): self.assertEqual(self.form.getWidgetState('label_field')[ 'enabled'], final_label_enabled_state) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_value_using_role_parameter_field(self): # Check that the value of the QLabel is saved to the state @@ -920,7 +901,7 @@ def test_getWidgetState_returns_value_using_role_parameter_field(self): self.assertEqual(self.form.getWidgetState( 'label', 'field')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_value_using_role_parameter_label(self): # Check that the value of the QLabel is saved to the state @@ -935,7 +916,7 @@ def test_getWidgetState_returns_value_using_role_parameter_label(self): self.assertEqual(self.form.getWidgetState( 'label', 'field')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_value_using_default_role_parameter(self): # Check that the value of the QLabel is saved to the state initial_label_value = 'test label' @@ -952,7 +933,7 @@ def test_getWidgetState_returns_value_using_default_role_parameter(self): # Test value is saved for all widget types ---------------------------------------------------------------- - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QLabel_value(self): # Check that the value of the QLabel is saved to the state @@ -967,7 +948,7 @@ def test_getWidgetState_returns_QLabel_value(self): self.assertEqual(self.form.getWidgetState( 'label_field')['value'], final_label_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QCheckBox_value(self): # Check that the value of the QCheckBox is saved to the state @@ -982,7 +963,7 @@ def test_getWidgetState_returns_QCheckBox_value(self): self.assertEqual(self.form.getWidgetState( 'checkBox_field')['value'], final_checkbox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QComboBox_value(self): # Check that the value of the QComboBox is saved to the state @@ -1000,7 +981,7 @@ def test_getWidgetState_returns_QComboBox_value(self): self.assertEqual(self.form.getWidgetState( 'comboBox_field')['value'], final_combobox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QDoubleSpinBox_value(self): # Check that the value of the QDoubleSpinBox is saved to the state @@ -1016,7 +997,7 @@ def test_getWidgetState_returns_QDoubleSpinBox_value(self): self.assertEqual(self.form.getWidgetState('doubleSpinBox_field')[ 'value'], final_doubleSpinBox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QSpinBox_value(self): # Check that the value of the QSpinBox is saved to the state @@ -1031,7 +1012,7 @@ def test_getWidgetState_returns_QSpinBox_value(self): self.assertEqual(self.form.getWidgetState( 'spinBox_field')['value'], final_spinBox_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QSlider_value(self): # Check that the value of the QSlider is saved to the state @@ -1046,7 +1027,7 @@ def test_getWidgetState_returns_QSlider_value(self): self.assertEqual(self.form.getWidgetState( 'slider_field')['value'], final_slider_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_UISliderWidget_value(self): # Check that the value of the UISliderWidget is returned in the state @@ -1061,7 +1042,7 @@ def test_getWidgetState_returns_UISliderWidget_value(self): self.assertEqual(self.form.getWidgetState( 'uiSliderWidget_field')['value'], final_slider_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QLineEdit_value(self): # Check that the value of the QLineEdit is saved to the state @@ -1077,7 +1058,7 @@ def test_getWidgetState_returns_QLineEdit_value(self): self.assertEqual(self.form.getWidgetState( 'lineEdit_field')['value'], final_lineEdit_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QTextEdit_value(self): # Check that the value of the QTextEdit is saved to the state @@ -1093,7 +1074,7 @@ def test_getWidgetState_returns_QTextEdit_value(self): self.assertEqual(self.form.getWidgetState( 'textEdit_field')['value'], final_textEdit_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QPlainTextEdit_value(self): # Check that the value of the QPlainTextEdit is saved to the state @@ -1111,7 +1092,7 @@ def test_getWidgetState_returns_QPlainTextEdit_value(self): self.assertEqual(self.form.getWidgetState('plainTextEdit_field')[ 'value'], final_plainTextEdit_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QPushButton_value(self): # Check that the value of the QPushButton is saved to the state @@ -1127,7 +1108,7 @@ def test_getWidgetState_returns_QPushButton_value(self): self.assertEqual(self.form.getWidgetState( 'button_field')['value'], final_button_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getWidgetState_returns_QRadioButton_value(self): # Check that the value of the QRadioButton is saved to the state @@ -1142,7 +1123,7 @@ def test_getWidgetState_returns_QRadioButton_value(self): self.assertEqual(self.form.getWidgetState( 'radioButton_field')['value'], final_radio_value) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetStates(self): state_to_set = { 'checkBox_field': {'value': True, 'enabled': False, 'visible': False}, @@ -1156,14 +1137,14 @@ def test_applyWidgetStates(self): self.assertEqual(self.simple_form.getWidgetState( 'label_field'), state_to_set['label_field']) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState('checkBox_field', state_to_set) self.assertEqual(self.simple_form.getWidgetState( 'checkBox_field'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState_using_role_parameter_field(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState( @@ -1171,7 +1152,7 @@ def test_applyWidgetState_using_role_parameter_field(self): self.assertEqual(self.simple_form.getWidgetState( 'checkBox', 'field'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState_using_role_parameter_label(self): state_to_set = {'value': 'test the checkbox:', 'enabled': False, 'visible': False} @@ -1180,14 +1161,14 @@ def test_applyWidgetState_using_role_parameter_label(self): self.assertEqual(self.simple_form.getWidgetState( 'checkBox', 'label'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_applyWidgetState_using_role_parameter_default(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState('checkBox', state_to_set) self.assertEqual(self.simple_form.getWidgetState( 'checkBox'), state_to_set) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_getAllWidgetStates(self): # Check that the state of all widgets is returned @@ -1200,7 +1181,7 @@ def test_getAllWidgetStates(self): self.assertEqual(self.simple_form.getAllWidgetStates(), expected_state) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_saveAllWidgetStates(self): # Check that the state of all widgets is saved to the state variable @@ -1216,7 +1197,7 @@ def test_saveAllWidgetStates(self): self.assertEqual( self.simple_form.widget().widget_states, expected_state) - @unittest.skipIf(skip_test, "Can't test interfaces if we can't connect to the display") + @skip_ci def test_restoreAllSavedWidgetStates(self): # Check that the state of all widgets is restored from the state variable @@ -1255,7 +1236,3 @@ def test_restoreAllSavedWidgetStates(self): 'label', 'label').isEnabled(), state_to_restore['label_label']['enabled']) self.assertEqual(self.simple_form.getWidget( 'label', 'label').isVisible(), state_to_restore['label_label']['visible']) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test_basic.py b/test/test_basic.py deleted file mode 100644 index 5cc7837..0000000 --- a/test/test_basic.py +++ /dev/null @@ -1,9 +0,0 @@ -import unittest -import os - -# skip test of UI imports on GitHub actions -if os.environ.get('CONDA_BUILD', '0') != '1': - from dialog_example_2_test import MainUI, DialogTest - -class TestModuleBase(unittest.TestCase): - pass diff --git a/test/test_io.py b/test/test_io.py index 398b9ef..1440a5a 100644 --- a/test/test_io.py +++ b/test/test_io.py @@ -1,47 +1,26 @@ from eqt.io import zip_directory -import unittest import os import shutil - - -class TestZipDirectory(unittest.TestCase): - - def setUp(self): - ''' - Create a session zip file, which contains a session.json file - ''' - self.title = "title" - self.app_name = "app_name" - - self.folder = "Test Folder" - self.subfolder = os.path.join(self.folder, "Test Subfolder") - self.subfile = os.path.join(self.subfolder, "test_file.txt") - - os.mkdir(self.folder) - os.mkdir(self.subfolder) - - with open(self.subfile, "w+") as f: - f.write("test") - - def _test_zip_directory(self): - # Check the zip file exists: - assert os.path.exists(self.folder + ".zip") - # extract the zipfile and check the subfile exists: - shutil.unpack_archive(self.folder + ".zip", "extracted") - assert os.path.exists(os.path.join( - "extracted", "Test Subfolder", "test_file.txt")) - - def test_zip_directory_compress_True(self): - zip_directory(self.folder) - self._test_zip_directory() - - def test_zip_directory_compress_False(self): - zip_directory(self.folder, compress=False) - self._test_zip_directory() - - def tearDown(self): - shutil.rmtree(self.folder) - - -if __name__ == "__main__": - unittest.main() +from pytest import fixture +from pytest import mark + +@fixture +def test_file(tmp_path): + ''' + Create a zip file, which contains a session.json file + ''' + subfile = tmp_path / "Test Subfolder" / "test_file.txt" + subfolder = subfile.parent + subfolder.mkdir(parents=True, exist_ok=True) + subfile.write_text("test") + return subfile + + +@mark.parametrize('compress', (True, False)) +def test_zip_directory_compress_False(test_file, compress): + folder = test_file.parent.parent + zip_directory(folder, compress=compress) + zipname = folder.with_suffix(".zip") + assert zipname.is_file() + shutil.unpack_archive(zipname, folder / "extracted") + assert (folder / "extracted" / test_file.parts[-2] / test_file.parts[-1]).is_file() From 107cbd70bbf0a9347357faca21a0ed8edc469296 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Thu, 6 Jul 2023 11:02:54 +0100 Subject: [PATCH 08/15] tests: add CI config --- .github/workflows/test.yml | 17 +++++++++++++++++ pyproject.toml | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bf54ad2..7905c25 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,24 @@ on: push: pull_request: jobs: + test: + if: github.event_name != 'pull_request' || !contains('OWNER,MEMBER,COLLABORATOR', github.event.pull_request.author_association) + name: py${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: [3.7, 3.11] + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - run: pip install -U .[dev] + - run: pytest deploy: + needs: [test] name: PyPI Deploy runs-on: ubuntu-latest steps: diff --git a/pyproject.toml b/pyproject.toml index 97ac51c..09470ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,9 @@ classifiers = [ "Programming Language :: Python :: 3 :: Only"] dependencies = ["pyside2", "qdarkstyle"] +[project.optional-dependencies] +dev = ["pytest>=6", "pytest-cov", "pytest-timeout"] + [tool.flake8] max_line_length = 99 exclude = [".git", "__pycache__", "build", "dist", ".eggs"] @@ -57,3 +60,10 @@ profile = "black" line_length = 99 multi_line_output = 4 known_first_party = ["eqt", "test"] + +[tool.pytest.ini_options] +minversion = "6.0" +timeout = 30 +log_level = "INFO" +testpaths = ["test"] +addopts = "-v --tb=short -rxs -W=error --durations=0 --durations-min=1 --cov=eqt --cov-report=term-missing --cov-report=xml" From 339f16d03dbf5f5abcd7fcad43df51da8581b7e1 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Thu, 6 Jul 2023 11:36:07 +0100 Subject: [PATCH 09/15] build: fix readme metadata --- README.md | 47 +++++++++++++++++------------------------------ pyproject.toml | 2 +- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 06f5dbc..b9ab7e6 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ running some task asynchronously so that the interface is still responsive. This little package tries to address both recurring requirements. -1. `UIFormWidget`, a class to help creating Qt forms - programmatically, useable in `QDockWidgets` and `QWidget` +1. `UIFormWidget`, a class to help creating Qt forms + programmatically, useable in `QDockWidgets` and `QWidget` 1. `FormDialog`, a `QDialog` with a form inside with OK and Cancel button -1. `Worker`, a class that defines a `QRunnable` to +1. `Worker`, a class that defines a `QRunnable` to handle worker thread setup, signals and wrap up ### Installation @@ -19,59 +19,50 @@ This little package tries to address both recurring requirements. You may install via `pip` or `conda` ```bash - - python -m pip install eqt - # or - conda install eqt -c paskino +python -m pip install eqt +# or +conda install eqt -c paskino ``` ### Example +In the `example` directory there is an example on how to launch a `QDialog` with a form inside using `eqt`'s [`QWidget`](https://github.com/paskino/qt-elements/blob/main/examples/dialog_example.py) or [`FormDialog`](https://github.com/paskino/qt-elements/blob/main/examples/dialog_example_2.py). -In the `example` directory there is an example on how to launch a -`QDialog` with a form inside using `eqt`'s - [`QWidget`](https://github.com/paskino/qt-elements/blob/main/examples/dialog_example.py) or [`FormDialog`](https://github.com/paskino/qt-elements/blob/main/examples/dialog_example_2.py). - ### Running asynchronous tasks -To run a function in a separate thread we use a `Worker` which is a subclass of a `QRunnable`. +To run a function in a separate thread we use a `Worker` which is a subclass of a `QRunnable`. For the `Worker` to work one needs to define: 1. the function that does what you need 2. Optional callback methods to get the status of the thread by means of `QtCore.QSignal`s - - - On [initialisation](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L32-L38) of the `Worker` the user needs to pass the function that has to run in the thread, i.e. `fn` below, and additional optional positional and keyword arguments, which will be passed on to the actual function that is run in the `QRunnable`. - ```python class Worker(QtCore.QRunnable): - def __init__(self, fn, *args, **kwargs): self.fn = fn self.args = args self.kwargs = kwargs self.signals = WorkerSignals() ``` - In practice the user will need to pass to the `Worker` as many parameters as there are listed in the [function](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L56 -) to be run. + +In practice the user will need to pass to the `Worker` as many parameters as there are listed in the [function](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L56) to be run. ```python result = self.fn(*self.args, **self.kwargs) ``` -But `Worker` will [add](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L41-L43 -) to the `**kwargs` the following `QSignal`. +But `Worker` will [add](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L41-L43) to the `**kwargs` the following `QSignal`. -```python +```python # Add progress callback to kwargs self.kwargs['progress_callback'] = self.signals.progress self.kwargs['message_callback'] = self.signals.message self.kwargs['status_callback'] = self.signals.status ``` -Therefore it is advisable to always have `**kwargs` in the function `fn` signature so that you can access the `QSignal` and emit the signal required. For instance one could emit a progress by +Therefore it is advisable to always have `**kwargs` in the function `fn` signature so that you can access the `QSignal` and emit the signal required. For instance one could emit a progress by: + ```python def fn(num_iter, **kwargs): progress_callback = kwargs.get('progress_callback', None) @@ -85,30 +76,26 @@ def fn(num_iter, **kwargs): This is done just after one has defined the `Worker`: ```python - def handle_progress(num_iter): # do something with the progress print ("Current progress is ", num_iter) worker = Worker(fn, 10) worker.signals.progress.connect(handle_progress) - ``` So, each time `fn` comes to `progress_callback.emit( i )` the function `handle_progress` will be called with the parameter `i` of its `for` loop. #### What are the available signals? - -The signals that are available in the `Worker` class are defined in [`WorkerSignal`](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L66) and are the following. Below you can also see the type of data that each signal can emit. +The signals that are available in the `Worker` class are defined in [`WorkerSignal`](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L66) and are the following. Below you can also see the type of data that each signal can emit. ```python - finished = QtCore.Signal() error = QtCore.Signal(tuple) result = QtCore.Signal(object) - + progress = QtCore.Signal(int) message = QtCore.Signal(str) status = QtCore.Signal(tuple) ``` -Read more on [Qt signals and slots](https://doc.qt.io/qt-5/signalsandslots.html) and on how to use them in [pyside2](https://wiki.qt.io/Qt_for_Python_Signals_and_Slots) +Read more on [Qt signals and slots](https://doc.qt.io/qt-5/signalsandslots.html) and on how to use them in [pyside2](https://wiki.qt.io/Qt_for_Python_Signals_and_Slots). diff --git a/pyproject.toml b/pyproject.toml index 09470ab..4d30de7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dynamic = ["version"] authors = [{name = "Edoardo Pasca", email = "edoardo.pasca@stfc.ac.uk"}] maintainers = [{name = "Casper da Costa-Luis", email = "casper.dcl@physics.org"}] description = "A number of templates and tools to develop Qt GUIs with Python effectively" -readme = "README.rst" +readme = "README.md" requires-python = ">=3.7" license = {text = "Apache-2.0"} classifiers = [ From c321b8c6fecff111794d1399b112f39044466195 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Thu, 6 Jul 2023 15:15:35 +0100 Subject: [PATCH 10/15] minor tidy --- eqt/io.py | 29 +++++++++-------------------- test/conftest.py | 0 test/dialog_example_2_test.py | 2 -- test/test_io.py | 5 +---- 4 files changed, 10 insertions(+), 26 deletions(-) delete mode 100644 test/conftest.py diff --git a/eqt/io.py b/eqt/io.py index 2a2c1da..5d5f715 100644 --- a/eqt/io.py +++ b/eqt/io.py @@ -2,29 +2,18 @@ import zipfile +def zip_directory(directory: str, compress: bool=True): + """ + Zips a directory, optionally compressing it. -def zip_directory(directory, compress=True, **kwargs): - ''' - Zips a directory, optionally compressing it. - - Parameters - ---------- - directory : str - The directory to be zipped. - compress : bool - Whether to compress the directory. - ''' - - zipper = zipfile.ZipFile(f'{directory}.zip', 'a') - - if compress: - compress_type = zipfile.ZIP_DEFLATED - else: - compress_type = zipfile.ZIP_STORED - + Args: + directory: The directory to be zipped. + compress: Whether to compress the directory. + """ + compress_type = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED + with zipfile.ZipFile(f'{directory}.zip', 'a') as zipper: for r, _, f in os.walk(directory): for _file in f: filepath = os.path.join(r, _file) arcname = os.path.relpath(filepath, directory) zipper.write(filepath, arcname, compress_type=compress_type) - zipper.close() diff --git a/test/conftest.py b/test/conftest.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/dialog_example_2_test.py b/test/dialog_example_2_test.py index 48331d7..b6431e8 100644 --- a/test/dialog_example_2_test.py +++ b/test/dialog_example_2_test.py @@ -72,7 +72,6 @@ def rejected(self): class DialogTest(unittest.TestCase): '''Test the margarita mixer GUI''' - @skip_ci def setUp(self): '''Create the GUI''' @@ -108,7 +107,6 @@ def test_openclose_dialog(self): def stest_defaults(self): '''Test the GUI in its default state''' - self.dialog = self.window.dialog print("test1") self.assertEqual(self.window.dialog.widgets['input1_field'].text(), '') diff --git a/test/test_io.py b/test/test_io.py index 1440a5a..101f9e9 100644 --- a/test/test_io.py +++ b/test/test_io.py @@ -1,14 +1,11 @@ from eqt.io import zip_directory -import os import shutil from pytest import fixture from pytest import mark @fixture def test_file(tmp_path): - ''' - Create a zip file, which contains a session.json file - ''' + """Create a zip file, which contains a session.json file""" subfile = tmp_path / "Test Subfolder" / "test_file.txt" subfolder = subfile.parent subfolder.mkdir(parents=True, exist_ok=True) From 29cf08d93bca652825a8a9b719727b13db83594d Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Thu, 6 Jul 2023 15:33:31 +0100 Subject: [PATCH 11/15] tests: DRY --- test/test__formUI_status_test.py | 1007 +++++------------------------- 1 file changed, 148 insertions(+), 859 deletions(-) diff --git a/test/test__formUI_status_test.py b/test/test__formUI_status_test.py index 564688e..09eb817 100644 --- a/test/test__formUI_status_test.py +++ b/test/test__formUI_status_test.py @@ -1,3 +1,4 @@ +import abc import unittest from unittest import mock @@ -9,63 +10,47 @@ from . import skip_ci -def add_every_widget_to_form(form): - ''' - Generate every widget and add it to the form - - Parameters - ---------- - form : FormWidget, FormDialog or FormDockWidget - The form to add the widgets to - ''' - form.addWidget(QtWidgets.QLabel('test label'), 'Label: ', 'label') - form.addWidget(QtWidgets.QCheckBox( - 'test checkbox'), 'CheckBox: ', 'checkBox') - form.addWidget(QtWidgets.QComboBox(), 'ComboBox: ', 'comboBox') - form.addWidget(QtWidgets.QDoubleSpinBox(), - 'DoubleSpinBox: ', 'doubleSpinBox') - form.addWidget(QtWidgets.QSpinBox(), 'SpinBox: ', 'spinBox') - form.addWidget(QtWidgets.QSlider(), 'Slider: ', 'slider') - form.addWidget(UISliderWidget(QtWidgets.QLabel()), - 'UISliderWidget: ', 'uiSliderWidget') - form.addWidget(QtWidgets.QRadioButton('test'), - 'RadioButton: ', 'radioButton') - form.addWidget(QtWidgets.QTextEdit('test'), 'TextEdit: ', 'textEdit') - form.addWidget(QtWidgets.QPlainTextEdit('test'), - 'PlainTextEdit: ', 'plainTextEdit') - form.addWidget(QtWidgets.QLineEdit('test'), 'LineEdit: ', 'lineEdit') - form.addWidget(QtWidgets.QPushButton('test'), 'Button: ', 'button') - - -def add_two_widgets_to_form(form): - ''' - Generate two widgets and add them to the form - - Parameters - ---------- - form : FormWidget, FormDialog or FormDockWidget - The form to add the widgets to - ''' - form.addWidget(QtWidgets.QLabel('test label'), 'Label: ', 'label') - form.addWidget(QtWidgets.QCheckBox( - 'test checkbox'), 'CheckBox: ', 'checkBox') - - -@skip_ci -class FormDialogStatusTest(unittest.TestCase): - - @skip_ci +class FormsCommonTests(metaclass=abc.ABCMeta): + """Common tests for all Form types""" + @abc.abstractmethod def setUp(self): - self.form = FormDialog() - add_every_widget_to_form(self.form) - self.simple_form = FormDialog() - add_two_widgets_to_form(self.simple_form) + raise NotImplementedError + + def add_every_widget(self): + """Generate every widget and add it to `self.form`""" + form = self.form + form.addWidget(QtWidgets.QLabel('test label'), 'Label: ', 'label') + form.addWidget(QtWidgets.QCheckBox( + 'test checkbox'), 'CheckBox: ', 'checkBox') + form.addWidget(QtWidgets.QComboBox(), 'ComboBox: ', 'comboBox') + form.addWidget(QtWidgets.QDoubleSpinBox(), + 'DoubleSpinBox: ', 'doubleSpinBox') + form.addWidget(QtWidgets.QSpinBox(), 'SpinBox: ', 'spinBox') + form.addWidget(QtWidgets.QSlider(), 'Slider: ', 'slider') + form.addWidget(UISliderWidget(QtWidgets.QLabel()), + 'UISliderWidget: ', 'uiSliderWidget') + form.addWidget(QtWidgets.QRadioButton('test'), + 'RadioButton: ', 'radioButton') + form.addWidget(QtWidgets.QTextEdit('test'), 'TextEdit: ', 'textEdit') + form.addWidget(QtWidgets.QPlainTextEdit('test'), + 'PlainTextEdit: ', 'plainTextEdit') + form.addWidget(QtWidgets.QLineEdit('test'), 'LineEdit: ', 'lineEdit') + form.addWidget(QtWidgets.QPushButton('test'), 'Button: ', 'button') + + + def add_two_widgets(self): + """Generate two widgets and add them to `self.simple_form`""" + form = self.simple_form + form.addWidget(QtWidgets.QLabel('test label'), 'Label: ', 'label') + form.addWidget(QtWidgets.QCheckBox( + 'test checkbox'), 'CheckBox: ', 'checkBox') - @skip_ci def test_getWidgetState_returns_visibility(self): - # Check that the visibility of the widget is saved to the state - # Have to use magic mock as we can't set the visibility of the QLabel - # to be True, because the FormDialog is not visible + """ + Check that the visibility of the widget is saved to the state + Have to use magic mock as we can't set the visibility of the QLabel + to be True, because the FormDialog is not visible + """ initial_label_visibility = True self.form.getWidget('label').isVisible = mock.MagicMock() self.form.getWidget( @@ -80,9 +65,8 @@ def test_getWidgetState_returns_visibility(self): self.assertEqual(self.form.getWidgetState('label_field')[ 'visible'], final_label_visibility) - @skip_ci def test_getWidgetState_returns_enabled_state(self): - # Check that the enabled state of the widget is saved to the state + """Check that the enabled state of the widget is saved to the state""" initial_label_enabled_state = True @@ -95,27 +79,8 @@ def test_getWidgetState_returns_enabled_state(self): self.assertEqual(self.form.getWidgetState('label_field')[ 'enabled'], final_label_enabled_state) - # Test value is saved for all widget types ---------------------------------------------------------------- - - @skip_ci - def test_getWidgetState_returns_QLabel_value(self): - # Check that the value of the QLabel is saved to the state - - initial_label_value = 'Label: ' - - self.assertEqual(self.form.getWidgetState( - 'label_label')['value'], initial_label_value) - - final_label_value = 'final test label' - self.form.getWidget('label', 'label').setText(final_label_value) - - self.assertEqual(self.form.getWidgetState( - 'label_label')['value'], final_label_value) - - @skip_ci def test_getWidgetState_returns_value_using_role_parameter_field(self): - # Check that the value of the QLabel is saved to the state - + """Check that the value of the QLabel is saved to the state""" initial_label_value = 'test label' self.assertEqual(self.form.getWidgetState( @@ -127,10 +92,8 @@ def test_getWidgetState_returns_value_using_role_parameter_field(self): self.assertEqual(self.form.getWidgetState( 'label', 'field')['value'], final_label_value) - @skip_ci def test_getWidgetState_returns_value_using_role_parameter_label(self): - # Check that the value of the QLabel is saved to the state - + """Check that the value of the QLabel is saved to the state""" initial_label_value = 'test label' self.assertEqual(self.form.getWidgetState( @@ -142,9 +105,8 @@ def test_getWidgetState_returns_value_using_role_parameter_label(self): self.assertEqual(self.form.getWidgetState( 'label', 'field')['value'], final_label_value) - @skip_ci def test_getWidgetState_returns_value_using_default_role_parameter(self): - # Check that the value of the QLabel is saved to the state + """Check that the value of the QLabel is saved to the state""" initial_label_value = 'test label' # In getWidgetState we do not specify if we want the 'field' or 'label' role, so it should default to 'field': @@ -157,10 +119,8 @@ def test_getWidgetState_returns_value_using_default_role_parameter(self): self.assertEqual(self.form.getWidgetState( 'label')['value'], final_label_value) - @skip_ci def test_getWidgetState_returns_QCheckBox_value(self): - # Check that the value of the QCheckBox is saved to the state - + """Check that the value of the QCheckBox is saved to the state""" initial_checkbox_value = False self.assertEqual(self.form.getWidgetState('checkBox_field')[ @@ -172,10 +132,8 @@ def test_getWidgetState_returns_QCheckBox_value(self): self.assertEqual(self.form.getWidgetState( 'checkBox_field')['value'], final_checkbox_value) - @skip_ci def test_getWidgetState_returns_QComboBox_value(self): - # Check that the value of the QComboBox is saved to the state - + """Check that the value of the QComboBox is saved to the state""" combobox_list = ['test', 'test2'] self.form.getWidget('comboBox').addItems(combobox_list) @@ -190,10 +148,8 @@ def test_getWidgetState_returns_QComboBox_value(self): self.assertEqual(self.form.getWidgetState( 'comboBox_field')['value'], final_combobox_value) - @skip_ci def test_getWidgetState_returns_QDoubleSpinBox_value(self): - # Check that the value of the QDoubleSpinBox is saved to the state - + """Check that the value of the QDoubleSpinBox is saved to the state""" initial_doubleSpinBox_value = 0.0 self.assertEqual(self.form.getWidgetState('doubleSpinBox_field')[ @@ -206,10 +162,8 @@ def test_getWidgetState_returns_QDoubleSpinBox_value(self): self.assertEqual(self.form.getWidgetState('doubleSpinBox_field')[ 'value'], final_doubleSpinBox_value) - @skip_ci def test_getWidgetState_returns_QSpinBox_value(self): - # Check that the value of the QSpinBox is saved to the state - + """Check that the value of the QSpinBox is saved to the state""" initial_spinBox_value = 0 self.assertEqual(self.form.getWidgetState('spinBox_field')[ @@ -221,10 +175,8 @@ def test_getWidgetState_returns_QSpinBox_value(self): self.assertEqual(self.form.getWidgetState( 'spinBox_field')['value'], final_spinBox_value) - @skip_ci def test_getWidgetState_returns_QSlider_value(self): - # Check that the value of the QSlider is saved to the state - + """Check that the value of the QSlider is saved to the state""" initial_slider_value = 0 self.assertEqual(self.form.getWidgetState( @@ -236,10 +188,8 @@ def test_getWidgetState_returns_QSlider_value(self): self.assertEqual(self.form.getWidgetState( 'slider_field')['value'], final_slider_value) - @skip_ci def test_getWidgetState_returns_UISliderWidget_value(self): - # Check that the value of the UISliderWidget is returned in the state - + """Check that the value of the UISliderWidget is returned in the state""" initial_slider_value = 0 self.assertEqual(self.form.getWidgetState( @@ -251,10 +201,8 @@ def test_getWidgetState_returns_UISliderWidget_value(self): self.assertEqual(self.form.getWidgetState( 'uiSliderWidget_field')['value'], final_slider_value) - @skip_ci def test_getWidgetState_returns_QLineEdit_value(self): - # Check that the value of the QLineEdit is saved to the state - + """Check that the value of the QLineEdit is saved to the state""" initial_lineEdit_value = '' self.form.getWidget('lineEdit').setText(initial_lineEdit_value) @@ -267,10 +215,8 @@ def test_getWidgetState_returns_QLineEdit_value(self): self.assertEqual(self.form.getWidgetState( 'lineEdit_field')['value'], final_lineEdit_value) - @skip_ci def test_getWidgetState_returns_QTextEdit_value(self): - # Check that the value of the QTextEdit is saved to the state - + """Check that the value of the QTextEdit is saved to the state""" initial_textEdit_value = '' self.form.getWidget('textEdit').setText(initial_textEdit_value) @@ -283,10 +229,8 @@ def test_getWidgetState_returns_QTextEdit_value(self): self.assertEqual(self.form.getWidgetState( 'textEdit_field')['value'], final_textEdit_value) - @skip_ci def test_getWidgetState_returns_QPlainTextEdit_value(self): - # Check that the value of the QPlainTextEdit is saved to the state - + """Check that the value of the QPlainTextEdit is saved to the state""" initial_plainTextEdit_value = '' self.form.getWidget('plainTextEdit').setPlainText( initial_plainTextEdit_value) @@ -301,10 +245,8 @@ def test_getWidgetState_returns_QPlainTextEdit_value(self): self.assertEqual(self.form.getWidgetState('plainTextEdit_field')[ 'value'], final_plainTextEdit_value) - @skip_ci def test_getWidgetState_returns_QPushButton_value(self): - # Check that the value of the QPushButton is saved to the state - + """Check that the value of the QPushButton is saved to the state""" initial_button_value = False self.assertEqual(self.form.getWidgetState( @@ -317,10 +259,8 @@ def test_getWidgetState_returns_QPushButton_value(self): self.assertEqual(self.form.getWidgetState( 'button_field')['value'], final_button_value) - @skip_ci def test_getWidgetState_returns_QRadioButton_value(self): - # Check that the value of the QRadioButton is saved to the state - + """Check that the value of the QRadioButton is saved to the state""" initial_radio_value = False self.assertEqual(self.form.getWidgetState( @@ -332,7 +272,6 @@ def test_getWidgetState_returns_QRadioButton_value(self): self.assertEqual(self.form.getWidgetState( 'radioButton_field')['value'], final_radio_value) - @skip_ci def test_applyWidgetStates(self): state_to_set = { 'checkBox_field': {'value': True, 'enabled': False, 'visible': False}, @@ -346,14 +285,12 @@ def test_applyWidgetStates(self): self.assertEqual(self.simple_form.getWidgetState( 'label_field'), state_to_set['label_field']) - @skip_ci def test_applyWidgetState(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState('checkBox_field', state_to_set) self.assertEqual(self.simple_form.getWidgetState( 'checkBox_field'), state_to_set) - @skip_ci def test_applyWidgetState_using_role_parameter_field(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState( @@ -361,7 +298,6 @@ def test_applyWidgetState_using_role_parameter_field(self): self.assertEqual(self.simple_form.getWidgetState( 'checkBox', 'field'), state_to_set) - @skip_ci def test_applyWidgetState_using_role_parameter_label(self): state_to_set = {'value': 'test the checkbox:', 'enabled': False, 'visible': False} @@ -370,17 +306,14 @@ def test_applyWidgetState_using_role_parameter_label(self): self.assertEqual(self.simple_form.getWidgetState( 'checkBox', 'label'), state_to_set) - @skip_ci def test_applyWidgetState_using_role_parameter_default(self): state_to_set = {'value': True, 'enabled': False, 'visible': False} self.simple_form.applyWidgetState('checkBox', state_to_set) self.assertEqual(self.simple_form.getWidgetState( 'checkBox'), state_to_set) - @skip_ci def test_getAllWidgetStates(self): - # Check that the state of all widgets is returned - + """Check that the state of all widgets is returned""" expected_state = { 'checkBox_field': {'value': False, 'enabled': True, 'visible': False}, 'label_field': {'value': 'test label', 'enabled': True, 'visible': False}, @@ -390,35 +323,47 @@ def test_getAllWidgetStates(self): self.assertEqual(self.simple_form.getAllWidgetStates(), expected_state) - @skip_ci - def test_saveAllWidgetStates(self): - # Check that the state of all widgets is saved to the state variable +@skip_ci +class FormDialogStatusTest(FormsCommonTests, unittest.TestCase): + def setUp(self): + self.form = FormDialog() + self.add_every_widget() + self.simple_form = FormDialog() + self.add_two_widgets() + + def test_getWidgetState_returns_QLabel_value(self): + """Check that the value of the QLabel is saved to the state""" + initial_label_value = 'Label: ' + self.assertEqual(self.form.getWidgetState( + 'label_label')['value'], initial_label_value) + + final_label_value = 'final test label' + self.form.getWidget('label', 'label').setText(final_label_value) + self.assertEqual(self.form.getWidgetState( + 'label_label')['value'], final_label_value) + + def test_saveAllWidgetStates(self): + """Check that the state of all widgets is saved to the state variable""" expected_state = { 'checkBox_field': {'value': False, 'enabled': True, 'visible': False}, 'label_field': {'value': 'test label', 'enabled': True, 'visible': False}, 'checkBox_label': {'value': 'CheckBox: ', 'enabled': True, 'visible': False}, 'label_label': {'value': 'Label: ', 'enabled': True, 'visible': False} } - self.simple_form.saveAllWidgetStates() - self.assertEqual( self.simple_form.formWidget.widget_states, expected_state) - @skip_ci def test_restoreAllSavedWidgetStates(self): - # Check that the state of all widgets is restored from the state variable - + """Check that the state of all widgets is restored from the state variable""" state_to_restore = { 'checkBox_field': {'value': True, 'enabled': False, 'visible': False}, 'label_field': {'value': 'applyWidgetStates Test', 'enabled': True, 'visible': False}, 'checkBox_label': {'value': 'CheckBox Test: ', 'enabled': True, 'visible': False}, 'label_label': {'value': 'Label Test: ', 'enabled': True, 'visible': False} } - self.simple_form.formWidget.widget_states = state_to_restore - self.simple_form.restoreAllSavedWidgetStates() self.assertEqual(self.simple_form.getWidget( @@ -448,768 +393,112 @@ def test_restoreAllSavedWidgetStates(self): @skip_ci -class FormWidgetStateTest(unittest.TestCase): - - @skip_ci +class FormWidgetStateTest(FormsCommonTests, unittest.TestCase): def setUp(self): self.form = FormWidget() - add_every_widget_to_form(self.form) + self.add_every_widget() self.simple_form = FormWidget() - add_two_widgets_to_form(self.simple_form) - - @skip_ci - def test_getWidgetState_returns_visibility(self): - # Check that the visibility of the widget is saved to the state - # Have to use magic mock as we can't set the visibility of the QLabel - # to be True, because the FormDialog is not visible - initial_label_visibility = True - self.form.getWidget('label').isVisible = mock.MagicMock() - self.form.getWidget( - 'label').isVisible.return_value = initial_label_visibility - - self.assertEqual(self.form.getWidgetState('label_field')[ - 'visible'], initial_label_visibility) - - final_label_visibility = False - self.form.getWidget('label').isVisible.return_value = False - - self.assertEqual(self.form.getWidgetState('label_field')[ - 'visible'], final_label_visibility) - - @skip_ci - def test_getWidgetState_returns_enabled_state(self): - # Check that the enabled state of the widget is saved to the state - - initial_label_enabled_state = True - - self.assertEqual(self.form.getWidgetState('label_field')[ - 'enabled'], initial_label_enabled_state) - - self.form.getWidget('label').setEnabled(False) - final_label_enabled_state = False - - self.assertEqual(self.form.getWidgetState('label_field')[ - 'enabled'], final_label_enabled_state) - - # Test value is saved for all widget types ---------------------------------------------------------------- - - @skip_ci - def test_getWidgetState_returns_value_using_role_parameter_field(self): - # Check that the value of the QLabel is saved to the state + self.add_two_widgets() + def test_getWidgetState_returns_QLabel_value(self): + """Check that the value of the QLabel is saved to the state""" initial_label_value = 'test label' - self.assertEqual(self.form.getWidgetState( - 'label', 'field')['value'], initial_label_value) + 'label_field')['value'], initial_label_value) final_label_value = 'final test label' self.form.getWidget('label').setText(final_label_value) - - self.assertEqual(self.form.getWidgetState( - 'label', 'field')['value'], final_label_value) - - @skip_ci - def test_getWidgetState_returns_value_using_role_parameter_label(self): - # Check that the value of the QLabel is saved to the state - - initial_label_value = 'test label' - self.assertEqual(self.form.getWidgetState( - 'label', 'field')['value'], initial_label_value) - - final_label_value = 'final test label' - self.form.getWidget('label').setText(final_label_value) + 'label_field')['value'], final_label_value) - self.assertEqual(self.form.getWidgetState( - 'label', 'field')['value'], final_label_value) + def test_saveAllWidgetStates(self): + """Check that the state of all widgets is saved to the state variable""" + expected_state = { + 'checkBox_field': {'value': False, 'enabled': True, 'visible': False}, + 'label_field': {'value': 'test label', 'enabled': True, 'visible': False}, + 'checkBox_label': {'value': 'CheckBox: ', 'enabled': True, 'visible': False}, + 'label_label': {'value': 'Label: ', 'enabled': True, 'visible': False} + } + self.simple_form.saveAllWidgetStates() + self.assertEqual(self.simple_form.widget_states, expected_state) - @skip_ci - def test_getWidgetState_returns_value_using_default_role_parameter(self): - # Check that the value of the QLabel is saved to the state - initial_label_value = 'test label' + def test_restoreAllSavedWidgetStates(self): + """Check that the state of all widgets is restored from the state variable""" + state_to_restore = { + 'checkBox_field': {'value': True, 'enabled': False, 'visible': False}, + 'label_field': {'value': 'applyWidgetStates Test', 'enabled': True, 'visible': False}, + 'checkBox_label': {'value': 'CheckBox Test: ', 'enabled': True, 'visible': False}, + 'label_label': {'value': 'Label Test: ', 'enabled': True, 'visible': False} + } + self.simple_form.widget_states = state_to_restore + self.simple_form.restoreAllSavedWidgetStates() - # In getWidgetState we do not specify if we want the 'field' or 'label' role, so it should default to 'field': - self.assertEqual(self.form.getWidgetState( - 'label')['value'], initial_label_value) + self.assertEqual(self.simple_form.getWidget( + 'checkBox').isChecked(), state_to_restore['checkBox_field']['value']) + self.assertEqual(self.simple_form.getWidget( + 'checkBox').isEnabled(), state_to_restore['checkBox_field']['enabled']) + self.assertEqual(self.simple_form.getWidget( + 'checkBox').isVisible(), state_to_restore['checkBox_field']['visible']) + self.assertEqual(self.simple_form.getWidget( + 'checkBox', 'label').text(), state_to_restore['checkBox_label']['value']) + self.assertEqual(self.simple_form.getWidget( + 'checkBox', 'label').isEnabled(), state_to_restore['checkBox_label']['enabled']) + self.assertEqual(self.simple_form.getWidget( + 'checkBox', 'label').isVisible(), state_to_restore['checkBox_label']['visible']) + self.assertEqual(self.simple_form.getWidget( + 'label').text(), state_to_restore['label_field']['value']) + self.assertEqual(self.simple_form.getWidget( + 'label').isEnabled(), state_to_restore['label_field']['enabled']) + self.assertEqual(self.simple_form.getWidget( + 'label').isVisible(), state_to_restore['label_field']['visible']) + self.assertEqual(self.simple_form.getWidget( + 'label', 'label').text(), state_to_restore['label_label']['value']) + self.assertEqual(self.simple_form.getWidget( + 'label', 'label').isEnabled(), state_to_restore['label_label']['enabled']) + self.assertEqual(self.simple_form.getWidget( + 'label', 'label').isVisible(), state_to_restore['label_label']['visible']) - final_label_value = 'final test label' - self.form.getWidget('label').setText(final_label_value) - self.assertEqual(self.form.getWidgetState( - 'label')['value'], final_label_value) +@skip_ci +class FormDockWidgetStateTest(FormsCommonTests, unittest.TestCase): + def setUp(self): + self.form = FormDockWidget() + self.add_every_widget() + self.simple_form = FormDockWidget() + self.add_two_widgets() - @skip_ci def test_getWidgetState_returns_QLabel_value(self): - # Check that the value of the QLabel is saved to the state - + """Check that the value of the QLabel is saved to the state""" initial_label_value = 'test label' - self.assertEqual(self.form.getWidgetState( 'label_field')['value'], initial_label_value) final_label_value = 'final test label' self.form.getWidget('label').setText(final_label_value) - self.assertEqual(self.form.getWidgetState( 'label_field')['value'], final_label_value) - @skip_ci - def test_getWidgetState_returns_QCheckBox_value(self): - # Check that the value of the QCheckBox is saved to the state - - initial_checkbox_value = False - - self.assertEqual(self.form.getWidgetState('checkBox_field')[ - 'value'], initial_checkbox_value) - - final_checkbox_value = True - self.form.getWidget('checkBox').setChecked(final_checkbox_value) - - self.assertEqual(self.form.getWidgetState( - 'checkBox_field')['value'], final_checkbox_value) - - @skip_ci - def test_getWidgetState_returns_QComboBox_value(self): - # Check that the value of the QComboBox is saved to the state - - combobox_list = ['test', 'test2'] - self.form.getWidget('comboBox').addItems(combobox_list) - - initial_combobox_value = 0 - - self.assertEqual(self.form.getWidgetState('comboBox_field')[ - 'value'], initial_combobox_value) - - final_combobox_value = 1 - self.form.getWidget('comboBox').setCurrentIndex(final_combobox_value) - - self.assertEqual(self.form.getWidgetState( - 'comboBox_field')['value'], final_combobox_value) - - @skip_ci - def test_getWidgetState_returns_QDoubleSpinBox_value(self): - # Check that the value of the QDoubleSpinBox is saved to the state - - initial_doubleSpinBox_value = 0.0 - - self.assertEqual(self.form.getWidgetState('doubleSpinBox_field')[ - 'value'], initial_doubleSpinBox_value) - - final_doubleSpinBox_value = 1.0 - self.form.getWidget('doubleSpinBox').setValue( - final_doubleSpinBox_value) - - self.assertEqual(self.form.getWidgetState('doubleSpinBox_field')[ - 'value'], final_doubleSpinBox_value) - - @skip_ci - def test_getWidgetState_returns_QSpinBox_value(self): - # Check that the value of the QSpinBox is saved to the state - - initial_spinBox_value = 0 - - self.assertEqual(self.form.getWidgetState('spinBox_field')[ - 'value'], initial_spinBox_value) - - final_spinBox_value = 1 - self.form.getWidget('spinBox').setValue(final_spinBox_value) - - self.assertEqual(self.form.getWidgetState( - 'spinBox_field')['value'], final_spinBox_value) - - @skip_ci - def test_getWidgetState_returns_QSlider_value(self): - # Check that the value of the QSlider is saved to the state - - initial_slider_value = 0 - - self.assertEqual(self.form.getWidgetState( - 'slider_field')['value'], initial_slider_value) - - final_slider_value = 1 - self.form.getWidget('slider').setValue(final_slider_value) - - self.assertEqual(self.form.getWidgetState( - 'slider_field')['value'], final_slider_value) - - @skip_ci - def test_getWidgetState_returns_UISliderWidget_value(self): - # Check that the value of the UISliderWidget is returned in the state - - initial_slider_value = 0 - - self.assertEqual(self.form.getWidgetState( - 'uiSliderWidget_field')['value'], initial_slider_value) - - final_slider_value = 1 - self.form.getWidget('uiSliderWidget').setValue(final_slider_value) - - self.assertEqual(self.form.getWidgetState( - 'uiSliderWidget_field')['value'], final_slider_value) + def test_saveAllWidgetStates(self): + """Check that the state of all widgets is saved to the state variable""" + expected_state = { + 'checkBox_field': {'value': False, 'enabled': True, 'visible': False}, + 'label_field': {'value': 'test label', 'enabled': True, 'visible': False}, + 'checkBox_label': {'value': 'CheckBox: ', 'enabled': True, 'visible': False}, + 'label_label': {'value': 'Label: ', 'enabled': True, 'visible': False} + } + self.simple_form.saveAllWidgetStates() + self.assertEqual( + self.simple_form.widget().widget_states, expected_state) - @skip_ci - def test_getWidgetState_returns_QLineEdit_value(self): - # Check that the value of the QLineEdit is saved to the state - - initial_lineEdit_value = '' - self.form.getWidget('lineEdit').setText(initial_lineEdit_value) - - self.assertEqual(self.form.getWidgetState('lineEdit_field')[ - 'value'], initial_lineEdit_value) - - final_lineEdit_value = 'test' - self.form.getWidget('lineEdit').setText(final_lineEdit_value) - - self.assertEqual(self.form.getWidgetState( - 'lineEdit_field')['value'], final_lineEdit_value) - - @skip_ci - def test_getWidgetState_returns_QTextEdit_value(self): - # Check that the value of the QTextEdit is saved to the state - - initial_textEdit_value = '' - self.form.getWidget('textEdit').setText(initial_textEdit_value) - - self.assertEqual(self.form.getWidgetState('textEdit_field')[ - 'value'], initial_textEdit_value) - - final_textEdit_value = 'test' - self.form.getWidget('textEdit').setText(final_textEdit_value) - - self.assertEqual(self.form.getWidgetState( - 'textEdit_field')['value'], final_textEdit_value) - - @skip_ci - def test_getWidgetState_returns_QPlainTextEdit_value(self): - # Check that the value of the QPlainTextEdit is saved to the state - - initial_plainTextEdit_value = '' - self.form.getWidget('plainTextEdit').setPlainText( - initial_plainTextEdit_value) - - self.assertEqual(self.form.getWidgetState('plainTextEdit_field')[ - 'value'], initial_plainTextEdit_value) - - final_plainTextEdit_value = 'test' - self.form.getWidget('plainTextEdit').setPlainText( - final_plainTextEdit_value) - - self.assertEqual(self.form.getWidgetState('plainTextEdit_field')[ - 'value'], final_plainTextEdit_value) - - @skip_ci - def test_getWidgetState_returns_QPushButton_value(self): - # Check that the value of the QPushButton is saved to the state - - initial_button_value = False - - self.assertEqual(self.form.getWidgetState( - 'button_field')['value'], initial_button_value) - - final_button_value = True - self.form.getWidget('button').setCheckable(True) - self.form.getWidget('button').setChecked(final_button_value) - - self.assertEqual(self.form.getWidgetState( - 'button_field')['value'], final_button_value) - - @skip_ci - def test_getWidgetState_returns_QRadioButton_value(self): - # Check that the value of the QRadioButton is saved to the state - - initial_radio_value = False - - self.assertEqual(self.form.getWidgetState( - 'radioButton_field')['value'], initial_radio_value) - - final_radio_value = True - self.form.getWidget('radioButton').setChecked(final_radio_value) - - self.assertEqual(self.form.getWidgetState( - 'radioButton_field')['value'], final_radio_value) - - @skip_ci - def test_applyWidgetStates(self): - state_to_set = { - 'checkBox_field': {'value': True, 'enabled': False, 'visible': False}, - 'label_field': {'value': 'applyWidgetStates Test', 'enabled': True, 'visible': False} - } - - self.simple_form.applyWidgetStates(state_to_set) - - self.assertEqual(self.simple_form.getWidgetState( - 'checkBox_field'), state_to_set['checkBox_field']) - self.assertEqual(self.simple_form.getWidgetState( - 'label_field'), state_to_set['label_field']) - - @skip_ci - def test_applyWidgetState(self): - state_to_set = {'value': True, 'enabled': False, 'visible': False} - self.simple_form.applyWidgetState('checkBox_field', state_to_set) - self.assertEqual(self.simple_form.getWidgetState( - 'checkBox_field'), state_to_set) - - @skip_ci - def test_applyWidgetState_using_role_parameter_field(self): - state_to_set = {'value': True, 'enabled': False, 'visible': False} - self.simple_form.applyWidgetState( - 'checkBox', state_to_set, role='field') - self.assertEqual(self.simple_form.getWidgetState( - 'checkBox', 'field'), state_to_set) - - @skip_ci - def test_applyWidgetState_using_role_parameter_label(self): - state_to_set = {'value': 'test the checkbox:', - 'enabled': False, 'visible': False} - self.simple_form.applyWidgetState( - 'checkBox', state_to_set, role='label') - self.assertEqual(self.simple_form.getWidgetState( - 'checkBox', 'label'), state_to_set) - - @skip_ci - def test_applyWidgetState_using_role_parameter_default(self): - state_to_set = {'value': True, 'enabled': False, 'visible': False} - self.simple_form.applyWidgetState('checkBox', state_to_set) - self.assertEqual(self.simple_form.getWidgetState( - 'checkBox'), state_to_set) - - @skip_ci - def test_getAllWidgetStates(self): - # Check that the state of all widgets is returned - - expected_state = { - 'checkBox_field': {'value': False, 'enabled': True, 'visible': False}, - 'label_field': {'value': 'test label', 'enabled': True, 'visible': False}, - 'checkBox_label': {'value': 'CheckBox: ', 'enabled': True, 'visible': False}, - 'label_label': {'value': 'Label: ', 'enabled': True, 'visible': False} - } - - self.assertEqual(self.simple_form.getAllWidgetStates(), expected_state) - - @skip_ci - def test_saveAllWidgetStates(self): - # Check that the state of all widgets is saved to the state variable - - expected_state = { - 'checkBox_field': {'value': False, 'enabled': True, 'visible': False}, - 'label_field': {'value': 'test label', 'enabled': True, 'visible': False}, - 'checkBox_label': {'value': 'CheckBox: ', 'enabled': True, 'visible': False}, - 'label_label': {'value': 'Label: ', 'enabled': True, 'visible': False} - } - - self.simple_form.saveAllWidgetStates() - - self.assertEqual(self.simple_form.widget_states, expected_state) - - @skip_ci - def test_restoreAllSavedWidgetStates(self): - # Check that the state of all widgets is restored from the state variable - - state_to_restore = { - 'checkBox_field': {'value': True, 'enabled': False, 'visible': False}, - 'label_field': {'value': 'applyWidgetStates Test', 'enabled': True, 'visible': False}, - 'checkBox_label': {'value': 'CheckBox Test: ', 'enabled': True, 'visible': False}, - 'label_label': {'value': 'Label Test: ', 'enabled': True, 'visible': False} - } - - self.simple_form.widget_states = state_to_restore - - self.simple_form.restoreAllSavedWidgetStates() - - self.assertEqual(self.simple_form.getWidget( - 'checkBox').isChecked(), state_to_restore['checkBox_field']['value']) - self.assertEqual(self.simple_form.getWidget( - 'checkBox').isEnabled(), state_to_restore['checkBox_field']['enabled']) - self.assertEqual(self.simple_form.getWidget( - 'checkBox').isVisible(), state_to_restore['checkBox_field']['visible']) - self.assertEqual(self.simple_form.getWidget( - 'checkBox', 'label').text(), state_to_restore['checkBox_label']['value']) - self.assertEqual(self.simple_form.getWidget( - 'checkBox', 'label').isEnabled(), state_to_restore['checkBox_label']['enabled']) - self.assertEqual(self.simple_form.getWidget( - 'checkBox', 'label').isVisible(), state_to_restore['checkBox_label']['visible']) - self.assertEqual(self.simple_form.getWidget( - 'label').text(), state_to_restore['label_field']['value']) - self.assertEqual(self.simple_form.getWidget( - 'label').isEnabled(), state_to_restore['label_field']['enabled']) - self.assertEqual(self.simple_form.getWidget( - 'label').isVisible(), state_to_restore['label_field']['visible']) - self.assertEqual(self.simple_form.getWidget( - 'label', 'label').text(), state_to_restore['label_label']['value']) - self.assertEqual(self.simple_form.getWidget( - 'label', 'label').isEnabled(), state_to_restore['label_label']['enabled']) - self.assertEqual(self.simple_form.getWidget( - 'label', 'label').isVisible(), state_to_restore['label_label']['visible']) - - -@skip_ci -class FormDockWidgetStateTest(unittest.TestCase): - - @skip_ci - def setUp(self): - self.form = FormDockWidget() - add_every_widget_to_form(self.form) - self.simple_form = FormDockWidget() - add_two_widgets_to_form(self.simple_form) - - @skip_ci - def test_getWidgetState_returns_visibility(self): - # Check that the visibility of the widget is saved to the state - # Have to use magic mock as we can't set the visibility of the QLabel - # to be True, because the FormDialog is not visible - initial_label_visibility = True - self.form.getWidget('label').isVisible = mock.MagicMock() - self.form.getWidget( - 'label').isVisible.return_value = initial_label_visibility - - self.assertEqual(self.form.getWidgetState('label_field')[ - 'visible'], initial_label_visibility) - - final_label_visibility = False - self.form.getWidget('label').isVisible.return_value = False - - self.assertEqual(self.form.getWidgetState('label_field')[ - 'visible'], final_label_visibility) - - @skip_ci - def test_getWidgetState_returns_enabled_state(self): - # Check that the enabled state of the widget is saved to the state - - initial_label_enabled_state = True - - self.assertEqual(self.form.getWidgetState('label_field')[ - 'enabled'], initial_label_enabled_state) - - self.form.getWidget('label').setEnabled(False) - final_label_enabled_state = False - - self.assertEqual(self.form.getWidgetState('label_field')[ - 'enabled'], final_label_enabled_state) - - @skip_ci - def test_getWidgetState_returns_value_using_role_parameter_field(self): - # Check that the value of the QLabel is saved to the state - - initial_label_value = 'test label' - - self.assertEqual(self.form.getWidgetState( - 'label', 'field')['value'], initial_label_value) - - final_label_value = 'final test label' - self.form.getWidget('label').setText(final_label_value) - - self.assertEqual(self.form.getWidgetState( - 'label', 'field')['value'], final_label_value) - - @skip_ci - def test_getWidgetState_returns_value_using_role_parameter_label(self): - # Check that the value of the QLabel is saved to the state - - initial_label_value = 'test label' - - self.assertEqual(self.form.getWidgetState( - 'label', 'field')['value'], initial_label_value) - - final_label_value = 'final test label' - self.form.getWidget('label').setText(final_label_value) - - self.assertEqual(self.form.getWidgetState( - 'label', 'field')['value'], final_label_value) - - @skip_ci - def test_getWidgetState_returns_value_using_default_role_parameter(self): - # Check that the value of the QLabel is saved to the state - initial_label_value = 'test label' - - # In getWidgetState we do not specify if we want the 'field' or 'label' role, so it should default to 'field': - self.assertEqual(self.form.getWidgetState( - 'label')['value'], initial_label_value) - - final_label_value = 'final test label' - self.form.getWidget('label').setText(final_label_value) - - self.assertEqual(self.form.getWidgetState( - 'label')['value'], final_label_value) - - # Test value is saved for all widget types ---------------------------------------------------------------- - - @skip_ci - def test_getWidgetState_returns_QLabel_value(self): - # Check that the value of the QLabel is saved to the state - - initial_label_value = 'test label' - - self.assertEqual(self.form.getWidgetState( - 'label_field')['value'], initial_label_value) - - final_label_value = 'final test label' - self.form.getWidget('label').setText(final_label_value) - - self.assertEqual(self.form.getWidgetState( - 'label_field')['value'], final_label_value) - - @skip_ci - def test_getWidgetState_returns_QCheckBox_value(self): - # Check that the value of the QCheckBox is saved to the state - - initial_checkbox_value = False - - self.assertEqual(self.form.getWidgetState('checkBox_field')[ - 'value'], initial_checkbox_value) - - final_checkbox_value = True - self.form.getWidget('checkBox').setChecked(final_checkbox_value) - - self.assertEqual(self.form.getWidgetState( - 'checkBox_field')['value'], final_checkbox_value) - - @skip_ci - def test_getWidgetState_returns_QComboBox_value(self): - # Check that the value of the QComboBox is saved to the state - - combobox_list = ['test', 'test2'] - self.form.getWidget('comboBox').addItems(combobox_list) - - initial_combobox_value = 0 - - self.assertEqual(self.form.getWidgetState('comboBox_field')[ - 'value'], initial_combobox_value) - - final_combobox_value = 1 - self.form.getWidget('comboBox').setCurrentIndex(final_combobox_value) - - self.assertEqual(self.form.getWidgetState( - 'comboBox_field')['value'], final_combobox_value) - - @skip_ci - def test_getWidgetState_returns_QDoubleSpinBox_value(self): - # Check that the value of the QDoubleSpinBox is saved to the state - - initial_doubleSpinBox_value = 0.0 - - self.assertEqual(self.form.getWidgetState('doubleSpinBox_field')[ - 'value'], initial_doubleSpinBox_value) - - final_doubleSpinBox_value = 1.0 - self.form.getWidget('doubleSpinBox').setValue( - final_doubleSpinBox_value) - - self.assertEqual(self.form.getWidgetState('doubleSpinBox_field')[ - 'value'], final_doubleSpinBox_value) - - @skip_ci - def test_getWidgetState_returns_QSpinBox_value(self): - # Check that the value of the QSpinBox is saved to the state - - initial_spinBox_value = 0 - - self.assertEqual(self.form.getWidgetState('spinBox_field')[ - 'value'], initial_spinBox_value) - - final_spinBox_value = 1 - self.form.getWidget('spinBox').setValue(final_spinBox_value) - - self.assertEqual(self.form.getWidgetState( - 'spinBox_field')['value'], final_spinBox_value) - - @skip_ci - def test_getWidgetState_returns_QSlider_value(self): - # Check that the value of the QSlider is saved to the state - - initial_slider_value = 0 - - self.assertEqual(self.form.getWidgetState( - 'slider_field')['value'], initial_slider_value) - - final_slider_value = 1 - self.form.getWidget('slider').setValue(final_slider_value) - - self.assertEqual(self.form.getWidgetState( - 'slider_field')['value'], final_slider_value) - - @skip_ci - def test_getWidgetState_returns_UISliderWidget_value(self): - # Check that the value of the UISliderWidget is returned in the state - - initial_slider_value = 0 - - self.assertEqual(self.form.getWidgetState( - 'uiSliderWidget_field')['value'], initial_slider_value) - - final_slider_value = 1 - self.form.getWidget('uiSliderWidget').setValue(final_slider_value) - - self.assertEqual(self.form.getWidgetState( - 'uiSliderWidget_field')['value'], final_slider_value) - - @skip_ci - def test_getWidgetState_returns_QLineEdit_value(self): - # Check that the value of the QLineEdit is saved to the state - - initial_lineEdit_value = '' - self.form.getWidget('lineEdit').setText(initial_lineEdit_value) - - self.assertEqual(self.form.getWidgetState('lineEdit_field')[ - 'value'], initial_lineEdit_value) - - final_lineEdit_value = 'test' - self.form.getWidget('lineEdit').setText(final_lineEdit_value) - - self.assertEqual(self.form.getWidgetState( - 'lineEdit_field')['value'], final_lineEdit_value) - - @skip_ci - def test_getWidgetState_returns_QTextEdit_value(self): - # Check that the value of the QTextEdit is saved to the state - - initial_textEdit_value = '' - self.form.getWidget('textEdit').setText(initial_textEdit_value) - - self.assertEqual(self.form.getWidgetState('textEdit_field')[ - 'value'], initial_textEdit_value) - - final_textEdit_value = 'test' - self.form.getWidget('textEdit').setText(final_textEdit_value) - - self.assertEqual(self.form.getWidgetState( - 'textEdit_field')['value'], final_textEdit_value) - - @skip_ci - def test_getWidgetState_returns_QPlainTextEdit_value(self): - # Check that the value of the QPlainTextEdit is saved to the state - - initial_plainTextEdit_value = '' - self.form.getWidget('plainTextEdit').setPlainText( - initial_plainTextEdit_value) - - self.assertEqual(self.form.getWidgetState('plainTextEdit_field')[ - 'value'], initial_plainTextEdit_value) - - final_plainTextEdit_value = 'test' - self.form.getWidget('plainTextEdit').setPlainText( - final_plainTextEdit_value) - - self.assertEqual(self.form.getWidgetState('plainTextEdit_field')[ - 'value'], final_plainTextEdit_value) - - @skip_ci - def test_getWidgetState_returns_QPushButton_value(self): - # Check that the value of the QPushButton is saved to the state - - initial_button_value = False - - self.assertEqual(self.form.getWidgetState( - 'button_field')['value'], initial_button_value) - - final_button_value = True - self.form.getWidget('button').setCheckable(True) - self.form.getWidget('button').setChecked(final_button_value) - - self.assertEqual(self.form.getWidgetState( - 'button_field')['value'], final_button_value) - - @skip_ci - def test_getWidgetState_returns_QRadioButton_value(self): - # Check that the value of the QRadioButton is saved to the state - - initial_radio_value = False - - self.assertEqual(self.form.getWidgetState( - 'radioButton_field')['value'], initial_radio_value) - - final_radio_value = True - self.form.getWidget('radioButton').setChecked(final_radio_value) - - self.assertEqual(self.form.getWidgetState( - 'radioButton_field')['value'], final_radio_value) - - @skip_ci - def test_applyWidgetStates(self): - state_to_set = { - 'checkBox_field': {'value': True, 'enabled': False, 'visible': False}, - 'label_field': {'value': 'applyWidgetStates Test', 'enabled': True, 'visible': False} - } - - self.simple_form.applyWidgetStates(state_to_set) - - self.assertEqual(self.simple_form.getWidgetState( - 'checkBox_field'), state_to_set['checkBox_field']) - self.assertEqual(self.simple_form.getWidgetState( - 'label_field'), state_to_set['label_field']) - - @skip_ci - def test_applyWidgetState(self): - state_to_set = {'value': True, 'enabled': False, 'visible': False} - self.simple_form.applyWidgetState('checkBox_field', state_to_set) - self.assertEqual(self.simple_form.getWidgetState( - 'checkBox_field'), state_to_set) - - @skip_ci - def test_applyWidgetState_using_role_parameter_field(self): - state_to_set = {'value': True, 'enabled': False, 'visible': False} - self.simple_form.applyWidgetState( - 'checkBox', state_to_set, role='field') - self.assertEqual(self.simple_form.getWidgetState( - 'checkBox', 'field'), state_to_set) - - @skip_ci - def test_applyWidgetState_using_role_parameter_label(self): - state_to_set = {'value': 'test the checkbox:', - 'enabled': False, 'visible': False} - self.simple_form.applyWidgetState( - 'checkBox', state_to_set, role='label') - self.assertEqual(self.simple_form.getWidgetState( - 'checkBox', 'label'), state_to_set) - - @skip_ci - def test_applyWidgetState_using_role_parameter_default(self): - state_to_set = {'value': True, 'enabled': False, 'visible': False} - self.simple_form.applyWidgetState('checkBox', state_to_set) - self.assertEqual(self.simple_form.getWidgetState( - 'checkBox'), state_to_set) - - @skip_ci - def test_getAllWidgetStates(self): - # Check that the state of all widgets is returned - - expected_state = { - 'checkBox_field': {'value': False, 'enabled': True, 'visible': False}, - 'label_field': {'value': 'test label', 'enabled': True, 'visible': False}, - 'checkBox_label': {'value': 'CheckBox: ', 'enabled': True, 'visible': False}, - 'label_label': {'value': 'Label: ', 'enabled': True, 'visible': False} - } - - self.assertEqual(self.simple_form.getAllWidgetStates(), expected_state) - - @skip_ci - def test_saveAllWidgetStates(self): - # Check that the state of all widgets is saved to the state variable - - expected_state = { - 'checkBox_field': {'value': False, 'enabled': True, 'visible': False}, - 'label_field': {'value': 'test label', 'enabled': True, 'visible': False}, - 'checkBox_label': {'value': 'CheckBox: ', 'enabled': True, 'visible': False}, - 'label_label': {'value': 'Label: ', 'enabled': True, 'visible': False} - } - - self.simple_form.saveAllWidgetStates() - - self.assertEqual( - self.simple_form.widget().widget_states, expected_state) - - @skip_ci def test_restoreAllSavedWidgetStates(self): - # Check that the state of all widgets is restored from the state variable - + """Check that the state of all widgets is restored from the state variable""" state_to_restore = { 'checkBox_field': {'value': True, 'enabled': False, 'visible': False}, 'label_field': {'value': 'applyWidgetStates Test', 'enabled': True, 'visible': False}, 'checkBox_label': {'value': 'CheckBox Test: ', 'enabled': True, 'visible': False}, 'label_label': {'value': 'Label Test: ', 'enabled': True, 'visible': False} } - self.simple_form.widget().widget_states = state_to_restore - self.simple_form.restoreAllSavedWidgetStates() self.assertEqual(self.simple_form.getWidget( From 4ba9d8e54b38d72eaa6dcbea3e891294da4225a4 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Thu, 6 Jul 2023 19:57:30 +0100 Subject: [PATCH 12/15] review comments --- LICENSE | 3 ++- eqt/io.py | 9 ++++++--- pyproject.toml | 4 +++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index 655d8f2..50acdf7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ -Copyright 2020 Edoardo Pasca +Copyright 2020 United Kingdom Research and Innovation (https://stfc.ukri.org) +Authors: https://github.com/paskino/qt-elements/graphs/contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/eqt/io.py b/eqt/io.py index 5d5f715..957e5c8 100644 --- a/eqt/io.py +++ b/eqt/io.py @@ -6,9 +6,12 @@ def zip_directory(directory: str, compress: bool=True): """ Zips a directory, optionally compressing it. - Args: - directory: The directory to be zipped. - compress: Whether to compress the directory. + Parameters + ---------- + directory + The directory to be zipped. + compress + Whether to compress the directory. """ compress_type = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED with zipfile.ZipFile(f'{directory}.zip', 'a') as zipper: diff --git a/pyproject.toml b/pyproject.toml index 4d30de7..aa61063 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,9 @@ changelog = "https://github.com/paskino/qt-elements/releases" [project] name = "eqt" dynamic = ["version"] -authors = [{name = "Edoardo Pasca", email = "edoardo.pasca@stfc.ac.uk"}] +authors = [ + {name = "Edoardo Pasca", email = "edoardo.pasca@stfc.ac.uk"}, + {name = "Laura Murgatroyd", email = "laura.murgatroyd@stfc.ac.uk"}] maintainers = [{name = "Casper da Costa-Luis", email = "casper.dcl@physics.org"}] description = "A number of templates and tools to develop Qt GUIs with Python effectively" readme = "README.md" From 4e918e64c7279c8c7fcdfcb0d2011f7a7e022a25 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Mon, 17 Jul 2023 17:58:51 +0000 Subject: [PATCH 13/15] build: enforce annotated tags --- .github/workflows/README.md | 27 ++++++++++++++++++++++++--- .github/workflows/test.yml | 33 +++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 586b491..3076995 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -14,8 +14,29 @@ Builds binary (`*.whl`) & source (`*.tar.gz`) distributions. ## Releasing -Runs automatically -- when a [tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) is pushed -- after builds (above) succeed. +Runs automatically -- when an annotated tag is pushed -- after builds (above) succeed. -Publishes to [PyPI](https://pypi.org/project/eqt) and drafts changelog (release notes) at . +Publishes to [PyPI](https://pypi.org/project/eqt). -:warning: The draft notes above need to be manually tidied & approved by a maintainer. +:warning: The annotated tag's `title` must be `Version ` (separated by a blank line) and the `body` must contain release notes, e.g.: + +```bash +git tag v1.33.7 -a +``` + +```md +Version 1.33.7 + + +``` + +The `` will be used in the changelog (below). + +### Changelog + +See , or offline: + +```bash +git config --global alias.changelog 'for-each-ref --sort=-*authordate --format="# %(contents:subject)%0a%(contents:body)" refs/tags' +git changelog +``` diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7905c25..cfafad5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,28 @@ jobs: - uses: actions/setup-python@v4 with: python-version: '3.x' + - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + id: changes + name: Check annotated tag + run: | + title=$(git for-each-ref --format="%(contents:subject)" ${GITHUB_REF}) + body=$(git for-each-ref --format="%(contents:body)" ${GITHUB_REF}) + tag="${GITHUB_REF#refs/tags/}" + if test "$title" = "Version ${tag#v}" -a -n "$body"; then + echo "title=$title" >> "$GITHUB_OUTPUT" + echo "body=$body" >> "$GITHUB_OUTPUT" + else + echo "::error title=Missing tag annotation::$tag" + changelog=$(git log --pretty='format:%d%n- %s%n%b---' $(git tag --sort=v:refname | tail -n2 | head -n1)..HEAD) + cat <> "$GITHUB_STEP_SUMMARY" + # Missing tag annotation + ## Fix + See . + ## Suggested body + $changelog + EOF + exit 1 + fi - id: dist uses: casperdcl/deploy-pypi@v2 with: @@ -38,9 +60,12 @@ jobs: upload: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') name: Release - run: | - changelog=$(git log --pretty='format:%d%n- %s%n%b---' $(git tag --sort=v:refname | tail -n2 | head -n1)..HEAD) - tag="${GITHUB_REF#refs/tags/}" - gh release create --title "Version $tag" --draft --notes "$changelog" "$tag" dist/${{ steps.dist.outputs.whl }} dist/${{ steps.dist.outputs.targz }} + run: > + gh release create + --title "${{ steps.changes.outputs.title }}" + --notes "${{ steps.changes.outputs.body }}" + "${GITHUB_REF#refs/tags/}" + dist/${{ steps.dist.outputs.whl }} + dist/${{ steps.dist.outputs.targz }} env: GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} From 756ea955130e1f91070482e89c66da2caaed4385 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Mon, 17 Jul 2023 18:13:45 +0000 Subject: [PATCH 14/15] build: skip PyPI upload on existing version --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cfafad5..16f41f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,6 +58,7 @@ jobs: build: true password: ${{ secrets.EQT_SECRET_TOKEN }} upload: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} + skip_existing: true # allow for annotated tag amendments - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') name: Release run: > From f3ca0dd1af440dfc5daf67a1e42e14df28becbb2 Mon Sep 17 00:00:00 2001 From: Casper da Costa-Luis Date: Wed, 19 Jul 2023 16:36:49 +0000 Subject: [PATCH 15/15] update URLs after repo migration --- .github/workflows/README.md | 2 +- .github/workflows/test.yml | 2 +- LICENSE | 2 +- README.md | 10 +++++----- pyproject.toml | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 3076995..a8ff02e 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -34,7 +34,7 @@ The `` will be used in the changelog (below). ### Changelog -See , or offline: +See , or offline: ```bash git config --global alias.changelog 'for-each-ref --sort=-*authordate --format="# %(contents:subject)%0a%(contents:body)" refs/tags' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 16f41f3..19ae71f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,7 +46,7 @@ jobs: cat <> "$GITHUB_STEP_SUMMARY" # Missing tag annotation ## Fix - See . + See . ## Suggested body $changelog EOF diff --git a/LICENSE b/LICENSE index 50acdf7..9e3bf15 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Copyright 2020 United Kingdom Research and Innovation (https://stfc.ukri.org) -Authors: https://github.com/paskino/qt-elements/graphs/contributors +Authors: https://github.com/TomographicImaging/eqt/graphs/contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index b9ab7e6..27a7a94 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ conda install eqt -c paskino ``` ### Example -In the `example` directory there is an example on how to launch a `QDialog` with a form inside using `eqt`'s [`QWidget`](https://github.com/paskino/qt-elements/blob/main/examples/dialog_example.py) or [`FormDialog`](https://github.com/paskino/qt-elements/blob/main/examples/dialog_example_2.py). +In the `example` directory there is an example on how to launch a `QDialog` with a form inside using `eqt`'s [`QWidget`](https://github.com/TomographicImaging/eqt/blob/main/examples/dialog_example.py) or [`FormDialog`](https://github.com/TomographicImaging/eqt/blob/main/examples/dialog_example_2.py). ### Running asynchronous tasks To run a function in a separate thread we use a `Worker` which is a subclass of a `QRunnable`. @@ -35,7 +35,7 @@ For the `Worker` to work one needs to define: 1. the function that does what you need 2. Optional callback methods to get the status of the thread by means of `QtCore.QSignal`s -On [initialisation](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L32-L38) of the `Worker` the user needs to pass the function that has to run in the thread, i.e. `fn` below, and additional optional positional and keyword arguments, which will be passed on to the actual function that is run in the `QRunnable`. +On [initialisation](https://github.com/TomographicImaging/eqt/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L32-L38) of the `Worker` the user needs to pass the function that has to run in the thread, i.e. `fn` below, and additional optional positional and keyword arguments, which will be passed on to the actual function that is run in the `QRunnable`. ```python class Worker(QtCore.QRunnable): @@ -46,13 +46,13 @@ class Worker(QtCore.QRunnable): self.signals = WorkerSignals() ``` -In practice the user will need to pass to the `Worker` as many parameters as there are listed in the [function](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L56) to be run. +In practice the user will need to pass to the `Worker` as many parameters as there are listed in the [function](https://github.com/TomographicImaging/eqt/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L56) to be run. ```python result = self.fn(*self.args, **self.kwargs) ``` -But `Worker` will [add](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L41-L43) to the `**kwargs` the following `QSignal`. +But `Worker` will [add](https://github.com/TomographicImaging/eqt/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L41-L43) to the `**kwargs` the following `QSignal`. ```python # Add progress callback to kwargs @@ -87,7 +87,7 @@ worker.signals.progress.connect(handle_progress) So, each time `fn` comes to `progress_callback.emit( i )` the function `handle_progress` will be called with the parameter `i` of its `for` loop. #### What are the available signals? -The signals that are available in the `Worker` class are defined in [`WorkerSignal`](https://github.com/paskino/qt-elements/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L66) and are the following. Below you can also see the type of data that each signal can emit. +The signals that are available in the `Worker` class are defined in [`WorkerSignal`](https://github.com/TomographicImaging/eqt/blob/535e487d09d928713d7d6aa1123657597627c4b0/eqt/threading/QtThreading.py#L66) and are the following. Below you can also see the type of data that each signal can emit. ```python finished = QtCore.Signal() error = QtCore.Signal(tuple) diff --git a/pyproject.toml b/pyproject.toml index aa61063..5f16a8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,9 +10,9 @@ write_to_template = "__version__ = '{version}'\n" exclude = ["test"] [project.urls] -documentation = "https://github.com/paskino/qt-elements#readme" -repository = "https://github.com/paskino/qt-elements" -changelog = "https://github.com/paskino/qt-elements/releases" +documentation = "https://github.com/TomographicImaging/eqt#readme" +repository = "https://github.com/TomographicImaging/eqt" +changelog = "https://github.com/TomographicImaging/eqt/releases" [project] name = "eqt"