def __init__(self, wrapped, parent=None, services=None): LoopingExecutionContext.__init__(self, wrapped, parent=parent, services=services) WithStatistics.__init__(self, 'in', 'out', 'err') self.input = Input() self.outputs = []
def __init__(self, wrapped, parent): LoopingExecutionContext.__init__(self, wrapped, parent) WithStatistics.__init__(self, 'in', 'out', 'err') self.input = Input() self.outputs = []
class NodeExecutionContext(WithStatistics, LoopingExecutionContext): """ todo: make the counter dependant of parent context? """ @property def alive(self): """todo check if this is right, and where it is used""" return self.input.alive and self._started and not self._stopped def __init__(self, wrapped, parent=None, services=None): LoopingExecutionContext.__init__(self, wrapped, parent=parent, services=services) WithStatistics.__init__(self, 'in', 'out', 'err') self.input = Input() self.outputs = [] def __str__(self): return (('+' if self.alive else '-') + ' ' + self.__name__ + ' ' + self.get_statistics_as_string()).strip() def __repr__(self): stats = self.get_statistics_as_string().strip() return '<{}({}{}){}>'.format( type(self).__name__, '+' if self.alive else '', self.__name__, (' ' + stats) if stats else '', ) def recv(self, *messages): """ Push a message list to this context's input queue. :param mixed value: message """ for message in messages: self.input.put(message) def send(self, value, _control=False): """ Sends a message to all of this context's outputs. :param mixed value: message :param _control: if true, won't count in statistics. """ if not _control: self.increment('out') for output in self.outputs: output.put(value) def get(self): """ Get from the queue first, then increment stats, so if Queue raise Timeout or Empty, stat won't be changed. """ row = self.input.get(timeout=self.PERIOD) self.increment('in') return row def loop(self): while True: try: self.step() except KeyboardInterrupt: raise except InactiveReadableError: break except Empty: sleep(self.PERIOD) continue except Exception as exc: # pylint: disable=broad-except self.handle_error(exc, traceback.format_exc()) def step(self): # Pull data from the first available input channel. """Runs a transformation callable with given args/kwargs and flush the result into the right output channel.""" input_bag = self.get() # todo add timer self.handle_results(input_bag, input_bag.apply(self._stack)) def push(self, bag): # MAKE THIS PUBLIC API FOR CONTEXT PROCESSORS !!! # xxx handle error or send in first call to apply(...)? # xxx return value ? bag.apply(self.handle_error) if is_error(bag) else self.send(bag) def handle_results(self, input_bag, results): # self._exec_time += timer.duration # Put data onto output channels try: results = iter_if_not_sequence(results) except TypeError: # not an iterator if results: self.push(_resolve(input_bag, results)) else: # case with no result, an execution went through anyway, use for stats. # self._exec_count += 1 pass else: while True: # iterator try: result = next(results) except StopIteration: break else: self.push(_resolve(input_bag, result))
def test_input_runlevels(): q = Input() # Before BEGIN, noone should be able to write in an Input queue. assert not q.alive with pytest.raises(InactiveWritableError): q.put('hello, unborn queue.') # Begin q.put(BEGIN) assert q.alive and q._runlevel == 1 q.put('foo') # Second Begin q.put(BEGIN) assert q.alive and q._runlevel == 2 q.put('bar') q.put(END) # FIFO assert q.get() == 'foo' assert q.get() == 'bar' # self.assertEqual(q.alive, False) XXX queue don't know it's dead yet, but it is ... # Async get raises Empty (End is not returned) with pytest.raises(Empty): q.get(block=False) assert q.alive # Before killing, let's slide some data in. q.put('baz') # Now kill the queue... q.put(END) with pytest.raises(InactiveWritableError): q.put('foo') # Still can get remaining data assert q.get() == 'baz' with pytest.raises(InactiveReadableError): q.get()