Skip to content

DOC: first additions to docs about pyside6, also add tag for parts of codebase where we do pyqt5/pyside6 specific things. #1214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/source/development/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,28 @@ should look like this:
working on that branch. The rebasing process re-writes the commit history so
any other checkout of the same branch referring to the old history will
create duplicates of all the commits.

Qt Wrapper Dependent Code
===========================
PyDM runs using python wrappers around the Qt library,
and there are two choices for python wrappers currently supported: PyQt (Qt5) and PySide6 (Qt6). Note: atm functionality
of PyDM should be the same regardless of if PyQt5 of PySide6 is used.

Furthermore, PyDM runs ontop an abstraction layer called "qtpy" (https://github.com/spyder-ide/qtpy), which is used to handle differences
between PyQt and PySide6 with one set of code.

But ends up that there are still some places in the PyDM codebase where we needed to implement
wrapper-specific or Qt version specific code, either b/c qtpy was lacking a specific abstraction we needed
or b/c we had gone around the abstraction layer in the past and now it's now difficult to change these sections.

Also, in the future PyDM may add the option to use Qt6 specific features, and these sections of code will only run on PySide6.

These wrapper-specific sections are noted in the codebase by the ``@QT_WRAPPER_SPECIFIC`` string. There are also comments in
these sections explaining what differences there are between the PyQt and Pyside6 implementations.

When changing code marked with ``@QT_WRAPPER_SPECIFIC``, developers should make sure their changes work on both PyQt and PySide6.
Automated testing on GitHub will ultimately run changes on both wrappers before any code gets merged,
but if you are changing a ``@QT_WRAPPER_SPECIFIC`` section it's recommended to have two separate conda environments setup
(one with PyQt and the other PySide6) so you can test PyDM with both wrappers.

Also, if new PySide6/Qt6 only features are implemented, these sections will also get denoted with the ``@QT_WRAPPER_SPECIFIC`` string.
37 changes: 35 additions & 2 deletions docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,21 @@ from scratch is probably using the Conda system. We recommended using Conda from
python environment, and want to install PyDM for use with that, you can do that
with pip.

PyDM runs using python bindings ontop of Qt, and works with either PyQt5 (to run on Qt5) or PySide6 (to run on Qt6). For now, PyQt5 is the recommended choice to run with,
whereas PySide6 provides future-proofing and the potential for new Qt6 specific features.
(Note: currently functionality is the same between PyQt and PySide6)

In general, running PyDm with a chosen python binding is as simple as follows:
loading a conda environment that has installed PyDM and PyQt5 or PySide6 (along with the other required packages),
and then setting the QT_API environment variable accordingly ("pyqt5" or "pyside6".)
Then when you run PyDM it will automatically use your selected binding.
(Note: It's not recommended to try installing both PyQt5 and PySide6 into the same conda environment, as this might cause issues with the underlying Qt libraries.)

Instructions for setting up conda environments for PyQt5 and for PySide6 are provided later on this page.

Please note, this guide is written with Unix in mind, so there are probably some differences when installing on Windows.

Installing PyDM and Prerequisites with Conda
Installing PyDM and Prerequisites with Conda (PyQt5)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. warning::
Expand Down Expand Up @@ -48,7 +60,24 @@ Now, you can use 'open' to open Designer.app::

$ export QT_MAC_WANTS_LAYER=1

Installing Manually, Without Anaconda
Installing PyDM and Prerequisites with Conda (PySide6)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

After installing Miniforge (see https://conda-forge.org/download/), create a new
environment for PyDM::

.. note::
Pyside6 is installed with into the conda environment with pip b/c the conda install doesn't provide the pyside6-designer bin currently.

$ conda create -n pydm-environment-pyside python pip numpy scipy six psutil pyqtgraph pydm -c conda-forge
$ source activate pydm-environment
$ pip install PySide6

Once you've installed and activated the environment, you should be able to run 'pydm' to launch PyDM, or run 'pyside6-designer' to launch Qt Designer.

MacOS and Windows instructions coming soon...

Installing Manually, Without Anaconda (PyQt5)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This alternate installation method is only recommended for large 'site' installations that want to avoid using Anaconda.

Expand Down Expand Up @@ -78,6 +107,10 @@ and extract the archive. Follow `the provided instructions <http://pyqt.sourcef
build and install it. Note that you may need to manually set the '--qmake' option to point to the
qmake binary you created when you built Qt5.

Installing Manually, Without Anaconda (PySide6)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Coming soon...

Installing PyDM with PIP
++++++++++++++++++++++++

Expand Down
2 changes: 2 additions & 0 deletions pydm/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def _compile_ui_file(uifile: str) -> Tuple[str, str]:
-------
Tuple[str, str] - The first element is the compiled ui file, the second is the name of the class (e.g. Ui_Form)
"""
# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5:
code_string = StringIO()
uic.compileUi(uifile, code_string)
Expand Down Expand Up @@ -126,6 +127,7 @@ def _compile_ui_file(uifile: str) -> Tuple[str, str]:


def _load_ui_into_display(uifile, display):
# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5:
klass, _ = uic.loadUiType(uifile)
else: # pyside6
Expand Down
1 change: 1 addition & 0 deletions pydm/tests/test_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def ui_filename(self):
qtbot.addWidget(my_display)


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5:

def test_nonexistent_ui_file_raises_pyqt5(qtbot):
Expand Down
1 change: 1 addition & 0 deletions pydm/tests/test_main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# The path to the .ui file for creating a main window
test_ui_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_data", "test.ui")

# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5:

@patch("qtpy.uic.compileUi", wraps=uic.compileUi)
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/baseplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def __init__(
if lineWidth is not None:
self._pen.setWidth(lineWidth)
if lineStyle is not None:
# @QT_WRAPPER_SPECIFIC
# The type hint for 'Optional' for lineStyle arg, which has allowed for some screens to
# pass int value for lineStyle. pyqt5 doesn't mind the int, but pyside6 complains so lets
# convert any ints here to the proper Qt.PenStyle enums. The int values get converted to enums
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ def __eq__(self, other):

value_signal_matched = self.value_signal is None and other.value_signal is None
if self.value_signal and other.value_signal:
# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5:
value_signal_matched = self.value_signal.signal == other.value_signal.signal
else:
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/colormaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,7 @@ class PyDMColorMap(object):
Hot = 6


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class TimeBase(object):
Seconds = 1


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/display_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class DisplayFormat(object):
Binary = 5


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/enum_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class WidgetType(object):
RadioButton = 1


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
2 changes: 2 additions & 0 deletions pydm/widgets/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class ReadingOrder(object):
Clike = 1


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down Expand Up @@ -53,6 +54,7 @@ class DimensionOrder(object):
WidthFirst = 1


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/logdisplay.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def as_dict():
return OrderedDict(sorted(entries, key=lambda x: x[1], reverse=False))


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/shell_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class TermOutputMode:
STORE = 2


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/template_repeater.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class LayoutType(object):
Flow = 2


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
1 change: 1 addition & 0 deletions pydm/widgets/timeplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class updateMode(object):
AtFixedRate = 2


# @QT_WRAPPER_SPECIFIC
if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYSIDE6:
from PySide6.QtCore import QEnum
from enum import Enum
Expand Down
2 changes: 1 addition & 1 deletion run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# and a Windows PyCA build exists
if os.name == "nt":
args.append("--ignore=pydm/tests/data_plugins/test_p4p_plugin_component.py")
args.append("--ignore=pydm/tests/data_plugins/test_psp_plugin_component.py")
args.append("--ignore=pydm/tests/data_plugins/test_psp_plugin_component.py")

print("pytest arguments: {}".format(args))

Expand Down
Loading