Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 330ab13

Browse files
lv-develerbwoodsend
authored andcommitted
hookutils: qt: look for commercial pyqt to determine layout
Commercial PyQt wheels install with the "_commercial" suffix as their package name but still make the regular PyQt5/PyQt6 namespace packages available. This leads the layout detection logic to assume the old layout because is_module_satisfies() enters a fallback code path where it looks for the __version__ attribute inside the namespace package, which doesn't exist. A possible solution is to look for package names with the "_commercial" suffix when the standard lookup fails. The _use_new_layout utility method was introduced to perform such checks and is then used in both PyQt5 and PyQt6 code paths.
1 parent 4c2652c commit 330ab13

File tree

2 files changed

+28
-23
lines changed

2 files changed

+28
-23
lines changed

PyInstaller/utils/hooks/qt/__init__.py

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -121,33 +121,12 @@ def __init__(self, namespace):
121121
# Windows, or with Qt and PyQt5 installed on linux using native package manager), and in those, the Qt
122122
# PrefixPath does not reflect the required relative target path for the frozen application.
123123
if namespace == 'PyQt5':
124-
# PyQt5 uses PyQt5/Qt on all platforms, or PyQt5/Qt5 from version 5.15.4 on
125-
try:
126-
# The call below might fail with AttributeError on some PyQt5 versions (e.g., 5.9.2 from conda's main
127-
# channel); missing dist information forces a fallback codepath that tries to check for __version__
128-
# attribute that does not exist, either. So handle the error gracefully and assume old layout.
129-
new_layout = hooks.is_module_satisfies("PyQt5 >= 5.15.4")
130-
except AttributeError:
131-
new_layout = False
132-
if new_layout:
124+
if self._use_new_layout("PyQt5", "5.15.4", False):
133125
self.qt_rel_dir = os.path.join('PyQt5', 'Qt5')
134126
else:
135127
self.qt_rel_dir = os.path.join('PyQt5', 'Qt')
136128
elif namespace == 'PyQt6':
137-
# Similarly to PyQt5, PyQt6 switched from PyQt6/Qt to PyQt6/Qt6 in 6.0.3
138-
try:
139-
# The call below might fail with AttributeError in case of a partial PyQt6 installation. For example,
140-
# user installs PyQt6 via pip, which also installs PyQt6-Qt6 and PyQt6-sip. Then they naively uninstall
141-
# PyQt6 package, which leaves the other two behind. PyQt6 now becomes a namespace package and there is
142-
# no dist metadata, so a fallback codepath in is_module_satisfies tries to check for __version__
143-
# attribute that does not exist, either. Handle such errors gracefully and assume new layout (with
144-
# PyQt6, the new layout is more likely); it does not really matter what layout we assume, as library is
145-
# not usable anyway, but we do need to be able to return an instance of QtLibraryInfo with "version"
146-
# attribute set to a falsey value.
147-
new_layout = hooks.is_module_satisfies("PyQt6 >= 6.0.3")
148-
except AttributeError:
149-
new_layout = True
150-
if new_layout:
129+
if self._use_new_layout("PyQt6", "6.0.3", True):
151130
self.qt_rel_dir = os.path.join('PyQt6', 'Qt6')
152131
else:
153132
self.qt_rel_dir = os.path.join('PyQt6', 'Qt')
@@ -181,6 +160,31 @@ def __getattr__(self, name):
181160
# ... and return the requested attribute
182161
return getattr(self, name)
183162

163+
# Check whether we must use the new layout (e.g. PyQt5/Qt5, PyQt6/Qt6) instead of the old layout (PyQt5/Qt,
164+
# PyQt6/Qt).
165+
@staticmethod
166+
def _use_new_layout(package_basename: str, version: str, fallback_value: bool) -> bool:
167+
# The call to is_module_satisfies might fail with AttributeError in case of a partial installation, or when dist
168+
# information is missing, in which case a fallback codepath tries to check for a __version__ attribute that does
169+
# not exist.
170+
#
171+
# This may happen for the following (not exhaustive) reasons:
172+
#
173+
# - PyQt 5.9.2 installed from conda's main channel.
174+
# - User installs PyQt6 via pip, which also installs PyQt6-Qt6 and PyQt6-sip. Then they naively uninstall PyQt6
175+
# package, which leaves the other two behind. PyQt6 now becomes a namespace package and there is no dist
176+
# metadata.
177+
# - The PyQt5 commercial wheel is installed. It creates the PyQt5 namespace package but dist information is
178+
# available under PyQt5_commercial. Since we first check for the non-commercial wheel, we trip the fallback
179+
# codepath inside is_module_satisfies.
180+
try:
181+
return hooks.is_module_satisfies(f"{package_basename} >= {version}")
182+
except AttributeError:
183+
try:
184+
return hooks.is_module_satisfies(f"{package_basename}_commercial >= {version}")
185+
except AttributeError:
186+
return fallback_value
187+
184188
# Load Qt information (called on first access to related fields)
185189
def _load_qt_info(self):
186190
"""

news/7770.hooks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for commercial PyQt5 and PyQt6 wheels.

0 commit comments

Comments
 (0)