mirror of
https://github.com/wxWidgets/Phoenix.git
synced 2025-07-21 12:41:10 +02:00
Implement hinting for types that are automatically converted
This handles any type with a defined `.convertFromPyObject` set in its sip generator.
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
import etgtools
|
||||
import etgtools.tweaker_tools as tools
|
||||
from etgtools import MethodDef
|
||||
import etgtools.tweaker_tools
|
||||
|
||||
PACKAGE = "wx"
|
||||
MODULE = "_core"
|
||||
@@ -43,7 +44,9 @@ def run():
|
||||
|
||||
# Allow on-the-fly creation of a wx.BitmapBundle from a wx.Bitmap, wx.Icon
|
||||
# or a wx.Image
|
||||
c.convertFromPyObject = """\
|
||||
c.convertFromPyObject = etgtools.tweaker_tools.AutoConversionInfo(
|
||||
('wx.Bitmap', 'wx.Icon', ),
|
||||
"""\
|
||||
// Check for type compatibility
|
||||
if (!sipIsErr) {
|
||||
if (sipCanConvertToType(sipPy, sipType_wxBitmap, SIP_NO_CONVERTORS))
|
||||
@@ -86,7 +89,7 @@ def run():
|
||||
*sipCppPtr = reinterpret_cast<wxBitmapBundle*>(
|
||||
sipConvertToType(sipPy, sipType_wxBitmapBundle, sipTransferObj, SIP_NO_CONVERTORS, 0, sipIsErr));
|
||||
return 0; // not a new instance
|
||||
"""
|
||||
""")
|
||||
|
||||
|
||||
c = module.find('wxBitmapBundleImpl')
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
import etgtools
|
||||
import etgtools.tweaker_tools as tools
|
||||
import etgtools.tweaker_tools
|
||||
|
||||
PACKAGE = "wx"
|
||||
MODULE = "_core"
|
||||
@@ -192,7 +193,9 @@ def run():
|
||||
# String with color name or #RRGGBB or #RRGGBBAA format
|
||||
# None (converts to wxNullColour)
|
||||
c.allowNone = True
|
||||
c.convertFromPyObject = """\
|
||||
c.convertFromPyObject = etgtools.tweaker_tools.AutoConversionInfo(
|
||||
('wx.Colour', '_ThreeInts', '_FourInts', 'str', 'None'),
|
||||
"""\
|
||||
// is it just a typecheck?
|
||||
if (!sipIsErr) {
|
||||
if (sipPy == Py_None)
|
||||
@@ -273,7 +276,7 @@ def run():
|
||||
*sipCppPtr = reinterpret_cast<wxColour*>(sipConvertToType(
|
||||
sipPy, sipType_wxColour, sipTransferObj, SIP_NO_CONVERTORS, 0, sipIsErr));
|
||||
return 0; // not a new instance
|
||||
"""
|
||||
""")
|
||||
|
||||
module.addPyCode('NamedColour = wx.deprecated(Colour, "Use Colour instead.")')
|
||||
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
import etgtools
|
||||
import etgtools.tweaker_tools as tools
|
||||
import etgtools.tweaker_tools
|
||||
|
||||
PACKAGE = "wx"
|
||||
MODULE = "_propgrid"
|
||||
@@ -70,7 +71,9 @@ def run():
|
||||
|
||||
c.find('GetPtr').overloads[0].ignore()
|
||||
|
||||
c.convertFromPyObject = """\
|
||||
c.convertFromPyObject = etgtools.tweaker_tools.AutoConversionInfo(
|
||||
('str', 'None', ),
|
||||
"""\
|
||||
// Code to test a PyObject for compatibility with wxPGPropArgCls
|
||||
if (!sipIsErr) {
|
||||
if (sipCanConvertToType(sipPy, sipType_wxPGPropArgCls, SIP_NO_CONVERTORS))
|
||||
@@ -109,7 +112,7 @@ def run():
|
||||
SIP_NO_CONVERTORS, 0, sipIsErr));
|
||||
return 0; // not a new instance
|
||||
}
|
||||
"""
|
||||
""")
|
||||
|
||||
|
||||
#----------------------------------------------------------
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
import etgtools
|
||||
import etgtools.tweaker_tools as tools
|
||||
import etgtools.tweaker_tools
|
||||
|
||||
PACKAGE = "wx"
|
||||
MODULE = "_core"
|
||||
@@ -76,7 +77,9 @@ def run():
|
||||
c.includeCppCode('src/stream_input.cpp')
|
||||
|
||||
# Use that class for the convert code
|
||||
c.convertFromPyObject = """\
|
||||
c.convertFromPyObject = etgtools.tweaker_tools.AutoConversionInfo(
|
||||
(), # TODO: Track down what python types actually can be wrapped
|
||||
"""\
|
||||
// is it just a typecheck?
|
||||
if (!sipIsErr) {
|
||||
if (wxPyInputStream::Check(sipPy))
|
||||
@@ -86,7 +89,7 @@ def run():
|
||||
// otherwise do the conversion
|
||||
*sipCppPtr = new wxPyInputStream(sipPy);
|
||||
return 0; //sipGetState(sipTransferObj);
|
||||
"""
|
||||
""")
|
||||
|
||||
# Add Python file-like methods so a wx.InputStream can be used as if it
|
||||
# was any other Python file object.
|
||||
@@ -236,7 +239,9 @@ def run():
|
||||
c.includeCppCode('src/stream_output.cpp')
|
||||
|
||||
# Use that class for the convert code
|
||||
c.convertFromPyObject = """\
|
||||
c.convertFromPyObject = etgtools.tweaker_tools.AutoConversionInfo(
|
||||
(), # TODO: Track down what python types can actually be converted
|
||||
"""\
|
||||
// is it just a typecheck?
|
||||
if (!sipIsErr) {
|
||||
if (wxPyOutputStream::Check(sipPy))
|
||||
@@ -246,7 +251,7 @@ def run():
|
||||
// otherwise do the conversion
|
||||
*sipCppPtr = new wxPyOutputStream(sipPy);
|
||||
return sipGetState(sipTransferObj);
|
||||
"""
|
||||
""")
|
||||
|
||||
|
||||
# Add Python file-like methods so a wx.OutputStream can be used as if it
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
import etgtools
|
||||
import etgtools.tweaker_tools as tools
|
||||
import etgtools.tweaker_tools
|
||||
|
||||
PACKAGE = "wx"
|
||||
MODULE = "_core"
|
||||
@@ -311,7 +312,9 @@ def run():
|
||||
|
||||
# Add some code (like MappedTypes) to automatically convert from a Python
|
||||
# datetime.date or a datetime.datetime object
|
||||
c.convertFromPyObject = """\
|
||||
c.convertFromPyObject = etgtools.tweaker_tools.AutoConversionInfo(
|
||||
('datetime', 'date', ),
|
||||
"""\
|
||||
// Code to test a PyObject for compatibility with wxDateTime
|
||||
if (!sipIsErr) {
|
||||
if (sipCanConvertToType(sipPy, sipType_wxDateTime, SIP_NO_CONVERTORS))
|
||||
@@ -335,7 +338,7 @@ def run():
|
||||
sipPy, sipType_wxDateTime, sipTransferObj, SIP_NO_CONVERTORS, 0, sipIsErr));
|
||||
|
||||
return 0; // Not a new instance
|
||||
"""
|
||||
""")
|
||||
|
||||
|
||||
#---------------------------------------------
|
||||
|
@@ -19,9 +19,9 @@ from typing import Optional
|
||||
import xml.etree.ElementTree as et
|
||||
import copy
|
||||
|
||||
from .tweaker_tools import FixWxPrefix, MethodType, magicMethods, \
|
||||
from .tweaker_tools import AutoConversionInfo, FixWxPrefix, MethodType, magicMethods, \
|
||||
guessTypeInt, guessTypeFloat, guessTypeStr, \
|
||||
textfile_open, Signature
|
||||
textfile_open, Signature, removeWxPrefix
|
||||
from sphinxtools.utilities import findDescendants
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
@@ -506,7 +506,7 @@ class FunctionDef(BaseDef, FixWxPrefix):
|
||||
# now grab just the last word, it should be the variable name
|
||||
# The rest will be the type information
|
||||
arg_type, arg = arg.rsplit(None, 1)
|
||||
arg, arg_type = self.parseNameAndType(arg, arg_type)
|
||||
arg, arg_type = self.parseNameAndType(arg, arg_type, True)
|
||||
params.append(P(arg, arg_type, default))
|
||||
if default == 'None':
|
||||
params[-1].make_optional()
|
||||
@@ -517,7 +517,7 @@ class FunctionDef(BaseDef, FixWxPrefix):
|
||||
continue
|
||||
if param.arraySize:
|
||||
continue
|
||||
s, param_type = self.parseNameAndType(param.pyName or param.name, param.type)
|
||||
s, param_type = self.parseNameAndType(param.pyName or param.name, param.type, not param.out)
|
||||
if param.out:
|
||||
if param_type:
|
||||
returns.append(param_type)
|
||||
@@ -696,7 +696,7 @@ class ClassDef(BaseDef):
|
||||
self.headerCode = []
|
||||
self.cppCode = []
|
||||
self.convertToPyObject = None
|
||||
self.convertFromPyObject = None
|
||||
self._convertFromPyObject = None
|
||||
self.allowNone = False # Allow the convertFrom code to handle None too.
|
||||
self.instanceCode = None # Code to be used to create new instances of this class
|
||||
self.innerclasses = []
|
||||
@@ -716,6 +716,18 @@ class ClassDef(BaseDef):
|
||||
if element is not None:
|
||||
self.extract(element)
|
||||
|
||||
@property
|
||||
def convertFromPyObject(self) -> Optional[str]:
|
||||
return self._convertFromPyObject
|
||||
|
||||
@convertFromPyObject.setter
|
||||
def convertFromPyObject(self, value: AutoConversionInfo) -> None:
|
||||
self._convertFromPyObject = value.code
|
||||
name = self.name or self.pyName
|
||||
name = removeWxPrefix(name)
|
||||
print('Registering:', name, value.convertables)
|
||||
FixWxPrefix.register_autoconversion(name, value.convertables)
|
||||
|
||||
def is_top_level(self) -> bool:
|
||||
"""Check if this class is a subclass of wx.TopLevelWindow"""
|
||||
if not self.nodeBases:
|
||||
|
@@ -80,6 +80,7 @@ header_pyi = """\
|
||||
|
||||
typing_imports = """\
|
||||
from __future__ import annotations
|
||||
from datetime import datetime, date
|
||||
from enum import IntEnum, IntFlag, auto
|
||||
from typing import (Any, overload, TypeAlias, Generic,
|
||||
Union, Optional, List, Tuple, Callable
|
||||
@@ -89,6 +90,12 @@ try:
|
||||
except ImportError:
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
_TwoInts: TypeAlias = Tuple[int, int]
|
||||
_ThreeInts: TypeAlias = Tuple[int, int, int]
|
||||
_FourInts: TypeAlias = Tuple[int, int, int, int]
|
||||
_TwoFloats: TypeAlias = Tuple[float, float]
|
||||
_FourFloats: TypeAlias = Tuple[float, float, float, float]
|
||||
|
||||
"""
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
@@ -168,7 +168,7 @@ class Signature:
|
||||
parameters = self._parameters.values()
|
||||
stringizer = str if typed else type(self).Parameter.untyped
|
||||
return_type = f' -> {self.return_type}' if self.return_type else ''
|
||||
return f'({', '.join(map(stringizer, parameters))}){return_type}'
|
||||
return f"({', '.join(map(stringizer, parameters))}){return_type}"
|
||||
|
||||
def signature(self, typed: bool = True) -> str:
|
||||
"""Get the full signature for the function/method, including method and
|
||||
@@ -254,6 +254,11 @@ class FixWxPrefix(object):
|
||||
"""
|
||||
|
||||
_coreTopLevelNames = None
|
||||
_auto_conversions: dict[str, tuple[str, ...]] = {}
|
||||
|
||||
@classmethod
|
||||
def register_autoconversion(cls, class_name: str, convertables: tuple[str, ...]) -> None:
|
||||
cls._auto_conversions[class_name] = convertables
|
||||
|
||||
def fixWxPrefix(self, name, checkIsCore=False):
|
||||
# By default remove the wx prefix like normal
|
||||
@@ -295,7 +300,9 @@ class FixWxPrefix(object):
|
||||
names.append(item.name)
|
||||
elif isinstance(item, ast.AnnAssign):
|
||||
if isinstance(item.target, ast.Name):
|
||||
names.append(item.target.id)
|
||||
# Exclude typing TypeAlias's from detection
|
||||
if not (item.annotation == 'TypeAlias' and item.target.id.startswith('_')):
|
||||
names.append(item.target.id)
|
||||
|
||||
names = list()
|
||||
filename = 'wx/core.pyi'
|
||||
@@ -336,7 +343,7 @@ class FixWxPrefix(object):
|
||||
else:
|
||||
return name
|
||||
|
||||
def cleanType(self, type_name: str) -> str:
|
||||
def cleanType(self, type_name: str, is_input: bool = False) -> str:
|
||||
"""Process a C++ type name for use as a type annotation in Python code.
|
||||
Handles translation of common C++ types to Python types, as well as a
|
||||
few specific wx types to Python types.
|
||||
@@ -389,9 +396,13 @@ class FixWxPrefix(object):
|
||||
return f'List[{type_name}]'
|
||||
else:
|
||||
return 'list'
|
||||
allowed_types = self._auto_conversions.get(type_name, ())
|
||||
if allowed_types and is_input:
|
||||
allowed_types = (type_name, *(self.cleanType(t) for t in allowed_types))
|
||||
type_name = f"Union[{', '.join(allowed_types)}]"
|
||||
return type_map.get(type_name, type_name)
|
||||
|
||||
def parseNameAndType(self, name_string: str, type_string: Optional[str]) -> Tuple[str, Optional[str]]:
|
||||
def parseNameAndType(self, name_string: str, type_string: Optional[str], is_input: bool = False) -> Tuple[str, Optional[str]]:
|
||||
"""Given an identifier name and an optional type annotation, process
|
||||
these per cleanName and cleanType. Further performs transforms on the
|
||||
identifier name that may be required due to the type annotation.
|
||||
@@ -400,7 +411,7 @@ class FixWxPrefix(object):
|
||||
"""
|
||||
name_string = self.cleanName(name_string, fix_wx=False)
|
||||
if type_string:
|
||||
type_string = self.cleanType(type_string)
|
||||
type_string = self.cleanType(type_string, is_input)
|
||||
if type_string == '...':
|
||||
name_string = '*args'
|
||||
type_string = None
|
||||
@@ -1029,7 +1040,9 @@ def addGetIMMethodTemplate(module, klass, fields):
|
||||
|
||||
def convertTwoIntegersTemplate(CLASS):
|
||||
# Note: The GIL is already acquired where this code is used.
|
||||
return """\
|
||||
return AutoConversionInfo(
|
||||
('_TwoInts', ),
|
||||
"""\
|
||||
// is it just a typecheck?
|
||||
if (!sipIsErr) {{
|
||||
// is it already an instance of {CLASS}?
|
||||
@@ -1057,12 +1070,14 @@ def convertTwoIntegersTemplate(CLASS):
|
||||
Py_DECREF(o1);
|
||||
Py_DECREF(o2);
|
||||
return SIP_TEMPORARY;
|
||||
""".format(**locals())
|
||||
""".format(**locals()))
|
||||
|
||||
|
||||
def convertFourIntegersTemplate(CLASS):
|
||||
# Note: The GIL is already acquired where this code is used.
|
||||
return """\
|
||||
return AutoConversionInfo(
|
||||
('_FourInts', ),
|
||||
"""\
|
||||
// is it just a typecheck?
|
||||
if (!sipIsErr) {{
|
||||
// is it already an instance of {CLASS}?
|
||||
@@ -1094,13 +1109,15 @@ def convertFourIntegersTemplate(CLASS):
|
||||
Py_DECREF(o3);
|
||||
Py_DECREF(o4);
|
||||
return SIP_TEMPORARY;
|
||||
""".format(**locals())
|
||||
""".format(**locals()))
|
||||
|
||||
|
||||
|
||||
def convertTwoDoublesTemplate(CLASS):
|
||||
# Note: The GIL is already acquired where this code is used.
|
||||
return """\
|
||||
return AutoConversionInfo(
|
||||
('_TwoFloats', ),
|
||||
"""\
|
||||
// is it just a typecheck?
|
||||
if (!sipIsErr) {{
|
||||
// is it already an instance of {CLASS}?
|
||||
@@ -1128,12 +1145,14 @@ def convertTwoDoublesTemplate(CLASS):
|
||||
Py_DECREF(o1);
|
||||
Py_DECREF(o2);
|
||||
return SIP_TEMPORARY;
|
||||
""".format(**locals())
|
||||
""".format(**locals()))
|
||||
|
||||
|
||||
def convertFourDoublesTemplate(CLASS):
|
||||
# Note: The GIL is already acquired where this code is used.
|
||||
return """\
|
||||
return AutoConversionInfo(
|
||||
('_FourFloats', ),
|
||||
"""\
|
||||
// is it just a typecheck?
|
||||
if (!sipIsErr) {{
|
||||
// is it already an instance of {CLASS}?
|
||||
@@ -1166,7 +1185,7 @@ def convertFourDoublesTemplate(CLASS):
|
||||
Py_DECREF(o3);
|
||||
Py_DECREF(o4);
|
||||
return SIP_TEMPORARY;
|
||||
""".format(**locals())
|
||||
""".format(**locals()))
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user