def method_test(self): """Test if a method can be correctly connected to a signal.""" signal = Signal() foo = FooClass() self.assertIsNone(foo.var) # connect the signal signal.connect(foo.set_var) # trigger the signal signal.emit("bar") # check if the callback triggered correctly self.assertEqual(foo.var, "bar") # try to trigger the signal again signal.emit("baz") self.assertEqual(foo.var, "baz") # now try to disconnect the signal signal.disconnect(foo.set_var) # check that calling the signal again # no longer triggers the callback signal.emit("anaconda") self.assertEqual(foo.var, "baz")
def lambda_test(self): """Test if a lambda can be correctly connected to a signal.""" foo = FooClass() signal = Signal() self.assertIsNone(foo.var) # connect the signal # pylint: disable=unnecessary-lambda lambda_instance = lambda x: foo.set_var(x) signal.connect(lambda_instance) # trigger the signal signal.emit("bar") # check if the callback triggered correctly self.assertEqual(foo.var, "bar") # try to trigger the signal again signal.emit("baz") self.assertEqual(foo.var, "baz") # now try to disconnect the signal signal.disconnect(lambda_instance) # check that calling the signal again # no longer triggers the callback signal.emit("anaconda") self.assertEqual(foo.var, "baz")
def function_test(self): """Test if a local function can be correctly connected to a signal.""" # create a local function def set_var(value): self.var = value signal = Signal() self.assertIsNone(self.var) # connect the signal signal.connect(set_var) # trigger the signal signal.emit("bar") # check if the callback triggered correctly self.assertEqual(self.var, "bar") # try to trigger the signal again signal.emit("baz") self.assertEqual(self.var, "baz") # now try to disconnect the signal signal.disconnect(set_var) # check that calling the signal again # no longer triggers the callback signal.emit("anaconda") self.assertEqual(self.var, "baz")
class TaskQueue(BaseTask): """TaskQueue represents a queue of TaskQueues or Tasks. TaskQueues and Tasks can be mixed in a single TaskQueue. """ def __init__(self, name, status_message=None): super(TaskQueue, self).__init__(name=name) self._status_message = status_message self._current_task_number = None self._current_queue_number = None # the list backing this TaskQueue instance self._list = [] # triggered if a TaskQueue contained in this one was started/completed self.queue_started = Signal() self.queue_completed = Signal() # triggered when a task is started self.task_started = Signal() self.task_completed = Signal() # connect to the task & queue started signals for # progress reporting purposes self.queue_started.connect(self._queue_started_cb) self.task_started.connect(self._task_started_cb) @synchronized def _queue_started_cb(self, *args): self._current_queue_number += 1 @synchronized def _task_started_cb(self, *args): self._current_task_number += 1 @property def status_message(self): """A status message describing the Queue is trying to achieve. Eq. "Converting all foo into bar." The current main usecase is to set the ProgressHub status message when a TaskQueue is started. :returns: a status message :rtype: str """ return self._status_message @property @synchronized def queue_count(self): """Returns number of TaskQueues contained in this and all nested TaskQueues. :returns: number of queues :rtype: int """ queue_count = 0 for item in self: # count only queues if isinstance(item, TaskQueue): # count the queue itself queue_count += 1 # and its contents queue_count += item.queue_count return queue_count @property @synchronized def task_count(self): """Returns number of tasks contained in this and all nested TaskQueues. :returns: number of tasks :rtype: int """ task_count = 0 for item in self: if isinstance(item, Task): # count tasks task_count += 1 elif isinstance(item, TaskQueue): # count tasks in nested queues task_count += item.task_count return task_count @property @synchronized def current_task_number(self): """Number of the currently running task (if any). :returns: number of the currently running task (if any) :rtype: int or None if no task is currently running """ return self._current_task_number @property @synchronized def current_queue_number(self): """Number of the currently running task queue (if any). :returns: number of the currently running task queue (if any) :rtype: int or None if no task queue is currently running """ return self._current_queue_number @property @synchronized def progress(self): """Task queue processing progress. The progress is reported as a floating point number from 0.0 to 1.0. :returns: task queue processing progress :rtype: float """ if self.current_task_number: return self.task_count / self.current_task_number else: return 0.0 @property @synchronized def summary(self): """Return a multi-line summary of the contents of the task queue. :returns: summary of task queue contents :rtype: str """ if self.parent is None: message = "Top-level task queue: %s\n" % self.name # this is the top-level queue, so add some "global" stats message += "Number of task queues: %d\n" % self.queue_count message += "Number of tasks: %d\n" % self.task_count message += "Task & task group listing:\n" else: message = "Task queue: %s\n" % self.name for item in self: for line in item.summary.splitlines(): message += " %s\n" % line # remove trailing newlines from the top level message if self.parent is None and message[-1] == "\n": message = message.rstrip("\n") return message @property @synchronized def parent(self): """The parent task queue of this task queue (if any). :returns: parent of this task queue (if any) :rtype: TaskQueue instance or None """ return self._parent @parent.setter @synchronized def parent(self, parent_item): # check if a parent is already set if self._parent is not None: # disconnect from the previous parent first self.started.disconnect(self._parent.queue_started.emit) self.completed.disconnect(self._parent.queue_completed.emit) self.queue_started.disconnect(self._parent.queue_started.emit) self.queue_completed.disconnect(self._parent.queue_completed.emit) self.task_started.disconnect(self._parent.task_started.emit) self.task_completed.disconnect(self._parent.task_completed.emit) # set the parent self._parent = parent_item # Connect own signals "up" to the parent, # so that it is possible to monitor how all nested TaskQueues and Tasks # are running from the top-level element. # connect own start/completion signal to parents queue start/completion signal self.started.connect(self._parent.queue_started.emit) self.completed.connect(self._parent.queue_completed.emit) # propagate start/completion signals from nested queues/tasks self.queue_started.connect(self._parent.queue_started.emit) self.queue_completed.connect(self._parent.queue_completed.emit) self.task_started.connect(self._parent.task_started.emit) self.task_completed.connect(self._parent.task_completed.emit) def start(self): """Start processing of the task queue.""" do_start = False with self._lock: # the task queue can only be started once if self.running or self.done: if self.running: # attempt to start a task that is already running log.error("Can't start task queue %s - already running.") else: # attempt to start a task that an already finished task log.error("Can't start task queue %s - already done.") else: do_start = True self._running = True if self.task_count: # only set the initial task number if we have some tasks self._current_task_number = 0 self._current_queue_number = 0 else: log.warning( "Attempting to start an empty task queue (%s).", self.name) if do_start: # go over all task groups and their tasks in order self.started.emit(self) if len(self) == 0: log.warning("The task group %s is empty.", self.name) for item in self: # start the item (TaskQueue/Task) item.start() # we are done, set the task queue state accordingly with self._lock: self._running = False self._done = True # also set the current task variables accordingly as we no longer process a task self._current_task_number = None self._current_queue_number = None # trigger the "completed" signals self.completed.emit(self) # implement the Python list "interface" and make sure parent is always # set to a correct value @synchronized def append(self, item): item.parent = self self._list.append(item) @synchronized def insert(self, index, item): item.parent = self self._list.insert(index, item) @synchronized def __setitem__(self, index, item): item.parent = self return self._list.__setitem__(index, item) @synchronized def __len__(self): return self._list.__len__() @synchronized def count(self): return self._list.count() @synchronized def __getitem__(self, ii): return self._list[ii] @synchronized def __delitem__(self, index): self._list[index].parent = None del self._list[index] @synchronized def pop(self): item = self._list.pop() item.parent = None return item @synchronized def clear(self): for item in self._list: item.parent = None self._list.clear()
class TaskQueue(BaseTask): """TaskQueue represents a queue of TaskQueues or Tasks. TaskQueues and Tasks can be mixed in a single TaskQueue. """ def __init__(self, name, status_message=None): super(TaskQueue, self).__init__(name=name) self._status_message = status_message self._current_task_number = None self._current_queue_number = None # the list backing this TaskQueue instance self._list = [] # triggered if a TaskQueue contained in this one was started/completed self.queue_started = Signal() self.queue_completed = Signal() # triggered when a task is started self.task_started = Signal() self.task_completed = Signal() # connect to the task & queue started signals for # progress reporting purposes self.queue_started.connect(self._queue_started_cb) self.task_started.connect(self._task_started_cb) @synchronized def _queue_started_cb(self, *args): self._current_queue_number += 1 @synchronized def _task_started_cb(self, *args): self._current_task_number += 1 @property def status_message(self): """A status message describing the Queue is trying to achieve. Eq. "Converting all foo into bar." The current main usecase is to set the ProgressHub status message when a TaskQueue is started. :returns: a status message :rtype: str """ return self._status_message @property @synchronized def queue_count(self): """Returns number of TaskQueues contained in this and all nested TaskQueues. :returns: number of queues :rtype: int """ queue_count = 0 for item in self: # count only queues if isinstance(item, TaskQueue): # count the queue itself queue_count += 1 # and its contents queue_count += item.queue_count return queue_count @property @synchronized def task_count(self): """Returns number of tasks contained in this and all nested TaskQueues. :returns: number of tasks :rtype: int """ task_count = 0 for item in self: if isinstance(item, Task): # count tasks task_count += 1 elif isinstance(item, TaskQueue): # count tasks in nested queues task_count += item.task_count return task_count @property @synchronized def current_task_number(self): """Number of the currently running task (if any). :returns: number of the currently running task (if any) :rtype: int or None if no task is currently running """ return self._current_task_number @property @synchronized def current_queue_number(self): """Number of the currently running task queue (if any). :returns: number of the currently running task queue (if any) :rtype: int or None if no task queue is currently running """ return self._current_queue_number @property @synchronized def progress(self): """Task queue processing progress. The progress is reported as a floating point number from 0.0 to 1.0. :returns: task queue processing progress :rtype: float """ if self.current_task_number: return self.task_count / self.current_task_number else: return 0.0 @property @synchronized def summary(self): """Return a multi-line summary of the contents of the task queue. :returns: summary of task queue contents :rtype: str """ if self.parent is None: message = "Top-level task queue: %s\n" % self.name # this is the top-level queue, so add some "global" stats message += "Number of task queues: %d\n" % self.queue_count message += "Number of tasks: %d\n" % self.task_count message += "Task & task group listing:\n" else: message = "Task queue: %s\n" % self.name for item in self: for line in item.summary.splitlines(): message += " %s\n" % line # remove trailing newlines from the top level message if self.parent is None and message[-1] == "\n": message = message.rstrip("\n") return message @property @synchronized def parent(self): """The parent task queue of this task queue (if any). :returns: parent of this task queue (if any) :rtype: TaskQueue instance or None """ return self._parent @parent.setter @synchronized def parent(self, parent_item): # check if a parent is already set if self._parent is not None: # disconnect from the previous parent first self.started.disconnect(self._parent.queue_started.emit) self.completed.disconnect(self._parent.queue_completed.emit) self.queue_started.disconnect(self._parent.queue_started.emit) self.queue_completed.disconnect(self._parent.queue_completed.emit) self.task_started.disconnect(self._parent.task_started.emit) self.task_completed.disconnect(self._parent.task_completed.emit) # set the parent self._parent = parent_item # Connect own signals "up" to the parent, # so that it is possible to monitor how all nested TaskQueues and Tasks # are running from the top-level element. # connect own start/completion signal to parents queue start/completion signal self.started.connect(self._parent.queue_started.emit) self.completed.connect(self._parent.queue_completed.emit) # propagate start/completion signals from nested queues/tasks self.queue_started.connect(self._parent.queue_started.emit) self.queue_completed.connect(self._parent.queue_completed.emit) self.task_started.connect(self._parent.task_started.emit) self.task_completed.connect(self._parent.task_completed.emit) def start(self): """Start processing of the task queue.""" do_start = False with self._lock: # the task queue can only be started once if self.running or self.done: if self.running: # attempt to start a task that is already running log.error("Can't start task queue %s - already running.") else: # attempt to start a task that an already finished task log.error("Can't start task queue %s - already done.") else: do_start = True self._running = True if self.task_count: # only set the initial task number if we have some tasks self._current_task_number = 0 self._current_queue_number = 0 else: log.warning("Attempting to start an empty task queue (%s).", self.name) if do_start: # go over all task groups and their tasks in order self.started.emit(self) if len(self) == 0: log.warning("The task group %s is empty.", self.name) for item in self: # start the item (TaskQueue/Task) item.start() # we are done, set the task queue state accordingly with self._lock: self._running = False self._done = True # also set the current task variables accordingly as we no longer process a task self._current_task_number = None self._current_queue_number = None # trigger the "completed" signals self.completed.emit(self) # implement the Python list "interface" and make sure parent is always # set to a correct value @synchronized def append(self, item): item.parent = self self._list.append(item) @synchronized def insert(self, index, item): item.parent = self self._list.insert(index, item) @synchronized def __setitem__(self, index, item): item.parent = self return self._list.__setitem__(index, item) @synchronized def __len__(self): return self._list.__len__() @synchronized def count(self): return self._list.count() @synchronized def __getitem__(self, ii): return self._list[ii] @synchronized def __delitem__(self, index): self._list[index].parent = None del self._list[index] @synchronized def pop(self): item = self._list.pop() item.parent = None return item @synchronized def clear(self): for item in self._list: item.parent = None self._list.clear()