From bc64661c063609fc7b6ea773974d23707365b976 Mon Sep 17 00:00:00 2001 From: Jason Brackman Date: Thu, 14 Mar 2024 10:39:19 -0700 Subject: [PATCH] The code now attempts to load the content directly as written, by the 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'). --- Qt.py | 37 ++++++++++++++-------------- tests.py | 74 +++++++++++++++++++++++++++++++++----------------------- 2 files changed, 63 insertions(+), 48 deletions(-) diff --git a/Qt.py b/Qt.py index 8ee4e9e4..a9f90637 100644 --- a/Qt.py +++ b/Qt.py @@ -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` @@ -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: @@ -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) diff --git a/tests.py b/tests.py index d7798039..d1a8fbdb 100644 --- a/tests.py +++ b/tests.py @@ -265,7 +265,7 @@ class Widget(QtWidgets.QWidget): CustomWidget QWidget -
tests
+
custom.customwidget.customwidget
@@ -273,6 +273,18 @@ class Widget(QtWidgets.QWidget): """ +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 @@ -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): @@ -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():