Пример #1
0
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))
Пример #2
0
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()