New Upstream Release - python-echo

Ready changes

Summary

Merged new upstream version: 0.8.0 (was: 0.5).

Resulting package

Built on 2022-11-19T18:22 (took 20m10s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-echo

Lintian Result

Diff

diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 0000000..a55cbf7
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,17 @@
+changelog:
+  exclude:
+    authors:
+      - pre-commit-ci
+  categories:
+    - title: Bug Fixes
+      labels:
+        - bug
+    - title: New Features
+      labels:
+        - enhancement
+    - title: Documentation
+      labels:
+        - Documentation
+    - title: Other Changes
+      labels:
+        - "*"
diff --git a/.github/workflows/ci_workflows.yml b/.github/workflows/ci_workflows.yml
new file mode 100644
index 0000000..655f176
--- /dev/null
+++ b/.github/workflows/ci_workflows.yml
@@ -0,0 +1,89 @@
+name: CI Workflows
+
+on:
+  push:
+    branches:
+    - main
+    tags:
+    - '*'
+  pull_request:
+
+jobs:
+  initial_checks:
+    # Mandatory checks before CI tests
+    uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1
+    with:
+      coverage: false
+      envs: |
+        # Code style
+        - linux: codestyle
+
+  tests:
+    needs: initial_checks
+    uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1
+    with:
+      coverage: codecov
+      display: true
+      # Linux PyQt 5.15 and 6.3 installations require apt-getting xcb and EGL deps
+      libraries: |
+        apt:
+          - '^libxcb.*-dev'
+          - libxkbcommon-x11-dev
+          - libegl1-mesa
+
+      envs: |
+        # Standard tests
+        # Linux builds - test on all supported PyQt5, PyQt6 and PySide2 versions
+        - linux: py37-test-pyqt510
+        - linux: py37-test-pyqt511
+        - linux: py37-test-pyqt512
+        - linux: py37-test-pyqt513
+        - linux: py39-test-pyqt515
+        - linux: py310-test-pyqt63
+
+        - linux: py37-test-pyside513
+        - linux: py38-test-pyside514
+        - linux: py310-test-pyside515
+        - linux: py310-test-pyside63
+
+        # Test a few configurations on MacOS X (ask for arm64 with py310; may not be available yet)
+        - macos: py37-test-pyqt513
+        - macos: py38-test-pyqt514
+        - macos: py310-test-pyqt515
+          PLAT: arm64
+        - macos: py310-test-pyqt63
+          PLAT: arm64
+
+        - macos: py39-test-pyside515
+        - macos: py310-test-pyside63
+
+        # Test some configurations on Windows
+        - windows: py37-test-pyqt510
+        - windows: py38-test-pyqt514
+        - windows: py39-test-pyqt515
+        - windows: py310-test-pyqt63
+
+        - windows: py38-test-pyside515
+        - windows: py310-test-pyside63
+
+        # Try out documentation build on Linux, macOS and Windows
+        - linux: py39-docs-pyqt513
+          coverage: false
+        - linux: py310-docs-pyqt63
+          coverage: false
+
+        - macos: py37-docs-pyqt513
+          coverage: false
+
+        - windows: py38-docs-pyqt513
+          coverage: false
+
+  publish:
+    needs: tests
+    uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@v1
+    with:
+      libraries: '^libxcb.*-dev libxkbcommon-x11-dev libgl1-mesa-glx xvfb'
+      test_extras: 'test,qt'
+      test_command: Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & sleep 3; DISPLAY=:99.0 pytest --pyargs echo
+    secrets:
+      pypi_token: ${{ secrets.pypi_token }}
diff --git a/.github/workflows/update-changelog.yaml b/.github/workflows/update-changelog.yaml
new file mode 100644
index 0000000..1432b5e
--- /dev/null
+++ b/.github/workflows/update-changelog.yaml
@@ -0,0 +1,33 @@
+# This workflow takes the GitHub release notes an updates the changelog on the
+# main branch with the body of the release notes, thereby keeping a log in
+# the git repo of the changes.
+
+name: "Update Changelog"
+
+on:
+  release:
+    types: [released]
+
+jobs:
+  update:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+        with:
+          ref: main
+
+      - name: Update Changelog
+        uses: stefanzweifel/changelog-updater-action@v1
+        with:
+          release-notes: ${{ github.event.release.body }}
+          latest-version: ${{ github.event.release.name }}
+          path-to-changelog: CHANGES.md
+
+      - name: Commit updated CHANGELOG
+        uses: stefanzweifel/git-auto-commit-action@v4
+        with:
+          branch: main
+          commit_message: Update CHANGELOG
+          file_pattern: CHANGES.md
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 0000000..66c3b56
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,51 @@
+# Full changelog
+
+## v0.7 - 2022-09-27
+
+### What's Changed
+
+#### Bug Fixes
+
+- Fix compatibility with PyQt6 by @astrofrog in https://github.com/glue-viz/echo/pull/29
+
+#### Other Changes
+
+- Migrate CI tests and publishing task to GitHub Actions by @dhomeier in https://github.com/glue-viz/echo/pull/33
+- Add PySide6 test envs to CI by @dhomeier in https://github.com/glue-viz/echo/pull/34
+
+### New Contributors
+
+- @dhomeier made their first contribution in https://github.com/glue-viz/echo/pull/33
+
+**Full Changelog**: https://github.com/glue-viz/echo/compare/v0.5...v0.6
+
+## 0.6 (2021-12-14)
+
+- Fixed compatibility with recent PyQt5 versions.
+
+## 0.5 (2020-11-08)
+
+- Add the ability to specify for `SelectionCallbackProperty` whether to
+  compare choices using equality or identity. [#26]
+
+- Fixed an issue that could lead to `CallbackContainer` returning dead
+  weak references during iteration. [#27]
+
+## 0.4 (2020-05-04)
+
+- Added the ability to add arbitrary callbacks to `CallbackDict` and
+  `CallbackList` via the `.callbacks` attribute. [#25]
+
+## 0.3 (2020-05-04)
+
+- Fix setting of defaults in callback list and dict properties. [#24]
+
+## 0.2 (2020-04-11)
+
+- Python 3.6 or later is now required. [#20]
+  
+- Significant refactoring of the package. [#20]
+
+## 0.1 (2014-03-13)
+
+- Initial version
diff --git a/CHANGES.rst b/CHANGES.rst
deleted file mode 100644
index 2e03d5c..0000000
--- a/CHANGES.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-Full changelog
-==============
-
-0.5 (2020-11-08)
-----------------
-
-* Add the ability to specify for ``SelectionCallbackProperty`` whether to
-  compare choices using equality or identity. [#26]
-
-* Fixed an issue that could lead to ``CallbackContainer`` returning dead
-  weak references during iteration. [#27]
-
-0.4 (2020-05-04)
-----------------
-
-* Added the ability to add arbitrary callbacks to ``CallbackDict`` and
- ``CallbackList`` via the ``.callbacks`` attribute. [#25]
-
-0.3 (2020-05-04)
-----------------
-
-* Fix setting of defaults in callback list and dict properties. [#24]
-
-0.2 (2020-04-11)
-----------------
-
-* Python 3.6 or later is now required. [#20]
-
-* Significant refactoring of the package. [#20]
-
-0.1 (2014-03-13)
-----------------
-
-* Initial version
diff --git a/PKG-INFO b/PKG-INFO
index dd5fd8c..efddff2 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: echo
-Version: 0.5
+Version: 0.8.0
 Summary: Callback Properties in Python
 Home-page: https://github.com/glue-viz/echo
 Author: Chris Beaumont and Thomas Robitaille
@@ -8,52 +8,6 @@ Author-email: thomas.robitaille@gmail.com
 Maintainer: Chris Beaumont and Thomas Robitaille
 Maintainer-email: thomas.robitaille@gmail.com
 License: MIT
-Description: |Azure Status| |Coverage status|
-        
-        echo: Callback Properties in Python
-        ===================================
-        
-        Echo is a small library for attaching callback functions to property
-        state changes. For example:
-        
-        ::
-        
-            class Switch(object):
-                state = CallbackProperty('off')
-        
-            def report_change(state):
-                print 'the switch is %s' % state
-        
-            s = Switch()
-            add_callback(s, 'state', report_change)
-        
-            s.state = 'on'  # prints 'the switch is on'
-        
-        CalllbackProperties can also be built using decorators
-        
-        ::
-        
-            class Switch(object):
-        
-                  @callback_property
-                  def state(self):
-                    return self._state
-        
-                  @state.setter
-                  def state(self, value):
-                      if value not in ['on', 'off']:
-                          raise ValueError("invalid setting")
-                      self._state = value
-        
-        Full documentation is avilable `here <http://echo.readthedocs.org/>`__
-        
-        .. |Azure Status| image:: https://dev.azure.com/glue-viz/echo/_apis/build/status/glue-viz.echo?branchName=master
-           :target: https://dev.azure.com/glue-viz/echo/_build/latest?definitionId=4&branchName=master
-        .. |Coverage Status| image:: https://codecov.io/gh/glue-viz/echo/branch/master/graph/badge.svg
-           :target: https://codecov.io/gh/glue-viz/echo
-        
-        
-Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
 Classifier: Environment :: Console
 Classifier: Intended Audience :: Developers
@@ -68,3 +22,49 @@ Requires-Python: >=3.6
 Provides-Extra: test
 Provides-Extra: docs
 Provides-Extra: qt
+License-File: LICENSE
+
+|Azure Status| |Coverage status|
+
+echo: Callback Properties in Python
+===================================
+
+Echo is a small library for attaching callback functions to property
+state changes. For example:
+
+::
+
+    class Switch(object):
+        state = CallbackProperty('off')
+
+    def report_change(state):
+        print 'the switch is %s' % state
+
+    s = Switch()
+    add_callback(s, 'state', report_change)
+
+    s.state = 'on'  # prints 'the switch is on'
+
+CalllbackProperties can also be built using decorators
+
+::
+
+    class Switch(object):
+
+          @callback_property
+          def state(self):
+            return self._state
+
+          @state.setter
+          def state(self, value):
+              if value not in ['on', 'off']:
+                  raise ValueError("invalid setting")
+              self._state = value
+
+Full documentation is avilable `here <http://echo.readthedocs.org/>`__
+
+.. |Azure Status| image:: https://dev.azure.com/glue-viz/echo/_apis/build/status/glue-viz.echo?branchName=master
+   :target: https://dev.azure.com/glue-viz/echo/_build/latest?definitionId=4&branchName=master
+.. |Coverage Status| image:: https://codecov.io/gh/glue-viz/echo/branch/master/graph/badge.svg
+   :target: https://codecov.io/gh/glue-viz/echo
+
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index d273352..0000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-resources:
-  repositories:
-  - repository: OpenAstronomy
-    type: github
-    endpoint: glue-viz
-    name: OpenAstronomy/azure-pipelines-templates
-    ref: master
-
-jobs:
-
-- template: run-tox-env.yml@OpenAstronomy
-  parameters:
-
-    xvfb: true
-    coverage: codecov
-    libraries:
-      apt:
-        - libxkbcommon-x11-0
-
-    envs:
-
-    - linux: codestyle
-      libraries: {}
-      coverage: 'false'
-
-    - linux: py36-test-pyqt59
-    - linux: py37-test-pyqt510
-    - linux: py37-test-pyqt511
-    - linux: py37-test-pyqt512
-    - linux: py37-test-pyqt513
-    - linux: py38-test-pyqt514
-    - linux: py36-test-pyside512
-    - linux: py37-test-pyside513
-    - linux: py38-test-pyside514
-
-    - macos: py36-test-pyqt59
-    - windows: py37-test-pyqt510
-    - macos: py37-test-pyqt511
-    - windows: py37-test-pyqt512
-    - macos: py37-test-pyqt513
-    - windows: py38-test-pyqt514
-    - macos: py36-test-pyside512
-    - windows: py37-test-pyside513
-    - macos: py38-test-pyside514
-
-    - linux: py36-docs-pyqt513
-    - macos: py37-docs-pyqt513
-    - windows: py38-docs-pyqt513
diff --git a/debian/changelog b/debian/changelog
index 9555577..0796f3e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,9 +1,10 @@
-python-echo (0.5-3) UNRELEASED; urgency=medium
+python-echo (0.8.0-1) UNRELEASED; urgency=medium
 
   * Set upstream metadata fields: Repository-Browse.
   * Update standards version to 4.6.1, no changes needed.
+  * New upstream release.
 
- -- Debian Janitor <janitor@jelmer.uk>  Tue, 15 Nov 2022 10:57:32 -0000
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 19 Nov 2022 18:07:49 -0000
 
 python-echo (0.5-2) unstable; urgency=medium
 
diff --git a/echo.egg-info/PKG-INFO b/echo.egg-info/PKG-INFO
index dd5fd8c..efddff2 100644
--- a/echo.egg-info/PKG-INFO
+++ b/echo.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: echo
-Version: 0.5
+Version: 0.8.0
 Summary: Callback Properties in Python
 Home-page: https://github.com/glue-viz/echo
 Author: Chris Beaumont and Thomas Robitaille
@@ -8,52 +8,6 @@ Author-email: thomas.robitaille@gmail.com
 Maintainer: Chris Beaumont and Thomas Robitaille
 Maintainer-email: thomas.robitaille@gmail.com
 License: MIT
-Description: |Azure Status| |Coverage status|
-        
-        echo: Callback Properties in Python
-        ===================================
-        
-        Echo is a small library for attaching callback functions to property
-        state changes. For example:
-        
-        ::
-        
-            class Switch(object):
-                state = CallbackProperty('off')
-        
-            def report_change(state):
-                print 'the switch is %s' % state
-        
-            s = Switch()
-            add_callback(s, 'state', report_change)
-        
-            s.state = 'on'  # prints 'the switch is on'
-        
-        CalllbackProperties can also be built using decorators
-        
-        ::
-        
-            class Switch(object):
-        
-                  @callback_property
-                  def state(self):
-                    return self._state
-        
-                  @state.setter
-                  def state(self, value):
-                      if value not in ['on', 'off']:
-                          raise ValueError("invalid setting")
-                      self._state = value
-        
-        Full documentation is avilable `here <http://echo.readthedocs.org/>`__
-        
-        .. |Azure Status| image:: https://dev.azure.com/glue-viz/echo/_apis/build/status/glue-viz.echo?branchName=master
-           :target: https://dev.azure.com/glue-viz/echo/_build/latest?definitionId=4&branchName=master
-        .. |Coverage Status| image:: https://codecov.io/gh/glue-viz/echo/branch/master/graph/badge.svg
-           :target: https://codecov.io/gh/glue-viz/echo
-        
-        
-Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
 Classifier: Environment :: Console
 Classifier: Intended Audience :: Developers
@@ -68,3 +22,49 @@ Requires-Python: >=3.6
 Provides-Extra: test
 Provides-Extra: docs
 Provides-Extra: qt
+License-File: LICENSE
+
+|Azure Status| |Coverage status|
+
+echo: Callback Properties in Python
+===================================
+
+Echo is a small library for attaching callback functions to property
+state changes. For example:
+
+::
+
+    class Switch(object):
+        state = CallbackProperty('off')
+
+    def report_change(state):
+        print 'the switch is %s' % state
+
+    s = Switch()
+    add_callback(s, 'state', report_change)
+
+    s.state = 'on'  # prints 'the switch is on'
+
+CalllbackProperties can also be built using decorators
+
+::
+
+    class Switch(object):
+
+          @callback_property
+          def state(self):
+            return self._state
+
+          @state.setter
+          def state(self, value):
+              if value not in ['on', 'off']:
+                  raise ValueError("invalid setting")
+              self._state = value
+
+Full documentation is avilable `here <http://echo.readthedocs.org/>`__
+
+.. |Azure Status| image:: https://dev.azure.com/glue-viz/echo/_apis/build/status/glue-viz.echo?branchName=master
+   :target: https://dev.azure.com/glue-viz/echo/_build/latest?definitionId=4&branchName=master
+.. |Coverage Status| image:: https://codecov.io/gh/glue-viz/echo/branch/master/graph/badge.svg
+   :target: https://codecov.io/gh/glue-viz/echo
+
diff --git a/echo.egg-info/SOURCES.txt b/echo.egg-info/SOURCES.txt
index 23f8717..f96843d 100644
--- a/echo.egg-info/SOURCES.txt
+++ b/echo.egg-info/SOURCES.txt
@@ -1,15 +1,17 @@
 .coveragerc
 .gitignore
 .readthedocs.yml
-CHANGES.rst
+CHANGES.md
 LICENSE
 MANIFEST.in
 README.rst
-azure-pipelines.yml
 requirements.txt
 setup.cfg
 setup.py
 tox.ini
+.github/release.yml
+.github/workflows/ci_workflows.yml
+.github/workflows/update-changelog.yaml
 doc/Makefile
 doc/api.rst
 doc/conf.py
@@ -37,6 +39,7 @@ echo/qt/tests/__init__.py
 echo/qt/tests/test_autoconnect.py
 echo/qt/tests/test_connect.py
 echo/qt/tests/test_connect_combo_selection.py
+echo/qt/tests/test_connect_list_selection.py
 echo/tests/__init__.py
 echo/tests/test_containers.py
 echo/tests/test_core.py
diff --git a/echo/qt/connect.py b/echo/qt/connect.py
index 4a77540..51c97ad 100644
--- a/echo/qt/connect.py
+++ b/echo/qt/connect.py
@@ -337,7 +337,7 @@ class connect_value(BaseConnection):
             imin, imax = self._widget.minimum(), self._widget.maximum()
             value = ((value - self._value_range[0])
                      / (self._value_range[1] - self._value_range[0]) * (imax - imin) + imin)
-        if isinstance(self._widget, QtWidgets.QSlider):
+        if isinstance(self._widget, (QtWidgets.QSlider, QtWidgets.QSpinBox)):
             self._widget.setValue(int(value))
         else:
             self._widget.setValue(value)
@@ -450,7 +450,7 @@ class connect_combo_selection(BaseConnection):
                 if isinstance(choice, ChoiceSeparator):
                     item = combo_model.item(index)
                     palette = self._widget.palette()
-                    item.setFlags(Qt.ItemFlags(int(item.flags()) & int(~(Qt.ItemIsSelectable | Qt.ItemIsEnabled))))
+                    item.setFlags(item.flags() & ~(Qt.ItemIsSelectable | Qt.ItemIsEnabled))
                     item.setData(palette.color(QtGui.QPalette.Disabled, QtGui.QPalette.Text))
 
             choices_updated = True
@@ -516,7 +516,7 @@ class connect_list_selection(BaseConnection):
         choice_labels = getattr(type(self._instance), self._prop).get_choice_labels(self._instance)
 
         for idx in range(len(choices)):
-            if choices[idx] is value:
+            if choices[idx] is value or (choices[idx] == value) is True:
                 break
         else:
             idx = -1
@@ -541,7 +541,7 @@ class connect_list_selection(BaseConnection):
 
                 # We interpret None data as being disabled rows (used for headers)
                 if isinstance(choice, ChoiceSeparator):
-                    item.setFlags(Qt.ItemFlags(int(item.flags()) & int(~(Qt.ItemIsSelectable | Qt.ItemIsEnabled))))
+                    item.setFlags(item.flags() & ~(Qt.ItemIsSelectable | Qt.ItemIsEnabled))
 
         if len(self._widget.selectedItems()) == 0:
             current_index = -1
diff --git a/echo/qt/tests/test_connect_list_selection.py b/echo/qt/tests/test_connect_list_selection.py
new file mode 100644
index 0000000..b1caf71
--- /dev/null
+++ b/echo/qt/tests/test_connect_list_selection.py
@@ -0,0 +1,135 @@
+import pytest
+import numpy as np
+
+from qtpy import QtWidgets
+from qtpy.QtCore import Qt
+
+from echo.core import CallbackProperty
+from echo.selection import SelectionCallbackProperty, ChoiceSeparator
+from echo.qt.connect import connect_list_selection
+
+
+class Example(object):
+    a = SelectionCallbackProperty(default_index=1)
+    b = CallbackProperty()
+
+
+def test_connect_list_selection():
+
+    t = Example()
+
+    a_prop = getattr(type(t), 'a')
+    a_prop.set_choices(t, [4, 3.5])
+    a_prop.set_display_func(t, lambda x: 'value: {0}'.format(x))
+
+    list_widget = QtWidgets.QListWidget()
+
+    c1 = connect_list_selection(t, 'a', list_widget)  # noqa
+
+    assert list_widget.item(0).text() == 'value: 4'
+    assert list_widget.item(1).text() == 'value: 3.5'
+    assert list_widget.item(0).data(Qt.UserRole).data == 4
+    assert list_widget.item(1).data(Qt.UserRole).data == 3.5
+
+    list_widget.setCurrentItem(list_widget.item(1))
+    assert t.a == 3.5
+
+    list_widget.setCurrentItem(list_widget.item(0))
+    assert t.a == 4
+
+    list_widget.setCurrentItem(list_widget.item(-1))
+    assert t.a is None
+
+    t.a = 3.5
+    assert len(list_widget.selectedItems()) == 1
+    assert list_widget.selectedItems()[0] is list_widget.item(1)
+
+    t.a = 4
+    assert len(list_widget.selectedItems()) == 1
+    assert list_widget.selectedItems()[0] is list_widget.item(0)
+
+    with pytest.raises(ValueError) as exc:
+        t.a = 2
+    assert exc.value.args[0] == 'value 2 is not in valid choices: [4, 3.5]'
+
+    t.a = None
+    assert len(list_widget.selectedItems()) == 0
+
+    # Changing choices should change Qt list_widget box. Let's first try with a case
+    # in which there is a matching data value in the new list_widget box
+
+    t.a = 3.5
+    assert len(list_widget.selectedItems()) == 1
+    assert list_widget.selectedItems()[0] is list_widget.item(1)
+
+    a_prop.set_choices(t, (4, 5, 3.5))
+    assert list_widget.count() == 3
+
+    assert t.a == 3.5
+    assert len(list_widget.selectedItems()) == 1
+    assert list_widget.selectedItems()[0] is list_widget.item(2)
+
+    assert list_widget.item(0).text() == 'value: 4'
+    assert list_widget.item(1).text() == 'value: 5'
+    assert list_widget.item(2).text() == 'value: 3.5'
+    assert list_widget.item(0).data(Qt.UserRole).data == 4
+    assert list_widget.item(1).data(Qt.UserRole).data == 5
+    assert list_widget.item(2).data(Qt.UserRole).data == 3.5
+
+    # Now we change the choices so that there is no matching data - in this case
+    # the index should change to that given by default_index
+
+    a_prop.set_choices(t, (4, 5, 6))
+
+    assert t.a == 5
+    assert len(list_widget.selectedItems()) == 1
+    assert list_widget.selectedItems()[0] is list_widget.item(1)
+    assert list_widget.count() == 3
+
+    assert list_widget.item(0).text() == 'value: 4'
+    assert list_widget.item(1).text() == 'value: 5'
+    assert list_widget.item(2).text() == 'value: 6'
+    assert list_widget.item(0).data(Qt.UserRole).data == 4
+    assert list_widget.item(1).data(Qt.UserRole).data == 5
+    assert list_widget.item(2).data(Qt.UserRole).data == 6
+
+    # Finally, if there are too few choices for the default_index to be valid,
+    # pick the last item in the list_widget
+
+    a_prop.set_choices(t, (9,))
+
+    assert t.a == 9
+    assert len(list_widget.selectedItems()) == 1
+    assert list_widget.selectedItems()[0] is list_widget.item(0)
+    assert list_widget.count() == 1
+
+    assert list_widget.item(0).text() == 'value: 9'
+    assert list_widget.item(0).data(Qt.UserRole).data == 9
+
+    # Now just make sure that ChoiceSeparator works
+
+    separator = ChoiceSeparator('header')
+    a_prop.set_choices(t, (separator, 1, 2))
+
+    assert list_widget.count() == 3
+    assert list_widget.item(0).text() == 'header'
+    assert list_widget.item(0).data(Qt.UserRole).data is separator
+
+    # And setting choices to an empty iterable shouldn't cause issues
+
+    a_prop.set_choices(t, ())
+    assert list_widget.count() == 0
+
+    # Try including an array in the choices
+    a_prop.set_choices(t, (4, 5, np.array([1, 2, 3])))
+
+
+def test_connect_list_widget_selection_invalid():
+
+    t = Example()
+
+    list_widget = QtWidgets.QListWidget()
+
+    with pytest.raises(TypeError) as exc:
+        connect_list_selection(t, 'b', list_widget)
+    assert exc.value.args[0] == 'connect_list_selection requires a SelectionCallbackProperty'
diff --git a/tox.ini b/tox.ini
index eb534c2..4cffd04 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 envlist =
-    py{36,37,38}-{codestyle,test,docs}-{pyqt57,pyqt58,pyqt59,pyqt510,pyqt511,pyqt512,pyqt513,pyside511,pyside512,pyside513}
+    py{37,38,39,310}-{codestyle,test,docs}-{pyqt510,pyqt511,pyqt512,pyqt513,pyqt514,pyqt515,pyside513,pyside514,pyside515,pyqt63,pyside63}
 requires = pip >= 18.0
            setuptools >= 30.3.0
 
@@ -12,16 +12,17 @@ changedir =
     test: .tmp/{envname}
     docs: doc
 deps =
-    pyqt59: PyQt5==5.9.*
     pyqt510: PyQt5==5.10.*
     pyqt511: PyQt5==5.11.*
     pyqt512: PyQt5==5.12.*
     pyqt513: PyQt5==5.13.*
     pyqt514: PyQt5==5.14.*
-    pyside511: PySide2==5.11.*
-    pyside512: PySide2==5.12.*
+    pyqt515: PyQt5==5.15.*
     pyside513: PySide2==5.13.*
     pyside514: PySide2==5.14.*
+    pyside515: PySide2==5.15.*
+    pyqt63: PyQt6==6.3.*
+    pyside63: PySide6==6.3.*
 extras =
     test
     docs: docs

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo-0.8.0.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo-0.8.0.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo-0.8.0.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo-0.8.0.egg-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo-0.8.0.egg-info/zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo/qt/tests/test_connect_list_selection.py

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo-0.5.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo-0.5.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo-0.5.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo-0.5.egg-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/echo-0.5.egg-info/zip-safe

No differences were encountered in the control files

More details

Full run details