From f1134b836da2b48e7b976c036e73038082b5346a Mon Sep 17 00:00:00 2001 From: AbnerErnaniADSFatec Date: Wed, 18 Sep 2024 17:48:24 -0300 Subject: [PATCH] [#68] Add warnings in import error when install dependencies --- .gitignore | 2 + scripts/build_requirements.py | 11 +++ scripts/linux/run-qgis.sh | 4 +- wtss_plugin/__init__.py | 164 +++++++++++++++++++++++++--------- wtss_plugin/pb_tool.cfg | 2 +- 5 files changed, 140 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 4fa2133..8536644 100644 --- a/.gitignore +++ b/.gitignore @@ -201,4 +201,6 @@ services_storage_user_application.json *lib-paths.txt +*requirements.csv + *requirements.txt diff --git a/scripts/build_requirements.py b/scripts/build_requirements.py index c2e9be6..c032110 100644 --- a/scripts/build_requirements.py +++ b/scripts/build_requirements.py @@ -16,6 +16,7 @@ # along with this program. If not, see . # +import csv import distutils.core from pathlib import Path @@ -25,3 +26,13 @@ for req in dist.install_requires: file.write(str(req) + "\n") file.close() + +rule = '>=' + +file = open(Path('wtss_plugin') / 'requirements.csv', 'w', newline='') +writer = csv.DictWriter(file, delimiter=',', fieldnames=['package', 'version']) +writer.writeheader() +for req in dist.install_requires: + pkg_ = req.split(rule) + writer.writerow({'package': pkg_[0], 'version': pkg_[1]}) +file.close() diff --git a/scripts/linux/run-qgis.sh b/scripts/linux/run-qgis.sh index 0b42d48..e37535a 100644 --- a/scripts/linux/run-qgis.sh +++ b/scripts/linux/run-qgis.sh @@ -22,12 +22,12 @@ xhost + docker run --rm \ --interactive \ --tty \ - --name="qgis-3-desktop" \ + --name qgis3 \ -i -t \ -v ${PWD}:/home/wtss_plugin \ -v ${PWD}/plugins:/root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/ \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -e DISPLAY=unix$DISPLAY \ - qgis/qgis:release-3_32 qgis + qgis/qgis qgis xhost - diff --git a/wtss_plugin/__init__.py b/wtss_plugin/__init__.py index a033975..f0eaa4b 100644 --- a/wtss_plugin/__init__.py +++ b/wtss_plugin/__init__.py @@ -18,9 +18,15 @@ """Python QGIS Plugin for WTSS.""" +import csv +import importlib import os +import subprocess from pathlib import Path +from PyQt5.QtWidgets import QMessageBox + + def lib_path(): """Get the path for python installed lib path.""" return str(Path(os.path.abspath(os.path.dirname(__file__))) / 'lib') @@ -33,19 +39,42 @@ def get_lib_paths(): """Get the path for python installed lib path.""" return [lib_path(), lib_path_end()] -def requirements_file(): +def requirements_file(ext): """Get the path for requirements file.""" - return str(Path(os.path.abspath(os.path.dirname(__file__))) / 'requirements.txt') + return str(Path(os.path.abspath(os.path.dirname(__file__))) / f'requirements.{ext}') -def warning(title, message): +def warning(type_message, title, message, **add_buttons): """Show a simple warning when ImportError.""" - from PyQt5.QtWidgets import QMessageBox msg = QMessageBox() - msg.setIcon(QMessageBox.Critical) + if type_message == 'error': + msg.setIcon(QMessageBox.Critical) + elif type_message == 'warning': + msg.setIcon(QMessageBox.Warning) + elif type_message == 'info': + msg.setIcon(QMessageBox.Information) msg.setWindowTitle(title) msg.setText(message) - msg.setStandardButtons(QMessageBox.Ok) - return msg.exec_() + buttons = {} + if add_buttons: + for button in add_buttons.keys(): + buttons[button] = msg.addButton(add_buttons[button][0], add_buttons[button][1]) + msg.exec_() + msg.deleteLater() + return msg, buttons + +def raise_restart(): + """Raise a warning requesting restart.""" + restart, buttons_ = warning( + "warning", + "Restart Required!", + "Restart your QGIS environment to load updates!", + ok = ['Ok', QMessageBox.YesRole], + cancel = ['Cancel', QMessageBox.RejectRole] + ) + if restart.clickedButton() == buttons_['ok']: + import sys + python = sys.executable + os.execl(python, python, *sys.argv) def set_lib_path(): """Setting lib path for installed libraries.""" @@ -56,6 +85,89 @@ def set_lib_path(): sys.path.remove(lib_path_end()) sys.path = get_lib_paths() + sys.path +def run_install_pkgs_process(): + """Run subprocess to install packages through.""" + install_requirements, buttons = warning( + "error", + "ImportError!", + ("Your environment does not have the minimal " + + "requirements to run WTSS Plugin, " + + "click OK to install them.\n\n" + str(error)), + install_all = ['Install All', QMessageBox.YesRole], + install_by = ['Install By', QMessageBox.YesRole], + cancel = ['Cancel', QMessageBox.RejectRole] + ) + if install_requirements.clickedButton() == buttons['install_all']: + try: + subprocess.run([ + 'pip', 'uninstall', + '-r', requirements_file('txt'), + '--break-system-packages' + ]) + except: + pass + subprocess.run([ + 'pip', 'install', + '--target', lib_path(), + '-r', requirements_file('txt'), + ]) + # + # Request restart + raise_restart() + # + elif install_requirements.clickedButton() == buttons['install_by']: + with open(requirements_file('csv'), newline='') as csvfile: + reader = csv.DictReader(csvfile, delimiter=',') + for row in reader: + pkg_name = row['package'] + pkg_version = row['version'] + pkg = None + try: + pkg = importlib.import_module(pkg_name) + except (ModuleNotFoundError, ImportError) as error: + pass + if pkg: + install_existing_lib, buttons_lib = warning( + "warning", + "Found conflicts!", + (f"Found existing installation for {pkg_name} version {pkg.__version__} in" + + f"\n\n{pkg.__file__}.\n\n" + + f"The WTSS Plugin needs version {pkg_version}."), + update = ['Update', QMessageBox.YesRole], + cancel = ['Cancel', QMessageBox.RejectRole] + ) + if install_existing_lib.clickedButton() == buttons_lib['update']: + subprocess.run(['pip', 'uninstall', pkg_name, '--break-system-packages']) + subprocess.run([ + 'pip', 'install', + '--target', lib_path(), + f"{pkg_name}>={pkg_version}" + ]) + else: + pass + else: + install_lib, buttons_lib = warning( + "warning", + "ImportError!", + (f"The WTSS Plugin needs package {pkg_name} version {pkg_version}."), + update = ['Install', QMessageBox.YesRole], + cancel = ['Cancel', QMessageBox.RejectRole] + ) + if install_lib.clickedButton() == buttons_lib['update']: + subprocess.run([ + 'pip', 'install', + '--target', lib_path(), + f"{pkg_name}>={pkg_version}" + ]) + else: + pass + # + # Request restart + raise_restart() + # + else: + pass + def start(iface): """Start WTSS QGIS Plugin""" # @@ -75,36 +187,8 @@ def classFactory(iface): try: return start(iface) except (ModuleNotFoundError, ImportError) as error: - ok_install_requirements = warning( - "ImportError!", - ("Your environment does not have the minimal " + - "requirements to run WTSS Plugin, " + - "click OK to install them.\n\n" + str(error)) - ) - if ok_install_requirements: - import subprocess - try: - subprocess.run([ - 'pip', 'uninstall', - '-r', requirements_file(), - ]) - except: - pass - subprocess.run([ - 'pip', 'install', - '--target', lib_path(), - '-r', requirements_file(), - ]) - ok_restart = warning( - "Restart Required!", - "Restart your QGIS environment to load updates!" - ) - if ok_restart: - import sys - python = sys.executable - os.execl(python, python, *sys.argv) - else: - return None - return start(iface) - else: - return None + # + # Run packages installation + run_install_pkgs_process() + # + return start(iface) diff --git a/wtss_plugin/pb_tool.cfg b/wtss_plugin/pb_tool.cfg index a8d8597..7098fbc 100644 --- a/wtss_plugin/pb_tool.cfg +++ b/wtss_plugin/pb_tool.cfg @@ -70,7 +70,7 @@ compiled_ui_files: resources.py resource_files: resources.qrc # Other files required for the plugin -extras: metadata.txt requirements.txt +extras: metadata.txt requirements.txt requirements.csv # Other directories to be deployed with the plugin. # These must be subdirectories under the plugin directory