aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Tismer <tismer@stackless.com>2021-05-17 16:48:17 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2021-05-21 10:16:30 +0000
commita78567ab592bd2f4b87c1e2870335f4ec20675a6 (patch)
tree7211d2cd627cb37fb4546e53d09e09a08687abd3
parent147d5a7e06c5827e5ccedf03b55d9e2edb7d3e67 (diff)
__feature__: Support generation of modified .pyi files
We add a possibility to select features before generating the pyi files. This will improve the feature adoption, because common IDEs can use these changed .pyi files. - avoid crashes because of early selection - add an option to generate_pyi - document pyside6-genpyi and shiboken6-genpyi - build a correct display of properties Task-number: PYSIDE-1019 Change-Id: Ib75dfcbaccaa969ef47eeb142d9c034a2a6c11d6 Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io> (cherry picked from commit d84d5859906fd4331d5d7814815bb58d32c988a6) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--sources/pyside6/PySide6/support/generate_pyi.py8
-rw-r--r--sources/pyside6/doc/feature-why.rst17
-rw-r--r--sources/shiboken6/libshiboken/basewrapper.cpp18
-rw-r--r--sources/shiboken6/libshiboken/signature/signature_doc.rst13
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py39
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py60
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py14
-rw-r--r--sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py2
8 files changed, 142 insertions, 29 deletions
diff --git a/sources/pyside6/PySide6/support/generate_pyi.py b/sources/pyside6/PySide6/support/generate_pyi.py
index 74ef2acd7..7e1c52ddd 100644
--- a/sources/pyside6/PySide6/support/generate_pyi.py
+++ b/sources/pyside6/PySide6/support/generate_pyi.py
@@ -84,6 +84,8 @@ def generate_all_pyi(outpath, options):
# Perhaps this can be automated?
PySide6.support.signature.mapping.USE_PEP563 = USE_PEP563
+ import __feature__ as feature
+
outpath = Path(outpath) if outpath and os.fspath(outpath) else Path(PySide6.__file__).parent
name_list = PySide6.__all__ if options.modules == ["all"] else options.modules
errors = ", ".join(set(name_list) - set(PySide6.__all__))
@@ -97,7 +99,9 @@ def generate_all_pyi(outpath, options):
name_list = [quirk1, quirk2]
for mod_name in name_list:
import_name = "PySide6." + mod_name
- generate_pyi(import_name, outpath, options)
+ feature_id = feature.get_select_id(options.feature)
+ with feature.force_selection(feature_id, import_name):
+ generate_pyi(import_name, outpath, options)
if __name__ == "__main__":
@@ -111,6 +115,8 @@ if __name__ == "__main__":
help="the output directory (default = binary location)")
parser.add_argument("--sys-path", nargs="+",
help="a list of strings prepended to sys.path")
+ parser.add_argument("--feature", nargs="+", choices=["snake_case", "true_property"], default=[],
+ help="""a list of feature names. Example: `--feature snake_case true_property`""")
options = parser.parse_args()
if options.quiet:
logger.setLevel(logging.WARNING)
diff --git a/sources/pyside6/doc/feature-why.rst b/sources/pyside6/doc/feature-why.rst
index 3f11c6131..b95cb77ac 100644
--- a/sources/pyside6/doc/feature-why.rst
+++ b/sources/pyside6/doc/feature-why.rst
@@ -244,4 +244,21 @@ see the Python documentation on `Import-Hooks`_.
If you would like to modify ``__import__`` anyway without destroying the features,
please override just the ``__orig_import__`` function.
+
+IDEs and Modifying Python stub files
+------------------------------------
+
+|project| comes with pre-generated ``.pyi`` stub files in the same location as
+the binary module. For instance, in the site-packages directory, you can find
+a ``QtCore.pyi`` file next to ``QtCore.abi3.so`` or ``QtCore.pyd`` on Windows.
+
+When using ``__feature__`` often with common IDEs, you may want to provide
+a feature-aware version of ``.pyi`` files to get a correct display. The simplest
+way to change them all in-place is the command
+
+.. code-block:: python
+
+ pyside6-genpyi all --feature snake_case true_property
+
+
.. _`Import-Hooks`: https://docs.python.org/3/reference/import.html#import-hooks
diff --git a/sources/shiboken6/libshiboken/basewrapper.cpp b/sources/shiboken6/libshiboken/basewrapper.cpp
index a467abbc6..7cb3f25b9 100644
--- a/sources/shiboken6/libshiboken/basewrapper.cpp
+++ b/sources/shiboken6/libshiboken/basewrapper.cpp
@@ -557,17 +557,29 @@ int SbkObjectType_GetReserved(PyTypeObject *type)
void SbkObjectType_SetReserved(PyTypeObject *type, int value)
{
- PepType_SOTP(reinterpret_cast<SbkObjectType *>(type))->pyside_reserved_bits = value;
+ auto ptr = PepType_SOTP(reinterpret_cast<SbkObjectType *>(type));
+ // PYSIDE-1019: During import PepType_SOTP is still zero.
+ if (ptr == nullptr)
+ return;
+ ptr->pyside_reserved_bits = value;
}
const char **SbkObjectType_GetPropertyStrings(PyTypeObject *type)
{
- return PepType_SOTP(type)->propertyStrings;
+ auto ptr = PepType_SOTP(reinterpret_cast<SbkObjectType *>(type));
+ // PYSIDE-1019: During import PepType_SOTP is still zero.
+ if (ptr == nullptr)
+ return nullptr;
+ return ptr->propertyStrings;
}
void SbkObjectType_SetPropertyStrings(PyTypeObject *type, const char **strings)
{
- PepType_SOTP(reinterpret_cast<SbkObjectType *>(type))->propertyStrings = strings;
+ auto ptr = PepType_SOTP(reinterpret_cast<SbkObjectType *>(type));
+ // PYSIDE-1019: During import PepType_SOTP is still zero.
+ if (ptr == nullptr)
+ return;
+ ptr->propertyStrings = strings;
}
//
diff --git a/sources/shiboken6/libshiboken/signature/signature_doc.rst b/sources/shiboken6/libshiboken/signature/signature_doc.rst
index 70fbda714..0fb26ae52 100644
--- a/sources/shiboken6/libshiboken/signature/signature_doc.rst
+++ b/sources/shiboken6/libshiboken/signature/signature_doc.rst
@@ -180,7 +180,7 @@ layout.py
As more applications used the signature module, different formatting of signatures
was needed. To support that, we created the function ``create_signature``, which
-has a parameter to choose from some prefefined layouts.
+has a parameter to choose from some predefined layouts.
*typing27.py*
@@ -326,6 +326,15 @@ not only signatures but also constants and enums of all PySide modules.
This serves as an extra challenge that has a very positive effect on
the completeness and correctness of signatures.
+The module has a ``--feature`` option to generate modified .pyi files.
+A shortcut for this command is ``pyside6-genpyi``.
+
+A useful command to change all .pyi files to use all features is
+
+.. code-block:: python
+
+ pyside6-genpyi all --feature snake_case true_property
+
pyi_generator.py
----------------
@@ -334,6 +343,8 @@ pyi_generator.py
has been extracted from ``generate_pyi.py``. It allows the generation of ``.pyi``
files from arbitrary extension modules created with shiboken.
+A shortcut for this command is ``shiboken6-genpyi``.
+
Current Extensions
------------------
diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py
index b6d6657fe..8776c7de9 100644
--- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py
+++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/feature.py
@@ -55,6 +55,7 @@ The select id `-1` has the spectial meaning "ignore this module".
"""
import sys
+from contextlib import contextmanager
all_feature_names = [
"snake_case",
@@ -128,12 +129,7 @@ def feature_import(name, *args, **kwargs):
# This is an `import from` statement that corresponds to `IMPORT_NAME`.
# The following `IMPORT_FROM` will handle errors. (Confusing, ofc.)
- flag = 0
- for feature in args[2]:
- if feature in _really_all_feature_names:
- flag |= globals()[feature]
- else:
- raise SyntaxError(f"PySide feature {feature} is not defined")
+ flag = get_select_id(args[2])
flag |= existing & 255 if isinstance(existing, int) and existing >= 0 else 0
pyside_feature_dict[importing_module] = flag
@@ -198,4 +194,35 @@ def _current_selection(flag):
names.append(name)
return names
+
+def get_select_id(feature_names):
+ flag = 0
+ for feature in feature_names:
+ if feature in _really_all_feature_names:
+ flag |= globals()[feature]
+ else:
+ raise SyntaxError(f"PySide feature {feature} is not defined")
+ return flag
+
+
+@contextmanager
+def force_selection(select_id, mod_name):
+ """
+ This function is for generating pyi files with features.
+ The selection id is set globally after performing the unswitched
+ import.
+
+ """
+ __init__()
+ saved_feature_dict = pyside_feature_dict.copy()
+ for name in pyside_feature_dict:
+ set_selection(0, name)
+ __import__(mod_name)
+ for name in pyside_feature_dict.copy():
+ set_selection(select_id, name)
+ try:
+ yield
+ finally:
+ pyside_feature_dict.update(saved_feature_dict)
+
#eof
diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py
index a71ed63f9..c9c741261 100644
--- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py
+++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py
@@ -50,7 +50,9 @@ by producing a lot of clarity.
import inspect
import sys
import types
+import typing
from shibokensupport.signature import get_signature as get_sig
+from shibokensupport.signature.layout import create_signature
class ExactEnumerator(object):
@@ -128,6 +130,7 @@ class ExactEnumerator(object):
subclasses = []
functions = []
enums = []
+ properties = []
for thing_name, thing in class_members:
if inspect.isclass(thing):
@@ -135,16 +138,20 @@ class ExactEnumerator(object):
subclasses.append((subclass_name, thing))
elif inspect.isroutine(thing):
func_name = thing_name.split(".")[0] # remove ".overload"
- signature = getattr(thing, "__signature__", None)
- if signature is not None:
- functions.append((func_name, thing))
+ functions.append((func_name, thing))
elif type(type(thing)) is EnumMeta:
# take the real enum name, not what is in the dict
enums.append((thing_name, type(thing).__qualname__, thing))
+ elif isinstance(thing, property):
+ properties.append((thing_name, thing))
+
init_signature = getattr(klass, "__signature__", None)
enums.sort(key=lambda tup: tup[1 : 3]) # sort by class then enum value
self.fmt.have_body = bool(subclasses or functions or enums or init_signature)
+ # We want to handle functions and properties together.
+ func_prop = sorted(functions + properties)
+
with self.fmt.klass(class_name, class_str):
self.fmt.level += 1
self.fmt.class_name = class_name
@@ -161,11 +168,14 @@ class ExactEnumerator(object):
if len(subclasses):
self.section()
ret.update(self.function("__init__", klass))
- for func_name, func in functions:
+ for func_name, func in func_prop:
if func_name != "__init__":
- ret.update(self.function(func_name, func))
+ if isinstance(func, property):
+ ret.update(self.fproperty(func_name, func))
+ else:
+ ret.update(self.function(func_name, func))
self.fmt.level -= 1
- if len(functions):
+ if len(func_prop):
self.section()
return ret
@@ -173,16 +183,24 @@ class ExactEnumerator(object):
def get_signature(func):
return func.__signature__
- def function(self, func_name, func):
+ def function(self, func_name, func, decorator=None):
self.func = func # for is_method()
ret = self.result_type()
- signature = self.get_signature(func)
+ signature = self.get_signature(func, decorator)
if signature is not None:
- with self.fmt.function(func_name, signature) as key:
+ with self.fmt.function(func_name, signature, decorator) as key:
ret[key] = signature
del self.func
return ret
+ def fproperty(self, prop_name, prop):
+ ret = self.function(prop_name, prop.fget, type(prop).__qualname__)
+ if prop.fset:
+ ret.update(self.function(prop_name, prop.fset, f"{prop_name}.setter"))
+ if prop.fdel:
+ ret.update(self.function(prop_name, prop.fdel, f"{prop_name}.deleter"))
+ return ret
+
def stringify(signature):
if isinstance(signature, list):
@@ -222,6 +240,24 @@ class HintingEnumerator(ExactEnumerator):
hinting stubs. Only default values are replaced by "...".
"""
- @staticmethod
- def get_signature(func):
- return get_sig(func, "hintingstub")
+ def __init__(self, *args, **kwds):
+ super().__init__(*args, **kwds)
+ # We need to provide default signatures for class properties.
+ cls_param = inspect.Parameter("cls", inspect._POSITIONAL_OR_KEYWORD)
+ set_param = inspect.Parameter("arg_1", inspect._POSITIONAL_OR_KEYWORD, annotation=object)
+ self.getter_sig = inspect.Signature([cls_param], return_annotation=object)
+ self.setter_sig = inspect.Signature([cls_param, set_param])
+ self.deleter_sig = inspect.Signature([cls_param])
+
+ def get_signature(self, func, decorator=None):
+ # Class properties don't have signature support (yet).
+ # In that case, produce a fake one.
+ sig = get_sig(func, "hintingstub")
+ if decorator and not sig:
+ if decorator.endswith(".setter"):
+ sig = self.setter_sig
+ elif decorator.endswith(".deleter"):
+ sig = self.deleter_sig
+ else:
+ sig = self.getter_sig
+ return sig
diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py
index e95b6e959..4adf2fb01 100644
--- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py
+++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py
@@ -142,6 +142,7 @@ class Formatter(Writer):
def module(self, mod_name):
self.mod_name = mod_name
support = "PySide6.support" if self.options._pyside_call else "shibokensupport"
+ extra = "from PySide6 import PyClassProperty" if self.options._pyside_call else ""
txt = f"""\
# Module `{mod_name}`
@@ -150,6 +151,7 @@ class Formatter(Writer):
from shiboken6 import Shiboken
from {support}.signature.mapping import (
Virtual, Missing, Invalid, Default, Instance)
+ {extra}
"""
self.print(dedent(txt))
# This line will be replaced by the missing imports postprocess.
@@ -170,7 +172,7 @@ class Formatter(Writer):
yield
@contextmanager
- def function(self, func_name, signature):
+ def function(self, func_name, signature, decorator=None):
if func_name == "__init__":
self.print()
key = func_name
@@ -180,14 +182,17 @@ class Formatter(Writer):
self.print(f'{spaces}@typing.overload')
self._function(func_name, sig, spaces)
else:
- self._function(func_name, signature, spaces)
+ self._function(func_name, signature, spaces, decorator)
if func_name == "__init__":
self.print()
yield key
- def _function(self, func_name, signature, spaces):
+ def _function(self, func_name, signature, spaces, decorator=None):
+ if decorator:
+ self.print(f'{spaces}@{decorator}')
if self.is_method() and "self" not in signature.parameters:
- self.print(f'{spaces}@staticmethod')
+ kind = "class" if "cls" in signature.parameters else "static"
+ self.print(f'{spaces}@{kind}method')
signature = self.optional_replacer(signature)
self.print(f'{spaces}def {func_name}{signature}: ...')
@@ -295,7 +300,6 @@ def generate_pyi(import_name, outpath, options):
subprocess.check_output([sys.executable, outfilepath])
-
if __name__ == "__main__":
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
index 9ba40679e..f4b3272b5 100644
--- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
+++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
@@ -232,6 +232,7 @@ type_map.update({
"int": int,
"List": ArrayLikeVariable,
"long": int,
+ "nullptr": None,
"PyCallable": typing.Callable,
"PyObject": object,
"PyArrayObject": ArrayLikeVariable, # numpy
@@ -386,7 +387,6 @@ def init_sample():
"Foo.HANDLE": int,
"HANDLE": int,
"Null": None,
- "nullptr": None,
"ObjectType.Identifier": Missing("sample.ObjectType.Identifier"),
"OddBool": bool,
"PStr": str,