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
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()
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' }] } }
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
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
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
def test_state_fails_with_invalid_callback_on_init(): states = States(callback="none shall pass")
def test_state_fails_with_invalid_callback(): states = States() states.callback = "none shall pass"
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
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()