From: Benjamin Braatz Date: Wed, 22 Sep 2021 12:22:16 +0000 (+0200) Subject: Improvements in Message and Alias. X-Git-Tag: v0.3.0~15 X-Git-Url: http://git.graph-it.com/?a=commitdiff_plain;h=30edc2048e0024ab1cee60eff8dbce9c0396d9d5;p=graphit%2Fcontrolpi.git Improvements in Message and Alias. * Explicitly given sender has higher priortity in Message constructor. * Alias retains everything except explicitly modified keys in 'to' and 'translate'. --- diff --git a/controlpi/messagebus.py b/controlpi/messagebus.py index b320245..4c61817 100644 --- a/controlpi/messagebus.py +++ b/controlpi/messagebus.py @@ -145,15 +145,30 @@ class Message(Dict[str, MessageValue]): {'sender': 'Example sender'} A dictionary can be given to the constructor: - >>> m = Message('Example sender', {'key 1': 'value 1', 'key 2': 'value 2'}) + >>> m = Message('Example sender', + ... {'key 1': 'value 1', 'key 2': 'value 2'}) >>> print(m) {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'} + A 'sender' set in the initial dictionary is overwritten by the explicitly + given sender in the first argument: + >>> m = Message('Example sender', + ... {'sender': 'Original sender', 'key': 'value'}) + >>> print(m) + {'sender': 'Example sender', 'key': 'value'} + Or the message can be modified after construction: >>> m = Message('Example sender', {'key 1': 'value 1'}) >>> m['key 2'] = 'value 2' >>> print(m) {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'} + + The 'sender' key can be overwritten, but this should only be done in + exceptional cases: + >>> m = Message('Example sender', {'key': 'value'}) + >>> m['sender'] = 'New sender' + >>> print(m) + {'sender': 'New sender', 'key': 'value'} """ def __init__(self, sender: str, @@ -168,18 +183,14 @@ class Message(Dict[str, MessageValue]): >>> m = Message('Example sender', {'key 1': 'value 1'}) >>> print(m) {'sender': 'Example sender', 'key 1': 'value 1'} - - The sender can be overwritten by the key-value pairs: - >>> m = Message('Example sender', {'sender': 'Another sender'}) - >>> print(m) - {'sender': 'Another sender'} """ if not isinstance(sender, str): raise TypeError(f"'{sender}' is not a valid sender name" " (not a string).") - self['sender'] = sender + self['sender'] = '' if init is not None: self.update(init) + self['sender'] = sender @staticmethod def check_value(value: MessageValue) -> bool: diff --git a/controlpi_plugins/util.py b/controlpi_plugins/util.py index 9928d18..5834a83 100644 --- a/controlpi_plugins/util.py +++ b/controlpi_plugins/util.py @@ -72,15 +72,7 @@ class Log(BasePlugin): The "filter" key is required: >>> asyncio.run(controlpi.test( ... {"Test Log": {"plugin": "Log"}}, [])) - 'filter' is a required property - - Failed validating 'required' in schema: - {'properties': {'filter': {'items': {'type': 'object'}, - 'type': 'array'}}, - 'required': ['filter']} - - On instance: - {'plugin': 'Log'} + data must contain ['filter'] properties Configuration for 'Test Log' is not valid. The "filter" key has to contain a list of message templates, i.e., @@ -88,13 +80,7 @@ class Log(BasePlugin): >>> asyncio.run(controlpi.test( ... {"Test Log": {"plugin": "Log", ... "filter": [42]}}, [])) - 42 is not of type 'object' - - Failed validating 'type' in schema['properties']['filter']['items']: - {'type': 'object'} - - On instance['filter'][0]: - 42 + data.filter[0] must be object Configuration for 'Test Log' is not valid. """ @@ -157,15 +143,7 @@ class Init(BasePlugin): The "messages" key is required: >>> asyncio.run(controlpi.test( ... {"Test Init": {"plugin": "Init"}}, [])) - 'messages' is a required property - - Failed validating 'required' in schema: - {'properties': {'messages': {'items': {'type': 'object'}, - 'type': 'array'}}, - 'required': ['messages']} - - On instance: - {'plugin': 'Init'} + data must contain ['messages'] properties Configuration for 'Test Init' is not valid. The "messages" key has to contain a list of (partial) messages, i.e., @@ -173,13 +151,7 @@ class Init(BasePlugin): >>> asyncio.run(controlpi.test( ... {"Test Init": {"plugin": "Init", ... "messages": [42]}}, [])) - 42 is not of type 'object' - - Failed validating 'type' in schema['properties']['messages']['items']: - {'type': 'object'} - - On instance['messages'][0]: - 42 + data.messages[0] must be object Configuration for 'Test Init' is not valid. """ @@ -292,11 +264,16 @@ class Alias(BasePlugin): """Translate messages to an alias. The "from" configuration key gets a message template and the - configuration key "to" a (partial) message. All messages matching the - template are received by the Alias instance and a message translated by - removing the keys of the "from" template and adding the keys and values - of the "to" message is sent. Keys that are neither in "from" nor in "to" - are retained. + configuration key "to" a (partial) message. The "translate" + configuration key contains pairs of message keys, where the "from" + message key is translated to the "to" message key if present in the + message. + + All messages matching the "from" template are received by the Alias + instance and a message translated by adding the keys and values of the + "to" message and the translated key-value pairs according to + "translate" is sent. Keys that are not "sender" and not modified by + "to" or "translate" are retained. In the example, the two messages sent by the test are translated by the Alias instance and the translated messages are sent by it preserving @@ -317,41 +294,16 @@ class Alias(BasePlugin): test(): {'sender': 'test()', 'id': 42, 'content': 'Test Message', 'old': 'content'} test(): {'sender': 'Test Alias', 'id': 'translated', - 'content': 'Test Message', 'new': 'content'} + 'content': 'Test Message', 'old': 'content', 'new': 'content'} test(): {'sender': 'test()', 'id': 42, 'content': 'Second Message', 'old': 'content'} test(): {'sender': 'Test Alias', 'id': 'translated', - 'content': 'Second Message', 'new': 'content'} + 'content': 'Second Message', 'old': 'content', 'new': 'content'} - The "from" and "to" keys are required: + The "from" key is required: >>> asyncio.run(controlpi.test( ... {"Test Alias": {"plugin": "Alias"}}, [])) - 'from' is a required property - - Failed validating 'required' in schema: - {'properties': {'from': {'type': 'object'}, - 'to': {'type': 'object'}, - 'translate': {'items': {'properties': {'from': {'type': 'string'}, - 'to': {'type': 'string'}}, - 'type': 'object'}, - 'type': 'array'}}, - 'required': ['from', 'to']} - - On instance: - {'plugin': 'Alias'} - 'to' is a required property - - Failed validating 'required' in schema: - {'properties': {'from': {'type': 'object'}, - 'to': {'type': 'object'}, - 'translate': {'items': {'properties': {'from': {'type': 'string'}, - 'to': {'type': 'string'}}, - 'type': 'object'}, - 'type': 'array'}}, - 'required': ['from', 'to']} - - On instance: - {'plugin': 'Alias'} + data must contain ['from'] properties Configuration for 'Test Alias' is not valid. The "from" key has to contain a message template and the "to" key a @@ -360,52 +312,44 @@ class Alias(BasePlugin): ... {"Test Alias": {"plugin": "Alias", ... "from": 42, ... "to": 42}}, [])) - 42 is not of type 'object' - - Failed validating 'type' in schema['properties']['from']: - {'type': 'object'} - - On instance['from']: - 42 - 42 is not of type 'object' - - Failed validating 'type' in schema['properties']['to']: - {'type': 'object'} - - On instance['to']: - 42 + data.from must be object + Configuration for 'Test Alias' is not valid. + >>> asyncio.run(controlpi.test( + ... {"Test Alias": {"plugin": "Alias", + ... "from": {"id": {"const": 42}}, + ... "to": 42}}, [])) + data.to must be object Configuration for 'Test Alias' is not valid. """ CONF_SCHEMA = {'properties': {'from': {'type': 'object'}, 'to': {'type': 'object'}, 'translate': {'type': 'array', - 'items': {'type': 'object', - 'properties': {'from': {'type': 'string'}, - 'to': {'type': 'string'}}}}}, - 'required': ['from', 'to']} + 'items': + {'type': 'object', + 'properties': + {'from': + {'type': 'string'}, + 'to': + {'type': 'string'}}}}}, + 'required': ['from']} """Schema for Alias plugin configuration. Required configuration keys: - 'from': template of messages to be translated. + + Optional configuration keys: + - 'to': translated message to be sent. + - 'translate': array of pairs of keys to be translated. """ - async def alias(self, message: Message) -> None: - """Translate and send message.""" - alias_message = Message(self.name) - alias_message.update(self.conf['to']) - for key in message: - if key != 'sender' and key not in self.conf['from']: - if key in self._translate: - alias_message[self._translate[key]] = message[key] - else: - alias_message[key] = message[key] - await self.bus.send(alias_message) - def process_conf(self) -> None: """Register plugin as bus client.""" + self._to = {} + if 'to' in self.conf: + self._to = self.conf['to'] self._translate = {} if 'translate' in self.conf: for pair in self.conf['translate']: @@ -415,6 +359,15 @@ class Alias(BasePlugin): [self.conf['from']], self.alias) + async def alias(self, message: Message) -> None: + """Translate and send message.""" + alias_message = Message(self.name, message) + alias_message.update(self._to) + for key in self._translate: + if key in message: + alias_message[self._translate[key]] = message[key] + await self.bus.send(alias_message) + async def run(self) -> None: """Run no code proactively.""" pass