예제 #1
0
def test_states():
    states = States()
    states.hello = Field('Hello, world.')
    assert states.hello == 'Hello, world.'
    states.hello = 'Hi'
    assert states.hello == 'Hi'
    states.a_number = 21
    assert states.a_number == 21
예제 #2
0
 def __init__(self, name=None):
     from .connections.registry import ConnectionRegistry
     self._name = validate_name(name) if name else uuid4().hex
     callback = self._trigger_frame_handler
     self.states = States(callback=callback)
     self.events = Events(callback=callback)
     self.messages = Messages(callback=callback)
     self._connections = ConnectionRegistry(self)
     self.inspect_handlers()
예제 #3
0
def test_states_describe():
    states = States()
    states.test = 'yo'
    assert states.describe() == {
        'states': {
            'expects': {},
            'fields': [{
                'field': {
                    'kind': 'Field',
                    'name': 'States.test',
                    'value': 'yo'
                },
                'name': 'test'
            }]
        }
    }
예제 #4
0
def test_states_with_callback_fails():
    def _trigger_frame_handler(frame, handler, internal):
        assert frame
        assert handler
        assert internal
        return handler(frame)

    def test_handler(state):
        assert state.name == 'the_answer'
        assert state.data.value in [42, 0]

    states = States()
    a_handler = Handler(KINDS.STATE, 'the_answer', test_handler)
    states.add_handler('the_answer', handler=a_handler)
    states.the_answer = Field()
    states.callback = _trigger_frame_handler
    assert states.callback == _trigger_frame_handler
    states.the_answer = 42
예제 #5
0
def test_states_fails_for_directly_set_field():
    states = States()
    states.data['whoa'] = 'oh noes!'  # Should not be directly set
    states.whoa = 'oh noes!'  # Fails
예제 #6
0
def test_states_fails_on_unexpected_field():
    states = States()
    states.data['whoa'] = 'oh noes!'  # Should not be directly set
    assert states.whoa == 'oh noes!'  # Fails
예제 #7
0
def test_state_fails_with_invalid_callback_on_init():
    states = States(callback="none shall pass")
예제 #8
0
def test_state_fails_with_invalid_callback():
    states = States()
    states.callback = "none shall pass"
예제 #9
0
def test_states_with_callback():
    def _trigger_frame_handler(frame, handler, internal):
        assert frame
        assert handler
        assert internal
        return handler(frame)

    def test_handler(state):
        assert state.name == 'the_answer'
        assert state.data.value in [42, 0]
        return True

    states = States()
    a_handler = Handler(KINDS.STATE, 'the_answer', test_handler)
    states.add_handler('the_answer', handler=a_handler)
    states.remove_handler('the_answer', handler=a_handler)
    states.add_handler('the_answer', handler=a_handler)
    states.the_answer = Field()
    states.callback = _trigger_frame_handler
    assert states.callback == _trigger_frame_handler
    states.the_answer = 42
    assert states['the_answer'] == 42
    states['the_answer'] = 0
    assert states['the_answer'] == 0
    states['not_the_answer'] = 'lol cats'
    assert states['not_the_answer'] == 'lol cats'
    del states['not_the_answer']
    assert 'not_the_answer' not in states
    del states.the_answer
    assert 'the_answer' not in states
