mirror of
https://github.com/wxWidgets/Phoenix.git
synced 2025-12-16 01:30:07 +01:00
Merge pull request #2468 from lojack5/lojack-typed-typestubs
Some checks failed
ci-build / build-source-dist (push) Failing after 0s
ci-build / build-wheels (x64, macos-13, 3.10) (push) Has been skipped
ci-build / build-wheels (x64, macos-13, 3.11) (push) Has been skipped
ci-build / build-wheels (x64, macos-13, 3.12) (push) Has been skipped
ci-build / build-wheels (x64, macos-13, 3.13) (push) Has been skipped
ci-build / build-wheels (x64, macos-13, 3.9) (push) Has been skipped
ci-build / build-wheels (x64, ubuntu-22.04, 3.10) (push) Has been skipped
ci-build / build-wheels (x64, ubuntu-22.04, 3.11) (push) Has been skipped
ci-build / build-wheels (x64, ubuntu-22.04, 3.12) (push) Has been skipped
ci-build / build-wheels (x64, ubuntu-22.04, 3.13) (push) Has been skipped
ci-build / build-wheels (x64, ubuntu-22.04, 3.9) (push) Has been skipped
ci-build / build-wheels (x64, windows-2022, 3.10) (push) Has been skipped
ci-build / build-wheels (x64, windows-2022, 3.11) (push) Has been skipped
ci-build / build-wheels (x64, windows-2022, 3.12) (push) Has been skipped
ci-build / build-wheels (x64, windows-2022, 3.13) (push) Has been skipped
ci-build / build-wheels (x64, windows-2022, 3.9) (push) Has been skipped
ci-build / build-wheels (x86, windows-2022, 3.10) (push) Has been skipped
ci-build / build-wheels (x86, windows-2022, 3.11) (push) Has been skipped
ci-build / build-wheels (x86, windows-2022, 3.12) (push) Has been skipped
ci-build / build-wheels (x86, windows-2022, 3.13) (push) Has been skipped
ci-build / build-wheels (x86, windows-2022, 3.9) (push) Has been skipped
ci-build / Publish Python distribution to PyPI (push) Has been skipped
ci-build / Create GitHub Release and upload source (push) Has been skipped
ci-build / Upload wheels to snapshot-builds on wxpython.org (push) Has been skipped
ci-build / Build wxPython documentation (push) Has been cancelled
Some checks failed
ci-build / build-source-dist (push) Failing after 0s
ci-build / build-wheels (x64, macos-13, 3.10) (push) Has been skipped
ci-build / build-wheels (x64, macos-13, 3.11) (push) Has been skipped
ci-build / build-wheels (x64, macos-13, 3.12) (push) Has been skipped
ci-build / build-wheels (x64, macos-13, 3.13) (push) Has been skipped
ci-build / build-wheels (x64, macos-13, 3.9) (push) Has been skipped
ci-build / build-wheels (x64, ubuntu-22.04, 3.10) (push) Has been skipped
ci-build / build-wheels (x64, ubuntu-22.04, 3.11) (push) Has been skipped
ci-build / build-wheels (x64, ubuntu-22.04, 3.12) (push) Has been skipped
ci-build / build-wheels (x64, ubuntu-22.04, 3.13) (push) Has been skipped
ci-build / build-wheels (x64, ubuntu-22.04, 3.9) (push) Has been skipped
ci-build / build-wheels (x64, windows-2022, 3.10) (push) Has been skipped
ci-build / build-wheels (x64, windows-2022, 3.11) (push) Has been skipped
ci-build / build-wheels (x64, windows-2022, 3.12) (push) Has been skipped
ci-build / build-wheels (x64, windows-2022, 3.13) (push) Has been skipped
ci-build / build-wheels (x64, windows-2022, 3.9) (push) Has been skipped
ci-build / build-wheels (x86, windows-2022, 3.10) (push) Has been skipped
ci-build / build-wheels (x86, windows-2022, 3.11) (push) Has been skipped
ci-build / build-wheels (x86, windows-2022, 3.12) (push) Has been skipped
ci-build / build-wheels (x86, windows-2022, 3.13) (push) Has been skipped
ci-build / build-wheels (x86, windows-2022, 3.9) (push) Has been skipped
ci-build / Publish Python distribution to PyPI (push) Has been skipped
ci-build / Create GitHub Release and upload source (push) Has been skipped
ci-build / Upload wheels to snapshot-builds on wxpython.org (push) Has been skipped
ci-build / Build wxPython documentation (push) Has been cancelled
Improve Python type-stubs
This commit is contained in:
33
etg/_core.py
33
etg/_core.py
@@ -291,7 +291,16 @@ def run():
|
||||
""")
|
||||
|
||||
|
||||
module.addPyFunction('CallAfter', '(callableObj, *args, **kw)', doc="""\
|
||||
module.addPyCode('import typing', order=10)
|
||||
module.addPyCode("""\
|
||||
_T = typing.TypeVar('_T')
|
||||
try:
|
||||
_P = typing.ParamSpec('_P')
|
||||
except AttributeError:
|
||||
import typing_extensions
|
||||
_P = typing_extensions.ParamSpec('_P')
|
||||
""")
|
||||
module.addPyFunction('CallAfter', '(callableObj: typing.Callable[_P, _T], *args: _P.args, **kw: _P.kwargs) -> None', doc="""\
|
||||
Call the specified function after the current and pending event
|
||||
handlers have been completed. This is also good for making GUI
|
||||
method calls from non-GUI threads. Any extra positional or
|
||||
@@ -322,7 +331,7 @@ def run():
|
||||
wx.PostEvent(app, evt)""")
|
||||
|
||||
|
||||
module.addPyClass('CallLater', ['object'],
|
||||
module.addPyClass('CallLater', ['typing.Generic[_P, _T]'],
|
||||
doc="""\
|
||||
A convenience class for :class:`wx.Timer`, that calls the given callable
|
||||
object once after the given amount of milliseconds, passing any
|
||||
@@ -342,7 +351,7 @@ def run():
|
||||
""",
|
||||
items = [
|
||||
PyCodeDef('__instances = {}'),
|
||||
PyFunctionDef('__init__', '(self, millis, callableObj, *args, **kwargs)',
|
||||
PyFunctionDef('__init__', '(self, millis, callableObj: typing.Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs) -> None',
|
||||
doc="""\
|
||||
Constructs a new :class:`wx.CallLater` object.
|
||||
|
||||
@@ -366,7 +375,7 @@ def run():
|
||||
|
||||
PyFunctionDef('__del__', '(self)', 'self.Stop()'),
|
||||
|
||||
PyFunctionDef('Start', '(self, millis=None, *args, **kwargs)',
|
||||
PyFunctionDef('Start', '(self, millis: typing.Optional[int]=None, *args: _P.args, **kwargs: _P.kwargs) -> None',
|
||||
doc="""\
|
||||
(Re)start the timer
|
||||
|
||||
@@ -388,7 +397,7 @@ def run():
|
||||
self.running = True"""),
|
||||
PyCodeDef('Restart = Start'),
|
||||
|
||||
PyFunctionDef('Stop', '(self)',
|
||||
PyFunctionDef('Stop', '(self) -> None',
|
||||
doc="Stop and destroy the timer.",
|
||||
body="""\
|
||||
if self in CallLater.__instances:
|
||||
@@ -397,16 +406,16 @@ def run():
|
||||
self.timer.Stop()
|
||||
self.timer = None"""),
|
||||
|
||||
PyFunctionDef('GetInterval', '(self)', """\
|
||||
PyFunctionDef('GetInterval', '(self) -> int', """\
|
||||
if self.timer is not None:
|
||||
return self.timer.GetInterval()
|
||||
else:
|
||||
return 0"""),
|
||||
|
||||
PyFunctionDef('IsRunning', '(self)',
|
||||
PyFunctionDef('IsRunning', '(self) -> bool',
|
||||
"""return self.timer is not None and self.timer.IsRunning()"""),
|
||||
|
||||
PyFunctionDef('SetArgs', '(self, *args, **kwargs)',
|
||||
PyFunctionDef('SetArgs', '(self, *args: _P.args, **kwargs: _P.kwargs) -> None',
|
||||
doc="""\
|
||||
(Re)set the args passed to the callable object. This is
|
||||
useful in conjunction with :meth:`Start` if
|
||||
@@ -421,7 +430,7 @@ def run():
|
||||
self.args = args
|
||||
self.kwargs = kwargs"""),
|
||||
|
||||
PyFunctionDef('HasRun', '(self)', 'return self.hasRun',
|
||||
PyFunctionDef('HasRun', '(self) -> bool', 'return self.hasRun',
|
||||
doc="""\
|
||||
Returns whether or not the callable has run.
|
||||
|
||||
@@ -429,7 +438,7 @@ def run():
|
||||
|
||||
"""),
|
||||
|
||||
PyFunctionDef('GetResult', '(self)', 'return self.result',
|
||||
PyFunctionDef('GetResult', '(self) -> _T', 'return self.result',
|
||||
doc="""\
|
||||
Returns the value of the callable.
|
||||
|
||||
@@ -437,7 +446,7 @@ def run():
|
||||
:return: result from callable
|
||||
"""),
|
||||
|
||||
PyFunctionDef('Notify', '(self)',
|
||||
PyFunctionDef('Notify', '(self) -> None',
|
||||
doc="The timer has expired so call the callable.",
|
||||
body="""\
|
||||
if self.callable and getattr(self.callable, 'im_self', True):
|
||||
@@ -456,7 +465,7 @@ def run():
|
||||
module.addPyCode("FutureCall = deprecated(CallLater, 'Use CallLater instead.')")
|
||||
|
||||
module.addPyCode("""\
|
||||
def GetDefaultPyEncoding():
|
||||
def GetDefaultPyEncoding() -> str:
|
||||
return "utf-8"
|
||||
GetDefaultPyEncoding = deprecated(GetDefaultPyEncoding, msg="wxPython now always uses utf-8")
|
||||
""")
|
||||
|
||||
@@ -464,21 +464,11 @@ class FunctionDef(BaseDef, FixWxPrefix):
|
||||
"""
|
||||
Create a pythonized version of the argsString in function and method
|
||||
items that can be used as part of the docstring.
|
||||
|
||||
TODO: Maybe (optionally) use this syntax to document arg types?
|
||||
http://www.python.org/dev/peps/pep-3107/
|
||||
"""
|
||||
def _cleanName(name):
|
||||
for txt in ['const', '*', '&', ' ']:
|
||||
name = name.replace(txt, '')
|
||||
name = name.replace('::', '.')
|
||||
name = self.fixWxPrefix(name, True)
|
||||
return name
|
||||
|
||||
params = list()
|
||||
returns = list()
|
||||
if self.type and self.type != 'void':
|
||||
returns.append(_cleanName(self.type))
|
||||
returns.append(self.cleanType(self.type))
|
||||
|
||||
defValueMap = { 'true': 'True',
|
||||
'false': 'False',
|
||||
@@ -486,6 +476,7 @@ class FunctionDef(BaseDef, FixWxPrefix):
|
||||
'wxString()': '""',
|
||||
'wxArrayString()' : '[]',
|
||||
'wxArrayInt()' : '[]',
|
||||
'wxEmptyString': "''", # Makes signatures much shorter
|
||||
}
|
||||
if isinstance(self, CppMethodDef):
|
||||
# rip apart the argsString instead of using the (empty) list of parameters
|
||||
@@ -504,7 +495,14 @@ class FunctionDef(BaseDef, FixWxPrefix):
|
||||
else:
|
||||
default = self.fixWxPrefix(default, True)
|
||||
# now grab just the last word, it should be the variable name
|
||||
arg = arg.split()[-1]
|
||||
# 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)
|
||||
@@ -515,25 +513,36 @@ class FunctionDef(BaseDef, FixWxPrefix):
|
||||
continue
|
||||
if param.arraySize:
|
||||
continue
|
||||
s = param.pyName or param.name
|
||||
s, param_type = self.parseNameAndType(param.pyName or param.name, param.type)
|
||||
if param.out:
|
||||
returns.append(s)
|
||||
if param_type:
|
||||
returns.append(param_type)
|
||||
else:
|
||||
if param.inOut:
|
||||
returns.append(s)
|
||||
if param_type:
|
||||
returns.append(param_type)
|
||||
if param.default:
|
||||
default = param.default
|
||||
if default in defValueMap:
|
||||
default = defValueMap.get(default)
|
||||
|
||||
s += '=' + '|'.join([_cleanName(x) for x in default.split('|')])
|
||||
if param_type:
|
||||
if default == 'None':
|
||||
s = f'{s}: Optional[{param_type}]'
|
||||
else:
|
||||
s = f'{s}: {param_type}'
|
||||
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 = '(' + ', '.join(params) + ')'
|
||||
if len(returns) == 1:
|
||||
self.pyArgsString += ' -> ' + returns[0]
|
||||
if len(returns) > 1:
|
||||
self.pyArgsString += ' -> (' + ', '.join(returns) + ')'
|
||||
self.pyArgsString = f"({', '.join(params)})"
|
||||
if not returns:
|
||||
self.pyArgsString = f'{self.pyArgsString} -> None'
|
||||
elif len(returns) == 1:
|
||||
self.pyArgsString = f'{self.pyArgsString} -> {returns[0]}'
|
||||
elif len(returns) > 1:
|
||||
self.pyArgsString = f"{self.pyArgsString} -> Tuple[{', '.join(returns)}]"
|
||||
|
||||
|
||||
def collectPySignatures(self):
|
||||
|
||||
@@ -81,12 +81,15 @@ def nci(text, numSpaces=0, stripLeading=True):
|
||||
return newText
|
||||
|
||||
|
||||
def wrapText(text):
|
||||
def wrapText(text, dontWrap: str = ''):
|
||||
import textwrap
|
||||
lines = []
|
||||
tw = textwrap.TextWrapper(width=70, break_long_words=False)
|
||||
for line in text.split('\n'):
|
||||
lines.append(tw.fill(line))
|
||||
if dontWrap and line.lstrip().startswith(dontWrap):
|
||||
lines.append(line)
|
||||
else:
|
||||
lines.append(tw.fill(line))
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ want to add some type info to that version of the file eventually...
|
||||
"""
|
||||
|
||||
import sys, os, re
|
||||
from typing import Union
|
||||
import etgtools.extractors as extractors
|
||||
import etgtools.generators as generators
|
||||
from etgtools.generators import nci, Utf8EncodingStream, textfile_open
|
||||
@@ -77,6 +78,19 @@ header_pyi = """\
|
||||
|
||||
"""
|
||||
|
||||
typing_imports = """\
|
||||
from __future__ import annotations
|
||||
from enum import IntEnum, IntFlag, auto
|
||||
from typing import (Any, overload, TypeAlias, Generic,
|
||||
Union, Optional, List, Tuple, Callable
|
||||
)
|
||||
try:
|
||||
from typing import ParamSpec
|
||||
except ImportError:
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
"""
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def piIgnored(obj):
|
||||
@@ -112,18 +126,21 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
||||
|
||||
if not SKIP_PI_FILE:
|
||||
_checkAndWriteHeader(destFile_pi, header_pi, module.docstring)
|
||||
self.writeSection(destFile_pi, 'typing-imports', typing_imports, at_end=False)
|
||||
self.writeSection(destFile_pi, module.name, stream.getvalue())
|
||||
|
||||
if not SKIP_PYI_FILE:
|
||||
_checkAndWriteHeader(destFile_pyi, header_pyi, module.docstring)
|
||||
self.writeSection(destFile_pyi, 'typing-imports', typing_imports, at_end=False)
|
||||
self.writeSection(destFile_pyi, module.name, stream.getvalue())
|
||||
|
||||
|
||||
def writeSection(self, destFile, sectionName, sectionText):
|
||||
def writeSection(self, destFile, sectionName, sectionText, at_end = True):
|
||||
"""
|
||||
Read all the lines from destFile, remove those currently between
|
||||
begin/end markers for sectionName (if any), and write the lines back
|
||||
to the file with the new text in sectionText.
|
||||
`at_end` determines where in the file the section is added when missing
|
||||
"""
|
||||
sectionBeginLine = -1
|
||||
sectionEndLine = -1
|
||||
@@ -139,10 +156,23 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
||||
sectionEndLine = idx
|
||||
|
||||
if sectionBeginLine == -1:
|
||||
# not there already, add to the end
|
||||
lines.append(sectionBeginMarker + '\n')
|
||||
lines.append(sectionText)
|
||||
lines.append(sectionEndMarker + '\n')
|
||||
if at_end:
|
||||
# not there already, add to the end
|
||||
lines.append(sectionBeginMarker + '\n')
|
||||
lines.append(sectionText)
|
||||
lines.append(sectionEndMarker + '\n')
|
||||
else:
|
||||
# not there already, add to the beginning
|
||||
# Skip the header
|
||||
idx = 0
|
||||
for idx, line in enumerate(lines):
|
||||
if not line.startswith('#'):
|
||||
break
|
||||
lines[idx+1:idx+1] = [
|
||||
sectionBeginMarker + '\n',
|
||||
sectionText,
|
||||
sectionEndMarker + '\n',
|
||||
]
|
||||
else:
|
||||
# replace the existing lines
|
||||
lines[sectionBeginLine+1:sectionEndLine] = [sectionText]
|
||||
@@ -202,11 +232,42 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
||||
assert isinstance(enum, extractors.EnumDef)
|
||||
if enum.ignored or piIgnored(enum):
|
||||
return
|
||||
# These enum classes aren't actually accessible from the real wx
|
||||
# module, o we need to prepend _. But we want to make a type alias to
|
||||
# the non-prefixed name, so method signatures can reference it without
|
||||
# any special code, and also allow bare ints as inputs.
|
||||
if '@' in enum.name or not enum.name:
|
||||
# Anonymous enum
|
||||
enum_name = f"_enum_{enum.name.replace('@', '').strip()}"
|
||||
alias = ''
|
||||
else:
|
||||
alias = self.fixWxPrefix(enum.name)
|
||||
enum_name = f'_{alias}'
|
||||
if 'Flags' in enum_name:
|
||||
enum_type = 'IntFlag'
|
||||
else:
|
||||
enum_type = 'IntEnum'
|
||||
# Create the enum definition
|
||||
stream.write(f'\n{indent}class {enum_name}({enum_type}):\n')
|
||||
for v in enum.items:
|
||||
if v.ignored or piIgnored(v):
|
||||
continue
|
||||
name = v.pyName or v.name
|
||||
stream.write('%s%s = 0\n' % (indent, name))
|
||||
stream.write(f'{indent} {name} = auto()\n')
|
||||
# Create the alias if needed
|
||||
if alias:
|
||||
stream.write(f'{indent}{alias}: TypeAlias = Union[{enum_name}, int]\n')
|
||||
# And bring the enum members into global scope. We can't use
|
||||
# enum.global_enum for this because:
|
||||
# 1. It's only available on Python 3.11+
|
||||
# 2. FixWxPrefix wouldn't be able to pick up the names, since it's
|
||||
# detecting based on AST parsing, not runtime changes (which
|
||||
# enum.global_enum performs).
|
||||
for v in enum.items:
|
||||
if v.ignored or piIgnored(v):
|
||||
continue
|
||||
name = v.pyName or v.name
|
||||
stream.write(f'{indent}{name} = {enum_name}.{name}\n')
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateGlobalVar(self, globalVar, stream):
|
||||
@@ -214,22 +275,16 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
||||
if globalVar.ignored or piIgnored(globalVar):
|
||||
return
|
||||
name = globalVar.pyName or globalVar.name
|
||||
valTyp = 'Any'
|
||||
if guessTypeInt(globalVar):
|
||||
valTyp = '0'
|
||||
valTyp = 'int'
|
||||
elif guessTypeFloat(globalVar):
|
||||
valTyp = '0.0'
|
||||
valTyp = 'float'
|
||||
elif guessTypeStr(globalVar):
|
||||
valTyp = '""'
|
||||
else:
|
||||
valTyp = globalVar.type
|
||||
valTyp = valTyp.replace('const ', '')
|
||||
valTyp = valTyp.replace('*', '')
|
||||
valTyp = valTyp.replace('&', '')
|
||||
valTyp = valTyp.replace(' ', '')
|
||||
valTyp = self.fixWxPrefix(valTyp)
|
||||
valTyp += '()'
|
||||
|
||||
stream.write('%s = %s\n' % (name, valTyp))
|
||||
valTyp = 'str'
|
||||
elif globalVar.type:
|
||||
valTyp = self.cleanType(globalVar.type) or valTyp
|
||||
stream.write(f'{name}: {valTyp}\n')
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateDefine(self, define, stream):
|
||||
@@ -237,10 +292,11 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
||||
if define.ignored or piIgnored(define):
|
||||
return
|
||||
# we're assuming that all #defines that are not ignored are integer or string values
|
||||
name = define.pyName or define.name
|
||||
if '"' in define.value:
|
||||
stream.write('%s = ""\n' % (define.pyName or define.name))
|
||||
stream.write(f'{name}: str\n')
|
||||
else:
|
||||
stream.write('%s = 0\n' % (define.pyName or define.name))
|
||||
stream.write(f'{name}: int\n')
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateTypedef(self, typedef, stream, indent=''):
|
||||
@@ -322,7 +378,7 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
||||
if pc.bases:
|
||||
stream.write('(%s):\n' % ', '.join(pc.bases))
|
||||
else:
|
||||
stream.write('(object):\n')
|
||||
stream.write(':\n')
|
||||
indent2 = indent + ' '*4
|
||||
if pc.briefDoc:
|
||||
stream.write('%s"""\n' % indent2)
|
||||
@@ -344,29 +400,32 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateFunction(self, function, stream):
|
||||
def generateFunction(self, function, stream, is_overload=False):
|
||||
assert isinstance(function, extractors.FunctionDef)
|
||||
if not function.pyName:
|
||||
return
|
||||
if not is_overload and function.hasOverloads():
|
||||
for f in function.overloads:
|
||||
self.generateFunction(f, stream, True)
|
||||
stream.write('\n@overload')
|
||||
elif is_overload:
|
||||
stream.write('\n@overload')
|
||||
stream.write('\ndef %s' % function.pyName)
|
||||
if function.hasOverloads():
|
||||
stream.write('(*args, **kw)')
|
||||
else:
|
||||
argsString = function.pyArgsString
|
||||
if not argsString:
|
||||
argsString = '()'
|
||||
if '->' in argsString:
|
||||
pos = argsString.find(')')
|
||||
argsString = argsString[:pos+1]
|
||||
if '(' != argsString[0]:
|
||||
pos = argsString.find('(')
|
||||
argsString = argsString[pos:]
|
||||
argsString = argsString.replace('::', '.')
|
||||
stream.write(argsString)
|
||||
argsString = function.pyArgsString
|
||||
if not argsString:
|
||||
argsString = '()'
|
||||
if '(' != argsString[0]:
|
||||
pos = argsString.find('(')
|
||||
argsString = argsString[pos:]
|
||||
argsString = argsString.replace('::', '.')
|
||||
stream.write(argsString)
|
||||
stream.write(':\n')
|
||||
stream.write(' """\n')
|
||||
stream.write(nci(function.pyDocstring, 4))
|
||||
stream.write(' """\n')
|
||||
if is_overload:
|
||||
stream.write(' ...\n')
|
||||
else:
|
||||
stream.write(' """\n')
|
||||
stream.write(nci(function.pyDocstring, 4))
|
||||
stream.write(' """\n')
|
||||
|
||||
|
||||
def generateParameters(self, parameters, stream, indent):
|
||||
@@ -413,8 +472,6 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
||||
bases = [self.fixWxPrefix(b, True) for b in bases]
|
||||
stream.write(', '.join(bases))
|
||||
stream.write(')')
|
||||
else:
|
||||
stream.write('(object)')
|
||||
stream.write(':\n')
|
||||
indent2 = indent + ' '*4
|
||||
|
||||
@@ -479,24 +536,35 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
||||
assert isinstance(memberVar, extractors.MemberVarDef)
|
||||
if memberVar.ignored or piIgnored(memberVar):
|
||||
return
|
||||
stream.write('%s%s = property(None, None)\n' % (indent, memberVar.name))
|
||||
member_type = memberVar.type
|
||||
if member_type:
|
||||
member_type = self.cleanType(member_type)
|
||||
if not member_type: # Unknown type for the member variable
|
||||
member_type = 'Any'
|
||||
stream.write(f'{indent}{memberVar.name}: {member_type}\n')
|
||||
|
||||
|
||||
def generateProperty(self, prop, stream, indent):
|
||||
assert isinstance(prop, extractors.PropertyDef)
|
||||
if prop.ignored or piIgnored(prop):
|
||||
return
|
||||
stream.write('%s%s = property(None, None)\n' % (indent, prop.name))
|
||||
self._generateProperty(prop, stream, indent)
|
||||
|
||||
|
||||
def generatePyProperty(self, prop, stream, indent):
|
||||
assert isinstance(prop, extractors.PyPropertyDef)
|
||||
self._generateProperty(prop, stream, indent)
|
||||
|
||||
def _generateProperty(self, prop: Union[extractors.PyPropertyDef, extractors.PropertyDef], stream, indent: str):
|
||||
if prop.ignored or piIgnored(prop):
|
||||
return
|
||||
stream.write('%s%s = property(None, None)\n' % (indent, prop.name))
|
||||
if prop.setter and prop.getter:
|
||||
stream.write(f'{indent}{prop.name} = property({prop.getter}, {prop.setter})\n')
|
||||
elif prop.getter:
|
||||
stream.write(f'{indent}{prop.name} = property({prop.getter})\n')
|
||||
elif prop.setter:
|
||||
stream.write(f'{indent}{prop.name} = property(fset={prop.setter})\n')
|
||||
|
||||
|
||||
def generateMethod(self, method, stream, indent, name=None, docstring=None):
|
||||
def generateMethod(self, method, stream, indent, name=None, docstring=None, is_overload=False):
|
||||
assert isinstance(method, extractors.MethodDef)
|
||||
for m in method.all(): # use the first not ignored if there are overloads
|
||||
if not m.ignored or piIgnored(m):
|
||||
@@ -512,44 +580,44 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
||||
name = magicMethods[name]
|
||||
|
||||
# write the method declaration
|
||||
if not is_overload and method.hasOverloads():
|
||||
for m in method.overloads:
|
||||
self.generateMethod(m, stream, indent, name, None, True)
|
||||
stream.write(f'\n{indent}@overload')
|
||||
elif is_overload:
|
||||
stream.write(f'\n{indent}@overload')
|
||||
if method.isStatic:
|
||||
stream.write('\n%s@staticmethod' % indent)
|
||||
stream.write('\n%sdef %s' % (indent, name))
|
||||
if method.hasOverloads():
|
||||
if not method.isStatic:
|
||||
stream.write('(self, *args, **kw)')
|
||||
argsString = method.pyArgsString
|
||||
if not argsString:
|
||||
argsString = '()'
|
||||
if '(' != argsString[0]:
|
||||
pos = argsString.find('(')
|
||||
argsString = argsString[pos:]
|
||||
if not method.isStatic:
|
||||
if argsString == '()':
|
||||
argsString = '(self)'
|
||||
else:
|
||||
stream.write('(*args, **kw)')
|
||||
else:
|
||||
argsString = method.pyArgsString
|
||||
if not argsString:
|
||||
argsString = '()'
|
||||
if '->' in argsString:
|
||||
pos = argsString.find(') ->')
|
||||
argsString = argsString[:pos+1]
|
||||
if '(' != argsString[0]:
|
||||
pos = argsString.find('(')
|
||||
argsString = argsString[pos:]
|
||||
if not method.isStatic:
|
||||
if argsString == '()':
|
||||
argsString = '(self)'
|
||||
else:
|
||||
argsString = '(self, ' + argsString[1:]
|
||||
argsString = argsString.replace('::', '.')
|
||||
stream.write(argsString)
|
||||
argsString = '(self, ' + argsString[1:]
|
||||
argsString = argsString.replace('::', '.')
|
||||
stream.write(argsString)
|
||||
stream.write(':\n')
|
||||
indent2 = indent + ' '*4
|
||||
|
||||
# docstring
|
||||
if not docstring:
|
||||
if hasattr(method, 'pyDocstring'):
|
||||
docstring = method.pyDocstring
|
||||
else:
|
||||
docstring = ""
|
||||
stream.write('%s"""\n' % indent2)
|
||||
if docstring.strip():
|
||||
stream.write(nci(docstring, len(indent2)))
|
||||
stream.write('%s"""\n' % indent2)
|
||||
if is_overload:
|
||||
stream.write(f'{indent2}...\n')
|
||||
else:
|
||||
if not docstring:
|
||||
if hasattr(method, 'pyDocstring'):
|
||||
docstring = method.pyDocstring
|
||||
else:
|
||||
docstring = ""
|
||||
stream.write('%s"""\n' % indent2)
|
||||
if docstring.strip():
|
||||
stream.write(nci(docstring, len(indent2)))
|
||||
stream.write('%s"""\n' % indent2)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -610,7 +610,7 @@ from .%s import *
|
||||
|
||||
# get the docstring text
|
||||
text = nci(extractors.flattenNode(item.briefDoc, False))
|
||||
text = wrapText(text)
|
||||
text = wrapText(text, item.pyName or item.name)
|
||||
|
||||
|
||||
#if isinstance(item, extractors.ClassDef):
|
||||
|
||||
@@ -14,9 +14,12 @@ stage of the ETG scripts.
|
||||
|
||||
import etgtools as extractors
|
||||
from .generators import textfile_open
|
||||
import keyword
|
||||
import re
|
||||
import sys, os
|
||||
import copy
|
||||
import textwrap
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
@@ -99,6 +102,7 @@ class FixWxPrefix(object):
|
||||
testName = name
|
||||
if '(' in name:
|
||||
testName = name[:name.find('(')]
|
||||
testName = testName.split('.')[0]
|
||||
|
||||
if testName in FixWxPrefix._coreTopLevelNames:
|
||||
return 'wx.'+name
|
||||
@@ -121,6 +125,9 @@ class FixWxPrefix(object):
|
||||
names.append(item.name)
|
||||
elif isinstance(item, ast.FunctionDef):
|
||||
names.append(item.name)
|
||||
elif isinstance(item, ast.AnnAssign):
|
||||
if isinstance(item.target, ast.Name):
|
||||
names.append(item.target.id)
|
||||
|
||||
names = list()
|
||||
filename = 'wx/core.pyi'
|
||||
@@ -136,7 +143,102 @@ class FixWxPrefix(object):
|
||||
|
||||
FixWxPrefix._coreTopLevelNames = names
|
||||
|
||||
def cleanName(self, name: str, is_expression: bool = False, fix_wx: bool = True) -> str:
|
||||
"""Process a C++ name for use in Python code. In all cases, this means
|
||||
handling name collisions with Python keywords. For names that will be
|
||||
used for an identifier (ex: class, method, constant) - `is_expression`
|
||||
is False - this also includes the reserved constant names 'False',
|
||||
'True', and 'None'. When `is_expression` is True, name are allowed to
|
||||
include special characters and the reserved constant names - this is
|
||||
intended for cleaning up type-hint expressions ans default value
|
||||
expressions.
|
||||
|
||||
Finally, the 'wx.' prefix is added if needed.
|
||||
"""
|
||||
for txt in ['const', '*', '&', ' ']:
|
||||
name = name.replace(txt, '')
|
||||
name = name.replace('::', '.')
|
||||
if not is_expression:
|
||||
name = re.sub(r'[^a-zA-Z0-9_\.]', '', name)
|
||||
if not (is_expression and name in ['True', 'False', 'None']) and keyword.iskeyword(name):
|
||||
name = f'_{name}' # Python keyword name collision
|
||||
name = name.strip()
|
||||
if fix_wx:
|
||||
return self.fixWxPrefix(name, True)
|
||||
else:
|
||||
return removeWxPrefix(name)
|
||||
|
||||
def cleanType(self, type_name: str) -> 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.
|
||||
"""
|
||||
double_type = 'float' if PY3 else 'double'
|
||||
long_type = 'int' if PY3 else 'long'
|
||||
type_map = {
|
||||
# Some types are guesses, marked with TODO to verify automatic
|
||||
# conversion actually happens. Also, these are the type-names
|
||||
# after processing by cleanName (so spaces are removed), or
|
||||
# after potentially lopping off an 'Array' prefix.
|
||||
# --String types
|
||||
'String': 'str',
|
||||
'Char': 'str',
|
||||
'char': 'str',
|
||||
'FileName': 'str', # TODO: check conversion
|
||||
# --Int types
|
||||
'byte': 'int',
|
||||
'short': 'int',
|
||||
'Int': 'int',
|
||||
'unsigned': 'int',
|
||||
'unsignedchar': 'int',
|
||||
'unsignedshort': 'int',
|
||||
'unsignedint': 'int',
|
||||
'time_t': 'int',
|
||||
'size_t': 'int',
|
||||
'Int32': 'int',
|
||||
'long': long_type,
|
||||
'unsignedlong': long_type,
|
||||
'ulong': long_type,
|
||||
'LongLong': long_type,
|
||||
# --Float types
|
||||
'double': double_type,
|
||||
'Double': double_type,
|
||||
# --Others
|
||||
'void': 'Any',
|
||||
'PyObject': 'Any',
|
||||
'WindowID': 'int', # defined in wx/defs.h
|
||||
'Coord': 'int', # defined in wx/types.h
|
||||
}
|
||||
type_name = self.cleanName(type_name)
|
||||
# Special handling of Vector<type> types -
|
||||
if type_name.startswith('Vector<') and type_name.endswith('>'):
|
||||
# Special handling for 'Vector<type>' types
|
||||
type_name = self.cleanType(type_name[7:-1])
|
||||
return f'List[{type_name}]'
|
||||
if type_name.startswith('Array'):
|
||||
type_name = self.cleanType(type_name[5:])
|
||||
if type_name:
|
||||
return f'List[{type_name}]'
|
||||
else:
|
||||
return 'list'
|
||||
return type_map.get(type_name, type_name)
|
||||
|
||||
def parseNameAndType(self, name_string: str, type_string: Optional[str]) -> 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.
|
||||
Ex. The transformation "any_identifier : ..." -> "*args" requires
|
||||
modifying both the identifier name and the annotation.
|
||||
"""
|
||||
name_string = self.cleanName(name_string, fix_wx=False)
|
||||
if type_string:
|
||||
type_string = self.cleanType(type_string)
|
||||
if type_string == '...':
|
||||
name_string = '*args'
|
||||
type_string = None
|
||||
if not type_string:
|
||||
type_string = None
|
||||
return name_string, type_string
|
||||
|
||||
|
||||
def ignoreAssignmentOperators(node):
|
||||
|
||||
@@ -28,3 +28,4 @@ markupsafe==1.1.1
|
||||
doc2dash==2.3.0
|
||||
beautifulsoup4
|
||||
attrdict3 ; sys_platform == 'win32'
|
||||
typing-extensions; python_version < '3.10'
|
||||
|
||||
@@ -3,3 +3,4 @@ numpy < 1.17 ; python_version <= '2.7'
|
||||
numpy ; python_version >= '3.0' and python_version < '3.12'
|
||||
# pillow < 3.0
|
||||
six
|
||||
typing-extensions; python_version < '3.10'
|
||||
Reference in New Issue
Block a user