Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 46 additions & 0 deletions _package/tests/unit_tests/observer_pyt.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,51 @@ def test_update_message(self):
self.assertEqual(message, self.observer.status['message'])


class PartialObserver(Observer):
"""Observer that overrides only on_progress_status and inherits the other handlers."""

def __init__(self):
"""
The __init__ function for the PartialObserver class.
"""
super().__init__()
self.progress = []

def on_progress_status(self, percent_complete):
"""
Record percent complete.

Args:
percent_complete: The percent complete.
"""
self.progress.append(percent_complete)


class TestObserverDefaultHandlers(unittest.TestCase):
"""Test the handlers Observer provides when a subclass does not override them."""

def test_observer_without_overrides(self):
"""
Test that an Observer with no overrides accepts every event without recursing.
"""
observer = Observer()
observer.begin_operation_string('count-jelly-beans')
# progress_status reaches the progress and timing handlers only when it reports
self.assertTrue(observer.progress_status(0.5))
observer.update_message('21 jelly beans counted so far.')
observer.end_operation()

def test_observer_with_partial_overrides(self):
"""
Test that a subclass overriding one handler still gets events while the rest use the defaults.
"""
observer = PartialObserver()
observer.begin_operation_string('count-jelly-beans')
observer.progress_status(0.5)
self.assertEqual([0.5], observer.progress)
observer.update_message('21 jelly beans counted so far.')
observer.end_operation()


if __name__ == '__main__':
unittest.main()
50 changes: 50 additions & 0 deletions _package/tests/unit_tests/progress_listener_pyt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
Test progress_listener.py.
"""
import unittest

from xms.core.misc.progress_listener import set_listener_callback


class TestProgressListener(unittest.TestCase):
"""Test ProgressListener Class."""

def test_on_begin_operation_string(self):
"""
Test that beginning an operation returns and reports stack index 1 for the outermost operation.
"""
messages = []
listener = set_listener_callback(messages.append)
self.assertEqual(1, listener.on_begin_operation_string('count-jelly-beans'))
self.assertEqual([('begin_operation', 1, 'count-jelly-beans')], messages)

def test_nested_operations(self):
"""
Test that the begin stack index tracks how many operations are in flight.
"""
messages = []
listener = set_listener_callback(messages.append)
listener.on_begin_operation_string('count-jelly-beans')
listener.on_begin_operation_string('count-red-jelly-beans')
listener.on_end_operation(2)
listener.on_begin_operation_string('count-green-jelly-beans')
expected = [
('begin_operation', 1, 'count-jelly-beans'),
('begin_operation', 2, 'count-red-jelly-beans'),
('end_operation', 2, ''),
('begin_operation', 2, 'count-green-jelly-beans'),
]
self.assertEqual(expected, messages)

def test_unmatched_end_operation(self):
"""
Test that ending an operation that never began leaves the next operation at stack index 1.
"""
# A Progress made before the listener was installed ends with stack index 0.
listener = set_listener_callback(lambda msg: None)
listener.on_end_operation(0)
self.assertEqual(1, listener.on_begin_operation_string('count-jelly-beans'))


if __name__ == '__main__':
unittest.main()
12 changes: 6 additions & 6 deletions _package/xms/core/misc/observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def on_progress_status(self, percent_complete):
Args:
percent_complete (float): The percent complete
"""
self.on_progress_status(percent_complete)
pass

def on_begin_operation_string(self, operation):
"""
Expand All @@ -30,13 +30,13 @@ def on_begin_operation_string(self, operation):
Args:
operation: Name of the operation being monitored.
"""
self.on_begin_operation_string(operation)
pass

def on_end_operation(self):
"""
The end operation event.
"""
self.on_end_operation()
pass

def on_update_message(self, message):
"""
Expand All @@ -45,7 +45,7 @@ def on_update_message(self, message):
Args:
message: The new message
"""
self.on_update_message(message)
pass

def time_remaining_in_seconds(self, remaining_seconds):
"""
Expand All @@ -54,7 +54,7 @@ def time_remaining_in_seconds(self, remaining_seconds):
Args:
remaining_seconds (Float): The time remaining for the current operation that the class is observing.
"""
self.time_remaining_in_seconds(remaining_seconds)
pass

def time_elapsed_in_seconds(self, elapsed_seconds):
"""
Expand All @@ -63,4 +63,4 @@ def time_elapsed_in_seconds(self, elapsed_seconds):
Args:
elapsed_seconds (Float): The elapsed time since the operation began.
"""
self.time_elapsed_in_seconds(elapsed_seconds)
pass
20 changes: 16 additions & 4 deletions _package/xms/core/misc/progress_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def __init__(self):
The __init__ function for the Observer class.
"""
super().__init__()
# The C++ listener numbers operations by how many are in flight, but only
# passes that number to the later events, so the depth is tracked here.
self._stack_depth = 0

def set_update_delay_seconds(self, delay):
"""
Expand All @@ -52,21 +55,30 @@ def on_begin_operation_string(self, operation):
"""
The operation string.

An override must call super() so the stack index stays right.

Args:
operation: Name of the operation being monitored.

Returns:
(int): stack index for the operation
(int): stack index for the operation, 1 for the outermost operation. The C++
listener keeps its own count and ignores this value.
"""
stack_index = self.on_begin_operation_string(operation)
self.call_back(('begin_operation', stack_index, operation))
return stack_index
self._stack_depth += 1
self.call_back(('begin_operation', self._stack_depth, operation))
return self._stack_depth

def on_end_operation(self, stack_index):
"""
The end operation event.

An override must call super() so the next operation gets the right stack index.

Args:
stack_index (int): stack index for the operation that ended
"""
self.call_back(('end_operation', stack_index, ''))
self._stack_depth = max(0, self._stack_depth - 1)

def on_update_message(self, stack_index, message):
"""
Expand Down
7 changes: 4 additions & 3 deletions pydocs/source/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ report progress. Subclass it and pass an instance to the consumer:
For applications that need to receive progress messages from nested operations,
use :class:`ProgressListener <xms.core.misc.ProgressListener>`. It exposes a
``stack_index`` for each in-flight operation so that a UI can show a stack of
progress bars. Constructing a ``ProgressListener`` registers it as the active
process-wide listener, so subsequent XMS operations route their events through
its callback. The :func:`set_listener_callback
progress bars. The outermost operation has stack index 1, and each operation
nested inside it adds one. Constructing a ``ProgressListener`` registers it as
the active process-wide listener, so subsequent XMS operations route their
events through its callback. The :func:`set_listener_callback
<xms.core.misc.progress_listener.set_listener_callback>` helper builds and
returns one wired to a single callable:

Expand Down
Loading
Loading