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

Improve Python type-stubs #2468

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
07de1d5
[etgtools] Disable text wrapping for specific lines in docstrings
lojack5 Oct 17, 2023
d303548
Move local `_cleanName` to `FixWxPrefix.cleanName`
lojack5 Oct 17, 2023
a28de82
Ensure needed imports from `typing` are included in type-stubs
lojack5 Oct 17, 2023
fa2bde4
Extract type information in `makePyArgsString`
lojack5 Oct 17, 2023
7f74a5f
Prepare for changes in generated #define and global variables
lojack5 Oct 18, 2023
6b6b810
Process overloaded functions and methods
lojack5 Oct 17, 2023
2c6100c
Enable return-type annotations
lojack5 Oct 17, 2023
7aad3d4
Better generated properties
lojack5 Oct 18, 2023
3e634c0
Better generated member variables
lojack5 Oct 18, 2023
ec15761
Better generated global variables
lojack5 Oct 18, 2023
e14be4f
Edge case on type-conversions: typdef int wxCoord
lojack5 Oct 18, 2023
e84d7ab
Possible way to handle enums.
lojack5 Oct 18, 2023
3d5290f
Another type-conversion edge case: void -> Any
lojack5 Oct 18, 2023
1fa0df5
Add typing to handwritten code for core.pyi
lojack5 Oct 18, 2023
3958d89
Optional fixing of wx prefix in cleanName
lojack5 Oct 18, 2023
17438ac
Python <3.10 typing compat: unions
lojack5 Oct 18, 2023
2ea917b
Python <3.9 typing compat: list
lojack5 Oct 18, 2023
b23c5a8
Python <3.9 typing compat: tuples
lojack5 Oct 18, 2023
804d3f1
fixup: union commit
lojack5 Oct 18, 2023
1bdc66a
Python <3.10 typing compat: Callable
lojack5 Oct 18, 2023
5167558
Tentative: fix for ParamSpec on Python < 3.10
lojack5 Oct 18, 2023
17cbd02
fixup: Non-generated core.pyi code
lojack5 Oct 18, 2023
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
33 changes: 21 additions & 12 deletions etg/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand All @@ -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

Expand All @@ -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:
Expand All @@ -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
Expand All @@ -421,23 +430,23 @@ 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.

:rtype: bool

"""),

PyFunctionDef('GetResult', '(self)', 'return self.result',
PyFunctionDef('GetResult', '(self) -> _T', 'return self.result',
doc="""\
Returns the value of the callable.

:rtype: a Python object
: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):
Expand All @@ -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")
""")
Expand Down
53 changes: 31 additions & 22 deletions etgtools/extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,28 +462,19 @@ def makePyArgsString(self):
"""
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',
'NULL': 'None',
'wxString()': '""',
'wxArrayString()' : '[]',
'wxArrayInt()' : '[]',
'wxEmptyString': "''", # Makes signatures much shorter
}
if isinstance(self, CppMethodDef):
# rip apart the argsString instead of using the (empty) list of parameters
Expand All @@ -502,7 +493,14 @@ def _cleanName(name):
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)
Expand All @@ -513,25 +511,36 @@ def _cleanName(name):
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):
Expand Down
7 changes: 5 additions & 2 deletions etgtools/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,15 @@ def _allSpaces(text):
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)


Expand Down
Loading
Loading