mirror of
https://github.com/wxWidgets/Phoenix.git
synced 2026-01-04 11:00: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
|
Call the specified function after the current and pending event
|
||||||
handlers have been completed. This is also good for making GUI
|
handlers have been completed. This is also good for making GUI
|
||||||
method calls from non-GUI threads. Any extra positional or
|
method calls from non-GUI threads. Any extra positional or
|
||||||
@@ -322,7 +331,7 @@ def run():
|
|||||||
wx.PostEvent(app, evt)""")
|
wx.PostEvent(app, evt)""")
|
||||||
|
|
||||||
|
|
||||||
module.addPyClass('CallLater', ['object'],
|
module.addPyClass('CallLater', ['typing.Generic[_P, _T]'],
|
||||||
doc="""\
|
doc="""\
|
||||||
A convenience class for :class:`wx.Timer`, that calls the given callable
|
A convenience class for :class:`wx.Timer`, that calls the given callable
|
||||||
object once after the given amount of milliseconds, passing any
|
object once after the given amount of milliseconds, passing any
|
||||||
@@ -342,7 +351,7 @@ def run():
|
|||||||
""",
|
""",
|
||||||
items = [
|
items = [
|
||||||
PyCodeDef('__instances = {}'),
|
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="""\
|
doc="""\
|
||||||
Constructs a new :class:`wx.CallLater` object.
|
Constructs a new :class:`wx.CallLater` object.
|
||||||
|
|
||||||
@@ -366,7 +375,7 @@ def run():
|
|||||||
|
|
||||||
PyFunctionDef('__del__', '(self)', 'self.Stop()'),
|
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="""\
|
doc="""\
|
||||||
(Re)start the timer
|
(Re)start the timer
|
||||||
|
|
||||||
@@ -388,7 +397,7 @@ def run():
|
|||||||
self.running = True"""),
|
self.running = True"""),
|
||||||
PyCodeDef('Restart = Start'),
|
PyCodeDef('Restart = Start'),
|
||||||
|
|
||||||
PyFunctionDef('Stop', '(self)',
|
PyFunctionDef('Stop', '(self) -> None',
|
||||||
doc="Stop and destroy the timer.",
|
doc="Stop and destroy the timer.",
|
||||||
body="""\
|
body="""\
|
||||||
if self in CallLater.__instances:
|
if self in CallLater.__instances:
|
||||||
@@ -397,16 +406,16 @@ def run():
|
|||||||
self.timer.Stop()
|
self.timer.Stop()
|
||||||
self.timer = None"""),
|
self.timer = None"""),
|
||||||
|
|
||||||
PyFunctionDef('GetInterval', '(self)', """\
|
PyFunctionDef('GetInterval', '(self) -> int', """\
|
||||||
if self.timer is not None:
|
if self.timer is not None:
|
||||||
return self.timer.GetInterval()
|
return self.timer.GetInterval()
|
||||||
else:
|
else:
|
||||||
return 0"""),
|
return 0"""),
|
||||||
|
|
||||||
PyFunctionDef('IsRunning', '(self)',
|
PyFunctionDef('IsRunning', '(self) -> bool',
|
||||||
"""return self.timer is not None and self.timer.IsRunning()"""),
|
"""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="""\
|
doc="""\
|
||||||
(Re)set the args passed to the callable object. This is
|
(Re)set the args passed to the callable object. This is
|
||||||
useful in conjunction with :meth:`Start` if
|
useful in conjunction with :meth:`Start` if
|
||||||
@@ -421,7 +430,7 @@ def run():
|
|||||||
self.args = args
|
self.args = args
|
||||||
self.kwargs = kwargs"""),
|
self.kwargs = kwargs"""),
|
||||||
|
|
||||||
PyFunctionDef('HasRun', '(self)', 'return self.hasRun',
|
PyFunctionDef('HasRun', '(self) -> bool', 'return self.hasRun',
|
||||||
doc="""\
|
doc="""\
|
||||||
Returns whether or not the callable has run.
|
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="""\
|
doc="""\
|
||||||
Returns the value of the callable.
|
Returns the value of the callable.
|
||||||
|
|
||||||
@@ -437,7 +446,7 @@ def run():
|
|||||||
:return: result from callable
|
:return: result from callable
|
||||||
"""),
|
"""),
|
||||||
|
|
||||||
PyFunctionDef('Notify', '(self)',
|
PyFunctionDef('Notify', '(self) -> None',
|
||||||
doc="The timer has expired so call the callable.",
|
doc="The timer has expired so call the callable.",
|
||||||
body="""\
|
body="""\
|
||||||
if self.callable and getattr(self.callable, 'im_self', True):
|
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("FutureCall = deprecated(CallLater, 'Use CallLater instead.')")
|
||||||
|
|
||||||
module.addPyCode("""\
|
module.addPyCode("""\
|
||||||
def GetDefaultPyEncoding():
|
def GetDefaultPyEncoding() -> str:
|
||||||
return "utf-8"
|
return "utf-8"
|
||||||
GetDefaultPyEncoding = deprecated(GetDefaultPyEncoding, msg="wxPython now always uses 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
|
Create a pythonized version of the argsString in function and method
|
||||||
items that can be used as part of the docstring.
|
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()
|
params = list()
|
||||||
returns = list()
|
returns = list()
|
||||||
if self.type and self.type != 'void':
|
if self.type and self.type != 'void':
|
||||||
returns.append(_cleanName(self.type))
|
returns.append(self.cleanType(self.type))
|
||||||
|
|
||||||
defValueMap = { 'true': 'True',
|
defValueMap = { 'true': 'True',
|
||||||
'false': 'False',
|
'false': 'False',
|
||||||
@@ -486,6 +476,7 @@ class FunctionDef(BaseDef, FixWxPrefix):
|
|||||||
'wxString()': '""',
|
'wxString()': '""',
|
||||||
'wxArrayString()' : '[]',
|
'wxArrayString()' : '[]',
|
||||||
'wxArrayInt()' : '[]',
|
'wxArrayInt()' : '[]',
|
||||||
|
'wxEmptyString': "''", # Makes signatures much shorter
|
||||||
}
|
}
|
||||||
if isinstance(self, CppMethodDef):
|
if isinstance(self, CppMethodDef):
|
||||||
# rip apart the argsString instead of using the (empty) list of parameters
|
# rip apart the argsString instead of using the (empty) list of parameters
|
||||||
@@ -504,7 +495,14 @@ class FunctionDef(BaseDef, FixWxPrefix):
|
|||||||
else:
|
else:
|
||||||
default = self.fixWxPrefix(default, True)
|
default = self.fixWxPrefix(default, True)
|
||||||
# now grab just the last word, it should be the variable name
|
# 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:
|
if default:
|
||||||
arg += '=' + default
|
arg += '=' + default
|
||||||
params.append(arg)
|
params.append(arg)
|
||||||
@@ -515,25 +513,36 @@ class FunctionDef(BaseDef, FixWxPrefix):
|
|||||||
continue
|
continue
|
||||||
if param.arraySize:
|
if param.arraySize:
|
||||||
continue
|
continue
|
||||||
s = param.pyName or param.name
|
s, param_type = self.parseNameAndType(param.pyName or param.name, param.type)
|
||||||
if param.out:
|
if param.out:
|
||||||
returns.append(s)
|
if param_type:
|
||||||
|
returns.append(param_type)
|
||||||
else:
|
else:
|
||||||
if param.inOut:
|
if param.inOut:
|
||||||
returns.append(s)
|
if param_type:
|
||||||
|
returns.append(param_type)
|
||||||
if param.default:
|
if param.default:
|
||||||
default = param.default
|
default = param.default
|
||||||
if default in defValueMap:
|
if default in defValueMap:
|
||||||
default = defValueMap.get(default)
|
default = defValueMap.get(default)
|
||||||
|
if param_type:
|
||||||
s += '=' + '|'.join([_cleanName(x) for x in default.split('|')])
|
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)
|
params.append(s)
|
||||||
|
|
||||||
self.pyArgsString = '(' + ', '.join(params) + ')'
|
self.pyArgsString = f"({', '.join(params)})"
|
||||||
if len(returns) == 1:
|
if not returns:
|
||||||
self.pyArgsString += ' -> ' + returns[0]
|
self.pyArgsString = f'{self.pyArgsString} -> None'
|
||||||
if len(returns) > 1:
|
elif len(returns) == 1:
|
||||||
self.pyArgsString += ' -> (' + ', '.join(returns) + ')'
|
self.pyArgsString = f'{self.pyArgsString} -> {returns[0]}'
|
||||||
|
elif len(returns) > 1:
|
||||||
|
self.pyArgsString = f"{self.pyArgsString} -> Tuple[{', '.join(returns)}]"
|
||||||
|
|
||||||
|
|
||||||
def collectPySignatures(self):
|
def collectPySignatures(self):
|
||||||
|
|||||||
@@ -81,12 +81,15 @@ def nci(text, numSpaces=0, stripLeading=True):
|
|||||||
return newText
|
return newText
|
||||||
|
|
||||||
|
|
||||||
def wrapText(text):
|
def wrapText(text, dontWrap: str = ''):
|
||||||
import textwrap
|
import textwrap
|
||||||
lines = []
|
lines = []
|
||||||
tw = textwrap.TextWrapper(width=70, break_long_words=False)
|
tw = textwrap.TextWrapper(width=70, break_long_words=False)
|
||||||
for line in text.split('\n'):
|
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)
|
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
|
import sys, os, re
|
||||||
|
from typing import Union
|
||||||
import etgtools.extractors as extractors
|
import etgtools.extractors as extractors
|
||||||
import etgtools.generators as generators
|
import etgtools.generators as generators
|
||||||
from etgtools.generators import nci, Utf8EncodingStream, textfile_open
|
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):
|
def piIgnored(obj):
|
||||||
@@ -112,18 +126,21 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
|||||||
|
|
||||||
if not SKIP_PI_FILE:
|
if not SKIP_PI_FILE:
|
||||||
_checkAndWriteHeader(destFile_pi, header_pi, module.docstring)
|
_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())
|
self.writeSection(destFile_pi, module.name, stream.getvalue())
|
||||||
|
|
||||||
if not SKIP_PYI_FILE:
|
if not SKIP_PYI_FILE:
|
||||||
_checkAndWriteHeader(destFile_pyi, header_pyi, module.docstring)
|
_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())
|
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
|
Read all the lines from destFile, remove those currently between
|
||||||
begin/end markers for sectionName (if any), and write the lines back
|
begin/end markers for sectionName (if any), and write the lines back
|
||||||
to the file with the new text in sectionText.
|
to the file with the new text in sectionText.
|
||||||
|
`at_end` determines where in the file the section is added when missing
|
||||||
"""
|
"""
|
||||||
sectionBeginLine = -1
|
sectionBeginLine = -1
|
||||||
sectionEndLine = -1
|
sectionEndLine = -1
|
||||||
@@ -139,10 +156,23 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
|||||||
sectionEndLine = idx
|
sectionEndLine = idx
|
||||||
|
|
||||||
if sectionBeginLine == -1:
|
if sectionBeginLine == -1:
|
||||||
# not there already, add to the end
|
if at_end:
|
||||||
lines.append(sectionBeginMarker + '\n')
|
# not there already, add to the end
|
||||||
lines.append(sectionText)
|
lines.append(sectionBeginMarker + '\n')
|
||||||
lines.append(sectionEndMarker + '\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:
|
else:
|
||||||
# replace the existing lines
|
# replace the existing lines
|
||||||
lines[sectionBeginLine+1:sectionEndLine] = [sectionText]
|
lines[sectionBeginLine+1:sectionEndLine] = [sectionText]
|
||||||
@@ -202,11 +232,42 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
|||||||
assert isinstance(enum, extractors.EnumDef)
|
assert isinstance(enum, extractors.EnumDef)
|
||||||
if enum.ignored or piIgnored(enum):
|
if enum.ignored or piIgnored(enum):
|
||||||
return
|
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:
|
for v in enum.items:
|
||||||
if v.ignored or piIgnored(v):
|
if v.ignored or piIgnored(v):
|
||||||
continue
|
continue
|
||||||
name = v.pyName or v.name
|
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):
|
def generateGlobalVar(self, globalVar, stream):
|
||||||
@@ -214,22 +275,16 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
|||||||
if globalVar.ignored or piIgnored(globalVar):
|
if globalVar.ignored or piIgnored(globalVar):
|
||||||
return
|
return
|
||||||
name = globalVar.pyName or globalVar.name
|
name = globalVar.pyName or globalVar.name
|
||||||
|
valTyp = 'Any'
|
||||||
if guessTypeInt(globalVar):
|
if guessTypeInt(globalVar):
|
||||||
valTyp = '0'
|
valTyp = 'int'
|
||||||
elif guessTypeFloat(globalVar):
|
elif guessTypeFloat(globalVar):
|
||||||
valTyp = '0.0'
|
valTyp = 'float'
|
||||||
elif guessTypeStr(globalVar):
|
elif guessTypeStr(globalVar):
|
||||||
valTyp = '""'
|
valTyp = 'str'
|
||||||
else:
|
elif globalVar.type:
|
||||||
valTyp = globalVar.type
|
valTyp = self.cleanType(globalVar.type) or valTyp
|
||||||
valTyp = valTyp.replace('const ', '')
|
stream.write(f'{name}: {valTyp}\n')
|
||||||
valTyp = valTyp.replace('*', '')
|
|
||||||
valTyp = valTyp.replace('&', '')
|
|
||||||
valTyp = valTyp.replace(' ', '')
|
|
||||||
valTyp = self.fixWxPrefix(valTyp)
|
|
||||||
valTyp += '()'
|
|
||||||
|
|
||||||
stream.write('%s = %s\n' % (name, valTyp))
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------
|
#-----------------------------------------------------------------------
|
||||||
def generateDefine(self, define, stream):
|
def generateDefine(self, define, stream):
|
||||||
@@ -237,10 +292,11 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
|||||||
if define.ignored or piIgnored(define):
|
if define.ignored or piIgnored(define):
|
||||||
return
|
return
|
||||||
# we're assuming that all #defines that are not ignored are integer or string values
|
# 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:
|
if '"' in define.value:
|
||||||
stream.write('%s = ""\n' % (define.pyName or define.name))
|
stream.write(f'{name}: str\n')
|
||||||
else:
|
else:
|
||||||
stream.write('%s = 0\n' % (define.pyName or define.name))
|
stream.write(f'{name}: int\n')
|
||||||
|
|
||||||
#-----------------------------------------------------------------------
|
#-----------------------------------------------------------------------
|
||||||
def generateTypedef(self, typedef, stream, indent=''):
|
def generateTypedef(self, typedef, stream, indent=''):
|
||||||
@@ -322,7 +378,7 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
|||||||
if pc.bases:
|
if pc.bases:
|
||||||
stream.write('(%s):\n' % ', '.join(pc.bases))
|
stream.write('(%s):\n' % ', '.join(pc.bases))
|
||||||
else:
|
else:
|
||||||
stream.write('(object):\n')
|
stream.write(':\n')
|
||||||
indent2 = indent + ' '*4
|
indent2 = indent + ' '*4
|
||||||
if pc.briefDoc:
|
if pc.briefDoc:
|
||||||
stream.write('%s"""\n' % indent2)
|
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)
|
assert isinstance(function, extractors.FunctionDef)
|
||||||
if not function.pyName:
|
if not function.pyName:
|
||||||
return
|
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)
|
stream.write('\ndef %s' % function.pyName)
|
||||||
if function.hasOverloads():
|
argsString = function.pyArgsString
|
||||||
stream.write('(*args, **kw)')
|
if not argsString:
|
||||||
else:
|
argsString = '()'
|
||||||
argsString = function.pyArgsString
|
if '(' != argsString[0]:
|
||||||
if not argsString:
|
pos = argsString.find('(')
|
||||||
argsString = '()'
|
argsString = argsString[pos:]
|
||||||
if '->' in argsString:
|
argsString = argsString.replace('::', '.')
|
||||||
pos = argsString.find(')')
|
stream.write(argsString)
|
||||||
argsString = argsString[:pos+1]
|
|
||||||
if '(' != argsString[0]:
|
|
||||||
pos = argsString.find('(')
|
|
||||||
argsString = argsString[pos:]
|
|
||||||
argsString = argsString.replace('::', '.')
|
|
||||||
stream.write(argsString)
|
|
||||||
stream.write(':\n')
|
stream.write(':\n')
|
||||||
stream.write(' """\n')
|
if is_overload:
|
||||||
stream.write(nci(function.pyDocstring, 4))
|
stream.write(' ...\n')
|
||||||
stream.write(' """\n')
|
else:
|
||||||
|
stream.write(' """\n')
|
||||||
|
stream.write(nci(function.pyDocstring, 4))
|
||||||
|
stream.write(' """\n')
|
||||||
|
|
||||||
|
|
||||||
def generateParameters(self, parameters, stream, indent):
|
def generateParameters(self, parameters, stream, indent):
|
||||||
@@ -413,8 +472,6 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
|||||||
bases = [self.fixWxPrefix(b, True) for b in bases]
|
bases = [self.fixWxPrefix(b, True) for b in bases]
|
||||||
stream.write(', '.join(bases))
|
stream.write(', '.join(bases))
|
||||||
stream.write(')')
|
stream.write(')')
|
||||||
else:
|
|
||||||
stream.write('(object)')
|
|
||||||
stream.write(':\n')
|
stream.write(':\n')
|
||||||
indent2 = indent + ' '*4
|
indent2 = indent + ' '*4
|
||||||
|
|
||||||
@@ -479,24 +536,35 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
|||||||
assert isinstance(memberVar, extractors.MemberVarDef)
|
assert isinstance(memberVar, extractors.MemberVarDef)
|
||||||
if memberVar.ignored or piIgnored(memberVar):
|
if memberVar.ignored or piIgnored(memberVar):
|
||||||
return
|
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):
|
def generateProperty(self, prop, stream, indent):
|
||||||
assert isinstance(prop, extractors.PropertyDef)
|
assert isinstance(prop, extractors.PropertyDef)
|
||||||
if prop.ignored or piIgnored(prop):
|
self._generateProperty(prop, stream, indent)
|
||||||
return
|
|
||||||
stream.write('%s%s = property(None, None)\n' % (indent, prop.name))
|
|
||||||
|
|
||||||
|
|
||||||
def generatePyProperty(self, prop, stream, indent):
|
def generatePyProperty(self, prop, stream, indent):
|
||||||
assert isinstance(prop, extractors.PyPropertyDef)
|
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):
|
if prop.ignored or piIgnored(prop):
|
||||||
return
|
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)
|
assert isinstance(method, extractors.MethodDef)
|
||||||
for m in method.all(): # use the first not ignored if there are overloads
|
for m in method.all(): # use the first not ignored if there are overloads
|
||||||
if not m.ignored or piIgnored(m):
|
if not m.ignored or piIgnored(m):
|
||||||
@@ -512,44 +580,44 @@ class PiWrapperGenerator(generators.WrapperGeneratorBase, FixWxPrefix):
|
|||||||
name = magicMethods[name]
|
name = magicMethods[name]
|
||||||
|
|
||||||
# write the method declaration
|
# 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:
|
if method.isStatic:
|
||||||
stream.write('\n%s@staticmethod' % indent)
|
stream.write('\n%s@staticmethod' % indent)
|
||||||
stream.write('\n%sdef %s' % (indent, name))
|
stream.write('\n%sdef %s' % (indent, name))
|
||||||
if method.hasOverloads():
|
argsString = method.pyArgsString
|
||||||
if not method.isStatic:
|
if not argsString:
|
||||||
stream.write('(self, *args, **kw)')
|
argsString = '()'
|
||||||
|
if '(' != argsString[0]:
|
||||||
|
pos = argsString.find('(')
|
||||||
|
argsString = argsString[pos:]
|
||||||
|
if not method.isStatic:
|
||||||
|
if argsString == '()':
|
||||||
|
argsString = '(self)'
|
||||||
else:
|
else:
|
||||||
stream.write('(*args, **kw)')
|
argsString = '(self, ' + argsString[1:]
|
||||||
else:
|
argsString = argsString.replace('::', '.')
|
||||||
argsString = method.pyArgsString
|
stream.write(argsString)
|
||||||
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)
|
|
||||||
stream.write(':\n')
|
stream.write(':\n')
|
||||||
indent2 = indent + ' '*4
|
indent2 = indent + ' '*4
|
||||||
|
|
||||||
# docstring
|
# docstring
|
||||||
if not docstring:
|
if is_overload:
|
||||||
if hasattr(method, 'pyDocstring'):
|
stream.write(f'{indent2}...\n')
|
||||||
docstring = method.pyDocstring
|
else:
|
||||||
else:
|
if not docstring:
|
||||||
docstring = ""
|
if hasattr(method, 'pyDocstring'):
|
||||||
stream.write('%s"""\n' % indent2)
|
docstring = method.pyDocstring
|
||||||
if docstring.strip():
|
else:
|
||||||
stream.write(nci(docstring, len(indent2)))
|
docstring = ""
|
||||||
stream.write('%s"""\n' % indent2)
|
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
|
# get the docstring text
|
||||||
text = nci(extractors.flattenNode(item.briefDoc, False))
|
text = nci(extractors.flattenNode(item.briefDoc, False))
|
||||||
text = wrapText(text)
|
text = wrapText(text, item.pyName or item.name)
|
||||||
|
|
||||||
|
|
||||||
#if isinstance(item, extractors.ClassDef):
|
#if isinstance(item, extractors.ClassDef):
|
||||||
|
|||||||
@@ -14,9 +14,12 @@ stage of the ETG scripts.
|
|||||||
|
|
||||||
import etgtools as extractors
|
import etgtools as extractors
|
||||||
from .generators import textfile_open
|
from .generators import textfile_open
|
||||||
|
import keyword
|
||||||
|
import re
|
||||||
import sys, os
|
import sys, os
|
||||||
import copy
|
import copy
|
||||||
import textwrap
|
import textwrap
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
PY3 = sys.version_info[0] == 3
|
PY3 = sys.version_info[0] == 3
|
||||||
@@ -99,6 +102,7 @@ class FixWxPrefix(object):
|
|||||||
testName = name
|
testName = name
|
||||||
if '(' in name:
|
if '(' in name:
|
||||||
testName = name[:name.find('(')]
|
testName = name[:name.find('(')]
|
||||||
|
testName = testName.split('.')[0]
|
||||||
|
|
||||||
if testName in FixWxPrefix._coreTopLevelNames:
|
if testName in FixWxPrefix._coreTopLevelNames:
|
||||||
return 'wx.'+name
|
return 'wx.'+name
|
||||||
@@ -121,6 +125,9 @@ class FixWxPrefix(object):
|
|||||||
names.append(item.name)
|
names.append(item.name)
|
||||||
elif isinstance(item, ast.FunctionDef):
|
elif isinstance(item, ast.FunctionDef):
|
||||||
names.append(item.name)
|
names.append(item.name)
|
||||||
|
elif isinstance(item, ast.AnnAssign):
|
||||||
|
if isinstance(item.target, ast.Name):
|
||||||
|
names.append(item.target.id)
|
||||||
|
|
||||||
names = list()
|
names = list()
|
||||||
filename = 'wx/core.pyi'
|
filename = 'wx/core.pyi'
|
||||||
@@ -136,7 +143,102 @@ class FixWxPrefix(object):
|
|||||||
|
|
||||||
FixWxPrefix._coreTopLevelNames = names
|
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):
|
def ignoreAssignmentOperators(node):
|
||||||
|
|||||||
@@ -28,3 +28,4 @@ markupsafe==1.1.1
|
|||||||
doc2dash==2.3.0
|
doc2dash==2.3.0
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
attrdict3 ; sys_platform == 'win32'
|
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'
|
numpy ; python_version >= '3.0' and python_version < '3.12'
|
||||||
# pillow < 3.0
|
# pillow < 3.0
|
||||||
six
|
six
|
||||||
|
typing-extensions; python_version < '3.10'
|
||||||
Reference in New Issue
Block a user