-
Notifications
You must be signed in to change notification settings - Fork 521
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
Python type-stubs problems #2665
Comments
Hi! Yeah, the system used for generating type-stubs isn't perfect at all. Most of the info is first grabbed from the wxWidgets source code, then transformed via sip as a wrapper into Python. The type information is almost entirely generated from the C++ signatures. So I'll go through these, but not as an excuse but more a documenting why it currently happens, and asking for if there might be a way to get this information on to the etg scripts that can then use the information:
wxWindowMSW(wxWindow *parent,
wxWindowID id,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0,
const wxString& name = wxASCII_STR(wxPanelNameStr))
class Test:
@property
def a(self) -> int: ...
@a.setter
def a(self, value: int) -> None: ...
def _get_b(self) -> int: ...
def _set_b(self, value: int) -> None: ...
b = property(_get_b, _set_b)
t = Test()
t.a # inferred as int
t.b # inferred as Any
Anyway, that's the current state, and I'm only familiar with the part of the code that takes what etg passes off to the typestub script. I'm guessing 3, 4, 5 are probably fixable relatively easily. 6 Might be fixable with a custom overload added in the sip wrapper? |
I think I know how to fix 4&5, plus possibly 3, so I can take care of those. |
OK, I thought I had an idea of how to fix 4&5, but it didn't work. The issue is because these
My initial thought was that we could change these to |
Hmm, I don't know enough about the process there to know the best way to fix that. I was thinking about 3 some more, it will take a little work (meaning, make sure the property generating part has access to the return types of the getter), but I could probably make it generate properties like this instead: Before: ...
Width = property(GetWidth, SetWidth)
... After: ...
@property
def Width(self) -> int: ...
@Width.setter
def Width(self, width: int, /) -> None: ...
... With appropriate checks for whether there are setters/getters (a few only have a setter, a few only have a getter). The only exception here is for setter-only properties it'll still have to be: ...
setter_only_property = property(None, SetterMethod)
... At least this way most of the properties exposed will be type-inferred correctly. Though there's still the case where wxPython accepts some types that automatically get converted to the right type, as an example, these are all valid: window.SetBackgroundColour('black')
window.SetBackgroundColour((0, 0, 0))
window.SetBackgroundColour(wx.Colour(0, 0, 0)) But the generated type-stub only has the |
To point 1:
Maybe it is somehow possible to build in a detection whether the current class inherits from However, this point bothers me the most of all... Currently you can only use the following workaround to avoid getting an error message (or you can use import typing
import wx
NONE_WINDOW = typing.cast(wx.Window, None)
wx.Frame(NONE_WINDOW) To point 2: import typing
Tgetter = typing.TypeVar("Tgetter")
Tsetter = typing.TypeVar("Tsetter")
class _wxProperty(property, typing.Generic[Tsetter, Tgetter]):
fget: typing.Callable[[Any], Tgetter] | None
fset: typing.Callable[[Any, Tsetter], None] | None
def __init__(
self,
fget: typing.Callable[[Any], Tgetter] | None = None,
fset: typing.Callable[[Any, Tsetter], None] | None = None,
) -> None: ...
def __get__(self, instance: Any, owner: type | None = None, /) -> Tgetter: ...
def __set__(self, instance: Any, value: Tsetter, /) -> None: ... |
I'll have to try to setup a build environment again, when I tried recently I was having issues with doxygen detection (building on windows is sort of a "figure it out" kind of situation). |
I remember reading that typing properties was still a bit harder currently. When improving the typing annotations, I would like to suggest to compare to the typing files that sip can generate by enabling [tool.sip.bindings.{base}]
docstrings = true
release-gil = true
exceptions = false
tracing = {tracing}
protected-is-public = false
generate-extracts = [\'{extracts}\']
pep484-pyi = true They are really clean and detailed, and as .pyi shouldn't contain docstrings (* see note), they don't. One difference between these sip-generated ones and the ones manually made up here is that the sip-generated ones are for the extension modules that sip generates. So they are exact, but only for these extension modules. Notice the underscores: they match the .so files (on linux). The non underscore .pyi files, like core.ypi, adv.pyi, aui.pyi, are .pyi files matching the additionnal .py files that wxPython creates to wrap these "private" extension modules. So sip couldn't generate the .pyi files for the wrapper of the sip-generated wrapper. My exploration of last weekend isn't finished, but I was trying to get the best of both worlds by getting the wxPython PI generator to output the classes as subclasses of the types in the extension modules (the ones with underscores). Overloads with correct types appear (from the sip-generated files), and docstrings (from wxPython PI generator) are kept, at least for vscode. I tried out at first by importing the sip-generated .pyi files into my container's site-packages, then manually editing the files to see the result. (*): .pyi shouldn't contain docstrings, generally... Though in the case here, since the implementation part of the code isn't really python, and the extension modules don't have their docstrings generated there (I assume, as clearing the .pyi of some classes didn't allow my IDE to find them from the classes), the docstrings here are the only way I see for now to have some docs, and it's perfectly fine. |
I have 3) fixed on a local copy, digging in to see if it's feasible to fix 1) currently (it's...complicated). @echoix If you have experience making sip generate the type-stubs, I'd be more than happy to see that personally (can't speak for the maintainers). I'd assume it'd have better accuracy and correctness than what the current homebrew(?) script generates. |
My experience with sip specifically starts only since saturday, but you can take a look at these by downloading the deist artifacts of the runs here: echoix#3 They are accurate (until proven otherwise) for the compiled extension modules. For example, extra methods implemented in Python attached to it after loading aren't included, as sip wasn't involved there |
Regarding TopLevelWindow.__init__ (self, parent: Optional[Window], id: int=ID_ANY, title: str='', pos: Point=DefaultPosition, size: Size=DefaultSize, style: int=DEFAULT_FRAME_STYLE, name: str=FrameNameStr) -> None
Dialog.__init__ (self, parent: Optional[Window], id: int=ID_ANY, title: str='', pos: Point=DefaultPosition, size: Size=DefaultSize, style: int=DEFAULT_DIALOG_STYLE, name: str=DialogNameStr) -> None
DirDialog.__init__ (self, parent: Optional[Window], message: str=DirSelectorPromptStr, defaultPath: str='', style: int=DD_DEFAULT_STYLE, pos: Point=DefaultPosition, size: Size=DefaultSize, name: str=DirDialogNameStr) -> None
FileDialog.__init__ (self, parent: Optional[Window], message: str=FileSelectorPromptStr, defaultDir: str='', defaultFile: str='', wildcard: str=FileSelectorDefaultWildcardStr, style: int=FD_DEFAULT_STYLE, pos: Point=DefaultPosition, size: Size=DefaultSize, name: str=FileDialogNameStr) -> None
Frame.__init__ (self, parent: Optional[Window], id: int=ID_ANY, title: str='', pos: Point=DefaultPosition, size: Size=DefaultSize, style: int=DEFAULT_FRAME_STYLE, name: str=FrameNameStr) -> None
MessageDialog.__init__ (self, parent: Optional[Window], message: str, caption: str=MessageBoxCaptionStr, style: int=OK|CENTRE, pos: Point=DefaultPosition) -> None
GenericMessageDialog.__init__ (self, parent: Optional[Window], message: str, caption: str=MessageBoxCaptionStr, style: int=OK|CENTRE, pos: Point=DefaultPosition) -> None
ColourDialog.__init__ (self, parent: Optional[Window], data: Optional[ColourData]=None) -> None
MultiChoiceDialog.__init__ (self, parent: Optional[Window], message: str, caption: str, choices: List[str], style: int=CHOICEDLG_STYLE, pos: Point=DefaultPosition) -> None
MultiChoiceDialog.__init__ (self, parent: Optional[Window], message: str, caption: str, n: int, choices: str, style: int=CHOICEDLG_STYLE, pos: Point=DefaultPosition) -> None
SingleChoiceDialog.__init__ (self, parent: Optional[Window], message: str, caption: str, choices: List[str], style: int=CHOICEDLG_STYLE, pos: Point=DefaultPosition) -> None
FindReplaceDialog.__init__ (self, parent: Optional[Window], data: FindReplaceData, title: str='', style: int=0) -> None
MDIParentFrame.__init__ (self, parent: Optional[Window], id: int=ID_ANY, title: str='', pos: Point=DefaultPosition, size: Size=DefaultSize, style: int=DEFAULT_FRAME_STYLE|VSCROLL|HSCROLL, name: str=FrameNameStr) -> None
MDIChildFrame.__init__ (self, parent: Optional[MDIParentFrame], id: int=ID_ANY, title: str='', pos: Point=DefaultPosition, size: Size=DefaultSize, style: int=DEFAULT_FRAME_STYLE, name: str=FrameNameStr) -> None
FontDialog.__init__ (self, parent: Optional[Window]) -> None
FontDialog.__init__ (self, parent: Optional[Window], data: FontData) -> None
RearrangeDialog.__init__ (self, parent: Optional[Window], message: str, title: str='', order: List[int]=[], items: List[str]=[], pos: Point=DefaultPosition, name: str=RearrangeDialogNameStr) -> None
MiniFrame.__init__ (self, parent: Optional[Window], id: int=ID_ANY, title: str='', pos: Point=DefaultPosition, size: Size=DefaultSize, style: int=CAPTION|RESIZE_BORDER, name: str=FrameNameStr) -> None
TextEntryDialog.__init__ (self, parent: Optional[Window], message: str, caption: str=GetTextFromUserPromptStr, value: str='', style: int=TextEntryDialogStyle, pos: Point=DefaultPosition) -> None
PasswordEntryDialog.__init__ (self, parent: Optional[Window], message: str, caption: str=GetPasswordFromUserPromptStr, defaultValue: str='', style: int=TextEntryDialogStyle, pos: Point=DefaultPosition) -> None
NumberEntryDialog.__init__ (self, parent: Optional[Window], message: str, prompt: str, caption: str, value: int, min: int, max: int, pos: Point=DefaultPosition) -> None
PreviewFrame.__init__ (self, preview: PrintPreview, parent: Optional[Window], title: str="PrintPreview", pos: Point=DefaultPosition, size: Size=DefaultSize, style: int=DEFAULT_FRAME_STYLE, name: str=FrameNameStr) -> None
PrintAbortDialog.__init__ (self, parent: Optional[Window], documentTitle: str, pos: Point=DefaultPosition, size: Size=DefaultSize, style: int=DEFAULT_DIALOG_STYLE, name: str="dialog") -> None Are there any false positives here? Any missing methods that should be detected as needing the Detection is done by finding subclasses of TopLevelWindow with |
I would like you others to take a look... Maybe just a small one at lojacks code/request. This would be important to a certain extent. |
Operating system: Windows 11
wxPython version & source: wxPython-4.2.3a1.dev5812+c3092be4-cp311-cp311-win_amd64
Python version & source: Python 3.11.6
Description of the problem:
Thanks a lot @lojack5 for creating #2468 and @swt2c for merging it. I tried the above mentioned snapshot in my codebase and found the following problems with VSCode and pylance v2024.12.1:
In
wx.Frame
,wx.Dialog
and every class that inherits from it, the parameterparent
can also beNone
and not onlywx.Window
.Properties are not correctly typed. Eg.
wx.Size().GetWidth()
isint
, butwx.Size().Width
is of typeAny
.wx.TIMER_CONTINUOUS
&wx.TIMER_ONE_SHOT
should bebool
instead ofint
, because inTimer.Start(...)
the parameteroneShot
is alsobool
wx.TreeItemId.__bool__()
has to returnbool
instead ofint
wx.dataview.DataViewItem.__bool__()
has to returnbool
instead ofint
wx.DateTime
can be constructed fromdatetime.datetime
anddatetime.time
, so an__init__
overload should be available for these types.The text was updated successfully, but these errors were encountered: