1# -*- coding: utf-8 -*- 2 3""" 4*************************************************************************** 5 utils.py 6 --------------------- 7 Date : November 2009 8 Copyright : (C) 2009 by Martin Dobias 9 Email : wonder dot sk at gmail dot com 10*************************************************************************** 11* * 12* This program is free software; you can redistribute it and/or modify * 13* it under the terms of the GNU General Public License as published by * 14* the Free Software Foundation; either version 2 of the License, or * 15* (at your option) any later version. * 16* * 17*************************************************************************** 18""" 19 20__author__ = 'Martin Dobias' 21__date__ = 'November 2009' 22__copyright__ = '(C) 2009, Martin Dobias' 23 24""" 25QGIS utilities module 26 27""" 28from typing import List, Dict, Optional 29 30from qgis.PyQt.QtCore import QCoreApplication, QLocale, QThread, qDebug, QUrl 31from qgis.PyQt.QtGui import QDesktopServices 32from qgis.PyQt.QtWidgets import QPushButton, QApplication 33from qgis.core import Qgis, QgsMessageLog, qgsfunction, QgsMessageOutput 34from qgis.gui import QgsMessageBar 35 36import os 37import sys 38import traceback 39import glob 40import os.path 41import configparser 42import warnings 43import codecs 44import time 45import functools 46 47import builtins 48builtins.__dict__['unicode'] = str 49builtins.__dict__['basestring'] = str 50builtins.__dict__['long'] = int 51builtins.__dict__['Set'] = set 52 53# ###################### 54# ERROR HANDLING 55 56warnings.simplefilter('default') 57warnings.filterwarnings("ignore", "the sets module is deprecated") 58 59 60def showWarning(message, category, filename, lineno, file=None, line=None): 61 stk = "" 62 for s in traceback.format_stack()[:-2]: 63 if hasattr(s, 'decode'): 64 stk += s.decode(sys.getfilesystemencoding()) 65 else: 66 stk += s 67 if hasattr(filename, 'decode'): 68 decoded_filename = filename.decode(sys.getfilesystemencoding()) 69 else: 70 decoded_filename = filename 71 QgsMessageLog.logMessage( 72 u"warning:{}\ntraceback:{}".format(warnings.formatwarning(message, category, decoded_filename, lineno), stk), 73 QCoreApplication.translate("Python", "Python warning") 74 ) 75 76 77def showException(type, value, tb, msg, messagebar=False, level=Qgis.Warning): 78 if msg is None: 79 msg = QCoreApplication.translate('Python', 'An error has occurred while executing Python code:') 80 81 logmessage = '' 82 for s in traceback.format_exception(type, value, tb): 83 logmessage += s.decode('utf-8', 'replace') if hasattr(s, 'decode') else s 84 85 title = QCoreApplication.translate('Python', 'Python error') 86 QgsMessageLog.logMessage(logmessage, title, level) 87 88 try: 89 blockingdialog = QApplication.instance().activeModalWidget() 90 window = QApplication.instance().activeWindow() 91 except: 92 blockingdialog = QApplication.activeModalWidget() 93 window = QApplication.activeWindow() 94 95 # Still show the normal blocking dialog in this case for now. 96 if blockingdialog or not window or not messagebar or not iface: 97 open_stack_dialog(type, value, tb, msg) 98 return 99 100 bar = iface.messageBar() if iface else None 101 102 # If it's not the main window see if we can find a message bar to report the error in 103 if not window.objectName() == "QgisApp": 104 widgets = window.findChildren(QgsMessageBar) 105 if widgets: 106 # Grab the first message bar for now 107 bar = widgets[0] 108 109 item = bar.currentItem() 110 if item and item.property("Error") == msg: 111 # Return of we already have a message with the same error message 112 return 113 114 widget = bar.createMessage(title, msg + " " + QCoreApplication.translate("Python", "See message log (Python Error) for more details.")) 115 widget.setProperty("Error", msg) 116 stackbutton = QPushButton(QCoreApplication.translate("Python", "Stack trace"), pressed=functools.partial(open_stack_dialog, type, value, tb, msg)) 117 button = QPushButton(QCoreApplication.translate("Python", "View message log"), pressed=show_message_log) 118 widget.layout().addWidget(stackbutton) 119 widget.layout().addWidget(button) 120 bar.pushWidget(widget, Qgis.Warning) 121 122 123def show_message_log(pop_error=True): 124 if pop_error: 125 iface.messageBar().popWidget() 126 127 iface.openMessageLog() 128 129 130def open_stack_dialog(type, value, tb, msg, pop_error=True): 131 if pop_error and iface is not None: 132 iface.messageBar().popWidget() 133 134 if msg is None: 135 msg = QCoreApplication.translate('Python', 'An error has occurred while executing Python code:') 136 137 # TODO Move this to a template HTML file 138 txt = u'''<font color="red"><b>{msg}</b></font> 139<br> 140<h3>{main_error}</h3> 141<pre> 142{error} 143</pre> 144<br> 145<b>{version_label}</b> {num} 146<br> 147<b>{qgis_label}</b> {qversion} {qgisrelease}, {devversion} 148<br> 149<h4>{pypath_label}</h4> 150<ul> 151{pypath} 152</ul>''' 153 154 error = '' 155 lst = traceback.format_exception(type, value, tb) 156 for s in lst: 157 error += s.decode('utf-8', 'replace') if hasattr(s, 'decode') else s 158 error = error.replace('\n', '<br>') 159 160 main_error = lst[-1].decode('utf-8', 'replace') if hasattr(lst[-1], 'decode') else lst[-1] 161 162 version_label = QCoreApplication.translate('Python', 'Python version:') 163 qgis_label = QCoreApplication.translate('Python', 'QGIS version:') 164 pypath_label = QCoreApplication.translate('Python', 'Python Path:') 165 txt = txt.format(msg=msg, 166 main_error=main_error, 167 error=error, 168 version_label=version_label, 169 num=sys.version, 170 qgis_label=qgis_label, 171 qversion=Qgis.QGIS_VERSION, 172 qgisrelease=Qgis.QGIS_RELEASE_NAME, 173 devversion=Qgis.QGIS_DEV_VERSION, 174 pypath_label=pypath_label, 175 pypath=u"".join(u"<li>{}</li>".format(path) for path in sys.path)) 176 177 txt = txt.replace(' ', ' ') # preserve whitespaces for nicer output 178 179 dlg = QgsMessageOutput.createMessageOutput() 180 dlg.setTitle(msg) 181 dlg.setMessage(txt, QgsMessageOutput.MessageHtml) 182 dlg.showMessage() 183 184 185def qgis_excepthook(type, value, tb): 186 # detect if running in the main thread 187 in_main_thread = QCoreApplication.instance() is None or QThread.currentThread() == QCoreApplication.instance().thread() 188 189 # only use messagebar if running in main thread - otherwise it will crash! 190 showException(type, value, tb, None, messagebar=in_main_thread) 191 192 193def installErrorHook(): 194 """ 195 Installs the QGIS application error/warning hook. This causes Python exceptions 196 to be intercepted by the QGIS application and shown in the main window message bar 197 and in custom dialogs. 198 199 Generally you shouldn't call this method - it's automatically called by 200 the QGIS app on startup, and has no use in standalone applications and scripts. 201 """ 202 sys.excepthook = qgis_excepthook 203 warnings.showwarning = showWarning 204 205 206def uninstallErrorHook(): 207 sys.excepthook = sys.__excepthook__ 208 209 210# initialize 'iface' object 211iface = None 212 213 214def initInterface(pointer): 215 from qgis.gui import QgisInterface 216 from sip import wrapinstance 217 218 global iface 219 iface = wrapinstance(pointer, QgisInterface) 220 221 222####################### 223# PLUGINS 224 225# list of plugin paths. it gets filled in by the QGIS python library 226plugin_paths = [] 227 228# dictionary of plugins 229plugins = {} 230 231plugin_times = {} 232 233# list of active (started) plugins 234active_plugins = [] 235 236# list of plugins in plugin directory and home plugin directory 237available_plugins = [] 238 239# dictionary of plugins providing metadata in a text file (metadata.txt) 240# key = plugin package name, value = config parser instance 241plugins_metadata_parser = {} 242 243 244def findPlugins(path): 245 """ for internal use: return list of plugins in given path """ 246 for plugin in glob.glob(path + "/*"): 247 if not os.path.isdir(plugin): 248 continue 249 if not os.path.exists(os.path.join(plugin, '__init__.py')): 250 continue 251 252 metadataFile = os.path.join(plugin, 'metadata.txt') 253 if not os.path.exists(metadataFile): 254 continue 255 256 cp = configparser.ConfigParser() 257 258 try: 259 with codecs.open(metadataFile, "r", "utf8") as f: 260 cp.read_file(f) 261 except: 262 cp = None 263 264 pluginName = os.path.basename(plugin) 265 yield (pluginName, cp) 266 267 268def metadataParser() -> dict: 269 """Used by other modules to access the local parser object""" 270 return plugins_metadata_parser 271 272 273def updateAvailablePlugins(sort_by_dependencies=False): 274 """ Go through the plugin_paths list and find out what plugins are available. """ 275 # merge the lists 276 plugins = [] 277 metadata_parser = {} 278 plugin_name_map = {} 279 for pluginpath in plugin_paths: 280 for plugin_id, parser in findPlugins(pluginpath): 281 if parser is None: 282 continue 283 if plugin_id not in plugins: 284 plugins.append(plugin_id) 285 metadata_parser[plugin_id] = parser 286 plugin_name_map[parser.get('general', 'name')] = plugin_id 287 288 global plugins_metadata_parser 289 plugins_metadata_parser = metadata_parser 290 291 global available_plugins 292 available_plugins = _sortAvailablePlugins(plugins, plugin_name_map) if sort_by_dependencies else plugins 293 294 295def _sortAvailablePlugins(plugins: List[str], plugin_name_map: Dict[str, str]) -> List[str]: 296 """Place dependent plugins after their dependencies 297 298 1. Make a copy of plugins list to modify it. 299 2. Get a plugin dependencies dict. 300 3. Iterate plugins and leave the real work to _move_plugin() 301 302 :param list plugins: List of available plugin ids 303 :param dict plugin_name_map: Map of plugin_names and plugin_ids, because 304 get_plugin_deps() only returns plugin names 305 :return: List of plugins sorted by dependencies. 306 """ 307 sorted_plugins = plugins.copy() 308 visited_plugins = [] 309 310 deps = {} 311 for plugin in plugins: 312 deps[plugin] = [plugin_name_map.get(dep, '') for dep in get_plugin_deps(plugin)] 313 314 for plugin in plugins: 315 _move_plugin(plugin, deps, visited_plugins, sorted_plugins) 316 317 return sorted_plugins 318 319 320def _move_plugin(plugin: str, deps: Dict[str, List[str]], visited: List[str], sorted_plugins: List[str]): 321 """Use recursion to move a plugin after its dependencies in a list of 322 sorted plugins. 323 324 Notes: 325 This function modifies both visited and sorted_plugins lists. 326 This function will not get trapped in circular dependencies. We avoid a 327 maximum recursion error by calling return when revisiting a plugin. 328 Therefore, if a plugin A depends on B and B depends on A, the order will 329 work in one direction (e.g., A depends on B), but the other direction won't 330 be satisfied. After all, a circular plugin dependency should not exist. 331 332 :param str plugin: Id of the plugin that should be moved in sorted_plugins. 333 :param dict deps: Dictionary of plugin dependencies. 334 :param list visited: List of plugins already visited. 335 :param list sorted_plugins: List of plugins to be modified and sorted. 336 """ 337 if plugin in visited: 338 return 339 elif plugin not in deps or not deps[plugin]: 340 visited.append(plugin) # Plugin with no dependencies 341 else: 342 visited.append(plugin) 343 344 # First move dependencies 345 for dep in deps[plugin]: 346 _move_plugin(dep, deps, visited, sorted_plugins) 347 348 # Remove current plugin from sorted 349 # list to get dependency indices 350 max_index = sorted_plugins.index(plugin) 351 sorted_plugins.pop(max_index) 352 353 for dep in deps[plugin]: 354 idx = sorted_plugins.index(dep) + 1 if dep in sorted_plugins else -1 355 max_index = max(idx, max_index) 356 357 # Finally, insert after dependencies 358 sorted_plugins.insert(max_index, plugin) 359 360 361def get_plugin_deps(plugin_id: str) -> Dict[str, Optional[str]]: 362 result = {} 363 try: 364 parser = plugins_metadata_parser[plugin_id] 365 plugin_deps = parser.get('general', 'plugin_dependencies') 366 except (configparser.NoOptionError, configparser.NoSectionError, KeyError): 367 return result 368 369 for dep in plugin_deps.split(','): 370 if dep.find('==') > 0: 371 name, version_required = dep.split('==') 372 else: 373 name = dep 374 version_required = None 375 result[name] = version_required 376 return result 377 378 379def pluginMetadata(packageName: str, fct: str) -> str: 380 """ fetch metadata from a plugin - use values from metadata.txt """ 381 try: 382 return plugins_metadata_parser[packageName].get('general', fct) 383 except Exception: 384 return "__error__" 385 386 387def loadPlugin(packageName: str) -> bool: 388 """ load plugin's package """ 389 390 try: 391 __import__(packageName) 392 return True 393 except: 394 pass # continue... 395 396 # snake in the grass, we know it's there 397 sys.path_importer_cache.clear() 398 399 # retry 400 try: 401 __import__(packageName) 402 return True 403 except: 404 msg = QCoreApplication.translate("Python", "Couldn't load plugin '{0}'").format(packageName) 405 showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True, level=Qgis.Critical) 406 return False 407 408 409def _startPlugin(packageName: str) -> bool: 410 """ initializes a plugin, but does not load GUI """ 411 global plugins, active_plugins, iface, plugin_times 412 413 if packageName in active_plugins: 414 return False 415 416 if packageName not in sys.modules: 417 return False 418 419 package = sys.modules[packageName] 420 421 # create an instance of the plugin 422 try: 423 plugins[packageName] = package.classFactory(iface) 424 except: 425 _unloadPluginModules(packageName) 426 errMsg = QCoreApplication.translate("Python", "Couldn't load plugin '{0}'").format(packageName) 427 msg = QCoreApplication.translate("Python", "{0} due to an error when calling its classFactory() method").format(errMsg) 428 showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True, level=Qgis.Critical) 429 return False 430 return True 431 432 433def _addToActivePlugins(packageName: str, duration: int): 434 """ Adds a plugin to the list of active plugins """ 435 active_plugins.append(packageName) 436 plugin_times[packageName] = "{0:02f}s".format(duration) 437 438 439def startPlugin(packageName: str) -> bool: 440 """ initialize the plugin """ 441 global plugins, active_plugins, iface, plugin_times 442 start = time.process_time() 443 if not _startPlugin(packageName): 444 return False 445 446 # initGui 447 try: 448 plugins[packageName].initGui() 449 except: 450 del plugins[packageName] 451 _unloadPluginModules(packageName) 452 errMsg = QCoreApplication.translate("Python", "Couldn't load plugin '{0}'").format(packageName) 453 msg = QCoreApplication.translate("Python", "{0} due to an error when calling its initGui() method").format(errMsg) 454 showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True, level=Qgis.Critical) 455 return False 456 457 end = time.process_time() 458 _addToActivePlugins(packageName, end - start) 459 return True 460 461 462def startProcessingPlugin(packageName: str) -> bool: 463 """ initialize only the Processing components of a plugin """ 464 global plugins, active_plugins, iface, plugin_times 465 start = time.process_time() 466 if not _startPlugin(packageName): 467 return False 468 469 errMsg = QCoreApplication.translate("Python", "Couldn't load plugin '{0}'").format(packageName) 470 if not hasattr(plugins[packageName], 'initProcessing'): 471 del plugins[packageName] 472 _unloadPluginModules(packageName) 473 msg = QCoreApplication.translate("Python", "{0} - plugin has no initProcessing() method").format(errMsg) 474 showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True, level=Qgis.Critical) 475 return False 476 477 # initProcessing 478 try: 479 plugins[packageName].initProcessing() 480 except: 481 del plugins[packageName] 482 _unloadPluginModules(packageName) 483 msg = QCoreApplication.translate("Python", "{0} due to an error when calling its initProcessing() method").format(errMsg) 484 showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True) 485 return False 486 487 end = time.process_time() 488 _addToActivePlugins(packageName, end - start) 489 490 return True 491 492 493def canUninstallPlugin(packageName: str) -> bool: 494 """ confirm that the plugin can be uninstalled """ 495 global plugins, active_plugins 496 497 if packageName not in plugins: 498 return False 499 if packageName not in active_plugins: 500 return False 501 502 try: 503 metadata = plugins[packageName] 504 if "canBeUninstalled" not in dir(metadata): 505 return True 506 return bool(metadata.canBeUninstalled()) 507 except: 508 msg = "Error calling " + packageName + ".canBeUninstalled" 509 showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True) 510 return True 511 512 513def unloadPlugin(packageName: str) -> bool: 514 """ unload and delete plugin! """ 515 global plugins, active_plugins 516 517 if packageName not in plugins: 518 return False 519 if packageName not in active_plugins: 520 return False 521 522 try: 523 plugins[packageName].unload() 524 del plugins[packageName] 525 active_plugins.remove(packageName) 526 _unloadPluginModules(packageName) 527 return True 528 except Exception as e: 529 msg = QCoreApplication.translate("Python", "Error while unloading plugin {0}").format(packageName) 530 showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg, messagebar=True) 531 return False 532 533 534def _unloadPluginModules(packageName: str): 535 """ unload plugin package with all its modules (files) """ 536 global _plugin_modules 537 mods = _plugin_modules[packageName] 538 539 for mod in mods: 540 if mod not in sys.modules: 541 continue 542 543 # if it looks like a Qt resource file, try to do a cleanup 544 # otherwise we might experience a segfault next time the plugin is loaded 545 # because Qt will try to access invalid plugin resource data 546 try: 547 if hasattr(sys.modules[mod], 'qCleanupResources'): 548 sys.modules[mod].qCleanupResources() 549 except: 550 # Print stack trace for debug 551 qDebug("qCleanupResources error:\n%s" % traceback.format_exc()) 552 553 # try removing path 554 if hasattr(sys.modules[mod], '__path__'): 555 for path in sys.modules[mod].__path__: 556 try: 557 sys.path.remove(path) 558 except ValueError: 559 # Discard if path is not there 560 pass 561 562 # try to remove the module from python 563 try: 564 del sys.modules[mod] 565 except: 566 qDebug("Error when removing module:\n%s" % traceback.format_exc()) 567 # remove the plugin entry 568 del _plugin_modules[packageName] 569 570 571def isPluginLoaded(packageName: str) -> bool: 572 """ find out whether a plugin is active (i.e. has been started) """ 573 global plugins, active_plugins 574 575 if packageName not in plugins: 576 return False 577 return (packageName in active_plugins) 578 579 580def reloadPlugin(packageName: str) -> bool: 581 """ unload and start again a plugin """ 582 global active_plugins 583 if packageName not in active_plugins: 584 return False # it's not active 585 586 unloadPlugin(packageName) 587 loadPlugin(packageName) 588 started = startPlugin(packageName) 589 return started 590 591 592def showPluginHelp(packageName: str = None, filename: str = "index", section: str = ""): 593 """Open help in the user's html browser. The help file should be named index-ll_CC.html or index-ll.html or index.html. 594 595 :param str packageName: name of package folder, if None it's using the current file package. Defaults to None. Optional. 596 :param str filename: name of file to open. It can be a path like 'doc/index' for example. Defaults to 'index'. 597 :param str section: URL path to open. Defaults to empty string. 598 """ 599 try: 600 source = "" 601 if packageName is None: 602 import inspect 603 604 source = inspect.currentframe().f_back.f_code.co_filename 605 else: 606 source = sys.modules[packageName].__file__ 607 except: 608 return 609 path = os.path.dirname(source) 610 locale = str(QLocale().name()) 611 helpfile = os.path.join(path, filename + "-" + locale + ".html") 612 if not os.path.exists(helpfile): 613 helpfile = os.path.join(path, filename + "-" + locale.split("_")[0] + ".html") 614 if not os.path.exists(helpfile): 615 helpfile = os.path.join(path, filename + "-en.html") 616 if not os.path.exists(helpfile): 617 helpfile = os.path.join(path, filename + "-en_US.html") 618 if not os.path.exists(helpfile): 619 helpfile = os.path.join(path, filename + ".html") 620 if os.path.exists(helpfile): 621 url = "file://" + helpfile 622 if section != "": 623 url = url + "#" + section 624 QDesktopServices.openUrl(QUrl(url)) 625 626 627def pluginDirectory(packageName: str) -> str: 628 """ return directory where the plugin resides. Plugin must be loaded already """ 629 return os.path.dirname(sys.modules[packageName].__file__) 630 631 632def reloadProjectMacros(): 633 # unload old macros 634 unloadProjectMacros() 635 636 from qgis.core import QgsProject 637 638 code, ok = QgsProject.instance().readEntry("Macros", "/pythonCode") 639 if not ok or not code or code == '': 640 return 641 642 # create a new empty python module 643 import importlib 644 mod = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("proj_macros_mod", None)) 645 646 # set the module code and store it sys.modules 647 exec(str(code), mod.__dict__) 648 sys.modules["proj_macros_mod"] = mod 649 650 # load new macros 651 openProjectMacro() 652 653 654def unloadProjectMacros(): 655 if "proj_macros_mod" not in sys.modules: 656 return 657 # unload old macros 658 closeProjectMacro() 659 # destroy the reference to the module 660 del sys.modules["proj_macros_mod"] 661 662 663def openProjectMacro(): 664 if "proj_macros_mod" not in sys.modules: 665 return 666 mod = sys.modules["proj_macros_mod"] 667 if hasattr(mod, 'openProject'): 668 mod.openProject() 669 670 671def saveProjectMacro(): 672 if "proj_macros_mod" not in sys.modules: 673 return 674 mod = sys.modules["proj_macros_mod"] 675 if hasattr(mod, 'saveProject'): 676 mod.saveProject() 677 678 679def closeProjectMacro(): 680 if "proj_macros_mod" not in sys.modules: 681 return 682 mod = sys.modules["proj_macros_mod"] 683 if hasattr(mod, 'closeProject'): 684 mod.closeProject() 685 686 687####################### 688# SERVER PLUGINS 689# 690# TODO: move into server_utils.py ? 691 692# list of plugin paths. it gets filled in by the QGIS python library 693server_plugin_paths = [] 694 695# dictionary of plugins 696server_plugins = {} 697 698# list of active (started) plugins 699server_active_plugins = [] 700 701 702# initialize 'serverIface' object 703serverIface = None 704 705 706def initServerInterface(pointer): 707 from qgis.server import QgsServerInterface 708 from sip import wrapinstance 709 sys.excepthook = sys.__excepthook__ 710 global serverIface 711 serverIface = wrapinstance(pointer, QgsServerInterface) 712 713 714def startServerPlugin(packageName: str): 715 """ initialize the plugin """ 716 global server_plugins, server_active_plugins, serverIface 717 718 if packageName in server_active_plugins: 719 return False 720 if packageName not in sys.modules: 721 return False 722 723 package = sys.modules[packageName] 724 725 errMsg = QCoreApplication.translate("Python", "Couldn't load server plugin {0}").format(packageName) 726 727 # create an instance of the plugin 728 try: 729 server_plugins[packageName] = package.serverClassFactory(serverIface) 730 except: 731 _unloadPluginModules(packageName) 732 msg = QCoreApplication.translate("Python", 733 "{0} due to an error when calling its serverClassFactory() method").format(errMsg) 734 showException(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], msg) 735 return False 736 737 # add to active plugins 738 server_active_plugins.append(packageName) 739 return True 740 741 742def spatialite_connect(*args, **kwargs): 743 """returns a dbapi2.Connection to a SpatiaLite db 744using the "mod_spatialite" extension (python3)""" 745 import sqlite3 746 import re 747 748 def fcnRegexp(pattern, string): 749 result = re.search(pattern, string) 750 return True if result else False 751 752 con = sqlite3.dbapi2.connect(*args, **kwargs) 753 con.enable_load_extension(True) 754 cur = con.cursor() 755 libs = [ 756 # SpatiaLite >= 4.2 and Sqlite >= 3.7.17, should work on all platforms 757 ("mod_spatialite", "sqlite3_modspatialite_init"), 758 # SpatiaLite >= 4.2 and Sqlite < 3.7.17 (Travis) 759 ("mod_spatialite.so", "sqlite3_modspatialite_init"), 760 # SpatiaLite < 4.2 (linux) 761 ("libspatialite.so", "sqlite3_extension_init") 762 ] 763 found = False 764 for lib, entry_point in libs: 765 try: 766 cur.execute("select load_extension('{}', '{}')".format(lib, entry_point)) 767 except sqlite3.OperationalError: 768 continue 769 else: 770 found = True 771 break 772 if not found: 773 raise RuntimeError("Cannot find any suitable spatialite module") 774 if any(['.gpkg' in arg for arg in args]): 775 try: 776 cur.execute("SELECT EnableGpkgAmphibiousMode()") 777 except (sqlite3.Error, sqlite3.DatabaseError, sqlite3.NotSupportedError): 778 QgsMessageLog.logMessage(u"warning:{}".format("Could not enable geopackage amphibious mode"), 779 QCoreApplication.translate("Python", "Python warning")) 780 781 cur.close() 782 con.enable_load_extension(False) 783 con.create_function("regexp", 2, fcnRegexp) 784 return con 785 786 787class OverrideCursor(): 788 """ 789 Executes a code block with a different cursor set and makes sure the cursor 790 is restored even if exceptions are raised or an intermediate ``return`` 791 statement is hit. 792 793 Example: 794 ``` 795 with OverrideCursor(Qt.WaitCursor): 796 do_a_slow(operation) 797 ``` 798 """ 799 800 def __init__(self, cursor): 801 self.cursor = cursor 802 803 def __enter__(self): 804 QApplication.setOverrideCursor(self.cursor) 805 806 def __exit__(self, exc_type, exc_val, exc_tb): 807 QApplication.restoreOverrideCursor() 808 return exc_type is None 809 810 811####################### 812# IMPORT wrapper 813 814if os.name == 'nt' and sys.version_info < (3, 8): 815 import ctypes 816 from ctypes import windll, wintypes 817 818 kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) 819 820 _hasAddDllDirectory = hasattr(kernel32, 'AddDllDirectory') 821 if _hasAddDllDirectory: 822 _import_path = os.environ['PATH'] 823 _import_paths = {} 824 825 def _errcheck_zero(result, func, args): 826 if not result: 827 raise ctypes.WinError(ctypes.get_last_error()) 828 return args 829 830 DLL_DIRECTORY_COOKIE = wintypes.LPVOID 831 832 _AddDllDirectory = kernel32.AddDllDirectory 833 _AddDllDirectory.errcheck = _errcheck_zero 834 _AddDllDirectory.restype = DLL_DIRECTORY_COOKIE 835 _AddDllDirectory.argtypes = (wintypes.LPCWSTR,) 836 837 _RemoveDllDirectory = kernel32.RemoveDllDirectory 838 _RemoveDllDirectory.errcheck = _errcheck_zero 839 _RemoveDllDirectory.argtypes = (DLL_DIRECTORY_COOKIE,) 840 841_uses_builtins = True 842try: 843 import builtins 844 _builtin_import = builtins.__import__ 845except AttributeError: 846 _uses_builtins = False 847 import __builtin__ 848 _builtin_import = __builtin__.__import__ 849 850_plugin_modules = {} 851 852 853def _import(name, globals={}, locals={}, fromlist=[], level=None): 854 """ 855 Wrapper around builtin import that keeps track of loaded plugin modules and blocks 856 certain unsafe imports 857 """ 858 if level is None: 859 level = 0 860 861 if 'PyQt4' in name: 862 msg = 'PyQt4 classes cannot be imported in QGIS 3.x.\n' \ 863 'Use {} or the version independent {} import instead.'.format(name.replace('PyQt4', 'PyQt5'), name.replace('PyQt4', 'qgis.PyQt')) 864 raise ImportError(msg) 865 866 if os.name == 'nt' and sys.version_info < (3, 8): 867 global _hasAddDllDirectory 868 if _hasAddDllDirectory: 869 global _import_path 870 global _import_paths 871 872 old_path = _import_path 873 new_path = os.environ['PATH'] 874 if old_path != new_path: 875 global _AddDllDirectory 876 global _RemoveDllDirectory 877 878 for p in set(new_path.split(';')) - set(old_path.split(';')): 879 if p is not None and p not in _import_path and os.path.isdir(p): 880 _import_paths[p] = _AddDllDirectory(p) 881 882 for p in set(old_path.split(';')) - set(new_path.split(';')): 883 if p in _import_paths: 884 _RemoveDllDirectory(_import_paths.pop(p)) 885 886 _import_path = new_path 887 888 mod = _builtin_import(name, globals, locals, fromlist, level) 889 890 if mod and getattr(mod, '__file__', None): 891 module_name = mod.__name__ if fromlist else name 892 package_name = module_name.split('.')[0] 893 # check whether the module belongs to one of our plugins 894 if package_name in available_plugins: 895 if package_name not in _plugin_modules: 896 _plugin_modules[package_name] = set() 897 _plugin_modules[package_name].add(module_name) 898 # check the fromlist for additional modules (from X import Y,Z) 899 if fromlist: 900 for fromitem in fromlist: 901 frmod = module_name + "." + fromitem 902 if frmod in sys.modules: 903 _plugin_modules[package_name].add(frmod) 904 905 return mod 906 907 908if not os.environ.get('QGIS_NO_OVERRIDE_IMPORT'): 909 if _uses_builtins: 910 builtins.__import__ = _import 911 else: 912 __builtin__.__import__ = _import 913 914 915def run_script_from_file(filepath: str): 916 """ 917 Runs a Python script from a given file. Supports loading processing scripts. 918 :param filepath: The .py file to load. 919 """ 920 import sys 921 import inspect 922 from qgis.processing import alg 923 try: 924 from qgis.core import QgsApplication, QgsProcessingAlgorithm, QgsProcessingFeatureBasedAlgorithm 925 from qgis.processing import execAlgorithmDialog 926 _locals = {} 927 exec(open(filepath.replace("\\\\", "/").encode(sys.getfilesystemencoding())).read(), _locals) 928 alginstance = None 929 try: 930 alginstance = alg.instances.pop().createInstance() 931 except IndexError: 932 for name, attr in _locals.items(): 933 if inspect.isclass(attr) and issubclass(attr, (QgsProcessingAlgorithm, QgsProcessingFeatureBasedAlgorithm)) and attr.__name__ not in ("QgsProcessingAlgorithm", "QgsProcessingFeatureBasedAlgorithm"): 934 alginstance = attr() 935 break 936 if alginstance: 937 alginstance.setProvider(QgsApplication.processingRegistry().providerById("script")) 938 alginstance.initAlgorithm() 939 execAlgorithmDialog(alginstance) 940 except ImportError: 941 pass 942