1"""Migrate imports of PyQt4 to PyQt wrapper 2""" 3# Author: Juergen E. Fischer 4# Adapted from fix_urllib 5 6# Local imports 7from lib2to3.fixes.fix_imports import alternates, FixImports 8from lib2to3 import fixer_base 9from lib2to3.fixer_util import (Name, Comma, FromImport, Newline, 10 find_indentation, Node, syms, Leaf) 11 12MAPPING = { 13 "PyQt4.QtGui": [ 14 ("qgis.PyQt.QtGui", [ 15 "QIcon", 16 "QCursor", 17 "QColor", 18 "QDesktopServices", 19 "QFont", 20 "QFontMetrics", 21 "QKeySequence", 22 "QStandardItemModel", 23 "QStandardItem", 24 "QClipboard", 25 "QPixmap", 26 "QDoubleValidator", 27 "QPainter", 28 "QPen", 29 "QBrush", 30 "QPalette", 31 "QPainterPath", 32 "QImage", 33 "QPolygonF", 34 "QFontMetricsF", 35 "QGradient", 36 "QIntValidator", 37 ]), 38 ("qgis.PyQt.QtWidgets", [ 39 "QAbstractButton", 40 "QAbstractGraphicsShapeItem", 41 "QAbstractItemDelegate", 42 "QAbstractItemView", 43 "QAbstractScrollArea", 44 "QAbstractSlider", 45 "QAbstractSpinBox", 46 "QAbstractTableModel", 47 "QAction", 48 "QActionGroup", 49 "QApplication", 50 "QBoxLayout", 51 "QButtonGroup", 52 "QCalendarWidget", 53 "QCheckBox", 54 "QColorDialog", 55 "QColumnView", 56 "QComboBox", 57 "QCommandLinkButton", 58 "QCommonStyle", 59 "QCompleter", 60 "QDataWidgetMapper", 61 "QDateEdit", 62 "QDateTimeEdit", 63 "QDesktopWidget", 64 "QDial", 65 "QDialog", 66 "QDialogButtonBox", 67 "QDirModel", 68 "QDockWidget", 69 "QDoubleSpinBox", 70 "QErrorMessage", 71 "QFileDialog", 72 "QFileIconProvider", 73 "QFileSystemModel", 74 "QFocusFrame", 75 "QFontComboBox", 76 "QFontDialog", 77 "QFormLayout", 78 "QFrame", 79 "QGesture", 80 "QGestureEvent", 81 "QGestureRecognizer", 82 "QGraphicsAnchor", 83 "QGraphicsAnchorLayout", 84 "QGraphicsBlurEffect", 85 "QGraphicsColorizeEffect", 86 "QGraphicsDropShadowEffect", 87 "QGraphicsEffect", 88 "QGraphicsEllipseItem", 89 "QGraphicsGridLayout", 90 "QGraphicsItem", 91 "QGraphicsItemGroup", 92 "QGraphicsLayout", 93 "QGraphicsLayoutItem", 94 "QGraphicsLineItem", 95 "QGraphicsLinearLayout", 96 "QGraphicsObject", 97 "QGraphicsOpacityEffect", 98 "QGraphicsPathItem", 99 "QGraphicsPixmapItem", 100 "QGraphicsPolygonItem", 101 "QGraphicsProxyWidget", 102 "QGraphicsRectItem", 103 "QGraphicsRotation", 104 "QGraphicsScale", 105 "QGraphicsScene", 106 "QGraphicsSceneContextMenuEvent", 107 "QGraphicsSceneDragDropEvent", 108 "QGraphicsSceneEvent", 109 "QGraphicsSceneHelpEvent", 110 "QGraphicsSceneHoverEvent", 111 "QGraphicsSceneMouseEvent", 112 "QGraphicsSceneMoveEvent", 113 "QGraphicsSceneResizeEvent", 114 "QGraphicsSceneWheelEvent", 115 "QGraphicsSimpleTextItem", 116 "QGraphicsTextItem", 117 "QGraphicsTransform", 118 "QGraphicsView", 119 "QGraphicsWidget", 120 "QGridLayout", 121 "QGroupBox", 122 "QHBoxLayout", 123 "QHeaderView", 124 "QInputDialog", 125 "QItemDelegate", 126 "QItemEditorCreatorBase", 127 "QItemEditorFactory", 128 "QKeyEventTransition", 129 "QLCDNumber", 130 "QLabel", 131 "QLayout", 132 "QLayoutItem", 133 "QLineEdit", 134 "QListView", 135 "QListWidget", 136 "QListWidgetItem", 137 "QMainWindow", 138 "QMdiArea", 139 "QMdiSubWindow", 140 "QMenu", 141 "QMenuBar", 142 "QMessageBox", 143 "QMouseEventTransition", 144 "QPanGesture", 145 "QPinchGesture", 146 "QPlainTextDocumentLayout", 147 "QPlainTextEdit", 148 "QProgressBar", 149 "QProgressDialog", 150 "QPushButton", 151 "QRadioButton", 152 "QRubberBand", 153 "QScrollArea", 154 "QScrollBar", 155 "QShortcut", 156 "QSizeGrip", 157 "QSizePolicy", 158 "QSlider", 159 "QSpacerItem", 160 "QSpinBox", 161 "QSplashScreen", 162 "QSplitter", 163 "QSplitterHandle", 164 "QStackedLayout", 165 "QStackedWidget", 166 "QStatusBar", 167 "QStyle", 168 "QStyleFactory", 169 "QStyleHintReturn", 170 "QStyleHintReturnMask", 171 "QStyleHintReturnVariant", 172 "QStyleOption", 173 "QStyleOptionButton", 174 "QStyleOptionComboBox", 175 "QStyleOptionComplex", 176 "QStyleOptionDockWidget", 177 "QStyleOptionFocusRect", 178 "QStyleOptionFrame", 179 "QStyleOptionGraphicsItem", 180 "QStyleOptionGroupBox", 181 "QStyleOptionHeader", 182 "QStyleOptionMenuItem", 183 "QStyleOptionProgressBar", 184 "QStyleOptionRubberBand", 185 "QStyleOptionSizeGrip", 186 "QStyleOptionSlider", 187 "QStyleOptionSpinBox", 188 "QStyleOptionTab", 189 "QStyleOptionTabBarBase", 190 "QStyleOptionTabWidgetFrame", 191 "QStyleOptionTitleBar", 192 "QStyleOptionToolBar", 193 "QStyleOptionToolBox", 194 "QStyleOptionToolButton", 195 "QStyleOptionViewItem", 196 "QStylePainter", 197 "QStyledItemDelegate", 198 "QSwipeGesture", 199 "QSystemTrayIcon", 200 "QTabBar", 201 "QTabWidget", 202 "QTableView", 203 "QTableWidget", 204 "QTableWidgetItem", 205 "QTableWidgetSelectionRange", 206 "QTapAndHoldGesture", 207 "QTapGesture", 208 "QTextBrowser", 209 "QTextEdit", 210 "QTimeEdit", 211 "QToolBar", 212 "QToolBox", 213 "QToolButton", 214 "QToolTip", 215 "QTreeView", 216 "QTreeWidget", 217 "QTreeWidgetItem", 218 "QTreeWidgetItemIterator", 219 "QUndoCommand", 220 "QUndoGroup", 221 "QUndoStack", 222 "QUndoView", 223 "QVBoxLayout", 224 "QWhatsThis", 225 "QWidget", 226 "QWidgetAction", 227 "QWidgetItem", 228 "QWizard", 229 "QWizardPage", 230 "qApp", 231 "qDrawBorderPixmap", 232 "qDrawPlainRect", 233 "qDrawShadeLine", 234 "qDrawShadePanel", 235 "qDrawShadeRect", 236 "qDrawWinButton", 237 "qDrawWinPanel", 238 ]), 239 ("qgis.PyQt.QtPrintSupport", [ 240 "QPrinter", 241 "QAbstractPrintDialog", 242 "QPageSetupDialog", 243 "QPrintDialog", 244 "QPrintEngine", 245 "QPrintPreviewDialog", 246 "QPrintPreviewWidget", 247 "QPrinterInfo", 248 ]), 249 ("qgis.PyQt.QtCore", [ 250 "QItemSelectionModel", 251 "QSortFilterProxyModel", 252 ]), 253 ], 254 "PyQt4.QtCore": [ 255 ("qgis.PyQt.QtCore", [ 256 "QAbstractItemModel", 257 "QAbstractTableModel", 258 "QByteArray", 259 "QCoreApplication", 260 "QDataStream", 261 "QDir", 262 "QEvent", 263 "QFile", 264 "QFileInfo", 265 "QIODevice", 266 "QLocale", 267 "QMimeData", 268 "QModelIndex", 269 "QMutex", 270 "QObject", 271 "QProcess", 272 "QSettings", 273 "QSize", 274 "QSizeF", 275 "QTextCodec", 276 "QThread", 277 "QThreadPool", 278 "QTimer", 279 "QTranslator", 280 "QUrl", 281 "Qt", 282 "pyqtProperty", 283 "pyqtWrapperType", 284 "pyqtSignal", 285 "pyqtSlot", 286 "qDebug", 287 "qWarning", 288 "qVersion", 289 "QDate", 290 "QTime", 291 "QDateTime", 292 "QRegExp", 293 "QTemporaryFile", 294 "QTextStream", 295 "QVariant", 296 "QPyNullVariant", 297 "QRect", 298 "QRectF", 299 "QMetaObject", 300 "QPoint", 301 "QPointF", 302 "QDirIterator", 303 "QEventLoop", 304 "NULL", 305 ]), 306 (None, [ 307 "SIGNAL", 308 "SLOT", 309 ]), 310 ], 311 "PyQt4.QtNetwork": [ 312 ("qgis.PyQt.QtNetwork", [ 313 "QNetworkReply", 314 "QNetworkRequest", 315 "QSslCertificate", 316 "QSslKey", 317 "QSsl" 318 ]) 319 ], 320 "PyQt4.QtXml": [ 321 ("qgis.PyQt.QtXml", [ 322 "QDomDocument" 323 ]), 324 ], 325 "PyQt4.Qsci": [ 326 ("qgis.PyQt.Qsci", [ 327 "QsciAPIs", 328 "QsciLexerCustom", 329 "QsciLexerPython", 330 "QsciScintilla", 331 "QsciLexerSQL", 332 "QsciStyle", 333 ]), 334 ], 335 "PyQt4.QtWebKit": [ 336 ("qgis.PyQt.QtWebKitWidgets", [ 337 "QGraphicsWebView", 338 "QWebFrame", 339 "QWebHitTestResult", 340 "QWebInspector", 341 "QWebPage", 342 "QWebView", 343 ]), 344 ], 345 "PyQt4.QtTest": [ 346 ("qgis.PyQt.QtTest", [ 347 "QTest", 348 ]), 349 ], 350 "PyQt4.QtSvg": [ 351 ("qgis.PyQt.QtSvg", [ 352 "QSvgRenderer", 353 "QSvgGenerator" 354 ]), 355 ], 356 "PyQt4.QtSql": [ 357 ("qgis.PyQt.QtSql", [ 358 "QSqlDatabase", 359 "QSqlQuery", 360 "QSqlField" 361 ]), 362 ], 363 "PyQt4.uic": [ 364 ("qgis.PyQt.uic", [ 365 "loadUiType", 366 "loadUi", 367 ]), 368 ], 369 "PyQt4": [ 370 ("qgis.PyQt", [ 371 "QtCore", 372 "QtGui", 373 "QtNetwork", 374 "QtXml", 375 "QtWebkit", 376 "QtSql", 377 "QtSvg", 378 "Qsci", 379 "uic", 380 ]) 381 ], 382} 383 384 385new_mappings = {} 386for key, value in MAPPING.items(): 387 match_str = key.replace('PyQt4', '') 388 match_str = '{}{}'.format('qgis.PyQt', match_str) 389 new_mappings[match_str] = value 390 391MAPPING.update(new_mappings) 392 393 394def build_pattern(): 395 bare = set() 396 for old_module, changes in list(MAPPING.items()): 397 for change in changes: 398 new_module, members = change 399 members = alternates(members) 400 401 if '.' not in old_module: 402 from_name = "%r" % old_module 403 404 else: 405 dotted = old_module.split('.') 406 if len(dotted) == 3: 407 from_name = "dotted_name<%r '.' %r '.' %r>" % (dotted[0], dotted[1], dotted[2]) 408 else: 409 assert len(dotted) == 2 410 from_name = "dotted_name<%r '.' %r>" % (dotted[0], dotted[1]) 411 412 yield """import_name< 'import' (module=%s 413 | dotted_as_names< any* module=%s any* >) > 414 """ % (from_name, from_name) 415 yield """import_from< 'from' mod_member=%s 'import' 416 ( member=%s | import_as_name< member=%s 'as' any > | 417 import_as_names< members=any* >) > 418 """ % (from_name, members, members) 419 yield """import_from< 'from' mod_member=%s 'import' '(' 420 ( member=%s | import_as_name< member=%s 'as' any > | 421 import_as_names< members=any* >) ')' > 422 """ % (from_name, members, members) 423 yield """import_from< 'from' module_star=%s 'import' star='*' > 424 """ % from_name 425 yield """import_name< 'import' 426 dotted_as_name< module_as=%s 'as' any > > 427 """ % from_name 428 # bare_with_attr has a special significance for FixImports.match(). 429 yield """power< bare_with_attr=%s trailer< '.' member=%s > any* > 430 """ % (from_name, members) 431 432 433class FixPyqt(FixImports): 434 435 def build_pattern(self): 436 return "|".join(build_pattern()) 437 438# def match(self, node): 439# res = super(FixImports, self).match( node ) 440# print repr(node) 441# return res 442 443 def transform_import(self, node, results): 444 """Transform for the basic import case. Replaces the old 445 import name with a comma separated list of its 446 replacements. 447 """ 448 import_mod = results.get("module") 449 pref = import_mod.prefix 450 451 names = [] 452 453 if isinstance(import_mod, Leaf): 454 # create a Node list of the replacement modules 455 for name in MAPPING[import_mod.value][:-1]: 456 names.extend([Name(name[0], prefix=pref), Comma()]) 457 names.append(Name(MAPPING[import_mod.value][-1][0], prefix=pref)) 458 import_mod.replace(names) 459 else: 460 self.cannot_convert(node, "imports like PyQt4.QtGui or import qgis.PyQt.QtGui are not supported") 461 462 def transform_member(self, node, results): 463 """Transform for imports of specific module elements. Replaces 464 the module to be imported from with the appropriate new 465 module. 466 """ 467 mod_member = results.get("mod_member") 468 if isinstance(mod_member, Node): 469 module = "" 470 for l in mod_member.leaves(): 471 module += l.value 472 mod_member.value = module 473 pref = mod_member.prefix 474 member = results.get("member") 475 476 missing = False 477 478 # Simple case with only a single member being imported 479 if member: 480 # this may be a list of length one, or just a node 481 if isinstance(member, list): 482 member = member[0] 483 new_name = '' 484 for change in MAPPING[mod_member.value]: 485 if member.value in change[1]: 486 new_name = change[0] 487 break 488 if new_name: 489 mod_member.replace(Name(new_name, prefix=pref)) 490 elif new_name == '': 491 self.cannot_convert(node, "This is an invalid module element") 492 else: 493 node.remove() 494 495 # Multiple members being imported 496 else: 497 # a dictionary for replacements, order matters 498 modules = [] 499 mod_dict = {} 500 members = results["members"] 501 for member in members: 502 # we only care about the actual members 503 if member.type == syms.import_as_name: 504 as_name = member.children[2].value 505 member_name = member.children[0].value 506 else: 507 member_name = member.value 508 as_name = None 509 if member_name != u",": 510 found = False 511 for change in MAPPING[mod_member.value]: 512 if member_name in change[1]: 513 if change[0] is not None: 514 if change[0] not in mod_dict: 515 modules.append(change[0]) 516 mod_dict.setdefault(change[0], []).append(member) 517 found = True 518 if not found: 519 f = open("/tmp/missing", "a+") 520 f.write("member %s of %s not found\n" % (member_name, mod_member.value)) 521 f.close() 522 missing = True 523 524 new_nodes = [] 525 indentation = find_indentation(node) 526 first = True 527 528 def handle_name(name, prefix): 529 if name.type == syms.import_as_name: 530 kids = [Name(name.children[0].value, prefix=prefix), 531 name.children[1].clone(), 532 name.children[2].clone()] 533 return [Node(syms.import_as_name, kids)] 534 return [Name(name.value, prefix=prefix)] 535 536 for module in modules: 537 elts = mod_dict[module] 538 names = [] 539 for elt in elts[:-1]: 540 names.extend(handle_name(elt, pref)) 541 names.append(Comma()) 542 names.extend(handle_name(elts[-1], pref)) 543 new = FromImport(module, names) 544 if not first or node.parent.prefix.endswith(indentation): 545 new.prefix = indentation 546 new_nodes.append(new) 547 first = False 548 549 if new_nodes: 550 nodes = [] 551 for new_node in new_nodes[:-1]: 552 nodes.extend([new_node, Newline()]) 553 nodes.append(new_nodes[-1]) 554 555 if node.prefix: 556 nodes[0].prefix = node.prefix 557 558 node.replace(nodes) 559 elif missing: 560 self.cannot_convert(node, "All module elements are invalid") 561 else: 562 node.remove() 563 564 def transform_dot(self, node, results): 565 """Transform for calls to module members in code.""" 566 module_dot = results.get("bare_with_attr") 567 member = results.get("member") 568 new_name = None 569 if isinstance(member, list): 570 member = member[0] 571 for change in MAPPING[module_dot.value]: 572 if member.value in change[1]: 573 new_name = change[0] 574 break 575 if new_name: 576 module_dot.replace(Name(new_name, 577 prefix=module_dot.prefix)) 578 else: 579 self.cannot_convert(node, "This is an invalid module element") 580 581 def transform(self, node, results): 582 if results.get("module"): 583 self.transform_import(node, results) 584 elif results.get("mod_member"): 585 self.transform_member(node, results) 586 elif results.get("bare_with_attr"): 587 self.transform_dot(node, results) 588 # Renaming and star imports are not supported for these modules. 589 elif results.get("module_star"): 590 self.cannot_convert(node, "Cannot handle star imports.") 591 elif results.get("module_as"): 592 self.cannot_convert(node, "This module is now multiple modules") 593