예제 #10
0
class Zentropian(object):
    def __init__(self, name=None):
        from .connections.registry import ConnectionRegistry
        self._name = validate_name(name) if name else uuid4().hex
        callback = self._trigger_frame_handler
        self.states = States(callback=callback)
        self.events = Events(callback=callback)
        self.messages = Messages(callback=callback)
        self._connections = ConnectionRegistry(self)
        self.inspect_handlers()

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = validate_name(name)

    def add_handler(self, handler):
        if handler.kind == KINDS.EVENT:
            self.events.add_handler(handler.name, handler)
        elif handler.kind == KINDS.STATE:
            self.states.add_handler(handler.name, handler)
        elif handler.kind == KINDS.MESSAGE:
            self.messages.add_handler(handler.name, handler)
        else:  # pragma: no cover
            raise ValueError('Unknown handler kind: {}'.format(handler.kind))

    def inspect_handlers(self):
        for attribute_name in dir(self):
            attr = getattr(self, attribute_name)
            if not callable(attr) or not hasattr(attr, 'meta'):
                continue
            for handler in attr.meta:
                self.add_handler(handler)

    def handle_frame(self, frame):
        if isinstance(frame, Event):
            frame, handlers = self.events.match(frame)
        elif isinstance(frame, State):
            frame, handlers = self.states.match(frame)
        elif isinstance(frame, Message):
            frame, handlers = self.messages.match(frame)
        else:
            raise ValueError(
                'Unknown frame {!r} with kind {!r}'
                ''.format(frame.name, KINDS(
                    frame.kind)))  # todo: KINDS might throw an exception?
        for handler in self.apply_filters(handlers):
            self._trigger_frame_handler(frame=frame,
                                        handler=handler,
                                        internal=True)

    def apply_filters(self, handlers):
        handlers_ = set(handlers)
        for handler in handlers:
            for filter_, value in handler.filters.items():
                if self.states.get(filter_, None) == value:
                    continue
                if handler not in handlers_:
                    continue
                handlers_.remove(handler)
        return handlers_

    def handle_return(self, frame, return_value):
        """Original frame and returned value from handler."""
        if isinstance(frame, Event):
            return return_value
        elif isinstance(frame, State):
            return return_value
        elif isinstance(frame, Message):
            if len(return_value) > FRAME_NAME_MAX_LENGTH:
                name = return_value[:FRAME_NAME_MAX_LENGTH - 5] + '...'
            else:
                name = return_value
            self.message(name=name,
                         data={'text': return_value},
                         reply_to=frame.id)
        else:
            raise NotImplementedError()

    def _trigger_frame_handler(self,
                               frame: Frame,
                               handler: Handler,
                               internal=False):
        if isinstance(frame, Message) and frame.source == self.name:
            return
        if isinstance(
                frame, Event
        ) and frame.source != self.name and frame.name.startswith('***'):
            return
        if handler.run_async:
            raise NotImplementedError('Async handlers are not supported '
                                      'by the base Zentropian class. '
                                      'Please use Agent.')
        payload = []  # type: list
        if handler.pass_self:
            payload.append(self)
        if handler.kind != KINDS.TIMER:
            payload.append(frame)
        return_value = handler(*payload)
        return self.handle_return(frame, return_value)

    def on_state(self,
                 name,
                 *,
                 exact=True,
                 parse=False,
                 fuzzy=False,
                 ignore_case=False,
                 **kwargs):
        def wrapper(handler):
            handler_obj = Handler(kind=KINDS.STATE,
                                  name=name,
                                  handler=handler,
                                  exact=exact,
                                  parse=parse,
                                  fuzzy=fuzzy,
                                  ignore_case=ignore_case,
                                  **kwargs)
            self.states.add_handler(name, handler_obj)
            return handler

        return wrapper

    def on_event(self,
                 name,
                 *,
                 exact=True,
                 parse=False,
                 fuzzy=False,
                 ignore_case=False,
                 **kwargs):
        def wrapper(handler):
            handler_obj = Handler(kind=KINDS.EVENT,
                                  name=name,
                                  handler=handler,
                                  exact=exact,
                                  parse=parse,
                                  fuzzy=fuzzy,
                                  ignore_case=ignore_case,
                                  **kwargs)
            self.events.add_handler(name, handler_obj)
            return handler

        return wrapper

    def on_message(self,
                   name,
                   *,
                   exact=True,
                   parse=False,
                   fuzzy=False,
                   ignore_case=True,
                   **kwargs):
        def wrapper(handler):
            handler_obj = Handler(kind=KINDS.MESSAGE,
                                  name=name,
                                  handler=handler,
                                  exact=exact,
                                  parse=parse,
                                  fuzzy=fuzzy,
                                  ignore_case=ignore_case,
                                  **kwargs)
            self.messages.add_handler(name, handler_obj)
            return handler

        return wrapper

    def emit(self, name, data=None, space=None, internal=False, reply_to=None):
        event = self.events.emit(name=name,
                                 data=data,
                                 space=space,
                                 internal=internal,
                                 source=self.name,
                                 reply_to=reply_to)
        if not internal and self._connections.connected:
            self._connections.broadcast(frame=event)
        return event

    def message(self,
                name,
                data=None,
                space=None,
                internal=False,
                reply_to=None):
        message = self.messages.message(name=name,
                                        data=data,
                                        space=space,
                                        internal=internal,
                                        source=self.name,
                                        reply_to=reply_to)
        if not internal and self._connections.connected:
            self._connections.broadcast(frame=message)
        return message

    def connect(self, endpoint, *, auth=None, tag='default'):
        self._connections.connect(endpoint, auth=auth, tag=tag)

    def bind(self, endpoint, *, tag='default'):
        self._connections.bind(endpoint, tag=tag)

    def join(self, space, *, tags: Optional[Union[list, str]] = None):
        self._connections.join(space, tags=tags)

    def leave(self, space, *, tags: Optional[Union[list, str]] = None):
        self._connections.leave(space, tags=tags)

    def close(self,
              *,
              endpoint: Optional[str] = None,
              tags: Optional[Union[list, str]] = None):
        """Closes all connections if no endpoint or tags given."""
        if endpoint and tags:
            raise ValueError('Expected either endpoint: {!r} or tags: {!r}.'
                             ''.format(endpoint, tags))
        elif endpoint:
            connections = self._connections.connections_by_endpoint(endpoint)
        elif tags:
            connections = self._connections.connections_by_tags(tags)
        else:
            connections = self._connections.connections
        for connection in connections:
            connection.close()