Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type stub improvements #2668

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions etg/bmpbndl.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,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 = tools.AutoConversionInfo(
('wx.Bitmap', 'wx.Icon', ),
"""\
// Check for type compatibility
if (!sipIsErr) {
if (sipCanConvertToType(sipPy, sipType_wxBitmap, SIP_NO_CONVERTORS))
Expand Down Expand Up @@ -86,7 +88,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')
Expand Down
6 changes: 4 additions & 2 deletions etg/colour.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ def run():
# String with color name or #RRGGBB or #RRGGBBAA format
# None (converts to wxNullColour)
c.allowNone = True
c.convertFromPyObject = """\
c.convertFromPyObject = tools.AutoConversionInfo(
('wx.Colour', '_ThreeInts', '_FourInts', 'str', 'None'),
"""\
// is it just a typecheck?
if (!sipIsErr) {
if (sipPy == Py_None)
Expand Down Expand Up @@ -273,7 +275,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.")')

Expand Down
6 changes: 4 additions & 2 deletions etg/propgridiface.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ def run():

c.find('GetPtr').overloads[0].ignore()

c.convertFromPyObject = """\
c.convertFromPyObject = tools.AutoConversionInfo(
('str', 'None', ),
"""\
// Code to test a PyObject for compatibility with wxPGPropArgCls
if (!sipIsErr) {
if (sipCanConvertToType(sipPy, sipType_wxPGPropArgCls, SIP_NO_CONVERTORS))
Expand Down Expand Up @@ -109,7 +111,7 @@ def run():
SIP_NO_CONVERTORS, 0, sipIsErr));
return 0; // not a new instance
}
"""
""")


#----------------------------------------------------------
Expand Down
12 changes: 8 additions & 4 deletions etg/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ def run():
c.includeCppCode('src/stream_input.cpp')

# Use that class for the convert code
c.convertFromPyObject = """\
c.convertFromPyObject = tools.AutoConversionInfo(
(), # TODO: Track down what python types actually can be wrapped
"""\
// is it just a typecheck?
if (!sipIsErr) {
if (wxPyInputStream::Check(sipPy))
Expand All @@ -86,7 +88,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.
Expand Down Expand Up @@ -236,7 +238,9 @@ def run():
c.includeCppCode('src/stream_output.cpp')

# Use that class for the convert code
c.convertFromPyObject = """\
c.convertFromPyObject = tools.AutoConversionInfo(
(), # TODO: Track down what python types can actually be converted
"""\
// is it just a typecheck?
if (!sipIsErr) {
if (wxPyOutputStream::Check(sipPy))
Expand All @@ -246,7 +250,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
Expand Down
6 changes: 4 additions & 2 deletions etg/wxdatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,9 @@ def fixParseMethod(m, code):

# Add some code (like MappedTypes) to automatically convert from a Python
# datetime.date or a datetime.datetime object
c.convertFromPyObject = """\
c.convertFromPyObject = tools.AutoConversionInfo(
('datetime', 'date', ),
"""\
// Code to test a PyObject for compatibility with wxDateTime
if (!sipIsErr) {
if (sipCanConvertToType(sipPy, sipType_wxDateTime, SIP_NO_CONVERTORS))
Expand All @@ -339,7 +341,7 @@ def fixParseMethod(m, code):
sipPy, sipType_wxDateTime, sipTransferObj, SIP_NO_CONVERTORS, 0, sipIsErr));

return 0; // Not a new instance
"""
""")


#---------------------------------------------
Expand Down
103 changes: 65 additions & 38 deletions etgtools/extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
import sys
import os
import pprint
from typing import Optional
import xml.etree.ElementTree as et
import copy

from .tweaker_tools import FixWxPrefix, magicMethods, \
from .tweaker_tools import AutoConversionInfo, FixWxPrefix, MethodType, magicMethods, \
guessTypeInt, guessTypeFloat, guessTypeStr, \
textfile_open
textfile_open, Signature, removeWxPrefix
from sphinxtools.utilities import findDescendants

#---------------------------------------------------------------------------
Expand Down Expand Up @@ -275,11 +276,14 @@ class FunctionDef(BaseDef, FixWxPrefix):
"""
Information about a standalone function.
"""
_default_method_type = MethodType.FUNCTION

def __init__(self, element=None, **kw):
super(FunctionDef, self).__init__()
self.type = None
self.definition = ''
self.argsString = ''
self.signature: Optional[Signature] = None
self.pyArgsString = ''
self.isOverloaded = False
self.overloads = []
Expand Down Expand Up @@ -401,7 +405,10 @@ def renameOverload(self, matchText, newName, **kw):
else:
parent = self.klass
item = self.findOverload(matchText)
assert item is not None
item.pyName = newName
if item.signature:
item.signature.method_name = newName
item.__dict__.update(kw)

if item is self and not self.hasOverloads():
Expand Down Expand Up @@ -465,8 +472,8 @@ def makePyArgsString(self):
Create a pythonized version of the argsString in function and method
items that can be used as part of the docstring.
"""
params = list()
returns = list()
params: list[Signature.Parameter] = []
returns: list[str] = []
if self.type and self.type != 'void':
returns.append(self.cleanType(self.type))

Expand All @@ -478,6 +485,7 @@ def makePyArgsString(self):
'wxArrayInt()' : '[]',
'wxEmptyString': "''", # Makes signatures much shorter
}
P = Signature.Parameter
if isinstance(self, CppMethodDef):
# rip apart the argsString instead of using the (empty) list of parameters
lastP = self.argsString.rfind(')')
Expand All @@ -490,59 +498,56 @@ def makePyArgsString(self):
if '=' in arg:
default = arg.split('=')[1].strip()
arg = arg.split('=')[0].strip()
if default in defValueMap:
default = defValueMap.get(default)
else:
default = self.fixWxPrefix(default, True)
default = defValueMap.get(default, default)
default = self.fixWxPrefix(default, True)
# 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)
if arg_type:
if default == 'None':
arg = f'{arg}: Optional[{arg_type}]'
else:
arg = f'{arg}: {arg_type}'
if default:
arg += '=' + default
params.append(arg)
arg, arg_type = self.parseNameAndType(arg, arg_type, True)
params.append(P(arg, arg_type, default))
if default == 'None':
params[-1].make_optional()
else:
for param in self.items:
assert isinstance(param, ParamDef)
if param.ignored:
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)
else:
default = ''
if param.inOut:
if param_type:
returns.append(param_type)
if param.default:
default = param.default
if default in defValueMap:
default = defValueMap.get(default)
if param_type:
if default == 'None':
s = f'{s}: Optional[{param_type}]'
else:
s = f'{s}: {param_type}'
default = defValueMap.get(default, default)
default = '|'.join([self.cleanName(x, True) for x in default.split('|')])
s = f'{s}={default}'
elif param_type:
s = f'{s} : {param_type}'
params.append(s)

self.pyArgsString = f"({', '.join(params)})"
if not returns:
self.pyArgsString = f'{self.pyArgsString} -> None'
params.append(P(s, param_type, default))
if default == 'None':
params[-1].make_optional()
if getattr(self, 'isCtor', False):
name = '__init__'
else:
name = self.pyName or self.name
name = self.fixWxPrefix(name)
# __bool__ and __nonzero__ need to be defined as returning int for SIP, but for Python
lojack5 marked this conversation as resolved.
Show resolved Hide resolved
# __bool__ is required to return a bool:
if name in ('__bool__', '__nonzero__'):
return_type = 'bool'
elif not returns:
return_type = 'None'
elif len(returns) == 1:
self.pyArgsString = f'{self.pyArgsString} -> {returns[0]}'
elif len(returns) > 1:
self.pyArgsString = f"{self.pyArgsString} -> Tuple[{', '.join(returns)}]"
return_type = returns[0]
else:
return_type = f"Tuple[{', '.join(returns)}]"
kind = MethodType.STATIC_METHOD if getattr(self, 'isStatic', False) else type(self)._default_method_type
self.signature = Signature(name, *params, return_type=return_type, method_type=kind)
self.pyArgsString = self.signature.args_string(False)


def collectPySignatures(self):
Expand Down Expand Up @@ -579,6 +584,8 @@ class MethodDef(FunctionDef):
"""
Represents a class method, ctor or dtor declaration.
"""
_default_method_type = MethodType.METHOD

def __init__(self, element=None, className=None, **kw):
super(MethodDef, self).__init__()
self.className = className
Expand Down Expand Up @@ -688,7 +695,7 @@ def __init__(self, element=None, kind='class', **kw):
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 = []
Expand All @@ -708,6 +715,26 @@ def __init__(self, element=None, kind='class', **kw):
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.pyName or self.name
name = removeWxPrefix(name)
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:
return False
all_classes, specials = self.nodeBases
if 'wxTopLevelWindow' in specials:
return True
return 'wxTopLevelWindow' in all_classes


def renameClass(self, newName):
self.pyName = newName
Expand Down Expand Up @@ -1265,7 +1292,7 @@ class CppMethodDef(MethodDef):
NOTE: This one is not automatically extracted, but can be added to
classes in the tweaker stage
"""
def __init__(self, type, name, argsString, body, doc=None, isConst=False,
def __init__(self, type, name, argsString: str, body, doc=None, isConst=False,
cppSignature=None, virtualCatcherCode=None, **kw):
super(CppMethodDef, self).__init__()
self.type = type
Expand Down
Loading