############################################################################# ## ## Copyright (C) 2017 The Qt Company Ltd. ## Contact: https://www.qt.io/licensing/ ## ## This file is part of Qt for Python. ## ## $QT_BEGIN_LICENSE:LGPL$ ## Commercial License Usage ## Licensees holding valid commercial Qt licenses may use this file in ## accordance with the commercial license agreement provided with the ## Software or, alternatively, in accordance with the terms contained in ## a written agreement between you and The Qt Company. For licensing terms ## and conditions see https://www.qt.io/terms-conditions. For further ## information use the contact form at https://www.qt.io/contact-us. ## ## GNU Lesser General Public License Usage ## Alternatively, this file may be used under the terms of the GNU Lesser ## General Public License version 3 as published by the Free Software ## Foundation and appearing in the file LICENSE.LGPL3 included in the ## packaging of this file. Please review the following information to ## ensure the GNU Lesser General Public License version 3 requirements ## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ## ## GNU General Public License Usage ## Alternatively, this file may be used under the terms of the GNU ## General Public License version 2.0 or (at your option) the GNU General ## Public license version 3 or any later version approved by the KDE Free ## Qt Foundation. The licenses are as published by the Free Software ## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ## included in the packaging of this file. Please review the following ## information to ensure the GNU General Public License requirements will ## be met: https://www.gnu.org/licenses/gpl-2.0.html and ## https://www.gnu.org/licenses/gpl-3.0.html. ## ## $QT_END_LICENSE$ ## ############################################################################# # This script is used to generate a summary of missing types / classes # which are present in C++ Qt5, but are missing in PySide2. # # Required packages: bs4 # Installed via: pip install bs4 # # The script uses beautiful soup 4 to parse out the class names from # the online Qt documentation. It then tries to import the types from # PySide2. # # Example invocation of script: # python missing_bindings.py --qt-version 5.9 -w all # --qt-version - specify which version of qt documentation to load. # -w - if PyQt5 is an installed package, check if the tested # class also exists there. from __future__ import print_function try: import urllib.request as urllib2 except ImportError: import urllib2 import argparse from bs4 import BeautifulSoup from collections import OrderedDict from time import gmtime, strftime import sys import os.path modules_to_test = OrderedDict() # Essentials modules_to_test['QtCore'] = 'qtcore-module.html' modules_to_test['QtGui'] = 'qtgui-module.html' modules_to_test['QtMultimedia'] = 'qtmultimedia-module.html' modules_to_test['QtMultimediaWidgets'] = 'qtmultimediawidgets-module.html' modules_to_test['QtNetwork'] = 'qtnetwork-module.html' modules_to_test['QtQml'] = 'qtqml-module.html' modules_to_test['QtQuick'] = 'qtquick-module.html' modules_to_test['QtSql'] = 'qtsql-module.html' modules_to_test['QtTest'] = 'qttest-module.html' modules_to_test['QtWidgets'] = 'qtwidgets-module.html' # Addons modules_to_test['Qt3DCore'] = 'qt3dcore-module.html' modules_to_test['Qt3DInput'] = 'qt3dinput-module.html' modules_to_test['Qt3DLogic'] = 'qt3dlogic-module.html' modules_to_test['Qt3DRender'] = 'qt3drender-module.html' modules_to_test['Qt3DAnimation'] = 'qt3danimation-module.html' modules_to_test['Qt3DExtras'] = 'qt3dextras-module.html' modules_to_test['QtConcurrent'] = 'qtconcurrent-module.html' modules_to_test['QtNetworkAuth'] = 'qtnetworkauth-module.html' modules_to_test['QtHelp'] = 'qthelp-module.html' modules_to_test['QtLocation'] = 'qtlocation-module.html' modules_to_test['QtPrintSupport'] = 'qtprintsupport-module.html' modules_to_test['QtSCXML'] = 'qtscxml-module.html' modules_to_test['QtSpeech'] = 'qtspeech-module.html' modules_to_test['QtSvg'] = 'qtsvg-module.html' modules_to_test['QtUiTools'] = 'qtuitools-module.html' modules_to_test['QtWebChannel'] = 'qtwebchannel-module.html' modules_to_test['QtWebEngine'] = 'qtwebengine-module.html' modules_to_test['QtWebEngineWidgets'] = 'qtwebenginewidgets-module.html' modules_to_test['QtWebSockets'] = 'qtwebsockets-module.html' modules_to_test['QtMacExtras'] = 'qtmacextras-module.html' modules_to_test['QtX11Extras'] = 'qtx11extras-module.html' modules_to_test['QtWinExtras'] = 'qtwinextras-module.html' modules_to_test['QtXml'] = 'qtxml-module.html' modules_to_test['QtXmlPatterns'] = 'qtxmlpatterns-module.html' modules_to_test['QtCharts'] = 'qt-charts-module.html' modules_to_test['QtDataVisualization'] = 'qtdatavisualization-module.html' types_to_ignore = set() # QtCore types_to_ignore.add('QFlag') types_to_ignore.add('QFlags') types_to_ignore.add('QGlobalStatic') types_to_ignore.add('QDebug') types_to_ignore.add('QDebugStateSaver') types_to_ignore.add('QMetaObject.Connection') types_to_ignore.add('QPointer') types_to_ignore.add('QAssociativeIterable') types_to_ignore.add('QSequentialIterable') types_to_ignore.add('QStaticPlugin') types_to_ignore.add('QChar') types_to_ignore.add('QLatin1Char') types_to_ignore.add('QHash') types_to_ignore.add('QMultiHash') types_to_ignore.add('QLinkedList') types_to_ignore.add('QList') types_to_ignore.add('QMap') types_to_ignore.add('QMultiMap') types_to_ignore.add('QMap.key_iterator') types_to_ignore.add('QPair') types_to_ignore.add('QQueue') types_to_ignore.add('QScopedArrayPointer') types_to_ignore.add('QScopedPointer') types_to_ignore.add('QScopedValueRollback') types_to_ignore.add('QMutableSetIterator') types_to_ignore.add('QSet') types_to_ignore.add('QSet.const_iterator') types_to_ignore.add('QSet.iterator') types_to_ignore.add('QExplicitlySharedDataPointer') types_to_ignore.add('QSharedData') types_to_ignore.add('QSharedDataPointer') types_to_ignore.add('QEnableSharedFromThis') types_to_ignore.add('QSharedPointer') types_to_ignore.add('QWeakPointer') types_to_ignore.add('QStack') types_to_ignore.add('QLatin1String') types_to_ignore.add('QString') types_to_ignore.add('QStringRef') types_to_ignore.add('QStringList') types_to_ignore.add('QStringMatcher') types_to_ignore.add('QVarLengthArray') types_to_ignore.add('QVector') types_to_ignore.add('QFutureIterator') types_to_ignore.add('QHashIterator') types_to_ignore.add('QMutableHashIterator') types_to_ignore.add('QLinkedListIterator') types_to_ignore.add('QMutableLinkedListIterator') types_to_ignore.add('QListIterator') types_to_ignore.add('QMutableListIterator') types_to_ignore.add('QMapIterator') types_to_ignore.add('QMutableMapIterator') types_to_ignore.add('QSetIterator') types_to_ignore.add('QMutableVectorIterator') types_to_ignore.add('QVectorIterator') # QtGui types_to_ignore.add('QIconEnginePlugin') types_to_ignore.add('QImageIOPlugin') types_to_ignore.add('QGenericPlugin') types_to_ignore.add('QGenericPluginFactory') types_to_ignore.add('QGenericMatrix') types_to_ignore.add('QOpenGLExtraFunctions') types_to_ignore.add('QOpenGLFunctions') types_to_ignore.add('QOpenGLFunctions_1_0') types_to_ignore.add('QOpenGLFunctions_1_1') types_to_ignore.add('QOpenGLFunctions_1_2') types_to_ignore.add('QOpenGLFunctions_1_3') types_to_ignore.add('QOpenGLFunctions_1_4') types_to_ignore.add('QOpenGLFunctions_1_5') types_to_ignore.add('QOpenGLFunctions_2_0') types_to_ignore.add('QOpenGLFunctions_2_1') types_to_ignore.add('QOpenGLFunctions_3_0') types_to_ignore.add('QOpenGLFunctions_3_1') types_to_ignore.add('QOpenGLFunctions_3_2_Compatibility') types_to_ignore.add('QOpenGLFunctions_3_2_Core') types_to_ignore.add('QOpenGLFunctions_3_3_Compatibility') types_to_ignore.add('QOpenGLFunctions_3_3_Core') types_to_ignore.add('QOpenGLFunctions_4_0_Compatibility') types_to_ignore.add('QOpenGLFunctions_4_0_Core') types_to_ignore.add('QOpenGLFunctions_4_1_Compatibility') types_to_ignore.add('QOpenGLFunctions_4_1_Core') types_to_ignore.add('QOpenGLFunctions_4_2_Compatibility') types_to_ignore.add('QOpenGLFunctions_4_2_Core') types_to_ignore.add('QOpenGLFunctions_4_3_Compatibility') types_to_ignore.add('QOpenGLFunctions_4_3_Core') types_to_ignore.add('QOpenGLFunctions_4_4_Compatibility') types_to_ignore.add('QOpenGLFunctions_4_4_Core') types_to_ignore.add('QOpenGLFunctions_4_5_Compatibility') types_to_ignore.add('QOpenGLFunctions_4_5_Core') types_to_ignore.add('QOpenGLFunctions_ES2') # QtWidgets types_to_ignore.add('QItemEditorCreator') types_to_ignore.add('QStandardItemEditorCreator') types_to_ignore.add('QStylePlugin') # QtSql types_to_ignore.add('QSqlDriverCreator') types_to_ignore.add('QSqlDriverPlugin') qt_documentation_website_prefixes = OrderedDict() qt_documentation_website_prefixes['5.6'] = 'http://doc.qt.io/qt-5.6/' qt_documentation_website_prefixes['5.8'] = 'http://doc.qt.io/qt-5.8/' qt_documentation_website_prefixes['5.9'] = 'http://doc.qt.io/qt-5.9/' qt_documentation_website_prefixes['5.10'] = 'http://doc.qt.io/qt-5.10/' qt_documentation_website_prefixes['5.11'] = 'http://doc-snapshots.qt.io/qt5-5.11/' qt_documentation_website_prefixes['dev'] = 'http://doc-snapshots.qt.io/qt5-dev/' def qt_version_to_doc_prefix(version): if version in qt_documentation_website_prefixes: return qt_documentation_website_prefixes[version] else: raise RuntimeError("The specified qt version is not supported") def create_doc_url(module_doc_page_url, version): return qt_version_to_doc_prefix(version) + module_doc_page_url parser = argparse.ArgumentParser() parser.add_argument("module", default='all', choices=list(modules_to_test.keys()).append('all'), nargs='?', type=str, help="the Qt module for which to get the missing types") parser.add_argument("--qt-version", "-v", default='5.11', choices=['5.6', '5.9', '5.11', 'dev'], type=str, dest='version', help="the Qt version to use to check for types") parser.add_argument("--which-missing", "-w", default='all', choices=['all', 'in-pyqt', 'not-in-pyqt'], type=str, dest='which_missing', help="Which missing types to show (all, or just those " "that are not present in PyQt)") args = parser.parse_args() if hasattr(args, "module") and args.module != 'all': saved_value = modules_to_test[args.module] modules_to_test.clear() modules_to_test[args.module] = saved_value pyside_package_name = "PySide2" pyqt_package_name = "PyQt5" total_missing_types_count = 0 total_missing_types_count_compared_to_pyqt = 0 total_missing_modules_count = 0 wiki_file = open('missing_bindings_for_wiki_qt_io.txt', 'w') wiki_file.truncate() def log(*pargs, **kw): print(*pargs) computed_str = '' for arg in pargs: computed_str += str(arg) style = 'text' if 'style' in kw: style = kw['style'] if style == 'heading1': computed_str = '= ' + computed_str + ' =' elif style == 'heading5': computed_str = '===== ' + computed_str + ' =====' elif style == 'with_newline': computed_str += '\n' elif style == 'bold_colon': computed_str = computed_str.replace(':', ":'''") computed_str += "'''" computed_str += '\n' elif style == 'error': computed_str = "''" + computed_str.strip('\n') + "''\n" elif style == 'text_with_link': computed_str = computed_str elif style == 'code': computed_str = ' ' + computed_str elif style == 'end': return print(computed_str, file=wiki_file) log('PySide2 bindings for Qt {}'.format(args.version), style='heading1') log("""Using Qt version {} documentation to find public API Qt types and test " "if the types are present in the PySide2 package.""".format(args.version)) log("""Results are usually stored at https://wiki.qt.io/PySide2_Missing_Bindings so consider taking the contents of the generated missing_bindings_for_wiki_qt_io.txt file and updating the linked wiki page.""", style='end') log("""Similar report: https://gist.github.com/ethanhs/6c626ca4e291f3682589699296377d3a""", style='text_with_link') python_executable = os.path.basename(sys.executable or '') command_line_arguments = ' '.join(sys.argv) report_date = strftime("%Y-%m-%d %H:%M:%S %Z", gmtime()) log(""" This report was generated by running the following command: {} {} on the following date: {} """.format(python_executable, command_line_arguments, report_date)) for module_name in modules_to_test.keys(): log(module_name, style='heading5') url = create_doc_url(modules_to_test[module_name], args.version) log('Documentation link: {}\n'.format(url), style='text_with_link') # Import the tested module try: pyside_tested_module = getattr(__import__(pyside_package_name, fromlist=[module_name]), module_name) except Exception as e: log('\nCould not load {}.{}. Received error: {}. Skipping.\n'.format( pyside_package_name, module_name, str(e).replace("'", '')), style='error') total_missing_modules_count += 1 continue try: pyqt_tested_module = getattr(__import__(pyqt_package_name, fromlist=[module_name]), module_name) except Exception as e: log("\nCould not load {}.{} for comparison. " "Received error: {}.\n".format(pyqt_package_name, module_name, str(e).replace("'", '')), style='error') # Get C++ class list from documentation page. page = urllib2.urlopen(url) soup = BeautifulSoup(page, 'html.parser') # Extract the Qt type names from the documentation classes table links = soup.body.select('.annotated a') types_on_html_page = [] for link in links: link_text = link.text link_text = link_text.replace('::', '.') if link_text not in types_to_ignore: types_on_html_page.append(link_text) log('Number of types in {}: {}'.format(module_name, len(types_on_html_page)), style='bold_colon') missing_types_count = 0 missing_types_compared_to_pyqt = 0 missing_types = [] for qt_type in types_on_html_page: try: pyside_qualified_type = 'pyside_tested_module.' + qt_type o = eval(pyside_qualified_type) except: missing_type = qt_type missing_types_count += 1 total_missing_types_count += 1 is_present_in_pyqt = False try: pyqt_qualified_type = 'pyqt_tested_module.' + qt_type eval(pyqt_qualified_type) missing_type += " (is present in PyQt5)" missing_types_compared_to_pyqt += 1 total_missing_types_count_compared_to_pyqt += 1 is_present_in_pyqt = True except: pass if args.which_missing == 'all': missing_types.append(missing_type) elif args.which_missing == 'in-pyqt' and is_present_in_pyqt: missing_types.append(missing_type) elif (args.which_missing == 'not-in-pyqt' and not is_present_in_pyqt): missing_types.append(missing_type) if len(missing_types) > 0: log('Missing types in {}:'.format(module_name), style='with_newline') missing_types.sort() for missing_type in missing_types: log(missing_type, style='code') log('') log('Number of missing types: {}'.format(missing_types_count), style='bold_colon') if len(missing_types) > 0: log('Number of missing types that are present in PyQt5: {}' .format(missing_types_compared_to_pyqt), style='bold_colon') log('End of missing types for {}\n'.format(module_name), style='end') else: log('', style='end') log('Summary', style='heading5') log('Total number of missing types: {}'.format(total_missing_types_count), style='bold_colon') log('Total number of missing types that are present in PyQt5: {}' .format(total_missing_types_count_compared_to_pyqt), style='bold_colon') log('Total number of missing modules: {}' .format(total_missing_modules_count), style='bold_colon') wiki_file.close()