Skip to content

Commit

Permalink
The code now attempts to load the content directly as written, by the…
Browse files Browse the repository at this point in the history
… user. If an ImportError occurs, a second attempt is made using the headerToModule code.

Tests have been updated to include a Python widget stored in a path using a name not available to the test suite directly.  (IE: Not named 'tests').
  • Loading branch information
jasonbrackman committed Mar 14, 2024
1 parent ecf9db6 commit bc64661
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 48 deletions.
37 changes: 19 additions & 18 deletions Qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -874,23 +874,6 @@ def get_arg(index):
return app.translate(*sanitized_args)


def _headerToModule(header):
"""
Translate a header file to python module path
foo/bar.h => foo.bar
"""

# Only manipulate header files, identified by the `.h` ext.
if header.endswith(".h") is False:
return header

# Remove header extension
module = os.path.splitext(header)[0]

# Replace os separator by python module separator
return module.replace("/", ".").replace("\\", ".")


def _loadUi(uifile, baseinstance=None):
"""Dynamically load a user interface from the given `uifile`
Expand Down Expand Up @@ -942,6 +925,17 @@ def _loadCustomWidgets(self, etree):
objects. Then we can directly use them in createWidget method.
"""

def headerToModule(header):
"""
Translate a header file to python module path
foo/bar.h => foo.bar
"""
# Remove header extension
module = os.path.splitext(header)[0]

# Replace os separator by python module separator
return module.replace("/", ".").replace("\\", ".")

custom_widgets = etree.find("customwidgets")

if custom_widgets is None:
Expand All @@ -950,7 +944,14 @@ def _loadCustomWidgets(self, etree):
for custom_widget in custom_widgets:
class_name = custom_widget.find("class").text
header = custom_widget.find("header").text
module = importlib.import_module(_headerToModule(header))

try:
# try to import the module using the header as defined by the user
module = importlib.import_module(header)
except ImportError:
# try again, but use the customized conversion of a path to a module
module = importlib.import_module(headerToModule(header))

self.custom_widgets[class_name] = getattr(module,
class_name)

Expand Down
74 changes: 44 additions & 30 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,26 @@ class Widget(QtWidgets.QWidget):
<customwidget>
<class>CustomWidget</class>
<extends>QWidget</extends>
<header>tests</header>
<header>custom.customwidget.customwidget</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
"""

python_custom_widget = '''
def CustomWidget(parent=None):
"""
Wrap CustomWidget class into a function to avoid global Qt import
"""
from Qt import QtWidgets
class Widget(QtWidgets.QWidget):
pass
return Widget(parent)
'''

def setup():
"""Module-wide initialisation
Expand All @@ -297,7 +309,8 @@ def saveUiFile(filename, ui_template):
self.ui_qpycustomwidget = saveUiFile("qpycustomwidget.ui", qpycustomwidget_ui)

def teardown():
shutil.rmtree(self.tempdir)
# shutil.rmtree(self.tempdir)
pass


def binding(binding):
Expand Down Expand Up @@ -448,38 +461,39 @@ def test_load_ui_customwidget():

app.exit()

def test_load_ui_pycustomwidget():
"""Tests to see if loadUi loads a custom widget properly"""
import sys
from Qt import QtWidgets, QtCompat

def test_headerToModule():
"""
Tests to see if headerToModule manipulates the path passed in appropriately.
- It should only affect `Header` files and paths, marked with an .h extension.
"""
# create a python file for the custom widget in a directory relative to the tempdir
filename = os.path.join(
self.tempdir,
self.tempdir,
"custom",
"customwidget",
"customwidget.py"
)
os.makedirs(os.path.dirname(filename))
with io.open(filename, "w", encoding="utf-8") as f:
f.write(self.python_custom_widget)

path_tests = {
# Input: Expected

# Valid paths; .h paths need to be updated to work with Qt.py
"path.to.module": "path.to.module", # valid python .path to module
"path\\to\\module.h": "path.to.module", # valid .h path with backslashes
"path\\to/module.h": "path.to.module", # mixed slashes
"path/to/module.h": "path.to.module", # valid .h path with forward slashes
"module.h": "module", # valid .h file in current path
"module": "module", # valid python module in current path

# malformed; Should we change QtDesigner input?
"path.to.module.py": "path.to.module.py",
"path\\to\\module.py": "path\\to\\module.py",
"path/to/module.py": "path/to/module.py",
"path\\to\\module": "path\\to\\module",
"path/to/module": "path/to/module",
"module.py": "module.py",
}
# append the path to ensure the future import can be loaded 'relative' to the tempdir
sys.path.append(self.tempdir)

import Qt
app = QtWidgets.QApplication([self.tempdir, ])
win = QtWidgets.QMainWindow()

for provided, expected in path_tests.items():
result = Qt._headerToModule(provided)
assert result == expected, "Provided: %s expected: %s got: %s" % (provided, expected, result)
QtCompat.loadUi(self.ui_qpycustomwidget, win)

# Ensure that the derived class was properly created
# and not the base class (in case of failure)
custom_class_name = getattr(win, "customwidget", None).__class__.__name__
excepted_class_name = CustomWidget(win).__class__.__name__
assert custom_class_name == excepted_class_name, \
"loadUi could not load custom widget to main window"

app.exit()


def test_load_ui_invalidpath():
Expand Down

0 comments on commit bc64661

Please sign in to comment.