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

Add wx toolkit support #246

Merged
merged 26 commits into from
Nov 26, 2020
Merged

Add wx toolkit support #246

merged 26 commits into from
Nov 26, 2020

Conversation

mdickinson
Copy link
Member

@mdickinson mdickinson commented Nov 25, 2020

[Code extracted from #173; includes the commits from #245]

Following #231, this PR adds support for the wxPython toolkit, providing wx-specific implementations of the Pinger, Pingee and GuiTestAssistant classes.

Known issues / tasks:

  • At least one of the examples (using a progress bar) is Qt-specific; we need to either make it work on wx, or have it give a clear error message in that case.
  • We're getting segfaults with Travis CI on Linux, likely due to some incompatibility between the wxPython-provided wheel and the system that it's running on. I haven't yet found the cause of these, but if we can establish that they're not directly related to the PR, we may want to simply skip that CI job, or mark it as an allowed failure. (To check: are TraitsUI and Pyface running into the same issue? If so, how are they dealing with this?)

ci/__main__.py Outdated Show resolved Hide resolved
ci/__main__.py Outdated Show resolved Hide resolved
@mdickinson
Copy link
Member Author

Here's what Pyface is doing for wx on Linux: https://github.com/enthought/pyface/blob/0d1516e60051e52251627123891dff9085737f4c/etstool.py#L215-L219

Looks the same as what we have here, except for the version restriction (< 4.1). I'll try that.

@mdickinson
Copy link
Member Author

The faulthandler output is interesting: it shows clearly that the gui_test_assistant for the null toolkit is being used, indicating that the segfault was likely at import time.

@mdickinson
Copy link
Member Author

The pi example fails because of a wxPython 4 compatibility issue in enable (fixed by enthought/enable#403).

Traceback (most recent call last):
  File "examples/pi_iterations.py", line 20, in <module>
    from chaco.api import ArrayPlotData, Plot
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/chaco/api.py", line 38, in <module>
    from .abstract_plot_renderer import AbstractPlotRenderer
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/chaco/abstract_plot_renderer.py", line 7, in <module>
    from .plot_component import PlotComponent
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/chaco/plot_component.py", line 4, in <module>
    from enable.api import Component
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/enable/api.py", line 21, in <module>
    from .markers import MarkerTrait, marker_trait, MarkerNameDict, marker_names, \
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/enable/markers.py", line 18, in <module>
    from .compiled_path import CompiledPath
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/enable/compiled_path.py", line 17, in <module>
    from .toolkit import toolkit_object
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/enable/toolkit.py", line 47, in <module>
    _init_toolkit()
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/enable/toolkit.py", line 35, in _init_toolkit
    __import__(backend)
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/enable/wx/quartz.py", line 14, in <module>
    from .base_window import BaseWindow
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/enable/wx/base_window.py", line 22, in <module>
    from .constants import (
  File "/Users/mdickinson/.edm/envs/traits-futures-py36-wxpython/lib/python3.6/site-packages/enable/wx/constants.py", line 127, in <module>
    wx.WXK_NEXT,
AttributeError: module 'wx' has no attribute 'WXK_NEXT'

@mdickinson
Copy link
Member Author

The "<4.1" in "wxPython<4.1" is problematic with EDM on Windows - the command passed to the EDM executable is not quoted properly by the EDM batch file, so it ends up interpreting the "<4.1" as a shell redirect.

One solution is to use an EDM bootstrap environment: then the invocation of edm from within that bootstrap environment doesn't go through a batch file.

We've fixed the same issue for Traits previously in enthought/traits#512

@mdickinson
Copy link
Member Author

mdickinson commented Nov 25, 2020

Still having trouble with the quoting and redirection on Windows. If the "wxPython<4.1" argument is not quoted, we get the following traceback, where the "cannot find the file specified" presumably means that the system is looking for a file called "4.1":

The system cannot find the file specified.
Traceback (most recent call last):
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\projects\traits-futures\ci\__main__.py", line 446, in <module>
    cli(prog_name="python -m ci")
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\site-packages\click\core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\site-packages\click\core.py", line 717, in main
    rv = self.invoke(ctx)
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\site-packages\click\core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\site-packages\click\core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\site-packages\click\core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "C:\projects\traits-futures\ci\__main__.py", line 127, in build
    pyenv.python(wxpython_install_cmd)
  File "C:\projects\traits-futures\ci\python_environment.py", line 252, in python
    return self.run(["python"] + command, capture)
  File "C:\projects\traits-futures\ci\python_environment.py", line 204, in run
    return self.edm(cmd)
  File "C:\projects\traits-futures\ci\python_environment.py", line 72, in edm
    return subprocess.check_call(self._edm_base_command + command)
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\subprocess.py", line 311, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['C:\\Users\\appveyor\\.edm\\envs\\bootstrap\\Scripts\\edm.EXE', '--config', 'C:\\projects\\traits-futures\\ci\\data\\edm.yml', 'run', '--environment', 'traits-futures-py36-wxpython', '--', 'python', '-m', 'pip', 'install', 'wxPython<4.1']' returned non-zero exit status 1.

If it is quoted, we get instead:

ERROR: Invalid requirement: '"wxPython<4.1"'
Traceback (most recent call last):
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\projects\traits-futures\ci\__main__.py", line 446, in <module>
    cli(prog_name="python -m ci")
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\site-packages\click\core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\site-packages\click\core.py", line 717, in main
    rv = self.invoke(ctx)
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\site-packages\click\core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\site-packages\click\core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\site-packages\click\core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "C:\projects\traits-futures\ci\__main__.py", line 127, in build
    pyenv.python(wxpython_install_cmd)
  File "C:\projects\traits-futures\ci\python_environment.py", line 252, in python
    return self.run(["python"] + command, capture)
  File "C:\projects\traits-futures\ci\python_environment.py", line 204, in run
    return self.edm(cmd)
  File "C:\projects\traits-futures\ci\python_environment.py", line 72, in edm
    return subprocess.check_call(self._edm_base_command + command)
  File "C:\Users\appveyor\.edm\envs\bootstrap\lib\subprocess.py", line 311, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['C:\\Users\\appveyor\\.edm\\envs\\bootstrap\\Scripts\\edm.EXE', '--config', 'C:\\projects\\traits-futures\\ci\\data\\edm.yml', 'run', '--environment', 'traits-futures-py36-wxpython', '--', 'python', '-m', 'pip', 'install', '"wxPython<4.1"']' returned non-zero exit status 1.

@rahulporuri
Copy link
Contributor

i'm not entirely sure why you're sticking with wxPython < 4.1 because traitsui doesn't place that restriction - https://github.com/enthought/traitsui/blob/b73c475fbf311465c244fe8a1e5b7a3aa16c294f/etstool.py#L254-L263

@mdickinson
Copy link
Member Author

i'm not entirely sure why you're sticking with wxPython < 4.1

I'm getting segfaults on Linux with wxPython 4.1, and no segfaults with wxPython < 4.1.

@mdickinson
Copy link
Member Author

I'm getting segfaults on Linux with wxPython 4.1, and no segfaults with wxPython < 4.1.

No longer. Thanks @kitchoi for pointing out the packages I was missing!

@@ -45,42 +45,58 @@ def tearDown(self):
def test_run_until_timeout(self):
# Trait never fired, condition never true.
dummy = Dummy()

start_time = time.monotonic()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to reviewer: the changes in this file aren't specific to wx, and could be moved to a separate PR. They're regression tests for a bug I discovered in the wx implementation of the GuiTestAssistant.

@rahulporuri
Copy link
Contributor

rahulporuri commented Nov 26, 2020

Note to reviewer: the changes in this file aren't specific to wx, and could be moved to a separate PR.

@mdickinson is this PR ready for review? on second thought, I might not be the best person to review this PR because there are a lot of wx-specific pieces which look alien to me.

@mdickinson
Copy link
Member Author

@rahulporuri Not ready for review yet - I'm trying to replace my own timer with the official Pyface timer, but the official Pyface timer is segfaulting.

@mdickinson
Copy link
Member Author

I'm trying to replace my own timer with the official Pyface timer, but the official Pyface timer is segfaulting.

Giving up on this for now, at least for the purposes of this PR (I'll continue to investigate and open an issue in pyface if I find a way to consistently reproduce).

@rahulporuri Yes, it's ready for review! I suspect that the wx-specific pieces are going to look alien to anyone other than Robin Dunn. :-)

@rahulporuri rahulporuri marked this pull request as ready for review November 26, 2020 09:14
@mdickinson
Copy link
Member Author

For making the examples work under wx, I'll open a new issue; I don't think it has to happen in this PR. The current status is:

  • the squares example works
  • the pi example runs into an Enable issue that's fixed by a pending PR: WxPython 4 Fixes enable#403
  • the primes example explicitly uses Qt machinery

Copy link
Contributor

@rahulporuri rahulporuri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a few questions/comments. The wx-specific code wasn't too hard to track down/understand.

traits_futures/wx/gui_test_assistant.py Show resolved Hide resolved
"""
Send a ping to the ping receiver.
"""
event = _PingEvent(-1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason why we set event_id to -1? It doesn't matter what integer value we use, IIUC.
Also, this might be overkill but do we want to raise an error if event_id is not an integer? Looks like wx.PostEvent is not thread-safe for event objects having string fields.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks as though I don't actually need this at all. I'll remove it.

@mdickinson
Copy link
Member Author

do you want to create an issue or add a fixme here about replacing this timer with the pyface timer?

Done. I've opened issues both on the pyface repo and here.

Copy link
Contributor

@rahulporuri rahulporuri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still LGTM

wx.EvtHandler.__init__(self)
self._on_ping = on_ping
self._binder = wx.PyEventBinder(_PING_EVENT_TYPE, 1)
self.Bind(self._binder, self._on_ping_event)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a Bind without a corresponding Unbind bothers me a bit here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the Unbind call needs to happen during garbage collection so we will need to override Pinger.__del__?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a Bind without a corresponding Unbind bothers me a bit here.

Same here, and I'd be tempted to add it back. If there is a reason Unbinding should cause issues, maybe it deserves a comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a case of adding it back, because it was never there, and the machinery to support "I'm done with this Pinger" isn't currently there. We'll need a `Pinger.close() method, or something similar.

Given that the lack of unbind isn't actually causing issues, I think I'll leave it as is for this PR, and then add the Pinger.close functionality in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IOW, there's currently no place in the code structure to put that Unbind call. I can fix that with a refactor, but it'll be messier than just adding wx support.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the Unbind call needs to happen during garbage collection so we will need to override Pinger.__del__?

I don't have any evidence that it needs to happen at all, and likely everything that needs to happen is already taken care of at garbage collection time. It's just the lack of the explicit counterpart that bothered me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And no, I definitely don't want to override __del__. __del__ methods are evil. :-) If we're going to unbind, it should be done at a time of our choosing rather than whenever the g.c. happens to feel like it.

…lear what it does, and we don't seem to need it.
@mdickinson
Copy link
Member Author

For making the examples work under wx, I'll open a new issue

Opened #253.

@mdickinson
Copy link
Member Author

Thanks for all the review comments! I think the only thing outstanding is the Unbind. I'm going to merge this, then open a PR with a tiny bit of restructuring that lets us call Unbind.

@mdickinson
Copy link
Member Author

One last pre-merge change: I removed the note in the documentation about the lack of wxPython support.

@mdickinson mdickinson merged commit 1cb1ce3 into master Nov 26, 2020
@mdickinson mdickinson deleted the feature/wxpython-support branch November 26, 2020 12:44
@mdickinson mdickinson mentioned this pull request Nov 26, 2020
6 tasks
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.

3 participants