diff options
12 files changed, 367 insertions, 11 deletions
diff --git a/sources/pyside2/PySide2/CMakeLists.txt b/sources/pyside2/PySide2/CMakeLists.txt index a101538e3..bceaa3bfa 100644 --- a/sources/pyside2/PySide2/CMakeLists.txt +++ b/sources/pyside2/PySide2/CMakeLists.txt @@ -8,12 +8,16 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/__init__.py.in" "${CMAKE_CURRENT_BINARY_DIR}/__init__.py" @ONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/__init__.py" "${CMAKE_CURRENT_BINARY_DIR}/support/__init__.py" COPYONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/generate_pyi.py" + "${CMAKE_CURRENT_BINARY_DIR}/support/generate_pyi.py" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/__init__.py" "${CMAKE_CURRENT_BINARY_DIR}/support/signature/__init__.py" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/layout.py" "${CMAKE_CURRENT_BINARY_DIR}/support/signature/layout.py" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/mapping.py" "${CMAKE_CURRENT_BINARY_DIR}/support/signature/mapping.py" COPYONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/typing.py" + "${CMAKE_CURRENT_BINARY_DIR}/support/signature/typing.py" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/lib/__init__.py" "${CMAKE_CURRENT_BINARY_DIR}/support/signature/lib/__init__.py" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/lib/enum_sig.py" diff --git a/sources/pyside2/PySide2/support/generate_pyi.py b/sources/pyside2/PySide2/support/generate_pyi.py new file mode 100644 index 000000000..312b1418a --- /dev/null +++ b/sources/pyside2/PySide2/support/generate_pyi.py @@ -0,0 +1,239 @@ +############################################################################# +## +## Copyright (C) 2018 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$ +## +############################################################################# + +from __future__ import print_function, absolute_import, unicode_literals + +""" +generate_pyi.py + +This script generates the .pyi files for all PySide modules. +""" + +import sys +import os +import io +import re +import PySide2 +import subprocess +import argparse +from contextlib import contextmanager +from textwrap import dedent + +from PySide2.support.signature import inspect +from PySide2.support.signature.lib.enum_sig import HintingEnumerator +from PySide2 import * # all modules + +# Make sure not to get .pyc in Python2. +sourcepath = os.path.splitext(__file__)[0] + ".py" + +# Can we use forward references? +USE_PEP563 = sys.version_info[:2] >= (3, 7) + +indent = " " * 4 + +class Writer(object): + def __init__(self, outfile): + self.outfile = outfile + + def print(self, *args, **kw): + if self.outfile: + if args == (): + # This is a Python 2.7 glitch. + print("", file=self.outfile, **kw) + else: + print(*args, file=self.outfile, **kw) + + +class Formatter(Writer): + """ + Formatter is formatting the signature listing of an enumerator. + + It is written as context managers in order to avoid many callbacks. + The separation in formatter and enumerator is done to keep the + unrelated tasks of enumeration and formatting apart. + """ + @contextmanager + def module(self, mod_name): + self.mod_name = mod_name + self.print("# Module", mod_name) + self.print("import shiboken2 as Shiboken") + from PySide2.support.signature import typing + typing_str = "from PySide2.support.signature import typing" + self.print(typing_str) + self.print() + self.print("class Object(object): pass") + self.print() + self.print("Shiboken.Object = Object") + self.print() + # This line will be replaced by the missing imports. + self.print("IMPORTS") + yield + + @contextmanager + def klass(self, class_name, class_str): + self.class_name = class_name + spaces = "" + while "." in class_name: + spaces += indent + class_name = class_name.split(".", 1)[-1] + class_str = class_str.split(".", 1)[-1] + self.print() + if not spaces: + self.print() + here = self.outfile.tell() + self.print("{spaces}class {class_str}:".format(**locals())) + self.print() + pos = self.outfile.tell() + self.spaces = spaces + yield + if pos == self.outfile.tell(): + # we have not written any function + self.outfile.seek(here) + self.outfile.truncate() + # Note: we cannot use class_str when we have no body. + self.print("{spaces}class {class_name}: ...".format(**locals())) + if "<" in class_name: + # This is happening in QtQuick for some reason: + ## class QSharedPointer<QQuickItemGrabResult >: + # We simply skip over this class. + self.outfile.seek(here) + self.outfile.truncate() + + @contextmanager + def function(self, func_name, signature): + key = func_name + spaces = indent + self.spaces if self.class_name else "" + if type(signature) == type([]): + for sig in signature: + self.print('{spaces}@typing.overload'.format(**locals())) + self._function(func_name, sig, spaces) + else: + self._function(func_name, signature, spaces) + yield key + + def _function(self, func_name, signature, spaces): + # this would be nicer to get somehow together with the signature + is_meth = re.match("\((\w*)", str(signature)).group(1) == "self" + if self.class_name and not is_meth: + self.print('{spaces}@staticmethod'.format(**locals())) + self.print('{spaces}def {func_name}{signature}: ...'.format(**locals())) + + +def get_license_text(): + with io.open(sourcepath) as f: + lines = f.readlines() + license_line = next((lno for lno, line in enumerate(lines) + if "$QT_END_LICENSE$" in line)) + return "".join(lines[:license_line + 3]) + + +def find_imports(text): + return [imp for imp in PySide2.__all__ if imp + "." in text] + + +def generate_pyi(mod_name, outpath): + module = sys.modules[mod_name] + mod_fullname = module.__file__ + plainname = os.path.basename(mod_fullname).split(".")[0] + if not outpath: + outpath = os.path.dirname(mod_fullname) + outfilepath = os.path.join(outpath, plainname + ".pyi") + outfile = io.StringIO() + fmt = Formatter(outfile) + enu = HintingEnumerator(fmt) + fmt.print(get_license_text()) + need_imports = not USE_PEP563 + if USE_PEP563: + fmt.print("from __future__ import annotations") + fmt.print() + fmt.print(dedent('''\ + """ + This file contains the exact signatures for all functions in PySide + for module '{mod_fullname}', + except for defaults which are replaced by "...". + """ + '''.format(**locals()))) + enu.module(mod_name) + fmt.print("# eof") + with io.open(outfilepath, "w") as realfile: + wr = Writer(realfile) + outfile.seek(0) + while True: + line = outfile.readline() + if not line: + break + line = line.rstrip() + # we remove the IMPORTS marker and insert imports if needed + if line == "IMPORTS": + if need_imports: + for imp in find_imports(outfile.getvalue()): + imp = "PySide2." + imp + if imp != mod_name: + wr.print("import " + imp) + wr.print("import " + mod_name) + wr.print() + wr.print() + else: + wr.print(line) + + print(outfilepath, file=sys.stderr) + if sys.version_info[0] == 3: + # Python 3: We can check the file directly if the syntax is ok. + subprocess.check_output([sys.executable, outfilepath]) + + +def generate_all_pyi(outpath=None): + for mod_name in PySide2.__all__: + generate_pyi("PySide2." + mod_name, outpath) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="This script generates the .pyi file for all PySide modules.") + subparsers = parser.add_subparsers(dest="command", metavar="", title="required argument") + # create the parser for the "run" command + parser_run = subparsers.add_parser("run", help="run the generation") + parser_run.add_argument("--outpath") + args = parser.parse_args() + outpath = args.outpath + if not outpath or os.path.exists(outpath): + generate_all_pyi(args.outpath) + else: + print("Please create the directory {outpath} before generating.".format(**locals())) + diff --git a/sources/pyside2/PySide2/support/signature/__init__.py b/sources/pyside2/PySide2/support/signature/__init__.py index d587a4d62..5a87a814a 100644 --- a/sources/pyside2/PySide2/support/signature/__init__.py +++ b/sources/pyside2/PySide2/support/signature/__init__.py @@ -41,6 +41,6 @@ from __future__ import print_function, absolute_import # Trigger initialization phase 2. _ = type.__signature__ -from signature_loader import get_signature, inspect +from signature_loader import get_signature, inspect, typing -__all__ = "get_signature inspect layout mapping lib".split() +__all__ = "get_signature inspect typing layout mapping lib".split() diff --git a/sources/pyside2/PySide2/support/signature/mapping.py b/sources/pyside2/PySide2/support/signature/mapping.py index 15727cb6a..cf5857c65 100644 --- a/sources/pyside2/PySide2/support/signature/mapping.py +++ b/sources/pyside2/PySide2/support/signature/mapping.py @@ -218,6 +218,7 @@ def init_QtCore(): def init_QtGui(): import PySide2.QtGui + from PySide2.QtGui import QPageLayout, QPageSize # 5.12 macOS type_map.update({ "QVector< QTextLayout.FormatRange >()": [], # do we need more structure? "USHRT_MAX": ushort_max, @@ -398,4 +399,16 @@ def init_QtWinExtras(): }) return locals() +# from 5.12, macOS +def init_QtDataVisualization(): + from PySide2.QtDataVisualization import QtDataVisualization + QtDataVisualization.QBarDataRow = typing.List[QtDataVisualization.QBarDataItem] + QtDataVisualization.QBarDataArray = typing.List[QtDataVisualization.QBarDataRow] + QtDataVisualization.QSurfaceDataRow = typing.List[QtDataVisualization.QSurfaceDataItem] + QtDataVisualization.QSurfaceDataArray = typing.List[QtDataVisualization.QSurfaceDataRow] + type_map.update({ + "100.0f": 100.0, + }) + return locals() + # end of file diff --git a/sources/pyside2/PySide2/support/signature/typing.py b/sources/pyside2/PySide2/support/signature/typing.py new file mode 100644 index 000000000..dd52a2c45 --- /dev/null +++ b/sources/pyside2/PySide2/support/signature/typing.py @@ -0,0 +1,42 @@ +############################################################################# +## +## Copyright (C) 2018 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$ +## +############################################################################# + +from __future__ import print_function, absolute_import + +from signature_loader.typing import * diff --git a/sources/shiboken2/shibokenmodule/support/signature/__init__.py b/sources/shiboken2/shibokenmodule/support/signature/__init__.py index f639f1ad0..253ba98dc 100644 --- a/sources/shiboken2/shibokenmodule/support/signature/__init__.py +++ b/sources/shiboken2/shibokenmodule/support/signature/__init__.py @@ -41,4 +41,4 @@ from __future__ import print_function, absolute_import # Trigger initialization phase 2. _ = type.__signature__ -from signature_loader import get_signature, inspect +from signature_loader import get_signature, inspect, typing diff --git a/sources/shiboken2/shibokenmodule/support/signature/backport_inspect.py b/sources/shiboken2/shibokenmodule/support/signature/backport_inspect.py index 0eafe9caa..6b97470e2 100644 --- a/sources/shiboken2/shibokenmodule/support/signature/backport_inspect.py +++ b/sources/shiboken2/shibokenmodule/support/signature/backport_inspect.py @@ -113,8 +113,8 @@ CO_NOFREE = 0x0040 # We use '__builtin__' and '__name__' instead. # It is further changed because we use a local copy of typing def formatannotation(annotation, base_module=None): - if getattr(annotation, '__module__', None) == 'PySide2.support.signature.typing': - return repr(annotation).replace('PySide2.support.signature.typing.', '') + if getattr(annotation, '__module__', None) == 'support.signature.typing': + return repr(annotation).replace('support.signature.typing', 'typing') if isinstance(annotation, type): if annotation.__module__ in ('__builtin__', base_module): return annotation.__name__ diff --git a/sources/shiboken2/shibokenmodule/support/signature/layout.py b/sources/shiboken2/shibokenmodule/support/signature/layout.py index 47f97fc2b..cd3a5dc8f 100644 --- a/sources/shiboken2/shibokenmodule/support/signature/layout.py +++ b/sources/shiboken2/shibokenmodule/support/signature/layout.py @@ -57,6 +57,7 @@ used literally as strings like "signature", "existence", etc. from textwrap import dedent from signature_loader import inspect +from signature_loader.mapping import ellipsis class SimpleNamespace(object): @@ -223,7 +224,7 @@ def create_signature(props, key): if not layout.defaults: defaults = () if layout.ellipsis: - defaults = ("...",) * len(defaults) + defaults = (ellipsis,) * len(defaults) annotations = props["annotations"].copy() if not layout.return_annotation and "return" in annotations: del annotations["return"] diff --git a/sources/shiboken2/shibokenmodule/support/signature/lib/enum_sig.py b/sources/shiboken2/shibokenmodule/support/signature/lib/enum_sig.py index 2ec14d62b..f79f3266a 100644 --- a/sources/shiboken2/shibokenmodule/support/signature/lib/enum_sig.py +++ b/sources/shiboken2/shibokenmodule/support/signature/lib/enum_sig.py @@ -39,6 +39,16 @@ from __future__ import print_function, absolute_import +""" +enum_sig.py + +Enumerate all signatures of a class. + +This module separates the enumeration process from the formatting. +It is not easy to adhere to this protocol, but in the end, it paid off +by producing a lot of clarity. +""" + import sys from signature_loader import get_signature, inspect @@ -51,6 +61,7 @@ class ExactEnumerator(object): An appropriate formatter should be supplied, if printable output is desired. """ + def __init__(self, formatter, result_type=dict): self.fmt = formatter self.result_type = result_type @@ -91,7 +102,8 @@ class ExactEnumerator(object): subclass_name = ".".join((class_name, thing_name)) subclasses.append((subclass_name, thing)) else: - ret.update(self.function(thing_name, thing)) + func_name = thing_name.split(".")[0] # remove ".overload" + ret.update(self.function(func_name, thing)) for subclass_name, subclass in subclasses: ret.update(self.klass(subclass_name, subclass)) return ret @@ -101,7 +113,7 @@ class ExactEnumerator(object): signature = getattr(func, '__signature__', None) if signature is not None: with self.fmt.function(func_name, signature) as key: - ret[key] = str(signature) + ret[key] = signature return ret @@ -133,3 +145,20 @@ class SimplifyingEnumerator(ExactEnumerator): with self.fmt.function(func_name, sig) as key: ret[key] = sig return ret + +class HintingEnumerator(ExactEnumerator): + """ + HintingEnumerator enumerates all signatures in a module slightly changed. + + This class is used for generating complete listings of all signatures for + hinting stubs. Only default values are replaced by "...". + """ + + def function(self, func_name, func): + ret = self.result_type() + signature = get_signature(func, 'hintingstub') + if signature is not None: + with self.fmt.function(func_name, signature) as key: + ret[key] = signature + return ret + diff --git a/sources/shiboken2/shibokenmodule/support/signature/loader.py b/sources/shiboken2/shibokenmodule/support/signature/loader.py index 83493e511..170fb0a2a 100644 --- a/sources/shiboken2/shibokenmodule/support/signature/loader.py +++ b/sources/shiboken2/shibokenmodule/support/signature/loader.py @@ -59,7 +59,6 @@ import os import traceback import types from contextlib import contextmanager -from distutils.sysconfig import get_python_lib """ A note on the import problem (solved): @@ -111,6 +110,30 @@ def ensure_import_support(): sys.path.pop(0) +# patching inspect's formatting to keep the word "typing": +def formatannotation(annotation, base_module=None): + # if getattr(annotation, '__module__', None) == 'typing': + # return repr(annotation).replace('typing.', '') + if isinstance(annotation, type): + if annotation.__module__ in ('builtins', base_module): + return annotation.__qualname__ + return annotation.__module__+'.'+annotation.__qualname__ + return repr(annotation) + +# patching __repr__ to disable the __repr__ of typing.TypeVar: +""" + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ +""" +def _typevar__repr__(self): + return "typing." + self.__name__ + with ensure_import_support(): # We store all needed modules in signature_loader. # This way, they are always accessible. @@ -119,6 +142,7 @@ with ensure_import_support(): if sys.version_info >= (3,): import typing import inspect + inspect.formatannotation = formatannotation else: import inspect namespace = inspect.__dict__ @@ -129,6 +153,7 @@ with ensure_import_support(): inspect.__doc__ += _doc # force inspect to find all attributes. See "heuristic" in pydoc.py! inspect.__all__ = list(x for x in dir(inspect) if not x.startswith("_")) + typing.TypeVar.__repr__ = _typevar__repr__ def put_into_loader_package(module, loader=signature_loader): # Note: the "with" statement hides that we are no longer in a diff --git a/sources/shiboken2/shibokenmodule/support/signature/mapping.py b/sources/shiboken2/shibokenmodule/support/signature/mapping.py index 724b0c751..bca1ce307 100644 --- a/sources/shiboken2/shibokenmodule/support/signature/mapping.py +++ b/sources/shiboken2/shibokenmodule/support/signature/mapping.py @@ -56,7 +56,10 @@ import pkgutil from signature_loader import typing -ellipsis = "..." +class ellipsis(object): + def __repr__(self): + return "..." +ellipsis = ellipsis() Char = typing.Union[str, int] # how do I model the limitation to 1 char? StringList = typing.List[str] IntList = typing.List[int] diff --git a/sources/shiboken2/shibokenmodule/support/signature/parser.py b/sources/shiboken2/shibokenmodule/support/signature/parser.py index 82cd9aab3..4bb1bf234 100644 --- a/sources/shiboken2/shibokenmodule/support/signature/parser.py +++ b/sources/shiboken2/shibokenmodule/support/signature/parser.py @@ -240,7 +240,7 @@ def fixup_multilines(sig_str): if idx > 0: continue # remove duplicates - multi_lines = list(set(multi_lines)) + multi_lines = sorted(set(multi_lines)) # renumber or return a single line nmulti = len(multi_lines) if nmulti > 1: |