diff options
Diffstat (limited to 'tools/missing_bindings.py')
-rw-r--r-- | tools/missing_bindings.py | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/tools/missing_bindings.py b/tools/missing_bindings.py new file mode 100644 index 000000000..8f3c0b808 --- /dev/null +++ b/tools/missing_bindings.py @@ -0,0 +1,448 @@ +############################################################################# +## +## 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'] = 'qtcharts-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.qt.io/qt-5.11/' +qt_documentation_website_prefixes['5.11'] = 'http://doc.qt.io/qt-5.11/' +qt_documentation_website_prefixes['5.12'] = 'http://doc.qt.io/qt-5.12/' +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.12', + choices=['5.6', '5.9', '5.11', '5.12', '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_module_name = module_name + if module_name == "QtCharts": + pyqt_module_name = module_name[:-1] + + pyqt_tested_module = getattr(__import__(pyqt_package_name, + fromlist=[pyqt_module_name]), pyqt_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.' + + if "QtCharts" == module_name: + pyside_qualified_type += 'QtCharts.' + elif "DataVisualization" in module_name: + pyside_qualified_type += 'QtDataVisualization.' + + pyside_qualified_type += qt_type + 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.' + + if "Charts" in module_name: + pyqt_qualified_type += 'QtCharts.' + elif "DataVisualization" in module_name: + pyqt_qualified_type += 'QtDataVisualization.' + + pyqt_qualified_type += 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() |