Skip to content
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

Add support for modules that are now available in Qt 6.5 #427

Draft
wants to merge 3 commits 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
13 changes: 11 additions & 2 deletions qtpy/QtLocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,27 @@
"""Provides QtLocation classes and functions."""

from . import (
parse,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only works "by accident"/as an internal implementation detail, as parse is in fact imported from packaging, not QtPy; thefore, it should be imported from there instead.

Also, AFAIK our existing tests use the cruder if PYQT_VERSION.startswith("6.5"), but since packaging is required anyway, we may as well use it to be cleaner and more precise, as you do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only works "by accident"/as an internal implementation detail, as parse is in fact imported from packaging, not QtPy; thefore, it should be imported from there instead.

I actually did originally have from packaging import parse but I for some reason I can't remember, I removed it and did this instead.

Also, AFAIK our existing tests use the cruder if PYQT_VERSION.startswith("6.5"), but since packaging is required anyway, we may as well use it to be cleaner and more precise, as you do.

I did see that when I was poking around. I can fix all the startswiths.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did see that when I was poking around. I can fix all the startswiths.

Yeah, thanks—that's probably worth doing in a separate PR from this one, just to be clear.

PYQT5,
PYQT6,
PYSIDE2,
PYSIDE6,
PYQT_VERSION,
PYSIDE_VERSION,
Comment on lines +16 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of importing both of these separately, and particularly if this is related to the Qt version rather than the binding version, you could just import QT_VERSION and check that instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually tried that first, and my editor insisted that QT_VERSION didn't exist.

This is also one of the modules that I need the environment matrix for. I think that PyQt6 had this module earlier than PySide6 did.

QtBindingMissingModuleError,
)

if PYQT5:
from PyQt5.QtLocation import *
elif PYQT6:
raise QtBindingMissingModuleError(name='QtLocation')
if parse(PYQT_VERSION) >= parse('6.5'):
from PyQt6.QtLocation import *
else:
raise QtBindingMissingModuleError(name='QtLocation')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this module was added in Qt 6.5 instead of just in both Python bindings at that same version, then I'm not sure this is the right error, as it is intended for reporting when a particular binding doesn't support a missing module, rather than it not being implemented in Qt itself—not sure how it got overlooked initially, as the current call signature will actually raise an error since it expects binding to be passed. Instead, it would seem to be better to use QtModuleNotInQtVersionError here, with a tweak to either output the full major + minor version automatically (probably best), or pass that version manually.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this module was added in Qt 6.5 instead of just in both Python bindings at that same version

This is one of the reasons I need to set up a local environment matrix. I believe that PyQt6 had some of these modules the entire time, and it's only PySide6 that just added them to the distribution, but I won't know for sure until I'm able to step through the versions.

elif PYSIDE2:
from PySide2.QtLocation import *
elif PYSIDE6:
raise QtBindingMissingModuleError(name='QtLocation')
if parse(PYSIDE_VERSION) >= parse('6.5'):
from PySide6.QtLocation import *
else:
raise QtBindingMissingModuleError(name='QtLocation')
30 changes: 30 additions & 0 deletions qtpy/QtSerialBus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -----------------------------------------------------------------------------
# Copyright © 2009- The Spyder Development Team
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)
# -----------------------------------------------------------------------------
Comment on lines +1 to +6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should at least be consistent about whether we're adding this header to the tests—either to all newly added ones (ideal), or else none.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the correct copyright header? Various files have slightly different headers and some of them also list specific individuals, so I've never been sure what header I should be adding.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to comment on that, but I didn't want to be too nitpicky, heh.

The up to date version of the canonical header (for new files, at least) is in the License Guidelines on the our governance repo, i.e.:

Suggested change
# -----------------------------------------------------------------------------
# Copyright © 2009- The Spyder Development Team
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Copyright (c) 2023- QtPy contributors
#
# Released under the terms of the MIT License
# (see LICENSE.txt in the project root directory for details)
# -----------------------------------------------------------------------------

The reason a number of files list different individuals is because a lot of the code was combined from multiple predecessor projects (see the Readme section that touches on a bit of this history), plus various other historical contributions. New files should just use the above, but it is important to retain the existing copyright notices in files that have them for both legal and ethical purposes.


"""Provides QtSerialBus classes and functions."""

from . import (
parse,
PYQT5,
PYQT6,
PYSIDE2,
PYSIDE6,
PYSIDE_VERSION,
QtBindingMissingModuleError,
)

if PYQT5:
from PyQt5.QtSerialBus import *
elif PYQT6:
from PyQt6.QtSerialBus import *
elif PYSIDE2:
from PySide2.QtSerialBus import *
elif PYSIDE6:
if parse(PYSIDE_VERSION) >= parse('6.5'):
from PySide6.QtSerialBus import *
else:
raise QtBindingMissingModuleError(name='QtSerialBus')
9 changes: 7 additions & 2 deletions qtpy/QtTextToSpeech.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

"""Provides QtTextToSpeech classes and functions."""

from . import (
from packaging.version import parse
from qtpy import (
PYQT5,
PYQT6,
PYSIDE2,
PYSIDE6,
PYSIDE_VERSION,
QtBindingMissingModuleError,
)

Expand All @@ -22,4 +24,7 @@
elif PYSIDE2:
from PySide2.QtTextToSpeech import *
elif PYSIDE6:
raise QtBindingMissingModuleError(name='QtTextToSpeech')
if parse(PYSIDE_VERSION) >= parse('6.5'):
from PySide6.QtTextToSpeech import *
else:
raise QtBindingMissingModuleError(name='QtTextToSpeech')
28 changes: 28 additions & 0 deletions qtpy/tests/test_qtserialbus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Test QtSerialBus."""

import pytest
from qtpy import (
Comment on lines +3 to +4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split third party imports from local imports (same with the others)

parse,
PYSIDE6,
PYSIDE_VERSION,
QtBindingMissingModuleError,
)


@pytest.mark.skipif(not PYSIDE6 or parse(PYSIDE_VERSION) >= parse('6.5'), reason='.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume you're going to fill the reason in later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup!

def test_qtserialbus_pyside6_below_6_5():
with pytest.raises(QtBindingMissingModuleError) as excinfo:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
with pytest.raises(QtBindingMissingModuleError) as excinfo:
with pytest.raises(QtBindingMissingModuleError):

No need for the as if you don't actually use excinfo, no? Or maybe you're planning to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was considering inspecting the actual error in the test. Do you have an opinion on whether that's worth doing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps, yeah, at least the custom attributes of the error object (as opposed to the message, which is considered a user-facing implementation detail subject to change and a test of which would be too fragile).

from qtpy import QtSerialBus


@pytest.mark.skipif(not PYSIDE6 or parse(PYSIDE_VERSION) < parse('6.5'), reason='.')
def test_qtserialbus_pyside6_above_6_5():
"""Test the qtpy.QtSerialBus namespace"""
from qtpy import QtSerialBus

assert QtSerialBus.QCanBus is not None
assert QtSerialBus.QCanBusDevice is not None
assert QtSerialBus.QCanBusDeviceInfo is not None
assert QtSerialBus.QModbusClient is not None
assert QtSerialBus.QModbusServer is not None
assert QtSerialBus.QModbusDevice is not None