aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside-tools/metaobjectdump.py
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside-tools/metaobjectdump.py')
-rw-r--r--sources/pyside-tools/metaobjectdump.py110
1 files changed, 79 insertions, 31 deletions
diff --git a/sources/pyside-tools/metaobjectdump.py b/sources/pyside-tools/metaobjectdump.py
index 05a14fdd6..0970f9974 100644
--- a/sources/pyside-tools/metaobjectdump.py
+++ b/sources/pyside-tools/metaobjectdump.py
@@ -8,7 +8,7 @@ import sys
import tokenize
from argparse import ArgumentParser, RawTextHelpFormatter
from pathlib import Path
-from typing import Dict, List, Optional, Tuple, Union
+from typing import Dict, List, Optional, Set, Tuple, Union
DESCRIPTION = """Parses Python source code to create QObject metatype
@@ -27,7 +27,19 @@ QML_IMPORT_MINOR_VERSION = "QML_IMPORT_MINOR_VERSION"
QT_MODULES = "QT_MODULES"
+ITEM_MODELS = ["QAbstractListModel", "QAbstractProxyModel",
+ "QAbstractTableModel", "QConcatenateTablesProxyModel",
+ "QFileSystemModel", "QIdentityProxyModel", "QPdfBookmarkModel",
+ "QPdfSearchModel", "QSortFilterProxyModel", "QSqlQueryModel",
+ "QStandardItemModel", "QStringListModel", "QTransposeProxyModel",
+ "QWebEngineHistoryModel"]
+
+
+QOBJECT_DERIVED = ["QObject", "QQuickItem", "QQuickPaintedItem"] + ITEM_MODELS
+
+
AstDecorator = Union[ast.Name, ast.Call]
+AstPySideTypeSpec = Union[ast.Name, ast.Constant]
ClassList = List[dict]
@@ -35,9 +47,10 @@ ClassList = List[dict]
PropertyEntry = Dict[str, Union[str, int, bool]]
-SignalArgument = Dict[str, str]
-SignalArguments = List[SignalArgument]
-Signal = Dict[str, Union[str, SignalArguments]]
+Argument = Dict[str, str]
+Arguments = List[Argument]
+Signal = Dict[str, Union[str, Arguments]]
+Slot = Dict[str, Union[str, Arguments]]
def _decorator(name: str, value: str) -> Dict[str, str]:
@@ -84,12 +97,42 @@ def _parse_assignment(node: ast.Assign) -> Tuple[Optional[str], Optional[ast.AST
return (None, None)
+def _parse_pyside_type(type_spec: AstPySideTypeSpec) -> str:
+ """Parse type specification of a Slot/Property decorator. Usually a type,
+ but can also be a string constant with a C++ type name."""
+ if isinstance(type_spec, ast.Constant):
+ return type_spec.value
+ return _python_to_cpp_type(_name(type_spec))
+
+
+def _parse_call_args(call: ast.Call):
+ """Parse arguments of a Signal call/Slot decorator (type list)."""
+ result: Arguments = []
+ for n, arg in enumerate(call.args):
+ par_name = f"a{n+1}"
+ par_type = _parse_pyside_type(arg)
+ result.append({"name": par_name, "type": par_type})
+ return result
+
+
+def _parse_slot(func_name: str, call: ast.Call) -> Slot:
+ """Parse a 'Slot' decorator."""
+ return_type = "void"
+ for kwarg in call.keywords:
+ if kwarg.arg == "result":
+ return_type = _python_to_cpp_type(_name(kwarg.value))
+ break
+ return {"access": "public", "name": func_name,
+ "arguments": _parse_call_args(call),
+ "returnType": return_type}
+
+
class VisitorContext:
"""Stores a list of QObject-derived classes encountered in order to find
out which classes inherit QObject."""
def __init__(self):
- self.qobject_derived = ["QObject", "QQuickItem", "QQuickPaintedItem"]
+ self.qobject_derived = QOBJECT_DERIVED
class MetaObjectDumpVisitor(ast.NodeVisitor):
@@ -104,7 +147,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor):
self._properties: List[PropertyEntry] = []
self._signals: List[Signal] = []
self._within_class: bool = False
- self._qt_modules: List[str] = []
+ self._qt_modules: Set[str] = set()
self._qml_import_name = ""
self._qml_import_major_version = 0
self._qml_import_minor_version = 0
@@ -119,7 +162,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor):
return (self._qml_import_major_version, self._qml_import_minor_version)
def qt_modules(self):
- return self._qt_modules
+ return sorted(self._qt_modules)
@staticmethod
def create_ast(filename: Path) -> ast.Module:
@@ -146,6 +189,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor):
"""Visit a class definition"""
self._properties = []
self._signals = []
+ self._slots = []
self._within_class = True
qualified_name = node.name
last_dot = qualified_name.rfind('.')
@@ -158,12 +202,14 @@ class MetaObjectDumpVisitor(ast.NodeVisitor):
q_object = False
bases = []
for b in node.bases:
- base_name = _name(b)
- if base_name in self._context.qobject_derived:
- q_object = True
- self._context.qobject_derived.append(name)
- base_dict = {"access": "public", "name": base_name}
- bases.append(base_dict)
+ # PYSIDE-2202: catch weird constructs like "class C(type(Base)):"
+ if isinstance(b, ast.Name):
+ base_name = _name(b)
+ if base_name in self._context.qobject_derived:
+ q_object = True
+ self._context.qobject_derived.append(name)
+ base_dict = {"access": "public", "name": base_name}
+ bases.append(base_dict)
data["object"] = q_object
if bases:
@@ -188,6 +234,9 @@ class MetaObjectDumpVisitor(ast.NodeVisitor):
if self._signals:
data["signals"] = self._signals
+ if self._slots:
+ data["slots"] = self._slots
+
self._json_class_list.append(data)
self._within_class = False
@@ -224,7 +273,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor):
elif name == "QmlNamedElement" and node.args:
name = node.args[0].value
class_decorators.append(_decorator("QML.Element", name))
- else:
+ elif name.startswith('Q'):
print('Unknown decorator with parameters:', name,
file=sys.stderr)
return
@@ -237,7 +286,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor):
class_decorators.append(_decorator("QML.Singleton", "true"))
elif name == "QmlAnonymous":
class_decorators.append(_decorator("QML.Element", "anonymous"))
- else:
+ elif name.startswith('Q'):
print('Unknown decorator:', name, file=sys.stderr)
return
@@ -269,16 +318,16 @@ class MetaObjectDumpVisitor(ast.NodeVisitor):
return
if isinstance(node, ast.Call):
- name = node.func.id
+ name = _name(node.func)
if name == "Property": # Property getter
- if node.args: # 1st is type
- type = _python_to_cpp_type(_name(node.args[0]))
+ if node.args: # 1st is type/type string
+ type = _parse_pyside_type(node.args[0])
prop = self._create_property_entry(func_name, type,
func_name)
_parse_property_kwargs(node.keywords, prop)
self._properties.append(prop)
elif name == "Slot":
- pass
+ self._slots.append(_parse_slot(func_name, node))
else:
print('Unknown decorator with parameters:', name,
file=sys.stderr)
@@ -290,13 +339,8 @@ class MetaObjectDumpVisitor(ast.NodeVisitor):
return
func_name = _func_name(call)
if func_name == "Signal" or func_name == "QtCore.Signal":
- arguments: SignalArguments = []
- for n, arg in enumerate(call.args):
- par_name = f"a{n+1}"
- par_type = _python_to_cpp_type(_name(arg))
- arguments.append({"name": par_name, "type": par_type})
signal: Signal = {"access": "public", "name": var_name,
- "arguments": arguments,
+ "arguments": _parse_call_args(call),
"returnType": "void"}
self._signals.append(signal)
elif func_name == "Property" or func_name == "QtCore.Property":
@@ -313,16 +357,20 @@ class MetaObjectDumpVisitor(ast.NodeVisitor):
self._properties.append(prop)
def visit_Import(self, node):
- if node.names:
- self._handle_import(node.names[0].name)
+ for n in node.names: # "import PySide6.QtWidgets"
+ self._handle_import(n.name)
def visit_ImportFrom(self, node):
- self._handle_import(node.module)
+ if "." in node.module: # "from PySide6.QtWidgets import QWidget"
+ self._handle_import(node.module)
+ elif node.module == "PySide6": # "from PySide6 import QtWidgets"
+ for n in node.names:
+ if n.name.startswith("Qt"):
+ self._qt_modules.add(n.name)
def _handle_import(self, mod: str):
- if mod.startswith('PySide'):
- dot = mod.index(".")
- self._qt_modules.append(mod[dot + 1:])
+ if mod.startswith("PySide6."):
+ self._qt_modules.add(mod[8:])
def create_arg_parser(desc: str) -> ArgumentParser: