Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Type stub improvements #2668

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open

Type stub improvements #2668

wants to merge 13 commits into from

Conversation

lojack5
Copy link
Contributor

@lojack5 lojack5 commented Jan 12, 2025

Follow up to #2468 , addressing issues brought up in #2665 and #2666 . (I finally figured out my problem with running etg without --nodoc, so now I can finally test the doc building warnings).

Issues addressed:

  • sphinx generator "SEVERE" warnings during signature validation:
    • Over-aggressive C++ syntax removal (ex: constraint -> raint) no longer happens.
    • Special case handle parameters named _from, _is, and _def (Python reserved keywords)
    • Most other warnings are handled by correctly factoring in (and removing) that the type-hints are present in the args string.
  • __bool__ returning int: Special case handling done in the writing of the method. I'm not sure why we can't just change the definition in the sip file, but sip complains that it must return an int.
  • None allowed for parent argument on TopLevelWindow subclasses.
  • Marks values coming from #define directives which are boolean as bool.
  • Properties declared using the variable = property(<getter>, <setter>) syntax not being seen as the applicable type. This is a typeshed issue (and not one easily solve). Addressed by using decorator syntax when a getter and type-hints are present on the getter and/or setter (setter-only properties still have to use the variable = property(fset=<setter>) syntax.
  • sphinx generator warning for SetAssertMode. The clean-name code was inappropriately removing applying the "remove wx prefix" code to argument names:
==> Function/Method signature from `extractors`: SetAssertMode(AppAssertMode : AppAssertMode)
==> Parameter list from wxWidgets XML items:     ['wxAppAssertMode']
  • wx.DateTime accepts datetime.datetime and datetime.date objects in its constructor. Similarly, wx.Point, wx.Size, and other similar automated conversions are handled.

Things I'd like extra eyes on:

  • None for parent argument: Are any of the changed type-stubs incorrect in allowing None there? Are there any missing?
  • Help with potentially addressing the wx.DateTime typing.

Closes #2665
Closes #2666

- Remove typehints from argsString before checking signature
- lie and detect `_from`, `_is`, and `_def` as `from`, `is`, and `def for
  signature validating purposes
- Fix: don't remove const from identifiers containing const (eg: constraint)
This one has probably been in the generated type-stub for ages.
One of the base classes in the generated stub was generated as `RichTextLine*`.
@swt2c
Copy link
Collaborator

swt2c commented Jan 12, 2025

Thanks a lot @lojack5 this looks good.

As far as the wx.DateTime issue goes, I think it's basically anywhere where a wx.DateTime can be passed as a parameter to the C++ side, a datetime.datetime or datetime.date can also be passed. Does that help with figuring out a place to do it?

@lojack5
Copy link
Contributor Author

lojack5 commented Jan 13, 2025

Maybe.

Does the conversion added happen anywhere a wx.DateTime is accepted as an input to any method, or only wx.DateTime method? For example does the conversion happen on wx.adv.CalendarCtrl.__init__'s date parameter? If so I might be able to do something with that. If not, then it's a lot more special casing that it might be pretty hard to get right.

If so, I could probably do something like:

DateTimeT: TypeAlias = Union[wx.DateTime, datetime.datetime, datetime.date]

Then wx.DateTime -> DateTimeT for input argument annotations only (ie, be careful not to do this replacement on return type annotations, etc).

Follow on, should something similar be done for wx.Point, wx.Size, and wx.Colour?. Like:

PointT: TypeAlias = Union[wx.Point, tuple[int, int]]
SizeT: TypeAlias = Union[wx.Size, tuple[int, int]]
ColourT: TypeAlias = Union[wx.Colour, tuple[int, int, int], tuple[int, int, int, int], str]

@echoix
Copy link
Contributor

echoix commented Jan 13, 2025

Note that typealias should come from typing_extensions for Python 3.9 (minimum supported version here), as it was only introduced in 3.10.

It's possible to use type aliases on Python 3.9, just without using the semicolon and TypeAlias, and directly using equal the thing it is aliasing.

@lojack5
Copy link
Contributor Author

lojack5 commented Jan 13, 2025

And yes, typing_extensions is already required for < Python 3.10.

@echoix
Copy link
Contributor

echoix commented Jan 13, 2025

What started my interest in improving wxPython typing a couple weeks ago was specifically because of the typing for Point and Size. So I'm interested in any improvement for that.

I remember trying hard to find where the conversion from class to tuple happens, but it wasn't clear yet.
This is close to the behaviour I was looking for

// can be created from a sequence of numbers, such as wx.Point, wx.Colour,

Phoenix/etg/gdicmn.py

Lines 243 to 252 in c3092be

# Because of our add-ons that make wx.Point and wx.Size act like 2-element
# sequences, and also the typecheck code that allows 2-element sequences, then
# we end up with a bit of confusion about the (Point,Point) and the
# (Point,Size) overloads of the wx.Rect constructor. The confusion can be
# dealt with by using keyword args, but I think that the (Point,Size) version
# will be used more, so reorder the overloads so it is found first.
m = module.find('wxRect.wxRect')
mo = m.findOverload('topLeft')
del m.overloads[m.overloads.index(mo)]
m.overloads.append(mo)

@lojack5
Copy link
Contributor Author

lojack5 commented Jan 13, 2025

They're in the sip generating files as far as I can tell:

So I guess adding things for all of these is on the table. I'll see if there's a more general way to do this, instead of hard-coding all of them.

@swt2c
Copy link
Collaborator

swt2c commented Jan 13, 2025

Maybe.

Does the conversion added happen anywhere a wx.DateTime is accepted as an input to any method, or only wx.DateTime method? For example does the conversion happen on wx.adv.CalendarCtrl.__init__'s date parameter? If so I might be able to do something with that. If not, then it's a lot more special casing that it might be pretty hard to get right.

Yes, it does. Passing datetime.date to wx.adv.CalendarCtrl works.

@lojack5
Copy link
Contributor Author

lojack5 commented Jan 13, 2025

Gotcha, I'll start working on a solution that should encompass all types with a .convertFromPyObject attribute. I did a quick glance through and it doesn't seem anything uses the similar-but-reversed .convertToPyObject.

Pulls out the data into a separate class, `tweaker_tools.Signature`.
This simplifies stringizing the args string, and allows for some
checks to be written in a much less complex way. The motivator for
this was adding unions to type-hints (for the upcoming automatically
converted python->wx types). This made parsing the already stringized
args string very complex to be able to handle the potential for commas
in `Union`.
@lojack5
Copy link
Contributor Author

lojack5 commented Jan 17, 2025

Ok, I'm ready for review of this. In particular if @timrid can find anymore edge cases to fixup, now would be a great time to find them :). Otherwise, I'm pretty sure this handles most cases correctly now. There are still some edge cases I found that aren't exactly easy to fix, but from what I can tell they are on rarely used interfaces.

Still would like a yes/no on the None for parent changes, just a look though to make sure classes that have parent marked as Optional[...] do in fact accept a None for that argument.

@lojack5
Copy link
Contributor Author

lojack5 commented Jan 17, 2025

Oh, actually there were two points I was unsure about:
wx.InputStream and wx.OutputStream both have a .convertFromPyObject set on them, although the comments in the cpp code there ask if this is only a type-check. I couldn't find precise documentation on exactly what python type would be deemed acceptable as an automated conversion here. Is it a specific python type? Or is it a more general "file-like" object - ie: supports a specific interface, like "must have read, tell, and seek? Possibly something that I could solve with a typing.Protocol type-hint there.

This handles any type with a defined `.convertFromPyObject` set in its
sip generator.
@timrid
Copy link
Contributor

timrid commented Jan 17, 2025

I downloaded the wxPython-wheel-win64-py3.13-x64 wheel from https://github.com/wxWidgets/Phoenix/actions/runs/12824937831 and tested it in my codebase. My findings:

  1. wx.Yield(), wx.MessageBox(...) and many more should have no self parameter.
    image

  2. In wx.Menu() the Method DestroyItem is missing. It was renamed to Destroy, but the comment is still correct:
    image

  3. In wx.ComboBox() there is a similar problem with GetTextSelection that is renamed to GetSelection:
    image

  4. Missing GetClientData() in wx.Choice(), wx.ListBox(), ... because it is missing in wx.ItemContainer()
    image

  5. I could not see any problem with parent set to None any more :)

@lojack5
Copy link
Contributor Author

lojack5 commented Jan 17, 2025

Thanks, I know what's happening with the functions (vs methods) - I can fix that quickly. The others, not so sure why they're getting renamed, so I'll have to look into that.

@lojack5
Copy link
Contributor Author

lojack5 commented Jan 17, 2025

Ok, those should be sorted now if you want to take another whirl at it (once the CI jobs complete).

The only one I couldn't reproduce was 3):
type-stub for ListBox:
image
and for ItemContainer:
image

@lojack5
Copy link
Contributor Author

lojack5 commented Jan 17, 2025

Also - I take back everything else working as intended - types that are automatically converted are only partially covered, because each generating file is fired up in a separate Python instance, meaning for example the wx.Point -> wx.Point | tuple[int, int] conversion only happens in the the generating file that defines wx.Point itself. This one might be tricky, because I need the scripts to remember these conversions between instances of it running.

@lojack5
Copy link
Contributor Author

lojack5 commented Jan 22, 2025

Sorry for the delay on this was having some computer issues (frequent BSOD) that I had to fix. Should get back into finishing this up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Warnings when building documentation after type stubs changes Python type-stubs problems
4 participants