-
Notifications
You must be signed in to change notification settings - Fork 85
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
Rework ui_dispatch tests to avoid need for Qt #1792
Conversation
@@ -615,10 +614,10 @@ class FastUITraitChangeNotifyWrapper(TraitChangeNotifyWrapper): | |||
""" | |||
|
|||
def dispatch(self, handler, *args): | |||
if threading.current_thread().ident == ui_thread: | |||
if threading.current_thread() == threading.main_thread(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the fix that should have been part of #1740.
@@ -153,11 +159,7 @@ def _get_handlers(self): | |||
thread. | |||
""" | |||
thread_local = self.thread_local | |||
if isinstance(thread_local, dict): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drive-by cleanup: this condition is never true in modern Python.
@flongford Do you have bandwidth for review? This is part of a longer-term goal of insulating Traits from changes in Qt / PySide, and eventually decoupling Traits and TraitsUI entirely. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Thanks for the explanation of the code changes @mdickinson. The approach to separate the concept of "a ui handler" from "Qt's ui handler" looks like what we want here. The asyncio
package also seems like an appropriate choice for creating a a test "ui handler".
I could mostly follow along with the what's being changed, though I've left some comments for my own understanding.
""" | ||
UI handler that dispatches to the asyncio event loop. | ||
""" | ||
event_loop.call_soon_threadsafe(lambda: handler(*args, **kwargs)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat!
else: | ||
ui_handler(handler, *args) | ||
_ui_handler(handler, *args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also guard against _ui_handler
being undefined here, as we do in ui_dispatch
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we definitely should! Now I have to go figure out why the tests aren't exercising this ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I think they are exercising this, but our tests for the exception don't distinguish between a RuntimeError
with a nice message and a TypeError
from trying to use None
as a callable. I need to re-raise exceptions and catch them ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworked in cc8653e
""" Tests for dynamic notifiers with `dispatch='ui'` set by method call. | ||
""" | ||
class TestMethodUINotifiers( | ||
BaseTestUINotifiers, unittest.IsolatedAsyncioTestCase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, TIL about unittest.IsolatedAsyncioTestCase
!
|
||
obj = self.obj_factory() | ||
def test_notification_from_separate_thread_failure_case(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understand the context for this test case, but just to be clear: do we accept that any background process unknown to the UI will be able to modify objects, but ideally we also want to know about it?
If so, is this a new edge case that we can just test more easily without Qt, or does this happen often?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the key point of the test (and of the dispatch="ui"
functionality) is that even though the object is modified off the main thread, the trait change handlers are run in the main UI thread.
That said, this pattern isn't ideal in the first place; my preference is to use something like Traits Futures so that we're not modifying the same object from multiple threads in the first place.
Not sure whether this answers your question or not ... ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see - so the main result of this test is that we don't see any notifiers and we observe an exception, since the handlers are always on the main UI tread.
That helps understand the context, yes, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right - the notifiers don't get executed at all in this case.
@flongford I've responded to comments above and made one minor update, for better exception handling. |
This should not have been done: Pyface directly accesses |
In #1792 we made `traits.trait_notifiers.ui_handler` private, renaming it to `_ui_handler`. However, it turns out that Pyface wants to access the value of `ui_handler` directly, so this change breaks current Pyface. This PR reverts that change. Pyface code links: - https://github.com/enthought/pyface/blob/46f700999284c8104fb2a5468f549677dfadf063/pyface/ui/qt/init.py#L15 - https://github.com/enthought/pyface/blob/46f700999284c8104fb2a5468f549677dfadf063/pyface/ui/wx/init.py#L14
In #1792 we made `traits.trait_notifiers.ui_handler` private, renaming it to `_ui_handler`. However, it turns out that Pyface wants to access the value of `ui_handler` directly, so this change breaks current Pyface. This PR reverts that change. Pyface code links: - https://github.com/enthought/pyface/blob/46f700999284c8104fb2a5468f549677dfadf063/pyface/ui/qt/init.py#L15 - https://github.com/enthought/pyface/blob/46f700999284c8104fb2a5468f549677dfadf063/pyface/ui/wx/init.py#L14 (cherry picked from commit 347e58b)
Since #1788, we have only one test module that makes use of the Qt event loop. That test module contains tests for the behaviour of handlers that use
dispatch='ui'
mechanism to redispatch off-thread notifications to the ui thread.This PR reworks that test module, with some significant collateral damage along the way.
In detail:
test_ui_notifiers
) to avoid the need for the Qt event loop; instead, it tests against aui_handler
based on asyncio, which redispatches to the running asyncio event loopget_ui_handler
counterpart toset_ui_handler
, and exposes both functions intraits.api
get_ui_handler
andset_ui_handler
trait_notifiers
:ui_handler
has been made private, whileui_thread
is removed altogetherthreading.local()
being a dict (which it hasn't been in living memory), and tidying up some uses of thread identity.