From 514e80ee035e02bfc162c4edc224d6c2d8f187e3 Mon Sep 17 00:00:00 2001 From: Benjamin Braatz Date: Mon, 9 Mar 2026 11:52:55 +0100 Subject: [PATCH] Update API documentation. --- doc/api/controlpi.html | 704 ++++ doc/api/controlpi/baseplugin.html | 933 +++++ doc/api/controlpi/messagebus.html | 5480 +++++++++++++++++++++++++ doc/api/controlpi/pluginregistry.html | 684 +++ doc/api/controlpi_plugins.html | 241 ++ doc/api/controlpi_plugins/state.html | 3256 +++++++++++++++ doc/api/controlpi_plugins/util.html | 2680 ++++++++++++ doc/api/controlpi_plugins/wait.html | 1585 +++++++ doc/api/index.html | 223 + doc/api/search.js | 46 + doc/controlpi/baseplugin.html | 621 --- doc/controlpi/index.html | 476 --- doc/controlpi/messagebus.html | 4244 ------------------- doc/controlpi/pluginregistry.html | 429 -- doc/controlpi_plugins/index.html | 70 - doc/controlpi_plugins/state.html | 1884 --------- doc/controlpi_plugins/util.html | 1421 ------- doc/controlpi_plugins/wait.html | 604 --- doc/index.md | 40 +- setup.py | 8 - 20 files changed, 15843 insertions(+), 9786 deletions(-) create mode 100644 doc/api/controlpi.html create mode 100644 doc/api/controlpi/baseplugin.html create mode 100644 doc/api/controlpi/messagebus.html create mode 100644 doc/api/controlpi/pluginregistry.html create mode 100644 doc/api/controlpi_plugins.html create mode 100644 doc/api/controlpi_plugins/state.html create mode 100644 doc/api/controlpi_plugins/util.html create mode 100644 doc/api/controlpi_plugins/wait.html create mode 100644 doc/api/index.html create mode 100644 doc/api/search.js delete mode 100644 doc/controlpi/baseplugin.html delete mode 100644 doc/controlpi/index.html delete mode 100644 doc/controlpi/messagebus.html delete mode 100644 doc/controlpi/pluginregistry.html delete mode 100644 doc/controlpi_plugins/index.html delete mode 100644 doc/controlpi_plugins/state.html delete mode 100644 doc/controlpi_plugins/util.html delete mode 100644 doc/controlpi_plugins/wait.html diff --git a/doc/api/controlpi.html b/doc/api/controlpi.html new file mode 100644 index 0000000..855274c --- /dev/null +++ b/doc/api/controlpi.html @@ -0,0 +1,704 @@ + + + + + + + controlpi API documentation + + + + + + + + + +
+
+

+controlpi

+ +

Provide the infrastructure for the ControlPi system.

+ +

The infrastructure consists of the message bus from module messagebus, the +plugin registry from module pluginregistry and the abstract base plugin from +module baseplugin.

+ +

The package combines them in its run function, which is used by __main__.py +to run a ControlPi system based on a configuration file indefinitely.

+ +

The test function is a utility function to test plugins with minimal +boilerplate code.

+
+ + + + + +
  1"""Provide the infrastructure for the ControlPi system.
+  2
+  3The infrastructure consists of the message bus from module messagebus, the
+  4plugin registry from module pluginregistry and the abstract base plugin from
+  5module baseplugin.
+  6
+  7The package combines them in its run function, which is used by __main__.py
+  8to run a ControlPi system based on a configuration file indefinitely.
+  9
+ 10The test function is a utility function to test plugins with minimal
+ 11boilerplate code.
+ 12"""
+ 13
+ 14import asyncio
+ 15import fastjsonschema
+ 16
+ 17from controlpi.messagebus import MessageBus, Message, MessageTemplate
+ 18from controlpi.pluginregistry import PluginRegistry
+ 19from controlpi.baseplugin import BasePlugin, PluginConf, ConfException
+ 20
+ 21from typing import Dict, List, Coroutine, Any
+ 22
+ 23
+ 24CONF_SCHEMA = {"type": "object", "patternProperties": {".*": {"type": "object"}}}
+ 25
+ 26
+ 27def _process_conf(
+ 28    message_bus: MessageBus, conf: Dict[str, PluginConf]
+ 29) -> List[Coroutine]:
+ 30    try:
+ 31        conf = fastjsonschema.validate(CONF_SCHEMA, conf)
+ 32    except fastjsonschema.JsonSchemaException as e:
+ 33        print(f"Configuration not valid:\n{e}")
+ 34        return []
+ 35    plugins = PluginRegistry("controlpi_plugins", BasePlugin)
+ 36    coroutines = [message_bus.run()]
+ 37    for instance_name in conf:
+ 38        instance_conf = conf[instance_name]
+ 39        if "plugin" not in instance_conf:
+ 40            print(f"No plugin implementation specified for instance '{instance_name}'.")
+ 41            continue
+ 42        plugin_name = instance_conf["plugin"]
+ 43        if plugin_name not in plugins:
+ 44            print(
+ 45                f"No implementation found for plugin '{plugin_name}'"
+ 46                f" (specified for instance '{instance_name}')."
+ 47            )
+ 48            continue
+ 49        plugin = plugins[plugin_name]
+ 50        try:
+ 51            instance = plugin(message_bus, instance_name, instance_conf)
+ 52            coroutines.append(instance.run())
+ 53        except ConfException as e:
+ 54            print(e)
+ 55            continue
+ 56    return coroutines
+ 57
+ 58
+ 59async def run(conf: Dict[str, PluginConf]) -> None:
+ 60    """Run the ControlPi system based on a configuration.
+ 61
+ 62    Setup message bus, process given configuration, and run message bus and
+ 63    plugins concurrently and indefinitely.
+ 64
+ 65    This function is mainly used by __main__.py to run a ControlPi system
+ 66    based on a configuration loaded from a configuration JSON file on disk.
+ 67
+ 68    >>> async def test_coroutine():
+ 69    ...     conf = {"Example Init":
+ 70    ...             {"plugin": "Init",
+ 71    ...              "messages": [{"id": 42,
+ 72    ...                            "content": "Test Message"},
+ 73    ...                           {"id": 42.42,
+ 74    ...                            "content": "Second Message"}]},
+ 75    ...             "Example Log":
+ 76    ...             {"plugin": "Log",
+ 77    ...              "filter": [{"sender": {"const": "Example Init"}}]}}
+ 78    ...     run_task = asyncio.create_task(run(conf))
+ 79    ...     await asyncio.sleep(0.1)
+ 80    ...     run_task.cancel()
+ 81    ...     try:
+ 82    ...         await run_task
+ 83    ...     except asyncio.exceptions.CancelledError:
+ 84    ...         pass
+ 85    >>> asyncio.run(test_coroutine())  # doctest: +NORMALIZE_WHITESPACE
+ 86    Example Log: {'sender': 'Example Init',
+ 87                  'id': 42, 'content': 'Test Message'}
+ 88    Example Log: {'sender': 'Example Init',
+ 89                  'id': 42.42, 'content': 'Second Message'}
+ 90    """
+ 91    message_bus = MessageBus()
+ 92    coroutines = _process_conf(message_bus, conf)
+ 93    try:
+ 94        await asyncio.gather(*coroutines)
+ 95    except asyncio.CancelledError:
+ 96        pass
+ 97
+ 98
+ 99async def test(
+100    conf: Dict[str, PluginConf], messages: List[Dict[str, Any]], wait: float = 0.0
+101) -> None:
+102    """Test configuration of ControlPi system.
+103
+104    Setup message bus, process given configuration, run message bus and
+105    plugins concurrently, send given messages on message bus and print all
+106    messages on message bus. Terminate when queue of message bus is empty.
+107
+108    This function allows to test single plugins or small plugin
+109    configurations with minimal boilerplate code:
+110    >>> asyncio.run(test(
+111    ...     {"Example Init": {"plugin": "Init",
+112    ...                       "messages": [{"id": 42,
+113    ...                                     "content": "Test Message"},
+114    ...                                    {"id": 42.42,
+115    ...                                     "content": "Second Message"}]}},
+116    ...     [{"target": "Example Init",
+117    ...       "command": "execute"}]))  # doctest: +NORMALIZE_WHITESPACE
+118    test(): {'sender': '', 'event': 'registered',
+119             'client': 'Example Init', 'plugin': 'Init',
+120             'sends': [{'id': {'const': 42},
+121                        'content': {'const': 'Test Message'}},
+122                       {'id': {'const': 42.42},
+123                        'content': {'const': 'Second Message'}}],
+124             'receives': [{'target': {'const': 'Example Init'},
+125                           'command': {'const': 'execute'}}]}
+126    test(): {'sender': 'Example Init',
+127             'id': 42, 'content': 'Test Message'}
+128    test(): {'sender': 'Example Init',
+129             'id': 42.42, 'content': 'Second Message'}
+130    test(): {'sender': 'test()', 'target': 'Example Init',
+131             'command': 'execute'}
+132    test(): {'sender': 'Example Init',
+133             'id': 42, 'content': 'Test Message'}
+134    test(): {'sender': 'Example Init',
+135             'id': 42.42, 'content': 'Second Message'}
+136
+137    Similar functionality could be reached by using the Log and Init plugins
+138    to print messages and send some messages on the bus, but these would
+139    clutter the test configuration and code to stop the indefinitely running
+140    bus would have to be added to each and every test.
+141
+142    Incorrect plugin configurations can also be tested by this:
+143    >>> asyncio.run(test(
+144    ...     {"Example Init": {"plugin": "Init"}}, []))
+145    data must contain ['messages'] properties
+146    Configuration for 'Example Init' is not valid.
+147    """
+148    message_bus = MessageBus()
+149
+150    async def log(message):
+151        if (
+152            "sender" in message
+153            and message["sender"] == ""
+154            and "event" in message
+155            and message["event"] == "registered"
+156            and "client" in message
+157            and message["client"] == "test()"
+158        ):
+159            # Do not log own registration of 'test()':
+160            return
+161        print(f"test(): {message}")
+162
+163    message_bus.register(
+164        "test()", "Test", [MessageTemplate()], [([MessageTemplate()], log)]
+165    )
+166
+167    coroutines = _process_conf(message_bus, conf)
+168    background_tasks = set()
+169    for coroutine in coroutines:
+170        task = asyncio.create_task(coroutine)
+171        background_tasks.add(task)
+172        task.add_done_callback(background_tasks.discard)
+173        # Give the created task opportunity to run:
+174        await asyncio.sleep(0)
+175    for message in messages:
+176        await message_bus.send(Message("test()", message))
+177        # Give immediate reactions to messages opportunity to happen:
+178        await asyncio.sleep(0)
+179    await asyncio.sleep(wait)
+180    await message_bus._queue.join()
+
+ + +
+
+
+ CONF_SCHEMA = +{'type': 'object', 'patternProperties': {'.*': {'type': 'object'}}} + + +
+ + + + +
+
+ +
+ + async def + run(conf: Dict[str, Dict[str, Any]]) -> None: + + + +
+ +
60async def run(conf: Dict[str, PluginConf]) -> None:
+61    """Run the ControlPi system based on a configuration.
+62
+63    Setup message bus, process given configuration, and run message bus and
+64    plugins concurrently and indefinitely.
+65
+66    This function is mainly used by __main__.py to run a ControlPi system
+67    based on a configuration loaded from a configuration JSON file on disk.
+68
+69    >>> async def test_coroutine():
+70    ...     conf = {"Example Init":
+71    ...             {"plugin": "Init",
+72    ...              "messages": [{"id": 42,
+73    ...                            "content": "Test Message"},
+74    ...                           {"id": 42.42,
+75    ...                            "content": "Second Message"}]},
+76    ...             "Example Log":
+77    ...             {"plugin": "Log",
+78    ...              "filter": [{"sender": {"const": "Example Init"}}]}}
+79    ...     run_task = asyncio.create_task(run(conf))
+80    ...     await asyncio.sleep(0.1)
+81    ...     run_task.cancel()
+82    ...     try:
+83    ...         await run_task
+84    ...     except asyncio.exceptions.CancelledError:
+85    ...         pass
+86    >>> asyncio.run(test_coroutine())  # doctest: +NORMALIZE_WHITESPACE
+87    Example Log: {'sender': 'Example Init',
+88                  'id': 42, 'content': 'Test Message'}
+89    Example Log: {'sender': 'Example Init',
+90                  'id': 42.42, 'content': 'Second Message'}
+91    """
+92    message_bus = MessageBus()
+93    coroutines = _process_conf(message_bus, conf)
+94    try:
+95        await asyncio.gather(*coroutines)
+96    except asyncio.CancelledError:
+97        pass
+
+ + +

Run the ControlPi system based on a configuration.

+ +

Setup message bus, process given configuration, and run message bus and +plugins concurrently and indefinitely.

+ +

This function is mainly used by __main__.py to run a ControlPi system +based on a configuration loaded from a configuration JSON file on disk.

+ +
+
>>> async def test_coroutine():
+...     conf = {"Example Init":
+...             {"plugin": "Init",
+...              "messages": [{"id": 42,
+...                            "content": "Test Message"},
+...                           {"id": 42.42,
+...                            "content": "Second Message"}]},
+...             "Example Log":
+...             {"plugin": "Log",
+...              "filter": [{"sender": {"const": "Example Init"}}]}}
+...     run_task = asyncio.create_task(run(conf))
+...     await asyncio.sleep(0.1)
+...     run_task.cancel()
+...     try:
+...         await run_task
+...     except asyncio.exceptions.CancelledError:
+...         pass
+>>> asyncio.run(test_coroutine())  # doctest: +NORMALIZE_WHITESPACE
+Example Log: {'sender': 'Example Init',
+              'id': 42, 'content': 'Test Message'}
+Example Log: {'sender': 'Example Init',
+              'id': 42.42, 'content': 'Second Message'}
+
+
+
+ + +
+
+ +
+ + async def + test( conf: Dict[str, Dict[str, Any]], messages: List[Dict[str, Any]], wait: float = 0.0) -> None: + + + +
+ +
100async def test(
+101    conf: Dict[str, PluginConf], messages: List[Dict[str, Any]], wait: float = 0.0
+102) -> None:
+103    """Test configuration of ControlPi system.
+104
+105    Setup message bus, process given configuration, run message bus and
+106    plugins concurrently, send given messages on message bus and print all
+107    messages on message bus. Terminate when queue of message bus is empty.
+108
+109    This function allows to test single plugins or small plugin
+110    configurations with minimal boilerplate code:
+111    >>> asyncio.run(test(
+112    ...     {"Example Init": {"plugin": "Init",
+113    ...                       "messages": [{"id": 42,
+114    ...                                     "content": "Test Message"},
+115    ...                                    {"id": 42.42,
+116    ...                                     "content": "Second Message"}]}},
+117    ...     [{"target": "Example Init",
+118    ...       "command": "execute"}]))  # doctest: +NORMALIZE_WHITESPACE
+119    test(): {'sender': '', 'event': 'registered',
+120             'client': 'Example Init', 'plugin': 'Init',
+121             'sends': [{'id': {'const': 42},
+122                        'content': {'const': 'Test Message'}},
+123                       {'id': {'const': 42.42},
+124                        'content': {'const': 'Second Message'}}],
+125             'receives': [{'target': {'const': 'Example Init'},
+126                           'command': {'const': 'execute'}}]}
+127    test(): {'sender': 'Example Init',
+128             'id': 42, 'content': 'Test Message'}
+129    test(): {'sender': 'Example Init',
+130             'id': 42.42, 'content': 'Second Message'}
+131    test(): {'sender': 'test()', 'target': 'Example Init',
+132             'command': 'execute'}
+133    test(): {'sender': 'Example Init',
+134             'id': 42, 'content': 'Test Message'}
+135    test(): {'sender': 'Example Init',
+136             'id': 42.42, 'content': 'Second Message'}
+137
+138    Similar functionality could be reached by using the Log and Init plugins
+139    to print messages and send some messages on the bus, but these would
+140    clutter the test configuration and code to stop the indefinitely running
+141    bus would have to be added to each and every test.
+142
+143    Incorrect plugin configurations can also be tested by this:
+144    >>> asyncio.run(test(
+145    ...     {"Example Init": {"plugin": "Init"}}, []))
+146    data must contain ['messages'] properties
+147    Configuration for 'Example Init' is not valid.
+148    """
+149    message_bus = MessageBus()
+150
+151    async def log(message):
+152        if (
+153            "sender" in message
+154            and message["sender"] == ""
+155            and "event" in message
+156            and message["event"] == "registered"
+157            and "client" in message
+158            and message["client"] == "test()"
+159        ):
+160            # Do not log own registration of 'test()':
+161            return
+162        print(f"test(): {message}")
+163
+164    message_bus.register(
+165        "test()", "Test", [MessageTemplate()], [([MessageTemplate()], log)]
+166    )
+167
+168    coroutines = _process_conf(message_bus, conf)
+169    background_tasks = set()
+170    for coroutine in coroutines:
+171        task = asyncio.create_task(coroutine)
+172        background_tasks.add(task)
+173        task.add_done_callback(background_tasks.discard)
+174        # Give the created task opportunity to run:
+175        await asyncio.sleep(0)
+176    for message in messages:
+177        await message_bus.send(Message("test()", message))
+178        # Give immediate reactions to messages opportunity to happen:
+179        await asyncio.sleep(0)
+180    await asyncio.sleep(wait)
+181    await message_bus._queue.join()
+
+ + +

Test configuration of ControlPi system.

+ +

Setup message bus, process given configuration, run message bus and +plugins concurrently, send given messages on message bus and print all +messages on message bus. Terminate when queue of message bus is empty.

+ +

This function allows to test single plugins or small plugin +configurations with minimal boilerplate code:

+ +
+
>>> asyncio.run(test(
+...     {"Example Init": {"plugin": "Init",
+...                       "messages": [{"id": 42,
+...                                     "content": "Test Message"},
+...                                    {"id": 42.42,
+...                                     "content": "Second Message"}]}},
+...     [{"target": "Example Init",
+...       "command": "execute"}]))  # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Example Init', 'plugin': 'Init',
+         'sends': [{'id': {'const': 42},
+                    'content': {'const': 'Test Message'}},
+                   {'id': {'const': 42.42},
+                    'content': {'const': 'Second Message'}}],
+         'receives': [{'target': {'const': 'Example Init'},
+                       'command': {'const': 'execute'}}]}
+test(): {'sender': 'Example Init',
+         'id': 42, 'content': 'Test Message'}
+test(): {'sender': 'Example Init',
+         'id': 42.42, 'content': 'Second Message'}
+test(): {'sender': 'test()', 'target': 'Example Init',
+         'command': 'execute'}
+test(): {'sender': 'Example Init',
+         'id': 42, 'content': 'Test Message'}
+test(): {'sender': 'Example Init',
+         'id': 42.42, 'content': 'Second Message'}
+
+
+ +

Similar functionality could be reached by using the Log and Init plugins +to print messages and send some messages on the bus, but these would +clutter the test configuration and code to stop the indefinitely running +bus would have to be added to each and every test.

+ +

Incorrect plugin configurations can also be tested by this:

+ +
+
>>> asyncio.run(test(
+...     {"Example Init": {"plugin": "Init"}}, []))
+data must contain ['messages'] properties
+Configuration for 'Example Init' is not valid.
+
+
+
+ + +
+
+ + \ No newline at end of file diff --git a/doc/api/controlpi/baseplugin.html b/doc/api/controlpi/baseplugin.html new file mode 100644 index 0000000..6089efd --- /dev/null +++ b/doc/api/controlpi/baseplugin.html @@ -0,0 +1,933 @@ + + + + + + + controlpi.baseplugin API documentation + + + + + + + + + +
+
+

+controlpi.baseplugin

+ +

Define base class for all ControlPi plugins.

+ +

The class BasePlugin provides the abstract base class for concrete plugins +running on the ControlPi system.

+ +

It has three abstract methods that have to be implemented by all concrete +plugins:

+ +
    +
  • The class property CONF_SCHEMA is the JSON schema of the configuration of +the plugin. The configuration read from the global configuration file is +checked against this schema during initialisation.
  • +
  • The method process_conf is called at the end of initialisation and is used +to initialise the plugin. It can be assumed that self.bus is the message +bus of the system, self.name the instance name, and self.conf the +configuration already validated against the schema.
  • +
  • The run coroutines of all plugins are executed concurrently by the main +system.
  • +
+ +
+
>>> class TestPlugin(BasePlugin):
+...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
+...                    'required': ['key']}
+...     def process_conf(self):
+...         if 'key' in self.conf:
+...             print(f"Processing '{self.conf['key']}'.")
+...     async def run(self):
+...         print("Doing something else.")
+
+
+ +

Plugins are configured and run based on the information in the global +configuration. Here, we test this manually:

+ +
+
>>> import asyncio
+>>> async def test():
+...     p = TestPlugin(MessageBus(), 'Test Instance', {'key': 'Something'})
+...     await p.run()
+>>> asyncio.run(test())
+Processing 'Something'.
+Doing something else.
+
+
+ +

Each plugin gets a reference to the system message bus during +initialisation, which can be accessed as self.bus in the functions of the +plugin class. This can be used to register and unregister message bus +clients:

+ +
+
>>> class BusPlugin(BasePlugin):
+...     CONF_SCHEMA = True
+...     def process_conf(self):
+...         self.bus.register(self.name, 'BusPlugin',
+...                           [{'event': {'type': 'string'}}],
+...                           [([{'target': {'const': self.name}}],
+...                             self._receive)])
+...     async def _receive(self, message):
+...         print(f"{self.name} received {message}.")
+...         await self.bus.send({'sender': self.name, 'event': 'Receive'})
+...     async def run(self):
+...         await self.bus.send({'sender': self.name, 'event': 'Run'})
+
+
+ +

Again, we run this manually here, but this is done by the main coroutine +when using the system in production:

+ +
+
>>> async def log(message):
+...     print(f"Log: {message}")
+>>> async def test_bus_plugin():
+...     bus = MessageBus()
+...     p = BusPlugin(bus, 'Bus Test', {})
+...     bus.register('Test', 'TestPlugin',
+...                  [{}], [([{'sender': {'const': 'Bus Test'}}], log)])
+...     bus_task = asyncio.create_task(bus.run())
+...     plugin_task = asyncio.create_task(p.run())
+...     await bus.send({'sender': 'Test', 'target': 'Bus Test', 'key': 'v'})
+...     await asyncio.sleep(0)
+...     await asyncio.sleep(0)
+...     bus_task.cancel()
+...     try:
+...         await asyncio.gather(bus_task, plugin_task)
+...     except asyncio.exceptions.CancelledError:
+...         pass
+>>> asyncio.run(test_bus_plugin())
+Bus Test received {'sender': 'Test', 'target': 'Bus Test', 'key': 'v'}.
+Log: {'sender': 'Bus Test', 'event': 'Run'}
+Log: {'sender': 'Bus Test', 'event': 'Receive'}
+
+
+ +

Often, there will be a one-to-one correspondence between plugin +instances and message bus clients, a plugin instance will be a message bus +client. But there are also cases, where one plugin instance might register +and unregister a lot of message bus clients, maybe even dynamically through +its lifetime. A plugin for an input/output card might register separate +clients for each pin of the card, a plugin for some kind of hardware bus +might register separate clients for all devices connected to the bus, or a +network socket plugin might register separate clients for all connections +to the socket (and unregister them when the connection is closed).

+
+ + + + + +
  1"""Define base class for all ControlPi plugins.
+  2
+  3The class BasePlugin provides the abstract base class for concrete plugins
+  4running on the ControlPi system.
+  5
+  6It has three abstract methods that have to be implemented by all concrete
+  7plugins:
+  8- The class property CONF_SCHEMA is the JSON schema of the configuration of
+  9  the plugin. The configuration read from the global configuration file is
+ 10  checked against this schema during initialisation.
+ 11- The method process_conf is called at the end of initialisation and is used
+ 12  to initialise the plugin. It can be assumed that self.bus is the message
+ 13  bus of the system, self.name the instance name, and self.conf the
+ 14  configuration already validated against the schema.
+ 15- The run coroutines of all plugins are executed concurrently by the main
+ 16  system.
+ 17>>> class TestPlugin(BasePlugin):
+ 18...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
+ 19...                    'required': ['key']}
+ 20...     def process_conf(self):
+ 21...         if 'key' in self.conf:
+ 22...             print(f"Processing '{self.conf['key']}'.")
+ 23...     async def run(self):
+ 24...         print("Doing something else.")
+ 25
+ 26Plugins are configured and run based on the information in the global
+ 27configuration. Here, we test this manually:
+ 28>>> import asyncio
+ 29>>> async def test():
+ 30...     p = TestPlugin(MessageBus(), 'Test Instance', {'key': 'Something'})
+ 31...     await p.run()
+ 32>>> asyncio.run(test())
+ 33Processing 'Something'.
+ 34Doing something else.
+ 35
+ 36Each plugin gets a reference to the system message bus during
+ 37initialisation, which can be accessed as self.bus in the functions of the
+ 38plugin class. This can be used to register and unregister message bus
+ 39clients:
+ 40>>> class BusPlugin(BasePlugin):
+ 41...     CONF_SCHEMA = True
+ 42...     def process_conf(self):
+ 43...         self.bus.register(self.name, 'BusPlugin',
+ 44...                           [{'event': {'type': 'string'}}],
+ 45...                           [([{'target': {'const': self.name}}],
+ 46...                             self._receive)])
+ 47...     async def _receive(self, message):
+ 48...         print(f"{self.name} received {message}.")
+ 49...         await self.bus.send({'sender': self.name, 'event': 'Receive'})
+ 50...     async def run(self):
+ 51...         await self.bus.send({'sender': self.name, 'event': 'Run'})
+ 52
+ 53Again, we run this manually here, but this is done by the main coroutine
+ 54when using the system in production:
+ 55>>> async def log(message):
+ 56...     print(f"Log: {message}")
+ 57>>> async def test_bus_plugin():
+ 58...     bus = MessageBus()
+ 59...     p = BusPlugin(bus, 'Bus Test', {})
+ 60...     bus.register('Test', 'TestPlugin',
+ 61...                  [{}], [([{'sender': {'const': 'Bus Test'}}], log)])
+ 62...     bus_task = asyncio.create_task(bus.run())
+ 63...     plugin_task = asyncio.create_task(p.run())
+ 64...     await bus.send({'sender': 'Test', 'target': 'Bus Test', 'key': 'v'})
+ 65...     await asyncio.sleep(0)
+ 66...     await asyncio.sleep(0)
+ 67...     bus_task.cancel()
+ 68...     try:
+ 69...         await asyncio.gather(bus_task, plugin_task)
+ 70...     except asyncio.exceptions.CancelledError:
+ 71...         pass
+ 72>>> asyncio.run(test_bus_plugin())
+ 73Bus Test received {'sender': 'Test', 'target': 'Bus Test', 'key': 'v'}.
+ 74Log: {'sender': 'Bus Test', 'event': 'Run'}
+ 75Log: {'sender': 'Bus Test', 'event': 'Receive'}
+ 76
+ 77Often, there will be a one-to-one correspondence between plugin
+ 78instances and message bus clients, a plugin instance will be a message bus
+ 79client. But there are also cases, where one plugin instance might register
+ 80and unregister a lot of message bus clients, maybe even dynamically through
+ 81its lifetime. A plugin for an input/output card might register separate
+ 82clients for each pin of the card, a plugin for some kind of hardware bus
+ 83might register separate clients for all devices connected to the bus, or a
+ 84network socket plugin might register separate clients for all connections
+ 85to the socket (and unregister them when the connection is closed).
+ 86"""
+ 87
+ 88__pdoc__ = {"BasePlugin.CONF_SCHEMA": False}
+ 89
+ 90from abc import ABC, abstractmethod
+ 91import fastjsonschema
+ 92
+ 93from controlpi.messagebus import MessageBus
+ 94
+ 95from typing import Union, Dict, List, Any, Optional, Callable
+ 96
+ 97JSONSchema = Union[
+ 98    bool, Dict[str, Union[None, str, int, float, bool, Dict[str, Any], List[Any]]]
+ 99]
+100# Could be more specific.
+101PluginConf = Dict[str, Any]
+102# Could be more specific.
+103
+104
+105class ConfException(Exception):
+106    """Raise for errors in plugin configurations."""
+107
+108
+109class BasePlugin(ABC):
+110    """Base class for all ControlPi plugins.
+111
+112    >>> class TestPlugin(BasePlugin):
+113    ...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
+114    ...                    'required': ['key']}
+115    ...     def process_conf(self):
+116    ...         if 'key' in self.conf:
+117    ...             print(f"Processing '{self.conf['key']}'.")
+118    ...     async def run(self):
+119    ...         print("Doing something else.")
+120
+121    Initialisation sets the instance variables bus to the given message bus,
+122    name to the given name, and conf to the given configuration:
+123    >>> import asyncio
+124    >>> class TestPlugin(BasePlugin):
+125    ...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
+126    ...                    'required': ['key']}
+127    ...     def process_conf(self):
+128    ...         if 'key' in self.conf:
+129    ...             print(f"Processing '{self.conf['key']}'.")
+130    ...     async def run(self):
+131    ...         print("Doing something else.")
+132    >>> async def test():
+133    ...     p = TestPlugin(MessageBus(), 'Test Instance',
+134    ...                    {'key': 'Something'})
+135    ...     print(p.bus)
+136    ...     print(p.name)
+137    ...     print(p.conf)
+138    >>> asyncio.run(test())  # doctest: +ELLIPSIS
+139    Processing 'Something'.
+140    <controlpi.messagebus.MessageBus object at 0x...>
+141    Test Instance
+142    {'key': 'Something'}
+143
+144    It also validates the configuration against the schema in CONF_SCHEMA
+145    and raises ConfException if is not validated.
+146    >>> async def test():
+147    ...     p = TestPlugin(MessageBus(), 'Test Instance',
+148    ...                    {'key': 42})
+149    >>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
+150    Traceback (most recent call last):
+151      ...
+152    baseplugin.ConfException: Configuration for 'Test Instance'
+153    is not valid.
+154    >>> async def test():
+155    ...     p = TestPlugin(MessageBus(), 'Test Instance',
+156    ...                    {'key 2': 'Something'})
+157    >>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
+158    Traceback (most recent call last):
+159      ...
+160    baseplugin.ConfException: Configuration for 'Test Instance'
+161    is not valid.
+162
+163    Finally, it calls process_conf, which is the function that should be
+164    overridden by concrete plugins.
+165    """
+166
+167    CONF_SCHEMA: JSONSchema = False
+168
+169    _validate: Optional[Callable[[PluginConf], PluginConf]] = None
+170
+171    def __init__(self, bus: MessageBus, name: str, conf: PluginConf) -> None:
+172        # noqa: D107
+173        self.bus = bus
+174        self.name = name
+175        if not type(self)._validate:
+176            type(self)._validate = fastjsonschema.compile(type(self).CONF_SCHEMA)
+177        self.conf = {}
+178        validate = type(self)._validate
+179        assert validate is not None
+180        try:
+181            self.conf = validate(conf)
+182        except fastjsonschema.JsonSchemaException as e:
+183            print(e)
+184            raise ConfException(f"Configuration for '{self.name}' is not valid.")
+185        self.process_conf()
+186
+187    @abstractmethod
+188    def process_conf(self) -> None:
+189        """Process the configuration.
+190
+191        Abstract method has to be overridden by concrete plugins.
+192        process_conf is called at the end of initialisation after the bus
+193        and the configuration are available as self.bus and self.conf, but
+194        before any of the run coroutines are executed.
+195        """
+196        raise NotImplementedError
+197
+198    @abstractmethod
+199    async def run(self) -> None:
+200        """Run the plugin.
+201
+202        The coroutine is run concurrently with the message bus and all
+203        other plugins. Initial messages and other tasks can be done here.
+204        It is also okay to run a plugin-specific infinite loop concurrently
+205        with the rest of the system.
+206        """
+207        raise NotImplementedError
+
+ + +
+
+
+ JSONSchema = + + bool | typing.Dict[str, None | str | int | float | bool | typing.Dict[str, typing.Any] | typing.List[typing.Any]] + + +
+ + + + +
+
+
+ PluginConf = +typing.Dict[str, typing.Any] + + +
+ + + + +
+
+ +
+ + class + ConfException(builtins.Exception): + + + +
+ +
106class ConfException(Exception):
+107    """Raise for errors in plugin configurations."""
+
+ + +

Raise for errors in plugin configurations.

+
+ + +
+
+ +
+ + class + BasePlugin(abc.ABC): + + + +
+ +
110class BasePlugin(ABC):
+111    """Base class for all ControlPi plugins.
+112
+113    >>> class TestPlugin(BasePlugin):
+114    ...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
+115    ...                    'required': ['key']}
+116    ...     def process_conf(self):
+117    ...         if 'key' in self.conf:
+118    ...             print(f"Processing '{self.conf['key']}'.")
+119    ...     async def run(self):
+120    ...         print("Doing something else.")
+121
+122    Initialisation sets the instance variables bus to the given message bus,
+123    name to the given name, and conf to the given configuration:
+124    >>> import asyncio
+125    >>> class TestPlugin(BasePlugin):
+126    ...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
+127    ...                    'required': ['key']}
+128    ...     def process_conf(self):
+129    ...         if 'key' in self.conf:
+130    ...             print(f"Processing '{self.conf['key']}'.")
+131    ...     async def run(self):
+132    ...         print("Doing something else.")
+133    >>> async def test():
+134    ...     p = TestPlugin(MessageBus(), 'Test Instance',
+135    ...                    {'key': 'Something'})
+136    ...     print(p.bus)
+137    ...     print(p.name)
+138    ...     print(p.conf)
+139    >>> asyncio.run(test())  # doctest: +ELLIPSIS
+140    Processing 'Something'.
+141    <controlpi.messagebus.MessageBus object at 0x...>
+142    Test Instance
+143    {'key': 'Something'}
+144
+145    It also validates the configuration against the schema in CONF_SCHEMA
+146    and raises ConfException if is not validated.
+147    >>> async def test():
+148    ...     p = TestPlugin(MessageBus(), 'Test Instance',
+149    ...                    {'key': 42})
+150    >>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
+151    Traceback (most recent call last):
+152      ...
+153    baseplugin.ConfException: Configuration for 'Test Instance'
+154    is not valid.
+155    >>> async def test():
+156    ...     p = TestPlugin(MessageBus(), 'Test Instance',
+157    ...                    {'key 2': 'Something'})
+158    >>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
+159    Traceback (most recent call last):
+160      ...
+161    baseplugin.ConfException: Configuration for 'Test Instance'
+162    is not valid.
+163
+164    Finally, it calls process_conf, which is the function that should be
+165    overridden by concrete plugins.
+166    """
+167
+168    CONF_SCHEMA: JSONSchema = False
+169
+170    _validate: Optional[Callable[[PluginConf], PluginConf]] = None
+171
+172    def __init__(self, bus: MessageBus, name: str, conf: PluginConf) -> None:
+173        # noqa: D107
+174        self.bus = bus
+175        self.name = name
+176        if not type(self)._validate:
+177            type(self)._validate = fastjsonschema.compile(type(self).CONF_SCHEMA)
+178        self.conf = {}
+179        validate = type(self)._validate
+180        assert validate is not None
+181        try:
+182            self.conf = validate(conf)
+183        except fastjsonschema.JsonSchemaException as e:
+184            print(e)
+185            raise ConfException(f"Configuration for '{self.name}' is not valid.")
+186        self.process_conf()
+187
+188    @abstractmethod
+189    def process_conf(self) -> None:
+190        """Process the configuration.
+191
+192        Abstract method has to be overridden by concrete plugins.
+193        process_conf is called at the end of initialisation after the bus
+194        and the configuration are available as self.bus and self.conf, but
+195        before any of the run coroutines are executed.
+196        """
+197        raise NotImplementedError
+198
+199    @abstractmethod
+200    async def run(self) -> None:
+201        """Run the plugin.
+202
+203        The coroutine is run concurrently with the message bus and all
+204        other plugins. Initial messages and other tasks can be done here.
+205        It is also okay to run a plugin-specific infinite loop concurrently
+206        with the rest of the system.
+207        """
+208        raise NotImplementedError
+
+ + +

Base class for all ControlPi plugins.

+ +
+
>>> class TestPlugin(BasePlugin):
+...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
+...                    'required': ['key']}
+...     def process_conf(self):
+...         if 'key' in self.conf:
+...             print(f"Processing '{self.conf['key']}'.")
+...     async def run(self):
+...         print("Doing something else.")
+
+
+ +

Initialisation sets the instance variables bus to the given message bus, +name to the given name, and conf to the given configuration:

+ +
+
>>> import asyncio
+>>> class TestPlugin(BasePlugin):
+...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
+...                    'required': ['key']}
+...     def process_conf(self):
+...         if 'key' in self.conf:
+...             print(f"Processing '{self.conf['key']}'.")
+...     async def run(self):
+...         print("Doing something else.")
+>>> async def test():
+...     p = TestPlugin(MessageBus(), 'Test Instance',
+...                    {'key': 'Something'})
+...     print(p.bus)
+...     print(p.name)
+...     print(p.conf)
+>>> asyncio.run(test())  # doctest: +ELLIPSIS
+Processing 'Something'.
+<controlpi.messagebus.MessageBus object at 0x...>
+Test Instance
+{'key': 'Something'}
+
+
+ +

It also validates the configuration against the schema in CONF_SCHEMA +and raises ConfException if is not validated.

+ +
+
>>> async def test():
+...     p = TestPlugin(MessageBus(), 'Test Instance',
+...                    {'key': 42})
+>>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
+Traceback (most recent call last):
+  ...
+baseplugin.ConfException: Configuration for 'Test Instance'
+is not valid.
+>>> async def test():
+...     p = TestPlugin(MessageBus(), 'Test Instance',
+...                    {'key 2': 'Something'})
+>>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
+Traceback (most recent call last):
+  ...
+baseplugin.ConfException: Configuration for 'Test Instance'
+is not valid.
+
+
+ +

Finally, it calls process_conf, which is the function that should be +overridden by concrete plugins.

+
+ + +
+
+ CONF_SCHEMA: bool | Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]] = +False + + +
+ + + + +
+
+
+ bus + + +
+ + + + +
+
+
+ name + + +
+ + + + +
+
+
+ conf + + +
+ + + + +
+
+ +
+
@abstractmethod
+ + def + process_conf(self) -> None: + + + +
+ +
188    @abstractmethod
+189    def process_conf(self) -> None:
+190        """Process the configuration.
+191
+192        Abstract method has to be overridden by concrete plugins.
+193        process_conf is called at the end of initialisation after the bus
+194        and the configuration are available as self.bus and self.conf, but
+195        before any of the run coroutines are executed.
+196        """
+197        raise NotImplementedError
+
+ + +

Process the configuration.

+ +

Abstract method has to be overridden by concrete plugins. +process_conf is called at the end of initialisation after the bus +and the configuration are available as self.bus and self.conf, but +before any of the run coroutines are executed.

+
+ + +
+
+ +
+
@abstractmethod
+ + async def + run(self) -> None: + + + +
+ +
199    @abstractmethod
+200    async def run(self) -> None:
+201        """Run the plugin.
+202
+203        The coroutine is run concurrently with the message bus and all
+204        other plugins. Initial messages and other tasks can be done here.
+205        It is also okay to run a plugin-specific infinite loop concurrently
+206        with the rest of the system.
+207        """
+208        raise NotImplementedError
+
+ + +

Run the plugin.

+ +

The coroutine is run concurrently with the message bus and all +other plugins. Initial messages and other tasks can be done here. +It is also okay to run a plugin-specific infinite loop concurrently +with the rest of the system.

+
+ + +
+
+
+ + \ No newline at end of file diff --git a/doc/api/controlpi/messagebus.html b/doc/api/controlpi/messagebus.html new file mode 100644 index 0000000..5ac6739 --- /dev/null +++ b/doc/api/controlpi/messagebus.html @@ -0,0 +1,5480 @@ + + + + + + + controlpi.messagebus API documentation + + + + + + + + + +
+
+

+controlpi.messagebus

+ +

Provide an asynchronous message bus.

+ +

A message is a dictionary with string keys and string, integer, float, +Boolean, dictionary, or list values, where the inner dictionaries again +have string keys and these values and the inner lists also have elements of +these types. All messages have a special key 'sender' with the name of the +sending client as string value, which is set by the constructor:

+ +
+
>>> 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'}
+
+
+ +

A message template is a mapping from string keys to JSON schemas as values. +A message template matches a message if all keys of the template are +contained in the message and the values in the message validate against the +respective schemas. An empty mapping therefore matches all messages.

+ +

The bus executes asynchronous callbacks for all messages to be received by +a client. We use a simple callback printing the message in all examples:

+ +
+
>>> def callback_for_receiver(receiver):
+...     async def callback(message):
+...         print(f"{receiver}: {message}")
+...     return callback
+
+
+ +

Clients can be registered at the bus with a name, lists of message templates +they want to use for sending and receiving and a callback function for +receiving. An empty list of templates means that the client does not want to +send or receive any messages, respectively. A list with an empty template +means that it wants to send arbitrary or receive all messages, respectively:

+ +
+
>>> async def setup(bus):
+...     bus.register('Logger', 'Test Plugin',
+...                  [],
+...                  [([MessageTemplate({})],
+...                    callback_for_receiver('Logger'))])
+...     bus.register('Client 1', 'Test Plugin',
+...                  [MessageTemplate({'k1': {'type': 'string'}})],
+...                  [([MessageTemplate({'target': {'const': 'Client 1'}})],
+...                    callback_for_receiver('Client 1'))])
+
+
+ +

While most clients should always use their own name for sending, this is not +enforced and debugging or management clients could send messages on behalf +of arbitrary client names.

+ +

The name of a client has to be unique and is not allowed to be empty +(otherwise registration fails).

+ +

The empty name is used to refer to the bus itself. The bus sends messages +for registrations and deregistrations of clients containing their complete +interface of send and receive templates. This can be used to allow dynamic +(debug) clients to deal with arbitrary configurations of clients. The bus +also reacts to 'get clients' command messages by sending the complete +information of all currently registered clients.

+ +

Clients can send to the bus with the send function. Each message has to +declare a sender. The send templates of that sender are checked for a +template matching the message:

+ +
+
>>> async def send(bus):
+...     print("Sending messages.")
+...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})
+...     await bus.send({'sender': '', 'target': 'Client 1'})
+
+
+ +

The run function executes the message bus forever. If we want to stop it, we +have to explicitly cancel the task:

+ +
+
>>> async def main():
+...     bus = MessageBus()
+...     await setup(bus)
+...     bus_task = asyncio.create_task(bus.run())
+...     await send(bus)
+...     await asyncio.sleep(0)
+...     bus_task.cancel()
+...     try:
+...         await bus_task
+...     except asyncio.exceptions.CancelledError:
+...         pass
+>>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+Sending messages.
+Logger: {'sender': '', 'event': 'registered',
+         'client': 'Logger', 'plugin': 'Test Plugin',
+         'sends': [], 'receives': [{}]}
+Logger: {'sender': '', 'event': 'registered',
+         'client': 'Client 1', 'plugin': 'Test Plugin',
+         'sends': [{'k1': {'type': 'string'}}],
+         'receives': [{'target': {'const': 'Client 1'}}]}
+Logger: {'sender': 'Client 1', 'k1': 'Test'}
+Logger: {'sender': '', 'target': 'Client 1'}
+Client 1: {'sender': '', 'target': 'Client 1'}
+
+
+
+ + + + + +
   1"""Provide an asynchronous message bus.
+   2
+   3A message is a dictionary with string keys and string, integer, float,
+   4Boolean, dictionary, or list values, where the inner dictionaries again
+   5have string keys and these values and the inner lists also have elements of
+   6these types. All messages have a special key 'sender' with the name of the
+   7sending client as string value, which is set by the constructor:
+   8>>> m = Message('Example sender', {'key 1': 'value 1'})
+   9>>> m['key 2'] = 'value 2'
+  10>>> print(m)
+  11{'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
+  12
+  13A message template is a mapping from string keys to JSON schemas as values.
+  14A message template matches a message if all keys of the template are
+  15contained in the message and the values in the message validate against the
+  16respective schemas. An empty mapping therefore matches all messages.
+  17
+  18The bus executes asynchronous callbacks for all messages to be received by
+  19a client. We use a simple callback printing the message in all examples:
+  20>>> def callback_for_receiver(receiver):
+  21...     async def callback(message):
+  22...         print(f"{receiver}: {message}")
+  23...     return callback
+  24
+  25Clients can be registered at the bus with a name, lists of message templates
+  26they want to use for sending and receiving and a callback function for
+  27receiving. An empty list of templates means that the client does not want to
+  28send or receive any messages, respectively. A list with an empty template
+  29means that it wants to send arbitrary or receive all messages, respectively:
+  30>>> async def setup(bus):
+  31...     bus.register('Logger', 'Test Plugin',
+  32...                  [],
+  33...                  [([MessageTemplate({})],
+  34...                    callback_for_receiver('Logger'))])
+  35...     bus.register('Client 1', 'Test Plugin',
+  36...                  [MessageTemplate({'k1': {'type': 'string'}})],
+  37...                  [([MessageTemplate({'target': {'const': 'Client 1'}})],
+  38...                    callback_for_receiver('Client 1'))])
+  39
+  40While most clients should always use their own name for sending, this is not
+  41enforced and debugging or management clients could send messages on behalf
+  42of arbitrary client names.
+  43
+  44The name of a client has to be unique and is not allowed to be empty
+  45(otherwise registration fails).
+  46
+  47The empty name is used to refer to the bus itself. The bus sends messages
+  48for registrations and deregistrations of clients containing their complete
+  49interface of send and receive templates. This can be used to allow dynamic
+  50(debug) clients to deal with arbitrary configurations of clients. The bus
+  51also reacts to 'get clients' command messages by sending the complete
+  52information of all currently registered clients.
+  53
+  54Clients can send to the bus with the send function. Each message has to
+  55declare a sender. The send templates of that sender are checked for a
+  56template matching the message:
+  57>>> async def send(bus):
+  58...     print("Sending messages.")
+  59...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})
+  60...     await bus.send({'sender': '', 'target': 'Client 1'})
+  61
+  62The run function executes the message bus forever. If we want to stop it, we
+  63have to explicitly cancel the task:
+  64>>> async def main():
+  65...     bus = MessageBus()
+  66...     await setup(bus)
+  67...     bus_task = asyncio.create_task(bus.run())
+  68...     await send(bus)
+  69...     await asyncio.sleep(0)
+  70...     bus_task.cancel()
+  71...     try:
+  72...         await bus_task
+  73...     except asyncio.exceptions.CancelledError:
+  74...         pass
+  75>>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+  76Sending messages.
+  77Logger: {'sender': '', 'event': 'registered',
+  78         'client': 'Logger', 'plugin': 'Test Plugin',
+  79         'sends': [], 'receives': [{}]}
+  80Logger: {'sender': '', 'event': 'registered',
+  81         'client': 'Client 1', 'plugin': 'Test Plugin',
+  82         'sends': [{'k1': {'type': 'string'}}],
+  83         'receives': [{'target': {'const': 'Client 1'}}]}
+  84Logger: {'sender': 'Client 1', 'k1': 'Test'}
+  85Logger: {'sender': '', 'target': 'Client 1'}
+  86Client 1: {'sender': '', 'target': 'Client 1'}
+  87"""
+  88
+  89import asyncio
+  90import json
+  91import fastjsonschema
+  92import sys
+  93
+  94from typing import (
+  95    Union,
+  96    Dict,
+  97    List,
+  98    Any,
+  99    Callable,
+ 100    Coroutine,
+ 101    Optional,
+ 102    Iterable,
+ 103    Tuple,
+ 104)
+ 105
+ 106MessageValue = Union[None, str, int, float, bool, Dict[str, Any], List[Any]]
+ 107# Should really be:
+ 108# MessageValue = Union[None, str, int, float, bool,
+ 109#                      Dict[str, 'MessageValue'], List['MessageValue']]
+ 110# But mypy does not support recursion by now:
+ 111# https://github.com/python/mypy/issues/731
+ 112JSONSchema = Union[bool, Dict[str, MessageValue]]
+ 113# Could be even more specific.
+ 114MessageCallback = Callable[["Message"], Coroutine[Any, Any, None]]
+ 115
+ 116
+ 117# Global cache of JSON schema validation functions:
+ 118_validates: Dict[str, Callable[[MessageValue], MessageValue]] = {}
+ 119
+ 120
+ 121def register_schema(schema: JSONSchema) -> bool:
+ 122    """Register the given JSON schema in the global cache."""
+ 123    global _validates
+ 124    schema_string = json.dumps(schema)
+ 125    if schema_string not in _validates:
+ 126        if not (isinstance(schema, dict) or isinstance(schema, bool)):
+ 127            return False
+ 128        try:
+ 129            _validates[schema_string] = fastjsonschema.compile(schema)
+ 130        except fastjsonschema.JsonSchemaDefinitionException:
+ 131            return False
+ 132    return True
+ 133
+ 134
+ 135def validate(schema_string: str, value: MessageValue) -> bool:
+ 136    """Validate the given MessageValue against the given JSON schema string."""
+ 137    global _validates
+ 138    if schema_string not in _validates:
+ 139        schema = json.loads(schema_string)
+ 140        _validates[schema_string] = fastjsonschema.compile(schema)
+ 141    validate = _validates[schema_string]
+ 142    try:
+ 143        validate(value)
+ 144    except fastjsonschema.JsonSchemaException:
+ 145        return False
+ 146    return True
+ 147
+ 148
+ 149class Message(Dict[str, MessageValue]):
+ 150    """Define arbitrary message.
+ 151
+ 152    Messages are dictionaries with string keys and values that are strings,
+ 153    integers, floats, Booleans, dictionaries that recursively have string
+ 154    keys and values of any of these types, or lists with elements that have
+ 155    any of these types. These constraints are checked when setting key-value
+ 156    pairs of the message.
+ 157
+ 158    A message has to have a sender, which is set by the constructor:
+ 159    >>> m = Message('Example sender')
+ 160    >>> print(m)
+ 161    {'sender': 'Example sender'}
+ 162
+ 163    A dictionary can be given to the constructor:
+ 164    >>> m = Message('Example sender',
+ 165    ...             {'key 1': 'value 1', 'key 2': 'value 2'})
+ 166    >>> print(m)
+ 167    {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
+ 168
+ 169    A 'sender' set in the initial dictionary is overwritten by the explicitly
+ 170    given sender in the first argument:
+ 171    >>> m = Message('Example sender',
+ 172    ...             {'sender': 'Original sender', 'key': 'value'})
+ 173    >>> print(m)
+ 174    {'sender': 'Example sender', 'key': 'value'}
+ 175
+ 176    Or the message can be modified after construction:
+ 177    >>> m = Message('Example sender', {'key 1': 'value 1'})
+ 178    >>> m['key 2'] = 'value 2'
+ 179    >>> print(m)
+ 180    {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
+ 181
+ 182    The 'sender' key can be overwritten, but this should only be done in
+ 183    exceptional cases:
+ 184    >>> m = Message('Example sender', {'key': 'value'})
+ 185    >>> m['sender'] = 'New sender'
+ 186    >>> print(m)
+ 187    {'sender': 'New sender', 'key': 'value'}
+ 188    """
+ 189
+ 190    def __init__(
+ 191        self, sender: str, init: Optional[Dict[str, MessageValue]] = None
+ 192    ) -> None:
+ 193        """Initialise message.
+ 194
+ 195        Message is initialised with given sender and possibly given
+ 196        key-value pairs:
+ 197        >>> m = Message('Example sender')
+ 198        >>> print(m)
+ 199        {'sender': 'Example sender'}
+ 200        >>> m = Message('Example sender', {'key 1': 'value 1'})
+ 201        >>> print(m)
+ 202        {'sender': 'Example sender', 'key 1': 'value 1'}
+ 203        """
+ 204        if not isinstance(sender, str):
+ 205            raise TypeError(f"'{sender}' is not a valid sender name (not a string).")
+ 206        self["sender"] = ""
+ 207        if init is not None:
+ 208            self.update(init)
+ 209        self["sender"] = sender
+ 210
+ 211    @staticmethod
+ 212    def check_value(value: MessageValue) -> bool:
+ 213        """Check recursively if a given value is valid.
+ 214
+ 215        None, strings, integers, floats and Booleans are valid:
+ 216        >>> Message.check_value(None)
+ 217        True
+ 218        >>> Message.check_value('Spam')
+ 219        True
+ 220        >>> Message.check_value(42)
+ 221        True
+ 222        >>> Message.check_value(42.42)
+ 223        True
+ 224        >>> Message.check_value(False)
+ 225        True
+ 226
+ 227        Other basic types are not valid:
+ 228        >>> Message.check_value(b'bytes')
+ 229        False
+ 230        >>> Message.check_value(1j)
+ 231        False
+ 232
+ 233        Dictionaries with string keys and recursively valid values are valid:
+ 234        >>> Message.check_value({'str value': 'Spam', 'int value': 42,
+ 235        ...                      'float value': 42.42, 'bool value': False})
+ 236        True
+ 237
+ 238        Empty dictionaries are valid:
+ 239        >>> Message.check_value({})
+ 240        True
+ 241
+ 242        Dictionaries with other keys are not valid:
+ 243        >>> Message.check_value({42: 'int key'})
+ 244        False
+ 245
+ 246        Dictionaries with invalid values are not valid:
+ 247        >>> Message.check_value({'complex value': 1j})
+ 248        False
+ 249
+ 250        Lists with valid elements are valid:
+ 251        >>> Message.check_value(['Spam', 42, 42.42, False])
+ 252        True
+ 253
+ 254        Empty lists are valid:
+ 255        >>> Message.check_value([])
+ 256        True
+ 257
+ 258        Lists with invalid elements are not valid:
+ 259        >>> Message.check_value([1j])
+ 260        False
+ 261        """
+ 262        if value is None:
+ 263            return True
+ 264        elif (
+ 265            isinstance(value, str)
+ 266            or isinstance(value, int)
+ 267            or isinstance(value, float)
+ 268            or isinstance(value, bool)
+ 269        ):
+ 270            return True
+ 271        elif isinstance(value, dict):
+ 272            for key in value:
+ 273                if not isinstance(key, str):
+ 274                    return False
+ 275                if not Message.check_value(value[key]):
+ 276                    return False
+ 277            return True
+ 278        elif isinstance(value, list):
+ 279            for element in value:
+ 280                if not Message.check_value(element):
+ 281                    return False
+ 282            return True
+ 283        return False
+ 284
+ 285    def __setitem__(self, key: str, value: MessageValue) -> None:
+ 286        """Check key and value before putting pair into dict.
+ 287
+ 288        >>> m = Message('Example sender')
+ 289        >>> m['key'] = 'value'
+ 290        >>> m['dict'] = {'k1': 'v1', 'k2': 2}
+ 291        >>> print(m)  # doctest: +NORMALIZE_WHITESPACE
+ 292        {'sender': 'Example sender', 'key': 'value',
+ 293         'dict': {'k1': 'v1', 'k2': 2}}
+ 294        >>> m = Message('Example sender')
+ 295        >>> m[42] = 'int key'
+ 296        Traceback (most recent call last):
+ 297          ...
+ 298        TypeError: '42' is not a valid key in Message (not a string).
+ 299        >>> m['complex value'] = 1j
+ 300        Traceback (most recent call last):
+ 301          ...
+ 302        TypeError: '1j' is not a valid value in Message.
+ 303        """
+ 304        if not isinstance(key, str):
+ 305            raise TypeError(f"'{key}' is not a valid key in Message (not a string).")
+ 306        if not self.check_value(value):
+ 307            raise TypeError(f"'{value}' is not a valid value in Message.")
+ 308        super().__setitem__(key, value)
+ 309
+ 310    def update(self, *args, **kwargs) -> None:
+ 311        """Override update to use validity checks.
+ 312
+ 313        >>> m = Message('Example sender')
+ 314        >>> m.update({'key 1': 'value 1', 'key 2': 'value 2'})
+ 315        >>> print(m)
+ 316        {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
+ 317        >>> m.update({42: 'int key'})
+ 318        Traceback (most recent call last):
+ 319          ...
+ 320        TypeError: '42' is not a valid key in Message (not a string).
+ 321        >>> m.update({'complex value': 1j})
+ 322        Traceback (most recent call last):
+ 323          ...
+ 324        TypeError: '1j' is not a valid value in Message.
+ 325
+ 326        This is also used in __init__:
+ 327        >>> m = Message('Example sender', {'key': 'value'})
+ 328        >>> print(m)
+ 329        {'sender': 'Example sender', 'key': 'value'}
+ 330        >>> m = Message('Example sender', {42: 'int key'})
+ 331        Traceback (most recent call last):
+ 332          ...
+ 333        TypeError: '42' is not a valid key in Message (not a string).
+ 334        >>> m = Message('Example sender', {'complex value': 1j})
+ 335        Traceback (most recent call last):
+ 336          ...
+ 337        TypeError: '1j' is not a valid value in Message.
+ 338        """
+ 339        if args:
+ 340            if len(args) > 1:
+ 341                raise TypeError(f"update expected at most 1 argument, got {len(args)}")
+ 342            other = dict(args[0])
+ 343            for key in other:
+ 344                self[key] = other[key]
+ 345        for key in kwargs:
+ 346            self[key] = kwargs[key]
+ 347
+ 348    def setdefault(self, key: str, value: MessageValue = None) -> MessageValue:
+ 349        """Override setdefault to use validity checks.
+ 350
+ 351        >>> m = Message('Example sender')
+ 352        >>> m.setdefault('key', 'value 1')
+ 353        'value 1'
+ 354        >>> m.setdefault('key', 'value 2')
+ 355        'value 1'
+ 356        >>> m.setdefault(42, 'int key')
+ 357        Traceback (most recent call last):
+ 358          ...
+ 359        TypeError: '42' is not a valid key in Message (not a string).
+ 360        >>> m.setdefault('complex value', 1j)
+ 361        Traceback (most recent call last):
+ 362          ...
+ 363        TypeError: '1j' is not a valid value in Message.
+ 364
+ 365        But __setitem__ is not called if the key is already present:
+ 366        >>> m.setdefault('key', 1j)
+ 367        'value 1'
+ 368        """
+ 369        if key not in self:
+ 370            self[key] = value
+ 371        return self[key]
+ 372
+ 373
+ 374class MessageTemplate(Dict[str, JSONSchema]):
+ 375    """Define a message template.
+ 376
+ 377    A message template is a mapping from string keys to JSON schemas as
+ 378    values:
+ 379    >>> t = MessageTemplate({'key 1': {'const': 'value'},
+ 380    ...                      'key 2': {'type': 'string'}})
+ 381    >>> t['key 3'] = {'type': 'object',
+ 382    ...               'properties': {'key 1': {'type': 'number'},
+ 383    ...                              'key 2': True}}
+ 384
+ 385    A message template matches a message if all keys of the template are
+ 386    contained in the message and the values in the message validate against
+ 387    the respective schemas:
+ 388    >>> t.check(Message('Example Sender',
+ 389    ...                 {'key 1': 'value', 'key 2': 'some string',
+ 390    ...                  'key 3': {'key 1': 42, 'key 2': None}}))
+ 391    True
+ 392
+ 393    An empty mapping therefore matches all messages:
+ 394    >>> t = MessageTemplate()
+ 395    >>> t.check(Message('Example Sender', {'arbitrary': 'content'}))
+ 396    True
+ 397    """
+ 398
+ 399    def __init__(self, init: Optional[Dict[str, JSONSchema]] = None) -> None:
+ 400        """Initialise message.
+ 401
+ 402        Template is initialised empty or with given key-value pairs:
+ 403        >>> t = MessageTemplate()
+ 404        >>> print(t)
+ 405        {}
+ 406        >>> t = MessageTemplate({'key': {'const': 'value'}})
+ 407        >>> print(t)
+ 408        {'key': {'const': 'value'}}
+ 409        """
+ 410        if init is not None:
+ 411            self.update(init)
+ 412
+ 413    @staticmethod
+ 414    def from_message(message: Message) -> "MessageTemplate":
+ 415        """Create template from message.
+ 416
+ 417        Template witch constant schemas is created from message:
+ 418        >>> m = Message('Example Sender', {'key': 'value'})
+ 419        >>> t = MessageTemplate.from_message(m)
+ 420        >>> print(t)
+ 421        {'sender': {'const': 'Example Sender'}, 'key': {'const': 'value'}}
+ 422        >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
+ 423        ...                                'list': [None, True, 'string']})
+ 424        >>> t = MessageTemplate.from_message(m)
+ 425        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+ 426        {'sender': {'const': 'Example Sender'},
+ 427         'dict': {'type': 'object',
+ 428                  'properties': {'int': {'const': 42},
+ 429                                 'float': {'const': 42.42}}},
+ 430         'list': {'type': 'array',
+ 431                  'items': [{'const': None},
+ 432                            {'const': True},
+ 433                            {'const': 'string'}]}}
+ 434
+ 435        This is especially useful for clients that send certain fully
+ 436        predefined messages, where the message is given in the configuration
+ 437        and the template for the registration can be constructed by this
+ 438        method.
+ 439        """
+ 440
+ 441        def schema_from_value(value: MessageValue) -> JSONSchema:
+ 442            schema: JSONSchema = False
+ 443            if value is None:
+ 444                schema = {"const": None}
+ 445            elif (
+ 446                isinstance(value, str)
+ 447                or isinstance(value, int)
+ 448                or isinstance(value, float)
+ 449                or isinstance(value, bool)
+ 450            ):
+ 451                schema = {"const": value}
+ 452            elif isinstance(value, dict):
+ 453                properties = {}
+ 454                for inner_key in value:
+ 455                    inner_value: Message = value[inner_key]
+ 456                    properties[inner_key] = schema_from_value(inner_value)
+ 457                schema = {"type": "object", "properties": properties}
+ 458            elif isinstance(value, list):
+ 459                schema = {
+ 460                    "type": "array",
+ 461                    "items": [schema_from_value(element) for element in value],
+ 462                }
+ 463            return schema
+ 464
+ 465        template = MessageTemplate()
+ 466        for key in message:
+ 467            template[key] = schema_from_value(message[key])
+ 468        return template
+ 469
+ 470    def __setitem__(self, key: str, value: JSONSchema) -> None:
+ 471        """Check key and value before putting pair into dict.
+ 472
+ 473        >>> t = MessageTemplate()
+ 474        >>> t['key 1'] = {'const': 'value'}
+ 475        >>> t['key 2'] = {'type': 'string'}
+ 476        >>> t['key 3'] = {'type': 'object',
+ 477        ...               'properties': {'key 1': {'type': 'number'},
+ 478        ...                              'key 2': True}}
+ 479        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+ 480        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
+ 481         'key 3': {'type': 'object',
+ 482                   'properties': {'key 1': {'type': 'number'},
+ 483                                  'key 2': True}}}
+ 484        >>> t[42] = {'const': 'int key'}
+ 485        Traceback (most recent call last):
+ 486          ...
+ 487        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+ 488        >>> t['key'] = 'schema'  # doctest: +NORMALIZE_WHITESPACE
+ 489        Traceback (most recent call last):
+ 490          ...
+ 491        TypeError: 'schema' is not a valid value in MessageTemplate
+ 492        (not a valid JSON schema).
+ 493        >>> t['key'] = True
+ 494        """
+ 495        if not isinstance(key, str):
+ 496            raise TypeError(
+ 497                f"'{key}' is not a valid key in MessageTemplate (not a string)."
+ 498            )
+ 499        if not register_schema(value):
+ 500            raise TypeError(
+ 501                f"'{value}' is not a valid value in"
+ 502                " MessageTemplate (not a valid JSON schema)."
+ 503            )
+ 504        super().__setitem__(key, value)
+ 505
+ 506    def update(self, *args, **kwargs) -> None:
+ 507        """Override update to use validity checks.
+ 508
+ 509        >>> t = MessageTemplate()
+ 510        >>> t.update({'key 1': {'const': 'value'},
+ 511        ...           'key 2': {'type': 'string'},
+ 512        ...           'key 3': {'type': 'object',
+ 513        ...                     'properties': {'key 1': {'type': 'number'},
+ 514        ...                                    'key 2': True}}})
+ 515        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+ 516        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
+ 517         'key 3': {'type': 'object',
+ 518                   'properties': {'key 1': {'type': 'number'},
+ 519                                  'key 2': True}}}
+ 520        >>> t.update({42: {'const': 'int key'}})
+ 521        Traceback (most recent call last):
+ 522          ...
+ 523        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+ 524        >>> t.update({'key': 'schema'})  # doctest: +NORMALIZE_WHITESPACE
+ 525        Traceback (most recent call last):
+ 526          ...
+ 527        TypeError: 'schema' is not a valid value in MessageTemplate
+ 528        (not a valid JSON schema).
+ 529        >>> t.update({'key': True})
+ 530
+ 531        This is also used in __init__:
+ 532        >>> t = MessageTemplate({'key 1': {'const': 'value'},
+ 533        ...                      'key 2': {'type': 'string'},
+ 534        ...                      'key 3': {'type': 'object',
+ 535        ...                                'properties': {
+ 536        ...                                    'key 1': {'type': 'number'},
+ 537        ...                                    'key 2': True}}})
+ 538        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+ 539        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
+ 540         'key 3': {'type': 'object',
+ 541                   'properties': {'key 1': {'type': 'number'},
+ 542                                  'key 2': True}}}
+ 543        >>> t = MessageTemplate({42: {'const': 'int key'}})
+ 544        Traceback (most recent call last):
+ 545          ...
+ 546        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+ 547        >>> t = MessageTemplate({'key': 'schema'})
+ 548        ... # doctest: +NORMALIZE_WHITESPACE
+ 549        Traceback (most recent call last):
+ 550          ...
+ 551        TypeError: 'schema' is not a valid value in MessageTemplate
+ 552        (not a valid JSON schema).
+ 553        >>> t = MessageTemplate({'key': True})
+ 554        """
+ 555        if args:
+ 556            if len(args) > 1:
+ 557                raise TypeError(f"update expected at most 1 argument, got {len(args)}")
+ 558            other = dict(args[0])
+ 559            for key in other:
+ 560                self[key] = other[key]
+ 561        for key in kwargs:
+ 562            self[key] = kwargs[key]
+ 563
+ 564    def setdefault(self, key: str, value: Optional[JSONSchema] = None) -> JSONSchema:
+ 565        """Override setdefault to use validity checks.
+ 566
+ 567        >>> t = MessageTemplate()
+ 568        >>> t.setdefault('key 1', {'const': 'value'})
+ 569        {'const': 'value'}
+ 570        >>> t.setdefault('key 2', {'type': 'string'})
+ 571        {'type': 'string'}
+ 572        >>> t.setdefault('key 3', {'type': 'object',
+ 573        ...                        'properties': {'key 1': {'type': 'number'},
+ 574        ...                                       'key 2': True}})
+ 575        ... # doctest: +NORMALIZE_WHITESPACE
+ 576        {'type': 'object',
+ 577                   'properties': {'key 1': {'type': 'number'},
+ 578                                  'key 2': True}}
+ 579        >>> t.setdefault(42, {'const': 'int key'})
+ 580        Traceback (most recent call last):
+ 581          ...
+ 582        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+ 583        >>> t.setdefault('key', 'schema')  # doctest: +NORMALIZE_WHITESPACE
+ 584        Traceback (most recent call last):
+ 585          ...
+ 586        TypeError: 'schema' is not a valid value in MessageTemplate
+ 587        (not a valid JSON schema).
+ 588
+ 589        But __setitem__ is not called if the key is already present:
+ 590        >>> t.setdefault('key 1', 'schema')
+ 591        {'const': 'value'}
+ 592        """
+ 593        if key not in self:
+ 594            if value is not None:
+ 595                self[key] = value
+ 596            else:
+ 597                self[key] = True
+ 598        return self[key]
+ 599
+ 600    def check(self, message: Message) -> bool:
+ 601        """Check message against this template.
+ 602
+ 603        Constant values have to match exactly:
+ 604        >>> t = MessageTemplate({'key': {'const': 'value'}})
+ 605        >>> t.check(Message('Example Sender', {'key': 'value'}))
+ 606        True
+ 607        >>> t.check(Message('Example Sender', {'key': 'other value'}))
+ 608        False
+ 609
+ 610        But for integers, floats with the same value are also valid:
+ 611        >>> t = MessageTemplate({'key': {'const': 42}})
+ 612        >>> t.check(Message('Example Sender', {'key': 42}))
+ 613        True
+ 614        >>> t.check(Message('Example Sender', {'key': 42.0}))
+ 615        True
+ 616
+ 617        Type integer is valid for floats with zero fractional part, but
+ 618        not by floats with non-zero fractional part:
+ 619        >>> t = MessageTemplate({'key': {'type': 'integer'}})
+ 620        >>> t.check(Message('Example Sender', {'key': 42}))
+ 621        True
+ 622        >>> t.check(Message('Example Sender', {'key': 42.0}))
+ 623        True
+ 624        >>> t.check(Message('Example Sender', {'key': 42.42}))
+ 625        False
+ 626
+ 627        Type number is valid for arbitrary ints or floats:
+ 628        >>> t = MessageTemplate({'key': {'type': 'number'}})
+ 629        >>> t.check(Message('Example Sender', {'key': 42}))
+ 630        True
+ 631        >>> t.check(Message('Example Sender', {'key': 42.42}))
+ 632        True
+ 633
+ 634        All keys in template have to be present in message:
+ 635        >>> t = MessageTemplate({'key 1': {'const': 'value'},
+ 636        ...                      'key 2': {'type': 'string'},
+ 637        ...                      'key 3': {'type': 'object',
+ 638        ...                                'properties': {
+ 639        ...                                    'key 1': {'type': 'number'},
+ 640        ...                                    'key 2': True,
+ 641        ...                                    'key 3': False}}})
+ 642        >>> t.check(Message('Example Sender',
+ 643        ...                 {'key 1': 'value', 'key 2': 'some string'}))
+ 644        False
+ 645
+ 646        But for nested objects their properties do not necessarily have
+ 647        to be present:
+ 648        >>> t.check(Message('Example Sender',
+ 649        ...                 {'key 1': 'value', 'key 2': 'some string',
+ 650        ...                  'key 3': {'key 1': 42}}))
+ 651        True
+ 652
+ 653        Schema True matches everything (even None):
+ 654        >>> t.check(Message('Example Sender',
+ 655        ...                 {'key 1': 'value', 'key 2': 'some string',
+ 656        ...                  'key 3': {'key 2': None}}))
+ 657        True
+ 658
+ 659        Schema False matches nothing:
+ 660        >>> t.check(Message('Example Sender',
+ 661        ...                 {'key 1': 'value', 'key 2': 'some string',
+ 662        ...                  'key 3': {'key 3': True}}))
+ 663        False
+ 664
+ 665        Message is valid for the constant template created from it:
+ 666        >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
+ 667        ...                                'list': [None, True, 'string']})
+ 668        >>> t = MessageTemplate.from_message(m)
+ 669        >>> t.check(m)
+ 670        True
+ 671        """
+ 672        for key in self:
+ 673            if key not in message:
+ 674                return False
+ 675            else:
+ 676                schema_string = json.dumps(self[key])
+ 677                if not validate(schema_string, message[key]):
+ 678                    return False
+ 679        return True
+ 680
+ 681
+ 682class TemplateRegistry:
+ 683    """Manage a collection of message templates with registered clients.
+ 684
+ 685    A new TemplateRegistry is created by:
+ 686    >>> r = TemplateRegistry()
+ 687
+ 688    Client names (strings) can be registered for message templates, which
+ 689    are mappings from keys to JSON schemas:
+ 690    >>> r.insert({'k1': {'const': 'v1'}}, 'C 1')
+ 691
+ 692    The check function checks if the templates registered for a client
+ 693    match a given message:
+ 694    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 695    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 696    ...     print(f"{m}: {r.check('C 1', m)}")
+ 697    {'k1': 'v1', 'k2': 'v1'}: True
+ 698    {'k1': 'v1', 'k2': 2}: True
+ 699    {'k1': 'v2', 'k2': 'v1'}: False
+ 700    {'k1': 'v2', 'k2': 2}: False
+ 701
+ 702    Clients can be registered for values validating against arbitrary JSON
+ 703    schemas, e.g. all values of a certain type:
+ 704    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}, 'C 2')
+ 705    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 706    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 707    ...     print(f"{m}: {r.check('C 2', m)}")
+ 708    {'k1': 'v1', 'k2': 'v1'}: False
+ 709    {'k1': 'v1', 'k2': 2}: False
+ 710    {'k1': 'v2', 'k2': 'v1'}: True
+ 711    {'k1': 'v2', 'k2': 2}: False
+ 712    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}, 'C 3')
+ 713    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 714    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 715    ...     print(f"{m}: {r.check('C 3', m)}")
+ 716    {'k1': 'v1', 'k2': 'v1'}: False
+ 717    {'k1': 'v1', 'k2': 2}: False
+ 718    {'k1': 'v2', 'k2': 'v1'}: False
+ 719    {'k1': 'v2', 'k2': 2}: True
+ 720
+ 721    The order of key-value pairs does not have to match the order in the
+ 722    messages and keys can be left out:
+ 723    >>> r.insert({'k2': {'const': 2}}, 'C 4')
+ 724    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 725    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 726    ...     print(f"{m}: {r.check('C 4', m)}")
+ 727    {'k1': 'v1', 'k2': 'v1'}: False
+ 728    {'k1': 'v1', 'k2': 2}: True
+ 729    {'k1': 'v2', 'k2': 'v1'}: False
+ 730    {'k1': 'v2', 'k2': 2}: True
+ 731
+ 732    A registration for an empty template matches all messages:
+ 733    >>> r.insert({}, 'C 5')
+ 734    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 735    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 736    ...     print(f"{m}: {r.check('C 5', m)}")
+ 737    {'k1': 'v1', 'k2': 'v1'}: True
+ 738    {'k1': 'v1', 'k2': 2}: True
+ 739    {'k1': 'v2', 'k2': 'v1'}: True
+ 740    {'k1': 'v2', 'k2': 2}: True
+ 741
+ 742    A client can be registered for multiple templates:
+ 743    >>> r.insert({'k1': {'const': 'v1'}}, 'C 6')
+ 744    >>> r.insert({'k2': {'const': 'v1'}}, 'C 6')
+ 745    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 746    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 747    ...     print(f"{m}: {r.check('C 6', m)}")
+ 748    {'k1': 'v1', 'k2': 'v1'}: True
+ 749    {'k1': 'v1', 'k2': 2}: True
+ 750    {'k1': 'v2', 'k2': 'v1'}: True
+ 751    {'k1': 'v2', 'k2': 2}: False
+ 752
+ 753    Clients can be deregistered again (the result is False if the registry
+ 754    is empty after the deletion):
+ 755    >>> r.insert({'k1': {'const': 'v1'}}, 'C 7')
+ 756    >>> r.delete('C 7')
+ 757    True
+ 758    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 759    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 760    ...     print(f"{m}: {r.check('C 7', m)}")
+ 761    {'k1': 'v1', 'k2': 'v1'}: False
+ 762    {'k1': 'v1', 'k2': 2}: False
+ 763    {'k1': 'v2', 'k2': 'v1'}: False
+ 764    {'k1': 'v2', 'k2': 2}: False
+ 765
+ 766    The get function returns all clients with registered templates matching
+ 767    a given message:
+ 768    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 769    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 770    ...     print(f"{m}: {r.get(m)}")
+ 771    {'k1': 'v1', 'k2': 'v1'}: ['C 5', 'C 1', 'C 6']
+ 772    {'k1': 'v1', 'k2': 2}: ['C 5', 'C 1', 'C 6', 'C 4']
+ 773    {'k1': 'v2', 'k2': 'v1'}: ['C 5', 'C 2', 'C 6']
+ 774    {'k1': 'v2', 'k2': 2}: ['C 5', 'C 3', 'C 4']
+ 775
+ 776    The get_templates function returns all templates for a given client:
+ 777    >>> for c in ['C 1', 'C 2', 'C 3', 'C 4', 'C 5', 'C 6']:
+ 778    ...     print(f"{c}: {r.get_templates(c)}")
+ 779    C 1: [{'k1': {'const': 'v1'}}]
+ 780    C 2: [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
+ 781    C 3: [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
+ 782    C 4: [{'k2': {'const': 2}}]
+ 783    C 5: [{}]
+ 784    C 6: [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
+ 785    """
+ 786
+ 787    def __init__(self) -> None:
+ 788        """Initialise an empty registry.
+ 789
+ 790        >>> r = TemplateRegistry()
+ 791        """
+ 792        self._clients: List[str] = []
+ 793        self._callbacks: Dict[str, List[MessageCallback]] = {}
+ 794        self._constants: Dict[str, Dict[str, TemplateRegistry]] = {}
+ 795        # First key is the message key, second key is the constant string
+ 796        self._schemas: Dict[str, Dict[str, TemplateRegistry]] = {}
+ 797        # First key is the message key, second key is the JSON schema string
+ 798        self._templates: Dict[str, List[MessageTemplate]] = {}
+ 799
+ 800    def insert(
+ 801        self,
+ 802        template: MessageTemplate,
+ 803        client: str,
+ 804        callback: Optional[MessageCallback] = None,
+ 805    ) -> None:
+ 806        """Register a client for a template.
+ 807
+ 808        >>> r = TemplateRegistry()
+ 809        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'integer'}}, 'C 1')
+ 810        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'string'}}, 'C 2')
+ 811        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v1'}}, 'C 3')
+ 812        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v2'}}, 'C 4')
+ 813        >>> r.insert({}, 'C 5')
+ 814        """
+ 815        if client not in self._templates:
+ 816            self._templates[client] = []
+ 817        self._templates[client].append(template)
+ 818        if not template:
+ 819            self._clients.append(client)
+ 820            if callback:
+ 821                if client not in self._callbacks:
+ 822                    self._callbacks[client] = []
+ 823                self._callbacks[client].append(callback)
+ 824        else:
+ 825            key, schema = next(iter(template.items()))
+ 826            reduced_template = MessageTemplate(
+ 827                {k: template[k] for k in template if k != key}
+ 828            )
+ 829            if (
+ 830                isinstance(schema, dict)
+ 831                and len(schema) == 1
+ 832                and "const" in schema
+ 833                and isinstance(schema["const"], str)
+ 834            ):
+ 835                value = schema["const"]
+ 836                if key not in self._constants:
+ 837                    self._constants[key] = {}
+ 838                if value not in self._constants[key]:
+ 839                    self._constants[key][value] = TemplateRegistry()
+ 840                self._constants[key][value].insert(reduced_template, client, callback)
+ 841            else:
+ 842                schema_string = json.dumps(schema)
+ 843                if key not in self._schemas:
+ 844                    self._schemas[key] = {}
+ 845                if schema_string not in self._schemas[key]:
+ 846                    self._schemas[key][schema_string] = TemplateRegistry()
+ 847                self._schemas[key][schema_string].insert(
+ 848                    reduced_template, client, callback
+ 849                )
+ 850
+ 851    def delete(self, client: str) -> bool:
+ 852        """Unregister a client from all templates.
+ 853
+ 854        >>> r = TemplateRegistry()
+ 855        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'integer'}}, 'C 1')
+ 856        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'string'}}, 'C 2')
+ 857        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v1'}}, 'C 3')
+ 858        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v2'}}, 'C 4')
+ 859        >>> r.insert({}, 'C 5')
+ 860        >>> r.delete('C 3')
+ 861        True
+ 862        >>> r.delete('C 4')
+ 863        True
+ 864        """
+ 865        if client in self._templates:
+ 866            del self._templates[client]
+ 867        self._clients = [c for c in self._clients if c != client]
+ 868        if client in self._callbacks:
+ 869            del self._callbacks[client]
+ 870        new_constants: Dict[str, Dict[str, TemplateRegistry]] = {}
+ 871        for key in self._constants:
+ 872            new_constants[key] = {}
+ 873            for value in self._constants[key]:
+ 874                if self._constants[key][value].delete(client):
+ 875                    new_constants[key][value] = self._constants[key][value]
+ 876            if not new_constants[key]:
+ 877                del new_constants[key]
+ 878        self._constants = new_constants
+ 879        new_schemas: Dict[str, Dict[str, TemplateRegistry]] = {}
+ 880        for key in self._schemas:
+ 881            new_schemas[key] = {}
+ 882            for schema in self._schemas[key]:
+ 883                if self._schemas[key][schema].delete(client):
+ 884                    new_schemas[key][schema] = self._schemas[key][schema]
+ 885            if not new_schemas[key]:
+ 886                del new_schemas[key]
+ 887        self._schemas = new_schemas
+ 888        if self._clients or self._callbacks or self._constants or self._schemas:
+ 889            return True
+ 890        return False
+ 891
+ 892    def check(self, client: str, message: Message) -> bool:
+ 893        """Get if a client has a registered template matching a message.
+ 894
+ 895        >>> r = TemplateRegistry()
+ 896        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+ 897        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 898        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 899        ...     print(f"{m}: {r.check('Client 1', m)}")
+ 900        {'k1': 'v1', 'k2': 'v1'}: True
+ 901        {'k1': 'v1', 'k2': 2}: True
+ 902        {'k1': 'v2', 'k2': 'v1'}: False
+ 903        {'k1': 'v2', 'k2': 2}: False
+ 904        >>> r.insert({'k2': {'type': 'integer'}}, 'Client 2')
+ 905        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 906        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 907        ...     print(f"{m}: {r.check('Client 2', m)}")
+ 908        {'k1': 'v1', 'k2': 'v1'}: False
+ 909        {'k1': 'v1', 'k2': 2}: True
+ 910        {'k1': 'v2', 'k2': 'v1'}: False
+ 911        {'k1': 'v2', 'k2': 2}: True
+ 912        """
+ 913        if client in self._clients:
+ 914            return True
+ 915        for key in self._constants:
+ 916            if (
+ 917                key in message
+ 918                and isinstance(message[key], str)
+ 919                and message[key] in self._constants[key]
+ 920            ):
+ 921                value = message[key]
+ 922                assert isinstance(value, str)
+ 923                child = self._constants[key][value]
+ 924                if child.check(client, message):
+ 925                    return True
+ 926        for key in self._schemas:
+ 927            if key in message:
+ 928                for schema_string in self._schemas[key]:
+ 929                    if validate(schema_string, message[key]):
+ 930                        child = self._schemas[key][schema_string]
+ 931                        if child.check(client, message):
+ 932                            return True
+ 933        return False
+ 934
+ 935    def get(self, message: Message) -> List[str]:
+ 936        """Get all clients registered for templates matching a message.
+ 937
+ 938        >>> r = TemplateRegistry()
+ 939        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+ 940        >>> r.insert({'k2': {'type': 'integer'}}, 'Client 2')
+ 941        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 942        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 943        ...     print(f"{m}: {r.get(m)}")
+ 944        {'k1': 'v1', 'k2': 'v1'}: ['Client 1']
+ 945        {'k1': 'v1', 'k2': 2}: ['Client 1', 'Client 2']
+ 946        {'k1': 'v2', 'k2': 'v1'}: []
+ 947        {'k1': 'v2', 'k2': 2}: ['Client 2']
+ 948        """
+ 949        result = []
+ 950        for client in self._clients:
+ 951            if client not in result:
+ 952                result.append(client)
+ 953        for key in self._constants:
+ 954            if (
+ 955                key in message
+ 956                and isinstance(message[key], str)
+ 957                and message[key] in self._constants[key]
+ 958            ):
+ 959                value = message[key]
+ 960                assert isinstance(value, str)
+ 961                child = self._constants[key][value]
+ 962                for client in child.get(message):
+ 963                    if client not in result:
+ 964                        result.append(client)
+ 965        for key in self._schemas:
+ 966            if key in message:
+ 967                for schema_string in self._schemas[key]:
+ 968                    if validate(schema_string, message[key]):
+ 969                        child = self._schemas[key][schema_string]
+ 970                        for client in child.get(message):
+ 971                            if client not in result:
+ 972                                result.append(client)
+ 973        return result
+ 974
+ 975    def get_callbacks(self, message: Message) -> List[MessageCallback]:
+ 976        """Get all callbacks registered for templates matching a message."""
+ 977        result = []
+ 978        for client in self._callbacks:
+ 979            for callback in self._callbacks[client]:
+ 980                if callback not in result:
+ 981                    result.append(callback)
+ 982        for key in self._constants:
+ 983            if (
+ 984                key in message
+ 985                and isinstance(message[key], str)
+ 986                and message[key] in self._constants[key]
+ 987            ):
+ 988                value = message[key]
+ 989                assert isinstance(value, str)
+ 990                child = self._constants[key][value]
+ 991                for callback in child.get_callbacks(message):
+ 992                    if callback not in result:
+ 993                        result.append(callback)
+ 994        for key in self._schemas:
+ 995            if key in message:
+ 996                for schema_string in self._schemas[key]:
+ 997                    if validate(schema_string, message[key]):
+ 998                        child = self._schemas[key][schema_string]
+ 999                        for callback in child.get_callbacks(message):
+1000                            if callback not in result:
+1001                                result.append(callback)
+1002        return result
+1003
+1004    def get_templates(self, client: str) -> List[MessageTemplate]:
+1005        """Get all templates for a client.
+1006
+1007        >>> r = TemplateRegistry()
+1008        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+1009        >>> r.get_templates('Client 1')
+1010        [{'k1': {'const': 'v1'}}]
+1011        >>> r.insert({'k1': {'const': 'v2'},
+1012        ...           'k2': {'type': 'string'}}, 'Client 2')
+1013        >>> r.get_templates('Client 2')
+1014        [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
+1015        >>> r.insert({'k1': {'const': 'v2'},
+1016        ...           'k2': {'type': 'integer'}}, 'Client 3')
+1017        >>> r.get_templates('Client 3')
+1018        [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
+1019        >>> r.insert({'k2': {'const': 2}}, 'Client 4')
+1020        >>> r.get_templates('Client 4')
+1021        [{'k2': {'const': 2}}]
+1022        >>> r.insert({}, 'Client 5')
+1023        >>> r.get_templates('Client 5')
+1024        [{}]
+1025        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 6')
+1026        >>> r.insert({'k2': {'const': 'v1'}}, 'Client 6')
+1027        >>> r.get_templates('Client 6')
+1028        [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
+1029        """
+1030        if client in self._templates:
+1031            return self._templates[client]
+1032        return []
+1033
+1034
+1035class BusException(Exception):
+1036    """Raise for errors in using message bus."""
+1037
+1038
+1039class MessageBus:
+1040    """Provide an asynchronous message bus.
+1041
+1042    The bus executes asynchronous callbacks for all messages to be received
+1043    by a client. We use a simple callback printing the message in all
+1044    examples:
+1045    >>> def callback_for_receiver(receiver):
+1046    ...     print(f"Creating callback for {receiver}.")
+1047    ...     async def callback(message):
+1048    ...         print(f"{receiver}: {message}")
+1049    ...     return callback
+1050
+1051    Clients can be registered at the bus with a name, lists of message
+1052    templates they want to use for sending and receiving and a callback
+1053    function for receiving. An empty list of templates means that the
+1054    client does not want to send or receive any messages, respectively.
+1055    A list with an empty template means that it wants to send arbitrary
+1056    or receive all messages, respectively:
+1057    >>> async def setup(bus):
+1058    ...     print("Setting up.")
+1059    ...     bus.register('Logger', 'Test Plugin',
+1060    ...                  [],
+1061    ...                  [([MessageTemplate({})],
+1062    ...                    callback_for_receiver('Logger'))])
+1063    ...     bus.register('Client 1', 'Test Plugin',
+1064    ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1065    ...                  [([MessageTemplate({'target':
+1066    ...                                      {'const': 'Client 1'}})],
+1067    ...                    callback_for_receiver('Client 1'))])
+1068    ...     bus.register('Client 2', 'Test Plugin',
+1069    ...                  [MessageTemplate({})],
+1070    ...                  [([MessageTemplate({'target':
+1071    ...                                      {'const': 'Client 2'}})],
+1072    ...                    callback_for_receiver('Client 2'))])
+1073
+1074    The bus itself is addressed by the empty string. It sends messages for
+1075    each registration and deregestration of a client with a key 'event' and
+1076    a value of 'registered' or 'unregistered', a key 'client' with the
+1077    client's name as value and for registrations also keys 'sends' and
+1078    'receives' with all templates registered for the client for sending and
+1079    receiving.
+1080
+1081    Clients can send to the bus with the send function. Each message has to
+1082    declare a sender. The send templates of that sender are checked for a
+1083    template matching the message. We cannot prevent arbitrary code from
+1084    impersonating any sender, but this should only be done in debugging or
+1085    management situations.
+1086
+1087    Messages that are intended for a specific client by convention have a
+1088    key 'target' with the target client's name as value. Such messages are
+1089    often commands to the client to do something, which is by convention
+1090    indicated by a key 'command' with a value that indicates what should be
+1091    done.
+1092
+1093    The bus, for example, reacts to a message with 'target': '' and
+1094    'command': 'get clients' by sending one message for each currently
+1095    registered with complete information about its registered send and
+1096    receive templates.
+1097
+1098    >>> async def send(bus):
+1099    ...     print("Sending messages.")
+1100    ...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})
+1101    ...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
+1102    ...     await bus.send({'sender': '', 'target': '',
+1103    ...                     'command': 'get clients'})
+1104
+1105    The run function executes the message bus forever. If we want to stop
+1106    it, we have to explicitly cancel the task:
+1107    >>> async def main():
+1108    ...     bus = MessageBus()
+1109    ...     await setup(bus)
+1110    ...     bus_task = asyncio.create_task(bus.run())
+1111    ...     await send(bus)
+1112    ...     await asyncio.sleep(0)
+1113    ...     bus_task.cancel()
+1114    ...     try:
+1115    ...         await bus_task
+1116    ...     except asyncio.exceptions.CancelledError:
+1117    ...         pass
+1118    >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+1119    Setting up.
+1120    Creating callback for Logger.
+1121    Creating callback for Client 1.
+1122    Creating callback for Client 2.
+1123    Sending messages.
+1124    Logger: {'sender': '', 'event': 'registered',
+1125             'client': 'Logger', 'plugin': 'Test Plugin',
+1126             'sends': [], 'receives': [{}]}
+1127    Logger: {'sender': '', 'event': 'registered',
+1128             'client': 'Client 1', 'plugin': 'Test Plugin',
+1129             'sends': [{'k1': {'type': 'string'}}],
+1130             'receives': [{'target': {'const': 'Client 1'}}]}
+1131    Logger: {'sender': '', 'event': 'registered',
+1132             'client': 'Client 2', 'plugin': 'Test Plugin',
+1133             'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
+1134    Logger: {'sender': 'Client 1', 'k1': 'Test'}
+1135    Logger: {'sender': 'Client 2', 'target': 'Client 1'}
+1136    Client 1: {'sender': 'Client 2', 'target': 'Client 1'}
+1137    Logger: {'sender': '', 'target': '', 'command': 'get clients'}
+1138    Logger: {'sender': '', 'client': 'Logger', 'plugin': 'Test Plugin',
+1139             'sends': [], 'receives': [{}]}
+1140    Logger: {'sender': '', 'client': 'Client 1', 'plugin': 'Test Plugin',
+1141             'sends': [{'k1': {'type': 'string'}}],
+1142             'receives': [{'target': {'const': 'Client 1'}}]}
+1143    Logger: {'sender': '', 'client': 'Client 2', 'plugin': 'Test Plugin',
+1144             'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
+1145    """
+1146
+1147    def __init__(self) -> None:
+1148        """Initialise a new bus without clients.
+1149
+1150        >>> async def main():
+1151        ...     bus = MessageBus()
+1152        >>> asyncio.run(main())
+1153        """
+1154        self._queue: asyncio.Queue = asyncio.Queue()
+1155        self._plugins: Dict[str, str] = {}
+1156        self._send_reg: TemplateRegistry = TemplateRegistry()
+1157        self._recv_reg: TemplateRegistry = TemplateRegistry()
+1158
+1159    def register(
+1160        self,
+1161        client: str,
+1162        plugin: str,
+1163        sends: Iterable[MessageTemplate],
+1164        receives: Iterable[Tuple[Iterable[MessageTemplate], MessageCallback]],
+1165    ) -> None:
+1166        """Register a client at the message bus.
+1167
+1168        >>> async def callback(message):
+1169        ...     print(message)
+1170        >>> async def main():
+1171        ...     bus = MessageBus()
+1172        ...     bus.register('Logger', 'Test Plugin',
+1173        ...                  [],    # send nothing
+1174        ...                  [([MessageTemplate({})],  # receive everything
+1175        ...                    callback)])
+1176        ...     bus.register('Client 1', 'Test Plugin',
+1177        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1178        ...                      # send with key 'k1' and string value
+1179        ...                  [([MessageTemplate({'target':
+1180        ...                                      {'const': 'Client 1'}})],
+1181        ...                      # receive for this client
+1182        ...                    callback)])
+1183        ...     bus.register('Client 2', 'Test Plugin',
+1184        ...                  [MessageTemplate({})],  # send arbitrary
+1185        ...                  [([MessageTemplate({'target':
+1186        ...                                      {'const': 'Client 2'}})],
+1187        ...                      # receive for this client
+1188        ...                    callback)])
+1189        >>> asyncio.run(main())
+1190        """
+1191        if not client:
+1192            raise BusException("Client name is not allowed to be empty.")
+1193        if client in self._plugins:
+1194            raise BusException(f"Client '{client}' already registered at message bus.")
+1195        event = Message("")
+1196        event["event"] = "registered"
+1197        event["client"] = client
+1198        self._plugins[client] = plugin
+1199        event["plugin"] = plugin
+1200        for template in sends:
+1201            self._send_reg.insert(template, client)
+1202        event["sends"] = self._send_reg.get_templates(client)
+1203        for templates, callback in receives:
+1204            for template in templates:
+1205                self._recv_reg.insert(template, client, callback)
+1206        event["receives"] = self._recv_reg.get_templates(client)
+1207        self._queue.put_nowait(event)
+1208
+1209    def unregister(self, client: str) -> None:
+1210        """Unregister a client from the message bus.
+1211
+1212        >>> async def callback(message):
+1213        ...     print(message)
+1214        >>> async def main():
+1215        ...     bus = MessageBus()
+1216        ...     bus.register('Client 1', 'Test Plugin',
+1217        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1218        ...                  [([MessageTemplate({'target':
+1219        ...                                      {'const': 'Client 1'}})],
+1220        ...                    callback)])
+1221        ...     bus.unregister('Client 1')
+1222        >>> asyncio.run(main())
+1223        """
+1224        if client not in self._plugins:
+1225            return
+1226        event = Message("")
+1227        event["event"] = "unregistered"
+1228        event["client"] = client
+1229        del self._plugins[client]
+1230        self._send_reg.delete(client)
+1231        self._recv_reg.delete(client)
+1232        self._queue.put_nowait(event)
+1233
+1234    async def run(self) -> None:
+1235        """Run the message bus forever.
+1236
+1237        >>> async def main():
+1238        ...     bus = MessageBus()
+1239        ...     bus_task = asyncio.create_task(bus.run())
+1240        ...     bus_task.cancel()
+1241        ...     try:
+1242        ...         await bus_task
+1243        ...     except asyncio.exceptions.CancelledError:
+1244        ...         pass
+1245        >>> asyncio.run(main())
+1246        """
+1247        background_tasks = set()
+1248        while True:
+1249            message = await self._queue.get()
+1250            if "target" in message and message["target"] == "" and "command" in message:
+1251                if message["command"] == "get clients":
+1252                    for client in self._plugins:
+1253                        answer = Message("")
+1254                        answer["client"] = client
+1255                        answer["plugin"] = self._plugins[client]
+1256                        answer["sends"] = self._send_reg.get_templates(client)
+1257                        answer["receives"] = self._recv_reg.get_templates(client)
+1258                        await self._queue.put(answer)
+1259                elif message["command"] == "push conf":
+1260                    conf = {}
+1261                    try:
+1262                        with open(sys.argv[1]) as conf_file:
+1263                            conf = json.load(conf_file)
+1264                    except (
+1265                        IndexError,
+1266                        FileNotFoundError,
+1267                        json.decoder.JSONDecodeError,
+1268                    ):
+1269                        pass
+1270                    if conf == message["conf"]:
+1271                        await self._queue.put(Message("", {"event": "conf unchanged"}))
+1272                    else:
+1273                        await self._queue.put(Message("", {"event": "conf changed"}))
+1274                        with open(sys.argv[1], "w") as conf_file:
+1275                            json.dump(message["conf"], conf_file)
+1276            for callback in self._recv_reg.get_callbacks(message):
+1277                task = asyncio.create_task(callback(message))
+1278                background_tasks.add(task)
+1279                task.add_done_callback(background_tasks.discard)
+1280            self._queue.task_done()
+1281
+1282    async def send(self, message: Message) -> None:
+1283        """Send a message to the message bus.
+1284
+1285        >>> async def callback(message):
+1286        ...     print(f"Got: {message}")
+1287        >>> async def main():
+1288        ...     bus = MessageBus()
+1289        ...     bus.register('Client 1', 'Test Plugin',
+1290        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1291        ...                  [([MessageTemplate({'target':
+1292        ...                                      {'const': 'Client 1'}})],
+1293        ...                    callback)])
+1294        ...     bus.register('Client 2', 'Test Plugin',
+1295        ...                  [MessageTemplate({})],
+1296        ...                  [([MessageTemplate({'target':
+1297        ...                                      {'const': 'Client 2'}})],
+1298        ...                    callback)])
+1299        ...     bus_task = asyncio.create_task(bus.run())
+1300        ...     await bus.send({'sender': 'Client 1', 'target': 'Client 2',
+1301        ...                     'k1': 'Test'})
+1302        ...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
+1303        ...     try:
+1304        ...         await bus.send({'sender': 'Client 1', 'target': 'Client 2',
+1305        ...                         'k1': 42})
+1306        ...     except BusException as e:
+1307        ...         print(e)
+1308        ...     await asyncio.sleep(0)
+1309        ...     bus_task.cancel()
+1310        ...     try:
+1311        ...         await bus_task
+1312        ...     except asyncio.exceptions.CancelledError:
+1313        ...         pass
+1314        >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+1315        Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
+1316        not allowed for sender 'Client 1'.
+1317        Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
+1318        Got: {'sender': 'Client 2', 'target': 'Client 1'}
+1319        """
+1320        assert isinstance(message["sender"], str)
+1321        sender = message["sender"]
+1322        if sender:
+1323            if not self._send_reg.check(sender, message):
+1324                raise BusException(
+1325                    f"Message '{message}' not allowed for sender '{sender}'."
+1326                )
+1327        await self._queue.put(message)
+1328
+1329    def send_nowait(self, message: Message) -> None:
+1330        """Send a message to the message bus without blocking.
+1331
+1332        >>> async def callback(message):
+1333        ...     print(f"Got: {message}")
+1334        >>> async def main():
+1335        ...     bus = MessageBus()
+1336        ...     bus.register('Client 1', 'Test Plugin',
+1337        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1338        ...                  [([MessageTemplate({'target':
+1339        ...                                      {'const': 'Client 1'}})],
+1340        ...                    callback)])
+1341        ...     bus.register('Client 2', 'Test Plugin',
+1342        ...                  [MessageTemplate({})],
+1343        ...                  [([MessageTemplate({'target':
+1344        ...                                      {'const': 'Client 2'}})],
+1345        ...                    callback)])
+1346        ...     bus_task = asyncio.create_task(bus.run())
+1347        ...     bus.send_nowait({'sender': 'Client 1', 'target': 'Client 2',
+1348        ...                      'k1': 'Test'})
+1349        ...     bus.send_nowait({'sender': 'Client 2', 'target': 'Client 1'})
+1350        ...     try:
+1351        ...         bus.send_nowait({'sender': 'Client 1',
+1352        ...                          'target': 'Client 2', 'k1': 42})
+1353        ...     except BusException as e:
+1354        ...         print(e)
+1355        ...     await asyncio.sleep(0)
+1356        ...     bus_task.cancel()
+1357        ...     try:
+1358        ...         await bus_task
+1359        ...     except asyncio.exceptions.CancelledError:
+1360        ...         pass
+1361        >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+1362        Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
+1363        not allowed for sender 'Client 1'.
+1364        Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
+1365        Got: {'sender': 'Client 2', 'target': 'Client 1'}
+1366        """
+1367        assert isinstance(message["sender"], str)
+1368        sender = message["sender"]
+1369        if sender:
+1370            if not self._send_reg.check(sender, message):
+1371                raise BusException(
+1372                    f"Message '{message}' not allowed for sender '{sender}'."
+1373                )
+1374        self._queue.put_nowait(message)
+
+ + +
+
+
+ MessageValue = +None | str | int | float | bool | typing.Dict[str, typing.Any] | typing.List[typing.Any] + + +
+ + + + +
+
+
+ JSONSchema = + + bool | typing.Dict[str, None | str | int | float | bool | typing.Dict[str, typing.Any] | typing.List[typing.Any]] + + +
+ + + + +
+
+
+ MessageCallback = +typing.Callable[[ForwardRef('Message')], typing.Coroutine[typing.Any, typing.Any, NoneType]] + + +
+ + + + +
+
+ +
+ + def + register_schema( schema: bool | Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]]) -> bool: + + + +
+ +
122def register_schema(schema: JSONSchema) -> bool:
+123    """Register the given JSON schema in the global cache."""
+124    global _validates
+125    schema_string = json.dumps(schema)
+126    if schema_string not in _validates:
+127        if not (isinstance(schema, dict) or isinstance(schema, bool)):
+128            return False
+129        try:
+130            _validates[schema_string] = fastjsonschema.compile(schema)
+131        except fastjsonschema.JsonSchemaDefinitionException:
+132            return False
+133    return True
+
+ + +

Register the given JSON schema in the global cache.

+
+ + +
+
+ +
+ + def + validate( schema_string: str, value: None | str | int | float | bool | Dict[str, Any] | List[Any]) -> bool: + + + +
+ +
136def validate(schema_string: str, value: MessageValue) -> bool:
+137    """Validate the given MessageValue against the given JSON schema string."""
+138    global _validates
+139    if schema_string not in _validates:
+140        schema = json.loads(schema_string)
+141        _validates[schema_string] = fastjsonschema.compile(schema)
+142    validate = _validates[schema_string]
+143    try:
+144        validate(value)
+145    except fastjsonschema.JsonSchemaException:
+146        return False
+147    return True
+
+ + +

Validate the given MessageValue against the given JSON schema string.

+
+ + +
+
+ +
+ + class + Message(typing.Dict[str, None | str | int | float | bool | typing.Dict[str, typing.Any] | typing.List[typing.Any]]): + + + +
+ +
150class Message(Dict[str, MessageValue]):
+151    """Define arbitrary message.
+152
+153    Messages are dictionaries with string keys and values that are strings,
+154    integers, floats, Booleans, dictionaries that recursively have string
+155    keys and values of any of these types, or lists with elements that have
+156    any of these types. These constraints are checked when setting key-value
+157    pairs of the message.
+158
+159    A message has to have a sender, which is set by the constructor:
+160    >>> m = Message('Example sender')
+161    >>> print(m)
+162    {'sender': 'Example sender'}
+163
+164    A dictionary can be given to the constructor:
+165    >>> m = Message('Example sender',
+166    ...             {'key 1': 'value 1', 'key 2': 'value 2'})
+167    >>> print(m)
+168    {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
+169
+170    A 'sender' set in the initial dictionary is overwritten by the explicitly
+171    given sender in the first argument:
+172    >>> m = Message('Example sender',
+173    ...             {'sender': 'Original sender', 'key': 'value'})
+174    >>> print(m)
+175    {'sender': 'Example sender', 'key': 'value'}
+176
+177    Or the message can be modified after construction:
+178    >>> m = Message('Example sender', {'key 1': 'value 1'})
+179    >>> m['key 2'] = 'value 2'
+180    >>> print(m)
+181    {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
+182
+183    The 'sender' key can be overwritten, but this should only be done in
+184    exceptional cases:
+185    >>> m = Message('Example sender', {'key': 'value'})
+186    >>> m['sender'] = 'New sender'
+187    >>> print(m)
+188    {'sender': 'New sender', 'key': 'value'}
+189    """
+190
+191    def __init__(
+192        self, sender: str, init: Optional[Dict[str, MessageValue]] = None
+193    ) -> None:
+194        """Initialise message.
+195
+196        Message is initialised with given sender and possibly given
+197        key-value pairs:
+198        >>> m = Message('Example sender')
+199        >>> print(m)
+200        {'sender': 'Example sender'}
+201        >>> m = Message('Example sender', {'key 1': 'value 1'})
+202        >>> print(m)
+203        {'sender': 'Example sender', 'key 1': 'value 1'}
+204        """
+205        if not isinstance(sender, str):
+206            raise TypeError(f"'{sender}' is not a valid sender name (not a string).")
+207        self["sender"] = ""
+208        if init is not None:
+209            self.update(init)
+210        self["sender"] = sender
+211
+212    @staticmethod
+213    def check_value(value: MessageValue) -> bool:
+214        """Check recursively if a given value is valid.
+215
+216        None, strings, integers, floats and Booleans are valid:
+217        >>> Message.check_value(None)
+218        True
+219        >>> Message.check_value('Spam')
+220        True
+221        >>> Message.check_value(42)
+222        True
+223        >>> Message.check_value(42.42)
+224        True
+225        >>> Message.check_value(False)
+226        True
+227
+228        Other basic types are not valid:
+229        >>> Message.check_value(b'bytes')
+230        False
+231        >>> Message.check_value(1j)
+232        False
+233
+234        Dictionaries with string keys and recursively valid values are valid:
+235        >>> Message.check_value({'str value': 'Spam', 'int value': 42,
+236        ...                      'float value': 42.42, 'bool value': False})
+237        True
+238
+239        Empty dictionaries are valid:
+240        >>> Message.check_value({})
+241        True
+242
+243        Dictionaries with other keys are not valid:
+244        >>> Message.check_value({42: 'int key'})
+245        False
+246
+247        Dictionaries with invalid values are not valid:
+248        >>> Message.check_value({'complex value': 1j})
+249        False
+250
+251        Lists with valid elements are valid:
+252        >>> Message.check_value(['Spam', 42, 42.42, False])
+253        True
+254
+255        Empty lists are valid:
+256        >>> Message.check_value([])
+257        True
+258
+259        Lists with invalid elements are not valid:
+260        >>> Message.check_value([1j])
+261        False
+262        """
+263        if value is None:
+264            return True
+265        elif (
+266            isinstance(value, str)
+267            or isinstance(value, int)
+268            or isinstance(value, float)
+269            or isinstance(value, bool)
+270        ):
+271            return True
+272        elif isinstance(value, dict):
+273            for key in value:
+274                if not isinstance(key, str):
+275                    return False
+276                if not Message.check_value(value[key]):
+277                    return False
+278            return True
+279        elif isinstance(value, list):
+280            for element in value:
+281                if not Message.check_value(element):
+282                    return False
+283            return True
+284        return False
+285
+286    def __setitem__(self, key: str, value: MessageValue) -> None:
+287        """Check key and value before putting pair into dict.
+288
+289        >>> m = Message('Example sender')
+290        >>> m['key'] = 'value'
+291        >>> m['dict'] = {'k1': 'v1', 'k2': 2}
+292        >>> print(m)  # doctest: +NORMALIZE_WHITESPACE
+293        {'sender': 'Example sender', 'key': 'value',
+294         'dict': {'k1': 'v1', 'k2': 2}}
+295        >>> m = Message('Example sender')
+296        >>> m[42] = 'int key'
+297        Traceback (most recent call last):
+298          ...
+299        TypeError: '42' is not a valid key in Message (not a string).
+300        >>> m['complex value'] = 1j
+301        Traceback (most recent call last):
+302          ...
+303        TypeError: '1j' is not a valid value in Message.
+304        """
+305        if not isinstance(key, str):
+306            raise TypeError(f"'{key}' is not a valid key in Message (not a string).")
+307        if not self.check_value(value):
+308            raise TypeError(f"'{value}' is not a valid value in Message.")
+309        super().__setitem__(key, value)
+310
+311    def update(self, *args, **kwargs) -> None:
+312        """Override update to use validity checks.
+313
+314        >>> m = Message('Example sender')
+315        >>> m.update({'key 1': 'value 1', 'key 2': 'value 2'})
+316        >>> print(m)
+317        {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
+318        >>> m.update({42: 'int key'})
+319        Traceback (most recent call last):
+320          ...
+321        TypeError: '42' is not a valid key in Message (not a string).
+322        >>> m.update({'complex value': 1j})
+323        Traceback (most recent call last):
+324          ...
+325        TypeError: '1j' is not a valid value in Message.
+326
+327        This is also used in __init__:
+328        >>> m = Message('Example sender', {'key': 'value'})
+329        >>> print(m)
+330        {'sender': 'Example sender', 'key': 'value'}
+331        >>> m = Message('Example sender', {42: 'int key'})
+332        Traceback (most recent call last):
+333          ...
+334        TypeError: '42' is not a valid key in Message (not a string).
+335        >>> m = Message('Example sender', {'complex value': 1j})
+336        Traceback (most recent call last):
+337          ...
+338        TypeError: '1j' is not a valid value in Message.
+339        """
+340        if args:
+341            if len(args) > 1:
+342                raise TypeError(f"update expected at most 1 argument, got {len(args)}")
+343            other = dict(args[0])
+344            for key in other:
+345                self[key] = other[key]
+346        for key in kwargs:
+347            self[key] = kwargs[key]
+348
+349    def setdefault(self, key: str, value: MessageValue = None) -> MessageValue:
+350        """Override setdefault to use validity checks.
+351
+352        >>> m = Message('Example sender')
+353        >>> m.setdefault('key', 'value 1')
+354        'value 1'
+355        >>> m.setdefault('key', 'value 2')
+356        'value 1'
+357        >>> m.setdefault(42, 'int key')
+358        Traceback (most recent call last):
+359          ...
+360        TypeError: '42' is not a valid key in Message (not a string).
+361        >>> m.setdefault('complex value', 1j)
+362        Traceback (most recent call last):
+363          ...
+364        TypeError: '1j' is not a valid value in Message.
+365
+366        But __setitem__ is not called if the key is already present:
+367        >>> m.setdefault('key', 1j)
+368        'value 1'
+369        """
+370        if key not in self:
+371            self[key] = value
+372        return self[key]
+
+ + +

Define arbitrary message.

+ +

Messages are dictionaries with string keys and values that are strings, +integers, floats, Booleans, dictionaries that recursively have string +keys and values of any of these types, or lists with elements that have +any of these types. These constraints are checked when setting key-value +pairs of the message.

+ +

A message has to have a sender, which is set by the constructor:

+ +
+
>>> m = Message('Example sender')
+>>> print(m)
+{'sender': 'Example sender'}
+
+
+ +

A dictionary can be given to the constructor:

+ +
+
>>> 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'}
+
+
+
+ + +
+ +
+ + Message( sender: str, init: Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]] | None = None) + + + +
+ +
191    def __init__(
+192        self, sender: str, init: Optional[Dict[str, MessageValue]] = None
+193    ) -> None:
+194        """Initialise message.
+195
+196        Message is initialised with given sender and possibly given
+197        key-value pairs:
+198        >>> m = Message('Example sender')
+199        >>> print(m)
+200        {'sender': 'Example sender'}
+201        >>> m = Message('Example sender', {'key 1': 'value 1'})
+202        >>> print(m)
+203        {'sender': 'Example sender', 'key 1': 'value 1'}
+204        """
+205        if not isinstance(sender, str):
+206            raise TypeError(f"'{sender}' is not a valid sender name (not a string).")
+207        self["sender"] = ""
+208        if init is not None:
+209            self.update(init)
+210        self["sender"] = sender
+
+ + +

Initialise message.

+ +

Message is initialised with given sender and possibly given +key-value pairs:

+ +
+
>>> m = Message('Example sender')
+>>> print(m)
+{'sender': 'Example sender'}
+>>> m = Message('Example sender', {'key 1': 'value 1'})
+>>> print(m)
+{'sender': 'Example sender', 'key 1': 'value 1'}
+
+
+
+ + +
+
+ +
+
@staticmethod
+ + def + check_value( value: None | str | int | float | bool | Dict[str, Any] | List[Any]) -> bool: + + + +
+ +
212    @staticmethod
+213    def check_value(value: MessageValue) -> bool:
+214        """Check recursively if a given value is valid.
+215
+216        None, strings, integers, floats and Booleans are valid:
+217        >>> Message.check_value(None)
+218        True
+219        >>> Message.check_value('Spam')
+220        True
+221        >>> Message.check_value(42)
+222        True
+223        >>> Message.check_value(42.42)
+224        True
+225        >>> Message.check_value(False)
+226        True
+227
+228        Other basic types are not valid:
+229        >>> Message.check_value(b'bytes')
+230        False
+231        >>> Message.check_value(1j)
+232        False
+233
+234        Dictionaries with string keys and recursively valid values are valid:
+235        >>> Message.check_value({'str value': 'Spam', 'int value': 42,
+236        ...                      'float value': 42.42, 'bool value': False})
+237        True
+238
+239        Empty dictionaries are valid:
+240        >>> Message.check_value({})
+241        True
+242
+243        Dictionaries with other keys are not valid:
+244        >>> Message.check_value({42: 'int key'})
+245        False
+246
+247        Dictionaries with invalid values are not valid:
+248        >>> Message.check_value({'complex value': 1j})
+249        False
+250
+251        Lists with valid elements are valid:
+252        >>> Message.check_value(['Spam', 42, 42.42, False])
+253        True
+254
+255        Empty lists are valid:
+256        >>> Message.check_value([])
+257        True
+258
+259        Lists with invalid elements are not valid:
+260        >>> Message.check_value([1j])
+261        False
+262        """
+263        if value is None:
+264            return True
+265        elif (
+266            isinstance(value, str)
+267            or isinstance(value, int)
+268            or isinstance(value, float)
+269            or isinstance(value, bool)
+270        ):
+271            return True
+272        elif isinstance(value, dict):
+273            for key in value:
+274                if not isinstance(key, str):
+275                    return False
+276                if not Message.check_value(value[key]):
+277                    return False
+278            return True
+279        elif isinstance(value, list):
+280            for element in value:
+281                if not Message.check_value(element):
+282                    return False
+283            return True
+284        return False
+
+ + +

Check recursively if a given value is valid.

+ +

None, strings, integers, floats and Booleans are valid:

+ +
+
>>> Message.check_value(None)
+True
+>>> Message.check_value('Spam')
+True
+>>> Message.check_value(42)
+True
+>>> Message.check_value(42.42)
+True
+>>> Message.check_value(False)
+True
+
+
+ +

Other basic types are not valid:

+ +
+
>>> Message.check_value(b'bytes')
+False
+>>> Message.check_value(1j)
+False
+
+
+ +

Dictionaries with string keys and recursively valid values are valid:

+ +
+
>>> Message.check_value({'str value': 'Spam', 'int value': 42,
+...                      'float value': 42.42, 'bool value': False})
+True
+
+
+ +

Empty dictionaries are valid:

+ +
+
>>> Message.check_value({})
+True
+
+
+ +

Dictionaries with other keys are not valid:

+ +
+
>>> Message.check_value({42: 'int key'})
+False
+
+
+ +

Dictionaries with invalid values are not valid:

+ +
+
>>> Message.check_value({'complex value': 1j})
+False
+
+
+ +

Lists with valid elements are valid:

+ +
+
>>> Message.check_value(['Spam', 42, 42.42, False])
+True
+
+
+ +

Empty lists are valid:

+ +
+
>>> Message.check_value([])
+True
+
+
+ +

Lists with invalid elements are not valid:

+ +
+
>>> Message.check_value([1j])
+False
+
+
+
+ + +
+
+ +
+ + def + update(self, *args, **kwargs) -> None: + + + +
+ +
311    def update(self, *args, **kwargs) -> None:
+312        """Override update to use validity checks.
+313
+314        >>> m = Message('Example sender')
+315        >>> m.update({'key 1': 'value 1', 'key 2': 'value 2'})
+316        >>> print(m)
+317        {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
+318        >>> m.update({42: 'int key'})
+319        Traceback (most recent call last):
+320          ...
+321        TypeError: '42' is not a valid key in Message (not a string).
+322        >>> m.update({'complex value': 1j})
+323        Traceback (most recent call last):
+324          ...
+325        TypeError: '1j' is not a valid value in Message.
+326
+327        This is also used in __init__:
+328        >>> m = Message('Example sender', {'key': 'value'})
+329        >>> print(m)
+330        {'sender': 'Example sender', 'key': 'value'}
+331        >>> m = Message('Example sender', {42: 'int key'})
+332        Traceback (most recent call last):
+333          ...
+334        TypeError: '42' is not a valid key in Message (not a string).
+335        >>> m = Message('Example sender', {'complex value': 1j})
+336        Traceback (most recent call last):
+337          ...
+338        TypeError: '1j' is not a valid value in Message.
+339        """
+340        if args:
+341            if len(args) > 1:
+342                raise TypeError(f"update expected at most 1 argument, got {len(args)}")
+343            other = dict(args[0])
+344            for key in other:
+345                self[key] = other[key]
+346        for key in kwargs:
+347            self[key] = kwargs[key]
+
+ + +

Override update to use validity checks.

+ +
+
>>> m = Message('Example sender')
+>>> m.update({'key 1': 'value 1', 'key 2': 'value 2'})
+>>> print(m)
+{'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
+>>> m.update({42: 'int key'})
+Traceback (most recent call last):
+  ...
+TypeError: '42' is not a valid key in Message (not a string).
+>>> m.update({'complex value': 1j})
+Traceback (most recent call last):
+  ...
+TypeError: '1j' is not a valid value in Message.
+
+
+ +

This is also used in __init__:

+ +
+
>>> m = Message('Example sender', {'key': 'value'})
+>>> print(m)
+{'sender': 'Example sender', 'key': 'value'}
+>>> m = Message('Example sender', {42: 'int key'})
+Traceback (most recent call last):
+  ...
+TypeError: '42' is not a valid key in Message (not a string).
+>>> m = Message('Example sender', {'complex value': 1j})
+Traceback (most recent call last):
+  ...
+TypeError: '1j' is not a valid value in Message.
+
+
+
+ + +
+
+ +
+ + def + setdefault( self, key: str, value: None | str | int | float | bool | Dict[str, Any] | List[Any] = None) -> None | str | int | float | bool | Dict[str, Any] | List[Any]: + + + +
+ +
349    def setdefault(self, key: str, value: MessageValue = None) -> MessageValue:
+350        """Override setdefault to use validity checks.
+351
+352        >>> m = Message('Example sender')
+353        >>> m.setdefault('key', 'value 1')
+354        'value 1'
+355        >>> m.setdefault('key', 'value 2')
+356        'value 1'
+357        >>> m.setdefault(42, 'int key')
+358        Traceback (most recent call last):
+359          ...
+360        TypeError: '42' is not a valid key in Message (not a string).
+361        >>> m.setdefault('complex value', 1j)
+362        Traceback (most recent call last):
+363          ...
+364        TypeError: '1j' is not a valid value in Message.
+365
+366        But __setitem__ is not called if the key is already present:
+367        >>> m.setdefault('key', 1j)
+368        'value 1'
+369        """
+370        if key not in self:
+371            self[key] = value
+372        return self[key]
+
+ + +

Override setdefault to use validity checks.

+ +
+
>>> m = Message('Example sender')
+>>> m.setdefault('key', 'value 1')
+'value 1'
+>>> m.setdefault('key', 'value 2')
+'value 1'
+>>> m.setdefault(42, 'int key')
+Traceback (most recent call last):
+  ...
+TypeError: '42' is not a valid key in Message (not a string).
+>>> m.setdefault('complex value', 1j)
+Traceback (most recent call last):
+  ...
+TypeError: '1j' is not a valid value in Message.
+
+
+ +

But __setitem__ is not called if the key is already present:

+ +
+
>>> m.setdefault('key', 1j)
+'value 1'
+
+
+
+ + +
+
+
+ +
+ + class + MessageTemplate(typing.Dict[str, bool | typing.Dict[str, None | str | int | float | bool | typing.Dict[str, typing.Any] | typing.List[typing.Any]]]): + + + +
+ +
375class MessageTemplate(Dict[str, JSONSchema]):
+376    """Define a message template.
+377
+378    A message template is a mapping from string keys to JSON schemas as
+379    values:
+380    >>> t = MessageTemplate({'key 1': {'const': 'value'},
+381    ...                      'key 2': {'type': 'string'}})
+382    >>> t['key 3'] = {'type': 'object',
+383    ...               'properties': {'key 1': {'type': 'number'},
+384    ...                              'key 2': True}}
+385
+386    A message template matches a message if all keys of the template are
+387    contained in the message and the values in the message validate against
+388    the respective schemas:
+389    >>> t.check(Message('Example Sender',
+390    ...                 {'key 1': 'value', 'key 2': 'some string',
+391    ...                  'key 3': {'key 1': 42, 'key 2': None}}))
+392    True
+393
+394    An empty mapping therefore matches all messages:
+395    >>> t = MessageTemplate()
+396    >>> t.check(Message('Example Sender', {'arbitrary': 'content'}))
+397    True
+398    """
+399
+400    def __init__(self, init: Optional[Dict[str, JSONSchema]] = None) -> None:
+401        """Initialise message.
+402
+403        Template is initialised empty or with given key-value pairs:
+404        >>> t = MessageTemplate()
+405        >>> print(t)
+406        {}
+407        >>> t = MessageTemplate({'key': {'const': 'value'}})
+408        >>> print(t)
+409        {'key': {'const': 'value'}}
+410        """
+411        if init is not None:
+412            self.update(init)
+413
+414    @staticmethod
+415    def from_message(message: Message) -> "MessageTemplate":
+416        """Create template from message.
+417
+418        Template witch constant schemas is created from message:
+419        >>> m = Message('Example Sender', {'key': 'value'})
+420        >>> t = MessageTemplate.from_message(m)
+421        >>> print(t)
+422        {'sender': {'const': 'Example Sender'}, 'key': {'const': 'value'}}
+423        >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
+424        ...                                'list': [None, True, 'string']})
+425        >>> t = MessageTemplate.from_message(m)
+426        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+427        {'sender': {'const': 'Example Sender'},
+428         'dict': {'type': 'object',
+429                  'properties': {'int': {'const': 42},
+430                                 'float': {'const': 42.42}}},
+431         'list': {'type': 'array',
+432                  'items': [{'const': None},
+433                            {'const': True},
+434                            {'const': 'string'}]}}
+435
+436        This is especially useful for clients that send certain fully
+437        predefined messages, where the message is given in the configuration
+438        and the template for the registration can be constructed by this
+439        method.
+440        """
+441
+442        def schema_from_value(value: MessageValue) -> JSONSchema:
+443            schema: JSONSchema = False
+444            if value is None:
+445                schema = {"const": None}
+446            elif (
+447                isinstance(value, str)
+448                or isinstance(value, int)
+449                or isinstance(value, float)
+450                or isinstance(value, bool)
+451            ):
+452                schema = {"const": value}
+453            elif isinstance(value, dict):
+454                properties = {}
+455                for inner_key in value:
+456                    inner_value: Message = value[inner_key]
+457                    properties[inner_key] = schema_from_value(inner_value)
+458                schema = {"type": "object", "properties": properties}
+459            elif isinstance(value, list):
+460                schema = {
+461                    "type": "array",
+462                    "items": [schema_from_value(element) for element in value],
+463                }
+464            return schema
+465
+466        template = MessageTemplate()
+467        for key in message:
+468            template[key] = schema_from_value(message[key])
+469        return template
+470
+471    def __setitem__(self, key: str, value: JSONSchema) -> None:
+472        """Check key and value before putting pair into dict.
+473
+474        >>> t = MessageTemplate()
+475        >>> t['key 1'] = {'const': 'value'}
+476        >>> t['key 2'] = {'type': 'string'}
+477        >>> t['key 3'] = {'type': 'object',
+478        ...               'properties': {'key 1': {'type': 'number'},
+479        ...                              'key 2': True}}
+480        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+481        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
+482         'key 3': {'type': 'object',
+483                   'properties': {'key 1': {'type': 'number'},
+484                                  'key 2': True}}}
+485        >>> t[42] = {'const': 'int key'}
+486        Traceback (most recent call last):
+487          ...
+488        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+489        >>> t['key'] = 'schema'  # doctest: +NORMALIZE_WHITESPACE
+490        Traceback (most recent call last):
+491          ...
+492        TypeError: 'schema' is not a valid value in MessageTemplate
+493        (not a valid JSON schema).
+494        >>> t['key'] = True
+495        """
+496        if not isinstance(key, str):
+497            raise TypeError(
+498                f"'{key}' is not a valid key in MessageTemplate (not a string)."
+499            )
+500        if not register_schema(value):
+501            raise TypeError(
+502                f"'{value}' is not a valid value in"
+503                " MessageTemplate (not a valid JSON schema)."
+504            )
+505        super().__setitem__(key, value)
+506
+507    def update(self, *args, **kwargs) -> None:
+508        """Override update to use validity checks.
+509
+510        >>> t = MessageTemplate()
+511        >>> t.update({'key 1': {'const': 'value'},
+512        ...           'key 2': {'type': 'string'},
+513        ...           'key 3': {'type': 'object',
+514        ...                     'properties': {'key 1': {'type': 'number'},
+515        ...                                    'key 2': True}}})
+516        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+517        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
+518         'key 3': {'type': 'object',
+519                   'properties': {'key 1': {'type': 'number'},
+520                                  'key 2': True}}}
+521        >>> t.update({42: {'const': 'int key'}})
+522        Traceback (most recent call last):
+523          ...
+524        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+525        >>> t.update({'key': 'schema'})  # doctest: +NORMALIZE_WHITESPACE
+526        Traceback (most recent call last):
+527          ...
+528        TypeError: 'schema' is not a valid value in MessageTemplate
+529        (not a valid JSON schema).
+530        >>> t.update({'key': True})
+531
+532        This is also used in __init__:
+533        >>> t = MessageTemplate({'key 1': {'const': 'value'},
+534        ...                      'key 2': {'type': 'string'},
+535        ...                      'key 3': {'type': 'object',
+536        ...                                'properties': {
+537        ...                                    'key 1': {'type': 'number'},
+538        ...                                    'key 2': True}}})
+539        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+540        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
+541         'key 3': {'type': 'object',
+542                   'properties': {'key 1': {'type': 'number'},
+543                                  'key 2': True}}}
+544        >>> t = MessageTemplate({42: {'const': 'int key'}})
+545        Traceback (most recent call last):
+546          ...
+547        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+548        >>> t = MessageTemplate({'key': 'schema'})
+549        ... # doctest: +NORMALIZE_WHITESPACE
+550        Traceback (most recent call last):
+551          ...
+552        TypeError: 'schema' is not a valid value in MessageTemplate
+553        (not a valid JSON schema).
+554        >>> t = MessageTemplate({'key': True})
+555        """
+556        if args:
+557            if len(args) > 1:
+558                raise TypeError(f"update expected at most 1 argument, got {len(args)}")
+559            other = dict(args[0])
+560            for key in other:
+561                self[key] = other[key]
+562        for key in kwargs:
+563            self[key] = kwargs[key]
+564
+565    def setdefault(self, key: str, value: Optional[JSONSchema] = None) -> JSONSchema:
+566        """Override setdefault to use validity checks.
+567
+568        >>> t = MessageTemplate()
+569        >>> t.setdefault('key 1', {'const': 'value'})
+570        {'const': 'value'}
+571        >>> t.setdefault('key 2', {'type': 'string'})
+572        {'type': 'string'}
+573        >>> t.setdefault('key 3', {'type': 'object',
+574        ...                        'properties': {'key 1': {'type': 'number'},
+575        ...                                       'key 2': True}})
+576        ... # doctest: +NORMALIZE_WHITESPACE
+577        {'type': 'object',
+578                   'properties': {'key 1': {'type': 'number'},
+579                                  'key 2': True}}
+580        >>> t.setdefault(42, {'const': 'int key'})
+581        Traceback (most recent call last):
+582          ...
+583        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+584        >>> t.setdefault('key', 'schema')  # doctest: +NORMALIZE_WHITESPACE
+585        Traceback (most recent call last):
+586          ...
+587        TypeError: 'schema' is not a valid value in MessageTemplate
+588        (not a valid JSON schema).
+589
+590        But __setitem__ is not called if the key is already present:
+591        >>> t.setdefault('key 1', 'schema')
+592        {'const': 'value'}
+593        """
+594        if key not in self:
+595            if value is not None:
+596                self[key] = value
+597            else:
+598                self[key] = True
+599        return self[key]
+600
+601    def check(self, message: Message) -> bool:
+602        """Check message against this template.
+603
+604        Constant values have to match exactly:
+605        >>> t = MessageTemplate({'key': {'const': 'value'}})
+606        >>> t.check(Message('Example Sender', {'key': 'value'}))
+607        True
+608        >>> t.check(Message('Example Sender', {'key': 'other value'}))
+609        False
+610
+611        But for integers, floats with the same value are also valid:
+612        >>> t = MessageTemplate({'key': {'const': 42}})
+613        >>> t.check(Message('Example Sender', {'key': 42}))
+614        True
+615        >>> t.check(Message('Example Sender', {'key': 42.0}))
+616        True
+617
+618        Type integer is valid for floats with zero fractional part, but
+619        not by floats with non-zero fractional part:
+620        >>> t = MessageTemplate({'key': {'type': 'integer'}})
+621        >>> t.check(Message('Example Sender', {'key': 42}))
+622        True
+623        >>> t.check(Message('Example Sender', {'key': 42.0}))
+624        True
+625        >>> t.check(Message('Example Sender', {'key': 42.42}))
+626        False
+627
+628        Type number is valid for arbitrary ints or floats:
+629        >>> t = MessageTemplate({'key': {'type': 'number'}})
+630        >>> t.check(Message('Example Sender', {'key': 42}))
+631        True
+632        >>> t.check(Message('Example Sender', {'key': 42.42}))
+633        True
+634
+635        All keys in template have to be present in message:
+636        >>> t = MessageTemplate({'key 1': {'const': 'value'},
+637        ...                      'key 2': {'type': 'string'},
+638        ...                      'key 3': {'type': 'object',
+639        ...                                'properties': {
+640        ...                                    'key 1': {'type': 'number'},
+641        ...                                    'key 2': True,
+642        ...                                    'key 3': False}}})
+643        >>> t.check(Message('Example Sender',
+644        ...                 {'key 1': 'value', 'key 2': 'some string'}))
+645        False
+646
+647        But for nested objects their properties do not necessarily have
+648        to be present:
+649        >>> t.check(Message('Example Sender',
+650        ...                 {'key 1': 'value', 'key 2': 'some string',
+651        ...                  'key 3': {'key 1': 42}}))
+652        True
+653
+654        Schema True matches everything (even None):
+655        >>> t.check(Message('Example Sender',
+656        ...                 {'key 1': 'value', 'key 2': 'some string',
+657        ...                  'key 3': {'key 2': None}}))
+658        True
+659
+660        Schema False matches nothing:
+661        >>> t.check(Message('Example Sender',
+662        ...                 {'key 1': 'value', 'key 2': 'some string',
+663        ...                  'key 3': {'key 3': True}}))
+664        False
+665
+666        Message is valid for the constant template created from it:
+667        >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
+668        ...                                'list': [None, True, 'string']})
+669        >>> t = MessageTemplate.from_message(m)
+670        >>> t.check(m)
+671        True
+672        """
+673        for key in self:
+674            if key not in message:
+675                return False
+676            else:
+677                schema_string = json.dumps(self[key])
+678                if not validate(schema_string, message[key]):
+679                    return False
+680        return True
+
+ + +

Define a message template.

+ +

A message template is a mapping from string keys to JSON schemas as +values:

+ +
+
>>> t = MessageTemplate({'key 1': {'const': 'value'},
+...                      'key 2': {'type': 'string'}})
+>>> t['key 3'] = {'type': 'object',
+...               'properties': {'key 1': {'type': 'number'},
+...                              'key 2': True}}
+
+
+ +

A message template matches a message if all keys of the template are +contained in the message and the values in the message validate against +the respective schemas:

+ +
+
>>> t.check(Message('Example Sender',
+...                 {'key 1': 'value', 'key 2': 'some string',
+...                  'key 3': {'key 1': 42, 'key 2': None}}))
+True
+
+
+ +

An empty mapping therefore matches all messages:

+ +
+
>>> t = MessageTemplate()
+>>> t.check(Message('Example Sender', {'arbitrary': 'content'}))
+True
+
+
+
+ + +
+ +
+ + MessageTemplate( init: Dict[str, bool | Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]]] | None = None) + + + +
+ +
400    def __init__(self, init: Optional[Dict[str, JSONSchema]] = None) -> None:
+401        """Initialise message.
+402
+403        Template is initialised empty or with given key-value pairs:
+404        >>> t = MessageTemplate()
+405        >>> print(t)
+406        {}
+407        >>> t = MessageTemplate({'key': {'const': 'value'}})
+408        >>> print(t)
+409        {'key': {'const': 'value'}}
+410        """
+411        if init is not None:
+412            self.update(init)
+
+ + +

Initialise message.

+ +

Template is initialised empty or with given key-value pairs:

+ +
+
>>> t = MessageTemplate()
+>>> print(t)
+{}
+>>> t = MessageTemplate({'key': {'const': 'value'}})
+>>> print(t)
+{'key': {'const': 'value'}}
+
+
+
+ + +
+
+ +
+
@staticmethod
+ + def + from_message( message: Message) -> MessageTemplate: + + + +
+ +
414    @staticmethod
+415    def from_message(message: Message) -> "MessageTemplate":
+416        """Create template from message.
+417
+418        Template witch constant schemas is created from message:
+419        >>> m = Message('Example Sender', {'key': 'value'})
+420        >>> t = MessageTemplate.from_message(m)
+421        >>> print(t)
+422        {'sender': {'const': 'Example Sender'}, 'key': {'const': 'value'}}
+423        >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
+424        ...                                'list': [None, True, 'string']})
+425        >>> t = MessageTemplate.from_message(m)
+426        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+427        {'sender': {'const': 'Example Sender'},
+428         'dict': {'type': 'object',
+429                  'properties': {'int': {'const': 42},
+430                                 'float': {'const': 42.42}}},
+431         'list': {'type': 'array',
+432                  'items': [{'const': None},
+433                            {'const': True},
+434                            {'const': 'string'}]}}
+435
+436        This is especially useful for clients that send certain fully
+437        predefined messages, where the message is given in the configuration
+438        and the template for the registration can be constructed by this
+439        method.
+440        """
+441
+442        def schema_from_value(value: MessageValue) -> JSONSchema:
+443            schema: JSONSchema = False
+444            if value is None:
+445                schema = {"const": None}
+446            elif (
+447                isinstance(value, str)
+448                or isinstance(value, int)
+449                or isinstance(value, float)
+450                or isinstance(value, bool)
+451            ):
+452                schema = {"const": value}
+453            elif isinstance(value, dict):
+454                properties = {}
+455                for inner_key in value:
+456                    inner_value: Message = value[inner_key]
+457                    properties[inner_key] = schema_from_value(inner_value)
+458                schema = {"type": "object", "properties": properties}
+459            elif isinstance(value, list):
+460                schema = {
+461                    "type": "array",
+462                    "items": [schema_from_value(element) for element in value],
+463                }
+464            return schema
+465
+466        template = MessageTemplate()
+467        for key in message:
+468            template[key] = schema_from_value(message[key])
+469        return template
+
+ + +

Create template from message.

+ +

Template witch constant schemas is created from message:

+ +
+
>>> m = Message('Example Sender', {'key': 'value'})
+>>> t = MessageTemplate.from_message(m)
+>>> print(t)
+{'sender': {'const': 'Example Sender'}, 'key': {'const': 'value'}}
+>>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
+...                                'list': [None, True, 'string']})
+>>> t = MessageTemplate.from_message(m)
+>>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+{'sender': {'const': 'Example Sender'},
+ 'dict': {'type': 'object',
+          'properties': {'int': {'const': 42},
+                         'float': {'const': 42.42}}},
+ 'list': {'type': 'array',
+          'items': [{'const': None},
+                    {'const': True},
+                    {'const': 'string'}]}}
+
+
+ +

This is especially useful for clients that send certain fully +predefined messages, where the message is given in the configuration +and the template for the registration can be constructed by this +method.

+
+ + +
+
+ +
+ + def + update(self, *args, **kwargs) -> None: + + + +
+ +
507    def update(self, *args, **kwargs) -> None:
+508        """Override update to use validity checks.
+509
+510        >>> t = MessageTemplate()
+511        >>> t.update({'key 1': {'const': 'value'},
+512        ...           'key 2': {'type': 'string'},
+513        ...           'key 3': {'type': 'object',
+514        ...                     'properties': {'key 1': {'type': 'number'},
+515        ...                                    'key 2': True}}})
+516        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+517        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
+518         'key 3': {'type': 'object',
+519                   'properties': {'key 1': {'type': 'number'},
+520                                  'key 2': True}}}
+521        >>> t.update({42: {'const': 'int key'}})
+522        Traceback (most recent call last):
+523          ...
+524        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+525        >>> t.update({'key': 'schema'})  # doctest: +NORMALIZE_WHITESPACE
+526        Traceback (most recent call last):
+527          ...
+528        TypeError: 'schema' is not a valid value in MessageTemplate
+529        (not a valid JSON schema).
+530        >>> t.update({'key': True})
+531
+532        This is also used in __init__:
+533        >>> t = MessageTemplate({'key 1': {'const': 'value'},
+534        ...                      'key 2': {'type': 'string'},
+535        ...                      'key 3': {'type': 'object',
+536        ...                                'properties': {
+537        ...                                    'key 1': {'type': 'number'},
+538        ...                                    'key 2': True}}})
+539        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+540        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
+541         'key 3': {'type': 'object',
+542                   'properties': {'key 1': {'type': 'number'},
+543                                  'key 2': True}}}
+544        >>> t = MessageTemplate({42: {'const': 'int key'}})
+545        Traceback (most recent call last):
+546          ...
+547        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+548        >>> t = MessageTemplate({'key': 'schema'})
+549        ... # doctest: +NORMALIZE_WHITESPACE
+550        Traceback (most recent call last):
+551          ...
+552        TypeError: 'schema' is not a valid value in MessageTemplate
+553        (not a valid JSON schema).
+554        >>> t = MessageTemplate({'key': True})
+555        """
+556        if args:
+557            if len(args) > 1:
+558                raise TypeError(f"update expected at most 1 argument, got {len(args)}")
+559            other = dict(args[0])
+560            for key in other:
+561                self[key] = other[key]
+562        for key in kwargs:
+563            self[key] = kwargs[key]
+
+ + +

Override update to use validity checks.

+ +
+
>>> t = MessageTemplate()
+>>> t.update({'key 1': {'const': 'value'},
+...           'key 2': {'type': 'string'},
+...           'key 3': {'type': 'object',
+...                     'properties': {'key 1': {'type': 'number'},
+...                                    'key 2': True}}})
+>>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+{'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
+ 'key 3': {'type': 'object',
+           'properties': {'key 1': {'type': 'number'},
+                          'key 2': True}}}
+>>> t.update({42: {'const': 'int key'}})
+Traceback (most recent call last):
+  ...
+TypeError: '42' is not a valid key in MessageTemplate (not a string).
+>>> t.update({'key': 'schema'})  # doctest: +NORMALIZE_WHITESPACE
+Traceback (most recent call last):
+  ...
+TypeError: 'schema' is not a valid value in MessageTemplate
+(not a valid JSON schema).
+>>> t.update({'key': True})
+
+
+ +

This is also used in __init__:

+ +
+
>>> t = MessageTemplate({'key 1': {'const': 'value'},
+...                      'key 2': {'type': 'string'},
+...                      'key 3': {'type': 'object',
+...                                'properties': {
+...                                    'key 1': {'type': 'number'},
+...                                    'key 2': True}}})
+>>> print(t)  # doctest: +NORMALIZE_WHITESPACE
+{'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
+ 'key 3': {'type': 'object',
+           'properties': {'key 1': {'type': 'number'},
+                          'key 2': True}}}
+>>> t = MessageTemplate({42: {'const': 'int key'}})
+Traceback (most recent call last):
+  ...
+TypeError: '42' is not a valid key in MessageTemplate (not a string).
+>>> t = MessageTemplate({'key': 'schema'})
+... # doctest: +NORMALIZE_WHITESPACE
+Traceback (most recent call last):
+  ...
+TypeError: 'schema' is not a valid value in MessageTemplate
+(not a valid JSON schema).
+>>> t = MessageTemplate({'key': True})
+
+
+
+ + +
+
+ +
+ + def + setdefault( self, key: str, value: bool | Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]] | None = None) -> bool | Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]]: + + + +
+ +
565    def setdefault(self, key: str, value: Optional[JSONSchema] = None) -> JSONSchema:
+566        """Override setdefault to use validity checks.
+567
+568        >>> t = MessageTemplate()
+569        >>> t.setdefault('key 1', {'const': 'value'})
+570        {'const': 'value'}
+571        >>> t.setdefault('key 2', {'type': 'string'})
+572        {'type': 'string'}
+573        >>> t.setdefault('key 3', {'type': 'object',
+574        ...                        'properties': {'key 1': {'type': 'number'},
+575        ...                                       'key 2': True}})
+576        ... # doctest: +NORMALIZE_WHITESPACE
+577        {'type': 'object',
+578                   'properties': {'key 1': {'type': 'number'},
+579                                  'key 2': True}}
+580        >>> t.setdefault(42, {'const': 'int key'})
+581        Traceback (most recent call last):
+582          ...
+583        TypeError: '42' is not a valid key in MessageTemplate (not a string).
+584        >>> t.setdefault('key', 'schema')  # doctest: +NORMALIZE_WHITESPACE
+585        Traceback (most recent call last):
+586          ...
+587        TypeError: 'schema' is not a valid value in MessageTemplate
+588        (not a valid JSON schema).
+589
+590        But __setitem__ is not called if the key is already present:
+591        >>> t.setdefault('key 1', 'schema')
+592        {'const': 'value'}
+593        """
+594        if key not in self:
+595            if value is not None:
+596                self[key] = value
+597            else:
+598                self[key] = True
+599        return self[key]
+
+ + +

Override setdefault to use validity checks.

+ +
+
>>> t = MessageTemplate()
+>>> t.setdefault('key 1', {'const': 'value'})
+{'const': 'value'}
+>>> t.setdefault('key 2', {'type': 'string'})
+{'type': 'string'}
+>>> t.setdefault('key 3', {'type': 'object',
+...                        'properties': {'key 1': {'type': 'number'},
+...                                       'key 2': True}})
+... # doctest: +NORMALIZE_WHITESPACE
+{'type': 'object',
+           'properties': {'key 1': {'type': 'number'},
+                          'key 2': True}}
+>>> t.setdefault(42, {'const': 'int key'})
+Traceback (most recent call last):
+  ...
+TypeError: '42' is not a valid key in MessageTemplate (not a string).
+>>> t.setdefault('key', 'schema')  # doctest: +NORMALIZE_WHITESPACE
+Traceback (most recent call last):
+  ...
+TypeError: 'schema' is not a valid value in MessageTemplate
+(not a valid JSON schema).
+
+
+ +

But __setitem__ is not called if the key is already present:

+ +
+
>>> t.setdefault('key 1', 'schema')
+{'const': 'value'}
+
+
+
+ + +
+
+ +
+ + def + check(self, message: Message) -> bool: + + + +
+ +
601    def check(self, message: Message) -> bool:
+602        """Check message against this template.
+603
+604        Constant values have to match exactly:
+605        >>> t = MessageTemplate({'key': {'const': 'value'}})
+606        >>> t.check(Message('Example Sender', {'key': 'value'}))
+607        True
+608        >>> t.check(Message('Example Sender', {'key': 'other value'}))
+609        False
+610
+611        But for integers, floats with the same value are also valid:
+612        >>> t = MessageTemplate({'key': {'const': 42}})
+613        >>> t.check(Message('Example Sender', {'key': 42}))
+614        True
+615        >>> t.check(Message('Example Sender', {'key': 42.0}))
+616        True
+617
+618        Type integer is valid for floats with zero fractional part, but
+619        not by floats with non-zero fractional part:
+620        >>> t = MessageTemplate({'key': {'type': 'integer'}})
+621        >>> t.check(Message('Example Sender', {'key': 42}))
+622        True
+623        >>> t.check(Message('Example Sender', {'key': 42.0}))
+624        True
+625        >>> t.check(Message('Example Sender', {'key': 42.42}))
+626        False
+627
+628        Type number is valid for arbitrary ints or floats:
+629        >>> t = MessageTemplate({'key': {'type': 'number'}})
+630        >>> t.check(Message('Example Sender', {'key': 42}))
+631        True
+632        >>> t.check(Message('Example Sender', {'key': 42.42}))
+633        True
+634
+635        All keys in template have to be present in message:
+636        >>> t = MessageTemplate({'key 1': {'const': 'value'},
+637        ...                      'key 2': {'type': 'string'},
+638        ...                      'key 3': {'type': 'object',
+639        ...                                'properties': {
+640        ...                                    'key 1': {'type': 'number'},
+641        ...                                    'key 2': True,
+642        ...                                    'key 3': False}}})
+643        >>> t.check(Message('Example Sender',
+644        ...                 {'key 1': 'value', 'key 2': 'some string'}))
+645        False
+646
+647        But for nested objects their properties do not necessarily have
+648        to be present:
+649        >>> t.check(Message('Example Sender',
+650        ...                 {'key 1': 'value', 'key 2': 'some string',
+651        ...                  'key 3': {'key 1': 42}}))
+652        True
+653
+654        Schema True matches everything (even None):
+655        >>> t.check(Message('Example Sender',
+656        ...                 {'key 1': 'value', 'key 2': 'some string',
+657        ...                  'key 3': {'key 2': None}}))
+658        True
+659
+660        Schema False matches nothing:
+661        >>> t.check(Message('Example Sender',
+662        ...                 {'key 1': 'value', 'key 2': 'some string',
+663        ...                  'key 3': {'key 3': True}}))
+664        False
+665
+666        Message is valid for the constant template created from it:
+667        >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
+668        ...                                'list': [None, True, 'string']})
+669        >>> t = MessageTemplate.from_message(m)
+670        >>> t.check(m)
+671        True
+672        """
+673        for key in self:
+674            if key not in message:
+675                return False
+676            else:
+677                schema_string = json.dumps(self[key])
+678                if not validate(schema_string, message[key]):
+679                    return False
+680        return True
+
+ + +

Check message against this template.

+ +

Constant values have to match exactly:

+ +
+
>>> t = MessageTemplate({'key': {'const': 'value'}})
+>>> t.check(Message('Example Sender', {'key': 'value'}))
+True
+>>> t.check(Message('Example Sender', {'key': 'other value'}))
+False
+
+
+ +

But for integers, floats with the same value are also valid:

+ +
+
>>> t = MessageTemplate({'key': {'const': 42}})
+>>> t.check(Message('Example Sender', {'key': 42}))
+True
+>>> t.check(Message('Example Sender', {'key': 42.0}))
+True
+
+
+ +

Type integer is valid for floats with zero fractional part, but +not by floats with non-zero fractional part:

+ +
+
>>> t = MessageTemplate({'key': {'type': 'integer'}})
+>>> t.check(Message('Example Sender', {'key': 42}))
+True
+>>> t.check(Message('Example Sender', {'key': 42.0}))
+True
+>>> t.check(Message('Example Sender', {'key': 42.42}))
+False
+
+
+ +

Type number is valid for arbitrary ints or floats:

+ +
+
>>> t = MessageTemplate({'key': {'type': 'number'}})
+>>> t.check(Message('Example Sender', {'key': 42}))
+True
+>>> t.check(Message('Example Sender', {'key': 42.42}))
+True
+
+
+ +

All keys in template have to be present in message:

+ +
+
>>> t = MessageTemplate({'key 1': {'const': 'value'},
+...                      'key 2': {'type': 'string'},
+...                      'key 3': {'type': 'object',
+...                                'properties': {
+...                                    'key 1': {'type': 'number'},
+...                                    'key 2': True,
+...                                    'key 3': False}}})
+>>> t.check(Message('Example Sender',
+...                 {'key 1': 'value', 'key 2': 'some string'}))
+False
+
+
+ +

But for nested objects their properties do not necessarily have +to be present:

+ +
+
>>> t.check(Message('Example Sender',
+...                 {'key 1': 'value', 'key 2': 'some string',
+...                  'key 3': {'key 1': 42}}))
+True
+
+
+ +

Schema True matches everything (even None):

+ +
+
>>> t.check(Message('Example Sender',
+...                 {'key 1': 'value', 'key 2': 'some string',
+...                  'key 3': {'key 2': None}}))
+True
+
+
+ +

Schema False matches nothing:

+ +
+
>>> t.check(Message('Example Sender',
+...                 {'key 1': 'value', 'key 2': 'some string',
+...                  'key 3': {'key 3': True}}))
+False
+
+
+ +

Message is valid for the constant template created from it:

+ +
+
>>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
+...                                'list': [None, True, 'string']})
+>>> t = MessageTemplate.from_message(m)
+>>> t.check(m)
+True
+
+
+
+ + +
+
+
+ +
+ + class + TemplateRegistry: + + + +
+ +
 683class TemplateRegistry:
+ 684    """Manage a collection of message templates with registered clients.
+ 685
+ 686    A new TemplateRegistry is created by:
+ 687    >>> r = TemplateRegistry()
+ 688
+ 689    Client names (strings) can be registered for message templates, which
+ 690    are mappings from keys to JSON schemas:
+ 691    >>> r.insert({'k1': {'const': 'v1'}}, 'C 1')
+ 692
+ 693    The check function checks if the templates registered for a client
+ 694    match a given message:
+ 695    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 696    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 697    ...     print(f"{m}: {r.check('C 1', m)}")
+ 698    {'k1': 'v1', 'k2': 'v1'}: True
+ 699    {'k1': 'v1', 'k2': 2}: True
+ 700    {'k1': 'v2', 'k2': 'v1'}: False
+ 701    {'k1': 'v2', 'k2': 2}: False
+ 702
+ 703    Clients can be registered for values validating against arbitrary JSON
+ 704    schemas, e.g. all values of a certain type:
+ 705    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}, 'C 2')
+ 706    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 707    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 708    ...     print(f"{m}: {r.check('C 2', m)}")
+ 709    {'k1': 'v1', 'k2': 'v1'}: False
+ 710    {'k1': 'v1', 'k2': 2}: False
+ 711    {'k1': 'v2', 'k2': 'v1'}: True
+ 712    {'k1': 'v2', 'k2': 2}: False
+ 713    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}, 'C 3')
+ 714    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 715    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 716    ...     print(f"{m}: {r.check('C 3', m)}")
+ 717    {'k1': 'v1', 'k2': 'v1'}: False
+ 718    {'k1': 'v1', 'k2': 2}: False
+ 719    {'k1': 'v2', 'k2': 'v1'}: False
+ 720    {'k1': 'v2', 'k2': 2}: True
+ 721
+ 722    The order of key-value pairs does not have to match the order in the
+ 723    messages and keys can be left out:
+ 724    >>> r.insert({'k2': {'const': 2}}, 'C 4')
+ 725    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 726    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 727    ...     print(f"{m}: {r.check('C 4', m)}")
+ 728    {'k1': 'v1', 'k2': 'v1'}: False
+ 729    {'k1': 'v1', 'k2': 2}: True
+ 730    {'k1': 'v2', 'k2': 'v1'}: False
+ 731    {'k1': 'v2', 'k2': 2}: True
+ 732
+ 733    A registration for an empty template matches all messages:
+ 734    >>> r.insert({}, 'C 5')
+ 735    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 736    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 737    ...     print(f"{m}: {r.check('C 5', m)}")
+ 738    {'k1': 'v1', 'k2': 'v1'}: True
+ 739    {'k1': 'v1', 'k2': 2}: True
+ 740    {'k1': 'v2', 'k2': 'v1'}: True
+ 741    {'k1': 'v2', 'k2': 2}: True
+ 742
+ 743    A client can be registered for multiple templates:
+ 744    >>> r.insert({'k1': {'const': 'v1'}}, 'C 6')
+ 745    >>> r.insert({'k2': {'const': 'v1'}}, 'C 6')
+ 746    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 747    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 748    ...     print(f"{m}: {r.check('C 6', m)}")
+ 749    {'k1': 'v1', 'k2': 'v1'}: True
+ 750    {'k1': 'v1', 'k2': 2}: True
+ 751    {'k1': 'v2', 'k2': 'v1'}: True
+ 752    {'k1': 'v2', 'k2': 2}: False
+ 753
+ 754    Clients can be deregistered again (the result is False if the registry
+ 755    is empty after the deletion):
+ 756    >>> r.insert({'k1': {'const': 'v1'}}, 'C 7')
+ 757    >>> r.delete('C 7')
+ 758    True
+ 759    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 760    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 761    ...     print(f"{m}: {r.check('C 7', m)}")
+ 762    {'k1': 'v1', 'k2': 'v1'}: False
+ 763    {'k1': 'v1', 'k2': 2}: False
+ 764    {'k1': 'v2', 'k2': 'v1'}: False
+ 765    {'k1': 'v2', 'k2': 2}: False
+ 766
+ 767    The get function returns all clients with registered templates matching
+ 768    a given message:
+ 769    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 770    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 771    ...     print(f"{m}: {r.get(m)}")
+ 772    {'k1': 'v1', 'k2': 'v1'}: ['C 5', 'C 1', 'C 6']
+ 773    {'k1': 'v1', 'k2': 2}: ['C 5', 'C 1', 'C 6', 'C 4']
+ 774    {'k1': 'v2', 'k2': 'v1'}: ['C 5', 'C 2', 'C 6']
+ 775    {'k1': 'v2', 'k2': 2}: ['C 5', 'C 3', 'C 4']
+ 776
+ 777    The get_templates function returns all templates for a given client:
+ 778    >>> for c in ['C 1', 'C 2', 'C 3', 'C 4', 'C 5', 'C 6']:
+ 779    ...     print(f"{c}: {r.get_templates(c)}")
+ 780    C 1: [{'k1': {'const': 'v1'}}]
+ 781    C 2: [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
+ 782    C 3: [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
+ 783    C 4: [{'k2': {'const': 2}}]
+ 784    C 5: [{}]
+ 785    C 6: [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
+ 786    """
+ 787
+ 788    def __init__(self) -> None:
+ 789        """Initialise an empty registry.
+ 790
+ 791        >>> r = TemplateRegistry()
+ 792        """
+ 793        self._clients: List[str] = []
+ 794        self._callbacks: Dict[str, List[MessageCallback]] = {}
+ 795        self._constants: Dict[str, Dict[str, TemplateRegistry]] = {}
+ 796        # First key is the message key, second key is the constant string
+ 797        self._schemas: Dict[str, Dict[str, TemplateRegistry]] = {}
+ 798        # First key is the message key, second key is the JSON schema string
+ 799        self._templates: Dict[str, List[MessageTemplate]] = {}
+ 800
+ 801    def insert(
+ 802        self,
+ 803        template: MessageTemplate,
+ 804        client: str,
+ 805        callback: Optional[MessageCallback] = None,
+ 806    ) -> None:
+ 807        """Register a client for a template.
+ 808
+ 809        >>> r = TemplateRegistry()
+ 810        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'integer'}}, 'C 1')
+ 811        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'string'}}, 'C 2')
+ 812        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v1'}}, 'C 3')
+ 813        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v2'}}, 'C 4')
+ 814        >>> r.insert({}, 'C 5')
+ 815        """
+ 816        if client not in self._templates:
+ 817            self._templates[client] = []
+ 818        self._templates[client].append(template)
+ 819        if not template:
+ 820            self._clients.append(client)
+ 821            if callback:
+ 822                if client not in self._callbacks:
+ 823                    self._callbacks[client] = []
+ 824                self._callbacks[client].append(callback)
+ 825        else:
+ 826            key, schema = next(iter(template.items()))
+ 827            reduced_template = MessageTemplate(
+ 828                {k: template[k] for k in template if k != key}
+ 829            )
+ 830            if (
+ 831                isinstance(schema, dict)
+ 832                and len(schema) == 1
+ 833                and "const" in schema
+ 834                and isinstance(schema["const"], str)
+ 835            ):
+ 836                value = schema["const"]
+ 837                if key not in self._constants:
+ 838                    self._constants[key] = {}
+ 839                if value not in self._constants[key]:
+ 840                    self._constants[key][value] = TemplateRegistry()
+ 841                self._constants[key][value].insert(reduced_template, client, callback)
+ 842            else:
+ 843                schema_string = json.dumps(schema)
+ 844                if key not in self._schemas:
+ 845                    self._schemas[key] = {}
+ 846                if schema_string not in self._schemas[key]:
+ 847                    self._schemas[key][schema_string] = TemplateRegistry()
+ 848                self._schemas[key][schema_string].insert(
+ 849                    reduced_template, client, callback
+ 850                )
+ 851
+ 852    def delete(self, client: str) -> bool:
+ 853        """Unregister a client from all templates.
+ 854
+ 855        >>> r = TemplateRegistry()
+ 856        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'integer'}}, 'C 1')
+ 857        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'string'}}, 'C 2')
+ 858        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v1'}}, 'C 3')
+ 859        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v2'}}, 'C 4')
+ 860        >>> r.insert({}, 'C 5')
+ 861        >>> r.delete('C 3')
+ 862        True
+ 863        >>> r.delete('C 4')
+ 864        True
+ 865        """
+ 866        if client in self._templates:
+ 867            del self._templates[client]
+ 868        self._clients = [c for c in self._clients if c != client]
+ 869        if client in self._callbacks:
+ 870            del self._callbacks[client]
+ 871        new_constants: Dict[str, Dict[str, TemplateRegistry]] = {}
+ 872        for key in self._constants:
+ 873            new_constants[key] = {}
+ 874            for value in self._constants[key]:
+ 875                if self._constants[key][value].delete(client):
+ 876                    new_constants[key][value] = self._constants[key][value]
+ 877            if not new_constants[key]:
+ 878                del new_constants[key]
+ 879        self._constants = new_constants
+ 880        new_schemas: Dict[str, Dict[str, TemplateRegistry]] = {}
+ 881        for key in self._schemas:
+ 882            new_schemas[key] = {}
+ 883            for schema in self._schemas[key]:
+ 884                if self._schemas[key][schema].delete(client):
+ 885                    new_schemas[key][schema] = self._schemas[key][schema]
+ 886            if not new_schemas[key]:
+ 887                del new_schemas[key]
+ 888        self._schemas = new_schemas
+ 889        if self._clients or self._callbacks or self._constants or self._schemas:
+ 890            return True
+ 891        return False
+ 892
+ 893    def check(self, client: str, message: Message) -> bool:
+ 894        """Get if a client has a registered template matching a message.
+ 895
+ 896        >>> r = TemplateRegistry()
+ 897        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+ 898        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 899        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 900        ...     print(f"{m}: {r.check('Client 1', m)}")
+ 901        {'k1': 'v1', 'k2': 'v1'}: True
+ 902        {'k1': 'v1', 'k2': 2}: True
+ 903        {'k1': 'v2', 'k2': 'v1'}: False
+ 904        {'k1': 'v2', 'k2': 2}: False
+ 905        >>> r.insert({'k2': {'type': 'integer'}}, 'Client 2')
+ 906        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 907        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 908        ...     print(f"{m}: {r.check('Client 2', m)}")
+ 909        {'k1': 'v1', 'k2': 'v1'}: False
+ 910        {'k1': 'v1', 'k2': 2}: True
+ 911        {'k1': 'v2', 'k2': 'v1'}: False
+ 912        {'k1': 'v2', 'k2': 2}: True
+ 913        """
+ 914        if client in self._clients:
+ 915            return True
+ 916        for key in self._constants:
+ 917            if (
+ 918                key in message
+ 919                and isinstance(message[key], str)
+ 920                and message[key] in self._constants[key]
+ 921            ):
+ 922                value = message[key]
+ 923                assert isinstance(value, str)
+ 924                child = self._constants[key][value]
+ 925                if child.check(client, message):
+ 926                    return True
+ 927        for key in self._schemas:
+ 928            if key in message:
+ 929                for schema_string in self._schemas[key]:
+ 930                    if validate(schema_string, message[key]):
+ 931                        child = self._schemas[key][schema_string]
+ 932                        if child.check(client, message):
+ 933                            return True
+ 934        return False
+ 935
+ 936    def get(self, message: Message) -> List[str]:
+ 937        """Get all clients registered for templates matching a message.
+ 938
+ 939        >>> r = TemplateRegistry()
+ 940        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+ 941        >>> r.insert({'k2': {'type': 'integer'}}, 'Client 2')
+ 942        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+ 943        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+ 944        ...     print(f"{m}: {r.get(m)}")
+ 945        {'k1': 'v1', 'k2': 'v1'}: ['Client 1']
+ 946        {'k1': 'v1', 'k2': 2}: ['Client 1', 'Client 2']
+ 947        {'k1': 'v2', 'k2': 'v1'}: []
+ 948        {'k1': 'v2', 'k2': 2}: ['Client 2']
+ 949        """
+ 950        result = []
+ 951        for client in self._clients:
+ 952            if client not in result:
+ 953                result.append(client)
+ 954        for key in self._constants:
+ 955            if (
+ 956                key in message
+ 957                and isinstance(message[key], str)
+ 958                and message[key] in self._constants[key]
+ 959            ):
+ 960                value = message[key]
+ 961                assert isinstance(value, str)
+ 962                child = self._constants[key][value]
+ 963                for client in child.get(message):
+ 964                    if client not in result:
+ 965                        result.append(client)
+ 966        for key in self._schemas:
+ 967            if key in message:
+ 968                for schema_string in self._schemas[key]:
+ 969                    if validate(schema_string, message[key]):
+ 970                        child = self._schemas[key][schema_string]
+ 971                        for client in child.get(message):
+ 972                            if client not in result:
+ 973                                result.append(client)
+ 974        return result
+ 975
+ 976    def get_callbacks(self, message: Message) -> List[MessageCallback]:
+ 977        """Get all callbacks registered for templates matching a message."""
+ 978        result = []
+ 979        for client in self._callbacks:
+ 980            for callback in self._callbacks[client]:
+ 981                if callback not in result:
+ 982                    result.append(callback)
+ 983        for key in self._constants:
+ 984            if (
+ 985                key in message
+ 986                and isinstance(message[key], str)
+ 987                and message[key] in self._constants[key]
+ 988            ):
+ 989                value = message[key]
+ 990                assert isinstance(value, str)
+ 991                child = self._constants[key][value]
+ 992                for callback in child.get_callbacks(message):
+ 993                    if callback not in result:
+ 994                        result.append(callback)
+ 995        for key in self._schemas:
+ 996            if key in message:
+ 997                for schema_string in self._schemas[key]:
+ 998                    if validate(schema_string, message[key]):
+ 999                        child = self._schemas[key][schema_string]
+1000                        for callback in child.get_callbacks(message):
+1001                            if callback not in result:
+1002                                result.append(callback)
+1003        return result
+1004
+1005    def get_templates(self, client: str) -> List[MessageTemplate]:
+1006        """Get all templates for a client.
+1007
+1008        >>> r = TemplateRegistry()
+1009        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+1010        >>> r.get_templates('Client 1')
+1011        [{'k1': {'const': 'v1'}}]
+1012        >>> r.insert({'k1': {'const': 'v2'},
+1013        ...           'k2': {'type': 'string'}}, 'Client 2')
+1014        >>> r.get_templates('Client 2')
+1015        [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
+1016        >>> r.insert({'k1': {'const': 'v2'},
+1017        ...           'k2': {'type': 'integer'}}, 'Client 3')
+1018        >>> r.get_templates('Client 3')
+1019        [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
+1020        >>> r.insert({'k2': {'const': 2}}, 'Client 4')
+1021        >>> r.get_templates('Client 4')
+1022        [{'k2': {'const': 2}}]
+1023        >>> r.insert({}, 'Client 5')
+1024        >>> r.get_templates('Client 5')
+1025        [{}]
+1026        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 6')
+1027        >>> r.insert({'k2': {'const': 'v1'}}, 'Client 6')
+1028        >>> r.get_templates('Client 6')
+1029        [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
+1030        """
+1031        if client in self._templates:
+1032            return self._templates[client]
+1033        return []
+
+ + +

Manage a collection of message templates with registered clients.

+ +

A new TemplateRegistry is created by:

+ +
+
>>> r = TemplateRegistry()
+
+
+ +

Client names (strings) can be registered for message templates, which +are mappings from keys to JSON schemas:

+ +
+
>>> r.insert({'k1': {'const': 'v1'}}, 'C 1')
+
+
+ +

The check function checks if the templates registered for a client +match a given message:

+ +
+
>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.check('C 1', m)}")
+{'k1': 'v1', 'k2': 'v1'}: True
+{'k1': 'v1', 'k2': 2}: True
+{'k1': 'v2', 'k2': 'v1'}: False
+{'k1': 'v2', 'k2': 2}: False
+
+
+ +

Clients can be registered for values validating against arbitrary JSON +schemas, e.g. all values of a certain type:

+ +
+
>>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}, 'C 2')
+>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.check('C 2', m)}")
+{'k1': 'v1', 'k2': 'v1'}: False
+{'k1': 'v1', 'k2': 2}: False
+{'k1': 'v2', 'k2': 'v1'}: True
+{'k1': 'v2', 'k2': 2}: False
+>>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}, 'C 3')
+>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.check('C 3', m)}")
+{'k1': 'v1', 'k2': 'v1'}: False
+{'k1': 'v1', 'k2': 2}: False
+{'k1': 'v2', 'k2': 'v1'}: False
+{'k1': 'v2', 'k2': 2}: True
+
+
+ +

The order of key-value pairs does not have to match the order in the +messages and keys can be left out:

+ +
+
>>> r.insert({'k2': {'const': 2}}, 'C 4')
+>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.check('C 4', m)}")
+{'k1': 'v1', 'k2': 'v1'}: False
+{'k1': 'v1', 'k2': 2}: True
+{'k1': 'v2', 'k2': 'v1'}: False
+{'k1': 'v2', 'k2': 2}: True
+
+
+ +

A registration for an empty template matches all messages:

+ +
+
>>> r.insert({}, 'C 5')
+>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.check('C 5', m)}")
+{'k1': 'v1', 'k2': 'v1'}: True
+{'k1': 'v1', 'k2': 2}: True
+{'k1': 'v2', 'k2': 'v1'}: True
+{'k1': 'v2', 'k2': 2}: True
+
+
+ +

A client can be registered for multiple templates:

+ +
+
>>> r.insert({'k1': {'const': 'v1'}}, 'C 6')
+>>> r.insert({'k2': {'const': 'v1'}}, 'C 6')
+>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.check('C 6', m)}")
+{'k1': 'v1', 'k2': 'v1'}: True
+{'k1': 'v1', 'k2': 2}: True
+{'k1': 'v2', 'k2': 'v1'}: True
+{'k1': 'v2', 'k2': 2}: False
+
+
+ +

Clients can be deregistered again (the result is False if the registry +is empty after the deletion):

+ +
+
>>> r.insert({'k1': {'const': 'v1'}}, 'C 7')
+>>> r.delete('C 7')
+True
+>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.check('C 7', m)}")
+{'k1': 'v1', 'k2': 'v1'}: False
+{'k1': 'v1', 'k2': 2}: False
+{'k1': 'v2', 'k2': 'v1'}: False
+{'k1': 'v2', 'k2': 2}: False
+
+
+ +

The get function returns all clients with registered templates matching +a given message:

+ +
+
>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.get(m)}")
+{'k1': 'v1', 'k2': 'v1'}: ['C 5', 'C 1', 'C 6']
+{'k1': 'v1', 'k2': 2}: ['C 5', 'C 1', 'C 6', 'C 4']
+{'k1': 'v2', 'k2': 'v1'}: ['C 5', 'C 2', 'C 6']
+{'k1': 'v2', 'k2': 2}: ['C 5', 'C 3', 'C 4']
+
+
+ +

The get_templates function returns all templates for a given client:

+ +
+
>>> for c in ['C 1', 'C 2', 'C 3', 'C 4', 'C 5', 'C 6']:
+...     print(f"{c}: {r.get_templates(c)}")
+C 1: [{'k1': {'const': 'v1'}}]
+C 2: [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
+C 3: [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
+C 4: [{'k2': {'const': 2}}]
+C 5: [{}]
+C 6: [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
+
+
+
+ + +
+ +
+ + TemplateRegistry() + + + +
+ +
788    def __init__(self) -> None:
+789        """Initialise an empty registry.
+790
+791        >>> r = TemplateRegistry()
+792        """
+793        self._clients: List[str] = []
+794        self._callbacks: Dict[str, List[MessageCallback]] = {}
+795        self._constants: Dict[str, Dict[str, TemplateRegistry]] = {}
+796        # First key is the message key, second key is the constant string
+797        self._schemas: Dict[str, Dict[str, TemplateRegistry]] = {}
+798        # First key is the message key, second key is the JSON schema string
+799        self._templates: Dict[str, List[MessageTemplate]] = {}
+
+ + +

Initialise an empty registry.

+ +
+
>>> r = TemplateRegistry()
+
+
+
+ + +
+
+ +
+ + def + insert( self, template: MessageTemplate, client: str, callback: Callable[[Message], Coroutine[Any, Any, NoneType]] | None = None) -> None: + + + +
+ +
801    def insert(
+802        self,
+803        template: MessageTemplate,
+804        client: str,
+805        callback: Optional[MessageCallback] = None,
+806    ) -> None:
+807        """Register a client for a template.
+808
+809        >>> r = TemplateRegistry()
+810        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'integer'}}, 'C 1')
+811        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'string'}}, 'C 2')
+812        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v1'}}, 'C 3')
+813        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v2'}}, 'C 4')
+814        >>> r.insert({}, 'C 5')
+815        """
+816        if client not in self._templates:
+817            self._templates[client] = []
+818        self._templates[client].append(template)
+819        if not template:
+820            self._clients.append(client)
+821            if callback:
+822                if client not in self._callbacks:
+823                    self._callbacks[client] = []
+824                self._callbacks[client].append(callback)
+825        else:
+826            key, schema = next(iter(template.items()))
+827            reduced_template = MessageTemplate(
+828                {k: template[k] for k in template if k != key}
+829            )
+830            if (
+831                isinstance(schema, dict)
+832                and len(schema) == 1
+833                and "const" in schema
+834                and isinstance(schema["const"], str)
+835            ):
+836                value = schema["const"]
+837                if key not in self._constants:
+838                    self._constants[key] = {}
+839                if value not in self._constants[key]:
+840                    self._constants[key][value] = TemplateRegistry()
+841                self._constants[key][value].insert(reduced_template, client, callback)
+842            else:
+843                schema_string = json.dumps(schema)
+844                if key not in self._schemas:
+845                    self._schemas[key] = {}
+846                if schema_string not in self._schemas[key]:
+847                    self._schemas[key][schema_string] = TemplateRegistry()
+848                self._schemas[key][schema_string].insert(
+849                    reduced_template, client, callback
+850                )
+
+ + +

Register a client for a template.

+ +
+
>>> r = TemplateRegistry()
+>>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'integer'}}, 'C 1')
+>>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'string'}}, 'C 2')
+>>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v1'}}, 'C 3')
+>>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v2'}}, 'C 4')
+>>> r.insert({}, 'C 5')
+
+
+
+ + +
+
+ +
+ + def + delete(self, client: str) -> bool: + + + +
+ +
852    def delete(self, client: str) -> bool:
+853        """Unregister a client from all templates.
+854
+855        >>> r = TemplateRegistry()
+856        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'integer'}}, 'C 1')
+857        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'string'}}, 'C 2')
+858        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v1'}}, 'C 3')
+859        >>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v2'}}, 'C 4')
+860        >>> r.insert({}, 'C 5')
+861        >>> r.delete('C 3')
+862        True
+863        >>> r.delete('C 4')
+864        True
+865        """
+866        if client in self._templates:
+867            del self._templates[client]
+868        self._clients = [c for c in self._clients if c != client]
+869        if client in self._callbacks:
+870            del self._callbacks[client]
+871        new_constants: Dict[str, Dict[str, TemplateRegistry]] = {}
+872        for key in self._constants:
+873            new_constants[key] = {}
+874            for value in self._constants[key]:
+875                if self._constants[key][value].delete(client):
+876                    new_constants[key][value] = self._constants[key][value]
+877            if not new_constants[key]:
+878                del new_constants[key]
+879        self._constants = new_constants
+880        new_schemas: Dict[str, Dict[str, TemplateRegistry]] = {}
+881        for key in self._schemas:
+882            new_schemas[key] = {}
+883            for schema in self._schemas[key]:
+884                if self._schemas[key][schema].delete(client):
+885                    new_schemas[key][schema] = self._schemas[key][schema]
+886            if not new_schemas[key]:
+887                del new_schemas[key]
+888        self._schemas = new_schemas
+889        if self._clients or self._callbacks or self._constants or self._schemas:
+890            return True
+891        return False
+
+ + +

Unregister a client from all templates.

+ +
+
>>> r = TemplateRegistry()
+>>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'integer'}}, 'C 1')
+>>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'string'}}, 'C 2')
+>>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v1'}}, 'C 3')
+>>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v2'}}, 'C 4')
+>>> r.insert({}, 'C 5')
+>>> r.delete('C 3')
+True
+>>> r.delete('C 4')
+True
+
+
+
+ + +
+
+ +
+ + def + check(self, client: str, message: Message) -> bool: + + + +
+ +
893    def check(self, client: str, message: Message) -> bool:
+894        """Get if a client has a registered template matching a message.
+895
+896        >>> r = TemplateRegistry()
+897        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+898        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+899        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+900        ...     print(f"{m}: {r.check('Client 1', m)}")
+901        {'k1': 'v1', 'k2': 'v1'}: True
+902        {'k1': 'v1', 'k2': 2}: True
+903        {'k1': 'v2', 'k2': 'v1'}: False
+904        {'k1': 'v2', 'k2': 2}: False
+905        >>> r.insert({'k2': {'type': 'integer'}}, 'Client 2')
+906        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+907        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+908        ...     print(f"{m}: {r.check('Client 2', m)}")
+909        {'k1': 'v1', 'k2': 'v1'}: False
+910        {'k1': 'v1', 'k2': 2}: True
+911        {'k1': 'v2', 'k2': 'v1'}: False
+912        {'k1': 'v2', 'k2': 2}: True
+913        """
+914        if client in self._clients:
+915            return True
+916        for key in self._constants:
+917            if (
+918                key in message
+919                and isinstance(message[key], str)
+920                and message[key] in self._constants[key]
+921            ):
+922                value = message[key]
+923                assert isinstance(value, str)
+924                child = self._constants[key][value]
+925                if child.check(client, message):
+926                    return True
+927        for key in self._schemas:
+928            if key in message:
+929                for schema_string in self._schemas[key]:
+930                    if validate(schema_string, message[key]):
+931                        child = self._schemas[key][schema_string]
+932                        if child.check(client, message):
+933                            return True
+934        return False
+
+ + +

Get if a client has a registered template matching a message.

+ +
+
>>> r = TemplateRegistry()
+>>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.check('Client 1', m)}")
+{'k1': 'v1', 'k2': 'v1'}: True
+{'k1': 'v1', 'k2': 2}: True
+{'k1': 'v2', 'k2': 'v1'}: False
+{'k1': 'v2', 'k2': 2}: False
+>>> r.insert({'k2': {'type': 'integer'}}, 'Client 2')
+>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.check('Client 2', m)}")
+{'k1': 'v1', 'k2': 'v1'}: False
+{'k1': 'v1', 'k2': 2}: True
+{'k1': 'v2', 'k2': 'v1'}: False
+{'k1': 'v2', 'k2': 2}: True
+
+
+
+ + +
+
+ +
+ + def + get(self, message: Message) -> List[str]: + + + +
+ +
936    def get(self, message: Message) -> List[str]:
+937        """Get all clients registered for templates matching a message.
+938
+939        >>> r = TemplateRegistry()
+940        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+941        >>> r.insert({'k2': {'type': 'integer'}}, 'Client 2')
+942        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+943        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+944        ...     print(f"{m}: {r.get(m)}")
+945        {'k1': 'v1', 'k2': 'v1'}: ['Client 1']
+946        {'k1': 'v1', 'k2': 2}: ['Client 1', 'Client 2']
+947        {'k1': 'v2', 'k2': 'v1'}: []
+948        {'k1': 'v2', 'k2': 2}: ['Client 2']
+949        """
+950        result = []
+951        for client in self._clients:
+952            if client not in result:
+953                result.append(client)
+954        for key in self._constants:
+955            if (
+956                key in message
+957                and isinstance(message[key], str)
+958                and message[key] in self._constants[key]
+959            ):
+960                value = message[key]
+961                assert isinstance(value, str)
+962                child = self._constants[key][value]
+963                for client in child.get(message):
+964                    if client not in result:
+965                        result.append(client)
+966        for key in self._schemas:
+967            if key in message:
+968                for schema_string in self._schemas[key]:
+969                    if validate(schema_string, message[key]):
+970                        child = self._schemas[key][schema_string]
+971                        for client in child.get(message):
+972                            if client not in result:
+973                                result.append(client)
+974        return result
+
+ + +

Get all clients registered for templates matching a message.

+ +
+
>>> r = TemplateRegistry()
+>>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+>>> r.insert({'k2': {'type': 'integer'}}, 'Client 2')
+>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
+...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
+...     print(f"{m}: {r.get(m)}")
+{'k1': 'v1', 'k2': 'v1'}: ['Client 1']
+{'k1': 'v1', 'k2': 2}: ['Client 1', 'Client 2']
+{'k1': 'v2', 'k2': 'v1'}: []
+{'k1': 'v2', 'k2': 2}: ['Client 2']
+
+
+
+ + +
+
+ +
+ + def + get_callbacks( self, message: Message) -> List[Callable[[Message], Coroutine[Any, Any, NoneType]]]: + + + +
+ +
 976    def get_callbacks(self, message: Message) -> List[MessageCallback]:
+ 977        """Get all callbacks registered for templates matching a message."""
+ 978        result = []
+ 979        for client in self._callbacks:
+ 980            for callback in self._callbacks[client]:
+ 981                if callback not in result:
+ 982                    result.append(callback)
+ 983        for key in self._constants:
+ 984            if (
+ 985                key in message
+ 986                and isinstance(message[key], str)
+ 987                and message[key] in self._constants[key]
+ 988            ):
+ 989                value = message[key]
+ 990                assert isinstance(value, str)
+ 991                child = self._constants[key][value]
+ 992                for callback in child.get_callbacks(message):
+ 993                    if callback not in result:
+ 994                        result.append(callback)
+ 995        for key in self._schemas:
+ 996            if key in message:
+ 997                for schema_string in self._schemas[key]:
+ 998                    if validate(schema_string, message[key]):
+ 999                        child = self._schemas[key][schema_string]
+1000                        for callback in child.get_callbacks(message):
+1001                            if callback not in result:
+1002                                result.append(callback)
+1003        return result
+
+ + +

Get all callbacks registered for templates matching a message.

+
+ + +
+
+ +
+ + def + get_templates(self, client: str) -> List[MessageTemplate]: + + + +
+ +
1005    def get_templates(self, client: str) -> List[MessageTemplate]:
+1006        """Get all templates for a client.
+1007
+1008        >>> r = TemplateRegistry()
+1009        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+1010        >>> r.get_templates('Client 1')
+1011        [{'k1': {'const': 'v1'}}]
+1012        >>> r.insert({'k1': {'const': 'v2'},
+1013        ...           'k2': {'type': 'string'}}, 'Client 2')
+1014        >>> r.get_templates('Client 2')
+1015        [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
+1016        >>> r.insert({'k1': {'const': 'v2'},
+1017        ...           'k2': {'type': 'integer'}}, 'Client 3')
+1018        >>> r.get_templates('Client 3')
+1019        [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
+1020        >>> r.insert({'k2': {'const': 2}}, 'Client 4')
+1021        >>> r.get_templates('Client 4')
+1022        [{'k2': {'const': 2}}]
+1023        >>> r.insert({}, 'Client 5')
+1024        >>> r.get_templates('Client 5')
+1025        [{}]
+1026        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 6')
+1027        >>> r.insert({'k2': {'const': 'v1'}}, 'Client 6')
+1028        >>> r.get_templates('Client 6')
+1029        [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
+1030        """
+1031        if client in self._templates:
+1032            return self._templates[client]
+1033        return []
+
+ + +

Get all templates for a client.

+ +
+
>>> r = TemplateRegistry()
+>>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
+>>> r.get_templates('Client 1')
+[{'k1': {'const': 'v1'}}]
+>>> r.insert({'k1': {'const': 'v2'},
+...           'k2': {'type': 'string'}}, 'Client 2')
+>>> r.get_templates('Client 2')
+[{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
+>>> r.insert({'k1': {'const': 'v2'},
+...           'k2': {'type': 'integer'}}, 'Client 3')
+>>> r.get_templates('Client 3')
+[{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
+>>> r.insert({'k2': {'const': 2}}, 'Client 4')
+>>> r.get_templates('Client 4')
+[{'k2': {'const': 2}}]
+>>> r.insert({}, 'Client 5')
+>>> r.get_templates('Client 5')
+[{}]
+>>> r.insert({'k1': {'const': 'v1'}}, 'Client 6')
+>>> r.insert({'k2': {'const': 'v1'}}, 'Client 6')
+>>> r.get_templates('Client 6')
+[{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
+
+
+
+ + +
+
+
+ +
+ + class + BusException(builtins.Exception): + + + +
+ +
1036class BusException(Exception):
+1037    """Raise for errors in using message bus."""
+
+ + +

Raise for errors in using message bus.

+
+ + +
+
+ +
+ + class + MessageBus: + + + +
+ +
1040class MessageBus:
+1041    """Provide an asynchronous message bus.
+1042
+1043    The bus executes asynchronous callbacks for all messages to be received
+1044    by a client. We use a simple callback printing the message in all
+1045    examples:
+1046    >>> def callback_for_receiver(receiver):
+1047    ...     print(f"Creating callback for {receiver}.")
+1048    ...     async def callback(message):
+1049    ...         print(f"{receiver}: {message}")
+1050    ...     return callback
+1051
+1052    Clients can be registered at the bus with a name, lists of message
+1053    templates they want to use for sending and receiving and a callback
+1054    function for receiving. An empty list of templates means that the
+1055    client does not want to send or receive any messages, respectively.
+1056    A list with an empty template means that it wants to send arbitrary
+1057    or receive all messages, respectively:
+1058    >>> async def setup(bus):
+1059    ...     print("Setting up.")
+1060    ...     bus.register('Logger', 'Test Plugin',
+1061    ...                  [],
+1062    ...                  [([MessageTemplate({})],
+1063    ...                    callback_for_receiver('Logger'))])
+1064    ...     bus.register('Client 1', 'Test Plugin',
+1065    ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1066    ...                  [([MessageTemplate({'target':
+1067    ...                                      {'const': 'Client 1'}})],
+1068    ...                    callback_for_receiver('Client 1'))])
+1069    ...     bus.register('Client 2', 'Test Plugin',
+1070    ...                  [MessageTemplate({})],
+1071    ...                  [([MessageTemplate({'target':
+1072    ...                                      {'const': 'Client 2'}})],
+1073    ...                    callback_for_receiver('Client 2'))])
+1074
+1075    The bus itself is addressed by the empty string. It sends messages for
+1076    each registration and deregestration of a client with a key 'event' and
+1077    a value of 'registered' or 'unregistered', a key 'client' with the
+1078    client's name as value and for registrations also keys 'sends' and
+1079    'receives' with all templates registered for the client for sending and
+1080    receiving.
+1081
+1082    Clients can send to the bus with the send function. Each message has to
+1083    declare a sender. The send templates of that sender are checked for a
+1084    template matching the message. We cannot prevent arbitrary code from
+1085    impersonating any sender, but this should only be done in debugging or
+1086    management situations.
+1087
+1088    Messages that are intended for a specific client by convention have a
+1089    key 'target' with the target client's name as value. Such messages are
+1090    often commands to the client to do something, which is by convention
+1091    indicated by a key 'command' with a value that indicates what should be
+1092    done.
+1093
+1094    The bus, for example, reacts to a message with 'target': '' and
+1095    'command': 'get clients' by sending one message for each currently
+1096    registered with complete information about its registered send and
+1097    receive templates.
+1098
+1099    >>> async def send(bus):
+1100    ...     print("Sending messages.")
+1101    ...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})
+1102    ...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
+1103    ...     await bus.send({'sender': '', 'target': '',
+1104    ...                     'command': 'get clients'})
+1105
+1106    The run function executes the message bus forever. If we want to stop
+1107    it, we have to explicitly cancel the task:
+1108    >>> async def main():
+1109    ...     bus = MessageBus()
+1110    ...     await setup(bus)
+1111    ...     bus_task = asyncio.create_task(bus.run())
+1112    ...     await send(bus)
+1113    ...     await asyncio.sleep(0)
+1114    ...     bus_task.cancel()
+1115    ...     try:
+1116    ...         await bus_task
+1117    ...     except asyncio.exceptions.CancelledError:
+1118    ...         pass
+1119    >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+1120    Setting up.
+1121    Creating callback for Logger.
+1122    Creating callback for Client 1.
+1123    Creating callback for Client 2.
+1124    Sending messages.
+1125    Logger: {'sender': '', 'event': 'registered',
+1126             'client': 'Logger', 'plugin': 'Test Plugin',
+1127             'sends': [], 'receives': [{}]}
+1128    Logger: {'sender': '', 'event': 'registered',
+1129             'client': 'Client 1', 'plugin': 'Test Plugin',
+1130             'sends': [{'k1': {'type': 'string'}}],
+1131             'receives': [{'target': {'const': 'Client 1'}}]}
+1132    Logger: {'sender': '', 'event': 'registered',
+1133             'client': 'Client 2', 'plugin': 'Test Plugin',
+1134             'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
+1135    Logger: {'sender': 'Client 1', 'k1': 'Test'}
+1136    Logger: {'sender': 'Client 2', 'target': 'Client 1'}
+1137    Client 1: {'sender': 'Client 2', 'target': 'Client 1'}
+1138    Logger: {'sender': '', 'target': '', 'command': 'get clients'}
+1139    Logger: {'sender': '', 'client': 'Logger', 'plugin': 'Test Plugin',
+1140             'sends': [], 'receives': [{}]}
+1141    Logger: {'sender': '', 'client': 'Client 1', 'plugin': 'Test Plugin',
+1142             'sends': [{'k1': {'type': 'string'}}],
+1143             'receives': [{'target': {'const': 'Client 1'}}]}
+1144    Logger: {'sender': '', 'client': 'Client 2', 'plugin': 'Test Plugin',
+1145             'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
+1146    """
+1147
+1148    def __init__(self) -> None:
+1149        """Initialise a new bus without clients.
+1150
+1151        >>> async def main():
+1152        ...     bus = MessageBus()
+1153        >>> asyncio.run(main())
+1154        """
+1155        self._queue: asyncio.Queue = asyncio.Queue()
+1156        self._plugins: Dict[str, str] = {}
+1157        self._send_reg: TemplateRegistry = TemplateRegistry()
+1158        self._recv_reg: TemplateRegistry = TemplateRegistry()
+1159
+1160    def register(
+1161        self,
+1162        client: str,
+1163        plugin: str,
+1164        sends: Iterable[MessageTemplate],
+1165        receives: Iterable[Tuple[Iterable[MessageTemplate], MessageCallback]],
+1166    ) -> None:
+1167        """Register a client at the message bus.
+1168
+1169        >>> async def callback(message):
+1170        ...     print(message)
+1171        >>> async def main():
+1172        ...     bus = MessageBus()
+1173        ...     bus.register('Logger', 'Test Plugin',
+1174        ...                  [],    # send nothing
+1175        ...                  [([MessageTemplate({})],  # receive everything
+1176        ...                    callback)])
+1177        ...     bus.register('Client 1', 'Test Plugin',
+1178        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1179        ...                      # send with key 'k1' and string value
+1180        ...                  [([MessageTemplate({'target':
+1181        ...                                      {'const': 'Client 1'}})],
+1182        ...                      # receive for this client
+1183        ...                    callback)])
+1184        ...     bus.register('Client 2', 'Test Plugin',
+1185        ...                  [MessageTemplate({})],  # send arbitrary
+1186        ...                  [([MessageTemplate({'target':
+1187        ...                                      {'const': 'Client 2'}})],
+1188        ...                      # receive for this client
+1189        ...                    callback)])
+1190        >>> asyncio.run(main())
+1191        """
+1192        if not client:
+1193            raise BusException("Client name is not allowed to be empty.")
+1194        if client in self._plugins:
+1195            raise BusException(f"Client '{client}' already registered at message bus.")
+1196        event = Message("")
+1197        event["event"] = "registered"
+1198        event["client"] = client
+1199        self._plugins[client] = plugin
+1200        event["plugin"] = plugin
+1201        for template in sends:
+1202            self._send_reg.insert(template, client)
+1203        event["sends"] = self._send_reg.get_templates(client)
+1204        for templates, callback in receives:
+1205            for template in templates:
+1206                self._recv_reg.insert(template, client, callback)
+1207        event["receives"] = self._recv_reg.get_templates(client)
+1208        self._queue.put_nowait(event)
+1209
+1210    def unregister(self, client: str) -> None:
+1211        """Unregister a client from the message bus.
+1212
+1213        >>> async def callback(message):
+1214        ...     print(message)
+1215        >>> async def main():
+1216        ...     bus = MessageBus()
+1217        ...     bus.register('Client 1', 'Test Plugin',
+1218        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1219        ...                  [([MessageTemplate({'target':
+1220        ...                                      {'const': 'Client 1'}})],
+1221        ...                    callback)])
+1222        ...     bus.unregister('Client 1')
+1223        >>> asyncio.run(main())
+1224        """
+1225        if client not in self._plugins:
+1226            return
+1227        event = Message("")
+1228        event["event"] = "unregistered"
+1229        event["client"] = client
+1230        del self._plugins[client]
+1231        self._send_reg.delete(client)
+1232        self._recv_reg.delete(client)
+1233        self._queue.put_nowait(event)
+1234
+1235    async def run(self) -> None:
+1236        """Run the message bus forever.
+1237
+1238        >>> async def main():
+1239        ...     bus = MessageBus()
+1240        ...     bus_task = asyncio.create_task(bus.run())
+1241        ...     bus_task.cancel()
+1242        ...     try:
+1243        ...         await bus_task
+1244        ...     except asyncio.exceptions.CancelledError:
+1245        ...         pass
+1246        >>> asyncio.run(main())
+1247        """
+1248        background_tasks = set()
+1249        while True:
+1250            message = await self._queue.get()
+1251            if "target" in message and message["target"] == "" and "command" in message:
+1252                if message["command"] == "get clients":
+1253                    for client in self._plugins:
+1254                        answer = Message("")
+1255                        answer["client"] = client
+1256                        answer["plugin"] = self._plugins[client]
+1257                        answer["sends"] = self._send_reg.get_templates(client)
+1258                        answer["receives"] = self._recv_reg.get_templates(client)
+1259                        await self._queue.put(answer)
+1260                elif message["command"] == "push conf":
+1261                    conf = {}
+1262                    try:
+1263                        with open(sys.argv[1]) as conf_file:
+1264                            conf = json.load(conf_file)
+1265                    except (
+1266                        IndexError,
+1267                        FileNotFoundError,
+1268                        json.decoder.JSONDecodeError,
+1269                    ):
+1270                        pass
+1271                    if conf == message["conf"]:
+1272                        await self._queue.put(Message("", {"event": "conf unchanged"}))
+1273                    else:
+1274                        await self._queue.put(Message("", {"event": "conf changed"}))
+1275                        with open(sys.argv[1], "w") as conf_file:
+1276                            json.dump(message["conf"], conf_file)
+1277            for callback in self._recv_reg.get_callbacks(message):
+1278                task = asyncio.create_task(callback(message))
+1279                background_tasks.add(task)
+1280                task.add_done_callback(background_tasks.discard)
+1281            self._queue.task_done()
+1282
+1283    async def send(self, message: Message) -> None:
+1284        """Send a message to the message bus.
+1285
+1286        >>> async def callback(message):
+1287        ...     print(f"Got: {message}")
+1288        >>> async def main():
+1289        ...     bus = MessageBus()
+1290        ...     bus.register('Client 1', 'Test Plugin',
+1291        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1292        ...                  [([MessageTemplate({'target':
+1293        ...                                      {'const': 'Client 1'}})],
+1294        ...                    callback)])
+1295        ...     bus.register('Client 2', 'Test Plugin',
+1296        ...                  [MessageTemplate({})],
+1297        ...                  [([MessageTemplate({'target':
+1298        ...                                      {'const': 'Client 2'}})],
+1299        ...                    callback)])
+1300        ...     bus_task = asyncio.create_task(bus.run())
+1301        ...     await bus.send({'sender': 'Client 1', 'target': 'Client 2',
+1302        ...                     'k1': 'Test'})
+1303        ...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
+1304        ...     try:
+1305        ...         await bus.send({'sender': 'Client 1', 'target': 'Client 2',
+1306        ...                         'k1': 42})
+1307        ...     except BusException as e:
+1308        ...         print(e)
+1309        ...     await asyncio.sleep(0)
+1310        ...     bus_task.cancel()
+1311        ...     try:
+1312        ...         await bus_task
+1313        ...     except asyncio.exceptions.CancelledError:
+1314        ...         pass
+1315        >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+1316        Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
+1317        not allowed for sender 'Client 1'.
+1318        Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
+1319        Got: {'sender': 'Client 2', 'target': 'Client 1'}
+1320        """
+1321        assert isinstance(message["sender"], str)
+1322        sender = message["sender"]
+1323        if sender:
+1324            if not self._send_reg.check(sender, message):
+1325                raise BusException(
+1326                    f"Message '{message}' not allowed for sender '{sender}'."
+1327                )
+1328        await self._queue.put(message)
+1329
+1330    def send_nowait(self, message: Message) -> None:
+1331        """Send a message to the message bus without blocking.
+1332
+1333        >>> async def callback(message):
+1334        ...     print(f"Got: {message}")
+1335        >>> async def main():
+1336        ...     bus = MessageBus()
+1337        ...     bus.register('Client 1', 'Test Plugin',
+1338        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1339        ...                  [([MessageTemplate({'target':
+1340        ...                                      {'const': 'Client 1'}})],
+1341        ...                    callback)])
+1342        ...     bus.register('Client 2', 'Test Plugin',
+1343        ...                  [MessageTemplate({})],
+1344        ...                  [([MessageTemplate({'target':
+1345        ...                                      {'const': 'Client 2'}})],
+1346        ...                    callback)])
+1347        ...     bus_task = asyncio.create_task(bus.run())
+1348        ...     bus.send_nowait({'sender': 'Client 1', 'target': 'Client 2',
+1349        ...                      'k1': 'Test'})
+1350        ...     bus.send_nowait({'sender': 'Client 2', 'target': 'Client 1'})
+1351        ...     try:
+1352        ...         bus.send_nowait({'sender': 'Client 1',
+1353        ...                          'target': 'Client 2', 'k1': 42})
+1354        ...     except BusException as e:
+1355        ...         print(e)
+1356        ...     await asyncio.sleep(0)
+1357        ...     bus_task.cancel()
+1358        ...     try:
+1359        ...         await bus_task
+1360        ...     except asyncio.exceptions.CancelledError:
+1361        ...         pass
+1362        >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+1363        Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
+1364        not allowed for sender 'Client 1'.
+1365        Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
+1366        Got: {'sender': 'Client 2', 'target': 'Client 1'}
+1367        """
+1368        assert isinstance(message["sender"], str)
+1369        sender = message["sender"]
+1370        if sender:
+1371            if not self._send_reg.check(sender, message):
+1372                raise BusException(
+1373                    f"Message '{message}' not allowed for sender '{sender}'."
+1374                )
+1375        self._queue.put_nowait(message)
+
+ + +

Provide an asynchronous message bus.

+ +

The bus executes asynchronous callbacks for all messages to be received +by a client. We use a simple callback printing the message in all +examples:

+ +
+
>>> def callback_for_receiver(receiver):
+...     print(f"Creating callback for {receiver}.")
+...     async def callback(message):
+...         print(f"{receiver}: {message}")
+...     return callback
+
+
+ +

Clients can be registered at the bus with a name, lists of message +templates they want to use for sending and receiving and a callback +function for receiving. An empty list of templates means that the +client does not want to send or receive any messages, respectively. +A list with an empty template means that it wants to send arbitrary +or receive all messages, respectively:

+ +
+
>>> async def setup(bus):
+...     print("Setting up.")
+...     bus.register('Logger', 'Test Plugin',
+...                  [],
+...                  [([MessageTemplate({})],
+...                    callback_for_receiver('Logger'))])
+...     bus.register('Client 1', 'Test Plugin',
+...                  [MessageTemplate({'k1': {'type': 'string'}})],
+...                  [([MessageTemplate({'target':
+...                                      {'const': 'Client 1'}})],
+...                    callback_for_receiver('Client 1'))])
+...     bus.register('Client 2', 'Test Plugin',
+...                  [MessageTemplate({})],
+...                  [([MessageTemplate({'target':
+...                                      {'const': 'Client 2'}})],
+...                    callback_for_receiver('Client 2'))])
+
+
+ +

The bus itself is addressed by the empty string. It sends messages for +each registration and deregestration of a client with a key 'event' and +a value of 'registered' or 'unregistered', a key 'client' with the +client's name as value and for registrations also keys 'sends' and +'receives' with all templates registered for the client for sending and +receiving.

+ +

Clients can send to the bus with the send function. Each message has to +declare a sender. The send templates of that sender are checked for a +template matching the message. We cannot prevent arbitrary code from +impersonating any sender, but this should only be done in debugging or +management situations.

+ +

Messages that are intended for a specific client by convention have a +key 'target' with the target client's name as value. Such messages are +often commands to the client to do something, which is by convention +indicated by a key 'command' with a value that indicates what should be +done.

+ +

The bus, for example, reacts to a message with 'target': '' and +'command': 'get clients' by sending one message for each currently +registered with complete information about its registered send and +receive templates.

+ +
+
>>> async def send(bus):
+...     print("Sending messages.")
+...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})
+...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
+...     await bus.send({'sender': '', 'target': '',
+...                     'command': 'get clients'})
+
+
+ +

The run function executes the message bus forever. If we want to stop +it, we have to explicitly cancel the task:

+ +
+
>>> async def main():
+...     bus = MessageBus()
+...     await setup(bus)
+...     bus_task = asyncio.create_task(bus.run())
+...     await send(bus)
+...     await asyncio.sleep(0)
+...     bus_task.cancel()
+...     try:
+...         await bus_task
+...     except asyncio.exceptions.CancelledError:
+...         pass
+>>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+Setting up.
+Creating callback for Logger.
+Creating callback for Client 1.
+Creating callback for Client 2.
+Sending messages.
+Logger: {'sender': '', 'event': 'registered',
+         'client': 'Logger', 'plugin': 'Test Plugin',
+         'sends': [], 'receives': [{}]}
+Logger: {'sender': '', 'event': 'registered',
+         'client': 'Client 1', 'plugin': 'Test Plugin',
+         'sends': [{'k1': {'type': 'string'}}],
+         'receives': [{'target': {'const': 'Client 1'}}]}
+Logger: {'sender': '', 'event': 'registered',
+         'client': 'Client 2', 'plugin': 'Test Plugin',
+         'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
+Logger: {'sender': 'Client 1', 'k1': 'Test'}
+Logger: {'sender': 'Client 2', 'target': 'Client 1'}
+Client 1: {'sender': 'Client 2', 'target': 'Client 1'}
+Logger: {'sender': '', 'target': '', 'command': 'get clients'}
+Logger: {'sender': '', 'client': 'Logger', 'plugin': 'Test Plugin',
+         'sends': [], 'receives': [{}]}
+Logger: {'sender': '', 'client': 'Client 1', 'plugin': 'Test Plugin',
+         'sends': [{'k1': {'type': 'string'}}],
+         'receives': [{'target': {'const': 'Client 1'}}]}
+Logger: {'sender': '', 'client': 'Client 2', 'plugin': 'Test Plugin',
+         'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
+
+
+
+ + +
+ +
+ + MessageBus() + + + +
+ +
1148    def __init__(self) -> None:
+1149        """Initialise a new bus without clients.
+1150
+1151        >>> async def main():
+1152        ...     bus = MessageBus()
+1153        >>> asyncio.run(main())
+1154        """
+1155        self._queue: asyncio.Queue = asyncio.Queue()
+1156        self._plugins: Dict[str, str] = {}
+1157        self._send_reg: TemplateRegistry = TemplateRegistry()
+1158        self._recv_reg: TemplateRegistry = TemplateRegistry()
+
+ + +

Initialise a new bus without clients.

+ +
+
>>> async def main():
+...     bus = MessageBus()
+>>> asyncio.run(main())
+
+
+
+ + +
+
+ +
+ + def + register( self, client: str, plugin: str, sends: Iterable[MessageTemplate], receives: Iterable[Tuple[Iterable[MessageTemplate], Callable[[Message], Coroutine[Any, Any, NoneType]]]]) -> None: + + + +
+ +
1160    def register(
+1161        self,
+1162        client: str,
+1163        plugin: str,
+1164        sends: Iterable[MessageTemplate],
+1165        receives: Iterable[Tuple[Iterable[MessageTemplate], MessageCallback]],
+1166    ) -> None:
+1167        """Register a client at the message bus.
+1168
+1169        >>> async def callback(message):
+1170        ...     print(message)
+1171        >>> async def main():
+1172        ...     bus = MessageBus()
+1173        ...     bus.register('Logger', 'Test Plugin',
+1174        ...                  [],    # send nothing
+1175        ...                  [([MessageTemplate({})],  # receive everything
+1176        ...                    callback)])
+1177        ...     bus.register('Client 1', 'Test Plugin',
+1178        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1179        ...                      # send with key 'k1' and string value
+1180        ...                  [([MessageTemplate({'target':
+1181        ...                                      {'const': 'Client 1'}})],
+1182        ...                      # receive for this client
+1183        ...                    callback)])
+1184        ...     bus.register('Client 2', 'Test Plugin',
+1185        ...                  [MessageTemplate({})],  # send arbitrary
+1186        ...                  [([MessageTemplate({'target':
+1187        ...                                      {'const': 'Client 2'}})],
+1188        ...                      # receive for this client
+1189        ...                    callback)])
+1190        >>> asyncio.run(main())
+1191        """
+1192        if not client:
+1193            raise BusException("Client name is not allowed to be empty.")
+1194        if client in self._plugins:
+1195            raise BusException(f"Client '{client}' already registered at message bus.")
+1196        event = Message("")
+1197        event["event"] = "registered"
+1198        event["client"] = client
+1199        self._plugins[client] = plugin
+1200        event["plugin"] = plugin
+1201        for template in sends:
+1202            self._send_reg.insert(template, client)
+1203        event["sends"] = self._send_reg.get_templates(client)
+1204        for templates, callback in receives:
+1205            for template in templates:
+1206                self._recv_reg.insert(template, client, callback)
+1207        event["receives"] = self._recv_reg.get_templates(client)
+1208        self._queue.put_nowait(event)
+
+ + +

Register a client at the message bus.

+ +
+
>>> async def callback(message):
+...     print(message)
+>>> async def main():
+...     bus = MessageBus()
+...     bus.register('Logger', 'Test Plugin',
+...                  [],    # send nothing
+...                  [([MessageTemplate({})],  # receive everything
+...                    callback)])
+...     bus.register('Client 1', 'Test Plugin',
+...                  [MessageTemplate({'k1': {'type': 'string'}})],
+...                      # send with key 'k1' and string value
+...                  [([MessageTemplate({'target':
+...                                      {'const': 'Client 1'}})],
+...                      # receive for this client
+...                    callback)])
+...     bus.register('Client 2', 'Test Plugin',
+...                  [MessageTemplate({})],  # send arbitrary
+...                  [([MessageTemplate({'target':
+...                                      {'const': 'Client 2'}})],
+...                      # receive for this client
+...                    callback)])
+>>> asyncio.run(main())
+
+
+
+ + +
+
+ +
+ + def + unregister(self, client: str) -> None: + + + +
+ +
1210    def unregister(self, client: str) -> None:
+1211        """Unregister a client from the message bus.
+1212
+1213        >>> async def callback(message):
+1214        ...     print(message)
+1215        >>> async def main():
+1216        ...     bus = MessageBus()
+1217        ...     bus.register('Client 1', 'Test Plugin',
+1218        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1219        ...                  [([MessageTemplate({'target':
+1220        ...                                      {'const': 'Client 1'}})],
+1221        ...                    callback)])
+1222        ...     bus.unregister('Client 1')
+1223        >>> asyncio.run(main())
+1224        """
+1225        if client not in self._plugins:
+1226            return
+1227        event = Message("")
+1228        event["event"] = "unregistered"
+1229        event["client"] = client
+1230        del self._plugins[client]
+1231        self._send_reg.delete(client)
+1232        self._recv_reg.delete(client)
+1233        self._queue.put_nowait(event)
+
+ + +

Unregister a client from the message bus.

+ +
+
>>> async def callback(message):
+...     print(message)
+>>> async def main():
+...     bus = MessageBus()
+...     bus.register('Client 1', 'Test Plugin',
+...                  [MessageTemplate({'k1': {'type': 'string'}})],
+...                  [([MessageTemplate({'target':
+...                                      {'const': 'Client 1'}})],
+...                    callback)])
+...     bus.unregister('Client 1')
+>>> asyncio.run(main())
+
+
+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
1235    async def run(self) -> None:
+1236        """Run the message bus forever.
+1237
+1238        >>> async def main():
+1239        ...     bus = MessageBus()
+1240        ...     bus_task = asyncio.create_task(bus.run())
+1241        ...     bus_task.cancel()
+1242        ...     try:
+1243        ...         await bus_task
+1244        ...     except asyncio.exceptions.CancelledError:
+1245        ...         pass
+1246        >>> asyncio.run(main())
+1247        """
+1248        background_tasks = set()
+1249        while True:
+1250            message = await self._queue.get()
+1251            if "target" in message and message["target"] == "" and "command" in message:
+1252                if message["command"] == "get clients":
+1253                    for client in self._plugins:
+1254                        answer = Message("")
+1255                        answer["client"] = client
+1256                        answer["plugin"] = self._plugins[client]
+1257                        answer["sends"] = self._send_reg.get_templates(client)
+1258                        answer["receives"] = self._recv_reg.get_templates(client)
+1259                        await self._queue.put(answer)
+1260                elif message["command"] == "push conf":
+1261                    conf = {}
+1262                    try:
+1263                        with open(sys.argv[1]) as conf_file:
+1264                            conf = json.load(conf_file)
+1265                    except (
+1266                        IndexError,
+1267                        FileNotFoundError,
+1268                        json.decoder.JSONDecodeError,
+1269                    ):
+1270                        pass
+1271                    if conf == message["conf"]:
+1272                        await self._queue.put(Message("", {"event": "conf unchanged"}))
+1273                    else:
+1274                        await self._queue.put(Message("", {"event": "conf changed"}))
+1275                        with open(sys.argv[1], "w") as conf_file:
+1276                            json.dump(message["conf"], conf_file)
+1277            for callback in self._recv_reg.get_callbacks(message):
+1278                task = asyncio.create_task(callback(message))
+1279                background_tasks.add(task)
+1280                task.add_done_callback(background_tasks.discard)
+1281            self._queue.task_done()
+
+ + +

Run the message bus forever.

+ +
+
>>> async def main():
+...     bus = MessageBus()
+...     bus_task = asyncio.create_task(bus.run())
+...     bus_task.cancel()
+...     try:
+...         await bus_task
+...     except asyncio.exceptions.CancelledError:
+...         pass
+>>> asyncio.run(main())
+
+
+
+ + +
+
+ +
+ + async def + send(self, message: Message) -> None: + + + +
+ +
1283    async def send(self, message: Message) -> None:
+1284        """Send a message to the message bus.
+1285
+1286        >>> async def callback(message):
+1287        ...     print(f"Got: {message}")
+1288        >>> async def main():
+1289        ...     bus = MessageBus()
+1290        ...     bus.register('Client 1', 'Test Plugin',
+1291        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1292        ...                  [([MessageTemplate({'target':
+1293        ...                                      {'const': 'Client 1'}})],
+1294        ...                    callback)])
+1295        ...     bus.register('Client 2', 'Test Plugin',
+1296        ...                  [MessageTemplate({})],
+1297        ...                  [([MessageTemplate({'target':
+1298        ...                                      {'const': 'Client 2'}})],
+1299        ...                    callback)])
+1300        ...     bus_task = asyncio.create_task(bus.run())
+1301        ...     await bus.send({'sender': 'Client 1', 'target': 'Client 2',
+1302        ...                     'k1': 'Test'})
+1303        ...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
+1304        ...     try:
+1305        ...         await bus.send({'sender': 'Client 1', 'target': 'Client 2',
+1306        ...                         'k1': 42})
+1307        ...     except BusException as e:
+1308        ...         print(e)
+1309        ...     await asyncio.sleep(0)
+1310        ...     bus_task.cancel()
+1311        ...     try:
+1312        ...         await bus_task
+1313        ...     except asyncio.exceptions.CancelledError:
+1314        ...         pass
+1315        >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+1316        Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
+1317        not allowed for sender 'Client 1'.
+1318        Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
+1319        Got: {'sender': 'Client 2', 'target': 'Client 1'}
+1320        """
+1321        assert isinstance(message["sender"], str)
+1322        sender = message["sender"]
+1323        if sender:
+1324            if not self._send_reg.check(sender, message):
+1325                raise BusException(
+1326                    f"Message '{message}' not allowed for sender '{sender}'."
+1327                )
+1328        await self._queue.put(message)
+
+ + +

Send a message to the message bus.

+ +
+
>>> async def callback(message):
+...     print(f"Got: {message}")
+>>> async def main():
+...     bus = MessageBus()
+...     bus.register('Client 1', 'Test Plugin',
+...                  [MessageTemplate({'k1': {'type': 'string'}})],
+...                  [([MessageTemplate({'target':
+...                                      {'const': 'Client 1'}})],
+...                    callback)])
+...     bus.register('Client 2', 'Test Plugin',
+...                  [MessageTemplate({})],
+...                  [([MessageTemplate({'target':
+...                                      {'const': 'Client 2'}})],
+...                    callback)])
+...     bus_task = asyncio.create_task(bus.run())
+...     await bus.send({'sender': 'Client 1', 'target': 'Client 2',
+...                     'k1': 'Test'})
+...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
+...     try:
+...         await bus.send({'sender': 'Client 1', 'target': 'Client 2',
+...                         'k1': 42})
+...     except BusException as e:
+...         print(e)
+...     await asyncio.sleep(0)
+...     bus_task.cancel()
+...     try:
+...         await bus_task
+...     except asyncio.exceptions.CancelledError:
+...         pass
+>>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
+not allowed for sender 'Client 1'.
+Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
+Got: {'sender': 'Client 2', 'target': 'Client 1'}
+
+
+
+ + +
+
+ +
+ + def + send_nowait(self, message: Message) -> None: + + + +
+ +
1330    def send_nowait(self, message: Message) -> None:
+1331        """Send a message to the message bus without blocking.
+1332
+1333        >>> async def callback(message):
+1334        ...     print(f"Got: {message}")
+1335        >>> async def main():
+1336        ...     bus = MessageBus()
+1337        ...     bus.register('Client 1', 'Test Plugin',
+1338        ...                  [MessageTemplate({'k1': {'type': 'string'}})],
+1339        ...                  [([MessageTemplate({'target':
+1340        ...                                      {'const': 'Client 1'}})],
+1341        ...                    callback)])
+1342        ...     bus.register('Client 2', 'Test Plugin',
+1343        ...                  [MessageTemplate({})],
+1344        ...                  [([MessageTemplate({'target':
+1345        ...                                      {'const': 'Client 2'}})],
+1346        ...                    callback)])
+1347        ...     bus_task = asyncio.create_task(bus.run())
+1348        ...     bus.send_nowait({'sender': 'Client 1', 'target': 'Client 2',
+1349        ...                      'k1': 'Test'})
+1350        ...     bus.send_nowait({'sender': 'Client 2', 'target': 'Client 1'})
+1351        ...     try:
+1352        ...         bus.send_nowait({'sender': 'Client 1',
+1353        ...                          'target': 'Client 2', 'k1': 42})
+1354        ...     except BusException as e:
+1355        ...         print(e)
+1356        ...     await asyncio.sleep(0)
+1357        ...     bus_task.cancel()
+1358        ...     try:
+1359        ...         await bus_task
+1360        ...     except asyncio.exceptions.CancelledError:
+1361        ...         pass
+1362        >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+1363        Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
+1364        not allowed for sender 'Client 1'.
+1365        Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
+1366        Got: {'sender': 'Client 2', 'target': 'Client 1'}
+1367        """
+1368        assert isinstance(message["sender"], str)
+1369        sender = message["sender"]
+1370        if sender:
+1371            if not self._send_reg.check(sender, message):
+1372                raise BusException(
+1373                    f"Message '{message}' not allowed for sender '{sender}'."
+1374                )
+1375        self._queue.put_nowait(message)
+
+ + +

Send a message to the message bus without blocking.

+ +
+
>>> async def callback(message):
+...     print(f"Got: {message}")
+>>> async def main():
+...     bus = MessageBus()
+...     bus.register('Client 1', 'Test Plugin',
+...                  [MessageTemplate({'k1': {'type': 'string'}})],
+...                  [([MessageTemplate({'target':
+...                                      {'const': 'Client 1'}})],
+...                    callback)])
+...     bus.register('Client 2', 'Test Plugin',
+...                  [MessageTemplate({})],
+...                  [([MessageTemplate({'target':
+...                                      {'const': 'Client 2'}})],
+...                    callback)])
+...     bus_task = asyncio.create_task(bus.run())
+...     bus.send_nowait({'sender': 'Client 1', 'target': 'Client 2',
+...                      'k1': 'Test'})
+...     bus.send_nowait({'sender': 'Client 2', 'target': 'Client 1'})
+...     try:
+...         bus.send_nowait({'sender': 'Client 1',
+...                          'target': 'Client 2', 'k1': 42})
+...     except BusException as e:
+...         print(e)
+...     await asyncio.sleep(0)
+...     bus_task.cancel()
+...     try:
+...         await bus_task
+...     except asyncio.exceptions.CancelledError:
+...         pass
+>>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
+Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
+not allowed for sender 'Client 1'.
+Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
+Got: {'sender': 'Client 2', 'target': 'Client 1'}
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/doc/api/controlpi/pluginregistry.html b/doc/api/controlpi/pluginregistry.html new file mode 100644 index 0000000..c073270 --- /dev/null +++ b/doc/api/controlpi/pluginregistry.html @@ -0,0 +1,684 @@ + + + + + + + controlpi.pluginregistry API documentation + + + + + + + + + +
+
+

+controlpi.pluginregistry

+ +

Provide a generic plugin system.

+ +

The class PluginRegistry is initialised with the name of a namespace +package and a base class.

+ +

All modules in the namespace package are loaded. These modules can be +included in different distribution packages, which allows to dynamically +add plugins to the system without changing any code.

+ +

Afterwards, all (direct and indirect) subclasses of the base class are +registered as plugins under their class name. Class names should be unique, +which cannot be programmatically enforced.

+ +
+
>>> class BasePlugin:
+...     pass
+>>> class Plugin1(BasePlugin):
+...     pass
+>>> class Plugin2(BasePlugin):
+...     pass
+>>> registry = PluginRegistry('importlib', BasePlugin)
+
+
+ +

The registry provides a generic mapping interface with the class names as +keys and the classes as values.

+ +
+
>>> print(len(registry))
+2
+>>> for name in registry:
+...     print(f"{name}: {registry[name]}")
+Plugin1: <class 'pluginregistry.Plugin1'>
+Plugin2: <class 'pluginregistry.Plugin2'>
+>>> if 'Plugin1' in registry:
+...     print(f"'Plugin1' is in registry.")
+'Plugin1' is in registry.
+>>> p1 = registry['Plugin1']
+>>> i1 = p1()
+
+
+
+ + + + + +
  1"""Provide a generic plugin system.
+  2
+  3The class PluginRegistry is initialised with the name of a namespace
+  4package and a base class.
+  5
+  6All modules in the namespace package are loaded. These modules can be
+  7included in different distribution packages, which allows to dynamically
+  8add plugins to the system without changing any code.
+  9
+ 10Afterwards, all (direct and indirect) subclasses of the base class are
+ 11registered as plugins under their class name. Class names should be unique,
+ 12which cannot be programmatically enforced.
+ 13
+ 14>>> class BasePlugin:
+ 15...     pass
+ 16>>> class Plugin1(BasePlugin):
+ 17...     pass
+ 18>>> class Plugin2(BasePlugin):
+ 19...     pass
+ 20>>> registry = PluginRegistry('importlib', BasePlugin)
+ 21
+ 22The registry provides a generic mapping interface with the class names as
+ 23keys and the classes as values.
+ 24
+ 25>>> print(len(registry))
+ 262
+ 27>>> for name in registry:
+ 28...     print(f"{name}: {registry[name]}")
+ 29Plugin1: <class 'pluginregistry.Plugin1'>
+ 30Plugin2: <class 'pluginregistry.Plugin2'>
+ 31>>> if 'Plugin1' in registry:
+ 32...     print(f"'Plugin1' is in registry.")
+ 33'Plugin1' is in registry.
+ 34>>> p1 = registry['Plugin1']
+ 35>>> i1 = p1()
+ 36"""
+ 37
+ 38import importlib
+ 39import pkgutil
+ 40import collections.abc
+ 41
+ 42
+ 43class PluginRegistry(collections.abc.Mapping):
+ 44    """Provide a registry for plugins.
+ 45
+ 46    Initialise the registry by loading all modules in the given namespace
+ 47    package and then registering all subclasses of the given base class as
+ 48    plugins (only simulated here – the code for Plugin1 and Plugin2 should
+ 49    be in modules in the given namespace package in real applications):
+ 50    >>> class BasePlugin:
+ 51    ...     pass
+ 52    >>> class Plugin1(BasePlugin):
+ 53    ...     pass
+ 54    >>> class Plugin2(BasePlugin):
+ 55    ...     pass
+ 56    >>> registry = PluginRegistry('importlib', BasePlugin)
+ 57
+ 58    After initialisation, provide a mapping interface to the plugins:
+ 59    >>> print(len(registry))
+ 60    2
+ 61    >>> for name in registry:
+ 62    ...     print(f"{name}: {registry[name]}")
+ 63    Plugin1: <class 'pluginregistry.Plugin1'>
+ 64    Plugin2: <class 'pluginregistry.Plugin2'>
+ 65    >>> if 'Plugin1' in registry:
+ 66    ...     print(f"'Plugin1' is in registry.")
+ 67    'Plugin1' is in registry.
+ 68    """
+ 69
+ 70    def __init__(self, namespace_package: str, base_class: type) -> None:
+ 71        """Initialise registry.
+ 72
+ 73        Import all modules defined in the given namespace package (in any
+ 74        distribution package currently installed in the path). Then register
+ 75        all subclasses of the given base class as plugins.
+ 76
+ 77        >>> class BasePlugin:
+ 78        ...     pass
+ 79        >>> class Plugin1(BasePlugin):
+ 80        ...     pass
+ 81        >>> class Plugin2(BasePlugin):
+ 82        ...     pass
+ 83        >>> registry = PluginRegistry('importlib', BasePlugin)
+ 84        >>> for name in registry._plugins:
+ 85        ...     print(f"{name}: {registry._plugins[name]}")
+ 86        Plugin1: <class 'pluginregistry.Plugin1'>
+ 87        Plugin2: <class 'pluginregistry.Plugin2'>
+ 88        """
+ 89        ns_mod = importlib.import_module(namespace_package)
+ 90        ns_path = ns_mod.__path__
+ 91        ns_name = ns_mod.__name__
+ 92        for _, mod_name, _ in pkgutil.iter_modules(ns_path):
+ 93            importlib.import_module(f"{ns_name}.{mod_name}")
+ 94
+ 95        def all_subclasses(cls):
+ 96            result = []
+ 97            for subcls in cls.__subclasses__():
+ 98                result.append(subcls)
+ 99                result.extend(all_subclasses(subcls))
+100            return result
+101
+102        self._plugins = {cls.__name__: cls for cls in all_subclasses(base_class)}
+103
+104    def __len__(self) -> int:
+105        """Get number of registered plugins.
+106
+107        >>> class BasePlugin:
+108        ...     pass
+109        >>> class Plugin1(BasePlugin):
+110        ...     pass
+111        >>> class Plugin2(BasePlugin):
+112        ...     pass
+113        >>> registry = PluginRegistry('importlib', BasePlugin)
+114        >>> print(registry.__len__())
+115        2
+116        """
+117        return len(self._plugins)
+118
+119    def __iter__(self):
+120        """Get an iterator of the registered plugins.
+121
+122        >>> class BasePlugin:
+123        ...     pass
+124        >>> class Plugin1(BasePlugin):
+125        ...     pass
+126        >>> class Plugin2(BasePlugin):
+127        ...     pass
+128        >>> registry = PluginRegistry('importlib', BasePlugin)
+129        >>> print(type(registry.__iter__()))
+130        <class 'dict_keyiterator'>
+131        >>> for name in registry:
+132        ...     print(name)
+133        Plugin1
+134        Plugin2
+135        """
+136        return iter(self._plugins)
+137
+138    def __getitem__(self, plugin_name: str) -> type:
+139        """Get a registered plugin given its name.
+140
+141        >>> class BasePlugin:
+142        ...     pass
+143        >>> class Plugin1(BasePlugin):
+144        ...     pass
+145        >>> class Plugin2(BasePlugin):
+146        ...     pass
+147        >>> registry = PluginRegistry('importlib', BasePlugin)
+148        >>> print(registry.__getitem__('Plugin1'))
+149        <class 'pluginregistry.Plugin1'>
+150        >>> print(registry.__getitem__('Plugin2'))
+151        <class 'pluginregistry.Plugin2'>
+152        >>> for name in registry:
+153        ...     print(registry[name])
+154        <class 'pluginregistry.Plugin1'>
+155        <class 'pluginregistry.Plugin2'>
+156        """
+157        return self._plugins[plugin_name]
+
+ + +
+
+ +
+ + class + PluginRegistry(collections.abc.Mapping): + + + +
+ +
 44class PluginRegistry(collections.abc.Mapping):
+ 45    """Provide a registry for plugins.
+ 46
+ 47    Initialise the registry by loading all modules in the given namespace
+ 48    package and then registering all subclasses of the given base class as
+ 49    plugins (only simulated here – the code for Plugin1 and Plugin2 should
+ 50    be in modules in the given namespace package in real applications):
+ 51    >>> class BasePlugin:
+ 52    ...     pass
+ 53    >>> class Plugin1(BasePlugin):
+ 54    ...     pass
+ 55    >>> class Plugin2(BasePlugin):
+ 56    ...     pass
+ 57    >>> registry = PluginRegistry('importlib', BasePlugin)
+ 58
+ 59    After initialisation, provide a mapping interface to the plugins:
+ 60    >>> print(len(registry))
+ 61    2
+ 62    >>> for name in registry:
+ 63    ...     print(f"{name}: {registry[name]}")
+ 64    Plugin1: <class 'pluginregistry.Plugin1'>
+ 65    Plugin2: <class 'pluginregistry.Plugin2'>
+ 66    >>> if 'Plugin1' in registry:
+ 67    ...     print(f"'Plugin1' is in registry.")
+ 68    'Plugin1' is in registry.
+ 69    """
+ 70
+ 71    def __init__(self, namespace_package: str, base_class: type) -> None:
+ 72        """Initialise registry.
+ 73
+ 74        Import all modules defined in the given namespace package (in any
+ 75        distribution package currently installed in the path). Then register
+ 76        all subclasses of the given base class as plugins.
+ 77
+ 78        >>> class BasePlugin:
+ 79        ...     pass
+ 80        >>> class Plugin1(BasePlugin):
+ 81        ...     pass
+ 82        >>> class Plugin2(BasePlugin):
+ 83        ...     pass
+ 84        >>> registry = PluginRegistry('importlib', BasePlugin)
+ 85        >>> for name in registry._plugins:
+ 86        ...     print(f"{name}: {registry._plugins[name]}")
+ 87        Plugin1: <class 'pluginregistry.Plugin1'>
+ 88        Plugin2: <class 'pluginregistry.Plugin2'>
+ 89        """
+ 90        ns_mod = importlib.import_module(namespace_package)
+ 91        ns_path = ns_mod.__path__
+ 92        ns_name = ns_mod.__name__
+ 93        for _, mod_name, _ in pkgutil.iter_modules(ns_path):
+ 94            importlib.import_module(f"{ns_name}.{mod_name}")
+ 95
+ 96        def all_subclasses(cls):
+ 97            result = []
+ 98            for subcls in cls.__subclasses__():
+ 99                result.append(subcls)
+100                result.extend(all_subclasses(subcls))
+101            return result
+102
+103        self._plugins = {cls.__name__: cls for cls in all_subclasses(base_class)}
+104
+105    def __len__(self) -> int:
+106        """Get number of registered plugins.
+107
+108        >>> class BasePlugin:
+109        ...     pass
+110        >>> class Plugin1(BasePlugin):
+111        ...     pass
+112        >>> class Plugin2(BasePlugin):
+113        ...     pass
+114        >>> registry = PluginRegistry('importlib', BasePlugin)
+115        >>> print(registry.__len__())
+116        2
+117        """
+118        return len(self._plugins)
+119
+120    def __iter__(self):
+121        """Get an iterator of the registered plugins.
+122
+123        >>> class BasePlugin:
+124        ...     pass
+125        >>> class Plugin1(BasePlugin):
+126        ...     pass
+127        >>> class Plugin2(BasePlugin):
+128        ...     pass
+129        >>> registry = PluginRegistry('importlib', BasePlugin)
+130        >>> print(type(registry.__iter__()))
+131        <class 'dict_keyiterator'>
+132        >>> for name in registry:
+133        ...     print(name)
+134        Plugin1
+135        Plugin2
+136        """
+137        return iter(self._plugins)
+138
+139    def __getitem__(self, plugin_name: str) -> type:
+140        """Get a registered plugin given its name.
+141
+142        >>> class BasePlugin:
+143        ...     pass
+144        >>> class Plugin1(BasePlugin):
+145        ...     pass
+146        >>> class Plugin2(BasePlugin):
+147        ...     pass
+148        >>> registry = PluginRegistry('importlib', BasePlugin)
+149        >>> print(registry.__getitem__('Plugin1'))
+150        <class 'pluginregistry.Plugin1'>
+151        >>> print(registry.__getitem__('Plugin2'))
+152        <class 'pluginregistry.Plugin2'>
+153        >>> for name in registry:
+154        ...     print(registry[name])
+155        <class 'pluginregistry.Plugin1'>
+156        <class 'pluginregistry.Plugin2'>
+157        """
+158        return self._plugins[plugin_name]
+
+ + +

Provide a registry for plugins.

+ +

Initialise the registry by loading all modules in the given namespace +package and then registering all subclasses of the given base class as +plugins (only simulated here – the code for Plugin1 and Plugin2 should +be in modules in the given namespace package in real applications):

+ +
+
>>> class BasePlugin:
+...     pass
+>>> class Plugin1(BasePlugin):
+...     pass
+>>> class Plugin2(BasePlugin):
+...     pass
+>>> registry = PluginRegistry('importlib', BasePlugin)
+
+
+ +

After initialisation, provide a mapping interface to the plugins:

+ +
+
>>> print(len(registry))
+2
+>>> for name in registry:
+...     print(f"{name}: {registry[name]}")
+Plugin1: <class 'pluginregistry.Plugin1'>
+Plugin2: <class 'pluginregistry.Plugin2'>
+>>> if 'Plugin1' in registry:
+...     print(f"'Plugin1' is in registry.")
+'Plugin1' is in registry.
+
+
+
+ + +
+ +
+ + PluginRegistry(namespace_package: str, base_class: type) + + + +
+ +
 71    def __init__(self, namespace_package: str, base_class: type) -> None:
+ 72        """Initialise registry.
+ 73
+ 74        Import all modules defined in the given namespace package (in any
+ 75        distribution package currently installed in the path). Then register
+ 76        all subclasses of the given base class as plugins.
+ 77
+ 78        >>> class BasePlugin:
+ 79        ...     pass
+ 80        >>> class Plugin1(BasePlugin):
+ 81        ...     pass
+ 82        >>> class Plugin2(BasePlugin):
+ 83        ...     pass
+ 84        >>> registry = PluginRegistry('importlib', BasePlugin)
+ 85        >>> for name in registry._plugins:
+ 86        ...     print(f"{name}: {registry._plugins[name]}")
+ 87        Plugin1: <class 'pluginregistry.Plugin1'>
+ 88        Plugin2: <class 'pluginregistry.Plugin2'>
+ 89        """
+ 90        ns_mod = importlib.import_module(namespace_package)
+ 91        ns_path = ns_mod.__path__
+ 92        ns_name = ns_mod.__name__
+ 93        for _, mod_name, _ in pkgutil.iter_modules(ns_path):
+ 94            importlib.import_module(f"{ns_name}.{mod_name}")
+ 95
+ 96        def all_subclasses(cls):
+ 97            result = []
+ 98            for subcls in cls.__subclasses__():
+ 99                result.append(subcls)
+100                result.extend(all_subclasses(subcls))
+101            return result
+102
+103        self._plugins = {cls.__name__: cls for cls in all_subclasses(base_class)}
+
+ + +

Initialise registry.

+ +

Import all modules defined in the given namespace package (in any +distribution package currently installed in the path). Then register +all subclasses of the given base class as plugins.

+ +
+
>>> class BasePlugin:
+...     pass
+>>> class Plugin1(BasePlugin):
+...     pass
+>>> class Plugin2(BasePlugin):
+...     pass
+>>> registry = PluginRegistry('importlib', BasePlugin)
+>>> for name in registry._plugins:
+...     print(f"{name}: {registry._plugins[name]}")
+Plugin1: <class 'pluginregistry.Plugin1'>
+Plugin2: <class 'pluginregistry.Plugin2'>
+
+
+
+ + +
+
+
+ + \ No newline at end of file diff --git a/doc/api/controlpi_plugins.html b/doc/api/controlpi_plugins.html new file mode 100644 index 0000000..6e2e933 --- /dev/null +++ b/doc/api/controlpi_plugins.html @@ -0,0 +1,241 @@ + + + + + + + controlpi_plugins API documentation + + + + + + + + + +
+
+

+controlpi_plugins

+ + + + + +
+
+ + \ No newline at end of file diff --git a/doc/api/controlpi_plugins/state.html b/doc/api/controlpi_plugins/state.html new file mode 100644 index 0000000..9ffcff8 --- /dev/null +++ b/doc/api/controlpi_plugins/state.html @@ -0,0 +1,3256 @@ + + + + + + + controlpi_plugins.state API documentation + + + + + + + + + +
+
+

+controlpi_plugins.state

+ +

Provide state plugins for all kinds of systems.

+ +
    +
  • State represents a Boolean state.
  • +
  • StateAlias translates to another state-like client.
  • +
  • AndState combines several state-like clients by conjunction.
  • +
  • OrState combines several state-like clients by disjunction.
  • +
  • AndSet sets a state due to a conjunction of other state-like clients.
  • +
  • OrSet sets a state due to a disjunction of other state-like clients.
  • +
+ +

All these plugins use the following conventions:

+ +
    +
  • If their state changes they send a message containing "event": "changed" +and "state": NEW STATE.
  • +
  • If their state is reported due to a message, but did not change they send +a message containing just "state": CURRENT STATE.
  • +
  • If they receive a message containing "target": NAME and +"command": "get state" they report their current state.
  • +
  • If State (or any other settable state using these conventions) receives +a message containing "target": NAME, "command": "set state" and +"new state": STATE TO SET it changes the state accordingly. If this +was really a change the corresponding event is sent. If it was already in +this state a report message without "event": "changed" is sent.
  • +
  • StateAlias can alias any message bus client using these conventions, not +just State instances. It translates all messages described here in both +directions.
  • +
  • AndState and OrState instances cannot be set.
  • +
  • AndState and OrState can combine any message bus clients using these +conventions, not just State instances. They only react to messages +containing "state" information.
  • +
+ +
+
>>> import asyncio
+>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test State": {"plugin": "State"},
+...      "Test State 2": {"plugin": "State"},
+...      "Test State 3": {"plugin": "State"},
+...      "Test State 4": {"plugin": "State"},
+...      "Test StateAlias": {"plugin": "StateAlias",
+...                          "alias for": "Test State 2"},
+...      "Test AndState": {"plugin": "AndState",
+...                        "states": ["Test State", "Test StateAlias"]},
+...      "Test OrState": {"plugin": "OrState",
+...                       "states": ["Test State", "Test StateAlias"]},
+...      "Test AndSet": {"plugin": "AndSet",
+...                      "input states": ["Test State", "Test StateAlias"],
+...                      "output state": "Test State 3"},
+...      "Test OrSet": {"plugin": "OrSet",
+...                      "input states": ["Test State", "Test StateAlias"],
+...                      "output state": "Test State 4"}},
+...     [{"target": "Test AndState",
+...       "command": "get state"},
+...      {"target": "Test OrState",
+...       "command": "get state"},
+...      {"target": "Test State",
+...       "command": "set state", "new state": True},
+...      {"target": "Test StateAlias",
+...       "command": "set state", "new state": True},
+...      {"target": "Test State",
+...       "command": "set state", "new state": False}]))
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered', ...
+test(): {'sender': 'test()', 'target': 'Test AndState', 'command': 'get state'}
+test(): {'sender': 'test()', 'target': 'Test OrState', 'command': 'get state'}
+test(): {'sender': 'Test AndState', 'state': False}
+test(): {'sender': 'test()', 'target': 'Test State',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test OrState', 'state': False}
+test(): {'sender': 'test()', 'target': 'Test StateAlias',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test State',
+         'command': 'set state', 'new state': False}
+test(): {'sender': 'Test StateAlias', 'target': 'Test State 2',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}
+test(): {'sender': 'Test OrSet', 'target': 'Test State 4',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test State', 'event': 'changed', 'state': False}
+test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+test(): {'sender': 'Test State 4', 'event': 'changed', 'state': True}
+
+
+
+ + + + + +
  1"""Provide state plugins for all kinds of systems.
+  2
+  3- State represents a Boolean state.
+  4- StateAlias translates to another state-like client.
+  5- AndState combines several state-like clients by conjunction.
+  6- OrState combines several state-like clients by disjunction.
+  7- AndSet sets a state due to a conjunction of other state-like clients.
+  8- OrSet sets a state due to a disjunction of other state-like clients.
+  9
+ 10All these plugins use the following conventions:
+ 11
+ 12- If their state changes they send a message containing "event": "changed"
+ 13  and "state": NEW STATE.
+ 14- If their state is reported due to a message, but did not change they send
+ 15  a message containing just "state": CURRENT STATE.
+ 16- If they receive a message containing "target": NAME and
+ 17  "command": "get state" they report their current state.
+ 18- If State (or any other settable state using these conventions) receives
+ 19  a message containing "target": NAME, "command": "set state" and
+ 20  "new state": STATE TO SET it changes the state accordingly. If this
+ 21  was really a change the corresponding event is sent. If it was already in
+ 22  this state a report message without "event": "changed" is sent.
+ 23- StateAlias can alias any message bus client using these conventions, not
+ 24  just State instances. It translates all messages described here in both
+ 25  directions.
+ 26- AndState and OrState instances cannot be set.
+ 27- AndState and OrState can combine any message bus clients using these
+ 28  conventions, not just State instances. They only react to messages
+ 29  containing "state" information.
+ 30
+ 31>>> import asyncio
+ 32>>> import controlpi
+ 33>>> asyncio.run(controlpi.test(
+ 34...     {"Test State": {"plugin": "State"},
+ 35...      "Test State 2": {"plugin": "State"},
+ 36...      "Test State 3": {"plugin": "State"},
+ 37...      "Test State 4": {"plugin": "State"},
+ 38...      "Test StateAlias": {"plugin": "StateAlias",
+ 39...                          "alias for": "Test State 2"},
+ 40...      "Test AndState": {"plugin": "AndState",
+ 41...                        "states": ["Test State", "Test StateAlias"]},
+ 42...      "Test OrState": {"plugin": "OrState",
+ 43...                       "states": ["Test State", "Test StateAlias"]},
+ 44...      "Test AndSet": {"plugin": "AndSet",
+ 45...                      "input states": ["Test State", "Test StateAlias"],
+ 46...                      "output state": "Test State 3"},
+ 47...      "Test OrSet": {"plugin": "OrSet",
+ 48...                      "input states": ["Test State", "Test StateAlias"],
+ 49...                      "output state": "Test State 4"}},
+ 50...     [{"target": "Test AndState",
+ 51...       "command": "get state"},
+ 52...      {"target": "Test OrState",
+ 53...       "command": "get state"},
+ 54...      {"target": "Test State",
+ 55...       "command": "set state", "new state": True},
+ 56...      {"target": "Test StateAlias",
+ 57...       "command": "set state", "new state": True},
+ 58...      {"target": "Test State",
+ 59...       "command": "set state", "new state": False}]))
+ 60... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+ 61test(): {'sender': '', 'event': 'registered', ...
+ 62test(): {'sender': 'test()', 'target': 'Test AndState', 'command': 'get state'}
+ 63test(): {'sender': 'test()', 'target': 'Test OrState', 'command': 'get state'}
+ 64test(): {'sender': 'Test AndState', 'state': False}
+ 65test(): {'sender': 'test()', 'target': 'Test State',
+ 66         'command': 'set state', 'new state': True}
+ 67test(): {'sender': 'Test OrState', 'state': False}
+ 68test(): {'sender': 'test()', 'target': 'Test StateAlias',
+ 69         'command': 'set state', 'new state': True}
+ 70test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
+ 71test(): {'sender': 'test()', 'target': 'Test State',
+ 72         'command': 'set state', 'new state': False}
+ 73test(): {'sender': 'Test StateAlias', 'target': 'Test State 2',
+ 74         'command': 'set state', 'new state': True}
+ 75test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}
+ 76test(): {'sender': 'Test OrSet', 'target': 'Test State 4',
+ 77         'command': 'set state', 'new state': True}
+ 78test(): {'sender': 'Test State', 'event': 'changed', 'state': False}
+ 79test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+ 80test(): {'sender': 'Test State 4', 'event': 'changed', 'state': True}
+ 81"""
+ 82
+ 83from controlpi import BasePlugin, Message, MessageTemplate
+ 84
+ 85from typing import Dict, List
+ 86
+ 87
+ 88class State(BasePlugin):
+ 89    """Provide a Boolean state.
+ 90
+ 91    The state of a State plugin instance can be queried with the "get state"
+ 92    command and set with the "set state" command to the new state given by
+ 93    the "new state" key:
+ 94    >>> import asyncio
+ 95    >>> import controlpi
+ 96    >>> asyncio.run(controlpi.test(
+ 97    ...     {"Test State": {"plugin": "State"}},
+ 98    ...     [{"target": "Test State", "command": "get state"},
+ 99    ...      {"target": "Test State", "command": "set state",
+100    ...       "new state": True},
+101    ...      {"target": "Test State", "command": "set state",
+102    ...       "new state": True},
+103    ...      {"target": "Test State", "command": "get state"}]))
+104    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+105    test(): {'sender': '', 'event': 'registered', ...
+106    test(): {'sender': 'test()', 'target': 'Test State',
+107             'command': 'get state'}
+108    test(): {'sender': 'test()', 'target': 'Test State',
+109             'command': 'set state', 'new state': True}
+110    test(): {'sender': 'Test State', 'state': False}
+111    test(): {'sender': 'test()', 'target': 'Test State',
+112             'command': 'set state', 'new state': True}
+113    test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
+114    test(): {'sender': 'test()', 'target': 'Test State',
+115             'command': 'get state'}
+116    test(): {'sender': 'Test State', 'state': True}
+117    test(): {'sender': 'Test State', 'state': True}
+118    """
+119
+120    CONF_SCHEMA = True
+121    """Schema for State plugin configuration.
+122
+123    There are no required or optional configuration keys.
+124    """
+125
+126    def process_conf(self) -> None:
+127        """Register plugin as bus client."""
+128        self.state: bool = False
+129        self.bus.register(
+130            self.name,
+131            "State",
+132            [
+133                MessageTemplate(
+134                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+135                ),
+136                MessageTemplate({"state": {"type": "boolean"}}),
+137            ],
+138            [
+139                (
+140                    [
+141                        MessageTemplate(
+142                            {
+143                                "target": {"const": self.name},
+144                                "command": {"const": "get state"},
+145                            }
+146                        )
+147                    ],
+148                    self._get_state,
+149                ),
+150                (
+151                    [
+152                        MessageTemplate(
+153                            {
+154                                "target": {"const": self.name},
+155                                "command": {"const": "set state"},
+156                                "new state": {"type": "boolean"},
+157                            }
+158                        )
+159                    ],
+160                    self._set_state,
+161                ),
+162            ],
+163        )
+164
+165    async def _get_state(self, message: Message) -> None:
+166        await self.bus.send(Message(self.name, {"state": self.state}))
+167
+168    async def _set_state(self, message: Message) -> None:
+169        if self.state != message["new state"]:
+170            assert isinstance(message["new state"], bool)
+171            self.state = message["new state"]
+172            await self.bus.send(
+173                Message(self.name, {"event": "changed", "state": self.state})
+174            )
+175        else:
+176            await self.bus.send(Message(self.name, {"state": self.state}))
+177
+178    async def run(self) -> None:
+179        """Run no code proactively."""
+180        pass
+181
+182
+183class StateAlias(BasePlugin):
+184    """Define an alias for another state.
+185
+186    The "alias for" configuration key gets the name for the other state that
+187    is aliased by the StateAlias plugin instance.
+188
+189    The "get state" and "set state" commands are forwarded to and the
+190    "changed" events and "state" messages are forwarded from this other
+191    state:
+192    >>> import asyncio
+193    >>> import controlpi
+194    >>> asyncio.run(controlpi.test(
+195    ...     {"Test State": {"plugin": "State"},
+196    ...      "Test StateAlias": {"plugin": "StateAlias",
+197    ...                          "alias for": "Test State"}},
+198    ...     [{"target": "Test State", "command": "get state"},
+199    ...      {"target": "Test StateAlias", "command": "set state",
+200    ...       "new state": True},
+201    ...      {"target": "Test State", "command": "set state",
+202    ...       "new state": True},
+203    ...      {"target": "Test StateAlias", "command": "get state"}]))
+204    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+205    test(): {'sender': '', 'event': 'registered', ...
+206    test(): {'sender': 'test()', 'target': 'Test State',
+207             'command': 'get state'}
+208    test(): {'sender': 'test()', 'target': 'Test StateAlias',
+209             'command': 'set state', 'new state': True}
+210    test(): {'sender': 'Test State', 'state': False}
+211    test(): {'sender': 'test()', 'target': 'Test State',
+212             'command': 'set state', 'new state': True}
+213    test(): {'sender': 'Test StateAlias', 'target': 'Test State',
+214             'command': 'set state', 'new state': True}
+215    test(): {'sender': 'Test StateAlias', 'state': False}
+216    test(): {'sender': 'test()', 'target': 'Test StateAlias',
+217             'command': 'get state'}
+218    test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
+219    test(): {'sender': 'Test State', 'state': True}
+220    test(): {'sender': 'Test StateAlias', 'target': 'Test State',
+221             'command': 'get state'}
+222    test(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True}
+223    test(): {'sender': 'Test StateAlias', 'state': True}
+224    """
+225
+226    CONF_SCHEMA = {
+227        "properties": {"alias for": {"type": "string"}},
+228        "required": ["alias for"],
+229    }
+230    """Schema for StateAlias plugin configuration.
+231
+232    Required configuration key:
+233
+234    - 'alias for': name of aliased state.
+235    """
+236
+237    def process_conf(self) -> None:
+238        """Register plugin as bus client."""
+239        self.bus.register(
+240            self.name,
+241            "StateAlias",
+242            [
+243                MessageTemplate(
+244                    {
+245                        "target": {"const": self.conf["alias for"]},
+246                        "command": {"const": "get state"},
+247                    }
+248                ),
+249                MessageTemplate(
+250                    {
+251                        "target": {"const": self.conf["alias for"]},
+252                        "command": {"const": "set state"},
+253                        "new state": {"type": "boolean"},
+254                    }
+255                ),
+256                MessageTemplate(
+257                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+258                ),
+259                MessageTemplate({"state": {"type": "boolean"}}),
+260            ],
+261            [
+262                (
+263                    [
+264                        MessageTemplate(
+265                            {
+266                                "target": {"const": self.name},
+267                                "command": {"const": "get state"},
+268                            }
+269                        )
+270                    ],
+271                    self._get_state,
+272                ),
+273                (
+274                    [
+275                        MessageTemplate(
+276                            {
+277                                "target": {"const": self.name},
+278                                "command": {"const": "set state"},
+279                                "new state": {"type": "boolean"},
+280                            }
+281                        )
+282                    ],
+283                    self._set_state,
+284                ),
+285                (
+286                    [
+287                        MessageTemplate(
+288                            {
+289                                "sender": {"const": self.conf["alias for"]},
+290                                "state": {"type": "boolean"},
+291                            }
+292                        )
+293                    ],
+294                    self._translate,
+295                ),
+296            ],
+297        )
+298
+299    async def _get_state(self, message: Message) -> None:
+300        await self.bus.send(
+301            Message(
+302                self.name, {"target": self.conf["alias for"], "command": "get state"}
+303            )
+304        )
+305
+306    async def _set_state(self, message: Message) -> None:
+307        await self.bus.send(
+308            Message(
+309                self.name,
+310                {
+311                    "target": self.conf["alias for"],
+312                    "command": "set state",
+313                    "new state": message["new state"],
+314                },
+315            )
+316        )
+317
+318    async def _translate(self, message: Message) -> None:
+319        alias_message = Message(self.name)
+320        if "event" in message and message["event"] == "changed":
+321            alias_message["event"] = "changed"
+322        alias_message["state"] = message["state"]
+323        await self.bus.send(alias_message)
+324
+325    async def run(self) -> None:
+326        """Run no code proactively."""
+327        pass
+328
+329
+330class AndState(BasePlugin):
+331    """Define conjunction of states.
+332
+333    The "states" configuration key gets an array of states to be combined.
+334    An AndState plugin client reacts to "get state" commands and sends
+335    "changed" events when a change in one of the combined states leads to
+336    a change for the conjunction:
+337    >>> import asyncio
+338    >>> import controlpi
+339    >>> asyncio.run(controlpi.test(
+340    ...     {"Test State 1": {"plugin": "State"},
+341    ...      "Test State 2": {"plugin": "State"},
+342    ...      "Test AndState": {"plugin": "AndState",
+343    ...                        "states": ["Test State 1", "Test State 2"]}},
+344    ...     [{"target": "Test State 1", "command": "set state",
+345    ...       "new state": True},
+346    ...      {"target": "Test State 2", "command": "set state",
+347    ...       "new state": True},
+348    ...      {"target": "Test State 1", "command": "set state",
+349    ...       "new state": False},
+350    ...      {"target": "Test AndState", "command": "get state"},
+351    ...      {"target": "Test AndState", "command": "get sources"}]))
+352    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+353    test(): {'sender': '', 'event': 'registered', ...
+354    test(): {'sender': 'test()', 'target': 'Test State 1',
+355             'command': 'set state', 'new state': True}
+356    test(): {'sender': 'test()', 'target': 'Test State 2',
+357             'command': 'set state', 'new state': True}
+358    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+359    test(): {'sender': 'test()', 'target': 'Test State 1',
+360             'command': 'set state', 'new state': False}
+361    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+362    test(): {'sender': 'test()', 'target': 'Test AndState',
+363             'command': 'get state'}
+364    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+365    test(): {'sender': 'Test AndState', 'event': 'changed', 'state': True}
+366    test(): {'sender': 'test()', 'target': 'Test AndState',
+367             'command': 'get sources'}
+368    test(): {'sender': 'Test AndState', 'state': True}
+369    test(): {'sender': 'Test AndState', 'event': 'changed', 'state': False}
+370    test(): {'sender': 'Test AndState',
+371             'states': ['Test State 1', 'Test State 2']}
+372    """
+373
+374    CONF_SCHEMA = {
+375        "properties": {"states": {"type": "array", "items": {"type": "string"}}},
+376        "required": ["states"],
+377    }
+378    """Schema for AndState plugin configuration.
+379
+380    Required configuration key:
+381
+382    - 'states': list of names of combined states.
+383    """
+384
+385    def process_conf(self) -> None:
+386        """Register plugin as bus client."""
+387        updates: List[MessageTemplate] = []
+388        self.states: Dict[str, bool] = {}
+389        for state in self.conf["states"]:
+390            updates.append(
+391                MessageTemplate(
+392                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+393                )
+394            )
+395            self.states[state] = False
+396        self.state: bool = all(self.states.values())
+397        self.bus.register(
+398            self.name,
+399            "AndState",
+400            [
+401                MessageTemplate(
+402                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+403                ),
+404                MessageTemplate({"state": {"type": "boolean"}}),
+405                MessageTemplate(
+406                    {"states": {"type": "array", "items": {"type": "string"}}}
+407                ),
+408            ],
+409            [
+410                (
+411                    [
+412                        MessageTemplate(
+413                            {
+414                                "target": {"const": self.name},
+415                                "command": {"const": "get state"},
+416                            }
+417                        )
+418                    ],
+419                    self._get_state,
+420                ),
+421                (
+422                    [
+423                        MessageTemplate(
+424                            {
+425                                "target": {"const": self.name},
+426                                "command": {"const": "get sources"},
+427                            }
+428                        )
+429                    ],
+430                    self._get_sources,
+431                ),
+432                (updates, self._update),
+433            ],
+434        )
+435
+436    async def _get_state(self, message: Message) -> None:
+437        await self.bus.send(Message(self.name, {"state": self.state}))
+438
+439    async def _get_sources(self, message: Message) -> None:
+440        source_states = list(self.states.keys())
+441        await self.bus.send(Message(self.name, {"states": source_states}))
+442
+443    async def _update(self, message: Message) -> None:
+444        assert isinstance(message["sender"], str)
+445        assert isinstance(message["state"], bool)
+446        self.states[message["sender"]] = message["state"]
+447        new_state = all(self.states.values())
+448        if self.state != new_state:
+449            self.state = new_state
+450            await self.bus.send(
+451                Message(self.name, {"event": "changed", "state": self.state})
+452            )
+453
+454    async def run(self) -> None:
+455        """Run no code proactively."""
+456        pass
+457
+458
+459class OrState(BasePlugin):
+460    """Define disjunction of states.
+461
+462    The "states" configuration key gets an array of states to be combined.
+463    An OrState plugin client reacts to "get state" commands and sends
+464    "changed" events when a change in one of the combined states leads to
+465    a change for the disjunction:
+466    >>> import asyncio
+467    >>> import controlpi
+468    >>> asyncio.run(controlpi.test(
+469    ...     {"Test State 1": {"plugin": "State"},
+470    ...      "Test State 2": {"plugin": "State"},
+471    ...      "Test OrState": {"plugin": "OrState",
+472    ...                       "states": ["Test State 1", "Test State 2"]}},
+473    ...     [{"target": "Test State 1", "command": "set state",
+474    ...       "new state": True},
+475    ...      {"target": "Test State 2", "command": "set state",
+476    ...       "new state": True},
+477    ...      {"target": "Test State 1", "command": "set state",
+478    ...       "new state": False},
+479    ...      {"target": "Test OrState", "command": "get state"},
+480    ...      {"target": "Test OrState", "command": "get sources"}]))
+481    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+482    test(): {'sender': '', 'event': 'registered', ...
+483    test(): {'sender': 'test()', 'target': 'Test State 1',
+484             'command': 'set state', 'new state': True}
+485    test(): {'sender': 'test()', 'target': 'Test State 2',
+486             'command': 'set state', 'new state': True}
+487    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+488    test(): {'sender': 'test()', 'target': 'Test State 1',
+489             'command': 'set state', 'new state': False}
+490    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+491    test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}
+492    test(): {'sender': 'test()', 'target': 'Test OrState',
+493             'command': 'get state'}
+494    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+495    test(): {'sender': 'test()', 'target': 'Test OrState',
+496             'command': 'get sources'}
+497    test(): {'sender': 'Test OrState', 'state': True}
+498    test(): {'sender': 'Test OrState',
+499             'states': ['Test State 1', 'Test State 2']}
+500    """
+501
+502    CONF_SCHEMA = {
+503        "properties": {"states": {"type": "array", "items": {"type": "string"}}},
+504        "required": ["states"],
+505    }
+506    """Schema for OrState plugin configuration.
+507
+508    Required configuration key:
+509
+510    - 'states': list of names of combined states.
+511    """
+512
+513    def process_conf(self) -> None:
+514        """Register plugin as bus client."""
+515        updates: List[MessageTemplate] = []
+516        self.states: Dict[str, bool] = {}
+517        for state in self.conf["states"]:
+518            updates.append(
+519                MessageTemplate(
+520                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+521                )
+522            )
+523            self.states[state] = False
+524        self.state: bool = any(self.states.values())
+525        self.bus.register(
+526            self.name,
+527            "OrState",
+528            [
+529                MessageTemplate(
+530                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+531                ),
+532                MessageTemplate({"state": {"type": "boolean"}}),
+533                MessageTemplate(
+534                    {"states": {"type": "array", "items": {"type": "string"}}}
+535                ),
+536            ],
+537            [
+538                (
+539                    [
+540                        MessageTemplate(
+541                            {
+542                                "target": {"const": self.name},
+543                                "command": {"const": "get state"},
+544                            }
+545                        )
+546                    ],
+547                    self._get_state,
+548                ),
+549                (
+550                    [
+551                        MessageTemplate(
+552                            {
+553                                "target": {"const": self.name},
+554                                "command": {"const": "get sources"},
+555                            }
+556                        )
+557                    ],
+558                    self._get_sources,
+559                ),
+560                (updates, self._update),
+561            ],
+562        )
+563
+564    async def _get_state(self, message: Message) -> None:
+565        await self.bus.send(Message(self.name, {"state": self.state}))
+566
+567    async def _get_sources(self, message: Message) -> None:
+568        source_states = list(self.states.keys())
+569        await self.bus.send(Message(self.name, {"states": source_states}))
+570
+571    async def _update(self, message: Message) -> None:
+572        assert isinstance(message["sender"], str)
+573        assert isinstance(message["state"], bool)
+574        self.states[message["sender"]] = message["state"]
+575        new_state = any(self.states.values())
+576        if self.state != new_state:
+577            self.state = new_state
+578            await self.bus.send(
+579                Message(self.name, {"event": "changed", "state": self.state})
+580            )
+581
+582    async def run(self) -> None:
+583        """Run no code proactively."""
+584        pass
+585
+586
+587class AndSet(BasePlugin):
+588    """Set state based on conjunction of other states.
+589
+590    The "input states" configuration key gets an array of states used to
+591    determine the state in the "output state" configuration key:
+592    >>> import asyncio
+593    >>> import controlpi
+594    >>> asyncio.run(controlpi.test(
+595    ...     {"Test State 1": {"plugin": "State"},
+596    ...      "Test State 2": {"plugin": "State"},
+597    ...      "Test State 3": {"plugin": "State"},
+598    ...      "Test AndSet": {"plugin": "AndSet",
+599    ...                      "input states": ["Test State 1",
+600    ...                                       "Test State 2"],
+601    ...                      "output state": "Test State 3"}},
+602    ...     [{"target": "Test State 1", "command": "set state",
+603    ...       "new state": True},
+604    ...      {"target": "Test State 2", "command": "set state",
+605    ...       "new state": True},
+606    ...      {"target": "Test AndSet", "command": "get state"},
+607    ...      {"target": "Test State 1", "command": "set state",
+608    ...       "new state": False},
+609    ...      {"target": "Test AndSet", "command": "get state"},
+610    ...      {"target": "Test AndSet", "command": "get sources"}]))
+611    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+612    test(): {'sender': '', 'event': 'registered', ...
+613    test(): {'sender': 'test()', 'target': 'Test State 1',
+614             'command': 'set state', 'new state': True}
+615    test(): {'sender': 'test()', 'target': 'Test State 2',
+616             'command': 'set state', 'new state': True}
+617    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+618    test(): {'sender': 'test()', 'target': 'Test AndSet',
+619             'command': 'get state'}
+620    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+621    test(): {'sender': 'test()', 'target': 'Test State 1',
+622             'command': 'set state', 'new state': False}
+623    test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+624             'command': 'set state', 'new state': False}
+625    test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+626             'command': 'set state', 'new state': True}
+627    test(): {'sender': 'test()', 'target': 'Test AndSet',
+628             'command': 'get state'}
+629    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+630    test(): {'sender': 'Test State 3', 'state': False}
+631    test(): {'sender': 'Test State 3', 'event': 'changed', 'state': True}
+632    test(): {'sender': 'test()', 'target': 'Test AndSet',
+633             'command': 'get sources'}
+634    test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+635             'command': 'set state', 'new state': True}
+636    test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+637             'command': 'set state', 'new state': False}
+638    test(): {'sender': 'Test AndSet',
+639             'states': ['Test State 1', 'Test State 2']}
+640    test(): {'sender': 'Test State 3', 'state': True}
+641    test(): {'sender': 'Test State 3', 'event': 'changed', 'state': False}
+642    """
+643
+644    CONF_SCHEMA = {
+645        "properties": {
+646            "input states": {"type": "array", "items": {"type": "string"}},
+647            "output state": {"type": "string"},
+648        },
+649        "required": ["input states", "output state"],
+650    }
+651    """Schema for AndSet plugin configuration.
+652
+653    Required configuration keys:
+654
+655    - 'input states': list of names of combined states.
+656    - 'output state': name of state to be set.
+657    """
+658
+659    def process_conf(self) -> None:
+660        """Register plugin as bus client."""
+661        updates: List[MessageTemplate] = []
+662        self.states: Dict[str, bool] = {}
+663        for state in self.conf["input states"]:
+664            updates.append(
+665                MessageTemplate(
+666                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+667                )
+668            )
+669            self.states[state] = False
+670        self.state: bool = all(self.states.values())
+671        self.bus.register(
+672            self.name,
+673            "AndSet",
+674            [
+675                MessageTemplate(
+676                    {
+677                        "target": {"const": self.conf["output state"]},
+678                        "command": {"const": "set state"},
+679                        "new state": {"type": "boolean"},
+680                    }
+681                ),
+682                MessageTemplate(
+683                    {"states": {"type": "array", "items": {"type": "string"}}}
+684                ),
+685            ],
+686            [
+687                (
+688                    [
+689                        MessageTemplate(
+690                            {
+691                                "target": {"const": self.name},
+692                                "command": {"const": "get state"},
+693                            }
+694                        )
+695                    ],
+696                    self._get_state,
+697                ),
+698                (
+699                    [
+700                        MessageTemplate(
+701                            {
+702                                "target": {"const": self.name},
+703                                "command": {"const": "get sources"},
+704                            }
+705                        )
+706                    ],
+707                    self._get_sources,
+708                ),
+709                (updates, self._update),
+710            ],
+711        )
+712
+713    async def _get_state(self, message: Message) -> None:
+714        await self.bus.send(
+715            Message(
+716                self.name,
+717                {
+718                    "target": self.conf["output state"],
+719                    "command": "set state",
+720                    "new state": self.state,
+721                },
+722            )
+723        )
+724
+725    async def _get_sources(self, message: Message) -> None:
+726        source_states = list(self.states.keys())
+727        await self.bus.send(Message(self.name, {"states": source_states}))
+728
+729    async def _update(self, message: Message) -> None:
+730        assert isinstance(message["sender"], str)
+731        assert isinstance(message["state"], bool)
+732        self.states[message["sender"]] = message["state"]
+733        new_state = all(self.states.values())
+734        if self.state != new_state:
+735            self.state = new_state
+736            await self.bus.send(
+737                Message(
+738                    self.name,
+739                    {
+740                        "target": self.conf["output state"],
+741                        "command": "set state",
+742                        "new state": self.state,
+743                    },
+744                )
+745            )
+746
+747    async def run(self) -> None:
+748        """Run no code proactively."""
+749        pass
+750
+751
+752class OrSet(BasePlugin):
+753    """Set state based on disjunction of other states.
+754
+755    The "input states" configuration key gets an array of states used to
+756    determine the state in the "output state" configuration key:
+757    >>> import asyncio
+758    >>> import controlpi
+759    >>> asyncio.run(controlpi.test(
+760    ...     {"Test State 1": {"plugin": "State"},
+761    ...      "Test State 2": {"plugin": "State"},
+762    ...      "Test State 3": {"plugin": "State"},
+763    ...      "Test OrSet": {"plugin": "OrSet",
+764    ...                      "input states": ["Test State 1",
+765    ...                                       "Test State 2"],
+766    ...                      "output state": "Test State 3"}},
+767    ...     [{"target": "Test State 1", "command": "set state",
+768    ...       "new state": True},
+769    ...      {"target": "Test OrSet", "command": "get state"},
+770    ...      {"target": "Test State 2", "command": "set state",
+771    ...       "new state": True},
+772    ...      {"target": "Test State 1", "command": "set state",
+773    ...       "new state": False},
+774    ...      {"target": "Test OrSet", "command": "get sources"}]))
+775    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+776    test(): {'sender': '', 'event': 'registered', ...
+777    test(): {'sender': 'test()', 'target': 'Test State 1',
+778             'command': 'set state', 'new state': True}
+779    test(): {'sender': 'test()', 'target': 'Test OrSet',
+780             'command': 'get state'}
+781    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+782    test(): {'sender': 'test()', 'target': 'Test State 2',
+783             'command': 'set state', 'new state': True}
+784    test(): {'sender': 'Test OrSet', 'target': 'Test State 3',
+785             'command': 'set state', 'new state': False}
+786    test(): {'sender': 'Test OrSet', 'target': 'Test State 3',
+787             'command': 'set state', 'new state': True}
+788    test(): {'sender': 'test()', 'target': 'Test State 1',
+789             'command': 'set state', 'new state': False}
+790    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+791    test(): {'sender': 'Test State 3', 'state': False}
+792    test(): {'sender': 'Test State 3', 'event': 'changed', 'state': True}
+793    test(): {'sender': 'test()', 'target': 'Test OrSet',
+794             'command': 'get sources'}
+795    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+796    test(): {'sender': 'Test OrSet',
+797             'states': ['Test State 1', 'Test State 2']}
+798    """
+799
+800    CONF_SCHEMA = {
+801        "properties": {
+802            "input states": {"type": "array", "items": {"type": "string"}},
+803            "output state": {"type": "string"},
+804        },
+805        "required": ["input states", "output state"],
+806    }
+807    """Schema for OrSet plugin configuration.
+808
+809    Required configuration keys:
+810
+811    - 'input states': list of names of combined states.
+812    - 'output state': name of state to be set.
+813    """
+814
+815    def process_conf(self) -> None:
+816        """Register plugin as bus client."""
+817        updates: List[MessageTemplate] = []
+818        self.states: Dict[str, bool] = {}
+819        for state in self.conf["input states"]:
+820            updates.append(
+821                MessageTemplate(
+822                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+823                )
+824            )
+825            self.states[state] = False
+826        self.state: bool = any(self.states.values())
+827        self.bus.register(
+828            self.name,
+829            "AndSet",
+830            [
+831                MessageTemplate(
+832                    {
+833                        "target": {"const": self.conf["output state"]},
+834                        "command": {"const": "set state"},
+835                        "new state": {"type": "boolean"},
+836                    }
+837                ),
+838                MessageTemplate(
+839                    {"states": {"type": "array", "items": {"type": "string"}}}
+840                ),
+841            ],
+842            [
+843                (
+844                    [
+845                        MessageTemplate(
+846                            {
+847                                "target": {"const": self.name},
+848                                "command": {"const": "get state"},
+849                            }
+850                        )
+851                    ],
+852                    self._get_state,
+853                ),
+854                (
+855                    [
+856                        MessageTemplate(
+857                            {
+858                                "target": {"const": self.name},
+859                                "command": {"const": "get sources"},
+860                            }
+861                        )
+862                    ],
+863                    self._get_sources,
+864                ),
+865                (updates, self._update),
+866            ],
+867        )
+868
+869    async def _get_state(self, message: Message) -> None:
+870        await self.bus.send(
+871            Message(
+872                self.name,
+873                {
+874                    "target": self.conf["output state"],
+875                    "command": "set state",
+876                    "new state": self.state,
+877                },
+878            )
+879        )
+880
+881    async def _get_sources(self, message: Message) -> None:
+882        source_states = list(self.states.keys())
+883        await self.bus.send(Message(self.name, {"states": source_states}))
+884
+885    async def _update(self, message: Message) -> None:
+886        assert isinstance(message["sender"], str)
+887        assert isinstance(message["state"], bool)
+888        self.states[message["sender"]] = message["state"]
+889        new_state = any(self.states.values())
+890        if self.state != new_state:
+891            self.state = new_state
+892            await self.bus.send(
+893                Message(
+894                    self.name,
+895                    {
+896                        "target": self.conf["output state"],
+897                        "command": "set state",
+898                        "new state": self.state,
+899                    },
+900                )
+901            )
+902
+903    async def run(self) -> None:
+904        """Run no code proactively."""
+905        pass
+
+ + +
+
+ +
+ + class + State(controlpi.baseplugin.BasePlugin): + + + +
+ +
 89class State(BasePlugin):
+ 90    """Provide a Boolean state.
+ 91
+ 92    The state of a State plugin instance can be queried with the "get state"
+ 93    command and set with the "set state" command to the new state given by
+ 94    the "new state" key:
+ 95    >>> import asyncio
+ 96    >>> import controlpi
+ 97    >>> asyncio.run(controlpi.test(
+ 98    ...     {"Test State": {"plugin": "State"}},
+ 99    ...     [{"target": "Test State", "command": "get state"},
+100    ...      {"target": "Test State", "command": "set state",
+101    ...       "new state": True},
+102    ...      {"target": "Test State", "command": "set state",
+103    ...       "new state": True},
+104    ...      {"target": "Test State", "command": "get state"}]))
+105    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+106    test(): {'sender': '', 'event': 'registered', ...
+107    test(): {'sender': 'test()', 'target': 'Test State',
+108             'command': 'get state'}
+109    test(): {'sender': 'test()', 'target': 'Test State',
+110             'command': 'set state', 'new state': True}
+111    test(): {'sender': 'Test State', 'state': False}
+112    test(): {'sender': 'test()', 'target': 'Test State',
+113             'command': 'set state', 'new state': True}
+114    test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
+115    test(): {'sender': 'test()', 'target': 'Test State',
+116             'command': 'get state'}
+117    test(): {'sender': 'Test State', 'state': True}
+118    test(): {'sender': 'Test State', 'state': True}
+119    """
+120
+121    CONF_SCHEMA = True
+122    """Schema for State plugin configuration.
+123
+124    There are no required or optional configuration keys.
+125    """
+126
+127    def process_conf(self) -> None:
+128        """Register plugin as bus client."""
+129        self.state: bool = False
+130        self.bus.register(
+131            self.name,
+132            "State",
+133            [
+134                MessageTemplate(
+135                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+136                ),
+137                MessageTemplate({"state": {"type": "boolean"}}),
+138            ],
+139            [
+140                (
+141                    [
+142                        MessageTemplate(
+143                            {
+144                                "target": {"const": self.name},
+145                                "command": {"const": "get state"},
+146                            }
+147                        )
+148                    ],
+149                    self._get_state,
+150                ),
+151                (
+152                    [
+153                        MessageTemplate(
+154                            {
+155                                "target": {"const": self.name},
+156                                "command": {"const": "set state"},
+157                                "new state": {"type": "boolean"},
+158                            }
+159                        )
+160                    ],
+161                    self._set_state,
+162                ),
+163            ],
+164        )
+165
+166    async def _get_state(self, message: Message) -> None:
+167        await self.bus.send(Message(self.name, {"state": self.state}))
+168
+169    async def _set_state(self, message: Message) -> None:
+170        if self.state != message["new state"]:
+171            assert isinstance(message["new state"], bool)
+172            self.state = message["new state"]
+173            await self.bus.send(
+174                Message(self.name, {"event": "changed", "state": self.state})
+175            )
+176        else:
+177            await self.bus.send(Message(self.name, {"state": self.state}))
+178
+179    async def run(self) -> None:
+180        """Run no code proactively."""
+181        pass
+
+ + +

Provide a Boolean state.

+ +

The state of a State plugin instance can be queried with the "get state" +command and set with the "set state" command to the new state given by +the "new state" key:

+ +
+
>>> import asyncio
+>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test State": {"plugin": "State"}},
+...     [{"target": "Test State", "command": "get state"},
+...      {"target": "Test State", "command": "set state",
+...       "new state": True},
+...      {"target": "Test State", "command": "set state",
+...       "new state": True},
+...      {"target": "Test State", "command": "get state"}]))
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered', ...
+test(): {'sender': 'test()', 'target': 'Test State',
+         'command': 'get state'}
+test(): {'sender': 'test()', 'target': 'Test State',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test State', 'state': False}
+test(): {'sender': 'test()', 'target': 'Test State',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test State',
+         'command': 'get state'}
+test(): {'sender': 'Test State', 'state': True}
+test(): {'sender': 'Test State', 'state': True}
+
+
+
+ + +
+
+ CONF_SCHEMA = +True + + +
+ + +

Schema for State plugin configuration.

+ +

There are no required or optional configuration keys.

+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
127    def process_conf(self) -> None:
+128        """Register plugin as bus client."""
+129        self.state: bool = False
+130        self.bus.register(
+131            self.name,
+132            "State",
+133            [
+134                MessageTemplate(
+135                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+136                ),
+137                MessageTemplate({"state": {"type": "boolean"}}),
+138            ],
+139            [
+140                (
+141                    [
+142                        MessageTemplate(
+143                            {
+144                                "target": {"const": self.name},
+145                                "command": {"const": "get state"},
+146                            }
+147                        )
+148                    ],
+149                    self._get_state,
+150                ),
+151                (
+152                    [
+153                        MessageTemplate(
+154                            {
+155                                "target": {"const": self.name},
+156                                "command": {"const": "set state"},
+157                                "new state": {"type": "boolean"},
+158                            }
+159                        )
+160                    ],
+161                    self._set_state,
+162                ),
+163            ],
+164        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
179    async def run(self) -> None:
+180        """Run no code proactively."""
+181        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + StateAlias(controlpi.baseplugin.BasePlugin): + + + +
+ +
184class StateAlias(BasePlugin):
+185    """Define an alias for another state.
+186
+187    The "alias for" configuration key gets the name for the other state that
+188    is aliased by the StateAlias plugin instance.
+189
+190    The "get state" and "set state" commands are forwarded to and the
+191    "changed" events and "state" messages are forwarded from this other
+192    state:
+193    >>> import asyncio
+194    >>> import controlpi
+195    >>> asyncio.run(controlpi.test(
+196    ...     {"Test State": {"plugin": "State"},
+197    ...      "Test StateAlias": {"plugin": "StateAlias",
+198    ...                          "alias for": "Test State"}},
+199    ...     [{"target": "Test State", "command": "get state"},
+200    ...      {"target": "Test StateAlias", "command": "set state",
+201    ...       "new state": True},
+202    ...      {"target": "Test State", "command": "set state",
+203    ...       "new state": True},
+204    ...      {"target": "Test StateAlias", "command": "get state"}]))
+205    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+206    test(): {'sender': '', 'event': 'registered', ...
+207    test(): {'sender': 'test()', 'target': 'Test State',
+208             'command': 'get state'}
+209    test(): {'sender': 'test()', 'target': 'Test StateAlias',
+210             'command': 'set state', 'new state': True}
+211    test(): {'sender': 'Test State', 'state': False}
+212    test(): {'sender': 'test()', 'target': 'Test State',
+213             'command': 'set state', 'new state': True}
+214    test(): {'sender': 'Test StateAlias', 'target': 'Test State',
+215             'command': 'set state', 'new state': True}
+216    test(): {'sender': 'Test StateAlias', 'state': False}
+217    test(): {'sender': 'test()', 'target': 'Test StateAlias',
+218             'command': 'get state'}
+219    test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
+220    test(): {'sender': 'Test State', 'state': True}
+221    test(): {'sender': 'Test StateAlias', 'target': 'Test State',
+222             'command': 'get state'}
+223    test(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True}
+224    test(): {'sender': 'Test StateAlias', 'state': True}
+225    """
+226
+227    CONF_SCHEMA = {
+228        "properties": {"alias for": {"type": "string"}},
+229        "required": ["alias for"],
+230    }
+231    """Schema for StateAlias plugin configuration.
+232
+233    Required configuration key:
+234
+235    - 'alias for': name of aliased state.
+236    """
+237
+238    def process_conf(self) -> None:
+239        """Register plugin as bus client."""
+240        self.bus.register(
+241            self.name,
+242            "StateAlias",
+243            [
+244                MessageTemplate(
+245                    {
+246                        "target": {"const": self.conf["alias for"]},
+247                        "command": {"const": "get state"},
+248                    }
+249                ),
+250                MessageTemplate(
+251                    {
+252                        "target": {"const": self.conf["alias for"]},
+253                        "command": {"const": "set state"},
+254                        "new state": {"type": "boolean"},
+255                    }
+256                ),
+257                MessageTemplate(
+258                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+259                ),
+260                MessageTemplate({"state": {"type": "boolean"}}),
+261            ],
+262            [
+263                (
+264                    [
+265                        MessageTemplate(
+266                            {
+267                                "target": {"const": self.name},
+268                                "command": {"const": "get state"},
+269                            }
+270                        )
+271                    ],
+272                    self._get_state,
+273                ),
+274                (
+275                    [
+276                        MessageTemplate(
+277                            {
+278                                "target": {"const": self.name},
+279                                "command": {"const": "set state"},
+280                                "new state": {"type": "boolean"},
+281                            }
+282                        )
+283                    ],
+284                    self._set_state,
+285                ),
+286                (
+287                    [
+288                        MessageTemplate(
+289                            {
+290                                "sender": {"const": self.conf["alias for"]},
+291                                "state": {"type": "boolean"},
+292                            }
+293                        )
+294                    ],
+295                    self._translate,
+296                ),
+297            ],
+298        )
+299
+300    async def _get_state(self, message: Message) -> None:
+301        await self.bus.send(
+302            Message(
+303                self.name, {"target": self.conf["alias for"], "command": "get state"}
+304            )
+305        )
+306
+307    async def _set_state(self, message: Message) -> None:
+308        await self.bus.send(
+309            Message(
+310                self.name,
+311                {
+312                    "target": self.conf["alias for"],
+313                    "command": "set state",
+314                    "new state": message["new state"],
+315                },
+316            )
+317        )
+318
+319    async def _translate(self, message: Message) -> None:
+320        alias_message = Message(self.name)
+321        if "event" in message and message["event"] == "changed":
+322            alias_message["event"] = "changed"
+323        alias_message["state"] = message["state"]
+324        await self.bus.send(alias_message)
+325
+326    async def run(self) -> None:
+327        """Run no code proactively."""
+328        pass
+
+ + +

Define an alias for another state.

+ +

The "alias for" configuration key gets the name for the other state that +is aliased by the StateAlias plugin instance.

+ +

The "get state" and "set state" commands are forwarded to and the +"changed" events and "state" messages are forwarded from this other +state:

+ +
+
>>> import asyncio
+>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test State": {"plugin": "State"},
+...      "Test StateAlias": {"plugin": "StateAlias",
+...                          "alias for": "Test State"}},
+...     [{"target": "Test State", "command": "get state"},
+...      {"target": "Test StateAlias", "command": "set state",
+...       "new state": True},
+...      {"target": "Test State", "command": "set state",
+...       "new state": True},
+...      {"target": "Test StateAlias", "command": "get state"}]))
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered', ...
+test(): {'sender': 'test()', 'target': 'Test State',
+         'command': 'get state'}
+test(): {'sender': 'test()', 'target': 'Test StateAlias',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test State', 'state': False}
+test(): {'sender': 'test()', 'target': 'Test State',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test StateAlias', 'target': 'Test State',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test StateAlias', 'state': False}
+test(): {'sender': 'test()', 'target': 'Test StateAlias',
+         'command': 'get state'}
+test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
+test(): {'sender': 'Test State', 'state': True}
+test(): {'sender': 'Test StateAlias', 'target': 'Test State',
+         'command': 'get state'}
+test(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True}
+test(): {'sender': 'Test StateAlias', 'state': True}
+
+
+
+ + +
+
+ CONF_SCHEMA = +{'properties': {'alias for': {'type': 'string'}}, 'required': ['alias for']} + + +
+ + +

Schema for StateAlias plugin configuration.

+ +

Required configuration key:

+ +
    +
  • 'alias for': name of aliased state.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
238    def process_conf(self) -> None:
+239        """Register plugin as bus client."""
+240        self.bus.register(
+241            self.name,
+242            "StateAlias",
+243            [
+244                MessageTemplate(
+245                    {
+246                        "target": {"const": self.conf["alias for"]},
+247                        "command": {"const": "get state"},
+248                    }
+249                ),
+250                MessageTemplate(
+251                    {
+252                        "target": {"const": self.conf["alias for"]},
+253                        "command": {"const": "set state"},
+254                        "new state": {"type": "boolean"},
+255                    }
+256                ),
+257                MessageTemplate(
+258                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+259                ),
+260                MessageTemplate({"state": {"type": "boolean"}}),
+261            ],
+262            [
+263                (
+264                    [
+265                        MessageTemplate(
+266                            {
+267                                "target": {"const": self.name},
+268                                "command": {"const": "get state"},
+269                            }
+270                        )
+271                    ],
+272                    self._get_state,
+273                ),
+274                (
+275                    [
+276                        MessageTemplate(
+277                            {
+278                                "target": {"const": self.name},
+279                                "command": {"const": "set state"},
+280                                "new state": {"type": "boolean"},
+281                            }
+282                        )
+283                    ],
+284                    self._set_state,
+285                ),
+286                (
+287                    [
+288                        MessageTemplate(
+289                            {
+290                                "sender": {"const": self.conf["alias for"]},
+291                                "state": {"type": "boolean"},
+292                            }
+293                        )
+294                    ],
+295                    self._translate,
+296                ),
+297            ],
+298        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
326    async def run(self) -> None:
+327        """Run no code proactively."""
+328        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + AndState(controlpi.baseplugin.BasePlugin): + + + +
+ +
331class AndState(BasePlugin):
+332    """Define conjunction of states.
+333
+334    The "states" configuration key gets an array of states to be combined.
+335    An AndState plugin client reacts to "get state" commands and sends
+336    "changed" events when a change in one of the combined states leads to
+337    a change for the conjunction:
+338    >>> import asyncio
+339    >>> import controlpi
+340    >>> asyncio.run(controlpi.test(
+341    ...     {"Test State 1": {"plugin": "State"},
+342    ...      "Test State 2": {"plugin": "State"},
+343    ...      "Test AndState": {"plugin": "AndState",
+344    ...                        "states": ["Test State 1", "Test State 2"]}},
+345    ...     [{"target": "Test State 1", "command": "set state",
+346    ...       "new state": True},
+347    ...      {"target": "Test State 2", "command": "set state",
+348    ...       "new state": True},
+349    ...      {"target": "Test State 1", "command": "set state",
+350    ...       "new state": False},
+351    ...      {"target": "Test AndState", "command": "get state"},
+352    ...      {"target": "Test AndState", "command": "get sources"}]))
+353    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+354    test(): {'sender': '', 'event': 'registered', ...
+355    test(): {'sender': 'test()', 'target': 'Test State 1',
+356             'command': 'set state', 'new state': True}
+357    test(): {'sender': 'test()', 'target': 'Test State 2',
+358             'command': 'set state', 'new state': True}
+359    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+360    test(): {'sender': 'test()', 'target': 'Test State 1',
+361             'command': 'set state', 'new state': False}
+362    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+363    test(): {'sender': 'test()', 'target': 'Test AndState',
+364             'command': 'get state'}
+365    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+366    test(): {'sender': 'Test AndState', 'event': 'changed', 'state': True}
+367    test(): {'sender': 'test()', 'target': 'Test AndState',
+368             'command': 'get sources'}
+369    test(): {'sender': 'Test AndState', 'state': True}
+370    test(): {'sender': 'Test AndState', 'event': 'changed', 'state': False}
+371    test(): {'sender': 'Test AndState',
+372             'states': ['Test State 1', 'Test State 2']}
+373    """
+374
+375    CONF_SCHEMA = {
+376        "properties": {"states": {"type": "array", "items": {"type": "string"}}},
+377        "required": ["states"],
+378    }
+379    """Schema for AndState plugin configuration.
+380
+381    Required configuration key:
+382
+383    - 'states': list of names of combined states.
+384    """
+385
+386    def process_conf(self) -> None:
+387        """Register plugin as bus client."""
+388        updates: List[MessageTemplate] = []
+389        self.states: Dict[str, bool] = {}
+390        for state in self.conf["states"]:
+391            updates.append(
+392                MessageTemplate(
+393                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+394                )
+395            )
+396            self.states[state] = False
+397        self.state: bool = all(self.states.values())
+398        self.bus.register(
+399            self.name,
+400            "AndState",
+401            [
+402                MessageTemplate(
+403                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+404                ),
+405                MessageTemplate({"state": {"type": "boolean"}}),
+406                MessageTemplate(
+407                    {"states": {"type": "array", "items": {"type": "string"}}}
+408                ),
+409            ],
+410            [
+411                (
+412                    [
+413                        MessageTemplate(
+414                            {
+415                                "target": {"const": self.name},
+416                                "command": {"const": "get state"},
+417                            }
+418                        )
+419                    ],
+420                    self._get_state,
+421                ),
+422                (
+423                    [
+424                        MessageTemplate(
+425                            {
+426                                "target": {"const": self.name},
+427                                "command": {"const": "get sources"},
+428                            }
+429                        )
+430                    ],
+431                    self._get_sources,
+432                ),
+433                (updates, self._update),
+434            ],
+435        )
+436
+437    async def _get_state(self, message: Message) -> None:
+438        await self.bus.send(Message(self.name, {"state": self.state}))
+439
+440    async def _get_sources(self, message: Message) -> None:
+441        source_states = list(self.states.keys())
+442        await self.bus.send(Message(self.name, {"states": source_states}))
+443
+444    async def _update(self, message: Message) -> None:
+445        assert isinstance(message["sender"], str)
+446        assert isinstance(message["state"], bool)
+447        self.states[message["sender"]] = message["state"]
+448        new_state = all(self.states.values())
+449        if self.state != new_state:
+450            self.state = new_state
+451            await self.bus.send(
+452                Message(self.name, {"event": "changed", "state": self.state})
+453            )
+454
+455    async def run(self) -> None:
+456        """Run no code proactively."""
+457        pass
+
+ + +

Define conjunction of states.

+ +

The "states" configuration key gets an array of states to be combined. +An AndState plugin client reacts to "get state" commands and sends +"changed" events when a change in one of the combined states leads to +a change for the conjunction:

+ +
+
>>> import asyncio
+>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test State 1": {"plugin": "State"},
+...      "Test State 2": {"plugin": "State"},
+...      "Test AndState": {"plugin": "AndState",
+...                        "states": ["Test State 1", "Test State 2"]}},
+...     [{"target": "Test State 1", "command": "set state",
+...       "new state": True},
+...      {"target": "Test State 2", "command": "set state",
+...       "new state": True},
+...      {"target": "Test State 1", "command": "set state",
+...       "new state": False},
+...      {"target": "Test AndState", "command": "get state"},
+...      {"target": "Test AndState", "command": "get sources"}]))
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered', ...
+test(): {'sender': 'test()', 'target': 'Test State 1',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'test()', 'target': 'Test State 2',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test State 1',
+         'command': 'set state', 'new state': False}
+test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test AndState',
+         'command': 'get state'}
+test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+test(): {'sender': 'Test AndState', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test AndState',
+         'command': 'get sources'}
+test(): {'sender': 'Test AndState', 'state': True}
+test(): {'sender': 'Test AndState', 'event': 'changed', 'state': False}
+test(): {'sender': 'Test AndState',
+         'states': ['Test State 1', 'Test State 2']}
+
+
+
+ + +
+
+ CONF_SCHEMA = +{'properties': {'states': {'type': 'array', 'items': {'type': 'string'}}}, 'required': ['states']} + + +
+ + +

Schema for AndState plugin configuration.

+ +

Required configuration key:

+ +
    +
  • 'states': list of names of combined states.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
386    def process_conf(self) -> None:
+387        """Register plugin as bus client."""
+388        updates: List[MessageTemplate] = []
+389        self.states: Dict[str, bool] = {}
+390        for state in self.conf["states"]:
+391            updates.append(
+392                MessageTemplate(
+393                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+394                )
+395            )
+396            self.states[state] = False
+397        self.state: bool = all(self.states.values())
+398        self.bus.register(
+399            self.name,
+400            "AndState",
+401            [
+402                MessageTemplate(
+403                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+404                ),
+405                MessageTemplate({"state": {"type": "boolean"}}),
+406                MessageTemplate(
+407                    {"states": {"type": "array", "items": {"type": "string"}}}
+408                ),
+409            ],
+410            [
+411                (
+412                    [
+413                        MessageTemplate(
+414                            {
+415                                "target": {"const": self.name},
+416                                "command": {"const": "get state"},
+417                            }
+418                        )
+419                    ],
+420                    self._get_state,
+421                ),
+422                (
+423                    [
+424                        MessageTemplate(
+425                            {
+426                                "target": {"const": self.name},
+427                                "command": {"const": "get sources"},
+428                            }
+429                        )
+430                    ],
+431                    self._get_sources,
+432                ),
+433                (updates, self._update),
+434            ],
+435        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
455    async def run(self) -> None:
+456        """Run no code proactively."""
+457        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + OrState(controlpi.baseplugin.BasePlugin): + + + +
+ +
460class OrState(BasePlugin):
+461    """Define disjunction of states.
+462
+463    The "states" configuration key gets an array of states to be combined.
+464    An OrState plugin client reacts to "get state" commands and sends
+465    "changed" events when a change in one of the combined states leads to
+466    a change for the disjunction:
+467    >>> import asyncio
+468    >>> import controlpi
+469    >>> asyncio.run(controlpi.test(
+470    ...     {"Test State 1": {"plugin": "State"},
+471    ...      "Test State 2": {"plugin": "State"},
+472    ...      "Test OrState": {"plugin": "OrState",
+473    ...                       "states": ["Test State 1", "Test State 2"]}},
+474    ...     [{"target": "Test State 1", "command": "set state",
+475    ...       "new state": True},
+476    ...      {"target": "Test State 2", "command": "set state",
+477    ...       "new state": True},
+478    ...      {"target": "Test State 1", "command": "set state",
+479    ...       "new state": False},
+480    ...      {"target": "Test OrState", "command": "get state"},
+481    ...      {"target": "Test OrState", "command": "get sources"}]))
+482    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+483    test(): {'sender': '', 'event': 'registered', ...
+484    test(): {'sender': 'test()', 'target': 'Test State 1',
+485             'command': 'set state', 'new state': True}
+486    test(): {'sender': 'test()', 'target': 'Test State 2',
+487             'command': 'set state', 'new state': True}
+488    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+489    test(): {'sender': 'test()', 'target': 'Test State 1',
+490             'command': 'set state', 'new state': False}
+491    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+492    test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}
+493    test(): {'sender': 'test()', 'target': 'Test OrState',
+494             'command': 'get state'}
+495    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+496    test(): {'sender': 'test()', 'target': 'Test OrState',
+497             'command': 'get sources'}
+498    test(): {'sender': 'Test OrState', 'state': True}
+499    test(): {'sender': 'Test OrState',
+500             'states': ['Test State 1', 'Test State 2']}
+501    """
+502
+503    CONF_SCHEMA = {
+504        "properties": {"states": {"type": "array", "items": {"type": "string"}}},
+505        "required": ["states"],
+506    }
+507    """Schema for OrState plugin configuration.
+508
+509    Required configuration key:
+510
+511    - 'states': list of names of combined states.
+512    """
+513
+514    def process_conf(self) -> None:
+515        """Register plugin as bus client."""
+516        updates: List[MessageTemplate] = []
+517        self.states: Dict[str, bool] = {}
+518        for state in self.conf["states"]:
+519            updates.append(
+520                MessageTemplate(
+521                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+522                )
+523            )
+524            self.states[state] = False
+525        self.state: bool = any(self.states.values())
+526        self.bus.register(
+527            self.name,
+528            "OrState",
+529            [
+530                MessageTemplate(
+531                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+532                ),
+533                MessageTemplate({"state": {"type": "boolean"}}),
+534                MessageTemplate(
+535                    {"states": {"type": "array", "items": {"type": "string"}}}
+536                ),
+537            ],
+538            [
+539                (
+540                    [
+541                        MessageTemplate(
+542                            {
+543                                "target": {"const": self.name},
+544                                "command": {"const": "get state"},
+545                            }
+546                        )
+547                    ],
+548                    self._get_state,
+549                ),
+550                (
+551                    [
+552                        MessageTemplate(
+553                            {
+554                                "target": {"const": self.name},
+555                                "command": {"const": "get sources"},
+556                            }
+557                        )
+558                    ],
+559                    self._get_sources,
+560                ),
+561                (updates, self._update),
+562            ],
+563        )
+564
+565    async def _get_state(self, message: Message) -> None:
+566        await self.bus.send(Message(self.name, {"state": self.state}))
+567
+568    async def _get_sources(self, message: Message) -> None:
+569        source_states = list(self.states.keys())
+570        await self.bus.send(Message(self.name, {"states": source_states}))
+571
+572    async def _update(self, message: Message) -> None:
+573        assert isinstance(message["sender"], str)
+574        assert isinstance(message["state"], bool)
+575        self.states[message["sender"]] = message["state"]
+576        new_state = any(self.states.values())
+577        if self.state != new_state:
+578            self.state = new_state
+579            await self.bus.send(
+580                Message(self.name, {"event": "changed", "state": self.state})
+581            )
+582
+583    async def run(self) -> None:
+584        """Run no code proactively."""
+585        pass
+
+ + +

Define disjunction of states.

+ +

The "states" configuration key gets an array of states to be combined. +An OrState plugin client reacts to "get state" commands and sends +"changed" events when a change in one of the combined states leads to +a change for the disjunction:

+ +
+
>>> import asyncio
+>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test State 1": {"plugin": "State"},
+...      "Test State 2": {"plugin": "State"},
+...      "Test OrState": {"plugin": "OrState",
+...                       "states": ["Test State 1", "Test State 2"]}},
+...     [{"target": "Test State 1", "command": "set state",
+...       "new state": True},
+...      {"target": "Test State 2", "command": "set state",
+...       "new state": True},
+...      {"target": "Test State 1", "command": "set state",
+...       "new state": False},
+...      {"target": "Test OrState", "command": "get state"},
+...      {"target": "Test OrState", "command": "get sources"}]))
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered', ...
+test(): {'sender': 'test()', 'target': 'Test State 1',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'test()', 'target': 'Test State 2',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test State 1',
+         'command': 'set state', 'new state': False}
+test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test OrState',
+         'command': 'get state'}
+test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+test(): {'sender': 'test()', 'target': 'Test OrState',
+         'command': 'get sources'}
+test(): {'sender': 'Test OrState', 'state': True}
+test(): {'sender': 'Test OrState',
+         'states': ['Test State 1', 'Test State 2']}
+
+
+
+ + +
+
+ CONF_SCHEMA = +{'properties': {'states': {'type': 'array', 'items': {'type': 'string'}}}, 'required': ['states']} + + +
+ + +

Schema for OrState plugin configuration.

+ +

Required configuration key:

+ +
    +
  • 'states': list of names of combined states.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
514    def process_conf(self) -> None:
+515        """Register plugin as bus client."""
+516        updates: List[MessageTemplate] = []
+517        self.states: Dict[str, bool] = {}
+518        for state in self.conf["states"]:
+519            updates.append(
+520                MessageTemplate(
+521                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+522                )
+523            )
+524            self.states[state] = False
+525        self.state: bool = any(self.states.values())
+526        self.bus.register(
+527            self.name,
+528            "OrState",
+529            [
+530                MessageTemplate(
+531                    {"event": {"const": "changed"}, "state": {"type": "boolean"}}
+532                ),
+533                MessageTemplate({"state": {"type": "boolean"}}),
+534                MessageTemplate(
+535                    {"states": {"type": "array", "items": {"type": "string"}}}
+536                ),
+537            ],
+538            [
+539                (
+540                    [
+541                        MessageTemplate(
+542                            {
+543                                "target": {"const": self.name},
+544                                "command": {"const": "get state"},
+545                            }
+546                        )
+547                    ],
+548                    self._get_state,
+549                ),
+550                (
+551                    [
+552                        MessageTemplate(
+553                            {
+554                                "target": {"const": self.name},
+555                                "command": {"const": "get sources"},
+556                            }
+557                        )
+558                    ],
+559                    self._get_sources,
+560                ),
+561                (updates, self._update),
+562            ],
+563        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
583    async def run(self) -> None:
+584        """Run no code proactively."""
+585        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + AndSet(controlpi.baseplugin.BasePlugin): + + + +
+ +
588class AndSet(BasePlugin):
+589    """Set state based on conjunction of other states.
+590
+591    The "input states" configuration key gets an array of states used to
+592    determine the state in the "output state" configuration key:
+593    >>> import asyncio
+594    >>> import controlpi
+595    >>> asyncio.run(controlpi.test(
+596    ...     {"Test State 1": {"plugin": "State"},
+597    ...      "Test State 2": {"plugin": "State"},
+598    ...      "Test State 3": {"plugin": "State"},
+599    ...      "Test AndSet": {"plugin": "AndSet",
+600    ...                      "input states": ["Test State 1",
+601    ...                                       "Test State 2"],
+602    ...                      "output state": "Test State 3"}},
+603    ...     [{"target": "Test State 1", "command": "set state",
+604    ...       "new state": True},
+605    ...      {"target": "Test State 2", "command": "set state",
+606    ...       "new state": True},
+607    ...      {"target": "Test AndSet", "command": "get state"},
+608    ...      {"target": "Test State 1", "command": "set state",
+609    ...       "new state": False},
+610    ...      {"target": "Test AndSet", "command": "get state"},
+611    ...      {"target": "Test AndSet", "command": "get sources"}]))
+612    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+613    test(): {'sender': '', 'event': 'registered', ...
+614    test(): {'sender': 'test()', 'target': 'Test State 1',
+615             'command': 'set state', 'new state': True}
+616    test(): {'sender': 'test()', 'target': 'Test State 2',
+617             'command': 'set state', 'new state': True}
+618    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+619    test(): {'sender': 'test()', 'target': 'Test AndSet',
+620             'command': 'get state'}
+621    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+622    test(): {'sender': 'test()', 'target': 'Test State 1',
+623             'command': 'set state', 'new state': False}
+624    test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+625             'command': 'set state', 'new state': False}
+626    test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+627             'command': 'set state', 'new state': True}
+628    test(): {'sender': 'test()', 'target': 'Test AndSet',
+629             'command': 'get state'}
+630    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+631    test(): {'sender': 'Test State 3', 'state': False}
+632    test(): {'sender': 'Test State 3', 'event': 'changed', 'state': True}
+633    test(): {'sender': 'test()', 'target': 'Test AndSet',
+634             'command': 'get sources'}
+635    test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+636             'command': 'set state', 'new state': True}
+637    test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+638             'command': 'set state', 'new state': False}
+639    test(): {'sender': 'Test AndSet',
+640             'states': ['Test State 1', 'Test State 2']}
+641    test(): {'sender': 'Test State 3', 'state': True}
+642    test(): {'sender': 'Test State 3', 'event': 'changed', 'state': False}
+643    """
+644
+645    CONF_SCHEMA = {
+646        "properties": {
+647            "input states": {"type": "array", "items": {"type": "string"}},
+648            "output state": {"type": "string"},
+649        },
+650        "required": ["input states", "output state"],
+651    }
+652    """Schema for AndSet plugin configuration.
+653
+654    Required configuration keys:
+655
+656    - 'input states': list of names of combined states.
+657    - 'output state': name of state to be set.
+658    """
+659
+660    def process_conf(self) -> None:
+661        """Register plugin as bus client."""
+662        updates: List[MessageTemplate] = []
+663        self.states: Dict[str, bool] = {}
+664        for state in self.conf["input states"]:
+665            updates.append(
+666                MessageTemplate(
+667                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+668                )
+669            )
+670            self.states[state] = False
+671        self.state: bool = all(self.states.values())
+672        self.bus.register(
+673            self.name,
+674            "AndSet",
+675            [
+676                MessageTemplate(
+677                    {
+678                        "target": {"const": self.conf["output state"]},
+679                        "command": {"const": "set state"},
+680                        "new state": {"type": "boolean"},
+681                    }
+682                ),
+683                MessageTemplate(
+684                    {"states": {"type": "array", "items": {"type": "string"}}}
+685                ),
+686            ],
+687            [
+688                (
+689                    [
+690                        MessageTemplate(
+691                            {
+692                                "target": {"const": self.name},
+693                                "command": {"const": "get state"},
+694                            }
+695                        )
+696                    ],
+697                    self._get_state,
+698                ),
+699                (
+700                    [
+701                        MessageTemplate(
+702                            {
+703                                "target": {"const": self.name},
+704                                "command": {"const": "get sources"},
+705                            }
+706                        )
+707                    ],
+708                    self._get_sources,
+709                ),
+710                (updates, self._update),
+711            ],
+712        )
+713
+714    async def _get_state(self, message: Message) -> None:
+715        await self.bus.send(
+716            Message(
+717                self.name,
+718                {
+719                    "target": self.conf["output state"],
+720                    "command": "set state",
+721                    "new state": self.state,
+722                },
+723            )
+724        )
+725
+726    async def _get_sources(self, message: Message) -> None:
+727        source_states = list(self.states.keys())
+728        await self.bus.send(Message(self.name, {"states": source_states}))
+729
+730    async def _update(self, message: Message) -> None:
+731        assert isinstance(message["sender"], str)
+732        assert isinstance(message["state"], bool)
+733        self.states[message["sender"]] = message["state"]
+734        new_state = all(self.states.values())
+735        if self.state != new_state:
+736            self.state = new_state
+737            await self.bus.send(
+738                Message(
+739                    self.name,
+740                    {
+741                        "target": self.conf["output state"],
+742                        "command": "set state",
+743                        "new state": self.state,
+744                    },
+745                )
+746            )
+747
+748    async def run(self) -> None:
+749        """Run no code proactively."""
+750        pass
+
+ + +

Set state based on conjunction of other states.

+ +

The "input states" configuration key gets an array of states used to +determine the state in the "output state" configuration key:

+ +
+
>>> import asyncio
+>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test State 1": {"plugin": "State"},
+...      "Test State 2": {"plugin": "State"},
+...      "Test State 3": {"plugin": "State"},
+...      "Test AndSet": {"plugin": "AndSet",
+...                      "input states": ["Test State 1",
+...                                       "Test State 2"],
+...                      "output state": "Test State 3"}},
+...     [{"target": "Test State 1", "command": "set state",
+...       "new state": True},
+...      {"target": "Test State 2", "command": "set state",
+...       "new state": True},
+...      {"target": "Test AndSet", "command": "get state"},
+...      {"target": "Test State 1", "command": "set state",
+...       "new state": False},
+...      {"target": "Test AndSet", "command": "get state"},
+...      {"target": "Test AndSet", "command": "get sources"}]))
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered', ...
+test(): {'sender': 'test()', 'target': 'Test State 1',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'test()', 'target': 'Test State 2',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test AndSet',
+         'command': 'get state'}
+test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test State 1',
+         'command': 'set state', 'new state': False}
+test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+         'command': 'set state', 'new state': False}
+test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'test()', 'target': 'Test AndSet',
+         'command': 'get state'}
+test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+test(): {'sender': 'Test State 3', 'state': False}
+test(): {'sender': 'Test State 3', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test AndSet',
+         'command': 'get sources'}
+test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+         'command': 'set state', 'new state': False}
+test(): {'sender': 'Test AndSet',
+         'states': ['Test State 1', 'Test State 2']}
+test(): {'sender': 'Test State 3', 'state': True}
+test(): {'sender': 'Test State 3', 'event': 'changed', 'state': False}
+
+
+
+ + +
+
+ CONF_SCHEMA = + + {'properties': {'input states': {'type': 'array', 'items': {'type': 'string'}}, 'output state': {'type': 'string'}}, 'required': ['input states', 'output state']} + + +
+ + +

Schema for AndSet plugin configuration.

+ +

Required configuration keys:

+ +
    +
  • 'input states': list of names of combined states.
  • +
  • 'output state': name of state to be set.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
660    def process_conf(self) -> None:
+661        """Register plugin as bus client."""
+662        updates: List[MessageTemplate] = []
+663        self.states: Dict[str, bool] = {}
+664        for state in self.conf["input states"]:
+665            updates.append(
+666                MessageTemplate(
+667                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+668                )
+669            )
+670            self.states[state] = False
+671        self.state: bool = all(self.states.values())
+672        self.bus.register(
+673            self.name,
+674            "AndSet",
+675            [
+676                MessageTemplate(
+677                    {
+678                        "target": {"const": self.conf["output state"]},
+679                        "command": {"const": "set state"},
+680                        "new state": {"type": "boolean"},
+681                    }
+682                ),
+683                MessageTemplate(
+684                    {"states": {"type": "array", "items": {"type": "string"}}}
+685                ),
+686            ],
+687            [
+688                (
+689                    [
+690                        MessageTemplate(
+691                            {
+692                                "target": {"const": self.name},
+693                                "command": {"const": "get state"},
+694                            }
+695                        )
+696                    ],
+697                    self._get_state,
+698                ),
+699                (
+700                    [
+701                        MessageTemplate(
+702                            {
+703                                "target": {"const": self.name},
+704                                "command": {"const": "get sources"},
+705                            }
+706                        )
+707                    ],
+708                    self._get_sources,
+709                ),
+710                (updates, self._update),
+711            ],
+712        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
748    async def run(self) -> None:
+749        """Run no code proactively."""
+750        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + OrSet(controlpi.baseplugin.BasePlugin): + + + +
+ +
753class OrSet(BasePlugin):
+754    """Set state based on disjunction of other states.
+755
+756    The "input states" configuration key gets an array of states used to
+757    determine the state in the "output state" configuration key:
+758    >>> import asyncio
+759    >>> import controlpi
+760    >>> asyncio.run(controlpi.test(
+761    ...     {"Test State 1": {"plugin": "State"},
+762    ...      "Test State 2": {"plugin": "State"},
+763    ...      "Test State 3": {"plugin": "State"},
+764    ...      "Test OrSet": {"plugin": "OrSet",
+765    ...                      "input states": ["Test State 1",
+766    ...                                       "Test State 2"],
+767    ...                      "output state": "Test State 3"}},
+768    ...     [{"target": "Test State 1", "command": "set state",
+769    ...       "new state": True},
+770    ...      {"target": "Test OrSet", "command": "get state"},
+771    ...      {"target": "Test State 2", "command": "set state",
+772    ...       "new state": True},
+773    ...      {"target": "Test State 1", "command": "set state",
+774    ...       "new state": False},
+775    ...      {"target": "Test OrSet", "command": "get sources"}]))
+776    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+777    test(): {'sender': '', 'event': 'registered', ...
+778    test(): {'sender': 'test()', 'target': 'Test State 1',
+779             'command': 'set state', 'new state': True}
+780    test(): {'sender': 'test()', 'target': 'Test OrSet',
+781             'command': 'get state'}
+782    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+783    test(): {'sender': 'test()', 'target': 'Test State 2',
+784             'command': 'set state', 'new state': True}
+785    test(): {'sender': 'Test OrSet', 'target': 'Test State 3',
+786             'command': 'set state', 'new state': False}
+787    test(): {'sender': 'Test OrSet', 'target': 'Test State 3',
+788             'command': 'set state', 'new state': True}
+789    test(): {'sender': 'test()', 'target': 'Test State 1',
+790             'command': 'set state', 'new state': False}
+791    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+792    test(): {'sender': 'Test State 3', 'state': False}
+793    test(): {'sender': 'Test State 3', 'event': 'changed', 'state': True}
+794    test(): {'sender': 'test()', 'target': 'Test OrSet',
+795             'command': 'get sources'}
+796    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+797    test(): {'sender': 'Test OrSet',
+798             'states': ['Test State 1', 'Test State 2']}
+799    """
+800
+801    CONF_SCHEMA = {
+802        "properties": {
+803            "input states": {"type": "array", "items": {"type": "string"}},
+804            "output state": {"type": "string"},
+805        },
+806        "required": ["input states", "output state"],
+807    }
+808    """Schema for OrSet plugin configuration.
+809
+810    Required configuration keys:
+811
+812    - 'input states': list of names of combined states.
+813    - 'output state': name of state to be set.
+814    """
+815
+816    def process_conf(self) -> None:
+817        """Register plugin as bus client."""
+818        updates: List[MessageTemplate] = []
+819        self.states: Dict[str, bool] = {}
+820        for state in self.conf["input states"]:
+821            updates.append(
+822                MessageTemplate(
+823                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+824                )
+825            )
+826            self.states[state] = False
+827        self.state: bool = any(self.states.values())
+828        self.bus.register(
+829            self.name,
+830            "AndSet",
+831            [
+832                MessageTemplate(
+833                    {
+834                        "target": {"const": self.conf["output state"]},
+835                        "command": {"const": "set state"},
+836                        "new state": {"type": "boolean"},
+837                    }
+838                ),
+839                MessageTemplate(
+840                    {"states": {"type": "array", "items": {"type": "string"}}}
+841                ),
+842            ],
+843            [
+844                (
+845                    [
+846                        MessageTemplate(
+847                            {
+848                                "target": {"const": self.name},
+849                                "command": {"const": "get state"},
+850                            }
+851                        )
+852                    ],
+853                    self._get_state,
+854                ),
+855                (
+856                    [
+857                        MessageTemplate(
+858                            {
+859                                "target": {"const": self.name},
+860                                "command": {"const": "get sources"},
+861                            }
+862                        )
+863                    ],
+864                    self._get_sources,
+865                ),
+866                (updates, self._update),
+867            ],
+868        )
+869
+870    async def _get_state(self, message: Message) -> None:
+871        await self.bus.send(
+872            Message(
+873                self.name,
+874                {
+875                    "target": self.conf["output state"],
+876                    "command": "set state",
+877                    "new state": self.state,
+878                },
+879            )
+880        )
+881
+882    async def _get_sources(self, message: Message) -> None:
+883        source_states = list(self.states.keys())
+884        await self.bus.send(Message(self.name, {"states": source_states}))
+885
+886    async def _update(self, message: Message) -> None:
+887        assert isinstance(message["sender"], str)
+888        assert isinstance(message["state"], bool)
+889        self.states[message["sender"]] = message["state"]
+890        new_state = any(self.states.values())
+891        if self.state != new_state:
+892            self.state = new_state
+893            await self.bus.send(
+894                Message(
+895                    self.name,
+896                    {
+897                        "target": self.conf["output state"],
+898                        "command": "set state",
+899                        "new state": self.state,
+900                    },
+901                )
+902            )
+903
+904    async def run(self) -> None:
+905        """Run no code proactively."""
+906        pass
+
+ + +

Set state based on disjunction of other states.

+ +

The "input states" configuration key gets an array of states used to +determine the state in the "output state" configuration key:

+ +
+
>>> import asyncio
+>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test State 1": {"plugin": "State"},
+...      "Test State 2": {"plugin": "State"},
+...      "Test State 3": {"plugin": "State"},
+...      "Test OrSet": {"plugin": "OrSet",
+...                      "input states": ["Test State 1",
+...                                       "Test State 2"],
+...                      "output state": "Test State 3"}},
+...     [{"target": "Test State 1", "command": "set state",
+...       "new state": True},
+...      {"target": "Test OrSet", "command": "get state"},
+...      {"target": "Test State 2", "command": "set state",
+...       "new state": True},
+...      {"target": "Test State 1", "command": "set state",
+...       "new state": False},
+...      {"target": "Test OrSet", "command": "get sources"}]))
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered', ...
+test(): {'sender': 'test()', 'target': 'Test State 1',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'test()', 'target': 'Test OrSet',
+         'command': 'get state'}
+test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test State 2',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'Test OrSet', 'target': 'Test State 3',
+         'command': 'set state', 'new state': False}
+test(): {'sender': 'Test OrSet', 'target': 'Test State 3',
+         'command': 'set state', 'new state': True}
+test(): {'sender': 'test()', 'target': 'Test State 1',
+         'command': 'set state', 'new state': False}
+test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
+test(): {'sender': 'Test State 3', 'state': False}
+test(): {'sender': 'Test State 3', 'event': 'changed', 'state': True}
+test(): {'sender': 'test()', 'target': 'Test OrSet',
+         'command': 'get sources'}
+test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
+test(): {'sender': 'Test OrSet',
+         'states': ['Test State 1', 'Test State 2']}
+
+
+
+ + +
+
+ CONF_SCHEMA = + + {'properties': {'input states': {'type': 'array', 'items': {'type': 'string'}}, 'output state': {'type': 'string'}}, 'required': ['input states', 'output state']} + + +
+ + +

Schema for OrSet plugin configuration.

+ +

Required configuration keys:

+ +
    +
  • 'input states': list of names of combined states.
  • +
  • 'output state': name of state to be set.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
816    def process_conf(self) -> None:
+817        """Register plugin as bus client."""
+818        updates: List[MessageTemplate] = []
+819        self.states: Dict[str, bool] = {}
+820        for state in self.conf["input states"]:
+821            updates.append(
+822                MessageTemplate(
+823                    {"sender": {"const": state}, "state": {"type": "boolean"}}
+824                )
+825            )
+826            self.states[state] = False
+827        self.state: bool = any(self.states.values())
+828        self.bus.register(
+829            self.name,
+830            "AndSet",
+831            [
+832                MessageTemplate(
+833                    {
+834                        "target": {"const": self.conf["output state"]},
+835                        "command": {"const": "set state"},
+836                        "new state": {"type": "boolean"},
+837                    }
+838                ),
+839                MessageTemplate(
+840                    {"states": {"type": "array", "items": {"type": "string"}}}
+841                ),
+842            ],
+843            [
+844                (
+845                    [
+846                        MessageTemplate(
+847                            {
+848                                "target": {"const": self.name},
+849                                "command": {"const": "get state"},
+850                            }
+851                        )
+852                    ],
+853                    self._get_state,
+854                ),
+855                (
+856                    [
+857                        MessageTemplate(
+858                            {
+859                                "target": {"const": self.name},
+860                                "command": {"const": "get sources"},
+861                            }
+862                        )
+863                    ],
+864                    self._get_sources,
+865                ),
+866                (updates, self._update),
+867            ],
+868        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
904    async def run(self) -> None:
+905        """Run no code proactively."""
+906        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/doc/api/controlpi_plugins/util.html b/doc/api/controlpi_plugins/util.html new file mode 100644 index 0000000..a2800c1 --- /dev/null +++ b/doc/api/controlpi_plugins/util.html @@ -0,0 +1,2680 @@ + + + + + + + controlpi_plugins.util API documentation + + + + + + + + + +
+
+

+controlpi_plugins.util

+ +

Provide utility plugins for all kinds of systems.

+ +
    +
  • Log logs messages on stdout.
  • +
  • Init sends list of messages on startup and on demand.
  • +
  • Execute sends configurable list of messages on demand.
  • +
  • Alias translates messages to an alias.
  • +
+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test Log": {"plugin": "Log",
+...                   "filter": [{"sender": {"const": "Test Alias"}}]},
+...      "Test Init": {"plugin": "Init",
+...                    "messages": [{"id": 42, "content": "Test Message"}]},
+...      "Test Alias": {"plugin": "Alias",
+...                     "from": {"sender": {"const": "Test Init"},
+...                              "id": {"const": 42}},
+...                     "to": {"id": "translated"}}}, []))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Log', 'plugin': 'Log',
+         'sends': [], 'receives': [{'sender': {'const': 'Test Alias'}}]}
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Init', 'plugin': 'Init',
+         'sends': [{'id': {'const': 42},
+                    'content': {'const': 'Test Message'}}],
+         'receives': [{'target': {'const': 'Test Init'},
+                       'command': {'const': 'execute'}}]}
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Alias', 'plugin': 'Alias',
+         'sends': [{'id': {'const': 'translated'}}],
+         'receives': [{'sender': {'const': 'Test Init'},
+                       'id': {'const': 42}}]}
+test(): {'sender': 'Test Init', 'id': 42,
+         'content': 'Test Message'}
+test(): {'sender': 'Test Alias', 'id': 'translated',
+         'content': 'Test Message'}
+Test Log: {'sender': 'Test Alias', 'id': 'translated',
+           'content': 'Test Message'}
+
+
+
+ + + + + +
  1"""Provide utility plugins for all kinds of systems.
+  2
+  3- Log logs messages on stdout.
+  4- Init sends list of messages on startup and on demand.
+  5- Execute sends configurable list of messages on demand.
+  6- Alias translates messages to an alias.
+  7
+  8>>> import controlpi
+  9>>> asyncio.run(controlpi.test(
+ 10...     {"Test Log": {"plugin": "Log",
+ 11...                   "filter": [{"sender": {"const": "Test Alias"}}]},
+ 12...      "Test Init": {"plugin": "Init",
+ 13...                    "messages": [{"id": 42, "content": "Test Message"}]},
+ 14...      "Test Alias": {"plugin": "Alias",
+ 15...                     "from": {"sender": {"const": "Test Init"},
+ 16...                              "id": {"const": 42}},
+ 17...                     "to": {"id": "translated"}}}, []))
+ 18... # doctest: +NORMALIZE_WHITESPACE
+ 19test(): {'sender': '', 'event': 'registered',
+ 20         'client': 'Test Log', 'plugin': 'Log',
+ 21         'sends': [], 'receives': [{'sender': {'const': 'Test Alias'}}]}
+ 22test(): {'sender': '', 'event': 'registered',
+ 23         'client': 'Test Init', 'plugin': 'Init',
+ 24         'sends': [{'id': {'const': 42},
+ 25                    'content': {'const': 'Test Message'}}],
+ 26         'receives': [{'target': {'const': 'Test Init'},
+ 27                       'command': {'const': 'execute'}}]}
+ 28test(): {'sender': '', 'event': 'registered',
+ 29         'client': 'Test Alias', 'plugin': 'Alias',
+ 30         'sends': [{'id': {'const': 'translated'}}],
+ 31         'receives': [{'sender': {'const': 'Test Init'},
+ 32                       'id': {'const': 42}}]}
+ 33test(): {'sender': 'Test Init', 'id': 42,
+ 34         'content': 'Test Message'}
+ 35test(): {'sender': 'Test Alias', 'id': 'translated',
+ 36         'content': 'Test Message'}
+ 37Test Log: {'sender': 'Test Alias', 'id': 'translated',
+ 38           'content': 'Test Message'}
+ 39"""
+ 40
+ 41import asyncio
+ 42from datetime import datetime
+ 43
+ 44from controlpi import BasePlugin, Message, MessageTemplate
+ 45
+ 46from typing import List
+ 47
+ 48
+ 49class Log(BasePlugin):
+ 50    """Log messages on stdout.
+ 51
+ 52    The "filter" configuration key gets a list of message templates defining
+ 53    the messages that should be logged by the plugin instance.
+ 54
+ 55    In the following example the first and third message match the given
+ 56    template and are logged by the instance "Test Log", while the second
+ 57    message does not match and is only logged by the test, but not by the
+ 58    Log instance:
+ 59    >>> import controlpi
+ 60    >>> asyncio.run(controlpi.test(
+ 61    ...     {"Test Log": {"plugin": "Log",
+ 62    ...                   "filter": [{"id": {"const": 42}}]}},
+ 63    ...     [{"id": 42, "message": "Test Message"},
+ 64    ...      {"id": 42.42, "message": "Second Message"},
+ 65    ...      {"id": 42, "message": "Third Message"}]))
+ 66    ... # doctest: +NORMALIZE_WHITESPACE
+ 67    test(): {'sender': '', 'event': 'registered',
+ 68             'client': 'Test Log', 'plugin': 'Log',
+ 69             'sends': [], 'receives': [{'id': {'const': 42}}]}
+ 70    test(): {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
+ 71    Test Log: {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
+ 72    test(): {'sender': 'test()', 'id': 42.42, 'message': 'Second Message'}
+ 73    test(): {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
+ 74    Test Log: {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
+ 75
+ 76    The "filter" key is required:
+ 77    >>> asyncio.run(controlpi.test(
+ 78    ...     {"Test Log": {"plugin": "Log"}}, []))
+ 79    data must contain ['filter'] properties
+ 80    Configuration for 'Test Log' is not valid.
+ 81
+ 82    The "filter" key has to contain a list of message templates, i.e.,
+ 83    JSON objects:
+ 84    >>> asyncio.run(controlpi.test(
+ 85    ...     {"Test Log": {"plugin": "Log",
+ 86    ...                   "filter": [42]}}, []))
+ 87    data.filter[0] must be object
+ 88    Configuration for 'Test Log' is not valid.
+ 89    """
+ 90
+ 91    CONF_SCHEMA = {
+ 92        "properties": {"filter": {"type": "array", "items": {"type": "object"}}},
+ 93        "required": ["filter"],
+ 94    }
+ 95    """Schema for Log plugin configuration.
+ 96
+ 97    Required configuration key:
+ 98
+ 99    - 'filter': list of message templates to be logged.
+100    """
+101
+102    def process_conf(self) -> None:
+103        """Register plugin as bus client."""
+104        self.bus.register(self.name, "Log", [], [(self.conf["filter"], self._log)])
+105
+106    async def _log(self, message: Message) -> None:
+107        print(f"{self.name}: {message}")
+108
+109    async def run(self) -> None:
+110        """Run no code proactively."""
+111        pass
+112
+113
+114class Init(BasePlugin):
+115    """Send list of messages on startup and on demand.
+116
+117    The "messages" configuration key gets a list of messages to be sent on
+118    startup. The same list is sent in reaction to a message with
+119    "target": NAME and "command": "execute".
+120
+121    In the example, the two configured messages are sent twice, once at
+122    startup and a second time in reaction to the "execute" command sent by
+123    the test:
+124    >>> import controlpi
+125    >>> asyncio.run(controlpi.test(
+126    ...     {"Test Init": {"plugin": "Init",
+127    ...                    "messages": [{"id": 42,
+128    ...                                  "content": "Test Message"},
+129    ...                                 {"id": 42.42,
+130    ...                                  "content": "Second Message"}]}},
+131    ...     [{"target": "Test Init", "command": "execute"}]))
+132    ... # doctest: +NORMALIZE_WHITESPACE
+133    test(): {'sender': '', 'event': 'registered',
+134             'client': 'Test Init', 'plugin': 'Init',
+135             'sends': [{'id': {'const': 42},
+136                        'content': {'const': 'Test Message'}},
+137                       {'id': {'const': 42.42},
+138                        'content': {'const': 'Second Message'}}],
+139             'receives': [{'target': {'const': 'Test Init'},
+140                           'command': {'const': 'execute'}}]}
+141    test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
+142    test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
+143    test(): {'sender': 'test()', 'target': 'Test Init', 'command': 'execute'}
+144    test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
+145    test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
+146
+147    The "messages" key is required:
+148    >>> asyncio.run(controlpi.test(
+149    ...     {"Test Init": {"plugin": "Init"}}, []))
+150    data must contain ['messages'] properties
+151    Configuration for 'Test Init' is not valid.
+152
+153    The "messages" key has to contain a list of (partial) messages, i.e.,
+154    JSON objects:
+155    >>> asyncio.run(controlpi.test(
+156    ...     {"Test Init": {"plugin": "Init",
+157    ...                    "messages": [42]}}, []))
+158    data.messages[0] must be object
+159    Configuration for 'Test Init' is not valid.
+160    """
+161
+162    CONF_SCHEMA = {
+163        "properties": {"messages": {"type": "array", "items": {"type": "object"}}},
+164        "required": ["messages"],
+165    }
+166    """Schema for Init plugin configuration.
+167
+168    Required configuration key:
+169
+170    - 'messages': list of messages to be sent.
+171    """
+172
+173    def process_conf(self) -> None:
+174        """Register plugin as bus client."""
+175        self.bus.register(
+176            self.name,
+177            "Init",
+178            [
+179                MessageTemplate.from_message(message)
+180                for message in self.conf["messages"]
+181            ],
+182            [
+183                (
+184                    [
+185                        MessageTemplate(
+186                            {
+187                                "target": {"const": self.name},
+188                                "command": {"const": "execute"},
+189                            }
+190                        )
+191                    ],
+192                    self._execute,
+193                )
+194            ],
+195        )
+196
+197    async def _execute(self, message: Message) -> None:
+198        for message in self.conf["messages"]:
+199            await self.bus.send(Message(self.name, message))
+200            # Give immediate reactions to messages opportunity to happen:
+201            await asyncio.sleep(0)
+202
+203    async def run(self) -> None:
+204        """Send configured messages on startup."""
+205        for message in self.conf["messages"]:
+206            await self.bus.send(Message(self.name, message))
+207
+208
+209class Execute(BasePlugin):
+210    """Send configurable list of messages on demand.
+211
+212    An Execute plugin instance receives two kinds of commands.
+213    The "set messages" command has a "messages" key with a list of (partial)
+214    messages, which are sent by the Execute instance in reaction to an
+215    "execute" command.
+216
+217    In the example, the first command sent by the test sets two messages,
+218    which are then sent in reaction to the second command sent by the test:
+219    >>> import controlpi
+220    >>> asyncio.run(controlpi.test(
+221    ...     {"Test Execute": {"plugin": "Execute"}},
+222    ...     [{"target": "Test Execute", "command": "set messages",
+223    ...       "messages": [{"id": 42, "content": "Test Message"},
+224    ...                    {"id": 42.42, "content": "Second Message"}]},
+225    ...      {"target": "Test Execute", "command": "execute"}]))
+226    ... # doctest: +NORMALIZE_WHITESPACE
+227    test(): {'sender': '', 'event': 'registered',
+228             'client': 'Test Execute', 'plugin': 'Execute',
+229             'sends': [{}],
+230             'receives': [{'target': {'const': 'Test Execute'},
+231                           'command': {'const': 'set messages'},
+232                           'messages': {'type': 'array',
+233                                        'items': {'type': 'object'}}},
+234                          {'target': {'const': 'Test Execute'},
+235                           'command': {'const': 'execute'}}]}
+236    test(): {'sender': 'test()', 'target': 'Test Execute',
+237             'command': 'set messages',
+238             'messages': [{'id': 42, 'content': 'Test Message'},
+239                          {'id': 42.42, 'content': 'Second Message'}]}
+240    test(): {'sender': 'test()', 'target': 'Test Execute',
+241             'command': 'execute'}
+242    test(): {'sender': 'Test Execute', 'id': 42,
+243             'content': 'Test Message'}
+244    test(): {'sender': 'Test Execute', 'id': 42.42,
+245             'content': 'Second Message'}
+246    """
+247
+248    CONF_SCHEMA = True
+249    """Schema for Execute plugin configuration.
+250
+251    There are no required or optional configuration keys.
+252    """
+253
+254    def process_conf(self) -> None:
+255        """Register plugin as bus client."""
+256        self.messages: List[Message] = []
+257        self.bus.register(
+258            self.name,
+259            "Execute",
+260            [MessageTemplate()],
+261            [
+262                (
+263                    [
+264                        MessageTemplate(
+265                            {
+266                                "target": {"const": self.name},
+267                                "command": {"const": "set messages"},
+268                                "messages": {
+269                                    "type": "array",
+270                                    "items": {"type": "object"},
+271                                },
+272                            }
+273                        )
+274                    ],
+275                    self._set_messages,
+276                ),
+277                (
+278                    [
+279                        MessageTemplate(
+280                            {
+281                                "target": {"const": self.name},
+282                                "command": {"const": "execute"},
+283                            }
+284                        )
+285                    ],
+286                    self._execute,
+287                ),
+288            ],
+289        )
+290
+291    async def _set_messages(self, message: Message) -> None:
+292        assert isinstance(message["messages"], list)
+293        self.messages = list(message["messages"])
+294
+295    async def _execute(self, message: Message) -> None:
+296        for message in self.messages:
+297            await self.bus.send(Message(self.name, message))
+298            # Give immediate reactions to messages opportunity to happen:
+299            await asyncio.sleep(0)
+300
+301    async def run(self) -> None:
+302        """Run no code proactively."""
+303        pass
+304
+305
+306class Alias(BasePlugin):
+307    """Translate messages to an alias.
+308
+309    The "from" configuration key gets a message template and the
+310    configuration key "to" a (partial) message. The "translate"
+311    configuration key contains pairs of message keys, where the "from"
+312    message key is translated to the "to" message key if present in the
+313    message.
+314
+315    All messages matching the "from" template are received by the Alias
+316    instance and a message translated by adding the keys and values of the
+317    "to" message and the translated key-value pairs according to
+318    "translate" is sent. Keys that are not "sender" and not modified by
+319    "to" or "translate" are retained.
+320
+321    In the example, the two messages sent by the test are translated by the
+322    Alias instance and the translated messages are sent by it preserving
+323    the "content" keys:
+324    >>> import controlpi
+325    >>> asyncio.run(controlpi.test(
+326    ...     {"Test Alias": {"plugin": "Alias",
+327    ...                     "from": {"id": {"const": 42}},
+328    ...                     "to": {"id": "translated"},
+329    ...                     "translate": [{'from': "old", "to": "new"}]}},
+330    ...     [{"id": 42, "content": "Test Message", "old": "content"},
+331    ...      {"id": 42, "content": "Second Message", "old": "content"}]))
+332    ... # doctest: +NORMALIZE_WHITESPACE
+333    test(): {'sender': '', 'event': 'registered',
+334             'client': 'Test Alias', 'plugin': 'Alias',
+335             'sends': [{'id': {'const': 'translated'}}],
+336             'receives': [{'id': {'const': 42}}]}
+337    test(): {'sender': 'test()', 'id': 42,
+338             'content': 'Test Message', 'old': 'content'}
+339    test(): {'sender': 'test()', 'id': 42,
+340             'content': 'Second Message', 'old': 'content'}
+341    test(): {'sender': 'Test Alias', 'id': 'translated',
+342             'content': 'Test Message', 'old': 'content', 'new': 'content'}
+343    test(): {'sender': 'Test Alias', 'id': 'translated',
+344             'content': 'Second Message', 'old': 'content', 'new': 'content'}
+345
+346    An Alias instance can also translate to a list of messages instead of
+347    a single message:
+348    >>> asyncio.run(controlpi.test(
+349    ...     {"Test Alias": {"plugin": "Alias",
+350    ...                     "from": {"id": {"const": 42}},
+351    ...                     "to": [{"id": "first"}, {"id": "second"}],
+352    ...                     "translate": [{'from': "old", "to": "new"}]}},
+353    ...     [{"id": 42, "content": "Test Message", "old": "content"}]))
+354    ... # doctest: +NORMALIZE_WHITESPACE
+355    test(): {'sender': '', 'event': 'registered',
+356             'client': 'Test Alias', 'plugin': 'Alias',
+357             'sends': [{'id': {'const': 'first'}},
+358                       {'id': {'const': 'second'}}],
+359             'receives': [{'id': {'const': 42}}]}
+360    test(): {'sender': 'test()', 'id': 42,
+361             'content': 'Test Message', 'old': 'content'}
+362    test(): {'sender': 'Test Alias', 'id': 'first',
+363             'content': 'Test Message', 'old': 'content', 'new': 'content'}
+364    test(): {'sender': 'Test Alias', 'id': 'second',
+365             'content': 'Test Message', 'old': 'content', 'new': 'content'}
+366
+367    The "from" key is required:
+368    >>> asyncio.run(controlpi.test(
+369    ...     {"Test Alias": {"plugin": "Alias"}}, []))
+370    data must contain ['from'] properties
+371    Configuration for 'Test Alias' is not valid.
+372
+373    The "from" key has to contain a message template and the "to" key a
+374    (partial) message, i.e., both have to be JSON objects:
+375    >>> asyncio.run(controlpi.test(
+376    ...     {"Test Alias": {"plugin": "Alias",
+377    ...                     "from": 42,
+378    ...                     "to": 42}}, []))
+379    data.from must be object
+380    Configuration for 'Test Alias' is not valid.
+381    >>> asyncio.run(controlpi.test(
+382    ...     {"Test Alias": {"plugin": "Alias",
+383    ...                     "from": {"id": {"const": 42}},
+384    ...                     "to": 42}}, []))
+385    data.to cannot be validated by any definition
+386    Configuration for 'Test Alias' is not valid.
+387    """
+388
+389    CONF_SCHEMA = {
+390        "properties": {
+391            "from": {"type": "object"},
+392            "to": {
+393                "anyOf": [
+394                    {"type": "object"},
+395                    {"type": "array", "items": {"type": "object"}},
+396                ]
+397            },
+398            "translate": {
+399                "type": "array",
+400                "items": {
+401                    "type": "object",
+402                    "properties": {
+403                        "from": {"type": "string"},
+404                        "to": {"type": "string"},
+405                    },
+406                },
+407            },
+408        },
+409        "required": ["from"],
+410    }
+411    """Schema for Alias plugin configuration.
+412
+413    Required configuration keys:
+414
+415    - 'from': template of messages to be translated.
+416
+417    Optional configuration keys:
+418
+419    - 'to': translated message(s) to be sent.
+420    - 'translate': array of pairs of keys to be translated.
+421    """
+422
+423    def process_conf(self) -> None:
+424        """Register plugin as bus client."""
+425        sends = []
+426        self._to = []
+427        if "to" in self.conf:
+428            if isinstance(self.conf["to"], list):
+429                self._to = self.conf["to"]
+430                for to in self.conf["to"]:
+431                    sends.append(MessageTemplate.from_message(to))
+432            else:
+433                self._to = [self.conf["to"]]
+434                sends.append(MessageTemplate.from_message(self.conf["to"]))
+435        self._translate = {}
+436        if "translate" in self.conf:
+437            for pair in self.conf["translate"]:
+438                self._translate[pair["from"]] = pair["to"]
+439        self.bus.register(
+440            self.name, "Alias", sends, [([self.conf["from"]], self._alias)]
+441        )
+442
+443    async def _alias(self, message: Message) -> None:
+444        # Prevent endless loop:
+445        if message["sender"] != self.name:
+446            for to in self._to:
+447                alias_message = Message(self.name, message)
+448                alias_message.update(to)
+449                for key in self._translate:
+450                    if key in message:
+451                        alias_message[self._translate[key]] = message[key]
+452                await self.bus.send(alias_message)
+453
+454    async def run(self) -> None:
+455        """Run no code proactively."""
+456        pass
+457
+458
+459class Counter(BasePlugin):
+460    """Count messages confirming to a given template.
+461
+462    The plugin counts messages confirming to the given template. The
+463    counter can be queried and reset by commands. The 'reset' command also
+464    queries the last count before the reset:
+465    >>> import controlpi
+466    >>> asyncio.run(controlpi.test(
+467    ...     {"Test Counter": {"plugin": "Counter",
+468    ...                       "count": {"id": {"const": 42}}}},
+469    ...     [{"target": "Test Counter", "command": "get count"},
+470    ...      {"id": 42}, {"id": 42}, {"id": 49},
+471    ...      {"target": "Test Counter", "command": "get count"},
+472    ...      {"id": 42}, {"id": 42}, {"id": 42},
+473    ...      {"target": "Test Counter", "command": "reset"},
+474    ...      {"target": "Test Counter", "command": "get count"},
+475    ...      {"id": 42}, {"id": 42}, {"id": 42},
+476    ...      {"target": "Test Counter", "command": "get count"}]))
+477    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+478    test(): {'sender': '', 'event': 'registered',
+479             'client': 'Test Counter', 'plugin': 'Counter',
+480             'sends': [{'count': {'type': 'integer'}}],
+481             'receives': [{'id': {'const': 42}},
+482                          {'target': {'const': 'Test Counter'},
+483                           'command': {'const': 'get count'}},
+484                          {'target': {'const': 'Test Counter'},
+485                           'command': {'const': 'reset'}}]}
+486    test(): {'sender': 'test()', 'target': 'Test Counter',
+487             'command': 'get count'}
+488    test(): {'sender': 'test()', 'id': 42}
+489    test(): {'sender': 'Test Counter', 'count': 0,
+490             'since': ..., 'until': ...}
+491    test(): {'sender': 'test()', 'id': 42}
+492    test(): {'sender': 'test()', 'id': 49}
+493    test(): {'sender': 'test()', 'target': 'Test Counter',
+494             'command': 'get count'}
+495    test(): {'sender': 'test()', 'id': 42}
+496    test(): {'sender': 'Test Counter', 'count': 2,
+497             'since': ..., 'until': ...}
+498    test(): {'sender': 'test()', 'id': 42}
+499    test(): {'sender': 'test()', 'id': 42}
+500    test(): {'sender': 'test()', 'target': 'Test Counter',
+501             'command': 'reset'}
+502    test(): {'sender': 'test()', 'target': 'Test Counter',
+503             'command': 'get count'}
+504    test(): {'sender': 'Test Counter', 'count': 5,
+505             'since': ..., 'until': ...}
+506    test(): {'sender': 'test()', 'id': 42}
+507    test(): {'sender': 'Test Counter', 'count': 0,
+508             'since': ..., 'until': ...}
+509    test(): {'sender': 'test()', 'id': 42}
+510    test(): {'sender': 'test()', 'id': 42}
+511    test(): {'sender': 'test()', 'target': 'Test Counter',
+512             'command': 'get count'}
+513    test(): {'sender': 'Test Counter', 'count': 3,
+514             'since': ..., 'until': ...}
+515    """
+516
+517    CONF_SCHEMA = {
+518        "properties": {
+519            "count": {"type": "object"},
+520            "date format": {"type": "string", "default": "%Y-%m-%d %H:%M:%S"},
+521        },
+522        "required": ["count"],
+523    }
+524    """Schema for Counter plugin configuration.
+525
+526    Required configuration key:
+527
+528    - 'count': template of messages to be counted.
+529    """
+530
+531    def process_conf(self) -> None:
+532        """Register plugin as bus client."""
+533        self._since = datetime.now().strftime(self.conf["date format"])
+534        self._counter = 0
+535        self.bus.register(
+536            self.name,
+537            "Counter",
+538            [MessageTemplate({"count": {"type": "integer"}})],
+539            [
+540                ([MessageTemplate(self.conf["count"])], self._count),
+541                (
+542                    [
+543                        MessageTemplate(
+544                            {
+545                                "target": {"const": self.name},
+546                                "command": {"const": "get count"},
+547                            }
+548                        )
+549                    ],
+550                    self._get_count,
+551                ),
+552                (
+553                    [
+554                        MessageTemplate(
+555                            {
+556                                "target": {"const": self.name},
+557                                "command": {"const": "reset"},
+558                            }
+559                        )
+560                    ],
+561                    self._reset,
+562                ),
+563            ],
+564        )
+565
+566    async def _count(self, message: Message) -> None:
+567        self._counter += 1
+568
+569    async def _get_count(self, message: Message) -> None:
+570        now = datetime.now().strftime(self.conf["date format"])
+571        await self.bus.send(
+572            Message(
+573                self.name, {"count": self._counter, "since": self._since, "until": now}
+574            )
+575        )
+576
+577    async def _reset(self, message: Message) -> None:
+578        now = datetime.now().strftime(self.conf["date format"])
+579        counter = self._counter
+580        self._counter = 0
+581        await self.bus.send(
+582            Message(self.name, {"count": counter, "since": self._since, "until": now})
+583        )
+584        self._since = now
+585
+586    async def run(self) -> None:
+587        """Run no code proactively."""
+588        pass
+589
+590
+591class Date(BasePlugin):
+592    """Send message with current date.
+593
+594    The plugin reacts to 'get date' commands by sending messages with
+595    a 'date' key:
+596    >>> import controlpi
+597    >>> asyncio.run(controlpi.test(
+598    ...     {"Test Date": {"plugin": "Date"}},
+599    ...     [{"target": "Test Date", "command": "get date"}]))
+600    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+601    test(): {'sender': '', 'event': 'registered',
+602             'client': 'Test Date', 'plugin': 'Date',
+603             'sends': [{'date': {'type': 'string'}}],
+604             'receives': [{'target': {'const': 'Test Date'},
+605                           'command': {'const': 'get date'}}]}
+606    test(): {'sender': 'test()', 'target': 'Test Date',
+607             'command': 'get date'}
+608    test(): {'sender': 'Test Date', 'date': ...}
+609
+610    The format of the date can be configured with the 'format'
+611    configuration key:
+612    >>> asyncio.run(controlpi.test(
+613    ...     {"Test Date": {"plugin": "Date",
+614    ...                    "format": "%Y%m%d%H%M%S%f"}},
+615    ...     [{"target": "Test Date", "command": "get date"}]))
+616    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+617    test(): {'sender': '', 'event': 'registered',
+618             'client': 'Test Date', 'plugin': 'Date',
+619             'sends': [{'date': {'type': 'string'}}],
+620             'receives': [{'target': {'const': 'Test Date'},
+621                           'command': {'const': 'get date'}}]}
+622    test(): {'sender': 'test()', 'target': 'Test Date',
+623             'command': 'get date'}
+624    test(): {'sender': 'Test Date', 'date': ...}
+625    """
+626
+627    CONF_SCHEMA = {
+628        "properties": {"format": {"type": "string", "default": "%Y-%m-%d %H:%M:%S"}}
+629    }
+630    """Schema for Date plugin configuration.
+631
+632    Optional configuration key:
+633
+634    - 'format': format for the sent datetime string.
+635                Default: '%Y-%m-%d %H:%M:%S'
+636    """
+637
+638    def process_conf(self) -> None:
+639        """Register plugin as bus client."""
+640        self.bus.register(
+641            self.name,
+642            "Date",
+643            [MessageTemplate({"date": {"type": "string"}})],
+644            [
+645                (
+646                    [
+647                        MessageTemplate(
+648                            {
+649                                "target": {"const": self.name},
+650                                "command": {"const": "get date"},
+651                            }
+652                        )
+653                    ],
+654                    self._date,
+655                )
+656            ],
+657        )
+658
+659    async def _date(self, message: Message) -> None:
+660        date = datetime.now().strftime(self.conf["format"])
+661        await self.bus.send(Message(self.name, {"date": date}))
+662
+663    async def run(self) -> None:
+664        """Run no code proactively."""
+665        pass
+
+ + +
+
+ +
+ + class + Log(controlpi.baseplugin.BasePlugin): + + + +
+ +
 50class Log(BasePlugin):
+ 51    """Log messages on stdout.
+ 52
+ 53    The "filter" configuration key gets a list of message templates defining
+ 54    the messages that should be logged by the plugin instance.
+ 55
+ 56    In the following example the first and third message match the given
+ 57    template and are logged by the instance "Test Log", while the second
+ 58    message does not match and is only logged by the test, but not by the
+ 59    Log instance:
+ 60    >>> import controlpi
+ 61    >>> asyncio.run(controlpi.test(
+ 62    ...     {"Test Log": {"plugin": "Log",
+ 63    ...                   "filter": [{"id": {"const": 42}}]}},
+ 64    ...     [{"id": 42, "message": "Test Message"},
+ 65    ...      {"id": 42.42, "message": "Second Message"},
+ 66    ...      {"id": 42, "message": "Third Message"}]))
+ 67    ... # doctest: +NORMALIZE_WHITESPACE
+ 68    test(): {'sender': '', 'event': 'registered',
+ 69             'client': 'Test Log', 'plugin': 'Log',
+ 70             'sends': [], 'receives': [{'id': {'const': 42}}]}
+ 71    test(): {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
+ 72    Test Log: {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
+ 73    test(): {'sender': 'test()', 'id': 42.42, 'message': 'Second Message'}
+ 74    test(): {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
+ 75    Test Log: {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
+ 76
+ 77    The "filter" key is required:
+ 78    >>> asyncio.run(controlpi.test(
+ 79    ...     {"Test Log": {"plugin": "Log"}}, []))
+ 80    data must contain ['filter'] properties
+ 81    Configuration for 'Test Log' is not valid.
+ 82
+ 83    The "filter" key has to contain a list of message templates, i.e.,
+ 84    JSON objects:
+ 85    >>> asyncio.run(controlpi.test(
+ 86    ...     {"Test Log": {"plugin": "Log",
+ 87    ...                   "filter": [42]}}, []))
+ 88    data.filter[0] must be object
+ 89    Configuration for 'Test Log' is not valid.
+ 90    """
+ 91
+ 92    CONF_SCHEMA = {
+ 93        "properties": {"filter": {"type": "array", "items": {"type": "object"}}},
+ 94        "required": ["filter"],
+ 95    }
+ 96    """Schema for Log plugin configuration.
+ 97
+ 98    Required configuration key:
+ 99
+100    - 'filter': list of message templates to be logged.
+101    """
+102
+103    def process_conf(self) -> None:
+104        """Register plugin as bus client."""
+105        self.bus.register(self.name, "Log", [], [(self.conf["filter"], self._log)])
+106
+107    async def _log(self, message: Message) -> None:
+108        print(f"{self.name}: {message}")
+109
+110    async def run(self) -> None:
+111        """Run no code proactively."""
+112        pass
+
+ + +

Log messages on stdout.

+ +

The "filter" configuration key gets a list of message templates defining +the messages that should be logged by the plugin instance.

+ +

In the following example the first and third message match the given +template and are logged by the instance "Test Log", while the second +message does not match and is only logged by the test, but not by the +Log instance:

+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test Log": {"plugin": "Log",
+...                   "filter": [{"id": {"const": 42}}]}},
+...     [{"id": 42, "message": "Test Message"},
+...      {"id": 42.42, "message": "Second Message"},
+...      {"id": 42, "message": "Third Message"}]))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Log', 'plugin': 'Log',
+         'sends': [], 'receives': [{'id': {'const': 42}}]}
+test(): {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
+Test Log: {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
+test(): {'sender': 'test()', 'id': 42.42, 'message': 'Second Message'}
+test(): {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
+Test Log: {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
+
+
+ +

The "filter" key is required:

+ +
+
>>> asyncio.run(controlpi.test(
+...     {"Test Log": {"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., +JSON objects:

+ +
+
>>> asyncio.run(controlpi.test(
+...     {"Test Log": {"plugin": "Log",
+...                   "filter": [42]}}, []))
+data.filter[0] must be object
+Configuration for 'Test Log' is not valid.
+
+
+
+ + +
+
+ CONF_SCHEMA = +{'properties': {'filter': {'type': 'array', 'items': {'type': 'object'}}}, 'required': ['filter']} + + +
+ + +

Schema for Log plugin configuration.

+ +

Required configuration key:

+ +
    +
  • 'filter': list of message templates to be logged.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
103    def process_conf(self) -> None:
+104        """Register plugin as bus client."""
+105        self.bus.register(self.name, "Log", [], [(self.conf["filter"], self._log)])
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
110    async def run(self) -> None:
+111        """Run no code proactively."""
+112        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + Init(controlpi.baseplugin.BasePlugin): + + + +
+ +
115class Init(BasePlugin):
+116    """Send list of messages on startup and on demand.
+117
+118    The "messages" configuration key gets a list of messages to be sent on
+119    startup. The same list is sent in reaction to a message with
+120    "target": NAME and "command": "execute".
+121
+122    In the example, the two configured messages are sent twice, once at
+123    startup and a second time in reaction to the "execute" command sent by
+124    the test:
+125    >>> import controlpi
+126    >>> asyncio.run(controlpi.test(
+127    ...     {"Test Init": {"plugin": "Init",
+128    ...                    "messages": [{"id": 42,
+129    ...                                  "content": "Test Message"},
+130    ...                                 {"id": 42.42,
+131    ...                                  "content": "Second Message"}]}},
+132    ...     [{"target": "Test Init", "command": "execute"}]))
+133    ... # doctest: +NORMALIZE_WHITESPACE
+134    test(): {'sender': '', 'event': 'registered',
+135             'client': 'Test Init', 'plugin': 'Init',
+136             'sends': [{'id': {'const': 42},
+137                        'content': {'const': 'Test Message'}},
+138                       {'id': {'const': 42.42},
+139                        'content': {'const': 'Second Message'}}],
+140             'receives': [{'target': {'const': 'Test Init'},
+141                           'command': {'const': 'execute'}}]}
+142    test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
+143    test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
+144    test(): {'sender': 'test()', 'target': 'Test Init', 'command': 'execute'}
+145    test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
+146    test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
+147
+148    The "messages" key is required:
+149    >>> asyncio.run(controlpi.test(
+150    ...     {"Test Init": {"plugin": "Init"}}, []))
+151    data must contain ['messages'] properties
+152    Configuration for 'Test Init' is not valid.
+153
+154    The "messages" key has to contain a list of (partial) messages, i.e.,
+155    JSON objects:
+156    >>> asyncio.run(controlpi.test(
+157    ...     {"Test Init": {"plugin": "Init",
+158    ...                    "messages": [42]}}, []))
+159    data.messages[0] must be object
+160    Configuration for 'Test Init' is not valid.
+161    """
+162
+163    CONF_SCHEMA = {
+164        "properties": {"messages": {"type": "array", "items": {"type": "object"}}},
+165        "required": ["messages"],
+166    }
+167    """Schema for Init plugin configuration.
+168
+169    Required configuration key:
+170
+171    - 'messages': list of messages to be sent.
+172    """
+173
+174    def process_conf(self) -> None:
+175        """Register plugin as bus client."""
+176        self.bus.register(
+177            self.name,
+178            "Init",
+179            [
+180                MessageTemplate.from_message(message)
+181                for message in self.conf["messages"]
+182            ],
+183            [
+184                (
+185                    [
+186                        MessageTemplate(
+187                            {
+188                                "target": {"const": self.name},
+189                                "command": {"const": "execute"},
+190                            }
+191                        )
+192                    ],
+193                    self._execute,
+194                )
+195            ],
+196        )
+197
+198    async def _execute(self, message: Message) -> None:
+199        for message in self.conf["messages"]:
+200            await self.bus.send(Message(self.name, message))
+201            # Give immediate reactions to messages opportunity to happen:
+202            await asyncio.sleep(0)
+203
+204    async def run(self) -> None:
+205        """Send configured messages on startup."""
+206        for message in self.conf["messages"]:
+207            await self.bus.send(Message(self.name, message))
+
+ + +

Send list of messages on startup and on demand.

+ +

The "messages" configuration key gets a list of messages to be sent on +startup. The same list is sent in reaction to a message with +"target": NAME and "command": "execute".

+ +

In the example, the two configured messages are sent twice, once at +startup and a second time in reaction to the "execute" command sent by +the test:

+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test Init": {"plugin": "Init",
+...                    "messages": [{"id": 42,
+...                                  "content": "Test Message"},
+...                                 {"id": 42.42,
+...                                  "content": "Second Message"}]}},
+...     [{"target": "Test Init", "command": "execute"}]))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Init', 'plugin': 'Init',
+         'sends': [{'id': {'const': 42},
+                    'content': {'const': 'Test Message'}},
+                   {'id': {'const': 42.42},
+                    'content': {'const': 'Second Message'}}],
+         'receives': [{'target': {'const': 'Test Init'},
+                       'command': {'const': 'execute'}}]}
+test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
+test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
+test(): {'sender': 'test()', 'target': 'Test Init', 'command': 'execute'}
+test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
+test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
+
+
+ +

The "messages" key is required:

+ +
+
>>> asyncio.run(controlpi.test(
+...     {"Test Init": {"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., +JSON objects:

+ +
+
>>> asyncio.run(controlpi.test(
+...     {"Test Init": {"plugin": "Init",
+...                    "messages": [42]}}, []))
+data.messages[0] must be object
+Configuration for 'Test Init' is not valid.
+
+
+
+ + +
+
+ CONF_SCHEMA = + + {'properties': {'messages': {'type': 'array', 'items': {'type': 'object'}}}, 'required': ['messages']} + + +
+ + +

Schema for Init plugin configuration.

+ +

Required configuration key:

+ +
    +
  • 'messages': list of messages to be sent.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
174    def process_conf(self) -> None:
+175        """Register plugin as bus client."""
+176        self.bus.register(
+177            self.name,
+178            "Init",
+179            [
+180                MessageTemplate.from_message(message)
+181                for message in self.conf["messages"]
+182            ],
+183            [
+184                (
+185                    [
+186                        MessageTemplate(
+187                            {
+188                                "target": {"const": self.name},
+189                                "command": {"const": "execute"},
+190                            }
+191                        )
+192                    ],
+193                    self._execute,
+194                )
+195            ],
+196        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
204    async def run(self) -> None:
+205        """Send configured messages on startup."""
+206        for message in self.conf["messages"]:
+207            await self.bus.send(Message(self.name, message))
+
+ + +

Send configured messages on startup.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + Execute(controlpi.baseplugin.BasePlugin): + + + +
+ +
210class Execute(BasePlugin):
+211    """Send configurable list of messages on demand.
+212
+213    An Execute plugin instance receives two kinds of commands.
+214    The "set messages" command has a "messages" key with a list of (partial)
+215    messages, which are sent by the Execute instance in reaction to an
+216    "execute" command.
+217
+218    In the example, the first command sent by the test sets two messages,
+219    which are then sent in reaction to the second command sent by the test:
+220    >>> import controlpi
+221    >>> asyncio.run(controlpi.test(
+222    ...     {"Test Execute": {"plugin": "Execute"}},
+223    ...     [{"target": "Test Execute", "command": "set messages",
+224    ...       "messages": [{"id": 42, "content": "Test Message"},
+225    ...                    {"id": 42.42, "content": "Second Message"}]},
+226    ...      {"target": "Test Execute", "command": "execute"}]))
+227    ... # doctest: +NORMALIZE_WHITESPACE
+228    test(): {'sender': '', 'event': 'registered',
+229             'client': 'Test Execute', 'plugin': 'Execute',
+230             'sends': [{}],
+231             'receives': [{'target': {'const': 'Test Execute'},
+232                           'command': {'const': 'set messages'},
+233                           'messages': {'type': 'array',
+234                                        'items': {'type': 'object'}}},
+235                          {'target': {'const': 'Test Execute'},
+236                           'command': {'const': 'execute'}}]}
+237    test(): {'sender': 'test()', 'target': 'Test Execute',
+238             'command': 'set messages',
+239             'messages': [{'id': 42, 'content': 'Test Message'},
+240                          {'id': 42.42, 'content': 'Second Message'}]}
+241    test(): {'sender': 'test()', 'target': 'Test Execute',
+242             'command': 'execute'}
+243    test(): {'sender': 'Test Execute', 'id': 42,
+244             'content': 'Test Message'}
+245    test(): {'sender': 'Test Execute', 'id': 42.42,
+246             'content': 'Second Message'}
+247    """
+248
+249    CONF_SCHEMA = True
+250    """Schema for Execute plugin configuration.
+251
+252    There are no required or optional configuration keys.
+253    """
+254
+255    def process_conf(self) -> None:
+256        """Register plugin as bus client."""
+257        self.messages: List[Message] = []
+258        self.bus.register(
+259            self.name,
+260            "Execute",
+261            [MessageTemplate()],
+262            [
+263                (
+264                    [
+265                        MessageTemplate(
+266                            {
+267                                "target": {"const": self.name},
+268                                "command": {"const": "set messages"},
+269                                "messages": {
+270                                    "type": "array",
+271                                    "items": {"type": "object"},
+272                                },
+273                            }
+274                        )
+275                    ],
+276                    self._set_messages,
+277                ),
+278                (
+279                    [
+280                        MessageTemplate(
+281                            {
+282                                "target": {"const": self.name},
+283                                "command": {"const": "execute"},
+284                            }
+285                        )
+286                    ],
+287                    self._execute,
+288                ),
+289            ],
+290        )
+291
+292    async def _set_messages(self, message: Message) -> None:
+293        assert isinstance(message["messages"], list)
+294        self.messages = list(message["messages"])
+295
+296    async def _execute(self, message: Message) -> None:
+297        for message in self.messages:
+298            await self.bus.send(Message(self.name, message))
+299            # Give immediate reactions to messages opportunity to happen:
+300            await asyncio.sleep(0)
+301
+302    async def run(self) -> None:
+303        """Run no code proactively."""
+304        pass
+
+ + +

Send configurable list of messages on demand.

+ +

An Execute plugin instance receives two kinds of commands. +The "set messages" command has a "messages" key with a list of (partial) +messages, which are sent by the Execute instance in reaction to an +"execute" command.

+ +

In the example, the first command sent by the test sets two messages, +which are then sent in reaction to the second command sent by the test:

+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test Execute": {"plugin": "Execute"}},
+...     [{"target": "Test Execute", "command": "set messages",
+...       "messages": [{"id": 42, "content": "Test Message"},
+...                    {"id": 42.42, "content": "Second Message"}]},
+...      {"target": "Test Execute", "command": "execute"}]))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Execute', 'plugin': 'Execute',
+         'sends': [{}],
+         'receives': [{'target': {'const': 'Test Execute'},
+                       'command': {'const': 'set messages'},
+                       'messages': {'type': 'array',
+                                    'items': {'type': 'object'}}},
+                      {'target': {'const': 'Test Execute'},
+                       'command': {'const': 'execute'}}]}
+test(): {'sender': 'test()', 'target': 'Test Execute',
+         'command': 'set messages',
+         'messages': [{'id': 42, 'content': 'Test Message'},
+                      {'id': 42.42, 'content': 'Second Message'}]}
+test(): {'sender': 'test()', 'target': 'Test Execute',
+         'command': 'execute'}
+test(): {'sender': 'Test Execute', 'id': 42,
+         'content': 'Test Message'}
+test(): {'sender': 'Test Execute', 'id': 42.42,
+         'content': 'Second Message'}
+
+
+
+ + +
+
+ CONF_SCHEMA = +True + + +
+ + +

Schema for Execute plugin configuration.

+ +

There are no required or optional configuration keys.

+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
255    def process_conf(self) -> None:
+256        """Register plugin as bus client."""
+257        self.messages: List[Message] = []
+258        self.bus.register(
+259            self.name,
+260            "Execute",
+261            [MessageTemplate()],
+262            [
+263                (
+264                    [
+265                        MessageTemplate(
+266                            {
+267                                "target": {"const": self.name},
+268                                "command": {"const": "set messages"},
+269                                "messages": {
+270                                    "type": "array",
+271                                    "items": {"type": "object"},
+272                                },
+273                            }
+274                        )
+275                    ],
+276                    self._set_messages,
+277                ),
+278                (
+279                    [
+280                        MessageTemplate(
+281                            {
+282                                "target": {"const": self.name},
+283                                "command": {"const": "execute"},
+284                            }
+285                        )
+286                    ],
+287                    self._execute,
+288                ),
+289            ],
+290        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
302    async def run(self) -> None:
+303        """Run no code proactively."""
+304        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + Alias(controlpi.baseplugin.BasePlugin): + + + +
+ +
307class Alias(BasePlugin):
+308    """Translate messages to an alias.
+309
+310    The "from" configuration key gets a message template and the
+311    configuration key "to" a (partial) message. The "translate"
+312    configuration key contains pairs of message keys, where the "from"
+313    message key is translated to the "to" message key if present in the
+314    message.
+315
+316    All messages matching the "from" template are received by the Alias
+317    instance and a message translated by adding the keys and values of the
+318    "to" message and the translated key-value pairs according to
+319    "translate" is sent. Keys that are not "sender" and not modified by
+320    "to" or "translate" are retained.
+321
+322    In the example, the two messages sent by the test are translated by the
+323    Alias instance and the translated messages are sent by it preserving
+324    the "content" keys:
+325    >>> import controlpi
+326    >>> asyncio.run(controlpi.test(
+327    ...     {"Test Alias": {"plugin": "Alias",
+328    ...                     "from": {"id": {"const": 42}},
+329    ...                     "to": {"id": "translated"},
+330    ...                     "translate": [{'from': "old", "to": "new"}]}},
+331    ...     [{"id": 42, "content": "Test Message", "old": "content"},
+332    ...      {"id": 42, "content": "Second Message", "old": "content"}]))
+333    ... # doctest: +NORMALIZE_WHITESPACE
+334    test(): {'sender': '', 'event': 'registered',
+335             'client': 'Test Alias', 'plugin': 'Alias',
+336             'sends': [{'id': {'const': 'translated'}}],
+337             'receives': [{'id': {'const': 42}}]}
+338    test(): {'sender': 'test()', 'id': 42,
+339             'content': 'Test Message', 'old': 'content'}
+340    test(): {'sender': 'test()', 'id': 42,
+341             'content': 'Second Message', 'old': 'content'}
+342    test(): {'sender': 'Test Alias', 'id': 'translated',
+343             'content': 'Test Message', 'old': 'content', 'new': 'content'}
+344    test(): {'sender': 'Test Alias', 'id': 'translated',
+345             'content': 'Second Message', 'old': 'content', 'new': 'content'}
+346
+347    An Alias instance can also translate to a list of messages instead of
+348    a single message:
+349    >>> asyncio.run(controlpi.test(
+350    ...     {"Test Alias": {"plugin": "Alias",
+351    ...                     "from": {"id": {"const": 42}},
+352    ...                     "to": [{"id": "first"}, {"id": "second"}],
+353    ...                     "translate": [{'from': "old", "to": "new"}]}},
+354    ...     [{"id": 42, "content": "Test Message", "old": "content"}]))
+355    ... # doctest: +NORMALIZE_WHITESPACE
+356    test(): {'sender': '', 'event': 'registered',
+357             'client': 'Test Alias', 'plugin': 'Alias',
+358             'sends': [{'id': {'const': 'first'}},
+359                       {'id': {'const': 'second'}}],
+360             'receives': [{'id': {'const': 42}}]}
+361    test(): {'sender': 'test()', 'id': 42,
+362             'content': 'Test Message', 'old': 'content'}
+363    test(): {'sender': 'Test Alias', 'id': 'first',
+364             'content': 'Test Message', 'old': 'content', 'new': 'content'}
+365    test(): {'sender': 'Test Alias', 'id': 'second',
+366             'content': 'Test Message', 'old': 'content', 'new': 'content'}
+367
+368    The "from" key is required:
+369    >>> asyncio.run(controlpi.test(
+370    ...     {"Test Alias": {"plugin": "Alias"}}, []))
+371    data must contain ['from'] properties
+372    Configuration for 'Test Alias' is not valid.
+373
+374    The "from" key has to contain a message template and the "to" key a
+375    (partial) message, i.e., both have to be JSON objects:
+376    >>> asyncio.run(controlpi.test(
+377    ...     {"Test Alias": {"plugin": "Alias",
+378    ...                     "from": 42,
+379    ...                     "to": 42}}, []))
+380    data.from must be object
+381    Configuration for 'Test Alias' is not valid.
+382    >>> asyncio.run(controlpi.test(
+383    ...     {"Test Alias": {"plugin": "Alias",
+384    ...                     "from": {"id": {"const": 42}},
+385    ...                     "to": 42}}, []))
+386    data.to cannot be validated by any definition
+387    Configuration for 'Test Alias' is not valid.
+388    """
+389
+390    CONF_SCHEMA = {
+391        "properties": {
+392            "from": {"type": "object"},
+393            "to": {
+394                "anyOf": [
+395                    {"type": "object"},
+396                    {"type": "array", "items": {"type": "object"}},
+397                ]
+398            },
+399            "translate": {
+400                "type": "array",
+401                "items": {
+402                    "type": "object",
+403                    "properties": {
+404                        "from": {"type": "string"},
+405                        "to": {"type": "string"},
+406                    },
+407                },
+408            },
+409        },
+410        "required": ["from"],
+411    }
+412    """Schema for Alias plugin configuration.
+413
+414    Required configuration keys:
+415
+416    - 'from': template of messages to be translated.
+417
+418    Optional configuration keys:
+419
+420    - 'to': translated message(s) to be sent.
+421    - 'translate': array of pairs of keys to be translated.
+422    """
+423
+424    def process_conf(self) -> None:
+425        """Register plugin as bus client."""
+426        sends = []
+427        self._to = []
+428        if "to" in self.conf:
+429            if isinstance(self.conf["to"], list):
+430                self._to = self.conf["to"]
+431                for to in self.conf["to"]:
+432                    sends.append(MessageTemplate.from_message(to))
+433            else:
+434                self._to = [self.conf["to"]]
+435                sends.append(MessageTemplate.from_message(self.conf["to"]))
+436        self._translate = {}
+437        if "translate" in self.conf:
+438            for pair in self.conf["translate"]:
+439                self._translate[pair["from"]] = pair["to"]
+440        self.bus.register(
+441            self.name, "Alias", sends, [([self.conf["from"]], self._alias)]
+442        )
+443
+444    async def _alias(self, message: Message) -> None:
+445        # Prevent endless loop:
+446        if message["sender"] != self.name:
+447            for to in self._to:
+448                alias_message = Message(self.name, message)
+449                alias_message.update(to)
+450                for key in self._translate:
+451                    if key in message:
+452                        alias_message[self._translate[key]] = message[key]
+453                await self.bus.send(alias_message)
+454
+455    async def run(self) -> None:
+456        """Run no code proactively."""
+457        pass
+
+ + +

Translate messages to an alias.

+ +

The "from" configuration key gets a message template and the +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 +the "content" keys:

+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test Alias": {"plugin": "Alias",
+...                     "from": {"id": {"const": 42}},
+...                     "to": {"id": "translated"},
+...                     "translate": [{'from': "old", "to": "new"}]}},
+...     [{"id": 42, "content": "Test Message", "old": "content"},
+...      {"id": 42, "content": "Second Message", "old": "content"}]))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Alias', 'plugin': 'Alias',
+         'sends': [{'id': {'const': 'translated'}}],
+         'receives': [{'id': {'const': 42}}]}
+test(): {'sender': 'test()', 'id': 42,
+         'content': 'Test Message', 'old': 'content'}
+test(): {'sender': 'test()', 'id': 42,
+         'content': 'Second Message', 'old': 'content'}
+test(): {'sender': 'Test Alias', 'id': 'translated',
+         'content': 'Test Message', 'old': 'content', 'new': 'content'}
+test(): {'sender': 'Test Alias', 'id': 'translated',
+         'content': 'Second Message', 'old': 'content', 'new': 'content'}
+
+
+ +

An Alias instance can also translate to a list of messages instead of +a single message:

+ +
+
>>> asyncio.run(controlpi.test(
+...     {"Test Alias": {"plugin": "Alias",
+...                     "from": {"id": {"const": 42}},
+...                     "to": [{"id": "first"}, {"id": "second"}],
+...                     "translate": [{'from': "old", "to": "new"}]}},
+...     [{"id": 42, "content": "Test Message", "old": "content"}]))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Alias', 'plugin': 'Alias',
+         'sends': [{'id': {'const': 'first'}},
+                   {'id': {'const': 'second'}}],
+         'receives': [{'id': {'const': 42}}]}
+test(): {'sender': 'test()', 'id': 42,
+         'content': 'Test Message', 'old': 'content'}
+test(): {'sender': 'Test Alias', 'id': 'first',
+         'content': 'Test Message', 'old': 'content', 'new': 'content'}
+test(): {'sender': 'Test Alias', 'id': 'second',
+         'content': 'Test Message', 'old': 'content', 'new': 'content'}
+
+
+ +

The "from" key is required:

+ +
+
>>> asyncio.run(controlpi.test(
+...     {"Test Alias": {"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 +(partial) message, i.e., both have to be JSON objects:

+ +
+
>>> asyncio.run(controlpi.test(
+...     {"Test Alias": {"plugin": "Alias",
+...                     "from": 42,
+...                     "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 cannot be validated by any definition
+Configuration for 'Test Alias' is not valid.
+
+
+
+ + +
+
+ CONF_SCHEMA = + + {'properties': {'from': {'type': 'object'}, 'to': {'anyOf': [{'type': 'object'}, {'type': 'array', 'items': {'type': 'object'}}]}, 'translate': {'type': 'array', '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(s) to be sent.
  • +
  • 'translate': array of pairs of keys to be translated.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
424    def process_conf(self) -> None:
+425        """Register plugin as bus client."""
+426        sends = []
+427        self._to = []
+428        if "to" in self.conf:
+429            if isinstance(self.conf["to"], list):
+430                self._to = self.conf["to"]
+431                for to in self.conf["to"]:
+432                    sends.append(MessageTemplate.from_message(to))
+433            else:
+434                self._to = [self.conf["to"]]
+435                sends.append(MessageTemplate.from_message(self.conf["to"]))
+436        self._translate = {}
+437        if "translate" in self.conf:
+438            for pair in self.conf["translate"]:
+439                self._translate[pair["from"]] = pair["to"]
+440        self.bus.register(
+441            self.name, "Alias", sends, [([self.conf["from"]], self._alias)]
+442        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
455    async def run(self) -> None:
+456        """Run no code proactively."""
+457        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + Counter(controlpi.baseplugin.BasePlugin): + + + +
+ +
460class Counter(BasePlugin):
+461    """Count messages confirming to a given template.
+462
+463    The plugin counts messages confirming to the given template. The
+464    counter can be queried and reset by commands. The 'reset' command also
+465    queries the last count before the reset:
+466    >>> import controlpi
+467    >>> asyncio.run(controlpi.test(
+468    ...     {"Test Counter": {"plugin": "Counter",
+469    ...                       "count": {"id": {"const": 42}}}},
+470    ...     [{"target": "Test Counter", "command": "get count"},
+471    ...      {"id": 42}, {"id": 42}, {"id": 49},
+472    ...      {"target": "Test Counter", "command": "get count"},
+473    ...      {"id": 42}, {"id": 42}, {"id": 42},
+474    ...      {"target": "Test Counter", "command": "reset"},
+475    ...      {"target": "Test Counter", "command": "get count"},
+476    ...      {"id": 42}, {"id": 42}, {"id": 42},
+477    ...      {"target": "Test Counter", "command": "get count"}]))
+478    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+479    test(): {'sender': '', 'event': 'registered',
+480             'client': 'Test Counter', 'plugin': 'Counter',
+481             'sends': [{'count': {'type': 'integer'}}],
+482             'receives': [{'id': {'const': 42}},
+483                          {'target': {'const': 'Test Counter'},
+484                           'command': {'const': 'get count'}},
+485                          {'target': {'const': 'Test Counter'},
+486                           'command': {'const': 'reset'}}]}
+487    test(): {'sender': 'test()', 'target': 'Test Counter',
+488             'command': 'get count'}
+489    test(): {'sender': 'test()', 'id': 42}
+490    test(): {'sender': 'Test Counter', 'count': 0,
+491             'since': ..., 'until': ...}
+492    test(): {'sender': 'test()', 'id': 42}
+493    test(): {'sender': 'test()', 'id': 49}
+494    test(): {'sender': 'test()', 'target': 'Test Counter',
+495             'command': 'get count'}
+496    test(): {'sender': 'test()', 'id': 42}
+497    test(): {'sender': 'Test Counter', 'count': 2,
+498             'since': ..., 'until': ...}
+499    test(): {'sender': 'test()', 'id': 42}
+500    test(): {'sender': 'test()', 'id': 42}
+501    test(): {'sender': 'test()', 'target': 'Test Counter',
+502             'command': 'reset'}
+503    test(): {'sender': 'test()', 'target': 'Test Counter',
+504             'command': 'get count'}
+505    test(): {'sender': 'Test Counter', 'count': 5,
+506             'since': ..., 'until': ...}
+507    test(): {'sender': 'test()', 'id': 42}
+508    test(): {'sender': 'Test Counter', 'count': 0,
+509             'since': ..., 'until': ...}
+510    test(): {'sender': 'test()', 'id': 42}
+511    test(): {'sender': 'test()', 'id': 42}
+512    test(): {'sender': 'test()', 'target': 'Test Counter',
+513             'command': 'get count'}
+514    test(): {'sender': 'Test Counter', 'count': 3,
+515             'since': ..., 'until': ...}
+516    """
+517
+518    CONF_SCHEMA = {
+519        "properties": {
+520            "count": {"type": "object"},
+521            "date format": {"type": "string", "default": "%Y-%m-%d %H:%M:%S"},
+522        },
+523        "required": ["count"],
+524    }
+525    """Schema for Counter plugin configuration.
+526
+527    Required configuration key:
+528
+529    - 'count': template of messages to be counted.
+530    """
+531
+532    def process_conf(self) -> None:
+533        """Register plugin as bus client."""
+534        self._since = datetime.now().strftime(self.conf["date format"])
+535        self._counter = 0
+536        self.bus.register(
+537            self.name,
+538            "Counter",
+539            [MessageTemplate({"count": {"type": "integer"}})],
+540            [
+541                ([MessageTemplate(self.conf["count"])], self._count),
+542                (
+543                    [
+544                        MessageTemplate(
+545                            {
+546                                "target": {"const": self.name},
+547                                "command": {"const": "get count"},
+548                            }
+549                        )
+550                    ],
+551                    self._get_count,
+552                ),
+553                (
+554                    [
+555                        MessageTemplate(
+556                            {
+557                                "target": {"const": self.name},
+558                                "command": {"const": "reset"},
+559                            }
+560                        )
+561                    ],
+562                    self._reset,
+563                ),
+564            ],
+565        )
+566
+567    async def _count(self, message: Message) -> None:
+568        self._counter += 1
+569
+570    async def _get_count(self, message: Message) -> None:
+571        now = datetime.now().strftime(self.conf["date format"])
+572        await self.bus.send(
+573            Message(
+574                self.name, {"count": self._counter, "since": self._since, "until": now}
+575            )
+576        )
+577
+578    async def _reset(self, message: Message) -> None:
+579        now = datetime.now().strftime(self.conf["date format"])
+580        counter = self._counter
+581        self._counter = 0
+582        await self.bus.send(
+583            Message(self.name, {"count": counter, "since": self._since, "until": now})
+584        )
+585        self._since = now
+586
+587    async def run(self) -> None:
+588        """Run no code proactively."""
+589        pass
+
+ + +

Count messages confirming to a given template.

+ +

The plugin counts messages confirming to the given template. The +counter can be queried and reset by commands. The 'reset' command also +queries the last count before the reset:

+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test Counter": {"plugin": "Counter",
+...                       "count": {"id": {"const": 42}}}},
+...     [{"target": "Test Counter", "command": "get count"},
+...      {"id": 42}, {"id": 42}, {"id": 49},
+...      {"target": "Test Counter", "command": "get count"},
+...      {"id": 42}, {"id": 42}, {"id": 42},
+...      {"target": "Test Counter", "command": "reset"},
+...      {"target": "Test Counter", "command": "get count"},
+...      {"id": 42}, {"id": 42}, {"id": 42},
+...      {"target": "Test Counter", "command": "get count"}]))
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Counter', 'plugin': 'Counter',
+         'sends': [{'count': {'type': 'integer'}}],
+         'receives': [{'id': {'const': 42}},
+                      {'target': {'const': 'Test Counter'},
+                       'command': {'const': 'get count'}},
+                      {'target': {'const': 'Test Counter'},
+                       'command': {'const': 'reset'}}]}
+test(): {'sender': 'test()', 'target': 'Test Counter',
+         'command': 'get count'}
+test(): {'sender': 'test()', 'id': 42}
+test(): {'sender': 'Test Counter', 'count': 0,
+         'since': ..., 'until': ...}
+test(): {'sender': 'test()', 'id': 42}
+test(): {'sender': 'test()', 'id': 49}
+test(): {'sender': 'test()', 'target': 'Test Counter',
+         'command': 'get count'}
+test(): {'sender': 'test()', 'id': 42}
+test(): {'sender': 'Test Counter', 'count': 2,
+         'since': ..., 'until': ...}
+test(): {'sender': 'test()', 'id': 42}
+test(): {'sender': 'test()', 'id': 42}
+test(): {'sender': 'test()', 'target': 'Test Counter',
+         'command': 'reset'}
+test(): {'sender': 'test()', 'target': 'Test Counter',
+         'command': 'get count'}
+test(): {'sender': 'Test Counter', 'count': 5,
+         'since': ..., 'until': ...}
+test(): {'sender': 'test()', 'id': 42}
+test(): {'sender': 'Test Counter', 'count': 0,
+         'since': ..., 'until': ...}
+test(): {'sender': 'test()', 'id': 42}
+test(): {'sender': 'test()', 'id': 42}
+test(): {'sender': 'test()', 'target': 'Test Counter',
+         'command': 'get count'}
+test(): {'sender': 'Test Counter', 'count': 3,
+         'since': ..., 'until': ...}
+
+
+
+ + +
+
+ CONF_SCHEMA = + + {'properties': {'count': {'type': 'object'}, 'date format': {'type': 'string', 'default': '%Y-%m-%d %H:%M:%S'}}, 'required': ['count']} + + +
+ + +

Schema for Counter plugin configuration.

+ +

Required configuration key:

+ +
    +
  • 'count': template of messages to be counted.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
532    def process_conf(self) -> None:
+533        """Register plugin as bus client."""
+534        self._since = datetime.now().strftime(self.conf["date format"])
+535        self._counter = 0
+536        self.bus.register(
+537            self.name,
+538            "Counter",
+539            [MessageTemplate({"count": {"type": "integer"}})],
+540            [
+541                ([MessageTemplate(self.conf["count"])], self._count),
+542                (
+543                    [
+544                        MessageTemplate(
+545                            {
+546                                "target": {"const": self.name},
+547                                "command": {"const": "get count"},
+548                            }
+549                        )
+550                    ],
+551                    self._get_count,
+552                ),
+553                (
+554                    [
+555                        MessageTemplate(
+556                            {
+557                                "target": {"const": self.name},
+558                                "command": {"const": "reset"},
+559                            }
+560                        )
+561                    ],
+562                    self._reset,
+563                ),
+564            ],
+565        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
587    async def run(self) -> None:
+588        """Run no code proactively."""
+589        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + Date(controlpi.baseplugin.BasePlugin): + + + +
+ +
592class Date(BasePlugin):
+593    """Send message with current date.
+594
+595    The plugin reacts to 'get date' commands by sending messages with
+596    a 'date' key:
+597    >>> import controlpi
+598    >>> asyncio.run(controlpi.test(
+599    ...     {"Test Date": {"plugin": "Date"}},
+600    ...     [{"target": "Test Date", "command": "get date"}]))
+601    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+602    test(): {'sender': '', 'event': 'registered',
+603             'client': 'Test Date', 'plugin': 'Date',
+604             'sends': [{'date': {'type': 'string'}}],
+605             'receives': [{'target': {'const': 'Test Date'},
+606                           'command': {'const': 'get date'}}]}
+607    test(): {'sender': 'test()', 'target': 'Test Date',
+608             'command': 'get date'}
+609    test(): {'sender': 'Test Date', 'date': ...}
+610
+611    The format of the date can be configured with the 'format'
+612    configuration key:
+613    >>> asyncio.run(controlpi.test(
+614    ...     {"Test Date": {"plugin": "Date",
+615    ...                    "format": "%Y%m%d%H%M%S%f"}},
+616    ...     [{"target": "Test Date", "command": "get date"}]))
+617    ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+618    test(): {'sender': '', 'event': 'registered',
+619             'client': 'Test Date', 'plugin': 'Date',
+620             'sends': [{'date': {'type': 'string'}}],
+621             'receives': [{'target': {'const': 'Test Date'},
+622                           'command': {'const': 'get date'}}]}
+623    test(): {'sender': 'test()', 'target': 'Test Date',
+624             'command': 'get date'}
+625    test(): {'sender': 'Test Date', 'date': ...}
+626    """
+627
+628    CONF_SCHEMA = {
+629        "properties": {"format": {"type": "string", "default": "%Y-%m-%d %H:%M:%S"}}
+630    }
+631    """Schema for Date plugin configuration.
+632
+633    Optional configuration key:
+634
+635    - 'format': format for the sent datetime string.
+636                Default: '%Y-%m-%d %H:%M:%S'
+637    """
+638
+639    def process_conf(self) -> None:
+640        """Register plugin as bus client."""
+641        self.bus.register(
+642            self.name,
+643            "Date",
+644            [MessageTemplate({"date": {"type": "string"}})],
+645            [
+646                (
+647                    [
+648                        MessageTemplate(
+649                            {
+650                                "target": {"const": self.name},
+651                                "command": {"const": "get date"},
+652                            }
+653                        )
+654                    ],
+655                    self._date,
+656                )
+657            ],
+658        )
+659
+660    async def _date(self, message: Message) -> None:
+661        date = datetime.now().strftime(self.conf["format"])
+662        await self.bus.send(Message(self.name, {"date": date}))
+663
+664    async def run(self) -> None:
+665        """Run no code proactively."""
+666        pass
+
+ + +

Send message with current date.

+ +

The plugin reacts to 'get date' commands by sending messages with +a 'date' key:

+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test Date": {"plugin": "Date"}},
+...     [{"target": "Test Date", "command": "get date"}]))
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Date', 'plugin': 'Date',
+         'sends': [{'date': {'type': 'string'}}],
+         'receives': [{'target': {'const': 'Test Date'},
+                       'command': {'const': 'get date'}}]}
+test(): {'sender': 'test()', 'target': 'Test Date',
+         'command': 'get date'}
+test(): {'sender': 'Test Date', 'date': ...}
+
+
+ +

The format of the date can be configured with the 'format' +configuration key:

+ +
+
>>> asyncio.run(controlpi.test(
+...     {"Test Date": {"plugin": "Date",
+...                    "format": "%Y%m%d%H%M%S%f"}},
+...     [{"target": "Test Date", "command": "get date"}]))
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Date', 'plugin': 'Date',
+         'sends': [{'date': {'type': 'string'}}],
+         'receives': [{'target': {'const': 'Test Date'},
+                       'command': {'const': 'get date'}}]}
+test(): {'sender': 'test()', 'target': 'Test Date',
+         'command': 'get date'}
+test(): {'sender': 'Test Date', 'date': ...}
+
+
+
+ + +
+
+ CONF_SCHEMA = +{'properties': {'format': {'type': 'string', 'default': '%Y-%m-%d %H:%M:%S'}}} + + +
+ + +

Schema for Date plugin configuration.

+ +

Optional configuration key:

+ +
    +
  • 'format': format for the sent datetime string. +Default: '%Y-%m-%d %H:%M:%S'
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
639    def process_conf(self) -> None:
+640        """Register plugin as bus client."""
+641        self.bus.register(
+642            self.name,
+643            "Date",
+644            [MessageTemplate({"date": {"type": "string"}})],
+645            [
+646                (
+647                    [
+648                        MessageTemplate(
+649                            {
+650                                "target": {"const": self.name},
+651                                "command": {"const": "get date"},
+652                            }
+653                        )
+654                    ],
+655                    self._date,
+656                )
+657            ],
+658        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
664    async def run(self) -> None:
+665        """Run no code proactively."""
+666        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/doc/api/controlpi_plugins/wait.html b/doc/api/controlpi_plugins/wait.html new file mode 100644 index 0000000..7d5f0c5 --- /dev/null +++ b/doc/api/controlpi_plugins/wait.html @@ -0,0 +1,1585 @@ + + + + + + + controlpi_plugins.wait API documentation + + + + + + + + + +
+
+

+controlpi_plugins.wait

+ +

Provide waiting/sleeping plugins for all kinds of systems.

+ +
    +
  • Wait waits for time defined in configuration and sends "finished" event.
  • +
  • GenericWait waits for time defined in "wait" command and sends "finished" +event with "id" string defined in "wait" command.
  • +
+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test Wait": {"plugin": "Wait", "seconds": 0.01},
+...      "Test GenericWait": {"plugin": "GenericWait"}},
+...     [{"target": "Test GenericWait", "command": "wait",
+...       "seconds": 0.02, "id": "Long Wait"},
+...      {"target": "Test Wait", "command": "wait"}], 0.025))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test Wait', 'plugin': 'Wait',
+         'sends': [{'event': {'const': 'finished'}}],
+         'receives': [{'target': {'const': 'Test Wait'},
+                       'command': {'const': 'wait'}}]}
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test GenericWait', 'plugin': 'GenericWait',
+         'sends': [{'event': {'const': 'finished'},
+                    'id': {'type': 'string'}}],
+         'receives': [{'target': {'const': 'Test GenericWait'},
+                       'command': {'const': 'wait'},
+                       'seconds': {'type': 'number'},
+                       'id': {'type': 'string'}}]}
+test(): {'sender': 'test()', 'target': 'Test GenericWait',
+         'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}
+test(): {'sender': 'test()', 'target': 'Test Wait', 'command': 'wait'}
+test(): {'sender': 'Test Wait', 'event': 'finished'}
+test(): {'sender': 'Test GenericWait', 'event': 'finished',
+         'id': 'Long Wait'}
+
+
+
+ + + + + +
  1"""Provide waiting/sleeping plugins for all kinds of systems.
+  2
+  3- Wait waits for time defined in configuration and sends "finished" event.
+  4- GenericWait waits for time defined in "wait" command and sends "finished"
+  5  event with "id" string defined in "wait" command.
+  6
+  7>>> import controlpi
+  8>>> asyncio.run(controlpi.test(
+  9...     {"Test Wait": {"plugin": "Wait", "seconds": 0.01},
+ 10...      "Test GenericWait": {"plugin": "GenericWait"}},
+ 11...     [{"target": "Test GenericWait", "command": "wait",
+ 12...       "seconds": 0.02, "id": "Long Wait"},
+ 13...      {"target": "Test Wait", "command": "wait"}], 0.025))
+ 14... # doctest: +NORMALIZE_WHITESPACE
+ 15test(): {'sender': '', 'event': 'registered',
+ 16         'client': 'Test Wait', 'plugin': 'Wait',
+ 17         'sends': [{'event': {'const': 'finished'}}],
+ 18         'receives': [{'target': {'const': 'Test Wait'},
+ 19                       'command': {'const': 'wait'}}]}
+ 20test(): {'sender': '', 'event': 'registered',
+ 21         'client': 'Test GenericWait', 'plugin': 'GenericWait',
+ 22         'sends': [{'event': {'const': 'finished'},
+ 23                    'id': {'type': 'string'}}],
+ 24         'receives': [{'target': {'const': 'Test GenericWait'},
+ 25                       'command': {'const': 'wait'},
+ 26                       'seconds': {'type': 'number'},
+ 27                       'id': {'type': 'string'}}]}
+ 28test(): {'sender': 'test()', 'target': 'Test GenericWait',
+ 29         'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}
+ 30test(): {'sender': 'test()', 'target': 'Test Wait', 'command': 'wait'}
+ 31test(): {'sender': 'Test Wait', 'event': 'finished'}
+ 32test(): {'sender': 'Test GenericWait', 'event': 'finished',
+ 33         'id': 'Long Wait'}
+ 34"""
+ 35
+ 36import asyncio
+ 37
+ 38from controlpi import BasePlugin, Message, MessageTemplate
+ 39
+ 40
+ 41class Wait(BasePlugin):
+ 42    """Wait for time defined in configuration.
+ 43
+ 44    The "seconds" configuration key gets the number of seconds to wait after
+ 45    receiving a "wait" command before sending the "finished" event:
+ 46    >>> import controlpi
+ 47    >>> asyncio.run(controlpi.test(
+ 48    ...     {"Long Wait": {"plugin": "Wait", "seconds": 0.02},
+ 49    ...      "Short Wait": {"plugin": "Wait", "seconds": 0.01}},
+ 50    ...     [{"target": "Long Wait", "command": "wait"},
+ 51    ...      {"target": "Short Wait", "command": "wait"}], 0.025))
+ 52    ... # doctest: +NORMALIZE_WHITESPACE
+ 53    test(): {'sender': '', 'event': 'registered',
+ 54             'client': 'Long Wait', 'plugin': 'Wait',
+ 55             'sends': [{'event': {'const': 'finished'}}],
+ 56             'receives': [{'target': {'const': 'Long Wait'},
+ 57                           'command': {'const': 'wait'}}]}
+ 58    test(): {'sender': '', 'event': 'registered',
+ 59             'client': 'Short Wait', 'plugin': 'Wait',
+ 60             'sends': [{'event': {'const': 'finished'}}],
+ 61             'receives': [{'target': {'const': 'Short Wait'},
+ 62                           'command': {'const': 'wait'}}]}
+ 63    test(): {'sender': 'test()', 'target': 'Long Wait', 'command': 'wait'}
+ 64    test(): {'sender': 'test()', 'target': 'Short Wait', 'command': 'wait'}
+ 65    test(): {'sender': 'Short Wait', 'event': 'finished'}
+ 66    test(): {'sender': 'Long Wait', 'event': 'finished'}
+ 67    """
+ 68
+ 69    CONF_SCHEMA = {
+ 70        "properties": {"seconds": {"type": "number"}},
+ 71        "required": ["seconds"],
+ 72    }
+ 73    """Schema for Wait plugin configuration.
+ 74
+ 75    Required configuration key:
+ 76
+ 77    - 'seconds': number of seconds to wait.
+ 78    """
+ 79
+ 80    def process_conf(self) -> None:
+ 81        """Register plugin as bus client."""
+ 82        self._tasks = set()
+ 83        self.bus.register(
+ 84            self.name,
+ 85            "Wait",
+ 86            [MessageTemplate({"event": {"const": "finished"}})],
+ 87            [
+ 88                (
+ 89                    [
+ 90                        MessageTemplate(
+ 91                            {
+ 92                                "target": {"const": self.name},
+ 93                                "command": {"const": "wait"},
+ 94                            }
+ 95                        )
+ 96                    ],
+ 97                    self._wait,
+ 98                )
+ 99            ],
+100        )
+101
+102    async def _wait(self, message: Message) -> None:
+103        async def wait_coroutine():
+104            await asyncio.sleep(self.conf["seconds"])
+105            await self.bus.send(Message(self.name, {"event": "finished"}))
+106
+107        # Done in separate task to not block queue awaiting this callback:
+108        task = asyncio.create_task(wait_coroutine())
+109        self._tasks.add(task)
+110        task.add_done_callback(self._tasks.discard)
+111
+112    async def run(self) -> None:
+113        """Run no code proactively."""
+114        pass
+115
+116
+117class GenericWait(BasePlugin):
+118    """Wait for time defined in "wait" command.
+119
+120    The "wait" command has message keys "seconds" defining the seconds to
+121    wait and "id" defining a string to be sent back in the "finished" event
+122    after the wait:
+123    >>> import controlpi
+124    >>> asyncio.run(controlpi.test(
+125    ...     {"Test GenericWait": {"plugin": "GenericWait"}},
+126    ...     [{"target": "Test GenericWait", "command": "wait",
+127    ...       "seconds": 0.02, "id": "Long Wait"},
+128    ...      {"target": "Test GenericWait", "command": "wait",
+129    ...       "seconds": 0.01, "id": "Short Wait"}], 0.025))
+130    ... # doctest: +NORMALIZE_WHITESPACE
+131    test(): {'sender': '', 'event': 'registered',
+132             'client': 'Test GenericWait', 'plugin': 'GenericWait',
+133             'sends': [{'event': {'const': 'finished'},
+134                        'id': {'type': 'string'}}],
+135             'receives': [{'target': {'const': 'Test GenericWait'},
+136                           'command': {'const': 'wait'},
+137                           'seconds': {'type': 'number'},
+138                           'id': {'type': 'string'}}]}
+139    test(): {'sender': 'test()', 'target': 'Test GenericWait',
+140             'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}
+141    test(): {'sender': 'test()', 'target': 'Test GenericWait',
+142             'command': 'wait', 'seconds': 0.01, 'id': 'Short Wait'}
+143    test(): {'sender': 'Test GenericWait', 'event': 'finished',
+144             'id': 'Short Wait'}
+145    test(): {'sender': 'Test GenericWait', 'event': 'finished',
+146             'id': 'Long Wait'}
+147    """
+148
+149    CONF_SCHEMA = True
+150    """Schema for GenericWait plugin configuration.
+151
+152    There are no required or optional configuration keys.
+153    """
+154
+155    def process_conf(self) -> None:
+156        """Register plugin as bus client."""
+157        self._tasks = set()
+158        self.bus.register(
+159            self.name,
+160            "GenericWait",
+161            [
+162                MessageTemplate(
+163                    {"event": {"const": "finished"}, "id": {"type": "string"}}
+164                )
+165            ],
+166            [
+167                (
+168                    [
+169                        MessageTemplate(
+170                            {
+171                                "target": {"const": self.name},
+172                                "command": {"const": "wait"},
+173                                "seconds": {"type": "number"},
+174                                "id": {"type": "string"},
+175                            }
+176                        )
+177                    ],
+178                    self._wait,
+179                )
+180            ],
+181        )
+182
+183    async def _wait(self, message: Message) -> None:
+184        async def wait_coroutine():
+185            assert isinstance(message["seconds"], float) or isinstance(
+186                message["seconds"], int
+187            )
+188            await asyncio.sleep(message["seconds"])
+189            await self.bus.send(
+190                Message(self.name, {"event": "finished", "id": message["id"]})
+191            )
+192
+193        # Done in separate task to not block queue awaiting this callback:
+194        task = asyncio.create_task(wait_coroutine())
+195        self._tasks.add(task)
+196        task.add_done_callback(self._tasks.discard)
+197
+198    async def run(self) -> None:
+199        """Run no code proactively."""
+200        pass
+201
+202
+203class Timer(BasePlugin):
+204    """Timer that can be started and cancelled.
+205
+206    The "seconds" configuration key gets the number of seconds to wait after
+207    receiving a "start" command before sending the "finished" event.
+208    The "cancel" command cancels all outstanding "finished" events and sends
+209    a corresponding "cancelled" event:
+210    >>> import controlpi
+211    >>> asyncio.run(controlpi.test(
+212    ...     {"Timer": {"plugin": "Timer", "seconds": 0.01}},
+213    ...     [{"target": "Timer", "command": "start"},
+214    ...      {"target": "Timer", "command": "start"},
+215    ...      {"target": "Timer", "command": "cancel"},
+216    ...      {"target": "Timer", "command": "start"},
+217    ...      {"target": "Timer", "command": "start"}], 0.015))
+218    ... # doctest: +NORMALIZE_WHITESPACE
+219    test(): {'sender': '', 'event': 'registered',
+220             'client': 'Timer', 'plugin': 'Timer',
+221             'sends': [{'event': {'const': 'finished'}},
+222                       {'event': {'const': 'cancelled'}}],
+223             'receives': [{'target': {'const': 'Timer'},
+224                           'command': {'const': 'start'}},
+225                          {'target': {'const': 'Timer'},
+226                           'command': {'const': 'cancel'}}]}
+227    test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+228    test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+229    test(): {'sender': 'test()', 'target': 'Timer', 'command': 'cancel'}
+230    test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+231    test(): {'sender': 'Timer', 'event': 'cancelled', 'count': 2}
+232    test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+233    test(): {'sender': 'Timer', 'event': 'finished'}
+234    test(): {'sender': 'Timer', 'event': 'finished'}
+235    """
+236
+237    CONF_SCHEMA = {
+238        "properties": {"seconds": {"type": "number"}},
+239        "required": ["seconds"],
+240    }
+241    """Schema for Timer plugin configuration.
+242
+243    Required configuration key:
+244
+245    - 'seconds': number of seconds to wait.
+246    """
+247
+248    def process_conf(self) -> None:
+249        """Register plugin as bus client."""
+250        self._tasks = set()
+251        self.started = 0
+252        self.cancelled = 0
+253        self.bus.register(
+254            self.name,
+255            "Timer",
+256            [
+257                MessageTemplate({"event": {"const": "finished"}}),
+258                MessageTemplate({"event": {"const": "cancelled"}}),
+259            ],
+260            [
+261                (
+262                    [
+263                        MessageTemplate(
+264                            {
+265                                "target": {"const": self.name},
+266                                "command": {"const": "start"},
+267                            }
+268                        )
+269                    ],
+270                    self._start,
+271                ),
+272                (
+273                    [
+274                        MessageTemplate(
+275                            {
+276                                "target": {"const": self.name},
+277                                "command": {"const": "cancel"},
+278                            }
+279                        )
+280                    ],
+281                    self._cancel,
+282                ),
+283            ],
+284        )
+285
+286    async def _start(self, message: Message) -> None:
+287        self.started += 1
+288
+289        async def wait_coroutine():
+290            await asyncio.sleep(self.conf["seconds"])
+291            if self.cancelled > 0:
+292                self.cancelled -= 1
+293                self.started -= 1
+294            elif self.started > 0:
+295                self.started -= 1
+296                await self.bus.send(Message(self.name, {"event": "finished"}))
+297
+298        # Done in separate task to not block queue awaiting this callback:
+299        task = asyncio.create_task(wait_coroutine())
+300        self._tasks.add(task)
+301        task.add_done_callback(self._tasks.discard)
+302
+303    async def _cancel(self, message: Message) -> None:
+304        if self.cancelled < self.started:
+305            cancel = self.started - self.cancelled
+306            self.cancelled = self.started
+307            await self.bus.send(
+308                Message(self.name, {"event": "cancelled", "count": cancel})
+309            )
+310
+311    async def run(self) -> None:
+312        """Run no code proactively."""
+313        pass
+314
+315
+316class Periodic(BasePlugin):
+317    """Send message periodically.
+318
+319    The "seconds" configuration key is the period of the repetition:
+320    receiving a "wait" command before sending the "finished" event:
+321    >>> import controlpi
+322    >>> asyncio.run(controlpi.test(
+323    ...     {"Loop": {"plugin": "Periodic", "seconds": 0.01,
+324    ...               "message": {"key": "value"}}},
+325    ...     [], 0.025))
+326    ... # doctest: +NORMALIZE_WHITESPACE
+327    test(): {'sender': '', 'event': 'registered',
+328             'client': 'Loop', 'plugin': 'Periodic',
+329             'sends': [{'key': {'const': 'value'}}], 'receives': []}
+330    test(): {'sender': 'Loop', 'key': 'value'}
+331    test(): {'sender': 'Loop', 'key': 'value'}
+332    """
+333
+334    CONF_SCHEMA = {
+335        "properties": {"seconds": {"type": "number"}, "message": {"type": "object"}},
+336        "required": ["seconds", "message"],
+337    }
+338    """Schema for Wait plugin configuration.
+339
+340    Required configuration key:
+341
+342    - 'seconds': period of repetition in seconds.
+343    - 'message': message to send periodically.
+344    """
+345
+346    def process_conf(self) -> None:
+347        """Register plugin as bus client."""
+348        self.bus.register(
+349            self.name,
+350            "Periodic",
+351            [MessageTemplate.from_message(self.conf["message"])],
+352            [],
+353        )
+354
+355    async def run(self) -> None:
+356        """Run periodic loop."""
+357        while True:
+358            await asyncio.sleep(self.conf["seconds"])
+359            await self.bus.send(Message(self.name, self.conf["message"]))
+
+ + +
+
+ +
+ + class + Wait(controlpi.baseplugin.BasePlugin): + + + +
+ +
 42class Wait(BasePlugin):
+ 43    """Wait for time defined in configuration.
+ 44
+ 45    The "seconds" configuration key gets the number of seconds to wait after
+ 46    receiving a "wait" command before sending the "finished" event:
+ 47    >>> import controlpi
+ 48    >>> asyncio.run(controlpi.test(
+ 49    ...     {"Long Wait": {"plugin": "Wait", "seconds": 0.02},
+ 50    ...      "Short Wait": {"plugin": "Wait", "seconds": 0.01}},
+ 51    ...     [{"target": "Long Wait", "command": "wait"},
+ 52    ...      {"target": "Short Wait", "command": "wait"}], 0.025))
+ 53    ... # doctest: +NORMALIZE_WHITESPACE
+ 54    test(): {'sender': '', 'event': 'registered',
+ 55             'client': 'Long Wait', 'plugin': 'Wait',
+ 56             'sends': [{'event': {'const': 'finished'}}],
+ 57             'receives': [{'target': {'const': 'Long Wait'},
+ 58                           'command': {'const': 'wait'}}]}
+ 59    test(): {'sender': '', 'event': 'registered',
+ 60             'client': 'Short Wait', 'plugin': 'Wait',
+ 61             'sends': [{'event': {'const': 'finished'}}],
+ 62             'receives': [{'target': {'const': 'Short Wait'},
+ 63                           'command': {'const': 'wait'}}]}
+ 64    test(): {'sender': 'test()', 'target': 'Long Wait', 'command': 'wait'}
+ 65    test(): {'sender': 'test()', 'target': 'Short Wait', 'command': 'wait'}
+ 66    test(): {'sender': 'Short Wait', 'event': 'finished'}
+ 67    test(): {'sender': 'Long Wait', 'event': 'finished'}
+ 68    """
+ 69
+ 70    CONF_SCHEMA = {
+ 71        "properties": {"seconds": {"type": "number"}},
+ 72        "required": ["seconds"],
+ 73    }
+ 74    """Schema for Wait plugin configuration.
+ 75
+ 76    Required configuration key:
+ 77
+ 78    - 'seconds': number of seconds to wait.
+ 79    """
+ 80
+ 81    def process_conf(self) -> None:
+ 82        """Register plugin as bus client."""
+ 83        self._tasks = set()
+ 84        self.bus.register(
+ 85            self.name,
+ 86            "Wait",
+ 87            [MessageTemplate({"event": {"const": "finished"}})],
+ 88            [
+ 89                (
+ 90                    [
+ 91                        MessageTemplate(
+ 92                            {
+ 93                                "target": {"const": self.name},
+ 94                                "command": {"const": "wait"},
+ 95                            }
+ 96                        )
+ 97                    ],
+ 98                    self._wait,
+ 99                )
+100            ],
+101        )
+102
+103    async def _wait(self, message: Message) -> None:
+104        async def wait_coroutine():
+105            await asyncio.sleep(self.conf["seconds"])
+106            await self.bus.send(Message(self.name, {"event": "finished"}))
+107
+108        # Done in separate task to not block queue awaiting this callback:
+109        task = asyncio.create_task(wait_coroutine())
+110        self._tasks.add(task)
+111        task.add_done_callback(self._tasks.discard)
+112
+113    async def run(self) -> None:
+114        """Run no code proactively."""
+115        pass
+
+ + +

Wait for time defined in configuration.

+ +

The "seconds" configuration key gets the number of seconds to wait after +receiving a "wait" command before sending the "finished" event:

+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Long Wait": {"plugin": "Wait", "seconds": 0.02},
+...      "Short Wait": {"plugin": "Wait", "seconds": 0.01}},
+...     [{"target": "Long Wait", "command": "wait"},
+...      {"target": "Short Wait", "command": "wait"}], 0.025))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Long Wait', 'plugin': 'Wait',
+         'sends': [{'event': {'const': 'finished'}}],
+         'receives': [{'target': {'const': 'Long Wait'},
+                       'command': {'const': 'wait'}}]}
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Short Wait', 'plugin': 'Wait',
+         'sends': [{'event': {'const': 'finished'}}],
+         'receives': [{'target': {'const': 'Short Wait'},
+                       'command': {'const': 'wait'}}]}
+test(): {'sender': 'test()', 'target': 'Long Wait', 'command': 'wait'}
+test(): {'sender': 'test()', 'target': 'Short Wait', 'command': 'wait'}
+test(): {'sender': 'Short Wait', 'event': 'finished'}
+test(): {'sender': 'Long Wait', 'event': 'finished'}
+
+
+
+ + +
+
+ CONF_SCHEMA = +{'properties': {'seconds': {'type': 'number'}}, 'required': ['seconds']} + + +
+ + +

Schema for Wait plugin configuration.

+ +

Required configuration key:

+ +
    +
  • 'seconds': number of seconds to wait.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
 81    def process_conf(self) -> None:
+ 82        """Register plugin as bus client."""
+ 83        self._tasks = set()
+ 84        self.bus.register(
+ 85            self.name,
+ 86            "Wait",
+ 87            [MessageTemplate({"event": {"const": "finished"}})],
+ 88            [
+ 89                (
+ 90                    [
+ 91                        MessageTemplate(
+ 92                            {
+ 93                                "target": {"const": self.name},
+ 94                                "command": {"const": "wait"},
+ 95                            }
+ 96                        )
+ 97                    ],
+ 98                    self._wait,
+ 99                )
+100            ],
+101        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
113    async def run(self) -> None:
+114        """Run no code proactively."""
+115        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + GenericWait(controlpi.baseplugin.BasePlugin): + + + +
+ +
118class GenericWait(BasePlugin):
+119    """Wait for time defined in "wait" command.
+120
+121    The "wait" command has message keys "seconds" defining the seconds to
+122    wait and "id" defining a string to be sent back in the "finished" event
+123    after the wait:
+124    >>> import controlpi
+125    >>> asyncio.run(controlpi.test(
+126    ...     {"Test GenericWait": {"plugin": "GenericWait"}},
+127    ...     [{"target": "Test GenericWait", "command": "wait",
+128    ...       "seconds": 0.02, "id": "Long Wait"},
+129    ...      {"target": "Test GenericWait", "command": "wait",
+130    ...       "seconds": 0.01, "id": "Short Wait"}], 0.025))
+131    ... # doctest: +NORMALIZE_WHITESPACE
+132    test(): {'sender': '', 'event': 'registered',
+133             'client': 'Test GenericWait', 'plugin': 'GenericWait',
+134             'sends': [{'event': {'const': 'finished'},
+135                        'id': {'type': 'string'}}],
+136             'receives': [{'target': {'const': 'Test GenericWait'},
+137                           'command': {'const': 'wait'},
+138                           'seconds': {'type': 'number'},
+139                           'id': {'type': 'string'}}]}
+140    test(): {'sender': 'test()', 'target': 'Test GenericWait',
+141             'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}
+142    test(): {'sender': 'test()', 'target': 'Test GenericWait',
+143             'command': 'wait', 'seconds': 0.01, 'id': 'Short Wait'}
+144    test(): {'sender': 'Test GenericWait', 'event': 'finished',
+145             'id': 'Short Wait'}
+146    test(): {'sender': 'Test GenericWait', 'event': 'finished',
+147             'id': 'Long Wait'}
+148    """
+149
+150    CONF_SCHEMA = True
+151    """Schema for GenericWait plugin configuration.
+152
+153    There are no required or optional configuration keys.
+154    """
+155
+156    def process_conf(self) -> None:
+157        """Register plugin as bus client."""
+158        self._tasks = set()
+159        self.bus.register(
+160            self.name,
+161            "GenericWait",
+162            [
+163                MessageTemplate(
+164                    {"event": {"const": "finished"}, "id": {"type": "string"}}
+165                )
+166            ],
+167            [
+168                (
+169                    [
+170                        MessageTemplate(
+171                            {
+172                                "target": {"const": self.name},
+173                                "command": {"const": "wait"},
+174                                "seconds": {"type": "number"},
+175                                "id": {"type": "string"},
+176                            }
+177                        )
+178                    ],
+179                    self._wait,
+180                )
+181            ],
+182        )
+183
+184    async def _wait(self, message: Message) -> None:
+185        async def wait_coroutine():
+186            assert isinstance(message["seconds"], float) or isinstance(
+187                message["seconds"], int
+188            )
+189            await asyncio.sleep(message["seconds"])
+190            await self.bus.send(
+191                Message(self.name, {"event": "finished", "id": message["id"]})
+192            )
+193
+194        # Done in separate task to not block queue awaiting this callback:
+195        task = asyncio.create_task(wait_coroutine())
+196        self._tasks.add(task)
+197        task.add_done_callback(self._tasks.discard)
+198
+199    async def run(self) -> None:
+200        """Run no code proactively."""
+201        pass
+
+ + +

Wait for time defined in "wait" command.

+ +

The "wait" command has message keys "seconds" defining the seconds to +wait and "id" defining a string to be sent back in the "finished" event +after the wait:

+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Test GenericWait": {"plugin": "GenericWait"}},
+...     [{"target": "Test GenericWait", "command": "wait",
+...       "seconds": 0.02, "id": "Long Wait"},
+...      {"target": "Test GenericWait", "command": "wait",
+...       "seconds": 0.01, "id": "Short Wait"}], 0.025))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Test GenericWait', 'plugin': 'GenericWait',
+         'sends': [{'event': {'const': 'finished'},
+                    'id': {'type': 'string'}}],
+         'receives': [{'target': {'const': 'Test GenericWait'},
+                       'command': {'const': 'wait'},
+                       'seconds': {'type': 'number'},
+                       'id': {'type': 'string'}}]}
+test(): {'sender': 'test()', 'target': 'Test GenericWait',
+         'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}
+test(): {'sender': 'test()', 'target': 'Test GenericWait',
+         'command': 'wait', 'seconds': 0.01, 'id': 'Short Wait'}
+test(): {'sender': 'Test GenericWait', 'event': 'finished',
+         'id': 'Short Wait'}
+test(): {'sender': 'Test GenericWait', 'event': 'finished',
+         'id': 'Long Wait'}
+
+
+
+ + +
+
+ CONF_SCHEMA = +True + + +
+ + +

Schema for GenericWait plugin configuration.

+ +

There are no required or optional configuration keys.

+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
156    def process_conf(self) -> None:
+157        """Register plugin as bus client."""
+158        self._tasks = set()
+159        self.bus.register(
+160            self.name,
+161            "GenericWait",
+162            [
+163                MessageTemplate(
+164                    {"event": {"const": "finished"}, "id": {"type": "string"}}
+165                )
+166            ],
+167            [
+168                (
+169                    [
+170                        MessageTemplate(
+171                            {
+172                                "target": {"const": self.name},
+173                                "command": {"const": "wait"},
+174                                "seconds": {"type": "number"},
+175                                "id": {"type": "string"},
+176                            }
+177                        )
+178                    ],
+179                    self._wait,
+180                )
+181            ],
+182        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
199    async def run(self) -> None:
+200        """Run no code proactively."""
+201        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + Timer(controlpi.baseplugin.BasePlugin): + + + +
+ +
204class Timer(BasePlugin):
+205    """Timer that can be started and cancelled.
+206
+207    The "seconds" configuration key gets the number of seconds to wait after
+208    receiving a "start" command before sending the "finished" event.
+209    The "cancel" command cancels all outstanding "finished" events and sends
+210    a corresponding "cancelled" event:
+211    >>> import controlpi
+212    >>> asyncio.run(controlpi.test(
+213    ...     {"Timer": {"plugin": "Timer", "seconds": 0.01}},
+214    ...     [{"target": "Timer", "command": "start"},
+215    ...      {"target": "Timer", "command": "start"},
+216    ...      {"target": "Timer", "command": "cancel"},
+217    ...      {"target": "Timer", "command": "start"},
+218    ...      {"target": "Timer", "command": "start"}], 0.015))
+219    ... # doctest: +NORMALIZE_WHITESPACE
+220    test(): {'sender': '', 'event': 'registered',
+221             'client': 'Timer', 'plugin': 'Timer',
+222             'sends': [{'event': {'const': 'finished'}},
+223                       {'event': {'const': 'cancelled'}}],
+224             'receives': [{'target': {'const': 'Timer'},
+225                           'command': {'const': 'start'}},
+226                          {'target': {'const': 'Timer'},
+227                           'command': {'const': 'cancel'}}]}
+228    test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+229    test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+230    test(): {'sender': 'test()', 'target': 'Timer', 'command': 'cancel'}
+231    test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+232    test(): {'sender': 'Timer', 'event': 'cancelled', 'count': 2}
+233    test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+234    test(): {'sender': 'Timer', 'event': 'finished'}
+235    test(): {'sender': 'Timer', 'event': 'finished'}
+236    """
+237
+238    CONF_SCHEMA = {
+239        "properties": {"seconds": {"type": "number"}},
+240        "required": ["seconds"],
+241    }
+242    """Schema for Timer plugin configuration.
+243
+244    Required configuration key:
+245
+246    - 'seconds': number of seconds to wait.
+247    """
+248
+249    def process_conf(self) -> None:
+250        """Register plugin as bus client."""
+251        self._tasks = set()
+252        self.started = 0
+253        self.cancelled = 0
+254        self.bus.register(
+255            self.name,
+256            "Timer",
+257            [
+258                MessageTemplate({"event": {"const": "finished"}}),
+259                MessageTemplate({"event": {"const": "cancelled"}}),
+260            ],
+261            [
+262                (
+263                    [
+264                        MessageTemplate(
+265                            {
+266                                "target": {"const": self.name},
+267                                "command": {"const": "start"},
+268                            }
+269                        )
+270                    ],
+271                    self._start,
+272                ),
+273                (
+274                    [
+275                        MessageTemplate(
+276                            {
+277                                "target": {"const": self.name},
+278                                "command": {"const": "cancel"},
+279                            }
+280                        )
+281                    ],
+282                    self._cancel,
+283                ),
+284            ],
+285        )
+286
+287    async def _start(self, message: Message) -> None:
+288        self.started += 1
+289
+290        async def wait_coroutine():
+291            await asyncio.sleep(self.conf["seconds"])
+292            if self.cancelled > 0:
+293                self.cancelled -= 1
+294                self.started -= 1
+295            elif self.started > 0:
+296                self.started -= 1
+297                await self.bus.send(Message(self.name, {"event": "finished"}))
+298
+299        # Done in separate task to not block queue awaiting this callback:
+300        task = asyncio.create_task(wait_coroutine())
+301        self._tasks.add(task)
+302        task.add_done_callback(self._tasks.discard)
+303
+304    async def _cancel(self, message: Message) -> None:
+305        if self.cancelled < self.started:
+306            cancel = self.started - self.cancelled
+307            self.cancelled = self.started
+308            await self.bus.send(
+309                Message(self.name, {"event": "cancelled", "count": cancel})
+310            )
+311
+312    async def run(self) -> None:
+313        """Run no code proactively."""
+314        pass
+
+ + +

Timer that can be started and cancelled.

+ +

The "seconds" configuration key gets the number of seconds to wait after +receiving a "start" command before sending the "finished" event. +The "cancel" command cancels all outstanding "finished" events and sends +a corresponding "cancelled" event:

+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Timer": {"plugin": "Timer", "seconds": 0.01}},
+...     [{"target": "Timer", "command": "start"},
+...      {"target": "Timer", "command": "start"},
+...      {"target": "Timer", "command": "cancel"},
+...      {"target": "Timer", "command": "start"},
+...      {"target": "Timer", "command": "start"}], 0.015))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Timer', 'plugin': 'Timer',
+         'sends': [{'event': {'const': 'finished'}},
+                   {'event': {'const': 'cancelled'}}],
+         'receives': [{'target': {'const': 'Timer'},
+                       'command': {'const': 'start'}},
+                      {'target': {'const': 'Timer'},
+                       'command': {'const': 'cancel'}}]}
+test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+test(): {'sender': 'test()', 'target': 'Timer', 'command': 'cancel'}
+test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+test(): {'sender': 'Timer', 'event': 'cancelled', 'count': 2}
+test(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}
+test(): {'sender': 'Timer', 'event': 'finished'}
+test(): {'sender': 'Timer', 'event': 'finished'}
+
+
+
+ + +
+
+ CONF_SCHEMA = +{'properties': {'seconds': {'type': 'number'}}, 'required': ['seconds']} + + +
+ + +

Schema for Timer plugin configuration.

+ +

Required configuration key:

+ +
    +
  • 'seconds': number of seconds to wait.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
249    def process_conf(self) -> None:
+250        """Register plugin as bus client."""
+251        self._tasks = set()
+252        self.started = 0
+253        self.cancelled = 0
+254        self.bus.register(
+255            self.name,
+256            "Timer",
+257            [
+258                MessageTemplate({"event": {"const": "finished"}}),
+259                MessageTemplate({"event": {"const": "cancelled"}}),
+260            ],
+261            [
+262                (
+263                    [
+264                        MessageTemplate(
+265                            {
+266                                "target": {"const": self.name},
+267                                "command": {"const": "start"},
+268                            }
+269                        )
+270                    ],
+271                    self._start,
+272                ),
+273                (
+274                    [
+275                        MessageTemplate(
+276                            {
+277                                "target": {"const": self.name},
+278                                "command": {"const": "cancel"},
+279                            }
+280                        )
+281                    ],
+282                    self._cancel,
+283                ),
+284            ],
+285        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
312    async def run(self) -> None:
+313        """Run no code proactively."""
+314        pass
+
+ + +

Run no code proactively.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ +
+ + class + Periodic(controlpi.baseplugin.BasePlugin): + + + +
+ +
317class Periodic(BasePlugin):
+318    """Send message periodically.
+319
+320    The "seconds" configuration key is the period of the repetition:
+321    receiving a "wait" command before sending the "finished" event:
+322    >>> import controlpi
+323    >>> asyncio.run(controlpi.test(
+324    ...     {"Loop": {"plugin": "Periodic", "seconds": 0.01,
+325    ...               "message": {"key": "value"}}},
+326    ...     [], 0.025))
+327    ... # doctest: +NORMALIZE_WHITESPACE
+328    test(): {'sender': '', 'event': 'registered',
+329             'client': 'Loop', 'plugin': 'Periodic',
+330             'sends': [{'key': {'const': 'value'}}], 'receives': []}
+331    test(): {'sender': 'Loop', 'key': 'value'}
+332    test(): {'sender': 'Loop', 'key': 'value'}
+333    """
+334
+335    CONF_SCHEMA = {
+336        "properties": {"seconds": {"type": "number"}, "message": {"type": "object"}},
+337        "required": ["seconds", "message"],
+338    }
+339    """Schema for Wait plugin configuration.
+340
+341    Required configuration key:
+342
+343    - 'seconds': period of repetition in seconds.
+344    - 'message': message to send periodically.
+345    """
+346
+347    def process_conf(self) -> None:
+348        """Register plugin as bus client."""
+349        self.bus.register(
+350            self.name,
+351            "Periodic",
+352            [MessageTemplate.from_message(self.conf["message"])],
+353            [],
+354        )
+355
+356    async def run(self) -> None:
+357        """Run periodic loop."""
+358        while True:
+359            await asyncio.sleep(self.conf["seconds"])
+360            await self.bus.send(Message(self.name, self.conf["message"]))
+
+ + +

Send message periodically.

+ +

The "seconds" configuration key is the period of the repetition: +receiving a "wait" command before sending the "finished" event:

+ +
+
>>> import controlpi
+>>> asyncio.run(controlpi.test(
+...     {"Loop": {"plugin": "Periodic", "seconds": 0.01,
+...               "message": {"key": "value"}}},
+...     [], 0.025))
+... # doctest: +NORMALIZE_WHITESPACE
+test(): {'sender': '', 'event': 'registered',
+         'client': 'Loop', 'plugin': 'Periodic',
+         'sends': [{'key': {'const': 'value'}}], 'receives': []}
+test(): {'sender': 'Loop', 'key': 'value'}
+test(): {'sender': 'Loop', 'key': 'value'}
+
+
+
+ + +
+
+ CONF_SCHEMA = + + {'properties': {'seconds': {'type': 'number'}, 'message': {'type': 'object'}}, 'required': ['seconds', 'message']} + + +
+ + +

Schema for Wait plugin configuration.

+ +

Required configuration key:

+ +
    +
  • 'seconds': period of repetition in seconds.
  • +
  • 'message': message to send periodically.
  • +
+
+ + +
+
+ +
+ + def + process_conf(self) -> None: + + + +
+ +
347    def process_conf(self) -> None:
+348        """Register plugin as bus client."""
+349        self.bus.register(
+350            self.name,
+351            "Periodic",
+352            [MessageTemplate.from_message(self.conf["message"])],
+353            [],
+354        )
+
+ + +

Register plugin as bus client.

+
+ + +
+
+ +
+ + async def + run(self) -> None: + + + +
+ +
356    async def run(self) -> None:
+357        """Run periodic loop."""
+358        while True:
+359            await asyncio.sleep(self.conf["seconds"])
+360            await self.bus.send(Message(self.name, self.conf["message"]))
+
+ + +

Run periodic loop.

+
+ + +
+
+
Inherited Members
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/doc/api/index.html b/doc/api/index.html new file mode 100644 index 0000000..fdd4c09 --- /dev/null +++ b/doc/api/index.html @@ -0,0 +1,223 @@ + + + + + + + Module List + + + + + + + + + + +
+ + pdoc + + +
+
+ + \ No newline at end of file diff --git a/doc/api/search.js b/doc/api/search.js new file mode 100644 index 0000000..96f6b7b --- /dev/null +++ b/doc/api/search.js @@ -0,0 +1,46 @@ +window.pdocSearch = (function(){ +/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();oProvide the infrastructure for the ControlPi system.

\n\n

The infrastructure consists of the message bus from module messagebus, the\nplugin registry from module pluginregistry and the abstract base plugin from\nmodule baseplugin.

\n\n

The package combines them in its run function, which is used by __main__.py\nto run a ControlPi system based on a configuration file indefinitely.

\n\n

The test function is a utility function to test plugins with minimal\nboilerplate code.

\n"}, "controlpi.CONF_SCHEMA": {"fullname": "controlpi.CONF_SCHEMA", "modulename": "controlpi", "qualname": "CONF_SCHEMA", "kind": "variable", "doc": "

\n", "default_value": "{'type': 'object', 'patternProperties': {'.*': {'type': 'object'}}}"}, "controlpi.run": {"fullname": "controlpi.run", "modulename": "controlpi", "qualname": "run", "kind": "function", "doc": "

Run the ControlPi system based on a configuration.

\n\n

Setup message bus, process given configuration, and run message bus and\nplugins concurrently and indefinitely.

\n\n

This function is mainly used by __main__.py to run a ControlPi system\nbased on a configuration loaded from a configuration JSON file on disk.

\n\n
\n
>>> async def test_coroutine():\n...     conf = {"Example Init":\n...             {"plugin": "Init",\n...              "messages": [{"id": 42,\n...                            "content": "Test Message"},\n...                           {"id": 42.42,\n...                            "content": "Second Message"}]},\n...             "Example Log":\n...             {"plugin": "Log",\n...              "filter": [{"sender": {"const": "Example Init"}}]}}\n...     run_task = asyncio.create_task(run(conf))\n...     await asyncio.sleep(0.1)\n...     run_task.cancel()\n...     try:\n...         await run_task\n...     except asyncio.exceptions.CancelledError:\n...         pass\n>>> asyncio.run(test_coroutine())  # doctest: +NORMALIZE_WHITESPACE\nExample Log: {'sender': 'Example Init',\n              'id': 42, 'content': 'Test Message'}\nExample Log: {'sender': 'Example Init',\n              'id': 42.42, 'content': 'Second Message'}\n
\n
\n", "signature": "(conf: Dict[str, Dict[str, Any]]) -> None:", "funcdef": "async def"}, "controlpi.test": {"fullname": "controlpi.test", "modulename": "controlpi", "qualname": "test", "kind": "function", "doc": "

Test configuration of ControlPi system.

\n\n

Setup message bus, process given configuration, run message bus and\nplugins concurrently, send given messages on message bus and print all\nmessages on message bus. Terminate when queue of message bus is empty.

\n\n

This function allows to test single plugins or small plugin\nconfigurations with minimal boilerplate code:

\n\n
\n
>>> asyncio.run(test(\n...     {"Example Init": {"plugin": "Init",\n...                       "messages": [{"id": 42,\n...                                     "content": "Test Message"},\n...                                    {"id": 42.42,\n...                                     "content": "Second Message"}]}},\n...     [{"target": "Example Init",\n...       "command": "execute"}]))  # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Example Init', 'plugin': 'Init',\n         'sends': [{'id': {'const': 42},\n                    'content': {'const': 'Test Message'}},\n                   {'id': {'const': 42.42},\n                    'content': {'const': 'Second Message'}}],\n         'receives': [{'target': {'const': 'Example Init'},\n                       'command': {'const': 'execute'}}]}\ntest(): {'sender': 'Example Init',\n         'id': 42, 'content': 'Test Message'}\ntest(): {'sender': 'Example Init',\n         'id': 42.42, 'content': 'Second Message'}\ntest(): {'sender': 'test()', 'target': 'Example Init',\n         'command': 'execute'}\ntest(): {'sender': 'Example Init',\n         'id': 42, 'content': 'Test Message'}\ntest(): {'sender': 'Example Init',\n         'id': 42.42, 'content': 'Second Message'}\n
\n
\n\n

Similar functionality could be reached by using the Log and Init plugins\nto print messages and send some messages on the bus, but these would\nclutter the test configuration and code to stop the indefinitely running\nbus would have to be added to each and every test.

\n\n

Incorrect plugin configurations can also be tested by this:

\n\n
\n
>>> asyncio.run(test(\n...     {"Example Init": {"plugin": "Init"}}, []))\ndata must contain ['messages'] properties\nConfiguration for 'Example Init' is not valid.\n
\n
\n", "signature": "(\tconf: Dict[str, Dict[str, Any]],\tmessages: List[Dict[str, Any]],\twait: float = 0.0) -> None:", "funcdef": "async def"}, "controlpi.baseplugin": {"fullname": "controlpi.baseplugin", "modulename": "controlpi.baseplugin", "kind": "module", "doc": "

Define base class for all ControlPi plugins.

\n\n

The class BasePlugin provides the abstract base class for concrete plugins\nrunning on the ControlPi system.

\n\n

It has three abstract methods that have to be implemented by all concrete\nplugins:

\n\n
    \n
  • The class property CONF_SCHEMA is the JSON schema of the configuration of\nthe plugin. The configuration read from the global configuration file is\nchecked against this schema during initialisation.
  • \n
  • The method process_conf is called at the end of initialisation and is used\nto initialise the plugin. It can be assumed that self.bus is the message\nbus of the system, self.name the instance name, and self.conf the\nconfiguration already validated against the schema.
  • \n
  • The run coroutines of all plugins are executed concurrently by the main\nsystem.
  • \n
\n\n
\n
>>> class TestPlugin(BasePlugin):\n...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},\n...                    'required': ['key']}\n...     def process_conf(self):\n...         if 'key' in self.conf:\n...             print(f"Processing '{self.conf['key']}'.")\n...     async def run(self):\n...         print("Doing something else.")\n
\n
\n\n

Plugins are configured and run based on the information in the global\nconfiguration. Here, we test this manually:

\n\n
\n
>>> import asyncio\n>>> async def test():\n...     p = TestPlugin(MessageBus(), 'Test Instance', {'key': 'Something'})\n...     await p.run()\n>>> asyncio.run(test())\nProcessing 'Something'.\nDoing something else.\n
\n
\n\n

Each plugin gets a reference to the system message bus during\ninitialisation, which can be accessed as self.bus in the functions of the\nplugin class. This can be used to register and unregister message bus\nclients:

\n\n
\n
>>> class BusPlugin(BasePlugin):\n...     CONF_SCHEMA = True\n...     def process_conf(self):\n...         self.bus.register(self.name, 'BusPlugin',\n...                           [{'event': {'type': 'string'}}],\n...                           [([{'target': {'const': self.name}}],\n...                             self._receive)])\n...     async def _receive(self, message):\n...         print(f"{self.name} received {message}.")\n...         await self.bus.send({'sender': self.name, 'event': 'Receive'})\n...     async def run(self):\n...         await self.bus.send({'sender': self.name, 'event': 'Run'})\n
\n
\n\n

Again, we run this manually here, but this is done by the main coroutine\nwhen using the system in production:

\n\n
\n
>>> async def log(message):\n...     print(f"Log: {message}")\n>>> async def test_bus_plugin():\n...     bus = MessageBus()\n...     p = BusPlugin(bus, 'Bus Test', {})\n...     bus.register('Test', 'TestPlugin',\n...                  [{}], [([{'sender': {'const': 'Bus Test'}}], log)])\n...     bus_task = asyncio.create_task(bus.run())\n...     plugin_task = asyncio.create_task(p.run())\n...     await bus.send({'sender': 'Test', 'target': 'Bus Test', 'key': 'v'})\n...     await asyncio.sleep(0)\n...     await asyncio.sleep(0)\n...     bus_task.cancel()\n...     try:\n...         await asyncio.gather(bus_task, plugin_task)\n...     except asyncio.exceptions.CancelledError:\n...         pass\n>>> asyncio.run(test_bus_plugin())\nBus Test received {'sender': 'Test', 'target': 'Bus Test', 'key': 'v'}.\nLog: {'sender': 'Bus Test', 'event': 'Run'}\nLog: {'sender': 'Bus Test', 'event': 'Receive'}\n
\n
\n\n

Often, there will be a one-to-one correspondence between plugin\ninstances and message bus clients, a plugin instance will be a message bus\nclient. But there are also cases, where one plugin instance might register\nand unregister a lot of message bus clients, maybe even dynamically through\nits lifetime. A plugin for an input/output card might register separate\nclients for each pin of the card, a plugin for some kind of hardware bus\nmight register separate clients for all devices connected to the bus, or a\nnetwork socket plugin might register separate clients for all connections\nto the socket (and unregister them when the connection is closed).

\n"}, "controlpi.baseplugin.JSONSchema": {"fullname": "controlpi.baseplugin.JSONSchema", "modulename": "controlpi.baseplugin", "qualname": "JSONSchema", "kind": "variable", "doc": "

\n", "default_value": "bool | typing.Dict[str, None | str | int | float | bool | typing.Dict[str, typing.Any] | typing.List[typing.Any]]"}, "controlpi.baseplugin.PluginConf": {"fullname": "controlpi.baseplugin.PluginConf", "modulename": "controlpi.baseplugin", "qualname": "PluginConf", "kind": "variable", "doc": "

\n", "default_value": "typing.Dict[str, typing.Any]"}, "controlpi.baseplugin.ConfException": {"fullname": "controlpi.baseplugin.ConfException", "modulename": "controlpi.baseplugin", "qualname": "ConfException", "kind": "class", "doc": "

Raise for errors in plugin configurations.

\n", "bases": "builtins.Exception"}, "controlpi.baseplugin.BasePlugin": {"fullname": "controlpi.baseplugin.BasePlugin", "modulename": "controlpi.baseplugin", "qualname": "BasePlugin", "kind": "class", "doc": "

Base class for all ControlPi plugins.

\n\n
\n
>>> class TestPlugin(BasePlugin):\n...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},\n...                    'required': ['key']}\n...     def process_conf(self):\n...         if 'key' in self.conf:\n...             print(f"Processing '{self.conf['key']}'.")\n...     async def run(self):\n...         print("Doing something else.")\n
\n
\n\n

Initialisation sets the instance variables bus to the given message bus,\nname to the given name, and conf to the given configuration:

\n\n
\n
>>> import asyncio\n>>> class TestPlugin(BasePlugin):\n...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},\n...                    'required': ['key']}\n...     def process_conf(self):\n...         if 'key' in self.conf:\n...             print(f"Processing '{self.conf['key']}'.")\n...     async def run(self):\n...         print("Doing something else.")\n>>> async def test():\n...     p = TestPlugin(MessageBus(), 'Test Instance',\n...                    {'key': 'Something'})\n...     print(p.bus)\n...     print(p.name)\n...     print(p.conf)\n>>> asyncio.run(test())  # doctest: +ELLIPSIS\nProcessing 'Something'.\n<controlpi.messagebus.MessageBus object at 0x...>\nTest Instance\n{'key': 'Something'}\n
\n
\n\n

It also validates the configuration against the schema in CONF_SCHEMA\nand raises ConfException if is not validated.

\n\n
\n
>>> async def test():\n...     p = TestPlugin(MessageBus(), 'Test Instance',\n...                    {'key': 42})\n>>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE\nTraceback (most recent call last):\n  ...\nbaseplugin.ConfException: Configuration for 'Test Instance'\nis not valid.\n>>> async def test():\n...     p = TestPlugin(MessageBus(), 'Test Instance',\n...                    {'key 2': 'Something'})\n>>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE\nTraceback (most recent call last):\n  ...\nbaseplugin.ConfException: Configuration for 'Test Instance'\nis not valid.\n
\n
\n\n

Finally, it calls process_conf, which is the function that should be\noverridden by concrete plugins.

\n", "bases": "abc.ABC"}, "controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"fullname": "controlpi.baseplugin.BasePlugin.CONF_SCHEMA", "modulename": "controlpi.baseplugin", "qualname": "BasePlugin.CONF_SCHEMA", "kind": "variable", "doc": "

\n", "annotation": ": bool | Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]]", "default_value": "False"}, "controlpi.baseplugin.BasePlugin.bus": {"fullname": "controlpi.baseplugin.BasePlugin.bus", "modulename": "controlpi.baseplugin", "qualname": "BasePlugin.bus", "kind": "variable", "doc": "

\n"}, "controlpi.baseplugin.BasePlugin.name": {"fullname": "controlpi.baseplugin.BasePlugin.name", "modulename": "controlpi.baseplugin", "qualname": "BasePlugin.name", "kind": "variable", "doc": "

\n"}, "controlpi.baseplugin.BasePlugin.conf": {"fullname": "controlpi.baseplugin.BasePlugin.conf", "modulename": "controlpi.baseplugin", "qualname": "BasePlugin.conf", "kind": "variable", "doc": "

\n"}, "controlpi.baseplugin.BasePlugin.process_conf": {"fullname": "controlpi.baseplugin.BasePlugin.process_conf", "modulename": "controlpi.baseplugin", "qualname": "BasePlugin.process_conf", "kind": "function", "doc": "

Process the configuration.

\n\n

Abstract method has to be overridden by concrete plugins.\nprocess_conf is called at the end of initialisation after the bus\nand the configuration are available as self.bus and self.conf, but\nbefore any of the run coroutines are executed.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi.baseplugin.BasePlugin.run": {"fullname": "controlpi.baseplugin.BasePlugin.run", "modulename": "controlpi.baseplugin", "qualname": "BasePlugin.run", "kind": "function", "doc": "

Run the plugin.

\n\n

The coroutine is run concurrently with the message bus and all\nother plugins. Initial messages and other tasks can be done here.\nIt is also okay to run a plugin-specific infinite loop concurrently\nwith the rest of the system.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi.messagebus": {"fullname": "controlpi.messagebus", "modulename": "controlpi.messagebus", "kind": "module", "doc": "

Provide an asynchronous message bus.

\n\n

A message is a dictionary with string keys and string, integer, float,\nBoolean, dictionary, or list values, where the inner dictionaries again\nhave string keys and these values and the inner lists also have elements of\nthese types. All messages have a special key 'sender' with the name of the\nsending client as string value, which is set by the constructor:

\n\n
\n
>>> m = Message('Example sender', {'key 1': 'value 1'})\n>>> m['key 2'] = 'value 2'\n>>> print(m)\n{'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}\n
\n
\n\n

A message template is a mapping from string keys to JSON schemas as values.\nA message template matches a message if all keys of the template are\ncontained in the message and the values in the message validate against the\nrespective schemas. An empty mapping therefore matches all messages.

\n\n

The bus executes asynchronous callbacks for all messages to be received by\na client. We use a simple callback printing the message in all examples:

\n\n
\n
>>> def callback_for_receiver(receiver):\n...     async def callback(message):\n...         print(f"{receiver}: {message}")\n...     return callback\n
\n
\n\n

Clients can be registered at the bus with a name, lists of message templates\nthey want to use for sending and receiving and a callback function for\nreceiving. An empty list of templates means that the client does not want to\nsend or receive any messages, respectively. A list with an empty template\nmeans that it wants to send arbitrary or receive all messages, respectively:

\n\n
\n
>>> async def setup(bus):\n...     bus.register('Logger', 'Test Plugin',\n...                  [],\n...                  [([MessageTemplate({})],\n...                    callback_for_receiver('Logger'))])\n...     bus.register('Client 1', 'Test Plugin',\n...                  [MessageTemplate({'k1': {'type': 'string'}})],\n...                  [([MessageTemplate({'target': {'const': 'Client 1'}})],\n...                    callback_for_receiver('Client 1'))])\n
\n
\n\n

While most clients should always use their own name for sending, this is not\nenforced and debugging or management clients could send messages on behalf\nof arbitrary client names.

\n\n

The name of a client has to be unique and is not allowed to be empty\n(otherwise registration fails).

\n\n

The empty name is used to refer to the bus itself. The bus sends messages\nfor registrations and deregistrations of clients containing their complete\ninterface of send and receive templates. This can be used to allow dynamic\n(debug) clients to deal with arbitrary configurations of clients. The bus\nalso reacts to 'get clients' command messages by sending the complete\ninformation of all currently registered clients.

\n\n

Clients can send to the bus with the send function. Each message has to\ndeclare a sender. The send templates of that sender are checked for a\ntemplate matching the message:

\n\n
\n
>>> async def send(bus):\n...     print("Sending messages.")\n...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})\n...     await bus.send({'sender': '', 'target': 'Client 1'})\n
\n
\n\n

The run function executes the message bus forever. If we want to stop it, we\nhave to explicitly cancel the task:

\n\n
\n
>>> async def main():\n...     bus = MessageBus()\n...     await setup(bus)\n...     bus_task = asyncio.create_task(bus.run())\n...     await send(bus)\n...     await asyncio.sleep(0)\n...     bus_task.cancel()\n...     try:\n...         await bus_task\n...     except asyncio.exceptions.CancelledError:\n...         pass\n>>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE\nSending messages.\nLogger: {'sender': '', 'event': 'registered',\n         'client': 'Logger', 'plugin': 'Test Plugin',\n         'sends': [], 'receives': [{}]}\nLogger: {'sender': '', 'event': 'registered',\n         'client': 'Client 1', 'plugin': 'Test Plugin',\n         'sends': [{'k1': {'type': 'string'}}],\n         'receives': [{'target': {'const': 'Client 1'}}]}\nLogger: {'sender': 'Client 1', 'k1': 'Test'}\nLogger: {'sender': '', 'target': 'Client 1'}\nClient 1: {'sender': '', 'target': 'Client 1'}\n
\n
\n"}, "controlpi.messagebus.MessageValue": {"fullname": "controlpi.messagebus.MessageValue", "modulename": "controlpi.messagebus", "qualname": "MessageValue", "kind": "variable", "doc": "

\n", "default_value": "None | str | int | float | bool | typing.Dict[str, typing.Any] | typing.List[typing.Any]"}, "controlpi.messagebus.JSONSchema": {"fullname": "controlpi.messagebus.JSONSchema", "modulename": "controlpi.messagebus", "qualname": "JSONSchema", "kind": "variable", "doc": "

\n", "default_value": "bool | typing.Dict[str, None | str | int | float | bool | typing.Dict[str, typing.Any] | typing.List[typing.Any]]"}, "controlpi.messagebus.MessageCallback": {"fullname": "controlpi.messagebus.MessageCallback", "modulename": "controlpi.messagebus", "qualname": "MessageCallback", "kind": "variable", "doc": "

\n", "default_value": "typing.Callable[[ForwardRef('Message')], typing.Coroutine[typing.Any, typing.Any, NoneType]]"}, "controlpi.messagebus.register_schema": {"fullname": "controlpi.messagebus.register_schema", "modulename": "controlpi.messagebus", "qualname": "register_schema", "kind": "function", "doc": "

Register the given JSON schema in the global cache.

\n", "signature": "(\tschema: bool | Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]]) -> bool:", "funcdef": "def"}, "controlpi.messagebus.validate": {"fullname": "controlpi.messagebus.validate", "modulename": "controlpi.messagebus", "qualname": "validate", "kind": "function", "doc": "

Validate the given MessageValue against the given JSON schema string.

\n", "signature": "(\tschema_string: str,\tvalue: None | str | int | float | bool | Dict[str, Any] | List[Any]) -> bool:", "funcdef": "def"}, "controlpi.messagebus.Message": {"fullname": "controlpi.messagebus.Message", "modulename": "controlpi.messagebus", "qualname": "Message", "kind": "class", "doc": "

Define arbitrary message.

\n\n

Messages are dictionaries with string keys and values that are strings,\nintegers, floats, Booleans, dictionaries that recursively have string\nkeys and values of any of these types, or lists with elements that have\nany of these types. These constraints are checked when setting key-value\npairs of the message.

\n\n

A message has to have a sender, which is set by the constructor:

\n\n
\n
>>> m = Message('Example sender')\n>>> print(m)\n{'sender': 'Example sender'}\n
\n
\n\n

A dictionary can be given to the constructor:

\n\n
\n
>>> m = Message('Example sender',\n...             {'key 1': 'value 1', 'key 2': 'value 2'})\n>>> print(m)\n{'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}\n
\n
\n\n

A 'sender' set in the initial dictionary is overwritten by the explicitly\ngiven sender in the first argument:

\n\n
\n
>>> m = Message('Example sender',\n...             {'sender': 'Original sender', 'key': 'value'})\n>>> print(m)\n{'sender': 'Example sender', 'key': 'value'}\n
\n
\n\n

Or the message can be modified after construction:

\n\n
\n
>>> m = Message('Example sender', {'key 1': 'value 1'})\n>>> m['key 2'] = 'value 2'\n>>> print(m)\n{'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}\n
\n
\n\n

The 'sender' key can be overwritten, but this should only be done in\nexceptional cases:

\n\n
\n
>>> m = Message('Example sender', {'key': 'value'})\n>>> m['sender'] = 'New sender'\n>>> print(m)\n{'sender': 'New sender', 'key': 'value'}\n
\n
\n", "bases": "typing.Dict[str, None | str | int | float | bool | typing.Dict[str, typing.Any] | typing.List[typing.Any]]"}, "controlpi.messagebus.Message.__init__": {"fullname": "controlpi.messagebus.Message.__init__", "modulename": "controlpi.messagebus", "qualname": "Message.__init__", "kind": "function", "doc": "

Initialise message.

\n\n

Message is initialised with given sender and possibly given\nkey-value pairs:

\n\n
\n
>>> m = Message('Example sender')\n>>> print(m)\n{'sender': 'Example sender'}\n>>> m = Message('Example sender', {'key 1': 'value 1'})\n>>> print(m)\n{'sender': 'Example sender', 'key 1': 'value 1'}\n
\n
\n", "signature": "(\tsender: str,\tinit: Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]] | None = None)"}, "controlpi.messagebus.Message.check_value": {"fullname": "controlpi.messagebus.Message.check_value", "modulename": "controlpi.messagebus", "qualname": "Message.check_value", "kind": "function", "doc": "

Check recursively if a given value is valid.

\n\n

None, strings, integers, floats and Booleans are valid:

\n\n
\n
>>> Message.check_value(None)\nTrue\n>>> Message.check_value('Spam')\nTrue\n>>> Message.check_value(42)\nTrue\n>>> Message.check_value(42.42)\nTrue\n>>> Message.check_value(False)\nTrue\n
\n
\n\n

Other basic types are not valid:

\n\n
\n
>>> Message.check_value(b'bytes')\nFalse\n>>> Message.check_value(1j)\nFalse\n
\n
\n\n

Dictionaries with string keys and recursively valid values are valid:

\n\n
\n
>>> Message.check_value({'str value': 'Spam', 'int value': 42,\n...                      'float value': 42.42, 'bool value': False})\nTrue\n
\n
\n\n

Empty dictionaries are valid:

\n\n
\n
>>> Message.check_value({})\nTrue\n
\n
\n\n

Dictionaries with other keys are not valid:

\n\n
\n
>>> Message.check_value({42: 'int key'})\nFalse\n
\n
\n\n

Dictionaries with invalid values are not valid:

\n\n
\n
>>> Message.check_value({'complex value': 1j})\nFalse\n
\n
\n\n

Lists with valid elements are valid:

\n\n
\n
>>> Message.check_value(['Spam', 42, 42.42, False])\nTrue\n
\n
\n\n

Empty lists are valid:

\n\n
\n
>>> Message.check_value([])\nTrue\n
\n
\n\n

Lists with invalid elements are not valid:

\n\n
\n
>>> Message.check_value([1j])\nFalse\n
\n
\n", "signature": "(\tvalue: None | str | int | float | bool | Dict[str, Any] | List[Any]) -> bool:", "funcdef": "def"}, "controlpi.messagebus.Message.update": {"fullname": "controlpi.messagebus.Message.update", "modulename": "controlpi.messagebus", "qualname": "Message.update", "kind": "function", "doc": "

Override update to use validity checks.

\n\n
\n
>>> m = Message('Example sender')\n>>> m.update({'key 1': 'value 1', 'key 2': 'value 2'})\n>>> print(m)\n{'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}\n>>> m.update({42: 'int key'})\nTraceback (most recent call last):\n  ...\nTypeError: '42' is not a valid key in Message (not a string).\n>>> m.update({'complex value': 1j})\nTraceback (most recent call last):\n  ...\nTypeError: '1j' is not a valid value in Message.\n
\n
\n\n

This is also used in __init__:

\n\n
\n
>>> m = Message('Example sender', {'key': 'value'})\n>>> print(m)\n{'sender': 'Example sender', 'key': 'value'}\n>>> m = Message('Example sender', {42: 'int key'})\nTraceback (most recent call last):\n  ...\nTypeError: '42' is not a valid key in Message (not a string).\n>>> m = Message('Example sender', {'complex value': 1j})\nTraceback (most recent call last):\n  ...\nTypeError: '1j' is not a valid value in Message.\n
\n
\n", "signature": "(self, *args, **kwargs) -> None:", "funcdef": "def"}, "controlpi.messagebus.Message.setdefault": {"fullname": "controlpi.messagebus.Message.setdefault", "modulename": "controlpi.messagebus", "qualname": "Message.setdefault", "kind": "function", "doc": "

Override setdefault to use validity checks.

\n\n
\n
>>> m = Message('Example sender')\n>>> m.setdefault('key', 'value 1')\n'value 1'\n>>> m.setdefault('key', 'value 2')\n'value 1'\n>>> m.setdefault(42, 'int key')\nTraceback (most recent call last):\n  ...\nTypeError: '42' is not a valid key in Message (not a string).\n>>> m.setdefault('complex value', 1j)\nTraceback (most recent call last):\n  ...\nTypeError: '1j' is not a valid value in Message.\n
\n
\n\n

But __setitem__ is not called if the key is already present:

\n\n
\n
>>> m.setdefault('key', 1j)\n'value 1'\n
\n
\n", "signature": "(\tself,\tkey: str,\tvalue: None | str | int | float | bool | Dict[str, Any] | List[Any] = None) -> None | str | int | float | bool | Dict[str, Any] | List[Any]:", "funcdef": "def"}, "controlpi.messagebus.MessageTemplate": {"fullname": "controlpi.messagebus.MessageTemplate", "modulename": "controlpi.messagebus", "qualname": "MessageTemplate", "kind": "class", "doc": "

Define a message template.

\n\n

A message template is a mapping from string keys to JSON schemas as\nvalues:

\n\n
\n
>>> t = MessageTemplate({'key 1': {'const': 'value'},\n...                      'key 2': {'type': 'string'}})\n>>> t['key 3'] = {'type': 'object',\n...               'properties': {'key 1': {'type': 'number'},\n...                              'key 2': True}}\n
\n
\n\n

A message template matches a message if all keys of the template are\ncontained in the message and the values in the message validate against\nthe respective schemas:

\n\n
\n
>>> t.check(Message('Example Sender',\n...                 {'key 1': 'value', 'key 2': 'some string',\n...                  'key 3': {'key 1': 42, 'key 2': None}}))\nTrue\n
\n
\n\n

An empty mapping therefore matches all messages:

\n\n
\n
>>> t = MessageTemplate()\n>>> t.check(Message('Example Sender', {'arbitrary': 'content'}))\nTrue\n
\n
\n", "bases": "typing.Dict[str, bool | typing.Dict[str, None | str | int | float | bool | typing.Dict[str, typing.Any] | typing.List[typing.Any]]]"}, "controlpi.messagebus.MessageTemplate.__init__": {"fullname": "controlpi.messagebus.MessageTemplate.__init__", "modulename": "controlpi.messagebus", "qualname": "MessageTemplate.__init__", "kind": "function", "doc": "

Initialise message.

\n\n

Template is initialised empty or with given key-value pairs:

\n\n
\n
>>> t = MessageTemplate()\n>>> print(t)\n{}\n>>> t = MessageTemplate({'key': {'const': 'value'}})\n>>> print(t)\n{'key': {'const': 'value'}}\n
\n
\n", "signature": "(\tinit: Dict[str, bool | Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]]] | None = None)"}, "controlpi.messagebus.MessageTemplate.from_message": {"fullname": "controlpi.messagebus.MessageTemplate.from_message", "modulename": "controlpi.messagebus", "qualname": "MessageTemplate.from_message", "kind": "function", "doc": "

Create template from message.

\n\n

Template witch constant schemas is created from message:

\n\n
\n
>>> m = Message('Example Sender', {'key': 'value'})\n>>> t = MessageTemplate.from_message(m)\n>>> print(t)\n{'sender': {'const': 'Example Sender'}, 'key': {'const': 'value'}}\n>>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},\n...                                'list': [None, True, 'string']})\n>>> t = MessageTemplate.from_message(m)\n>>> print(t)  # doctest: +NORMALIZE_WHITESPACE\n{'sender': {'const': 'Example Sender'},\n 'dict': {'type': 'object',\n          'properties': {'int': {'const': 42},\n                         'float': {'const': 42.42}}},\n 'list': {'type': 'array',\n          'items': [{'const': None},\n                    {'const': True},\n                    {'const': 'string'}]}}\n
\n
\n\n

This is especially useful for clients that send certain fully\npredefined messages, where the message is given in the configuration\nand the template for the registration can be constructed by this\nmethod.

\n", "signature": "(\tmessage: controlpi.messagebus.Message) -> controlpi.messagebus.MessageTemplate:", "funcdef": "def"}, "controlpi.messagebus.MessageTemplate.update": {"fullname": "controlpi.messagebus.MessageTemplate.update", "modulename": "controlpi.messagebus", "qualname": "MessageTemplate.update", "kind": "function", "doc": "

Override update to use validity checks.

\n\n
\n
>>> t = MessageTemplate()\n>>> t.update({'key 1': {'const': 'value'},\n...           'key 2': {'type': 'string'},\n...           'key 3': {'type': 'object',\n...                     'properties': {'key 1': {'type': 'number'},\n...                                    'key 2': True}}})\n>>> print(t)  # doctest: +NORMALIZE_WHITESPACE\n{'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},\n 'key 3': {'type': 'object',\n           'properties': {'key 1': {'type': 'number'},\n                          'key 2': True}}}\n>>> t.update({42: {'const': 'int key'}})\nTraceback (most recent call last):\n  ...\nTypeError: '42' is not a valid key in MessageTemplate (not a string).\n>>> t.update({'key': 'schema'})  # doctest: +NORMALIZE_WHITESPACE\nTraceback (most recent call last):\n  ...\nTypeError: 'schema' is not a valid value in MessageTemplate\n(not a valid JSON schema).\n>>> t.update({'key': True})\n
\n
\n\n

This is also used in __init__:

\n\n
\n
>>> t = MessageTemplate({'key 1': {'const': 'value'},\n...                      'key 2': {'type': 'string'},\n...                      'key 3': {'type': 'object',\n...                                'properties': {\n...                                    'key 1': {'type': 'number'},\n...                                    'key 2': True}}})\n>>> print(t)  # doctest: +NORMALIZE_WHITESPACE\n{'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},\n 'key 3': {'type': 'object',\n           'properties': {'key 1': {'type': 'number'},\n                          'key 2': True}}}\n>>> t = MessageTemplate({42: {'const': 'int key'}})\nTraceback (most recent call last):\n  ...\nTypeError: '42' is not a valid key in MessageTemplate (not a string).\n>>> t = MessageTemplate({'key': 'schema'})\n... # doctest: +NORMALIZE_WHITESPACE\nTraceback (most recent call last):\n  ...\nTypeError: 'schema' is not a valid value in MessageTemplate\n(not a valid JSON schema).\n>>> t = MessageTemplate({'key': True})\n
\n
\n", "signature": "(self, *args, **kwargs) -> None:", "funcdef": "def"}, "controlpi.messagebus.MessageTemplate.setdefault": {"fullname": "controlpi.messagebus.MessageTemplate.setdefault", "modulename": "controlpi.messagebus", "qualname": "MessageTemplate.setdefault", "kind": "function", "doc": "

Override setdefault to use validity checks.

\n\n
\n
>>> t = MessageTemplate()\n>>> t.setdefault('key 1', {'const': 'value'})\n{'const': 'value'}\n>>> t.setdefault('key 2', {'type': 'string'})\n{'type': 'string'}\n>>> t.setdefault('key 3', {'type': 'object',\n...                        'properties': {'key 1': {'type': 'number'},\n...                                       'key 2': True}})\n... # doctest: +NORMALIZE_WHITESPACE\n{'type': 'object',\n           'properties': {'key 1': {'type': 'number'},\n                          'key 2': True}}\n>>> t.setdefault(42, {'const': 'int key'})\nTraceback (most recent call last):\n  ...\nTypeError: '42' is not a valid key in MessageTemplate (not a string).\n>>> t.setdefault('key', 'schema')  # doctest: +NORMALIZE_WHITESPACE\nTraceback (most recent call last):\n  ...\nTypeError: 'schema' is not a valid value in MessageTemplate\n(not a valid JSON schema).\n
\n
\n\n

But __setitem__ is not called if the key is already present:

\n\n
\n
>>> t.setdefault('key 1', 'schema')\n{'const': 'value'}\n
\n
\n", "signature": "(\tself,\tkey: str,\tvalue: bool | Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]] | None = None) -> bool | Dict[str, None | str | int | float | bool | Dict[str, Any] | List[Any]]:", "funcdef": "def"}, "controlpi.messagebus.MessageTemplate.check": {"fullname": "controlpi.messagebus.MessageTemplate.check", "modulename": "controlpi.messagebus", "qualname": "MessageTemplate.check", "kind": "function", "doc": "

Check message against this template.

\n\n

Constant values have to match exactly:

\n\n
\n
>>> t = MessageTemplate({'key': {'const': 'value'}})\n>>> t.check(Message('Example Sender', {'key': 'value'}))\nTrue\n>>> t.check(Message('Example Sender', {'key': 'other value'}))\nFalse\n
\n
\n\n

But for integers, floats with the same value are also valid:

\n\n
\n
>>> t = MessageTemplate({'key': {'const': 42}})\n>>> t.check(Message('Example Sender', {'key': 42}))\nTrue\n>>> t.check(Message('Example Sender', {'key': 42.0}))\nTrue\n
\n
\n\n

Type integer is valid for floats with zero fractional part, but\nnot by floats with non-zero fractional part:

\n\n
\n
>>> t = MessageTemplate({'key': {'type': 'integer'}})\n>>> t.check(Message('Example Sender', {'key': 42}))\nTrue\n>>> t.check(Message('Example Sender', {'key': 42.0}))\nTrue\n>>> t.check(Message('Example Sender', {'key': 42.42}))\nFalse\n
\n
\n\n

Type number is valid for arbitrary ints or floats:

\n\n
\n
>>> t = MessageTemplate({'key': {'type': 'number'}})\n>>> t.check(Message('Example Sender', {'key': 42}))\nTrue\n>>> t.check(Message('Example Sender', {'key': 42.42}))\nTrue\n
\n
\n\n

All keys in template have to be present in message:

\n\n
\n
>>> t = MessageTemplate({'key 1': {'const': 'value'},\n...                      'key 2': {'type': 'string'},\n...                      'key 3': {'type': 'object',\n...                                'properties': {\n...                                    'key 1': {'type': 'number'},\n...                                    'key 2': True,\n...                                    'key 3': False}}})\n>>> t.check(Message('Example Sender',\n...                 {'key 1': 'value', 'key 2': 'some string'}))\nFalse\n
\n
\n\n

But for nested objects their properties do not necessarily have\nto be present:

\n\n
\n
>>> t.check(Message('Example Sender',\n...                 {'key 1': 'value', 'key 2': 'some string',\n...                  'key 3': {'key 1': 42}}))\nTrue\n
\n
\n\n

Schema True matches everything (even None):

\n\n
\n
>>> t.check(Message('Example Sender',\n...                 {'key 1': 'value', 'key 2': 'some string',\n...                  'key 3': {'key 2': None}}))\nTrue\n
\n
\n\n

Schema False matches nothing:

\n\n
\n
>>> t.check(Message('Example Sender',\n...                 {'key 1': 'value', 'key 2': 'some string',\n...                  'key 3': {'key 3': True}}))\nFalse\n
\n
\n\n

Message is valid for the constant template created from it:

\n\n
\n
>>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},\n...                                'list': [None, True, 'string']})\n>>> t = MessageTemplate.from_message(m)\n>>> t.check(m)\nTrue\n
\n
\n", "signature": "(self, message: controlpi.messagebus.Message) -> bool:", "funcdef": "def"}, "controlpi.messagebus.TemplateRegistry": {"fullname": "controlpi.messagebus.TemplateRegistry", "modulename": "controlpi.messagebus", "qualname": "TemplateRegistry", "kind": "class", "doc": "

Manage a collection of message templates with registered clients.

\n\n

A new TemplateRegistry is created by:

\n\n
\n
>>> r = TemplateRegistry()\n
\n
\n\n

Client names (strings) can be registered for message templates, which\nare mappings from keys to JSON schemas:

\n\n
\n
>>> r.insert({'k1': {'const': 'v1'}}, 'C 1')\n
\n
\n\n

The check function checks if the templates registered for a client\nmatch a given message:

\n\n
\n
>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.check('C 1', m)}")\n{'k1': 'v1', 'k2': 'v1'}: True\n{'k1': 'v1', 'k2': 2}: True\n{'k1': 'v2', 'k2': 'v1'}: False\n{'k1': 'v2', 'k2': 2}: False\n
\n
\n\n

Clients can be registered for values validating against arbitrary JSON\nschemas, e.g. all values of a certain type:

\n\n
\n
>>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}, 'C 2')\n>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.check('C 2', m)}")\n{'k1': 'v1', 'k2': 'v1'}: False\n{'k1': 'v1', 'k2': 2}: False\n{'k1': 'v2', 'k2': 'v1'}: True\n{'k1': 'v2', 'k2': 2}: False\n>>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}, 'C 3')\n>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.check('C 3', m)}")\n{'k1': 'v1', 'k2': 'v1'}: False\n{'k1': 'v1', 'k2': 2}: False\n{'k1': 'v2', 'k2': 'v1'}: False\n{'k1': 'v2', 'k2': 2}: True\n
\n
\n\n

The order of key-value pairs does not have to match the order in the\nmessages and keys can be left out:

\n\n
\n
>>> r.insert({'k2': {'const': 2}}, 'C 4')\n>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.check('C 4', m)}")\n{'k1': 'v1', 'k2': 'v1'}: False\n{'k1': 'v1', 'k2': 2}: True\n{'k1': 'v2', 'k2': 'v1'}: False\n{'k1': 'v2', 'k2': 2}: True\n
\n
\n\n

A registration for an empty template matches all messages:

\n\n
\n
>>> r.insert({}, 'C 5')\n>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.check('C 5', m)}")\n{'k1': 'v1', 'k2': 'v1'}: True\n{'k1': 'v1', 'k2': 2}: True\n{'k1': 'v2', 'k2': 'v1'}: True\n{'k1': 'v2', 'k2': 2}: True\n
\n
\n\n

A client can be registered for multiple templates:

\n\n
\n
>>> r.insert({'k1': {'const': 'v1'}}, 'C 6')\n>>> r.insert({'k2': {'const': 'v1'}}, 'C 6')\n>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.check('C 6', m)}")\n{'k1': 'v1', 'k2': 'v1'}: True\n{'k1': 'v1', 'k2': 2}: True\n{'k1': 'v2', 'k2': 'v1'}: True\n{'k1': 'v2', 'k2': 2}: False\n
\n
\n\n

Clients can be deregistered again (the result is False if the registry\nis empty after the deletion):

\n\n
\n
>>> r.insert({'k1': {'const': 'v1'}}, 'C 7')\n>>> r.delete('C 7')\nTrue\n>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.check('C 7', m)}")\n{'k1': 'v1', 'k2': 'v1'}: False\n{'k1': 'v1', 'k2': 2}: False\n{'k1': 'v2', 'k2': 'v1'}: False\n{'k1': 'v2', 'k2': 2}: False\n
\n
\n\n

The get function returns all clients with registered templates matching\na given message:

\n\n
\n
>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.get(m)}")\n{'k1': 'v1', 'k2': 'v1'}: ['C 5', 'C 1', 'C 6']\n{'k1': 'v1', 'k2': 2}: ['C 5', 'C 1', 'C 6', 'C 4']\n{'k1': 'v2', 'k2': 'v1'}: ['C 5', 'C 2', 'C 6']\n{'k1': 'v2', 'k2': 2}: ['C 5', 'C 3', 'C 4']\n
\n
\n\n

The get_templates function returns all templates for a given client:

\n\n
\n
>>> for c in ['C 1', 'C 2', 'C 3', 'C 4', 'C 5', 'C 6']:\n...     print(f"{c}: {r.get_templates(c)}")\nC 1: [{'k1': {'const': 'v1'}}]\nC 2: [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]\nC 3: [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]\nC 4: [{'k2': {'const': 2}}]\nC 5: [{}]\nC 6: [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]\n
\n
\n"}, "controlpi.messagebus.TemplateRegistry.__init__": {"fullname": "controlpi.messagebus.TemplateRegistry.__init__", "modulename": "controlpi.messagebus", "qualname": "TemplateRegistry.__init__", "kind": "function", "doc": "

Initialise an empty registry.

\n\n
\n
>>> r = TemplateRegistry()\n
\n
\n", "signature": "()"}, "controlpi.messagebus.TemplateRegistry.insert": {"fullname": "controlpi.messagebus.TemplateRegistry.insert", "modulename": "controlpi.messagebus", "qualname": "TemplateRegistry.insert", "kind": "function", "doc": "

Register a client for a template.

\n\n
\n
>>> r = TemplateRegistry()\n>>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'integer'}}, 'C 1')\n>>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'string'}}, 'C 2')\n>>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v1'}}, 'C 3')\n>>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v2'}}, 'C 4')\n>>> r.insert({}, 'C 5')\n
\n
\n", "signature": "(\tself,\ttemplate: controlpi.messagebus.MessageTemplate,\tclient: str,\tcallback: Callable[[controlpi.messagebus.Message], Coroutine[Any, Any, NoneType]] | None = None) -> None:", "funcdef": "def"}, "controlpi.messagebus.TemplateRegistry.delete": {"fullname": "controlpi.messagebus.TemplateRegistry.delete", "modulename": "controlpi.messagebus", "qualname": "TemplateRegistry.delete", "kind": "function", "doc": "

Unregister a client from all templates.

\n\n
\n
>>> r = TemplateRegistry()\n>>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'integer'}}, 'C 1')\n>>> r.insert({'k1': {'const': 'v1'}, 'k2': {'type': 'string'}}, 'C 2')\n>>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v1'}}, 'C 3')\n>>> r.insert({'k1': {'type': 'integer'}, 'k2': {'const': 'v2'}}, 'C 4')\n>>> r.insert({}, 'C 5')\n>>> r.delete('C 3')\nTrue\n>>> r.delete('C 4')\nTrue\n
\n
\n", "signature": "(self, client: str) -> bool:", "funcdef": "def"}, "controlpi.messagebus.TemplateRegistry.check": {"fullname": "controlpi.messagebus.TemplateRegistry.check", "modulename": "controlpi.messagebus", "qualname": "TemplateRegistry.check", "kind": "function", "doc": "

Get if a client has a registered template matching a message.

\n\n
\n
>>> r = TemplateRegistry()\n>>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')\n>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.check('Client 1', m)}")\n{'k1': 'v1', 'k2': 'v1'}: True\n{'k1': 'v1', 'k2': 2}: True\n{'k1': 'v2', 'k2': 'v1'}: False\n{'k1': 'v2', 'k2': 2}: False\n>>> r.insert({'k2': {'type': 'integer'}}, 'Client 2')\n>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.check('Client 2', m)}")\n{'k1': 'v1', 'k2': 'v1'}: False\n{'k1': 'v1', 'k2': 2}: True\n{'k1': 'v2', 'k2': 'v1'}: False\n{'k1': 'v2', 'k2': 2}: True\n
\n
\n", "signature": "(self, client: str, message: controlpi.messagebus.Message) -> bool:", "funcdef": "def"}, "controlpi.messagebus.TemplateRegistry.get": {"fullname": "controlpi.messagebus.TemplateRegistry.get", "modulename": "controlpi.messagebus", "qualname": "TemplateRegistry.get", "kind": "function", "doc": "

Get all clients registered for templates matching a message.

\n\n
\n
>>> r = TemplateRegistry()\n>>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')\n>>> r.insert({'k2': {'type': 'integer'}}, 'Client 2')\n>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},\n...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:\n...     print(f"{m}: {r.get(m)}")\n{'k1': 'v1', 'k2': 'v1'}: ['Client 1']\n{'k1': 'v1', 'k2': 2}: ['Client 1', 'Client 2']\n{'k1': 'v2', 'k2': 'v1'}: []\n{'k1': 'v2', 'k2': 2}: ['Client 2']\n
\n
\n", "signature": "(self, message: controlpi.messagebus.Message) -> List[str]:", "funcdef": "def"}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"fullname": "controlpi.messagebus.TemplateRegistry.get_callbacks", "modulename": "controlpi.messagebus", "qualname": "TemplateRegistry.get_callbacks", "kind": "function", "doc": "

Get all callbacks registered for templates matching a message.

\n", "signature": "(\tself,\tmessage: controlpi.messagebus.Message) -> List[Callable[[controlpi.messagebus.Message], Coroutine[Any, Any, NoneType]]]:", "funcdef": "def"}, "controlpi.messagebus.TemplateRegistry.get_templates": {"fullname": "controlpi.messagebus.TemplateRegistry.get_templates", "modulename": "controlpi.messagebus", "qualname": "TemplateRegistry.get_templates", "kind": "function", "doc": "

Get all templates for a client.

\n\n
\n
>>> r = TemplateRegistry()\n>>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')\n>>> r.get_templates('Client 1')\n[{'k1': {'const': 'v1'}}]\n>>> r.insert({'k1': {'const': 'v2'},\n...           'k2': {'type': 'string'}}, 'Client 2')\n>>> r.get_templates('Client 2')\n[{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]\n>>> r.insert({'k1': {'const': 'v2'},\n...           'k2': {'type': 'integer'}}, 'Client 3')\n>>> r.get_templates('Client 3')\n[{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]\n>>> r.insert({'k2': {'const': 2}}, 'Client 4')\n>>> r.get_templates('Client 4')\n[{'k2': {'const': 2}}]\n>>> r.insert({}, 'Client 5')\n>>> r.get_templates('Client 5')\n[{}]\n>>> r.insert({'k1': {'const': 'v1'}}, 'Client 6')\n>>> r.insert({'k2': {'const': 'v1'}}, 'Client 6')\n>>> r.get_templates('Client 6')\n[{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]\n
\n
\n", "signature": "(self, client: str) -> List[controlpi.messagebus.MessageTemplate]:", "funcdef": "def"}, "controlpi.messagebus.BusException": {"fullname": "controlpi.messagebus.BusException", "modulename": "controlpi.messagebus", "qualname": "BusException", "kind": "class", "doc": "

Raise for errors in using message bus.

\n", "bases": "builtins.Exception"}, "controlpi.messagebus.MessageBus": {"fullname": "controlpi.messagebus.MessageBus", "modulename": "controlpi.messagebus", "qualname": "MessageBus", "kind": "class", "doc": "

Provide an asynchronous message bus.

\n\n

The bus executes asynchronous callbacks for all messages to be received\nby a client. We use a simple callback printing the message in all\nexamples:

\n\n
\n
>>> def callback_for_receiver(receiver):\n...     print(f"Creating callback for {receiver}.")\n...     async def callback(message):\n...         print(f"{receiver}: {message}")\n...     return callback\n
\n
\n\n

Clients can be registered at the bus with a name, lists of message\ntemplates they want to use for sending and receiving and a callback\nfunction for receiving. An empty list of templates means that the\nclient does not want to send or receive any messages, respectively.\nA list with an empty template means that it wants to send arbitrary\nor receive all messages, respectively:

\n\n
\n
>>> async def setup(bus):\n...     print("Setting up.")\n...     bus.register('Logger', 'Test Plugin',\n...                  [],\n...                  [([MessageTemplate({})],\n...                    callback_for_receiver('Logger'))])\n...     bus.register('Client 1', 'Test Plugin',\n...                  [MessageTemplate({'k1': {'type': 'string'}})],\n...                  [([MessageTemplate({'target':\n...                                      {'const': 'Client 1'}})],\n...                    callback_for_receiver('Client 1'))])\n...     bus.register('Client 2', 'Test Plugin',\n...                  [MessageTemplate({})],\n...                  [([MessageTemplate({'target':\n...                                      {'const': 'Client 2'}})],\n...                    callback_for_receiver('Client 2'))])\n
\n
\n\n

The bus itself is addressed by the empty string. It sends messages for\neach registration and deregestration of a client with a key 'event' and\na value of 'registered' or 'unregistered', a key 'client' with the\nclient's name as value and for registrations also keys 'sends' and\n'receives' with all templates registered for the client for sending and\nreceiving.

\n\n

Clients can send to the bus with the send function. Each message has to\ndeclare a sender. The send templates of that sender are checked for a\ntemplate matching the message. We cannot prevent arbitrary code from\nimpersonating any sender, but this should only be done in debugging or\nmanagement situations.

\n\n

Messages that are intended for a specific client by convention have a\nkey 'target' with the target client's name as value. Such messages are\noften commands to the client to do something, which is by convention\nindicated by a key 'command' with a value that indicates what should be\ndone.

\n\n

The bus, for example, reacts to a message with 'target': '' and\n'command': 'get clients' by sending one message for each currently\nregistered with complete information about its registered send and\nreceive templates.

\n\n
\n
>>> async def send(bus):\n...     print("Sending messages.")\n...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})\n...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})\n...     await bus.send({'sender': '', 'target': '',\n...                     'command': 'get clients'})\n
\n
\n\n

The run function executes the message bus forever. If we want to stop\nit, we have to explicitly cancel the task:

\n\n
\n
>>> async def main():\n...     bus = MessageBus()\n...     await setup(bus)\n...     bus_task = asyncio.create_task(bus.run())\n...     await send(bus)\n...     await asyncio.sleep(0)\n...     bus_task.cancel()\n...     try:\n...         await bus_task\n...     except asyncio.exceptions.CancelledError:\n...         pass\n>>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE\nSetting up.\nCreating callback for Logger.\nCreating callback for Client 1.\nCreating callback for Client 2.\nSending messages.\nLogger: {'sender': '', 'event': 'registered',\n         'client': 'Logger', 'plugin': 'Test Plugin',\n         'sends': [], 'receives': [{}]}\nLogger: {'sender': '', 'event': 'registered',\n         'client': 'Client 1', 'plugin': 'Test Plugin',\n         'sends': [{'k1': {'type': 'string'}}],\n         'receives': [{'target': {'const': 'Client 1'}}]}\nLogger: {'sender': '', 'event': 'registered',\n         'client': 'Client 2', 'plugin': 'Test Plugin',\n         'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}\nLogger: {'sender': 'Client 1', 'k1': 'Test'}\nLogger: {'sender': 'Client 2', 'target': 'Client 1'}\nClient 1: {'sender': 'Client 2', 'target': 'Client 1'}\nLogger: {'sender': '', 'target': '', 'command': 'get clients'}\nLogger: {'sender': '', 'client': 'Logger', 'plugin': 'Test Plugin',\n         'sends': [], 'receives': [{}]}\nLogger: {'sender': '', 'client': 'Client 1', 'plugin': 'Test Plugin',\n         'sends': [{'k1': {'type': 'string'}}],\n         'receives': [{'target': {'const': 'Client 1'}}]}\nLogger: {'sender': '', 'client': 'Client 2', 'plugin': 'Test Plugin',\n         'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}\n
\n
\n"}, "controlpi.messagebus.MessageBus.__init__": {"fullname": "controlpi.messagebus.MessageBus.__init__", "modulename": "controlpi.messagebus", "qualname": "MessageBus.__init__", "kind": "function", "doc": "

Initialise a new bus without clients.

\n\n
\n
>>> async def main():\n...     bus = MessageBus()\n>>> asyncio.run(main())\n
\n
\n", "signature": "()"}, "controlpi.messagebus.MessageBus.register": {"fullname": "controlpi.messagebus.MessageBus.register", "modulename": "controlpi.messagebus", "qualname": "MessageBus.register", "kind": "function", "doc": "

Register a client at the message bus.

\n\n
\n
>>> async def callback(message):\n...     print(message)\n>>> async def main():\n...     bus = MessageBus()\n...     bus.register('Logger', 'Test Plugin',\n...                  [],    # send nothing\n...                  [([MessageTemplate({})],  # receive everything\n...                    callback)])\n...     bus.register('Client 1', 'Test Plugin',\n...                  [MessageTemplate({'k1': {'type': 'string'}})],\n...                      # send with key 'k1' and string value\n...                  [([MessageTemplate({'target':\n...                                      {'const': 'Client 1'}})],\n...                      # receive for this client\n...                    callback)])\n...     bus.register('Client 2', 'Test Plugin',\n...                  [MessageTemplate({})],  # send arbitrary\n...                  [([MessageTemplate({'target':\n...                                      {'const': 'Client 2'}})],\n...                      # receive for this client\n...                    callback)])\n>>> asyncio.run(main())\n
\n
\n", "signature": "(\tself,\tclient: str,\tplugin: str,\tsends: Iterable[controlpi.messagebus.MessageTemplate],\treceives: Iterable[Tuple[Iterable[controlpi.messagebus.MessageTemplate], Callable[[controlpi.messagebus.Message], Coroutine[Any, Any, NoneType]]]]) -> None:", "funcdef": "def"}, "controlpi.messagebus.MessageBus.unregister": {"fullname": "controlpi.messagebus.MessageBus.unregister", "modulename": "controlpi.messagebus", "qualname": "MessageBus.unregister", "kind": "function", "doc": "

Unregister a client from the message bus.

\n\n
\n
>>> async def callback(message):\n...     print(message)\n>>> async def main():\n...     bus = MessageBus()\n...     bus.register('Client 1', 'Test Plugin',\n...                  [MessageTemplate({'k1': {'type': 'string'}})],\n...                  [([MessageTemplate({'target':\n...                                      {'const': 'Client 1'}})],\n...                    callback)])\n...     bus.unregister('Client 1')\n>>> asyncio.run(main())\n
\n
\n", "signature": "(self, client: str) -> None:", "funcdef": "def"}, "controlpi.messagebus.MessageBus.run": {"fullname": "controlpi.messagebus.MessageBus.run", "modulename": "controlpi.messagebus", "qualname": "MessageBus.run", "kind": "function", "doc": "

Run the message bus forever.

\n\n
\n
>>> async def main():\n...     bus = MessageBus()\n...     bus_task = asyncio.create_task(bus.run())\n...     bus_task.cancel()\n...     try:\n...         await bus_task\n...     except asyncio.exceptions.CancelledError:\n...         pass\n>>> asyncio.run(main())\n
\n
\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi.messagebus.MessageBus.send": {"fullname": "controlpi.messagebus.MessageBus.send", "modulename": "controlpi.messagebus", "qualname": "MessageBus.send", "kind": "function", "doc": "

Send a message to the message bus.

\n\n
\n
>>> async def callback(message):\n...     print(f"Got: {message}")\n>>> async def main():\n...     bus = MessageBus()\n...     bus.register('Client 1', 'Test Plugin',\n...                  [MessageTemplate({'k1': {'type': 'string'}})],\n...                  [([MessageTemplate({'target':\n...                                      {'const': 'Client 1'}})],\n...                    callback)])\n...     bus.register('Client 2', 'Test Plugin',\n...                  [MessageTemplate({})],\n...                  [([MessageTemplate({'target':\n...                                      {'const': 'Client 2'}})],\n...                    callback)])\n...     bus_task = asyncio.create_task(bus.run())\n...     await bus.send({'sender': 'Client 1', 'target': 'Client 2',\n...                     'k1': 'Test'})\n...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})\n...     try:\n...         await bus.send({'sender': 'Client 1', 'target': 'Client 2',\n...                         'k1': 42})\n...     except BusException as e:\n...         print(e)\n...     await asyncio.sleep(0)\n...     bus_task.cancel()\n...     try:\n...         await bus_task\n...     except asyncio.exceptions.CancelledError:\n...         pass\n>>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE\nMessage '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'\nnot allowed for sender 'Client 1'.\nGot: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}\nGot: {'sender': 'Client 2', 'target': 'Client 1'}\n
\n
\n", "signature": "(self, message: controlpi.messagebus.Message) -> None:", "funcdef": "async def"}, "controlpi.messagebus.MessageBus.send_nowait": {"fullname": "controlpi.messagebus.MessageBus.send_nowait", "modulename": "controlpi.messagebus", "qualname": "MessageBus.send_nowait", "kind": "function", "doc": "

Send a message to the message bus without blocking.

\n\n
\n
>>> async def callback(message):\n...     print(f"Got: {message}")\n>>> async def main():\n...     bus = MessageBus()\n...     bus.register('Client 1', 'Test Plugin',\n...                  [MessageTemplate({'k1': {'type': 'string'}})],\n...                  [([MessageTemplate({'target':\n...                                      {'const': 'Client 1'}})],\n...                    callback)])\n...     bus.register('Client 2', 'Test Plugin',\n...                  [MessageTemplate({})],\n...                  [([MessageTemplate({'target':\n...                                      {'const': 'Client 2'}})],\n...                    callback)])\n...     bus_task = asyncio.create_task(bus.run())\n...     bus.send_nowait({'sender': 'Client 1', 'target': 'Client 2',\n...                      'k1': 'Test'})\n...     bus.send_nowait({'sender': 'Client 2', 'target': 'Client 1'})\n...     try:\n...         bus.send_nowait({'sender': 'Client 1',\n...                          'target': 'Client 2', 'k1': 42})\n...     except BusException as e:\n...         print(e)\n...     await asyncio.sleep(0)\n...     bus_task.cancel()\n...     try:\n...         await bus_task\n...     except asyncio.exceptions.CancelledError:\n...         pass\n>>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE\nMessage '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'\nnot allowed for sender 'Client 1'.\nGot: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}\nGot: {'sender': 'Client 2', 'target': 'Client 1'}\n
\n
\n", "signature": "(self, message: controlpi.messagebus.Message) -> None:", "funcdef": "def"}, "controlpi.pluginregistry": {"fullname": "controlpi.pluginregistry", "modulename": "controlpi.pluginregistry", "kind": "module", "doc": "

Provide a generic plugin system.

\n\n

The class PluginRegistry is initialised with the name of a namespace\npackage and a base class.

\n\n

All modules in the namespace package are loaded. These modules can be\nincluded in different distribution packages, which allows to dynamically\nadd plugins to the system without changing any code.

\n\n

Afterwards, all (direct and indirect) subclasses of the base class are\nregistered as plugins under their class name. Class names should be unique,\nwhich cannot be programmatically enforced.

\n\n
\n
>>> class BasePlugin:\n...     pass\n>>> class Plugin1(BasePlugin):\n...     pass\n>>> class Plugin2(BasePlugin):\n...     pass\n>>> registry = PluginRegistry('importlib', BasePlugin)\n
\n
\n\n

The registry provides a generic mapping interface with the class names as\nkeys and the classes as values.

\n\n
\n
>>> print(len(registry))\n2\n>>> for name in registry:\n...     print(f"{name}: {registry[name]}")\nPlugin1: <class 'pluginregistry.Plugin1'>\nPlugin2: <class 'pluginregistry.Plugin2'>\n>>> if 'Plugin1' in registry:\n...     print(f"'Plugin1' is in registry.")\n'Plugin1' is in registry.\n>>> p1 = registry['Plugin1']\n>>> i1 = p1()\n
\n
\n"}, "controlpi.pluginregistry.PluginRegistry": {"fullname": "controlpi.pluginregistry.PluginRegistry", "modulename": "controlpi.pluginregistry", "qualname": "PluginRegistry", "kind": "class", "doc": "

Provide a registry for plugins.

\n\n

Initialise the registry by loading all modules in the given namespace\npackage and then registering all subclasses of the given base class as\nplugins (only simulated here \u2013 the code for Plugin1 and Plugin2 should\nbe in modules in the given namespace package in real applications):

\n\n
\n
>>> class BasePlugin:\n...     pass\n>>> class Plugin1(BasePlugin):\n...     pass\n>>> class Plugin2(BasePlugin):\n...     pass\n>>> registry = PluginRegistry('importlib', BasePlugin)\n
\n
\n\n

After initialisation, provide a mapping interface to the plugins:

\n\n
\n
>>> print(len(registry))\n2\n>>> for name in registry:\n...     print(f"{name}: {registry[name]}")\nPlugin1: <class 'pluginregistry.Plugin1'>\nPlugin2: <class 'pluginregistry.Plugin2'>\n>>> if 'Plugin1' in registry:\n...     print(f"'Plugin1' is in registry.")\n'Plugin1' is in registry.\n
\n
\n", "bases": "collections.abc.Mapping"}, "controlpi.pluginregistry.PluginRegistry.__init__": {"fullname": "controlpi.pluginregistry.PluginRegistry.__init__", "modulename": "controlpi.pluginregistry", "qualname": "PluginRegistry.__init__", "kind": "function", "doc": "

Initialise registry.

\n\n

Import all modules defined in the given namespace package (in any\ndistribution package currently installed in the path). Then register\nall subclasses of the given base class as plugins.

\n\n
\n
>>> class BasePlugin:\n...     pass\n>>> class Plugin1(BasePlugin):\n...     pass\n>>> class Plugin2(BasePlugin):\n...     pass\n>>> registry = PluginRegistry('importlib', BasePlugin)\n>>> for name in registry._plugins:\n...     print(f"{name}: {registry._plugins[name]}")\nPlugin1: <class 'pluginregistry.Plugin1'>\nPlugin2: <class 'pluginregistry.Plugin2'>\n
\n
\n", "signature": "(namespace_package: str, base_class: type)"}, "controlpi_plugins": {"fullname": "controlpi_plugins", "modulename": "controlpi_plugins", "kind": "module", "doc": "

\n"}, "controlpi_plugins.state": {"fullname": "controlpi_plugins.state", "modulename": "controlpi_plugins.state", "kind": "module", "doc": "

Provide state plugins for all kinds of systems.

\n\n
    \n
  • State represents a Boolean state.
  • \n
  • StateAlias translates to another state-like client.
  • \n
  • AndState combines several state-like clients by conjunction.
  • \n
  • OrState combines several state-like clients by disjunction.
  • \n
  • AndSet sets a state due to a conjunction of other state-like clients.
  • \n
  • OrSet sets a state due to a disjunction of other state-like clients.
  • \n
\n\n

All these plugins use the following conventions:

\n\n
    \n
  • If their state changes they send a message containing \"event\": \"changed\"\nand \"state\": NEW STATE.
  • \n
  • If their state is reported due to a message, but did not change they send\na message containing just \"state\": CURRENT STATE.
  • \n
  • If they receive a message containing \"target\": NAME and\n\"command\": \"get state\" they report their current state.
  • \n
  • If State (or any other settable state using these conventions) receives\na message containing \"target\": NAME, \"command\": \"set state\" and\n\"new state\": STATE TO SET it changes the state accordingly. If this\nwas really a change the corresponding event is sent. If it was already in\nthis state a report message without \"event\": \"changed\" is sent.
  • \n
  • StateAlias can alias any message bus client using these conventions, not\njust State instances. It translates all messages described here in both\ndirections.
  • \n
  • AndState and OrState instances cannot be set.
  • \n
  • AndState and OrState can combine any message bus clients using these\nconventions, not just State instances. They only react to messages\ncontaining \"state\" information.
  • \n
\n\n
\n
>>> import asyncio\n>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test State": {"plugin": "State"},\n...      "Test State 2": {"plugin": "State"},\n...      "Test State 3": {"plugin": "State"},\n...      "Test State 4": {"plugin": "State"},\n...      "Test StateAlias": {"plugin": "StateAlias",\n...                          "alias for": "Test State 2"},\n...      "Test AndState": {"plugin": "AndState",\n...                        "states": ["Test State", "Test StateAlias"]},\n...      "Test OrState": {"plugin": "OrState",\n...                       "states": ["Test State", "Test StateAlias"]},\n...      "Test AndSet": {"plugin": "AndSet",\n...                      "input states": ["Test State", "Test StateAlias"],\n...                      "output state": "Test State 3"},\n...      "Test OrSet": {"plugin": "OrSet",\n...                      "input states": ["Test State", "Test StateAlias"],\n...                      "output state": "Test State 4"}},\n...     [{"target": "Test AndState",\n...       "command": "get state"},\n...      {"target": "Test OrState",\n...       "command": "get state"},\n...      {"target": "Test State",\n...       "command": "set state", "new state": True},\n...      {"target": "Test StateAlias",\n...       "command": "set state", "new state": True},\n...      {"target": "Test State",\n...       "command": "set state", "new state": False}]))\n... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\ntest(): {'sender': '', 'event': 'registered', ...\ntest(): {'sender': 'test()', 'target': 'Test AndState', 'command': 'get state'}\ntest(): {'sender': 'test()', 'target': 'Test OrState', 'command': 'get state'}\ntest(): {'sender': 'Test AndState', 'state': False}\ntest(): {'sender': 'test()', 'target': 'Test State',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test OrState', 'state': False}\ntest(): {'sender': 'test()', 'target': 'Test StateAlias',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test State', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test State',\n         'command': 'set state', 'new state': False}\ntest(): {'sender': 'Test StateAlias', 'target': 'Test State 2',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}\ntest(): {'sender': 'Test OrSet', 'target': 'Test State 4',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test State', 'event': 'changed', 'state': False}\ntest(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}\ntest(): {'sender': 'Test State 4', 'event': 'changed', 'state': True}\n
\n
\n"}, "controlpi_plugins.state.State": {"fullname": "controlpi_plugins.state.State", "modulename": "controlpi_plugins.state", "qualname": "State", "kind": "class", "doc": "

Provide a Boolean state.

\n\n

The state of a State plugin instance can be queried with the \"get state\"\ncommand and set with the \"set state\" command to the new state given by\nthe \"new state\" key:

\n\n
\n
>>> import asyncio\n>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test State": {"plugin": "State"}},\n...     [{"target": "Test State", "command": "get state"},\n...      {"target": "Test State", "command": "set state",\n...       "new state": True},\n...      {"target": "Test State", "command": "set state",\n...       "new state": True},\n...      {"target": "Test State", "command": "get state"}]))\n... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\ntest(): {'sender': '', 'event': 'registered', ...\ntest(): {'sender': 'test()', 'target': 'Test State',\n         'command': 'get state'}\ntest(): {'sender': 'test()', 'target': 'Test State',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test State', 'state': False}\ntest(): {'sender': 'test()', 'target': 'Test State',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test State', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test State',\n         'command': 'get state'}\ntest(): {'sender': 'Test State', 'state': True}\ntest(): {'sender': 'Test State', 'state': True}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.state.State.CONF_SCHEMA": {"fullname": "controlpi_plugins.state.State.CONF_SCHEMA", "modulename": "controlpi_plugins.state", "qualname": "State.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for State plugin configuration.

\n\n

There are no required or optional configuration keys.

\n", "default_value": "True"}, "controlpi_plugins.state.State.process_conf": {"fullname": "controlpi_plugins.state.State.process_conf", "modulename": "controlpi_plugins.state", "qualname": "State.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.state.State.run": {"fullname": "controlpi_plugins.state.State.run", "modulename": "controlpi_plugins.state", "qualname": "State.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.state.StateAlias": {"fullname": "controlpi_plugins.state.StateAlias", "modulename": "controlpi_plugins.state", "qualname": "StateAlias", "kind": "class", "doc": "

Define an alias for another state.

\n\n

The \"alias for\" configuration key gets the name for the other state that\nis aliased by the StateAlias plugin instance.

\n\n

The \"get state\" and \"set state\" commands are forwarded to and the\n\"changed\" events and \"state\" messages are forwarded from this other\nstate:

\n\n
\n
>>> import asyncio\n>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test State": {"plugin": "State"},\n...      "Test StateAlias": {"plugin": "StateAlias",\n...                          "alias for": "Test State"}},\n...     [{"target": "Test State", "command": "get state"},\n...      {"target": "Test StateAlias", "command": "set state",\n...       "new state": True},\n...      {"target": "Test State", "command": "set state",\n...       "new state": True},\n...      {"target": "Test StateAlias", "command": "get state"}]))\n... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\ntest(): {'sender': '', 'event': 'registered', ...\ntest(): {'sender': 'test()', 'target': 'Test State',\n         'command': 'get state'}\ntest(): {'sender': 'test()', 'target': 'Test StateAlias',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test State', 'state': False}\ntest(): {'sender': 'test()', 'target': 'Test State',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test StateAlias', 'target': 'Test State',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test StateAlias', 'state': False}\ntest(): {'sender': 'test()', 'target': 'Test StateAlias',\n         'command': 'get state'}\ntest(): {'sender': 'Test State', 'event': 'changed', 'state': True}\ntest(): {'sender': 'Test State', 'state': True}\ntest(): {'sender': 'Test StateAlias', 'target': 'Test State',\n         'command': 'get state'}\ntest(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True}\ntest(): {'sender': 'Test StateAlias', 'state': True}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"fullname": "controlpi_plugins.state.StateAlias.CONF_SCHEMA", "modulename": "controlpi_plugins.state", "qualname": "StateAlias.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for StateAlias plugin configuration.

\n\n

Required configuration key:

\n\n
    \n
  • 'alias for': name of aliased state.
  • \n
\n", "default_value": "{'properties': {'alias for': {'type': 'string'}}, 'required': ['alias for']}"}, "controlpi_plugins.state.StateAlias.process_conf": {"fullname": "controlpi_plugins.state.StateAlias.process_conf", "modulename": "controlpi_plugins.state", "qualname": "StateAlias.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.state.StateAlias.run": {"fullname": "controlpi_plugins.state.StateAlias.run", "modulename": "controlpi_plugins.state", "qualname": "StateAlias.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.state.AndState": {"fullname": "controlpi_plugins.state.AndState", "modulename": "controlpi_plugins.state", "qualname": "AndState", "kind": "class", "doc": "

Define conjunction of states.

\n\n

The \"states\" configuration key gets an array of states to be combined.\nAn AndState plugin client reacts to \"get state\" commands and sends\n\"changed\" events when a change in one of the combined states leads to\na change for the conjunction:

\n\n
\n
>>> import asyncio\n>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test State 1": {"plugin": "State"},\n...      "Test State 2": {"plugin": "State"},\n...      "Test AndState": {"plugin": "AndState",\n...                        "states": ["Test State 1", "Test State 2"]}},\n...     [{"target": "Test State 1", "command": "set state",\n...       "new state": True},\n...      {"target": "Test State 2", "command": "set state",\n...       "new state": True},\n...      {"target": "Test State 1", "command": "set state",\n...       "new state": False},\n...      {"target": "Test AndState", "command": "get state"},\n...      {"target": "Test AndState", "command": "get sources"}]))\n... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\ntest(): {'sender': '', 'event': 'registered', ...\ntest(): {'sender': 'test()', 'target': 'Test State 1',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'test()', 'target': 'Test State 2',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test State 1',\n         'command': 'set state', 'new state': False}\ntest(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test AndState',\n         'command': 'get state'}\ntest(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}\ntest(): {'sender': 'Test AndState', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test AndState',\n         'command': 'get sources'}\ntest(): {'sender': 'Test AndState', 'state': True}\ntest(): {'sender': 'Test AndState', 'event': 'changed', 'state': False}\ntest(): {'sender': 'Test AndState',\n         'states': ['Test State 1', 'Test State 2']}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"fullname": "controlpi_plugins.state.AndState.CONF_SCHEMA", "modulename": "controlpi_plugins.state", "qualname": "AndState.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for AndState plugin configuration.

\n\n

Required configuration key:

\n\n
    \n
  • 'states': list of names of combined states.
  • \n
\n", "default_value": "{'properties': {'states': {'type': 'array', 'items': {'type': 'string'}}}, 'required': ['states']}"}, "controlpi_plugins.state.AndState.process_conf": {"fullname": "controlpi_plugins.state.AndState.process_conf", "modulename": "controlpi_plugins.state", "qualname": "AndState.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.state.AndState.run": {"fullname": "controlpi_plugins.state.AndState.run", "modulename": "controlpi_plugins.state", "qualname": "AndState.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.state.OrState": {"fullname": "controlpi_plugins.state.OrState", "modulename": "controlpi_plugins.state", "qualname": "OrState", "kind": "class", "doc": "

Define disjunction of states.

\n\n

The \"states\" configuration key gets an array of states to be combined.\nAn OrState plugin client reacts to \"get state\" commands and sends\n\"changed\" events when a change in one of the combined states leads to\na change for the disjunction:

\n\n
\n
>>> import asyncio\n>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test State 1": {"plugin": "State"},\n...      "Test State 2": {"plugin": "State"},\n...      "Test OrState": {"plugin": "OrState",\n...                       "states": ["Test State 1", "Test State 2"]}},\n...     [{"target": "Test State 1", "command": "set state",\n...       "new state": True},\n...      {"target": "Test State 2", "command": "set state",\n...       "new state": True},\n...      {"target": "Test State 1", "command": "set state",\n...       "new state": False},\n...      {"target": "Test OrState", "command": "get state"},\n...      {"target": "Test OrState", "command": "get sources"}]))\n... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\ntest(): {'sender': '', 'event': 'registered', ...\ntest(): {'sender': 'test()', 'target': 'Test State 1',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'test()', 'target': 'Test State 2',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test State 1',\n         'command': 'set state', 'new state': False}\ntest(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}\ntest(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test OrState',\n         'command': 'get state'}\ntest(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}\ntest(): {'sender': 'test()', 'target': 'Test OrState',\n         'command': 'get sources'}\ntest(): {'sender': 'Test OrState', 'state': True}\ntest(): {'sender': 'Test OrState',\n         'states': ['Test State 1', 'Test State 2']}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"fullname": "controlpi_plugins.state.OrState.CONF_SCHEMA", "modulename": "controlpi_plugins.state", "qualname": "OrState.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for OrState plugin configuration.

\n\n

Required configuration key:

\n\n
    \n
  • 'states': list of names of combined states.
  • \n
\n", "default_value": "{'properties': {'states': {'type': 'array', 'items': {'type': 'string'}}}, 'required': ['states']}"}, "controlpi_plugins.state.OrState.process_conf": {"fullname": "controlpi_plugins.state.OrState.process_conf", "modulename": "controlpi_plugins.state", "qualname": "OrState.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.state.OrState.run": {"fullname": "controlpi_plugins.state.OrState.run", "modulename": "controlpi_plugins.state", "qualname": "OrState.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.state.AndSet": {"fullname": "controlpi_plugins.state.AndSet", "modulename": "controlpi_plugins.state", "qualname": "AndSet", "kind": "class", "doc": "

Set state based on conjunction of other states.

\n\n

The \"input states\" configuration key gets an array of states used to\ndetermine the state in the \"output state\" configuration key:

\n\n
\n
>>> import asyncio\n>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test State 1": {"plugin": "State"},\n...      "Test State 2": {"plugin": "State"},\n...      "Test State 3": {"plugin": "State"},\n...      "Test AndSet": {"plugin": "AndSet",\n...                      "input states": ["Test State 1",\n...                                       "Test State 2"],\n...                      "output state": "Test State 3"}},\n...     [{"target": "Test State 1", "command": "set state",\n...       "new state": True},\n...      {"target": "Test State 2", "command": "set state",\n...       "new state": True},\n...      {"target": "Test AndSet", "command": "get state"},\n...      {"target": "Test State 1", "command": "set state",\n...       "new state": False},\n...      {"target": "Test AndSet", "command": "get state"},\n...      {"target": "Test AndSet", "command": "get sources"}]))\n... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\ntest(): {'sender': '', 'event': 'registered', ...\ntest(): {'sender': 'test()', 'target': 'Test State 1',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'test()', 'target': 'Test State 2',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test AndSet',\n         'command': 'get state'}\ntest(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test State 1',\n         'command': 'set state', 'new state': False}\ntest(): {'sender': 'Test AndSet', 'target': 'Test State 3',\n         'command': 'set state', 'new state': False}\ntest(): {'sender': 'Test AndSet', 'target': 'Test State 3',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'test()', 'target': 'Test AndSet',\n         'command': 'get state'}\ntest(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}\ntest(): {'sender': 'Test State 3', 'state': False}\ntest(): {'sender': 'Test State 3', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test AndSet',\n         'command': 'get sources'}\ntest(): {'sender': 'Test AndSet', 'target': 'Test State 3',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test AndSet', 'target': 'Test State 3',\n         'command': 'set state', 'new state': False}\ntest(): {'sender': 'Test AndSet',\n         'states': ['Test State 1', 'Test State 2']}\ntest(): {'sender': 'Test State 3', 'state': True}\ntest(): {'sender': 'Test State 3', 'event': 'changed', 'state': False}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"fullname": "controlpi_plugins.state.AndSet.CONF_SCHEMA", "modulename": "controlpi_plugins.state", "qualname": "AndSet.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for AndSet plugin configuration.

\n\n

Required configuration keys:

\n\n
    \n
  • 'input states': list of names of combined states.
  • \n
  • 'output state': name of state to be set.
  • \n
\n", "default_value": "{'properties': {'input states': {'type': 'array', 'items': {'type': 'string'}}, 'output state': {'type': 'string'}}, 'required': ['input states', 'output state']}"}, "controlpi_plugins.state.AndSet.process_conf": {"fullname": "controlpi_plugins.state.AndSet.process_conf", "modulename": "controlpi_plugins.state", "qualname": "AndSet.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.state.AndSet.run": {"fullname": "controlpi_plugins.state.AndSet.run", "modulename": "controlpi_plugins.state", "qualname": "AndSet.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.state.OrSet": {"fullname": "controlpi_plugins.state.OrSet", "modulename": "controlpi_plugins.state", "qualname": "OrSet", "kind": "class", "doc": "

Set state based on disjunction of other states.

\n\n

The \"input states\" configuration key gets an array of states used to\ndetermine the state in the \"output state\" configuration key:

\n\n
\n
>>> import asyncio\n>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test State 1": {"plugin": "State"},\n...      "Test State 2": {"plugin": "State"},\n...      "Test State 3": {"plugin": "State"},\n...      "Test OrSet": {"plugin": "OrSet",\n...                      "input states": ["Test State 1",\n...                                       "Test State 2"],\n...                      "output state": "Test State 3"}},\n...     [{"target": "Test State 1", "command": "set state",\n...       "new state": True},\n...      {"target": "Test OrSet", "command": "get state"},\n...      {"target": "Test State 2", "command": "set state",\n...       "new state": True},\n...      {"target": "Test State 1", "command": "set state",\n...       "new state": False},\n...      {"target": "Test OrSet", "command": "get sources"}]))\n... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\ntest(): {'sender': '', 'event': 'registered', ...\ntest(): {'sender': 'test()', 'target': 'Test State 1',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'test()', 'target': 'Test OrSet',\n         'command': 'get state'}\ntest(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test State 2',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'Test OrSet', 'target': 'Test State 3',\n         'command': 'set state', 'new state': False}\ntest(): {'sender': 'Test OrSet', 'target': 'Test State 3',\n         'command': 'set state', 'new state': True}\ntest(): {'sender': 'test()', 'target': 'Test State 1',\n         'command': 'set state', 'new state': False}\ntest(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}\ntest(): {'sender': 'Test State 3', 'state': False}\ntest(): {'sender': 'Test State 3', 'event': 'changed', 'state': True}\ntest(): {'sender': 'test()', 'target': 'Test OrSet',\n         'command': 'get sources'}\ntest(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}\ntest(): {'sender': 'Test OrSet',\n         'states': ['Test State 1', 'Test State 2']}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"fullname": "controlpi_plugins.state.OrSet.CONF_SCHEMA", "modulename": "controlpi_plugins.state", "qualname": "OrSet.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for OrSet plugin configuration.

\n\n

Required configuration keys:

\n\n
    \n
  • 'input states': list of names of combined states.
  • \n
  • 'output state': name of state to be set.
  • \n
\n", "default_value": "{'properties': {'input states': {'type': 'array', 'items': {'type': 'string'}}, 'output state': {'type': 'string'}}, 'required': ['input states', 'output state']}"}, "controlpi_plugins.state.OrSet.process_conf": {"fullname": "controlpi_plugins.state.OrSet.process_conf", "modulename": "controlpi_plugins.state", "qualname": "OrSet.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.state.OrSet.run": {"fullname": "controlpi_plugins.state.OrSet.run", "modulename": "controlpi_plugins.state", "qualname": "OrSet.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.util": {"fullname": "controlpi_plugins.util", "modulename": "controlpi_plugins.util", "kind": "module", "doc": "

Provide utility plugins for all kinds of systems.

\n\n
    \n
  • Log logs messages on stdout.
  • \n
  • Init sends list of messages on startup and on demand.
  • \n
  • Execute sends configurable list of messages on demand.
  • \n
  • Alias translates messages to an alias.
  • \n
\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test Log": {"plugin": "Log",\n...                   "filter": [{"sender": {"const": "Test Alias"}}]},\n...      "Test Init": {"plugin": "Init",\n...                    "messages": [{"id": 42, "content": "Test Message"}]},\n...      "Test Alias": {"plugin": "Alias",\n...                     "from": {"sender": {"const": "Test Init"},\n...                              "id": {"const": 42}},\n...                     "to": {"id": "translated"}}}, []))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Log', 'plugin': 'Log',\n         'sends': [], 'receives': [{'sender': {'const': 'Test Alias'}}]}\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Init', 'plugin': 'Init',\n         'sends': [{'id': {'const': 42},\n                    'content': {'const': 'Test Message'}}],\n         'receives': [{'target': {'const': 'Test Init'},\n                       'command': {'const': 'execute'}}]}\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Alias', 'plugin': 'Alias',\n         'sends': [{'id': {'const': 'translated'}}],\n         'receives': [{'sender': {'const': 'Test Init'},\n                       'id': {'const': 42}}]}\ntest(): {'sender': 'Test Init', 'id': 42,\n         'content': 'Test Message'}\ntest(): {'sender': 'Test Alias', 'id': 'translated',\n         'content': 'Test Message'}\nTest Log: {'sender': 'Test Alias', 'id': 'translated',\n           'content': 'Test Message'}\n
\n
\n"}, "controlpi_plugins.util.Log": {"fullname": "controlpi_plugins.util.Log", "modulename": "controlpi_plugins.util", "qualname": "Log", "kind": "class", "doc": "

Log messages on stdout.

\n\n

The \"filter\" configuration key gets a list of message templates defining\nthe messages that should be logged by the plugin instance.

\n\n

In the following example the first and third message match the given\ntemplate and are logged by the instance \"Test Log\", while the second\nmessage does not match and is only logged by the test, but not by the\nLog instance:

\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test Log": {"plugin": "Log",\n...                   "filter": [{"id": {"const": 42}}]}},\n...     [{"id": 42, "message": "Test Message"},\n...      {"id": 42.42, "message": "Second Message"},\n...      {"id": 42, "message": "Third Message"}]))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Log', 'plugin': 'Log',\n         'sends': [], 'receives': [{'id': {'const': 42}}]}\ntest(): {'sender': 'test()', 'id': 42, 'message': 'Test Message'}\nTest Log: {'sender': 'test()', 'id': 42, 'message': 'Test Message'}\ntest(): {'sender': 'test()', 'id': 42.42, 'message': 'Second Message'}\ntest(): {'sender': 'test()', 'id': 42, 'message': 'Third Message'}\nTest Log: {'sender': 'test()', 'id': 42, 'message': 'Third Message'}\n
\n
\n\n

The \"filter\" key is required:

\n\n
\n
>>> asyncio.run(controlpi.test(\n...     {"Test Log": {"plugin": "Log"}}, []))\ndata must contain ['filter'] properties\nConfiguration for 'Test Log' is not valid.\n
\n
\n\n

The \"filter\" key has to contain a list of message templates, i.e.,\nJSON objects:

\n\n
\n
>>> asyncio.run(controlpi.test(\n...     {"Test Log": {"plugin": "Log",\n...                   "filter": [42]}}, []))\ndata.filter[0] must be object\nConfiguration for 'Test Log' is not valid.\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"fullname": "controlpi_plugins.util.Log.CONF_SCHEMA", "modulename": "controlpi_plugins.util", "qualname": "Log.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for Log plugin configuration.

\n\n

Required configuration key:

\n\n
    \n
  • 'filter': list of message templates to be logged.
  • \n
\n", "default_value": "{'properties': {'filter': {'type': 'array', 'items': {'type': 'object'}}}, 'required': ['filter']}"}, "controlpi_plugins.util.Log.process_conf": {"fullname": "controlpi_plugins.util.Log.process_conf", "modulename": "controlpi_plugins.util", "qualname": "Log.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.util.Log.run": {"fullname": "controlpi_plugins.util.Log.run", "modulename": "controlpi_plugins.util", "qualname": "Log.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.util.Init": {"fullname": "controlpi_plugins.util.Init", "modulename": "controlpi_plugins.util", "qualname": "Init", "kind": "class", "doc": "

Send list of messages on startup and on demand.

\n\n

The \"messages\" configuration key gets a list of messages to be sent on\nstartup. The same list is sent in reaction to a message with\n\"target\": NAME and \"command\": \"execute\".

\n\n

In the example, the two configured messages are sent twice, once at\nstartup and a second time in reaction to the \"execute\" command sent by\nthe test:

\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test Init": {"plugin": "Init",\n...                    "messages": [{"id": 42,\n...                                  "content": "Test Message"},\n...                                 {"id": 42.42,\n...                                  "content": "Second Message"}]}},\n...     [{"target": "Test Init", "command": "execute"}]))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Init', 'plugin': 'Init',\n         'sends': [{'id': {'const': 42},\n                    'content': {'const': 'Test Message'}},\n                   {'id': {'const': 42.42},\n                    'content': {'const': 'Second Message'}}],\n         'receives': [{'target': {'const': 'Test Init'},\n                       'command': {'const': 'execute'}}]}\ntest(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}\ntest(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}\ntest(): {'sender': 'test()', 'target': 'Test Init', 'command': 'execute'}\ntest(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}\ntest(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}\n
\n
\n\n

The \"messages\" key is required:

\n\n
\n
>>> asyncio.run(controlpi.test(\n...     {"Test Init": {"plugin": "Init"}}, []))\ndata must contain ['messages'] properties\nConfiguration for 'Test Init' is not valid.\n
\n
\n\n

The \"messages\" key has to contain a list of (partial) messages, i.e.,\nJSON objects:

\n\n
\n
>>> asyncio.run(controlpi.test(\n...     {"Test Init": {"plugin": "Init",\n...                    "messages": [42]}}, []))\ndata.messages[0] must be object\nConfiguration for 'Test Init' is not valid.\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"fullname": "controlpi_plugins.util.Init.CONF_SCHEMA", "modulename": "controlpi_plugins.util", "qualname": "Init.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for Init plugin configuration.

\n\n

Required configuration key:

\n\n
    \n
  • 'messages': list of messages to be sent.
  • \n
\n", "default_value": "{'properties': {'messages': {'type': 'array', 'items': {'type': 'object'}}}, 'required': ['messages']}"}, "controlpi_plugins.util.Init.process_conf": {"fullname": "controlpi_plugins.util.Init.process_conf", "modulename": "controlpi_plugins.util", "qualname": "Init.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.util.Init.run": {"fullname": "controlpi_plugins.util.Init.run", "modulename": "controlpi_plugins.util", "qualname": "Init.run", "kind": "function", "doc": "

Send configured messages on startup.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.util.Execute": {"fullname": "controlpi_plugins.util.Execute", "modulename": "controlpi_plugins.util", "qualname": "Execute", "kind": "class", "doc": "

Send configurable list of messages on demand.

\n\n

An Execute plugin instance receives two kinds of commands.\nThe \"set messages\" command has a \"messages\" key with a list of (partial)\nmessages, which are sent by the Execute instance in reaction to an\n\"execute\" command.

\n\n

In the example, the first command sent by the test sets two messages,\nwhich are then sent in reaction to the second command sent by the test:

\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test Execute": {"plugin": "Execute"}},\n...     [{"target": "Test Execute", "command": "set messages",\n...       "messages": [{"id": 42, "content": "Test Message"},\n...                    {"id": 42.42, "content": "Second Message"}]},\n...      {"target": "Test Execute", "command": "execute"}]))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Execute', 'plugin': 'Execute',\n         'sends': [{}],\n         'receives': [{'target': {'const': 'Test Execute'},\n                       'command': {'const': 'set messages'},\n                       'messages': {'type': 'array',\n                                    'items': {'type': 'object'}}},\n                      {'target': {'const': 'Test Execute'},\n                       'command': {'const': 'execute'}}]}\ntest(): {'sender': 'test()', 'target': 'Test Execute',\n         'command': 'set messages',\n         'messages': [{'id': 42, 'content': 'Test Message'},\n                      {'id': 42.42, 'content': 'Second Message'}]}\ntest(): {'sender': 'test()', 'target': 'Test Execute',\n         'command': 'execute'}\ntest(): {'sender': 'Test Execute', 'id': 42,\n         'content': 'Test Message'}\ntest(): {'sender': 'Test Execute', 'id': 42.42,\n         'content': 'Second Message'}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"fullname": "controlpi_plugins.util.Execute.CONF_SCHEMA", "modulename": "controlpi_plugins.util", "qualname": "Execute.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for Execute plugin configuration.

\n\n

There are no required or optional configuration keys.

\n", "default_value": "True"}, "controlpi_plugins.util.Execute.process_conf": {"fullname": "controlpi_plugins.util.Execute.process_conf", "modulename": "controlpi_plugins.util", "qualname": "Execute.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.util.Execute.run": {"fullname": "controlpi_plugins.util.Execute.run", "modulename": "controlpi_plugins.util", "qualname": "Execute.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.util.Alias": {"fullname": "controlpi_plugins.util.Alias", "modulename": "controlpi_plugins.util", "qualname": "Alias", "kind": "class", "doc": "

Translate messages to an alias.

\n\n

The \"from\" configuration key gets a message template and the\nconfiguration key \"to\" a (partial) message. The \"translate\"\nconfiguration key contains pairs of message keys, where the \"from\"\nmessage key is translated to the \"to\" message key if present in the\nmessage.

\n\n

All messages matching the \"from\" template are received by the Alias\ninstance and a message translated by adding the keys and values of the\n\"to\" message and the translated key-value pairs according to\n\"translate\" is sent. Keys that are not \"sender\" and not modified by\n\"to\" or \"translate\" are retained.

\n\n

In the example, the two messages sent by the test are translated by the\nAlias instance and the translated messages are sent by it preserving\nthe \"content\" keys:

\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test Alias": {"plugin": "Alias",\n...                     "from": {"id": {"const": 42}},\n...                     "to": {"id": "translated"},\n...                     "translate": [{'from': "old", "to": "new"}]}},\n...     [{"id": 42, "content": "Test Message", "old": "content"},\n...      {"id": 42, "content": "Second Message", "old": "content"}]))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Alias', 'plugin': 'Alias',\n         'sends': [{'id': {'const': 'translated'}}],\n         'receives': [{'id': {'const': 42}}]}\ntest(): {'sender': 'test()', 'id': 42,\n         'content': 'Test Message', 'old': 'content'}\ntest(): {'sender': 'test()', 'id': 42,\n         'content': 'Second Message', 'old': 'content'}\ntest(): {'sender': 'Test Alias', 'id': 'translated',\n         'content': 'Test Message', 'old': 'content', 'new': 'content'}\ntest(): {'sender': 'Test Alias', 'id': 'translated',\n         'content': 'Second Message', 'old': 'content', 'new': 'content'}\n
\n
\n\n

An Alias instance can also translate to a list of messages instead of\na single message:

\n\n
\n
>>> asyncio.run(controlpi.test(\n...     {"Test Alias": {"plugin": "Alias",\n...                     "from": {"id": {"const": 42}},\n...                     "to": [{"id": "first"}, {"id": "second"}],\n...                     "translate": [{'from': "old", "to": "new"}]}},\n...     [{"id": 42, "content": "Test Message", "old": "content"}]))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Alias', 'plugin': 'Alias',\n         'sends': [{'id': {'const': 'first'}},\n                   {'id': {'const': 'second'}}],\n         'receives': [{'id': {'const': 42}}]}\ntest(): {'sender': 'test()', 'id': 42,\n         'content': 'Test Message', 'old': 'content'}\ntest(): {'sender': 'Test Alias', 'id': 'first',\n         'content': 'Test Message', 'old': 'content', 'new': 'content'}\ntest(): {'sender': 'Test Alias', 'id': 'second',\n         'content': 'Test Message', 'old': 'content', 'new': 'content'}\n
\n
\n\n

The \"from\" key is required:

\n\n
\n
>>> asyncio.run(controlpi.test(\n...     {"Test Alias": {"plugin": "Alias"}}, []))\ndata must contain ['from'] properties\nConfiguration for 'Test Alias' is not valid.\n
\n
\n\n

The \"from\" key has to contain a message template and the \"to\" key a\n(partial) message, i.e., both have to be JSON objects:

\n\n
\n
>>> asyncio.run(controlpi.test(\n...     {"Test Alias": {"plugin": "Alias",\n...                     "from": 42,\n...                     "to": 42}}, []))\ndata.from must be object\nConfiguration for 'Test Alias' is not valid.\n>>> asyncio.run(controlpi.test(\n...     {"Test Alias": {"plugin": "Alias",\n...                     "from": {"id": {"const": 42}},\n...                     "to": 42}}, []))\ndata.to cannot be validated by any definition\nConfiguration for 'Test Alias' is not valid.\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"fullname": "controlpi_plugins.util.Alias.CONF_SCHEMA", "modulename": "controlpi_plugins.util", "qualname": "Alias.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for Alias plugin configuration.

\n\n

Required configuration keys:

\n\n
    \n
  • 'from': template of messages to be translated.
  • \n
\n\n

Optional configuration keys:

\n\n
    \n
  • 'to': translated message(s) to be sent.
  • \n
  • 'translate': array of pairs of keys to be translated.
  • \n
\n", "default_value": "{'properties': {'from': {'type': 'object'}, 'to': {'anyOf': [{'type': 'object'}, {'type': 'array', 'items': {'type': 'object'}}]}, 'translate': {'type': 'array', 'items': {'type': 'object', 'properties': {'from': {'type': 'string'}, 'to': {'type': 'string'}}}}}, 'required': ['from']}"}, "controlpi_plugins.util.Alias.process_conf": {"fullname": "controlpi_plugins.util.Alias.process_conf", "modulename": "controlpi_plugins.util", "qualname": "Alias.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.util.Alias.run": {"fullname": "controlpi_plugins.util.Alias.run", "modulename": "controlpi_plugins.util", "qualname": "Alias.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.util.Counter": {"fullname": "controlpi_plugins.util.Counter", "modulename": "controlpi_plugins.util", "qualname": "Counter", "kind": "class", "doc": "

Count messages confirming to a given template.

\n\n

The plugin counts messages confirming to the given template. The\ncounter can be queried and reset by commands. The 'reset' command also\nqueries the last count before the reset:

\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test Counter": {"plugin": "Counter",\n...                       "count": {"id": {"const": 42}}}},\n...     [{"target": "Test Counter", "command": "get count"},\n...      {"id": 42}, {"id": 42}, {"id": 49},\n...      {"target": "Test Counter", "command": "get count"},\n...      {"id": 42}, {"id": 42}, {"id": 42},\n...      {"target": "Test Counter", "command": "reset"},\n...      {"target": "Test Counter", "command": "get count"},\n...      {"id": 42}, {"id": 42}, {"id": 42},\n...      {"target": "Test Counter", "command": "get count"}]))\n... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Counter', 'plugin': 'Counter',\n         'sends': [{'count': {'type': 'integer'}}],\n         'receives': [{'id': {'const': 42}},\n                      {'target': {'const': 'Test Counter'},\n                       'command': {'const': 'get count'}},\n                      {'target': {'const': 'Test Counter'},\n                       'command': {'const': 'reset'}}]}\ntest(): {'sender': 'test()', 'target': 'Test Counter',\n         'command': 'get count'}\ntest(): {'sender': 'test()', 'id': 42}\ntest(): {'sender': 'Test Counter', 'count': 0,\n         'since': ..., 'until': ...}\ntest(): {'sender': 'test()', 'id': 42}\ntest(): {'sender': 'test()', 'id': 49}\ntest(): {'sender': 'test()', 'target': 'Test Counter',\n         'command': 'get count'}\ntest(): {'sender': 'test()', 'id': 42}\ntest(): {'sender': 'Test Counter', 'count': 2,\n         'since': ..., 'until': ...}\ntest(): {'sender': 'test()', 'id': 42}\ntest(): {'sender': 'test()', 'id': 42}\ntest(): {'sender': 'test()', 'target': 'Test Counter',\n         'command': 'reset'}\ntest(): {'sender': 'test()', 'target': 'Test Counter',\n         'command': 'get count'}\ntest(): {'sender': 'Test Counter', 'count': 5,\n         'since': ..., 'until': ...}\ntest(): {'sender': 'test()', 'id': 42}\ntest(): {'sender': 'Test Counter', 'count': 0,\n         'since': ..., 'until': ...}\ntest(): {'sender': 'test()', 'id': 42}\ntest(): {'sender': 'test()', 'id': 42}\ntest(): {'sender': 'test()', 'target': 'Test Counter',\n         'command': 'get count'}\ntest(): {'sender': 'Test Counter', 'count': 3,\n         'since': ..., 'until': ...}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"fullname": "controlpi_plugins.util.Counter.CONF_SCHEMA", "modulename": "controlpi_plugins.util", "qualname": "Counter.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for Counter plugin configuration.

\n\n

Required configuration key:

\n\n
    \n
  • 'count': template of messages to be counted.
  • \n
\n", "default_value": "{'properties': {'count': {'type': 'object'}, 'date format': {'type': 'string', 'default': '%Y-%m-%d %H:%M:%S'}}, 'required': ['count']}"}, "controlpi_plugins.util.Counter.process_conf": {"fullname": "controlpi_plugins.util.Counter.process_conf", "modulename": "controlpi_plugins.util", "qualname": "Counter.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.util.Counter.run": {"fullname": "controlpi_plugins.util.Counter.run", "modulename": "controlpi_plugins.util", "qualname": "Counter.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.util.Date": {"fullname": "controlpi_plugins.util.Date", "modulename": "controlpi_plugins.util", "qualname": "Date", "kind": "class", "doc": "

Send message with current date.

\n\n

The plugin reacts to 'get date' commands by sending messages with\na 'date' key:

\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test Date": {"plugin": "Date"}},\n...     [{"target": "Test Date", "command": "get date"}]))\n... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Date', 'plugin': 'Date',\n         'sends': [{'date': {'type': 'string'}}],\n         'receives': [{'target': {'const': 'Test Date'},\n                       'command': {'const': 'get date'}}]}\ntest(): {'sender': 'test()', 'target': 'Test Date',\n         'command': 'get date'}\ntest(): {'sender': 'Test Date', 'date': ...}\n
\n
\n\n

The format of the date can be configured with the 'format'\nconfiguration key:

\n\n
\n
>>> asyncio.run(controlpi.test(\n...     {"Test Date": {"plugin": "Date",\n...                    "format": "%Y%m%d%H%M%S%f"}},\n...     [{"target": "Test Date", "command": "get date"}]))\n... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Date', 'plugin': 'Date',\n         'sends': [{'date': {'type': 'string'}}],\n         'receives': [{'target': {'const': 'Test Date'},\n                       'command': {'const': 'get date'}}]}\ntest(): {'sender': 'test()', 'target': 'Test Date',\n         'command': 'get date'}\ntest(): {'sender': 'Test Date', 'date': ...}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"fullname": "controlpi_plugins.util.Date.CONF_SCHEMA", "modulename": "controlpi_plugins.util", "qualname": "Date.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for Date plugin configuration.

\n\n

Optional configuration key:

\n\n
    \n
  • 'format': format for the sent datetime string.\nDefault: '%Y-%m-%d %H:%M:%S'
  • \n
\n", "default_value": "{'properties': {'format': {'type': 'string', 'default': '%Y-%m-%d %H:%M:%S'}}}"}, "controlpi_plugins.util.Date.process_conf": {"fullname": "controlpi_plugins.util.Date.process_conf", "modulename": "controlpi_plugins.util", "qualname": "Date.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.util.Date.run": {"fullname": "controlpi_plugins.util.Date.run", "modulename": "controlpi_plugins.util", "qualname": "Date.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.wait": {"fullname": "controlpi_plugins.wait", "modulename": "controlpi_plugins.wait", "kind": "module", "doc": "

Provide waiting/sleeping plugins for all kinds of systems.

\n\n
    \n
  • Wait waits for time defined in configuration and sends \"finished\" event.
  • \n
  • GenericWait waits for time defined in \"wait\" command and sends \"finished\"\nevent with \"id\" string defined in \"wait\" command.
  • \n
\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test Wait": {"plugin": "Wait", "seconds": 0.01},\n...      "Test GenericWait": {"plugin": "GenericWait"}},\n...     [{"target": "Test GenericWait", "command": "wait",\n...       "seconds": 0.02, "id": "Long Wait"},\n...      {"target": "Test Wait", "command": "wait"}], 0.025))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test Wait', 'plugin': 'Wait',\n         'sends': [{'event': {'const': 'finished'}}],\n         'receives': [{'target': {'const': 'Test Wait'},\n                       'command': {'const': 'wait'}}]}\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test GenericWait', 'plugin': 'GenericWait',\n         'sends': [{'event': {'const': 'finished'},\n                    'id': {'type': 'string'}}],\n         'receives': [{'target': {'const': 'Test GenericWait'},\n                       'command': {'const': 'wait'},\n                       'seconds': {'type': 'number'},\n                       'id': {'type': 'string'}}]}\ntest(): {'sender': 'test()', 'target': 'Test GenericWait',\n         'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}\ntest(): {'sender': 'test()', 'target': 'Test Wait', 'command': 'wait'}\ntest(): {'sender': 'Test Wait', 'event': 'finished'}\ntest(): {'sender': 'Test GenericWait', 'event': 'finished',\n         'id': 'Long Wait'}\n
\n
\n"}, "controlpi_plugins.wait.Wait": {"fullname": "controlpi_plugins.wait.Wait", "modulename": "controlpi_plugins.wait", "qualname": "Wait", "kind": "class", "doc": "

Wait for time defined in configuration.

\n\n

The \"seconds\" configuration key gets the number of seconds to wait after\nreceiving a \"wait\" command before sending the \"finished\" event:

\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Long Wait": {"plugin": "Wait", "seconds": 0.02},\n...      "Short Wait": {"plugin": "Wait", "seconds": 0.01}},\n...     [{"target": "Long Wait", "command": "wait"},\n...      {"target": "Short Wait", "command": "wait"}], 0.025))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Long Wait', 'plugin': 'Wait',\n         'sends': [{'event': {'const': 'finished'}}],\n         'receives': [{'target': {'const': 'Long Wait'},\n                       'command': {'const': 'wait'}}]}\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Short Wait', 'plugin': 'Wait',\n         'sends': [{'event': {'const': 'finished'}}],\n         'receives': [{'target': {'const': 'Short Wait'},\n                       'command': {'const': 'wait'}}]}\ntest(): {'sender': 'test()', 'target': 'Long Wait', 'command': 'wait'}\ntest(): {'sender': 'test()', 'target': 'Short Wait', 'command': 'wait'}\ntest(): {'sender': 'Short Wait', 'event': 'finished'}\ntest(): {'sender': 'Long Wait', 'event': 'finished'}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"fullname": "controlpi_plugins.wait.Wait.CONF_SCHEMA", "modulename": "controlpi_plugins.wait", "qualname": "Wait.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for Wait plugin configuration.

\n\n

Required configuration key:

\n\n
    \n
  • 'seconds': number of seconds to wait.
  • \n
\n", "default_value": "{'properties': {'seconds': {'type': 'number'}}, 'required': ['seconds']}"}, "controlpi_plugins.wait.Wait.process_conf": {"fullname": "controlpi_plugins.wait.Wait.process_conf", "modulename": "controlpi_plugins.wait", "qualname": "Wait.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.wait.Wait.run": {"fullname": "controlpi_plugins.wait.Wait.run", "modulename": "controlpi_plugins.wait", "qualname": "Wait.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.wait.GenericWait": {"fullname": "controlpi_plugins.wait.GenericWait", "modulename": "controlpi_plugins.wait", "qualname": "GenericWait", "kind": "class", "doc": "

Wait for time defined in \"wait\" command.

\n\n

The \"wait\" command has message keys \"seconds\" defining the seconds to\nwait and \"id\" defining a string to be sent back in the \"finished\" event\nafter the wait:

\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Test GenericWait": {"plugin": "GenericWait"}},\n...     [{"target": "Test GenericWait", "command": "wait",\n...       "seconds": 0.02, "id": "Long Wait"},\n...      {"target": "Test GenericWait", "command": "wait",\n...       "seconds": 0.01, "id": "Short Wait"}], 0.025))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Test GenericWait', 'plugin': 'GenericWait',\n         'sends': [{'event': {'const': 'finished'},\n                    'id': {'type': 'string'}}],\n         'receives': [{'target': {'const': 'Test GenericWait'},\n                       'command': {'const': 'wait'},\n                       'seconds': {'type': 'number'},\n                       'id': {'type': 'string'}}]}\ntest(): {'sender': 'test()', 'target': 'Test GenericWait',\n         'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}\ntest(): {'sender': 'test()', 'target': 'Test GenericWait',\n         'command': 'wait', 'seconds': 0.01, 'id': 'Short Wait'}\ntest(): {'sender': 'Test GenericWait', 'event': 'finished',\n         'id': 'Short Wait'}\ntest(): {'sender': 'Test GenericWait', 'event': 'finished',\n         'id': 'Long Wait'}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"fullname": "controlpi_plugins.wait.GenericWait.CONF_SCHEMA", "modulename": "controlpi_plugins.wait", "qualname": "GenericWait.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for GenericWait plugin configuration.

\n\n

There are no required or optional configuration keys.

\n", "default_value": "True"}, "controlpi_plugins.wait.GenericWait.process_conf": {"fullname": "controlpi_plugins.wait.GenericWait.process_conf", "modulename": "controlpi_plugins.wait", "qualname": "GenericWait.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.wait.GenericWait.run": {"fullname": "controlpi_plugins.wait.GenericWait.run", "modulename": "controlpi_plugins.wait", "qualname": "GenericWait.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.wait.Timer": {"fullname": "controlpi_plugins.wait.Timer", "modulename": "controlpi_plugins.wait", "qualname": "Timer", "kind": "class", "doc": "

Timer that can be started and cancelled.

\n\n

The \"seconds\" configuration key gets the number of seconds to wait after\nreceiving a \"start\" command before sending the \"finished\" event.\nThe \"cancel\" command cancels all outstanding \"finished\" events and sends\na corresponding \"cancelled\" event:

\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Timer": {"plugin": "Timer", "seconds": 0.01}},\n...     [{"target": "Timer", "command": "start"},\n...      {"target": "Timer", "command": "start"},\n...      {"target": "Timer", "command": "cancel"},\n...      {"target": "Timer", "command": "start"},\n...      {"target": "Timer", "command": "start"}], 0.015))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Timer', 'plugin': 'Timer',\n         'sends': [{'event': {'const': 'finished'}},\n                   {'event': {'const': 'cancelled'}}],\n         'receives': [{'target': {'const': 'Timer'},\n                       'command': {'const': 'start'}},\n                      {'target': {'const': 'Timer'},\n                       'command': {'const': 'cancel'}}]}\ntest(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}\ntest(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}\ntest(): {'sender': 'test()', 'target': 'Timer', 'command': 'cancel'}\ntest(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}\ntest(): {'sender': 'Timer', 'event': 'cancelled', 'count': 2}\ntest(): {'sender': 'test()', 'target': 'Timer', 'command': 'start'}\ntest(): {'sender': 'Timer', 'event': 'finished'}\ntest(): {'sender': 'Timer', 'event': 'finished'}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"fullname": "controlpi_plugins.wait.Timer.CONF_SCHEMA", "modulename": "controlpi_plugins.wait", "qualname": "Timer.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for Timer plugin configuration.

\n\n

Required configuration key:

\n\n
    \n
  • 'seconds': number of seconds to wait.
  • \n
\n", "default_value": "{'properties': {'seconds': {'type': 'number'}}, 'required': ['seconds']}"}, "controlpi_plugins.wait.Timer.process_conf": {"fullname": "controlpi_plugins.wait.Timer.process_conf", "modulename": "controlpi_plugins.wait", "qualname": "Timer.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.wait.Timer.run": {"fullname": "controlpi_plugins.wait.Timer.run", "modulename": "controlpi_plugins.wait", "qualname": "Timer.run", "kind": "function", "doc": "

Run no code proactively.

\n", "signature": "(self) -> None:", "funcdef": "async def"}, "controlpi_plugins.wait.Periodic": {"fullname": "controlpi_plugins.wait.Periodic", "modulename": "controlpi_plugins.wait", "qualname": "Periodic", "kind": "class", "doc": "

Send message periodically.

\n\n

The \"seconds\" configuration key is the period of the repetition:\nreceiving a \"wait\" command before sending the \"finished\" event:

\n\n
\n
>>> import controlpi\n>>> asyncio.run(controlpi.test(\n...     {"Loop": {"plugin": "Periodic", "seconds": 0.01,\n...               "message": {"key": "value"}}},\n...     [], 0.025))\n... # doctest: +NORMALIZE_WHITESPACE\ntest(): {'sender': '', 'event': 'registered',\n         'client': 'Loop', 'plugin': 'Periodic',\n         'sends': [{'key': {'const': 'value'}}], 'receives': []}\ntest(): {'sender': 'Loop', 'key': 'value'}\ntest(): {'sender': 'Loop', 'key': 'value'}\n
\n
\n", "bases": "controlpi.baseplugin.BasePlugin"}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"fullname": "controlpi_plugins.wait.Periodic.CONF_SCHEMA", "modulename": "controlpi_plugins.wait", "qualname": "Periodic.CONF_SCHEMA", "kind": "variable", "doc": "

Schema for Wait plugin configuration.

\n\n

Required configuration key:

\n\n
    \n
  • 'seconds': period of repetition in seconds.
  • \n
  • 'message': message to send periodically.
  • \n
\n", "default_value": "{'properties': {'seconds': {'type': 'number'}, 'message': {'type': 'object'}}, 'required': ['seconds', 'message']}"}, "controlpi_plugins.wait.Periodic.process_conf": {"fullname": "controlpi_plugins.wait.Periodic.process_conf", "modulename": "controlpi_plugins.wait", "qualname": "Periodic.process_conf", "kind": "function", "doc": "

Register plugin as bus client.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "controlpi_plugins.wait.Periodic.run": {"fullname": "controlpi_plugins.wait.Periodic.run", "modulename": "controlpi_plugins.wait", "qualname": "Periodic.run", "kind": "function", "doc": "

Run periodic loop.

\n", "signature": "(self) -> None:", "funcdef": "async def"}}, "docInfo": {"controlpi": {"qualname": 0, "fullname": 1, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 82}, "controlpi.CONF_SCHEMA": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 26, "signature": 0, "bases": 0, "doc": 3}, "controlpi.run": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 41, "bases": 0, "doc": 536}, "controlpi.test": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 88, "bases": 0, "doc": 750}, "controlpi.baseplugin": {"qualname": 0, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 1755}, "controlpi.baseplugin.JSONSchema": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 22, "signature": 0, "bases": 0, "doc": 3}, "controlpi.baseplugin.PluginConf": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 4, "signature": 0, "bases": 0, "doc": 3}, "controlpi.baseplugin.ConfException": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 9}, "controlpi.baseplugin.BasePlugin": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 1076}, "controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"qualname": 3, "fullname": 5, "annotation": 18, "default_value": 1, "signature": 0, "bases": 0, "doc": 3}, "controlpi.baseplugin.BasePlugin.bus": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "controlpi.baseplugin.BasePlugin.name": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "controlpi.baseplugin.BasePlugin.conf": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "controlpi.baseplugin.BasePlugin.process_conf": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 50}, "controlpi.baseplugin.BasePlugin.run": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 49}, "controlpi.messagebus": {"qualname": 0, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 1501}, "controlpi.messagebus.MessageValue": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 18, "signature": 0, "bases": 0, "doc": 3}, "controlpi.messagebus.JSONSchema": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 22, "signature": 0, "bases": 0, "doc": 3}, "controlpi.messagebus.MessageCallback": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 12, "signature": 0, "bases": 0, "doc": 3}, "controlpi.messagebus.register_schema": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 90, "bases": 0, "doc": 12}, "controlpi.messagebus.validate": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 86, "bases": 0, "doc": 13}, "controlpi.messagebus.Message": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 20, "doc": 725}, "controlpi.messagebus.Message.__init__": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 103, "bases": 0, "doc": 178}, "controlpi.messagebus.Message.check_value": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 74, "bases": 0, "doc": 670}, "controlpi.messagebus.Message.update": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 28, "bases": 0, "doc": 556}, "controlpi.messagebus.Message.setdefault": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 152, "bases": 0, "doc": 340}, "controlpi.messagebus.MessageTemplate": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 24, "doc": 494}, "controlpi.messagebus.MessageTemplate.__init__": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 108, "bases": 0, "doc": 148}, "controlpi.messagebus.MessageTemplate.from_message": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 40, "bases": 0, "doc": 473}, "controlpi.messagebus.MessageTemplate.update": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 28, "bases": 0, "doc": 1029}, "controlpi.messagebus.MessageTemplate.setdefault": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 190, "bases": 0, "doc": 556}, "controlpi.messagebus.MessageTemplate.check": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 34, "bases": 0, "doc": 1682}, "controlpi.messagebus.TemplateRegistry": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3554}, "controlpi.messagebus.TemplateRegistry.__init__": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 4, "bases": 0, "doc": 37}, "controlpi.messagebus.TemplateRegistry.insert": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 109, "bases": 0, "doc": 416}, "controlpi.messagebus.TemplateRegistry.delete": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 476}, "controlpi.messagebus.TemplateRegistry.check": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 44, "bases": 0, "doc": 766}, "controlpi.messagebus.TemplateRegistry.get": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 40, "bases": 0, "doc": 466}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 78, "bases": 0, "doc": 12}, "controlpi.messagebus.TemplateRegistry.get_templates": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 40, "bases": 0, "doc": 762}, "controlpi.messagebus.BusException": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 10}, "controlpi.messagebus.MessageBus": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 1839}, "controlpi.messagebus.MessageBus.__init__": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 4, "bases": 0, "doc": 79}, "controlpi.messagebus.MessageBus.register": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 141, "bases": 0, "doc": 488}, "controlpi.messagebus.MessageBus.unregister": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 282}, "controlpi.messagebus.MessageBus.run": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 177}, "controlpi.messagebus.MessageBus.send": {"qualname": 2, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 34, "bases": 0, "doc": 852}, "controlpi.messagebus.MessageBus.send_nowait": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 34, "bases": 0, "doc": 848}, "controlpi.pluginregistry": {"qualname": 0, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 465}, "controlpi.pluginregistry.PluginRegistry": {"qualname": 1, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 376}, "controlpi.pluginregistry.PluginRegistry.__init__": {"qualname": 3, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 26, "bases": 0, "doc": 261}, "controlpi_plugins": {"qualname": 0, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "controlpi_plugins.state": {"qualname": 0, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 1650}, "controlpi_plugins.state.State": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 618}, "controlpi_plugins.state.State.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 1, "signature": 0, "bases": 0, "doc": 19}, "controlpi_plugins.state.State.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.state.State.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.state.StateAlias": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 810}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 30, "signature": 0, "bases": 0, "doc": 28}, "controlpi_plugins.state.StateAlias.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.state.StateAlias.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.state.AndState": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 948}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 40, "signature": 0, "bases": 0, "doc": 29}, "controlpi_plugins.state.AndState.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.state.AndState.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.state.OrState": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 923}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 40, "signature": 0, "bases": 0, "doc": 29}, "controlpi_plugins.state.OrState.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.state.OrState.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.state.AndSet": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 1260}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 60, "signature": 0, "bases": 0, "doc": 43}, "controlpi_plugins.state.AndSet.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.state.AndSet.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.state.OrSet": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 1056}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 60, "signature": 0, "bases": 0, "doc": 43}, "controlpi_plugins.state.OrSet.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.state.OrSet.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.util": {"qualname": 0, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 806}, "controlpi_plugins.util.Log": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 774}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 40, "signature": 0, "bases": 0, "doc": 30}, "controlpi_plugins.util.Log.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.util.Log.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.util.Init": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 850}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 40, "signature": 0, "bases": 0, "doc": 29}, "controlpi_plugins.util.Init.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.util.Init.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.util.Execute": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 685}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 1, "signature": 0, "bases": 0, "doc": 19}, "controlpi_plugins.util.Execute.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.util.Execute.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.util.Alias": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 1688}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 122, "signature": 0, "bases": 0, "doc": 64}, "controlpi_plugins.util.Alias.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.util.Alias.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.util.Counter": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 1198}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 52, "signature": 0, "bases": 0, "doc": 29}, "controlpi_plugins.util.Counter.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.util.Counter.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.util.Date": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 648}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 29, "signature": 0, "bases": 0, "doc": 34}, "controlpi_plugins.util.Date.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.util.Date.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.wait": {"qualname": 0, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 714}, "controlpi_plugins.wait.Wait": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 590}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 28, "signature": 0, "bases": 0, "doc": 28}, "controlpi_plugins.wait.Wait.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.wait.Wait.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.wait.GenericWait": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 623}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 1, "signature": 0, "bases": 0, "doc": 19}, "controlpi_plugins.wait.GenericWait.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.wait.GenericWait.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.wait.Timer": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 736}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 28, "signature": 0, "bases": 0, "doc": 28}, "controlpi_plugins.wait.Timer.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.wait.Timer.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 7}, "controlpi_plugins.wait.Periodic": {"qualname": 1, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 3, "doc": 292}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 44, "signature": 0, "bases": 0, "doc": 38}, "controlpi_plugins.wait.Periodic.process_conf": {"qualname": 3, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 8}, "controlpi_plugins.wait.Periodic.run": {"qualname": 2, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 6}}, "length": 119, "save": true}, "index": {"qualname": {"root": {"docs": {"controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 5, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "f": {"docs": {"controlpi.CONF_SCHEMA": {"tf": 1}, "controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}, "controlpi.baseplugin.BasePlugin.conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}}, "df": 36, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin.ConfException": {"tf": 1}}, "df": 1}}}}}}}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}}, "df": 4}}}}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}}, "df": 3}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}}, "df": 1}}}}}}}}}, "s": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {"controlpi.CONF_SCHEMA": {"tf": 1}, "controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 19}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}}, "df": 2}}}}}}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 2}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.State.run": {"tf": 1}}, "df": 4, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}}, "df": 4}}}}}}}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi_plugins.state.State.run": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 19}}, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 2}}}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}}, "df": 8}}}}}}}}, "s": {"docs": {"controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}}, "df": 1}}}}}}}}, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}}, "df": 4}}}}}, "j": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1}}, "df": 2}}}}}}}}}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "f": {"docs": {"controlpi.baseplugin.PluginConf": {"tf": 1}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 2}}}}}}}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}}, "df": 17}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 4}}}}}}}}, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}, "controlpi.baseplugin.BasePlugin.bus": {"tf": 1}, "controlpi.baseplugin.BasePlugin.name": {"tf": 1}, "controlpi.baseplugin.BasePlugin.conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}}, "df": 7}}}}}}}}}, "u": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin.BasePlugin.bus": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.BusException": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin.BasePlugin.name": {"tf": 1}}, "df": 1}}}, "o": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 1}}}}}}, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 6, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageValue": {"tf": 1}}, "df": 1}}}}}, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.messagebus.MessageCallback": {"tf": 1}}, "df": 1}}}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 6}}}}}}}}, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 7}}}}}}}}}}, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.validate": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1}}, "df": 1}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}}, "df": 9}}, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}}, "df": 1}}}}}}, "u": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}}, "df": 2}}}}}, "n": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus.MessageBus.unregister": {"tf": 1}}, "df": 1}}}}}}}}}}, "f": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 1}}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}}, "df": 4}}}}, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}}, "df": 3}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}}, "df": 4}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}}, "df": 4}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}}, "df": 4}}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}}, "df": 4}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}}, "df": 4}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}}, "df": 4}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}}, "df": 4}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}}, "df": 4}}}}}}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}}, "df": 4}}}}}}, "fullname": {"root": {"docs": {"controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 5, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {"controlpi": {"tf": 1}, "controlpi.CONF_SCHEMA": {"tf": 1}, "controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.JSONSchema": {"tf": 1}, "controlpi.baseplugin.PluginConf": {"tf": 1}, "controlpi.baseplugin.ConfException": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}, "controlpi.baseplugin.BasePlugin.bus": {"tf": 1}, "controlpi.baseplugin.BasePlugin.name": {"tf": 1}, "controlpi.baseplugin.BasePlugin.conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageValue": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1}, "controlpi.messagebus.MessageCallback": {"tf": 1}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.BusException": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.State.run": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 119}}}}}}, "f": {"docs": {"controlpi.CONF_SCHEMA": {"tf": 1}, "controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}, "controlpi.baseplugin.BasePlugin.conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}}, "df": 36, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin.ConfException": {"tf": 1}}, "df": 1}}}}}}}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}}, "df": 4}}}}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}}, "df": 3}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}}, "df": 1}}}}}}}}}, "s": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {"controlpi.CONF_SCHEMA": {"tf": 1}, "controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 19}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}}, "df": 2}}}}}}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 2}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.process_conf": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.run": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}}, "df": 25, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}}, "df": 4}}}}}}}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi_plugins.state.State.run": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 19}}, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 2}}}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}}, "df": 8}}}}}}}}, "s": {"docs": {"controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}}, "df": 1}}}}}}}}, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}}, "df": 4}}}}}, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.JSONSchema": {"tf": 1}, "controlpi.baseplugin.PluginConf": {"tf": 1}, "controlpi.baseplugin.ConfException": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.bus": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.name": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.conf": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1.4142135623730951}}, "df": 11}}}}}}}}}, "u": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin.BasePlugin.bus": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.BusException": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "j": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1}}, "df": 2}}}}}}}}}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "f": {"docs": {"controlpi.baseplugin.PluginConf": {"tf": 1}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.4142135623730951}}, "df": 3}}}}}}}}, "s": {"docs": {"controlpi_plugins": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.State.run": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 68}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}}, "df": 17}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 4}}}}}}}}, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin.BasePlugin.name": {"tf": 1}}, "df": 1}}}, "o": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 1}}}}}}, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 6, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageValue": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1}, "controlpi.messagebus.MessageCallback": {"tf": 1}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.BusException": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.run": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}}, "df": 33}}}, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageValue": {"tf": 1}}, "df": 1}}}}}, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.messagebus.MessageCallback": {"tf": 1}}, "df": 1}}}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 6}}}}}}}}}}}}}}}, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.validate": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1}}, "df": 1}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}}, "df": 9}}, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}}, "df": 1}}}}}}, "u": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}}, "df": 2}}}}}, "n": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus.MessageBus.unregister": {"tf": 1}}, "df": 1}}}}}}}}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {"controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}}, "df": 25}}}}, "f": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 1}}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}}, "df": 4}}}}, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}}, "df": 3}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}}, "df": 4}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}}, "df": 4}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}}, "df": 4}}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}}, "df": 4}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}}, "df": 4}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}}, "df": 4}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}}, "df": 4}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}}, "df": 4}}}}}}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait.run": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 17}}}}}}, "annotation": {"root": {"docs": {"controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 2.8284271247461903}}, "df": 1, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 1}}}}, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "[": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "[": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}}}}}}}, "default_value": {"root": {"docs": {"controlpi.CONF_SCHEMA": {"tf": 3}, "controlpi.baseplugin.JSONSchema": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageValue": {"tf": 2.449489742783178}, "controlpi.messagebus.JSONSchema": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageCallback": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 3.1622776601683795}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 3.605551275463989}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 3.605551275463989}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 4.123105625617661}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 4.123105625617661}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 3.605551275463989}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 3.605551275463989}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 6.164414002968976}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 3.872983346207417}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 2.8284271247461903}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 3.1622776601683795}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 3.1622776601683795}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 3.7416573867739413}}, "df": 18, "x": {"2": {"7": {"docs": {"controlpi.CONF_SCHEMA": {"tf": 3.4641016151377544}, "controlpi.messagebus.MessageCallback": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 3.4641016151377544}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 4.242640687119285}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 4.242640687119285}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 5.0990195135927845}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 5.0990195135927845}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 4.242640687119285}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 4.242640687119285}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 7.483314773547883}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 4.69041575982343}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 4.47213595499958}}, "df": 15}, "docs": {}, "df": 0}, "docs": {}, "df": 0}, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1.7320508075688772}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 2.8284271247461903}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 14}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 2}, "controlpi.baseplugin.PluginConf": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageValue": {"tf": 1.7320508075688772}, "controlpi.messagebus.JSONSchema": {"tf": 2}, "controlpi.messagebus.MessageCallback": {"tf": 1.7320508075688772}}, "df": 5}}}}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}}, "df": 3}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}}}}}, "o": {"docs": {"controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 1}}, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 2}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 6}}}}}, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 2}}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}}}}}}}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 13}}}}}}}}}}, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageValue": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1.4142135623730951}}, "df": 3}}}}, "d": {"docs": {"controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 2, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "[": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 1.4142135623730951}, "controlpi.baseplugin.PluginConf": {"tf": 1}, "controlpi.messagebus.MessageValue": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1.4142135623730951}}, "df": 4}}}}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}, "e": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 2}}}}}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 1}, "controlpi.messagebus.MessageValue": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1}}, "df": 3, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageCallback": {"tf": 1}}, "df": 1}}}}}}}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 3}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 1}, "controlpi.messagebus.MessageValue": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1}}, "df": 3, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 8}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 2, "s": {"docs": {"controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 4}}}}}, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 3}}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 1}, "controlpi.messagebus.MessageValue": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1}}, "df": 3}, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 2}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 7}}}}}, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 1}, "controlpi.messagebus.MessageValue": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1}}, "df": 3}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {"controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 1, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 2}}}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.7320508075688772}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 1.4142135623730951}, "controlpi.baseplugin.PluginConf": {"tf": 1}, "controlpi.messagebus.MessageValue": {"tf": 1.4142135623730951}, "controlpi.messagebus.JSONSchema": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageCallback": {"tf": 1.4142135623730951}}, "df": 5, "o": {"docs": {}, "df": 0, "f": {"docs": {"controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {"controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 7}}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "[": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.baseplugin.JSONSchema": {"tf": 1}, "controlpi.messagebus.MessageValue": {"tf": 1}, "controlpi.messagebus.JSONSchema": {"tf": 1}}, "df": 3}}}}}}}}}}}, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "[": {"docs": {}, "df": 0, "[": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "f": {"docs": {"controlpi.messagebus.MessageCallback": {"tf": 1}}, "df": 1}}}}}}}}}}}}}}}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "[": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.MessageCallback": {"tf": 1}}, "df": 1}}}}}}}}}}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "m": {"docs": {"controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 2, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageCallback": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 2, "s": {"docs": {"controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 12}}}}}}}}, "y": {"docs": {"controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 2}, "h": {"docs": {}, "df": 0, ":": {"docs": {}, "df": 0, "%": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, ":": {"docs": {}, "df": 0, "%": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 2}}}}}}}}}, "signature": {"root": {"0": {"docs": {"controlpi.test": {"tf": 1.4142135623730951}}, "df": 1}, "docs": {"controlpi.run": {"tf": 5.830951894845301}, "controlpi.test": {"tf": 8.48528137423857}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 3.4641016151377544}, "controlpi.baseplugin.BasePlugin.run": {"tf": 3.4641016151377544}, "controlpi.messagebus.register_schema": {"tf": 8.660254037844387}, "controlpi.messagebus.validate": {"tf": 8.426149773176359}, "controlpi.messagebus.Message.__init__": {"tf": 9.273618495495704}, "controlpi.messagebus.Message.check_value": {"tf": 7.874007874011811}, "controlpi.messagebus.Message.update": {"tf": 4.898979485566356}, "controlpi.messagebus.Message.setdefault": {"tf": 11.269427669584644}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 9.486832980505138}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 5.744562646538029}, "controlpi.messagebus.MessageTemplate.update": {"tf": 4.898979485566356}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 12.569805089976535}, "controlpi.messagebus.MessageTemplate.check": {"tf": 5.291502622129181}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 9.486832980505138}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 4.47213595499958}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 6}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 5.744562646538029}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 8}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 5.744562646538029}, "controlpi.messagebus.MessageBus.__init__": {"tf": 2}, "controlpi.messagebus.MessageBus.register": {"tf": 10.723805294763608}, "controlpi.messagebus.MessageBus.unregister": {"tf": 4.47213595499958}, "controlpi.messagebus.MessageBus.run": {"tf": 3.4641016151377544}, "controlpi.messagebus.MessageBus.send": {"tf": 5.291502622129181}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 5.291502622129181}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 4.47213595499958}, "controlpi_plugins.state.State.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.state.State.run": {"tf": 3.4641016151377544}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.state.StateAlias.run": {"tf": 3.4641016151377544}, "controlpi_plugins.state.AndState.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.state.AndState.run": {"tf": 3.4641016151377544}, "controlpi_plugins.state.OrState.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.state.OrState.run": {"tf": 3.4641016151377544}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.state.AndSet.run": {"tf": 3.4641016151377544}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.state.OrSet.run": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Log.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Log.run": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Init.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Init.run": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Execute.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Execute.run": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Alias.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Alias.run": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Counter.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Counter.run": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Date.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Date.run": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.Wait.run": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.GenericWait.run": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.Timer.run": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 3.4641016151377544}, "controlpi_plugins.wait.Periodic.run": {"tf": 3.4641016151377544}}, "df": 61, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "f": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}}, "df": 2}, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 10}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 3}}}}}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}}, "df": 6}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 3}}}}}}}}, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.run": {"tf": 1.4142135623730951}, "controlpi.test": {"tf": 1.7320508075688772}, "controlpi.messagebus.register_schema": {"tf": 1.4142135623730951}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2}}, "df": 9}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.run": {"tf": 1.4142135623730951}, "controlpi.test": {"tf": 1.7320508075688772}, "controlpi.messagebus.register_schema": {"tf": 1.7320508075688772}, "controlpi.messagebus.validate": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message.__init__": {"tf": 2}, "controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.setdefault": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2.6457513110645907}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 17, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.validate": {"tf": 1}}, "df": 1}}}}}, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "f": {"docs": {"controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.State.run": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 50}}, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus.Message.__init__": {"tf": 1}}, "df": 1}}, "s": {"docs": {"controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 1}}}}, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {"controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}}, "df": 2}}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1.4142135623730951}, "controlpi.messagebus.register_schema": {"tf": 1.4142135623730951}, "controlpi.messagebus.validate": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.setdefault": {"tf": 2}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}}, "df": 12}}, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}}, "df": 2}}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.State.run": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 51, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 3}}}}}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 1}}}}}}}}}, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}}, "df": 9, "s": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 10}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}}, "df": 4}}}}}}}}}}}}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}}, "df": 11}}}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}}}, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}}, "df": 8}}}}}, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.messagebus.register_schema": {"tf": 1.7320508075688772}, "controlpi.messagebus.validate": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}}, "df": 10}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}}, "df": 7}, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}}, "df": 2}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageBus.register": {"tf": 1.7320508075688772}}, "df": 1}}}}}}}}, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}}, "df": 4}}}}}, "k": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}}, "df": 2}}}}}, "e": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}}, "df": 2}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}}, "df": 1}}}}}}}, "u": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 1}}}}, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 1}}}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 1}}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 1}}}}}}}}}}, "bases": {"root": {"docs": {"controlpi.messagebus.Message": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate": {"tf": 2.6457513110645907}}, "df": 2, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin.ConfException": {"tf": 1}, "controlpi.messagebus.BusException": {"tf": 1}}, "df": 2}}}}}}}, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}}, "df": 2}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi_plugins.state.State": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Timer": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic": {"tf": 1.4142135623730951}}, "df": 16}}}}}}}}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin.ConfException": {"tf": 1}, "controlpi.messagebus.BusException": {"tf": 1}}, "df": 2}}}}}}}}}, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "c": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 2}}, "n": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}}, "df": 2}}}, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.Message": {"tf": 2}, "controlpi.messagebus.MessageTemplate": {"tf": 2.23606797749979}}, "df": 2}}}}}}, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "[": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1.7320508075688772}}, "df": 2}}}}}}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}}, "df": 2}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}}, "df": 2}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}}, "df": 2}}}, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}}, "df": 2}}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "[": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}}, "df": 2}}}}}}}}}}}, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 1}}}}}}}}}, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {"controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 16}}}}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 1}}}}}}}}}, "doc": {"root": {"0": {"1": {"5": {"docs": {"controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 1}, "docs": {"controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 5}, "2": {"5": {"docs": {"controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 4}, "docs": {"controlpi_plugins.wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1.4142135623730951}}, "df": 3}, "docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 2}, "controlpi_plugins.wait.Wait": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.GenericWait": {"tf": 2.23606797749979}, "controlpi_plugins.wait.Timer": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic": {"tf": 1.4142135623730951}}, "df": 13, "x": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}}, "df": 1}}, "1": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.messagebus": {"tf": 3.872983346207417}, "controlpi.messagebus.Message": {"tf": 2.8284271247461903}, "controlpi.messagebus.Message.__init__": {"tf": 2}, "controlpi.messagebus.Message.check_value": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message.update": {"tf": 2.449489742783178}, "controlpi.messagebus.Message.setdefault": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate": {"tf": 2}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2.6457513110645907}, "controlpi.messagebus.TemplateRegistry": {"tf": 2.449489742783178}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 3.7416573867739413}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.send": {"tf": 3}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 3}, "controlpi_plugins.state.AndState": {"tf": 3}, "controlpi_plugins.state.OrState": {"tf": 3}, "controlpi_plugins.state.AndSet": {"tf": 3}, "controlpi_plugins.state.OrSet": {"tf": 3}}, "df": 26, "j": {"docs": {"controlpi.messagebus.Message.update": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.setdefault": {"tf": 1}}, "df": 2}}, "2": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.Message": {"tf": 2.8284271247461903}, "controlpi.messagebus.Message.update": {"tf": 2}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 2}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2.6457513110645907}, "controlpi.messagebus.TemplateRegistry": {"tf": 6.244997998398398}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 3.1622776601683795}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 2.6457513110645907}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 2}, "controlpi.messagebus.MessageBus": {"tf": 3.3166247903554}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 2.8284271247461903}, "controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 2}, "controlpi_plugins.state.AndState": {"tf": 2.449489742783178}, "controlpi_plugins.state.OrState": {"tf": 2.449489742783178}, "controlpi_plugins.state.AndSet": {"tf": 2.449489742783178}, "controlpi_plugins.state.OrSet": {"tf": 2.449489742783178}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 28}, "3": {"9": {"docs": {"controlpi.run": {"tf": 4.47213595499958}, "controlpi.test": {"tf": 10.392304845413264}, "controlpi.baseplugin": {"tf": 10.099504938362077}, "controlpi.baseplugin.BasePlugin": {"tf": 7.874007874011811}, "controlpi.messagebus": {"tf": 11.74734012447073}, "controlpi.messagebus.Message": {"tf": 9.273618495495704}, "controlpi.messagebus.Message.__init__": {"tf": 4.47213595499958}, "controlpi.messagebus.Message.check_value": {"tf": 4.47213595499958}, "controlpi.messagebus.Message.update": {"tf": 7.483314773547883}, "controlpi.messagebus.Message.setdefault": {"tf": 5.0990195135927845}, "controlpi.messagebus.MessageTemplate": {"tf": 7.0710678118654755}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 3.4641016151377544}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 8.246211251235321}, "controlpi.messagebus.MessageTemplate.update": {"tf": 11.832159566199232}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 8.366600265340756}, "controlpi.messagebus.MessageTemplate.check": {"tf": 12.489995996796797}, "controlpi.messagebus.TemplateRegistry": {"tf": 24.819347291981714}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 7.615773105863909}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 7.874007874011811}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 11.489125293076057}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 8.94427190999916}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 10.862780491200215}, "controlpi.messagebus.MessageBus": {"tf": 15.620499351813308}, "controlpi.messagebus.MessageBus.register": {"tf": 5.656854249492381}, "controlpi.messagebus.MessageBus.unregister": {"tf": 4.242640687119285}, "controlpi.messagebus.MessageBus.send": {"tf": 9.486832980505138}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 9.486832980505138}, "controlpi.pluginregistry": {"tf": 3.7416573867739413}, "controlpi.pluginregistry.PluginRegistry": {"tf": 3.4641016151377544}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 2.449489742783178}, "controlpi_plugins.state": {"tf": 12.806248474865697}, "controlpi_plugins.state.State": {"tf": 9.38083151964686}, "controlpi_plugins.state.StateAlias": {"tf": 11.40175425099138}, "controlpi_plugins.state.AndState": {"tf": 11.832159566199232}, "controlpi_plugins.state.OrState": {"tf": 11.40175425099138}, "controlpi_plugins.state.AndSet": {"tf": 14.628738838327793}, "controlpi_plugins.state.OrSet": {"tf": 12.569805089976535}, "controlpi_plugins.util": {"tf": 11.74734012447073}, "controlpi_plugins.util.Log": {"tf": 8.94427190999916}, "controlpi_plugins.util.Init": {"tf": 10.488088481701515}, "controlpi_plugins.util.Execute": {"tf": 10.677078252031311}, "controlpi_plugins.util.Alias": {"tf": 14.142135623730951}, "controlpi_plugins.util.Counter": {"tf": 14.7648230602334}, "controlpi_plugins.util.Date": {"tf": 10.583005244258363}, "controlpi_plugins.wait": {"tf": 12}, "controlpi_plugins.wait.Wait": {"tf": 10.770329614269007}, "controlpi_plugins.wait.GenericWait": {"tf": 10.770329614269007}, "controlpi_plugins.wait.Timer": {"tf": 11.916375287812984}, "controlpi_plugins.wait.Periodic": {"tf": 6.48074069840786}}, "df": 49}, "docs": {"controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2.449489742783178}, "controlpi.messagebus.TemplateRegistry": {"tf": 2.23606797749979}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 3.1622776601683795}, "controlpi_plugins.state.OrSet": {"tf": 2.449489742783178}, "controlpi_plugins.util.Counter": {"tf": 1}}, "df": 12}, "4": {"2": {"docs": {"controlpi.run": {"tf": 2.449489742783178}, "controlpi.test": {"tf": 3.4641016151377544}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 3.1622776601683795}, "controlpi.messagebus.Message.update": {"tf": 2}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 3.7416573867739413}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}, "controlpi_plugins.util": {"tf": 2.23606797749979}, "controlpi_plugins.util.Log": {"tf": 3.605551275463989}, "controlpi_plugins.util.Init": {"tf": 3.605551275463989}, "controlpi_plugins.util.Execute": {"tf": 3}, "controlpi_plugins.util.Alias": {"tf": 3.7416573867739413}, "controlpi_plugins.util.Counter": {"tf": 4.242640687119285}}, "df": 19}, "9": {"docs": {"controlpi_plugins.util.Counter": {"tf": 1.4142135623730951}}, "df": 1}, "docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 2.449489742783178}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 2}}, "df": 5}, "5": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 2.8284271247461903}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}}, "df": 5}, "6": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 2.8284271247461903}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1.7320508075688772}}, "df": 2}, "7": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1.7320508075688772}}, "df": 1}, "docs": {"controlpi": {"tf": 3.4641016151377544}, "controlpi.CONF_SCHEMA": {"tf": 1.7320508075688772}, "controlpi.run": {"tf": 18.601075237738275}, "controlpi.test": {"tf": 19.183326093250876}, "controlpi.baseplugin": {"tf": 33.04542328371661}, "controlpi.baseplugin.JSONSchema": {"tf": 1.7320508075688772}, "controlpi.baseplugin.PluginConf": {"tf": 1.7320508075688772}, "controlpi.baseplugin.ConfException": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin": {"tf": 27.349588662354687}, "controlpi.baseplugin.BasePlugin.CONF_SCHEMA": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin.bus": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin.name": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin.conf": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 2.449489742783178}, "controlpi.baseplugin.BasePlugin.run": {"tf": 2.449489742783178}, "controlpi.messagebus": {"tf": 27.910571473905726}, "controlpi.messagebus.MessageValue": {"tf": 1.7320508075688772}, "controlpi.messagebus.JSONSchema": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageCallback": {"tf": 1.7320508075688772}, "controlpi.messagebus.register_schema": {"tf": 1.7320508075688772}, "controlpi.messagebus.validate": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message": {"tf": 19.8997487421324}, "controlpi.messagebus.Message.__init__": {"tf": 10.295630140987}, "controlpi.messagebus.Message.check_value": {"tf": 21.166010488516726}, "controlpi.messagebus.Message.update": {"tf": 18.24828759089466}, "controlpi.messagebus.Message.setdefault": {"tf": 14.560219778561036}, "controlpi.messagebus.MessageTemplate": {"tf": 17.944358444926362}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 9.899494936611665}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 16.55294535724685}, "controlpi.messagebus.MessageTemplate.update": {"tf": 25.337718918639855}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 18.627936010197157}, "controlpi.messagebus.MessageTemplate.check": {"tf": 34.02939905434711}, "controlpi.messagebus.TemplateRegistry": {"tf": 46.87216658103186}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 5.291502622129181}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 16.97056274847714}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 18.110770276274835}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 22.22611077089287}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 17.26267650163207}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 22.090722034374522}, "controlpi.messagebus.BusException": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 31.368774282716245}, "controlpi.messagebus.MessageBus.__init__": {"tf": 7.681145747868608}, "controlpi.messagebus.MessageBus.register": {"tf": 19.209372712298546}, "controlpi.messagebus.MessageBus.unregister": {"tf": 14.628738838327793}, "controlpi.messagebus.MessageBus.run": {"tf": 11.789826122551595}, "controlpi.messagebus.MessageBus.send": {"tf": 24.576411454889016}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 24.454038521274967}, "controlpi.pluginregistry": {"tf": 16.24807680927192}, "controlpi.pluginregistry.PluginRegistry": {"tf": 15.033296378372908}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 13}, "controlpi_plugins": {"tf": 1.7320508075688772}, "controlpi_plugins.state": {"tf": 29}, "controlpi_plugins.state.State": {"tf": 18.027756377319946}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 2.449489742783178}, "controlpi_plugins.state.State.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.state.State.run": {"tf": 1.7320508075688772}, "controlpi_plugins.state.StateAlias": {"tf": 20.174241001832016}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 3.7416573867739413}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.state.StateAlias.run": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndState": {"tf": 22.090722034374522}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 3.7416573867739413}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndState.run": {"tf": 1.7320508075688772}, "controlpi_plugins.state.OrState": {"tf": 21.93171219946131}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 3.7416573867739413}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.state.OrState.run": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndSet": {"tf": 25.13961017995307}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 4.358898943540674}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndSet.run": {"tf": 1.7320508075688772}, "controlpi_plugins.state.OrSet": {"tf": 23.473389188611005}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 4.358898943540674}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.state.OrSet.run": {"tf": 1.7320508075688772}, "controlpi_plugins.util": {"tf": 21}, "controlpi_plugins.util.Log": {"tf": 20.493901531919196}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 3.7416573867739413}, "controlpi_plugins.util.Log.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log.run": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Init": {"tf": 21.18962010041709}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 3.7416573867739413}, "controlpi_plugins.util.Init.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Init.run": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Execute": {"tf": 18.520259177452136}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 2.449489742783178}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Execute.run": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias": {"tf": 30.331501776206203}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 5.477225575051661}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias.run": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Counter": {"tf": 25.337718918639855}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 3.7416573867739413}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Counter.run": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Date": {"tf": 18.65475810617763}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 3.7416573867739413}, "controlpi_plugins.util.Date.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Date.run": {"tf": 1.7320508075688772}, "controlpi_plugins.wait": {"tf": 19.078784028338912}, "controlpi_plugins.wait.Wait": {"tf": 17.435595774162696}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 3.7416573867739413}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Wait.run": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.GenericWait": {"tf": 17.86057109949175}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 2.449489742783178}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Timer": {"tf": 19.519221295943137}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 3.7416573867739413}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Timer.run": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Periodic": {"tf": 12.84523257866513}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 4.358898943540674}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Periodic.run": {"tf": 1.7320508075688772}}, "df": 119, "p": {"1": {"docs": {"controlpi.pluginregistry": {"tf": 1.4142135623730951}}, "df": 1}, "docs": {"controlpi.baseplugin": {"tf": 2}, "controlpi.baseplugin.BasePlugin": {"tf": 2.449489742783178}}, "df": 2, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {"controlpi": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1}}, "df": 9, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}}, "df": 2}}}}}, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1.4142135623730951}}, "df": 5, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}}, "df": 2}}}}}}}, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 11}}}, "y": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}, "d": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}}}, "g": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1}}}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi_plugins.state.State.run": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}}, "df": 14}}}}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.test": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 2}, "controlpi.baseplugin.BasePlugin": {"tf": 2.6457513110645907}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message": {"tf": 2.23606797749979}, "controlpi.messagebus.Message.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.update": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry": {"tf": 3}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}, "controlpi.pluginregistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 21, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 4}}, "r": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.util.Alias": {"tf": 1}}, "df": 1}}}}}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 1}}}}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}}}}}, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"1": {"docs": {"controlpi.pluginregistry": {"tf": 2.6457513110645907}, "controlpi.pluginregistry.PluginRegistry": {"tf": 2.6457513110645907}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.7320508075688772}}, "df": 3}, "2": {"docs": {"controlpi.pluginregistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry": {"tf": 2}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.7320508075688772}}, "df": 3}, "docs": {"controlpi": {"tf": 1.4142135623730951}, "controlpi.run": {"tf": 1.4142135623730951}, "controlpi.test": {"tf": 2.23606797749979}, "controlpi.baseplugin": {"tf": 3.7416573867739413}, "controlpi.baseplugin.ConfException": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageBus": {"tf": 3.872983346207417}, "controlpi.messagebus.MessageBus.register": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}, "controlpi.pluginregistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 3}, "controlpi_plugins.state.State": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1.7320508075688772}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 2}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 2}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 2}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 2}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.util": {"tf": 2.449489742783178}, "controlpi_plugins.util.Log": {"tf": 2.23606797749979}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 2}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 2.6457513110645907}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 2.23606797749979}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.wait": {"tf": 2}, "controlpi_plugins.wait.Wait": {"tf": 2}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}}, "df": 64, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi": {"tf": 1}, "controlpi.pluginregistry": {"tf": 2}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.7320508075688772}}, "df": 4}}}}}}}}, "s": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1.7320508075688772}, "controlpi.baseplugin": {"tf": 2.23606797749979}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.7320508075688772}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1}}, "df": 13}}}}}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"controlpi": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.4142135623730951}}, "df": 4, "s": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1}}}}}}, "s": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.7320508075688772}}, "df": 10}}, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}}, "df": 6}}}, "r": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}}, "df": 1, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}}, "df": 3}}}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {"controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 1}}}, "y": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1}}, "df": 2}, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.Message.__init__": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 2, "i": {"docs": {}, "df": 0, "c": {"docs": {"controlpi_plugins.wait.Periodic": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 2, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 2}}}}}}}}}}}}, "t": {"docs": {"controlpi.messagebus.MessageTemplate": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 2}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 2}, "controlpi.messagebus.MessageTemplate.update": {"tf": 3.3166247903554}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageTemplate.check": {"tf": 4.47213595499958}}, "df": 6, "h": {"docs": {}, "df": 0, "e": {"docs": {"controlpi": {"tf": 2.8284271247461903}, "controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 2}, "controlpi.baseplugin": {"tf": 5.477225575051661}, "controlpi.baseplugin.BasePlugin": {"tf": 2.6457513110645907}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 2.23606797749979}, "controlpi.baseplugin.BasePlugin.run": {"tf": 2.23606797749979}, "controlpi.messagebus": {"tf": 5.196152422706632}, "controlpi.messagebus.register_schema": {"tf": 1.4142135623730951}, "controlpi.messagebus.validate": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message": {"tf": 2.8284271247461903}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry": {"tf": 3.1622776601683795}, "controlpi.messagebus.MessageBus": {"tf": 4.242640687119285}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi.pluginregistry": {"tf": 2.8284271247461903}, "controlpi.pluginregistry.PluginRegistry": {"tf": 2.449489742783178}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.7320508075688772}, "controlpi_plugins.state": {"tf": 1.7320508075688772}, "controlpi_plugins.state.State": {"tf": 2.23606797749979}, "controlpi_plugins.state.StateAlias": {"tf": 2.449489742783178}, "controlpi_plugins.state.AndState": {"tf": 1.7320508075688772}, "controlpi_plugins.state.OrState": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndSet": {"tf": 1.7320508075688772}, "controlpi_plugins.state.OrSet": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Init": {"tf": 2.8284271247461903}, "controlpi_plugins.util.Execute": {"tf": 2.6457513110645907}, "controlpi_plugins.util.Alias": {"tf": 4.47213595499958}, "controlpi_plugins.util.Counter": {"tf": 2.449489742783178}, "controlpi_plugins.util.Date": {"tf": 2}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.GenericWait": {"tf": 2}, "controlpi_plugins.wait.Timer": {"tf": 2}, "controlpi_plugins.wait.Periodic": {"tf": 2}}, "df": 44, "m": {"docs": {"controlpi": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}}, "df": 2}, "s": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message": {"tf": 1.7320508075688772}, "controlpi.pluginregistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 2}}, "df": 5}}, "r": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}}, "df": 4, "f": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}}, "df": 2}}}}}}, "y": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.state": {"tf": 2.23606797749979}}, "df": 3}, "i": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.7320508075688772}}, "df": 4}}, "n": {"docs": {"controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}}, "df": 3}}, "i": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 2.23606797749979}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1}}, "df": 13}, "r": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.util.Log": {"tf": 2}}, "df": 1}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "h": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 2.23606797749979}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 10}}}, "o": {"docs": {"controlpi": {"tf": 1.4142135623730951}, "controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 2.23606797749979}, "controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 4}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 3.3166247903554}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 2.449489742783178}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1.7320508075688772}, "controlpi_plugins.state.OrState": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 2}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 4.242640687119285}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 2}, "controlpi_plugins.util.Counter": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 47}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi": {"tf": 1.4142135623730951}, "controlpi.run": {"tf": 2}, "controlpi.test": {"tf": 4.123105625617661}, "controlpi.baseplugin": {"tf": 4}, "controlpi.baseplugin.BasePlugin": {"tf": 3.4641016151377544}, "controlpi.messagebus": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageBus": {"tf": 3.3166247903554}, "controlpi.messagebus.MessageBus.register": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 2}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 2}, "controlpi_plugins.state": {"tf": 7.874007874011811}, "controlpi_plugins.state.State": {"tf": 5.196152422706632}, "controlpi_plugins.state.StateAlias": {"tf": 6.244997998398398}, "controlpi_plugins.state.AndState": {"tf": 6.557438524302}, "controlpi_plugins.state.OrState": {"tf": 6.4031242374328485}, "controlpi_plugins.state.AndSet": {"tf": 7.937253933193772}, "controlpi_plugins.state.OrSet": {"tf": 7}, "controlpi_plugins.util": {"tf": 5.0990195135927845}, "controlpi_plugins.util.Log": {"tf": 5}, "controlpi_plugins.util.Init": {"tf": 5.291502622129181}, "controlpi_plugins.util.Execute": {"tf": 4.795831523312719}, "controlpi_plugins.util.Alias": {"tf": 6.244997998398398}, "controlpi_plugins.util.Counter": {"tf": 7.3484692283495345}, "controlpi_plugins.util.Date": {"tf": 4.69041575982343}, "controlpi_plugins.wait": {"tf": 4.58257569495584}, "controlpi_plugins.wait.Wait": {"tf": 3}, "controlpi_plugins.wait.GenericWait": {"tf": 4.123105625617661}, "controlpi_plugins.wait.Timer": {"tf": 3.872983346207417}, "controlpi_plugins.wait.Periodic": {"tf": 2}}, "df": 30, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin": {"tf": 2.23606797749979}}, "df": 2}}}}}}}}, "r": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}}}}}}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageTemplate": {"tf": 2}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}}, "df": 14, "s": {"docs": {"controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.TemplateRegistry": {"tf": 2.8284271247461903}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageBus": {"tf": 2.23606797749979}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}}, "df": 9}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}}, "df": 7}}}}}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.run": {"tf": 2}, "controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.messagebus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus.run": {"tf": 2}, "controlpi.messagebus.MessageBus.send": {"tf": 2}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 2}}, "df": 7, "s": {"docs": {"controlpi.baseplugin.BasePlugin.run": {"tf": 1}}, "df": 1}}}, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.test": {"tf": 1.7320508075688772}, "controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.messagebus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus": {"tf": 3.7416573867739413}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 2.8284271247461903}, "controlpi_plugins.state": {"tf": 3.7416573867739413}, "controlpi_plugins.state.State": {"tf": 2.8284271247461903}, "controlpi_plugins.state.StateAlias": {"tf": 3.1622776601683795}, "controlpi_plugins.state.AndState": {"tf": 3.1622776601683795}, "controlpi_plugins.state.OrState": {"tf": 3.1622776601683795}, "controlpi_plugins.state.AndSet": {"tf": 4}, "controlpi_plugins.state.OrSet": {"tf": 3.4641016151377544}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 2}, "controlpi_plugins.util.Execute": {"tf": 2.449489742783178}, "controlpi_plugins.util.Counter": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Date": {"tf": 2.449489742783178}, "controlpi_plugins.wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.Wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.GenericWait": {"tf": 2.23606797749979}, "controlpi_plugins.wait.Timer": {"tf": 3.4641016151377544}}, "df": 24}}}}}, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}}, "df": 7}, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 3}, "controlpi.messagebus.MessageTemplate": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 3.7416573867739413}, "controlpi.messagebus.TemplateRegistry": {"tf": 3.7416573867739413}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 2}, "controlpi_plugins.state": {"tf": 3.1622776601683795}, "controlpi_plugins.state.State": {"tf": 2.6457513110645907}, "controlpi_plugins.state.StateAlias": {"tf": 3}, "controlpi_plugins.state.AndState": {"tf": 2.8284271247461903}, "controlpi_plugins.state.OrState": {"tf": 2.8284271247461903}, "controlpi_plugins.state.AndSet": {"tf": 3.1622776601683795}, "controlpi_plugins.state.OrSet": {"tf": 2.8284271247461903}}, "df": 17}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.update": {"tf": 2}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}}, "df": 5}}}}}}, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Alias": {"tf": 2.6457513110645907}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}}, "df": 2, "s": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.util": {"tf": 1}}, "df": 2}, "d": {"docs": {"controlpi_plugins.util": {"tf": 2}, "controlpi_plugins.util.Alias": {"tf": 3}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.7320508075688772}}, "df": 3}}}}}}}}}, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 3.4641016151377544}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2.6457513110645907}, "controlpi.messagebus.TemplateRegistry": {"tf": 2.23606797749979}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 2}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.GenericWait": {"tf": 1.7320508075688772}}, "df": 24, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 1}}, "df": 3}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus.Message.update": {"tf": 2}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}}, "df": 4}}}}}}}}, "w": {"docs": {}, "df": 0, "o": {"docs": {"controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 3}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Init": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}}, "df": 4, "r": {"docs": {"controlpi_plugins.wait.Timer": {"tf": 4.47213595499958}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}}, "df": 2}}}}}, "i": {"1": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1}, "docs": {"controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 3, "n": {"docs": {"controlpi": {"tf": 1}, "controlpi.baseplugin": {"tf": 2}, "controlpi.baseplugin.ConfException": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message.update": {"tf": 2.23606797749979}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry": {"tf": 3.1622776601683795}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.BusException": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.pluginregistry": {"tf": 2.449489742783178}, "controlpi.pluginregistry.PluginRegistry": {"tf": 2.8284271247461903}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 2}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Execute": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 35, "f": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"controlpi": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}}, "df": 4}}}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin.BasePlugin.run": {"tf": 1}}, "df": 1}}}}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}}, "df": 3}}}}}}}}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}, "s": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1}}}}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.run": {"tf": 2.23606797749979}, "controlpi.test": {"tf": 3.872983346207417}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi_plugins.util": {"tf": 3}, "controlpi_plugins.util.Init": {"tf": 4.123105625617661}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}}, "df": 7, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}}, "df": 2, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 4}}}}}, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 7, "d": {"docs": {"controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}}, "df": 3}}}}}}}}}, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}}}}}, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 2}, "controlpi.baseplugin.BasePlugin": {"tf": 2.6457513110645907}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1.7320508075688772}}, "df": 7, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.7320508075688772}}, "df": 2}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 1}}}}}, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.util.Alias": {"tf": 1}}, "df": 1}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 2.8284271247461903}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 2.23606797749979}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 2.23606797749979}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 2.6457513110645907}}, "df": 6}}}}, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}}, "df": 5, "/": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}}}}}}, "t": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.update": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 7, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}}, "df": 9, "s": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 3}}}}, "r": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 3}}}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}}}}, "s": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 1}}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}}, "df": 1}}}, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "t": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}, "controlpi_plugins.state": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 8, "s": {"docs": {"controlpi": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 3, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "f": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}}, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}}, "df": 2}}}}, "s": {"docs": {"controlpi": {"tf": 1.4142135623730951}, "controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.baseplugin.BasePlugin": {"tf": 2}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 2.449489742783178}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 2.23606797749979}, "controlpi.messagebus.Message.setdefault": {"tf": 2}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.pluginregistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 1.7320508075688772}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 2}, "controlpi_plugins.util.Init": {"tf": 2}, "controlpi_plugins.util.Alias": {"tf": 2.449489742783178}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 29}, "d": {"docs": {"controlpi.run": {"tf": 2}, "controlpi.test": {"tf": 2.8284271247461903}, "controlpi_plugins.util": {"tf": 3}, "controlpi_plugins.util.Log": {"tf": 3.1622776601683795}, "controlpi_plugins.util.Init": {"tf": 2.8284271247461903}, "controlpi_plugins.util.Execute": {"tf": 2.449489742783178}, "controlpi_plugins.util.Alias": {"tf": 4.58257569495584}, "controlpi_plugins.util.Counter": {"tf": 4.47213595499958}, "controlpi_plugins.wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.GenericWait": {"tf": 3}}, "df": 10}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet": {"tf": 1.4142135623730951}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 22, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {"controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 3}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "f": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 2.449489742783178}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 14}}, "f": {"docs": {"controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 3}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}}, "df": 13, "o": {"docs": {}, "df": 0, "r": {"docs": {"controlpi": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.baseplugin.ConfException": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.messagebus": {"tf": 3}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2.23606797749979}, "controlpi.messagebus.TemplateRegistry": {"tf": 3.872983346207417}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.BusException": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 4.358898943540674}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 2}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 49, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}}, "df": 3}}}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.state.StateAlias": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.util.Date": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 2}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}}, "df": 2}}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"controlpi": {"tf": 1.7320508075688772}, "controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 2}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 3.605551275463989}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}}, "df": 15}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi": {"tf": 1.7320508075688772}, "controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}}, "df": 7, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}}}}, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}}, "df": 3}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.run": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 2.449489742783178}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}}, "df": 4, "[": {"0": {"docs": {"controlpi_plugins.util.Log": {"tf": 1}}, "df": 1}, "docs": {}, "df": 0}}}}}, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.Wait": {"tf": 2.23606797749979}, "controlpi_plugins.wait.GenericWait": {"tf": 2}, "controlpi_plugins.wait.Timer": {"tf": 2.23606797749979}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 5}}}}}}, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.7320508075688772}}, "df": 4}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 4, "s": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2}}, "df": 3}}}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1}}}, "l": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2.449489742783178}, "controlpi.messagebus.TemplateRegistry": {"tf": 4}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 2}, "controlpi_plugins.state": {"tf": 2.23606797749979}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 2}, "controlpi_plugins.state.OrState": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndSet": {"tf": 2.6457513110645907}, "controlpi_plugins.state.OrSet": {"tf": 2.23606797749979}}, "df": 11}}}}}, "c": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 6.6332495807108}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 2.23606797749979}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 2.6457513110645907}}, "df": 3, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {"controlpi": {"tf": 1.4142135623730951}, "controlpi.run": {"tf": 1.4142135623730951}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet": {"tf": 1.4142135623730951}, "controlpi_plugins.util": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 2}, "controlpi_plugins.util.Init": {"tf": 2}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 2.449489742783178}, "controlpi_plugins.util.Counter": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Date": {"tf": 1.7320508075688772}, "controlpi_plugins.wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Timer": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic": {"tf": 1.4142135623730951}}, "df": 24}}}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.run": {"tf": 2}, "controlpi.test": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi_plugins.util": {"tf": 2.23606797749979}, "controlpi_plugins.util.Init": {"tf": 2.8284271247461903}, "controlpi_plugins.util.Execute": {"tf": 2.449489742783178}, "controlpi_plugins.util.Alias": {"tf": 5}}, "df": 7}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.test": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}}, "df": 4, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}}, "df": 2}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi_plugins.state": {"tf": 2.23606797749979}}, "df": 2}}}, "s": {"docs": {"controlpi_plugins.util.Alias": {"tf": 1}}, "df": 1}}}}}, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"controlpi": {"tf": 1}}, "df": 1}}}}, "t": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 2.449489742783178}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry": {"tf": 3.605551275463989}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 3.4641016151377544}, "controlpi.messagebus.MessageBus": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}, "controlpi_plugins.util": {"tf": 3.3166247903554}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 2.449489742783178}, "controlpi_plugins.util.Execute": {"tf": 2}, "controlpi_plugins.util.Alias": {"tf": 2.8284271247461903}, "controlpi_plugins.util.Counter": {"tf": 2.449489742783178}, "controlpi_plugins.util.Date": {"tf": 2}, "controlpi_plugins.wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.Wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.GenericWait": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Timer": {"tf": 2.449489742783178}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 33, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus": {"tf": null}, "controlpi.messagebus.Message": {"tf": null}}, "df": 2}}, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.Message": {"tf": 1}}, "df": 1}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.Message": {"tf": 1}}, "df": 1}}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}}, "df": 2}}}}}, "f": {"docs": {"controlpi.run": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 3}, "controlpi.baseplugin.BasePlugin": {"tf": 3.4641016151377544}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1.4142135623730951}}, "df": 4, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 2}, "controlpi.test": {"tf": 2}, "controlpi.baseplugin": {"tf": 2.23606797749979}, "controlpi.baseplugin.BasePlugin": {"tf": 2}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 2.449489742783178}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 36, "s": {"docs": {"controlpi.test": {"tf": 1.4142135623730951}, "controlpi.baseplugin.ConfException": {"tf": 1}, "controlpi.messagebus": {"tf": 1}}, "df": 3}}}}}, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}}, "df": 2}}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}}, "df": 4}}}}}, "r": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.util.Counter": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}}, "df": 1}}}}}}}}}}, "c": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1.4142135623730951}}, "df": 4}}}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}}, "df": 3}}}}}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}}, "df": 1, "s": {"docs": {"controlpi_plugins.state": {"tf": 2}}, "df": 1}}}}}}}}, "j": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1}}, "df": 3}}}}}}}}}, "m": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state": {"tf": 1}}, "df": 1, "s": {"docs": {"controlpi": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.4142135623730951}}, "df": 2}, "d": {"docs": {"controlpi_plugins.state.AndState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}}, "df": 6}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.test": {"tf": 1.7320508075688772}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi_plugins.state": {"tf": 3.7416573867739413}, "controlpi_plugins.state.State": {"tf": 3.1622776601683795}, "controlpi_plugins.state.StateAlias": {"tf": 3.1622776601683795}, "controlpi_plugins.state.AndState": {"tf": 3.1622776601683795}, "controlpi_plugins.state.OrState": {"tf": 3.1622776601683795}, "controlpi_plugins.state.AndSet": {"tf": 4}, "controlpi_plugins.state.OrSet": {"tf": 3.4641016151377544}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 2.23606797749979}, "controlpi_plugins.util.Execute": {"tf": 3.1622776601683795}, "controlpi_plugins.util.Counter": {"tf": 3.605551275463989}, "controlpi_plugins.util.Date": {"tf": 2.449489742783178}, "controlpi_plugins.wait": {"tf": 2.8284271247461903}, "controlpi_plugins.wait.Wait": {"tf": 2.6457513110645907}, "controlpi_plugins.wait.GenericWait": {"tf": 2.6457513110645907}, "controlpi_plugins.wait.Timer": {"tf": 3.7416573867739413}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 20, "s": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}}, "df": 7}}}}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}, "x": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.setdefault": {"tf": 1}}, "df": 3}}}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {"controlpi": {"tf": 1}, "controlpi.test": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi_plugins.state.State.run": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}}, "df": 19}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.run": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}}, "df": 3, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}}, "df": 2}}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 2}}}}}}}}}}}, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.messagebus": {"tf": 1}}, "df": 2}}, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.util.Counter": {"tf": 4.242640687119285}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 3, "s": {"docs": {"controlpi_plugins.util.Counter": {"tf": 1}}, "df": 1}, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi_plugins.util.Counter": {"tf": 4.69041575982343}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}}, "df": 2}, "d": {"docs": {"controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1}}}}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 8, "d": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 3}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 2}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.pluginregistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 15, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 2}}, "df": 8, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.wait.Timer": {"tf": 2}}, "df": 1, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 7}}}}}}}}, "s": {"docs": {"controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 1}}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 4}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.update": {"tf": 2}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}}, "df": 5, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}}, "df": 4}}, "s": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}}, "df": 1}, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.messagebus": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageBus": {"tf": 3.4641016151377544}, "controlpi.messagebus.MessageBus.register": {"tf": 2}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.7320508075688772}}, "df": 6, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 3}}}}}}}, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}}, "df": 2}}}, "r": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}}, "df": 1}}, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.register_schema": {"tf": 1}}, "df": 1}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 4.242640687119285}, "controlpi.messagebus.TemplateRegistry": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 2.23606797749979}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 2.449489742783178}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 3.7416573867739413}, "controlpi.messagebus.MessageBus": {"tf": 6.324555320336759}, "controlpi.messagebus.MessageBus.register": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageBus.unregister": {"tf": 2}, "controlpi.messagebus.MessageBus.send": {"tf": 4.123105625617661}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 4.123105625617661}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.util": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}}, "df": 45, "s": {"docs": {"controlpi.baseplugin": {"tf": 2.449489742783178}, "controlpi.messagebus": {"tf": 3}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi_plugins.state": {"tf": 2.23606797749979}}, "df": 8}}}}}, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.pluginregistry": {"tf": 3.3166247903554}, "controlpi.pluginregistry.PluginRegistry": {"tf": 2.449489742783178}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 2.449489742783178}}, "df": 5, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1}}}}}, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 3.872983346207417}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 3.872983346207417}, "controlpi.messagebus.TemplateRegistry": {"tf": 2.8284271247461903}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1.4142135623730951}}, "df": 5, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 4}}, "s": {"docs": {"controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 5}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1}}}, "e": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1.4142135623730951}}, "df": 3, "s": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}}, "df": 1}, "d": {"docs": {"controlpi_plugins.state": {"tf": 2.6457513110645907}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndState": {"tf": 2.449489742783178}, "controlpi_plugins.state.OrState": {"tf": 2.23606797749979}, "controlpi_plugins.state.AndSet": {"tf": 2.23606797749979}, "controlpi_plugins.state.OrSet": {"tf": 2}}, "df": 7}}}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Date": {"tf": 1}}, "df": 2, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 3}}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 2}}}}}}}, "s": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}}, "df": 2, "y": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {"controlpi": {"tf": 1.4142135623730951}, "controlpi.run": {"tf": 1.4142135623730951}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 2.23606797749979}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}}, "df": 6, "s": {"docs": {"controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1}}, "df": 3}}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 3.3166247903554}, "controlpi_plugins.state.State": {"tf": 2.449489742783178}, "controlpi_plugins.state.StateAlias": {"tf": 2.449489742783178}, "controlpi_plugins.state.AndState": {"tf": 2.449489742783178}, "controlpi_plugins.state.OrState": {"tf": 2.449489742783178}, "controlpi_plugins.state.AndSet": {"tf": 3.3166247903554}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 3}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 2}}, "df": 12, "u": {"docs": {}, "df": 0, "p": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}}, "df": 4}}, "s": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Execute": {"tf": 1}}, "df": 3}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}}, "df": 2}}}, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state": {"tf": 1}}, "df": 1}}}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message.setdefault": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2.6457513110645907}}, "df": 2}}}}}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {"controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}}, "df": 2}}}}}, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.run": {"tf": 1.4142135623730951}, "controlpi.test": {"tf": 2}, "controlpi_plugins.util.Log": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Init": {"tf": 2.23606797749979}, "controlpi_plugins.util.Execute": {"tf": 2}, "controlpi_plugins.util.Alias": {"tf": 2.449489742783178}}, "df": 6, "s": {"docs": {"controlpi_plugins.wait": {"tf": 2}, "controlpi_plugins.wait.Wait": {"tf": 2}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait": {"tf": 2.6457513110645907}, "controlpi_plugins.wait.Timer": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 8}}}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.test": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.messagebus": {"tf": 3.3166247903554}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 3.3166247903554}, "controlpi.messagebus.MessageBus.register": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.send": {"tf": 2}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 2}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 15, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.run": {"tf": 1.7320508075688772}, "controlpi.test": {"tf": 2.449489742783178}, "controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.messagebus": {"tf": 3.605551275463989}, "controlpi.messagebus.Message": {"tf": 4.795831523312719}, "controlpi.messagebus.Message.__init__": {"tf": 2.6457513110645907}, "controlpi.messagebus.Message.update": {"tf": 2.8284271247461903}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.check": {"tf": 3.7416573867739413}, "controlpi.messagebus.MessageBus": {"tf": 4}, "controlpi.messagebus.MessageBus.send": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 2.6457513110645907}, "controlpi_plugins.state": {"tf": 3.872983346207417}, "controlpi_plugins.state.State": {"tf": 3}, "controlpi_plugins.state.StateAlias": {"tf": 3.605551275463989}, "controlpi_plugins.state.AndState": {"tf": 3.605551275463989}, "controlpi_plugins.state.OrState": {"tf": 3.4641016151377544}, "controlpi_plugins.state.AndSet": {"tf": 4.358898943540674}, "controlpi_plugins.state.OrSet": {"tf": 3.7416573867739413}, "controlpi_plugins.util": {"tf": 3.1622776601683795}, "controlpi_plugins.util.Log": {"tf": 2.449489742783178}, "controlpi_plugins.util.Init": {"tf": 2.449489742783178}, "controlpi_plugins.util.Execute": {"tf": 2.23606797749979}, "controlpi_plugins.util.Alias": {"tf": 3.1622776601683795}, "controlpi_plugins.util.Counter": {"tf": 4.47213595499958}, "controlpi_plugins.util.Date": {"tf": 2.449489742783178}, "controlpi_plugins.wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.Wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.GenericWait": {"tf": 2.23606797749979}, "controlpi_plugins.wait.Timer": {"tf": 3}, "controlpi_plugins.wait.Periodic": {"tf": 1.7320508075688772}}, "df": 33}}, "s": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 2.8284271247461903}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.util": {"tf": 2.23606797749979}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 2}, "controlpi_plugins.wait.Wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 17}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageBus": {"tf": 2.23606797749979}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 6}}}}, "t": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 2}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 2}, "controlpi_plugins.util.Alias": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}}, "df": 8}}, "l": {"docs": {}, "df": 0, "f": {"docs": {"controlpi.baseplugin": {"tf": 4.47213595499958}, "controlpi.baseplugin.BasePlugin": {"tf": 2.8284271247461903}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1.4142135623730951}}, "df": 3}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1.7320508075688772}}, "df": 1}}}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 6}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.test": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 2}}}, "c": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Counter": {"tf": 2.23606797749979}}, "df": 1}}}, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 1}}}}}}}, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}}}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2}}, "df": 4, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.baseplugin": {"tf": 2}, "controlpi.baseplugin.BasePlugin": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 3}}}}}}}, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}}, "df": 1}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state.AndState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet": {"tf": 1.4142135623730951}}, "df": 4}}}}}}, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 3}}, "r": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 2.6457513110645907}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2.449489742783178}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.GenericWait": {"tf": 1.7320508075688772}}, "df": 26, "s": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 3}}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state": {"tf": 9.055385138137417}, "controlpi_plugins.state.State": {"tf": 6.082762530298219}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 6.244997998398398}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 6.164414002968976}, "controlpi_plugins.state.OrState": {"tf": 6.082762530298219}, "controlpi_plugins.state.AndSet": {"tf": 7.937253933193772}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet": {"tf": 7.14142842854285}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 11, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state": {"tf": 3.3166247903554}, "controlpi_plugins.state.StateAlias": {"tf": 3.4641016151377544}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}}, "df": 3}}}}}, "s": {"docs": {"controlpi_plugins.state": {"tf": 2}, "controlpi_plugins.state.AndState": {"tf": 2.449489742783178}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 2.449489742783178}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 2.23606797749979}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet": {"tf": 2.23606797749979}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 9}}}, "r": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.wait.Timer": {"tf": 3.1622776601683795}}, "df": 1, "u": {"docs": {}, "df": 0, "p": {"docs": {"controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Init.run": {"tf": 1}}, "df": 3}}, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 1}}}}}, "d": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}}, "df": 2}}}}}, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {"controlpi.baseplugin": {"tf": 2.449489742783178}, "controlpi.baseplugin.BasePlugin": {"tf": 2}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 23, "s": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}}, "df": 4}}}}}}, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}}, "df": 7}}}, "r": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.wait.Wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.GenericWait": {"tf": 1.7320508075688772}}, "df": 2}}}}, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1.7320508075688772}}, "df": 1}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}}, "df": 2}}}, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}, "b": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 3}}}}}}}}}}, "o": {"docs": {}, "df": 0, "f": {"docs": {"controlpi": {"tf": 1}, "controlpi.test": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 3}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 3.4641016151377544}, "controlpi.messagebus.Message": {"tf": 2}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 2.23606797749979}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.7320508075688772}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1.7320508075688772}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1.7320508075688772}, "controlpi_plugins.state.OrSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1.7320508075688772}, "controlpi_plugins.util": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias": {"tf": 2}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 41, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}}, "n": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1.7320508075688772}, "controlpi.test": {"tf": 1.7320508075688772}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util": {"tf": 2}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}}, "df": 12, "e": {"docs": {"controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}}, "df": 4}, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}}, "df": 5}}, "c": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Init": {"tf": 1}}, "df": 1}}}, "r": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}}, "df": 12, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.messagebus.Message": {"tf": 1}}, "df": 1}}}}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}}, "df": 1}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state": {"tf": 3}, "controlpi_plugins.state.OrState": {"tf": 3.1622776601683795}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}}, "df": 3}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state": {"tf": 2}, "controlpi_plugins.state.OrSet": {"tf": 3}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}}, "df": 3}}}}, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 10, "s": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 4}}}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}}, "df": 2}}}, "e": {"docs": {"controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}}, "df": 4}}}}, "w": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.Message": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.baseplugin.BasePlugin.run": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.7320508075688772}, "controlpi_plugins.state.StateAlias": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}}, "df": 7, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1}}}}}}}}, "k": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.baseplugin.BasePlugin.run": {"tf": 1}}, "df": 1}}}, "w": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1}}, "u": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}}, "df": 5}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 1}}}}}}}}}}, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}}, "df": 5}}}}}}}, "l": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.util.Alias": {"tf": 3.4641016151377544}}, "df": 1}}}, "m": {"docs": {"controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message": {"tf": 3.4641016151377544}, "controlpi.messagebus.Message.__init__": {"tf": 2}, "controlpi.messagebus.Message.update": {"tf": 3}, "controlpi.messagebus.Message.setdefault": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 2}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry": {"tf": 4.898979485566356}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 2.449489742783178}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 11, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 2.449489742783178}, "controlpi.test": {"tf": 3.605551275463989}, "controlpi.baseplugin": {"tf": 3.1622776601683795}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 3.872983346207417}, "controlpi.messagebus.Message": {"tf": 3}, "controlpi.messagebus.Message.__init__": {"tf": 2}, "controlpi.messagebus.Message.check_value": {"tf": 3.7416573867739413}, "controlpi.messagebus.Message.update": {"tf": 2.8284271247461903}, "controlpi.messagebus.Message.setdefault": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageTemplate.check": {"tf": 4.242640687119285}, "controlpi.messagebus.TemplateRegistry": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.BusException": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 3.1622776601683795}, "controlpi.messagebus.MessageBus.register": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 2.23606797749979}, "controlpi_plugins.state": {"tf": 2.8284271247461903}, "controlpi_plugins.util": {"tf": 2.23606797749979}, "controlpi_plugins.util.Log": {"tf": 4.47213595499958}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 3}, "controlpi_plugins.util.Execute": {"tf": 2.449489742783178}, "controlpi_plugins.util.Alias": {"tf": 4.58257569495584}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1.4142135623730951}}, "df": 39, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"controlpi": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 2.23606797749979}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 11}}}, "s": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 2.449489742783178}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 3.1622776601683795}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 2.8284271247461903}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.util": {"tf": 2.23606797749979}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 3.1622776601683795}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init.run": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 3.3166247903554}, "controlpi_plugins.util.Alias": {"tf": 2.23606797749979}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}}, "df": 22, "[": {"0": {"docs": {"controlpi_plugins.util.Init": {"tf": 1}}, "df": 1}, "docs": {}, "df": 0}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 3}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageBus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus.register": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send": {"tf": 2}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 2}}, "df": 12}}}}}}}}, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.validate": {"tf": 1}}, "df": 1}}}}}}}}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 3, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}}, "df": 2}}}}, "o": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi": {"tf": 1.7320508075688772}}, "df": 1, "s": {"docs": {"controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 3}}}}, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 2}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 2}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}}, "df": 6}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.run": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}}, "df": 11, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.run": {"tf": 1}}, "df": 1}}}}, "n": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}}}}}}, "y": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}, "p": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 4, "s": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1}}}}}}, "t": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}}, "df": 3, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 4}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 7}}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi": {"tf": 1}, "controlpi.test": {"tf": 1}}, "df": 2}}}}}, "g": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin": {"tf": 2}}, "df": 1}}}}, "u": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.test": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}}, "df": 4}}, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1}}}}}}}}, "b": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1}}, "df": 1, "u": {"docs": {}, "df": 0, "s": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1.4142135623730951}, "controlpi.test": {"tf": 2.6457513110645907}, "controlpi.baseplugin": {"tf": 5.477225575051661}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 4.58257569495584}, "controlpi.messagebus.BusException": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 4.69041575982343}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.register": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus.unregister": {"tf": 2}, "controlpi.messagebus.MessageBus.run": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageBus.send": {"tf": 3.3166247903554}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 3.3166247903554}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}}, "df": 33, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1.7320508075688772}}, "df": 1}}}}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 2}}}}}}}}}}, "t": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}}, "df": 10}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"controlpi": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 6, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin": {"tf": 2}, "controlpi.pluginregistry": {"tf": 2}, "controlpi.pluginregistry.PluginRegistry": {"tf": 2}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 2}}, "df": 6}}}}}}, "d": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}}, "df": 5}}, "i": {"docs": {}, "df": 0, "c": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1}}, "df": 1}}}, "c": {"docs": {}, "df": 0, "k": {"docs": {"controlpi_plugins.wait.GenericWait": {"tf": 1}}, "df": 1}}}, "y": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 2.449489742783178}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 2}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias": {"tf": 2.6457513110645907}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}}, "df": 22, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi": {"tf": 1}, "controlpi.test": {"tf": 1}}, "df": 2}}}}}}}}}, "o": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}}, "df": 3, "s": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1}}, "df": 2}}}}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {"controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 2}}}, "e": {"docs": {"controlpi.test": {"tf": 1.7320508075688772}, "controlpi.baseplugin": {"tf": 2.449489742783178}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 2.23606797749979}, "controlpi.messagebus.Message": {"tf": 2}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi.pluginregistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 30, "t": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}, "f": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 5}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "f": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 1}}}}}}}}, "r": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 4.358898943540674}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 2.449489742783178}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 2.8284271247461903}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 2.23606797749979}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 3.7416573867739413}}, "df": 7, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.pluginregistry": {"tf": 3}, "controlpi.pluginregistry.PluginRegistry": {"tf": 3}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 2}}, "df": 6}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 4, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.register": {"tf": 2}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}}, "df": 26, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.TemplateRegistry": {"tf": 2.449489742783178}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 2.8284271247461903}, "controlpi.pluginregistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 27}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 1}}}}}}}}}, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 2}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.register": {"tf": 1.7320508075688772}, "controlpi_plugins.state": {"tf": 1}}, "df": 5, "s": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 2.6457513110645907}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.util": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 16}, "d": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 4}, "r": {"docs": {"controlpi.messagebus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus": {"tf": 2.6457513110645907}}, "df": 2}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 5}}}}}, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.update": {"tf": 2}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}}, "df": 5}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}}, "df": 2}}}}}}}}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}}, "t": {"docs": {"controlpi_plugins.state": {"tf": 1}}, "df": 1, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}}, "df": 5}, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi_plugins.util.Init": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}}, "df": 2}}}}}, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}, "l": {"docs": {"controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 1, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi_plugins.state": {"tf": 1}}, "df": 1}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 20}}}}}}, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin.BasePlugin.run": {"tf": 1}}, "df": 1}, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}}, "df": 2, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}}, "df": 2}}}}}}}}}, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.util.Counter": {"tf": 2.449489742783178}}, "df": 1}}}, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2, "s": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.util.Alias": {"tf": 1}}, "df": 1}}}}}}, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state": {"tf": 1}}, "df": 1}}}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}}, "df": 1, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.state": {"tf": 1}}, "df": 1}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 2}}}}}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {"controlpi": {"tf": 1.4142135623730951}, "controlpi.run": {"tf": 2.8284271247461903}, "controlpi.test": {"tf": 1.7320508075688772}, "controlpi.baseplugin": {"tf": 3.4641016151377544}, "controlpi.baseplugin.BasePlugin": {"tf": 2.23606797749979}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1.7320508075688772}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.State.run": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 2.23606797749979}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Date.run": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 49, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}}, "df": 2}}}}}}, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin.ConfException": {"tf": 1}, "controlpi.messagebus.BusException": {"tf": 1}}, "df": 2, "s": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}}, "df": 1}}}}}}, "a": {"docs": {"controlpi": {"tf": 1.7320508075688772}, "controlpi.run": {"tf": 2}, "controlpi.baseplugin": {"tf": 2.8284271247461903}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 3.872983346207417}, "controlpi.messagebus.Message": {"tf": 2}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 2.449489742783178}, "controlpi.messagebus.Message.setdefault": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2}, "controlpi.messagebus.TemplateRegistry": {"tf": 3}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 4}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi.pluginregistry": {"tf": 2}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 3.4641016151377544}, "controlpi_plugins.state.State": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 2}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 2.6457513110645907}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 41, "n": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}}, "df": 14, "d": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1.7320508075688772}, "controlpi.test": {"tf": 2.449489742783178}, "controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 3.1622776601683795}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 3}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 2.23606797749979}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Init": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias": {"tf": 2.6457513110645907}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1.4142135623730951}}, "df": 31, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state": {"tf": 2.8284271247461903}, "controlpi_plugins.state.AndState": {"tf": 3.3166247903554}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}}, "df": 3}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndSet": {"tf": 3.605551275463989}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}}, "df": 3}}}}, "y": {"docs": {"controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 8}, "o": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}}, "df": 2}}}}}}, "b": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"controlpi": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}}, "df": 3}}}}}}, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}}}, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.state.State.process_conf": {"tf": 1}, "controlpi_plugins.state.StateAlias.process_conf": {"tf": 1}, "controlpi_plugins.state.AndState.process_conf": {"tf": 1}, "controlpi_plugins.state.OrState.process_conf": {"tf": 1}, "controlpi_plugins.state.AndSet.process_conf": {"tf": 1}, "controlpi_plugins.state.OrSet.process_conf": {"tf": 1}, "controlpi_plugins.util.Log.process_conf": {"tf": 1}, "controlpi_plugins.util.Init.process_conf": {"tf": 1}, "controlpi_plugins.util.Execute.process_conf": {"tf": 1}, "controlpi_plugins.util.Alias.process_conf": {"tf": 1}, "controlpi_plugins.util.Counter.process_conf": {"tf": 1}, "controlpi_plugins.util.Date.process_conf": {"tf": 1}, "controlpi_plugins.wait.Wait.process_conf": {"tf": 1}, "controlpi_plugins.wait.GenericWait.process_conf": {"tf": 1}, "controlpi_plugins.wait.Timer.process_conf": {"tf": 1}, "controlpi_plugins.wait.Periodic.process_conf": {"tf": 1}}, "df": 26, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 2.449489742783178}, "controlpi.baseplugin.BasePlugin": {"tf": 2.23606797749979}, "controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}}, "df": 11, "i": {"docs": {}, "df": 0, "o": {"docs": {"controlpi.run": {"tf": 2}, "controlpi.test": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 3}, "controlpi.baseplugin.BasePlugin": {"tf": 2}, "controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.send": {"tf": 2}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 2}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet": {"tf": 1.4142135623730951}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Init": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 2.23606797749979}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 31}}, "h": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}}, "df": 2}}}}}}}}}}, "s": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.run": {"tf": 1.4142135623730951}, "controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.messagebus": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageBus": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}}, "df": 7}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 2.23606797749979}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 1.7320508075688772}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 21, "o": {"docs": {}, "df": 0, "w": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1, "s": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}}, "df": 2}, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 3}}}}}, "s": {"docs": {}, "df": 0, "o": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}}, "df": 11}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}}, "df": 4}}}}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1.7320508075688772}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util": {"tf": 3.1622776601683795}, "controlpi_plugins.util.Alias": {"tf": 5}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}}, "df": 6, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}}, "df": 2}}}}}}, "d": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.util.Alias": {"tf": 1}}, "df": 1}}}}}, "g": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 3, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 7}}}}}}, "t": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}}, "df": 7}, "r": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message.check_value": {"tf": 3}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 2.23606797749979}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}}, "df": 18}, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 7}}}}}}}, "g": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.Message": {"tf": 1}}, "df": 1}}}}}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1}}, "df": 7}}}}, "c": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.util.Alias": {"tf": 1}}, "df": 1, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi_plugins.state": {"tf": 1}}, "df": 1}}}}}}}}}}, "f": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 7, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1}}}}}}}}}, "v": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}}, "df": 1}}}}}}}}, "p": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "w": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"controlpi": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}}, "df": 9}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 29}}}}}}}, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}}, "df": 2}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}}, "df": 5}, "r": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 4}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"controlpi": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 2.449489742783178}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.__init__": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 3.1622776601683795}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.7320508075688772}, "controlpi_plugins.wait": {"tf": 1}}, "df": 18, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}}, "df": 4}}}}, "c": {"docs": {}, "df": 0, "h": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 1}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}}, "df": 1}}}, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.test": {"tf": 1.4142135623730951}}, "df": 1}}}}, "e": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 2}}, "df": 3}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}}, "df": 2, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}, "s": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}}, "df": 1}, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.wait": {"tf": 4.47213595499958}, "controlpi_plugins.wait.Wait": {"tf": 5}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1.4142135623730951}, "controlpi_plugins.wait.GenericWait": {"tf": 4}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 8, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "/": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.wait": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "s": {"docs": {"controlpi_plugins.wait": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 1}}, "df": 7, "d": {"docs": {"controlpi": {"tf": 1}, "controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}}, "df": 8}, "f": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus.BusException": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.7320508075688772}}, "df": 4}}}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"controlpi": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}}, "df": 2}}}}}}, "n": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.baseplugin": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1.4142135623730951}}, "df": 3, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}}}}}}}}}, "i": {"docs": {}, "df": 0, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}}, "df": 2}}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1}}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {"controlpi_plugins.util.Counter": {"tf": 2.23606797749979}}, "df": 1}}}}, "p": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}}, "df": 1, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus.Message.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.23606797749979}}, "df": 2}}}}}}, "g": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.__init__": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.4142135623730951}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1.4142135623730951}}, "df": 16}}}}, "t": {"docs": {"controlpi.run": {"tf": 2.449489742783178}, "controlpi.test": {"tf": 2.449489742783178}, "controlpi.baseplugin": {"tf": 4.898979485566356}, "controlpi.baseplugin.BasePlugin": {"tf": 5.291502622129181}, "controlpi.messagebus": {"tf": 4.898979485566356}, "controlpi.messagebus.Message": {"tf": 6}, "controlpi.messagebus.Message.__init__": {"tf": 3.4641016151377544}, "controlpi.messagebus.Message.check_value": {"tf": 6.48074069840786}, "controlpi.messagebus.Message.update": {"tf": 5.196152422706632}, "controlpi.messagebus.Message.setdefault": {"tf": 4.242640687119285}, "controlpi.messagebus.MessageTemplate": {"tf": 3.872983346207417}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 3.4641016151377544}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 4.242640687119285}, "controlpi.messagebus.MessageTemplate.update": {"tf": 5.744562646538029}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 4.58257569495584}, "controlpi.messagebus.MessageTemplate.check": {"tf": 7.937253933193772}, "controlpi.messagebus.TemplateRegistry": {"tf": 7.54983443527075}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 4.242640687119285}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 4.898979485566356}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 3.872983346207417}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 3.4641016151377544}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 6.48074069840786}, "controlpi.messagebus.MessageBus": {"tf": 3.872983346207417}, "controlpi.messagebus.MessageBus.__init__": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageBus.register": {"tf": 3}, "controlpi.messagebus.MessageBus.unregister": {"tf": 3}, "controlpi.messagebus.MessageBus.run": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageBus.send": {"tf": 3}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 3}, "controlpi.pluginregistry": {"tf": 5.385164807134504}, "controlpi.pluginregistry.PluginRegistry": {"tf": 4.795831523312719}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 4.123105625617661}, "controlpi_plugins.state": {"tf": 3}, "controlpi_plugins.state.State": {"tf": 3}, "controlpi_plugins.state.StateAlias": {"tf": 3}, "controlpi_plugins.state.AndState": {"tf": 3}, "controlpi_plugins.state.OrState": {"tf": 3}, "controlpi_plugins.state.AndSet": {"tf": 3}, "controlpi_plugins.state.OrSet": {"tf": 3}, "controlpi_plugins.util": {"tf": 2.449489742783178}, "controlpi_plugins.util.Log": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Init": {"tf": 3.4641016151377544}, "controlpi_plugins.util.Execute": {"tf": 2.449489742783178}, "controlpi_plugins.util.Alias": {"tf": 4.242640687119285}, "controlpi_plugins.util.Counter": {"tf": 2.449489742783178}, "controlpi_plugins.util.Date": {"tf": 3}, "controlpi_plugins.wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.Wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.GenericWait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.Timer": {"tf": 2.449489742783178}, "controlpi_plugins.wait.Periodic": {"tf": 2.449489742783178}}, "df": 52}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus.register_schema": {"tf": 1}}, "df": 2}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.get_callbacks": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}, "controlpi_plugins.state": {"tf": 2.23606797749979}, "controlpi_plugins.state.State": {"tf": 2.23606797749979}, "controlpi_plugins.state.StateAlias": {"tf": 2.449489742783178}, "controlpi_plugins.state.AndState": {"tf": 2.23606797749979}, "controlpi_plugins.state.OrState": {"tf": 2.23606797749979}, "controlpi_plugins.state.AndSet": {"tf": 2.449489742783178}, "controlpi_plugins.state.OrSet": {"tf": 2}, "controlpi_plugins.util.Counter": {"tf": 3}, "controlpi_plugins.util.Date": {"tf": 2.6457513110645907}}, "df": 16, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 11}}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"controlpi.pluginregistry": {"tf": 1.4142135623730951}}, "df": 1, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.wait": {"tf": 3}, "controlpi_plugins.wait.GenericWait": {"tf": 3.3166247903554}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}}, "df": 3}}}}}}}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}, "o": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageBus.send": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.7320508075688772}}, "df": 2}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}}, "df": 2}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 1}}}}}, "g": {"docs": {"controlpi.run": {"tf": 2}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 2.23606797749979}, "controlpi_plugins.util": {"tf": 2.449489742783178}, "controlpi_plugins.util.Log": {"tf": 3.872983346207417}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}}, "df": 6, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageBus": {"tf": 3.7416573867739413}, "controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 3}, "d": {"docs": {"controlpi_plugins.util.Log": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}}, "df": 2}}}, "s": {"docs": {"controlpi_plugins.util": {"tf": 1}}, "df": 1}}, "t": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}, "o": {"docs": {}, "df": 0, "p": {"docs": {"controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 2}, "controlpi_plugins.wait.Periodic.run": {"tf": 1}}, "df": 3}}, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.wait": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Wait": {"tf": 2.449489742783178}, "controlpi_plugins.wait.GenericWait": {"tf": 1.7320508075688772}}, "df": 3}}}, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 2}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 15, "s": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 4}}}, "k": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state": {"tf": 2.23606797749979}}, "df": 1}}}, "t": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.4142135623730951}}, "df": 4}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.update": {"tf": 2}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}}, "df": 6}}}, "e": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1}}, "n": {"docs": {"controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}}, "df": 2}, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}}, "df": 2}}}}}, "j": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message.update": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}}, "df": 3, "s": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.register_schema": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 12}}}, "u": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.state": {"tf": 1.7320508075688772}}, "df": 1}}}}, "d": {"docs": {"controlpi_plugins.util.Date": {"tf": 1}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 2, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.run": {"tf": 1}}, "df": 1}, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.pluginregistry": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 2}}}}}}}}}, "j": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrState": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet": {"tf": 1}}, "df": 3}}}}}}}}}, "c": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 2, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}}, "df": 2}, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 2}}, "df": 3}}}}}}}}}}, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1}}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.pluginregistry": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.state": {"tf": 1}}, "df": 1}}}}}}}}, "d": {"docs": {"controlpi_plugins.state": {"tf": 1}}, "df": 1}}, "e": {"docs": {}, "df": 0, "f": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 2.8284271247461903}, "controlpi.baseplugin.BasePlugin": {"tf": 2.6457513110645907}, "controlpi.messagebus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}}, "df": 11, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}}, "df": 6, "d": {"docs": {"controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}}, "df": 4}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1.4142135623730951}}, "df": 2}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi_plugins.util.Alias": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}}, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1}}, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}}}, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1}}}, "e": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1.4142135623730951}}, "df": 2}}}}, "s": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.state": {"tf": 1}}, "df": 1}}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}}, "df": 2}}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.util": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}}, "df": 3}}}}}, "o": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 29}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}}, "df": 2}}}, "n": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}}, "df": 4}}, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}}, "df": 4}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {"controlpi.test": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1.7320508075688772}}, "df": 4}, "e": {"docs": {"controlpi_plugins.util.Date": {"tf": 5.477225575051661}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 2, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}}, "df": 1}}}}, "e": {"docs": {"controlpi_plugins.state": {"tf": 1.7320508075688772}}, "df": 1}}, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"controlpi.messagebus": {"tf": 1}}, "df": 1, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}}, "df": 2}}}}}}}}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.run": {"tf": 5.830951894845301}, "controlpi.test": {"tf": 5.830951894845301}, "controlpi.baseplugin": {"tf": 2.8284271247461903}, "controlpi.baseplugin.BasePlugin": {"tf": 2.8284271247461903}, "controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.TemplateRegistry": {"tf": 4.242640687119285}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}, "controlpi.pluginregistry": {"tf": 2}, "controlpi.pluginregistry.PluginRegistry": {"tf": 2}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.4142135623730951}, "controlpi_plugins.state": {"tf": 11.661903789690601}, "controlpi_plugins.state.State": {"tf": 6.48074069840786}, "controlpi_plugins.state.StateAlias": {"tf": 7.211102550927978}, "controlpi_plugins.state.AndState": {"tf": 8.366600265340756}, "controlpi_plugins.state.OrState": {"tf": 8.366600265340756}, "controlpi_plugins.state.AndSet": {"tf": 9.38083151964686}, "controlpi_plugins.state.OrSet": {"tf": 8.94427190999916}, "controlpi_plugins.util": {"tf": 7.211102550927978}, "controlpi_plugins.util.Log": {"tf": 6.6332495807108}, "controlpi_plugins.util.Init": {"tf": 6.48074069840786}, "controlpi_plugins.util.Execute": {"tf": 6}, "controlpi_plugins.util.Alias": {"tf": 10.770329614269007}, "controlpi_plugins.util.Counter": {"tf": 8.366600265340756}, "controlpi_plugins.util.Date": {"tf": 5.656854249492381}, "controlpi_plugins.wait": {"tf": 6}, "controlpi_plugins.wait.Wait": {"tf": 5.656854249492381}, "controlpi_plugins.wait.GenericWait": {"tf": 5.830951894845301}, "controlpi_plugins.wait.Timer": {"tf": 6.928203230275509}, "controlpi_plugins.wait.Periodic": {"tf": 3.7416573867739413}}, "df": 33}}, "e": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1}}, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}}, "df": 2}, "s": {"docs": {"controlpi_plugins.util.Counter": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 6, "x": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.run": {"tf": 2.6457513110645907}, "controlpi.test": {"tf": 3.3166247903554}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message": {"tf": 3}, "controlpi.messagebus.Message.__init__": {"tf": 2}, "controlpi.messagebus.Message.update": {"tf": 2.449489742783178}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 2}, "controlpi.messagebus.MessageTemplate.check": {"tf": 3.7416573867739413}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 15, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 2}}}}}, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 1}}}}}, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.4142135623730951}}, "df": 7, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.run": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}}, "df": 7}, "a": {"docs": {}, "df": 0, "l": {"docs": {"controlpi.messagebus.Message": {"tf": 1}}, "df": 1}}}}}}}}}, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.test": {"tf": 1.7320508075688772}, "controlpi_plugins.util": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 2.23606797749979}, "controlpi_plugins.util.Execute": {"tf": 4.242640687119285}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}}, "df": 5, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}}, "df": 2}, "s": {"docs": {"controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}}, "df": 2}}}}}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}}, "df": 3}}}}}}}}}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.messagebus": {"tf": 2.23606797749979}, "controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry.__init__": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}}, "df": 8}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 2, "t": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 2.23606797749979}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi_plugins.state": {"tf": 3}, "controlpi_plugins.state.State": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1.7320508075688772}, "controlpi_plugins.state.AndState": {"tf": 2.449489742783178}, "controlpi_plugins.state.OrState": {"tf": 2.23606797749979}, "controlpi_plugins.state.AndSet": {"tf": 2.449489742783178}, "controlpi_plugins.state.OrSet": {"tf": 2.23606797749979}, "controlpi_plugins.util": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 2.8284271247461903}, "controlpi_plugins.wait.Wait": {"tf": 2.6457513110645907}, "controlpi_plugins.wait.GenericWait": {"tf": 2.23606797749979}, "controlpi_plugins.wait.Timer": {"tf": 2.8284271247461903}, "controlpi_plugins.wait.Periodic": {"tf": 1.4142135623730951}}, "df": 23, "s": {"docs": {"controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}}, "df": 4}}}, "r": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.test": {"tf": 1}}, "df": 1, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 2}}}}}}}}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}}, "df": 4}}}, "n": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}}, "df": 2}, "f": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}}, "df": 2}}}}}}}, "l": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}}, "df": 2}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}}, "df": 10}}}}}}, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}}, "df": 3}}}}}}}, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin.ConfException": {"tf": 1}, "controlpi.messagebus.BusException": {"tf": 1}}, "df": 2}}}}}, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}}, "df": 1}}}}}}}}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {"controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.State.run": {"tf": 1}, "controlpi_plugins.state.StateAlias.run": {"tf": 1}, "controlpi_plugins.state.AndState.run": {"tf": 1}, "controlpi_plugins.state.OrState.run": {"tf": 1}, "controlpi_plugins.state.AndSet.run": {"tf": 1}, "controlpi_plugins.state.OrSet.run": {"tf": 1}, "controlpi_plugins.util.Log.run": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.run": {"tf": 1}, "controlpi_plugins.util.Alias.run": {"tf": 1}, "controlpi_plugins.util.Counter.run": {"tf": 1}, "controlpi_plugins.util.Date.run": {"tf": 1}, "controlpi_plugins.wait.Wait.run": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait.run": {"tf": 1}, "controlpi_plugins.wait.Timer.run": {"tf": 1}}, "df": 17, "r": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.run": {"tf": 1}, "controlpi.test": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1}, "controlpi_plugins.state.OrSet": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Counter": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 1}}, "df": 29}}}}}}}, "t": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.messagebus": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message.check_value": {"tf": 2}, "controlpi.messagebus.Message.update": {"tf": 2.449489742783178}, "controlpi.messagebus.Message.setdefault": {"tf": 2}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 1}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 1}, "controlpi_plugins.state": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log": {"tf": 2}, "controlpi_plugins.util.Init": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 2.23606797749979}}, "df": 17, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.MessageBus.register": {"tf": 1}}, "df": 2}}}}}, "n": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 1, "e": {"docs": {"controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.7320508075688772}}, "df": 4}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"controlpi.messagebus.MessageBus.send_nowait": {"tf": 1.7320508075688772}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.baseplugin.BasePlugin": {"tf": 1.7320508075688772}, "controlpi.messagebus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus": {"tf": 1.7320508075688772}, "controlpi.pluginregistry": {"tf": 2.23606797749979}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.7320508075688772}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1.7320508075688772}, "controlpi_plugins.state": {"tf": 1.4142135623730951}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}}, "df": 13, "s": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}}, "df": 7, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.pluginregistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry.PluginRegistry.__init__": {"tf": 1}}, "df": 3}}}}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "k": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}, "w": {"docs": {"controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus.__init__": {"tf": 1}, "controlpi_plugins.state": {"tf": 3.1622776601683795}, "controlpi_plugins.state.State": {"tf": 2.449489742783178}, "controlpi_plugins.state.StateAlias": {"tf": 2.23606797749979}, "controlpi_plugins.state.AndState": {"tf": 2.449489742783178}, "controlpi_plugins.state.OrState": {"tf": 2.449489742783178}, "controlpi_plugins.state.AndSet": {"tf": 3.1622776601683795}, "controlpi_plugins.state.OrSet": {"tf": 2.8284271247461903}, "controlpi_plugins.util.Alias": {"tf": 2.449489742783178}}, "df": 11}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 1}}}}, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1}}, "df": 1}}}}}}}}}}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"controlpi.messagebus.MessageTemplate": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.7320508075688772}, "controlpi_plugins.wait": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}}, "df": 10}}}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin": {"tf": 1}, "controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.Message": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 8}}, "s": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin.process_conf": {"tf": 1}, "controlpi.messagebus": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi_plugins.util.Log": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.wait.GenericWait": {"tf": 1}}, "df": 11}, "r": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}, "controlpi.baseplugin.BasePlugin.run": {"tf": 1}, "controlpi.pluginregistry.PluginRegistry": {"tf": 1}, "controlpi_plugins.state": {"tf": 1}}, "df": 4}}}, "%": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "%": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.util.Date": {"tf": 1}}, "df": 1}}}}, ":": {"docs": {}, "df": 0, "%": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, ":": {"docs": {}, "df": 0, "%": {"docs": {}, "df": 0, "s": {"docs": {"controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 1}}}}}}}, "v": {"1": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 8.426149773176359}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1.7320508075688772}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 4.123105625617661}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 3}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 2.449489742783178}}, "df": 6}, "2": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 6}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 1}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 2.8284271247461903}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 2}}, "df": 6}, "docs": {"controlpi.baseplugin": {"tf": 1.4142135623730951}}, "df": 1, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.test": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 3.4641016151377544}, "controlpi.messagebus.Message.update": {"tf": 2}, "controlpi.messagebus.Message.setdefault": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.check": {"tf": 2}, "controlpi_plugins.util.Log": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Init": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Alias": {"tf": 1.7320508075688772}}, "df": 11, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 1}, "controlpi.messagebus.validate": {"tf": 1}, "controlpi.messagebus.MessageTemplate": {"tf": 1}}, "df": 3, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}, "controlpi.baseplugin.BasePlugin": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 3}, "s": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}}, "df": 1}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 1}}, "df": 1}}}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.messagebus.Message.update": {"tf": 1}, "controlpi.messagebus.Message.setdefault": {"tf": 1}, "controlpi.messagebus.MessageTemplate.update": {"tf": 1}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 1}}, "df": 4}}}}}, "u": {"docs": {}, "df": 0, "e": {"docs": {"controlpi.messagebus": {"tf": 2.23606797749979}, "controlpi.messagebus.Message": {"tf": 3.605551275463989}, "controlpi.messagebus.Message.__init__": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message.check_value": {"tf": 4.47213595499958}, "controlpi.messagebus.Message.update": {"tf": 3.1622776601683795}, "controlpi.messagebus.Message.setdefault": {"tf": 2.6457513110645907}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 2}, "controlpi.messagebus.MessageTemplate.check": {"tf": 3}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 2}}, "df": 17, "s": {"docs": {"controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi.pluginregistry": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 1}}, "df": 8}}}}, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"controlpi.baseplugin.BasePlugin": {"tf": 1}}, "df": 1}}}}}}}}}, "k": {"1": {"docs": {"controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.TemplateRegistry": {"tf": 8.54400374531753}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 4.123105625617661}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 3}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 2.8284271247461903}, "controlpi.messagebus.MessageBus": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus.register": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus.unregister": {"tf": 1}, "controlpi.messagebus.MessageBus.send": {"tf": 2.23606797749979}, "controlpi.messagebus.MessageBus.send_nowait": {"tf": 2.23606797749979}}, "df": 12}, "2": {"docs": {"controlpi.messagebus.TemplateRegistry": {"tf": 8.48528137423857}, "controlpi.messagebus.TemplateRegistry.insert": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.delete": {"tf": 2}, "controlpi.messagebus.TemplateRegistry.check": {"tf": 4.123105625617661}, "controlpi.messagebus.TemplateRegistry.get": {"tf": 3}, "controlpi.messagebus.TemplateRegistry.get_templates": {"tf": 2.8284271247461903}}, "df": 6}, "docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "y": {"docs": {"controlpi.baseplugin": {"tf": 2.6457513110645907}, "controlpi.baseplugin.BasePlugin": {"tf": 3.4641016151377544}, "controlpi.messagebus": {"tf": 2.23606797749979}, "controlpi.messagebus.Message": {"tf": 3.7416573867739413}, "controlpi.messagebus.Message.__init__": {"tf": 1.7320508075688772}, "controlpi.messagebus.Message.check_value": {"tf": 1}, "controlpi.messagebus.Message.update": {"tf": 3.1622776601683795}, "controlpi.messagebus.Message.setdefault": {"tf": 2.449489742783178}, "controlpi.messagebus.MessageTemplate": {"tf": 3.1622776601683795}, "controlpi.messagebus.MessageTemplate.__init__": {"tf": 1.7320508075688772}, "controlpi.messagebus.MessageTemplate.from_message": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.update": {"tf": 5.291502622129181}, "controlpi.messagebus.MessageTemplate.setdefault": {"tf": 3.4641016151377544}, "controlpi.messagebus.MessageTemplate.check": {"tf": 5.744562646538029}, "controlpi.messagebus.TemplateRegistry": {"tf": 1}, "controlpi.messagebus.MessageBus": {"tf": 2}, "controlpi.messagebus.MessageBus.register": {"tf": 1}, "controlpi_plugins.state.State": {"tf": 1}, "controlpi_plugins.state.StateAlias": {"tf": 1}, "controlpi_plugins.state.StateAlias.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndState": {"tf": 1}, "controlpi_plugins.state.AndState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrState": {"tf": 1}, "controlpi_plugins.state.OrState.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet": {"tf": 1.4142135623730951}, "controlpi_plugins.state.OrSet": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Log": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Log.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Init": {"tf": 1.7320508075688772}, "controlpi_plugins.util.Init.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 3}, "controlpi_plugins.util.Counter.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Date": {"tf": 1.4142135623730951}, "controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Wait": {"tf": 1}, "controlpi_plugins.wait.Wait.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Timer": {"tf": 1}, "controlpi_plugins.wait.Timer.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.wait.Periodic": {"tf": 2.23606797749979}, "controlpi_plugins.wait.Periodic.CONF_SCHEMA": {"tf": 1}}, "df": 41, "s": {"docs": {"controlpi.messagebus": {"tf": 2}, "controlpi.messagebus.Message": {"tf": 1.4142135623730951}, "controlpi.messagebus.Message.check_value": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageTemplate.check": {"tf": 1}, "controlpi.messagebus.TemplateRegistry": {"tf": 1.4142135623730951}, "controlpi.messagebus.MessageBus": {"tf": 1}, "controlpi.pluginregistry": {"tf": 1}, "controlpi_plugins.state.State.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.AndSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.state.OrSet.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Execute.CONF_SCHEMA": {"tf": 1}, "controlpi_plugins.util.Alias": {"tf": 2}, "controlpi_plugins.util.Alias.CONF_SCHEMA": {"tf": 1.7320508075688772}, "controlpi_plugins.wait.GenericWait": {"tf": 1}, "controlpi_plugins.wait.GenericWait.CONF_SCHEMA": {"tf": 1}}, "df": 16}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {"controlpi.baseplugin": {"tf": 1}}, "df": 1, "s": {"docs": {"controlpi_plugins.state": {"tf": 1}, "controlpi_plugins.util": {"tf": 1}, "controlpi_plugins.util.Execute": {"tf": 1}, "controlpi_plugins.wait": {"tf": 1}}, "df": 4}}}}}, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {"controlpi.messagebus.MessageTemplate.check": {"tf": 1.4142135623730951}}, "df": 1}}}}, "y": {"docs": {"controlpi_plugins.util.Date.CONF_SCHEMA": {"tf": 1}}, "df": 1, "%": {"docs": {}, "df": 0, "m": {"docs": {"controlpi_plugins.util.Date": {"tf": 1}}, "df": 1}}}}}}, "pipeline": ["trimmer"], "_isPrebuiltIndex": true}; + + // mirrored in build-search-index.js (part 1) + // Also split on html tags. this is a cheap heuristic, but good enough. + elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); + + let searchIndex; + if (docs._isPrebuiltIndex) { + console.info("using precompiled search index"); + searchIndex = elasticlunr.Index.load(docs); + } else { + console.time("building search index"); + // mirrored in build-search-index.js (part 2) + searchIndex = elasticlunr(function () { + this.pipeline.remove(elasticlunr.stemmer); + this.pipeline.remove(elasticlunr.stopWordFilter); + this.addField("qualname"); + this.addField("fullname"); + this.addField("annotation"); + this.addField("default_value"); + this.addField("signature"); + this.addField("bases"); + this.addField("doc"); + this.setRef("fullname"); + }); + for (let doc of docs) { + searchIndex.addDoc(doc); + } + console.timeEnd("building search index"); + } + + return (term) => searchIndex.search(term, { + fields: { + qualname: {boost: 4}, + fullname: {boost: 2}, + annotation: {boost: 2}, + default_value: {boost: 2}, + signature: {boost: 2}, + bases: {boost: 2}, + doc: {boost: 1}, + }, + expand: true + }); +})(); \ No newline at end of file diff --git a/doc/controlpi/baseplugin.html b/doc/controlpi/baseplugin.html deleted file mode 100644 index 7f2f42f..0000000 --- a/doc/controlpi/baseplugin.html +++ /dev/null @@ -1,621 +0,0 @@ - - - - - - -controlpi.baseplugin API documentation - - - - - - - - - - - -
-
-
-

Module controlpi.baseplugin

-
-
-

Define base class for all ControlPi plugins.

-

The class BasePlugin provides the abstract base class for concrete plugins -running on the ControlPi system.

-

It has three abstract methods that have to be implemented by all concrete -plugins: -- The class property CONF_SCHEMA is the JSON schema of the configuration of -the plugin. The configuration read from the global configuration file is -checked against this schema during initialisation. -- The method process_conf is called at the end of initialisation and is used -to initialise the plugin. It can be assumed that self.bus is the message -bus of the system, self.name the instance name, and self.conf the -configuration already validated against the schema. -- The run coroutines of all plugins are executed concurrently by the main -system.

-
>>> class TestPlugin(BasePlugin):
-...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
-...                    'required': ['key']}
-...     def process_conf(self):
-...         if 'key' in self.conf:
-...             print(f"Processing '{self.conf['key']}'.")
-...     async def run(self):
-...         print("Doing something else.")
-
-

Plugins are configured and run based on the information in the global -configuration. Here, we test this manually:

-
>>> async def test():
-...     p = TestPlugin(MessageBus(), 'Test Instance', {'key': 'Something'})
-...     await p.run()
->>> asyncio.run(test())
-Processing 'Something'.
-Doing something else.
-
-

Each plugin gets a reference to the system message bus during -initialisation, which can be accessed as self.bus in the functions of the -plugin class. This can be used to register and unregister message bus -clients:

-
>>> class BusPlugin(BasePlugin):
-...     CONF_SCHEMA = True
-...     async def receive(self, message):
-...         print(f"{self.name} received {message}.")
-...         await self.bus.send({'sender': self.name, 'event': 'Receive'})
-...     def process_conf(self):
-...         self.bus.register(self.name, 'BusPlugin',
-...                           [{'event': {'type': 'string'}}],
-...                           [{'target': {'const': self.name}}],
-...                           self.receive)
-...     async def run(self):
-...         await self.bus.send({'sender': self.name, 'event': 'Run'})
-
-

Again, we run this manually here, but this is done by the main coroutine -when using the system in production:

-
>>> async def log(message):
-...     print(f"Log: {message}")
->>> async def test_bus_plugin():
-...     bus = MessageBus()
-...     p = BusPlugin(bus, 'Bus Test', {})
-...     bus.register('Test', 'TestPlugin',
-...                  [{}], [{'sender': {'const': 'Bus Test'}}], log)
-...     bus_task = asyncio.create_task(bus.run())
-...     asyncio.create_task(p.run())
-...     await bus.send({'sender': 'Test', 'target': 'Bus Test', 'key': 'v'})
-...     await asyncio.sleep(0)
-...     await asyncio.sleep(0)
-...     bus_task.cancel()
->>> asyncio.run(test_bus_plugin())
-Bus Test received {'sender': 'Test', 'target': 'Bus Test', 'key': 'v'}.
-Log: {'sender': 'Bus Test', 'event': 'Receive'}
-Log: {'sender': 'Bus Test', 'event': 'Run'}
-
-

Often, there will be a one-to-one correspondence between plugin -instances and message bus clients, a plugin instance will be a message bus -client. But there are also cases, where one plugin instance might register -and unregister a lot of message bus clients, maybe even dynamically through -its lifetime. A plugin for an input/output card might register separate -clients for each pin of the card, a plugin for some kind of hardware bus -might register separate clients for all devices connected to the bus, or a -network socket plugin might register separate clients for all connections -to the socket (and unregister them when the connection is closed).

-
- -Expand source code - -
"""Define base class for all ControlPi plugins.
-
-The class BasePlugin provides the abstract base class for concrete plugins
-running on the ControlPi system.
-
-It has three abstract methods that have to be implemented by all concrete
-plugins:
-- The class property CONF_SCHEMA is the JSON schema of the configuration of
-  the plugin. The configuration read from the global configuration file is
-  checked against this schema during initialisation.
-- The method process_conf is called at the end of initialisation and is used
-  to initialise the plugin. It can be assumed that self.bus is the message
-  bus of the system, self.name the instance name, and self.conf the
-  configuration already validated against the schema.
-- The run coroutines of all plugins are executed concurrently by the main
-  system.
->>> class TestPlugin(BasePlugin):
-...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
-...                    'required': ['key']}
-...     def process_conf(self):
-...         if 'key' in self.conf:
-...             print(f"Processing '{self.conf['key']}'.")
-...     async def run(self):
-...         print("Doing something else.")
-
-Plugins are configured and run based on the information in the global
-configuration. Here, we test this manually:
->>> async def test():
-...     p = TestPlugin(MessageBus(), 'Test Instance', {'key': 'Something'})
-...     await p.run()
->>> asyncio.run(test())
-Processing 'Something'.
-Doing something else.
-
-Each plugin gets a reference to the system message bus during
-initialisation, which can be accessed as self.bus in the functions of the
-plugin class. This can be used to register and unregister message bus
-clients:
->>> class BusPlugin(BasePlugin):
-...     CONF_SCHEMA = True
-...     async def receive(self, message):
-...         print(f"{self.name} received {message}.")
-...         await self.bus.send({'sender': self.name, 'event': 'Receive'})
-...     def process_conf(self):
-...         self.bus.register(self.name, 'BusPlugin',
-...                           [{'event': {'type': 'string'}}],
-...                           [{'target': {'const': self.name}}],
-...                           self.receive)
-...     async def run(self):
-...         await self.bus.send({'sender': self.name, 'event': 'Run'})
-
-Again, we run this manually here, but this is done by the main coroutine
-when using the system in production:
->>> async def log(message):
-...     print(f"Log: {message}")
->>> async def test_bus_plugin():
-...     bus = MessageBus()
-...     p = BusPlugin(bus, 'Bus Test', {})
-...     bus.register('Test', 'TestPlugin',
-...                  [{}], [{'sender': {'const': 'Bus Test'}}], log)
-...     bus_task = asyncio.create_task(bus.run())
-...     asyncio.create_task(p.run())
-...     await bus.send({'sender': 'Test', 'target': 'Bus Test', 'key': 'v'})
-...     await asyncio.sleep(0)
-...     await asyncio.sleep(0)
-...     bus_task.cancel()
->>> asyncio.run(test_bus_plugin())
-Bus Test received {'sender': 'Test', 'target': 'Bus Test', 'key': 'v'}.
-Log: {'sender': 'Bus Test', 'event': 'Receive'}
-Log: {'sender': 'Bus Test', 'event': 'Run'}
-
-Often, there will be a one-to-one correspondence between plugin
-instances and message bus clients, a plugin instance will be a message bus
-client. But there are also cases, where one plugin instance might register
-and unregister a lot of message bus clients, maybe even dynamically through
-its lifetime. A plugin for an input/output card might register separate
-clients for each pin of the card, a plugin for some kind of hardware bus
-might register separate clients for all devices connected to the bus, or a
-network socket plugin might register separate clients for all connections
-to the socket (and unregister them when the connection is closed).
-"""
-__pdoc__ = {'BasePlugin.CONF_SCHEMA': False}
-
-from abc import ABC, abstractmethod
-import asyncio
-import jsonschema  # type: ignore
-
-from controlpi.messagebus import MessageBus
-
-from typing import Union, Dict, List, Any
-JSONSchema = Union[bool, Dict[str, Union[None, str, int, float, bool,
-                                         Dict[str, Any], List[Any]]]]
-# Could be more specific.
-PluginConf = Dict[str, Any]
-# Could be more specific.
-
-
-class ConfException(Exception):
-    """Raise for errors in plugin configurations."""
-
-
-class BasePlugin(ABC):
-    """Base class for all ControlPi plugins.
-
-    >>> class TestPlugin(BasePlugin):
-    ...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
-    ...                    'required': ['key']}
-    ...     def process_conf(self):
-    ...         if 'key' in self.conf:
-    ...             print(f"Processing '{self.conf['key']}'.")
-    ...     async def run(self):
-    ...         print("Doing something else.")
-
-    Initialisation sets the instance variables bus to the given message bus,
-    name to the given name, and conf to the given configuration:
-    >>> class TestPlugin(BasePlugin):
-    ...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
-    ...                    'required': ['key']}
-    ...     def process_conf(self):
-    ...         if 'key' in self.conf:
-    ...             print(f"Processing '{self.conf['key']}'.")
-    ...     async def run(self):
-    ...         print("Doing something else.")
-    >>> async def test():
-    ...     p = TestPlugin(MessageBus(), 'Test Instance',
-    ...                    {'key': 'Something'})
-    ...     print(p.bus)
-    ...     print(p.name)
-    ...     print(p.conf)
-    >>> asyncio.run(test())  # doctest: +ELLIPSIS
-    Processing 'Something'.
-    <controlpi.messagebus.MessageBus object at 0x...>
-    Test Instance
-    {'key': 'Something'}
-
-    It also validates the configuration against the schema in CONF_SCHEMA
-    and raises ConfException if is not validated.
-    >>> async def test():
-    ...     p = TestPlugin(MessageBus(), 'Test Instance',
-    ...                    {'key': 42})
-    >>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
-    Traceback (most recent call last):
-      ...
-    baseplugin.ConfException: Configuration for 'Test Instance'
-    is not valid.
-    >>> async def test():
-    ...     p = TestPlugin(MessageBus(), 'Test Instance',
-    ...                    {'key 2': 'Something'})
-    >>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
-    Traceback (most recent call last):
-      ...
-    baseplugin.ConfException: Configuration for 'Test Instance'
-    is not valid.
-
-    Finally, it calls process_conf, which is the function that should be
-    overridden by concrete plugins.
-    """
-
-    @property
-    @classmethod
-    @abstractmethod
-    def CONF_SCHEMA(cls) -> JSONSchema:
-        """JSON schema for configuration of plugin.
-
-        Given configurations are validated against this schema in __init__.
-        process_conf and run can assume a valid configuration in self.conf.
-        """
-        raise NotImplementedError
-
-    def __init__(self, bus: MessageBus, name: str, conf: PluginConf) -> None:
-        assert isinstance(bus, MessageBus)
-        self.bus = bus
-        assert isinstance(name, str)
-        self.name = name
-        jsonschema.Draft7Validator.check_schema(type(self).CONF_SCHEMA)
-        validator = jsonschema.Draft7Validator(type(self).CONF_SCHEMA)
-        assert isinstance(conf, dict)
-        valid = True
-        for error in validator.iter_errors(conf):
-            print(error)
-            valid = False
-        if not valid:
-            raise ConfException(f"Configuration for '{self.name}'"
-                                " is not valid.")
-        self.conf = conf
-        self.process_conf()
-
-    @abstractmethod
-    def process_conf(self) -> None:
-        """Process the configuration.
-
-        Abstract method has to be overridden by concrete plugins.
-        process_conf is called at the end of initialisation after the bus
-        and the configuration are available as self.bus and self.conf, but
-        before any of the run coroutines are executed.
-        """
-        raise NotImplementedError
-
-    @abstractmethod
-    async def run(self) -> None:
-        """Run the plugin.
-
-        The coroutine is run concurrently with the message bus and all
-        other plugins. Initial messages and other tasks can be done here.
-        It is also okay to run a plugin-specific infinite loop concurrently
-        with the rest of the system.
-        """
-        raise NotImplementedError
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class ConfException -(*args, **kwargs) -
-
-

Raise for errors in plugin configurations.

-
- -Expand source code - -
class ConfException(Exception):
-    """Raise for errors in plugin configurations."""
-
-

Ancestors

-
    -
  • builtins.Exception
  • -
  • builtins.BaseException
  • -
-
-
-class BasePlugin -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

Base class for all ControlPi plugins.

-
>>> class TestPlugin(BasePlugin):
-...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
-...                    'required': ['key']}
-...     def process_conf(self):
-...         if 'key' in self.conf:
-...             print(f"Processing '{self.conf['key']}'.")
-...     async def run(self):
-...         print("Doing something else.")
-
-

Initialisation sets the instance variables bus to the given message bus, -name to the given name, and conf to the given configuration:

-
>>> class TestPlugin(BasePlugin):
-...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
-...                    'required': ['key']}
-...     def process_conf(self):
-...         if 'key' in self.conf:
-...             print(f"Processing '{self.conf['key']}'.")
-...     async def run(self):
-...         print("Doing something else.")
->>> async def test():
-...     p = TestPlugin(MessageBus(), 'Test Instance',
-...                    {'key': 'Something'})
-...     print(p.bus)
-...     print(p.name)
-...     print(p.conf)
->>> asyncio.run(test())  # doctest: +ELLIPSIS
-Processing 'Something'.
-<controlpi.messagebus.MessageBus object at 0x...>
-Test Instance
-{'key': 'Something'}
-
-

It also validates the configuration against the schema in CONF_SCHEMA -and raises ConfException if is not validated.

-
>>> async def test():
-...     p = TestPlugin(MessageBus(), 'Test Instance',
-...                    {'key': 42})
->>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
-Traceback (most recent call last):
-  ...
-baseplugin.ConfException: Configuration for 'Test Instance'
-is not valid.
->>> async def test():
-...     p = TestPlugin(MessageBus(), 'Test Instance',
-...                    {'key 2': 'Something'})
->>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
-Traceback (most recent call last):
-  ...
-baseplugin.ConfException: Configuration for 'Test Instance'
-is not valid.
-
-

Finally, it calls process_conf, which is the function that should be -overridden by concrete plugins.

-
- -Expand source code - -
class BasePlugin(ABC):
-    """Base class for all ControlPi plugins.
-
-    >>> class TestPlugin(BasePlugin):
-    ...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
-    ...                    'required': ['key']}
-    ...     def process_conf(self):
-    ...         if 'key' in self.conf:
-    ...             print(f"Processing '{self.conf['key']}'.")
-    ...     async def run(self):
-    ...         print("Doing something else.")
-
-    Initialisation sets the instance variables bus to the given message bus,
-    name to the given name, and conf to the given configuration:
-    >>> class TestPlugin(BasePlugin):
-    ...     CONF_SCHEMA = {'properties': {'key': {'type': 'string'}},
-    ...                    'required': ['key']}
-    ...     def process_conf(self):
-    ...         if 'key' in self.conf:
-    ...             print(f"Processing '{self.conf['key']}'.")
-    ...     async def run(self):
-    ...         print("Doing something else.")
-    >>> async def test():
-    ...     p = TestPlugin(MessageBus(), 'Test Instance',
-    ...                    {'key': 'Something'})
-    ...     print(p.bus)
-    ...     print(p.name)
-    ...     print(p.conf)
-    >>> asyncio.run(test())  # doctest: +ELLIPSIS
-    Processing 'Something'.
-    <controlpi.messagebus.MessageBus object at 0x...>
-    Test Instance
-    {'key': 'Something'}
-
-    It also validates the configuration against the schema in CONF_SCHEMA
-    and raises ConfException if is not validated.
-    >>> async def test():
-    ...     p = TestPlugin(MessageBus(), 'Test Instance',
-    ...                    {'key': 42})
-    >>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
-    Traceback (most recent call last):
-      ...
-    baseplugin.ConfException: Configuration for 'Test Instance'
-    is not valid.
-    >>> async def test():
-    ...     p = TestPlugin(MessageBus(), 'Test Instance',
-    ...                    {'key 2': 'Something'})
-    >>> asyncio.run(test())  # doctest: +NORMALIZE_WHITESPACE
-    Traceback (most recent call last):
-      ...
-    baseplugin.ConfException: Configuration for 'Test Instance'
-    is not valid.
-
-    Finally, it calls process_conf, which is the function that should be
-    overridden by concrete plugins.
-    """
-
-    @property
-    @classmethod
-    @abstractmethod
-    def CONF_SCHEMA(cls) -> JSONSchema:
-        """JSON schema for configuration of plugin.
-
-        Given configurations are validated against this schema in __init__.
-        process_conf and run can assume a valid configuration in self.conf.
-        """
-        raise NotImplementedError
-
-    def __init__(self, bus: MessageBus, name: str, conf: PluginConf) -> None:
-        assert isinstance(bus, MessageBus)
-        self.bus = bus
-        assert isinstance(name, str)
-        self.name = name
-        jsonschema.Draft7Validator.check_schema(type(self).CONF_SCHEMA)
-        validator = jsonschema.Draft7Validator(type(self).CONF_SCHEMA)
-        assert isinstance(conf, dict)
-        valid = True
-        for error in validator.iter_errors(conf):
-            print(error)
-            valid = False
-        if not valid:
-            raise ConfException(f"Configuration for '{self.name}'"
-                                " is not valid.")
-        self.conf = conf
-        self.process_conf()
-
-    @abstractmethod
-    def process_conf(self) -> None:
-        """Process the configuration.
-
-        Abstract method has to be overridden by concrete plugins.
-        process_conf is called at the end of initialisation after the bus
-        and the configuration are available as self.bus and self.conf, but
-        before any of the run coroutines are executed.
-        """
-        raise NotImplementedError
-
-    @abstractmethod
-    async def run(self) -> None:
-        """Run the plugin.
-
-        The coroutine is run concurrently with the message bus and all
-        other plugins. Initial messages and other tasks can be done here.
-        It is also okay to run a plugin-specific infinite loop concurrently
-        with the rest of the system.
-        """
-        raise NotImplementedError
-
-

Ancestors

-
    -
  • abc.ABC
  • -
-

Subclasses

- -

Methods

-
-
-def process_conf(self) ‑> NoneType -
-
-

Process the configuration.

-

Abstract method has to be overridden by concrete plugins. -process_conf is called at the end of initialisation after the bus -and the configuration are available as self.bus and self.conf, but -before any of the run coroutines are executed.

-
- -Expand source code - -
@abstractmethod
-def process_conf(self) -> None:
-    """Process the configuration.
-
-    Abstract method has to be overridden by concrete plugins.
-    process_conf is called at the end of initialisation after the bus
-    and the configuration are available as self.bus and self.conf, but
-    before any of the run coroutines are executed.
-    """
-    raise NotImplementedError
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run the plugin.

-

The coroutine is run concurrently with the message bus and all -other plugins. Initial messages and other tasks can be done here. -It is also okay to run a plugin-specific infinite loop concurrently -with the rest of the system.

-
- -Expand source code - -
@abstractmethod
-async def run(self) -> None:
-    """Run the plugin.
-
-    The coroutine is run concurrently with the message bus and all
-    other plugins. Initial messages and other tasks can be done here.
-    It is also okay to run a plugin-specific infinite loop concurrently
-    with the rest of the system.
-    """
-    raise NotImplementedError
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/doc/controlpi/index.html b/doc/controlpi/index.html deleted file mode 100644 index d42fb6c..0000000 --- a/doc/controlpi/index.html +++ /dev/null @@ -1,476 +0,0 @@ - - - - - - -controlpi API documentation - - - - - - - - - - - -
-
-
-

Package controlpi

-
-
-

Provide the infrastructure for the ControlPi system.

-

The infrastructure consists of the message bus from module messagebus, the -plugin registry from module pluginregistry and the abstract base plugin from -module baseplugin.

-

The package combines them in its run function, which is used by main.py -to run a ControlPi system based on a configuration file indefinitely.

-

The test function is a utility function to test plugins with minimal -boilerplate code.

-
- -Expand source code - -
"""Provide the infrastructure for the ControlPi system.
-
-The infrastructure consists of the message bus from module messagebus, the
-plugin registry from module pluginregistry and the abstract base plugin from
-module baseplugin.
-
-The package combines them in its run function, which is used by __main__.py
-to run a ControlPi system based on a configuration file indefinitely.
-
-The test function is a utility function to test plugins with minimal
-boilerplate code.
-"""
-import asyncio
-import jsonschema  # type: ignore
-
-from controlpi.messagebus import MessageBus, Message, MessageTemplate
-from controlpi.pluginregistry import PluginRegistry
-from controlpi.baseplugin import BasePlugin, PluginConf, ConfException
-
-from typing import Dict, List, Coroutine, Any
-
-
-CONF_SCHEMA = {'type': 'object',
-               'patternProperties': {'.*': {'type': 'object'}}}
-
-
-def _process_conf(message_bus: MessageBus,
-                  conf: Dict[str, PluginConf]) -> List[Coroutine]:
-    jsonschema.Draft7Validator.check_schema(CONF_SCHEMA)
-    validator = jsonschema.Draft7Validator(CONF_SCHEMA)
-    valid = True
-    for error in validator.iter_errors(conf):
-        print(error)
-        valid = False
-    if not valid:
-        return []
-    plugins = PluginRegistry('controlpi_plugins', BasePlugin)
-    coroutines = [message_bus.run()]
-    for instance_name in conf:
-        instance_conf = conf[instance_name]
-        if 'plugin' not in instance_conf:
-            print("No plugin implementation specified for instance"
-                  f" '{instance_name}'.")
-            continue
-        plugin_name = instance_conf['plugin']
-        if plugin_name not in plugins:
-            print(f"No implementation found for plugin '{plugin_name}'"
-                  f" (specified for instance '{instance_name}').")
-            continue
-        plugin = plugins[plugin_name]
-        try:
-            instance = plugin(message_bus, instance_name, instance_conf)
-            coroutines.append(instance.run())
-        except ConfException as e:
-            print(e)
-            continue
-    return coroutines
-
-
-async def run(conf: Dict[str, PluginConf]) -> None:
-    """Run the ControlPi system based on a configuration.
-
-    Setup message bus, process given configuration, and run message bus and
-    plugins concurrently and indefinitely.
-
-    This function is mainly used by __main__.py to run a ControlPi system
-    based on a configuration loaded from a configuration JSON file on disk.
-
-    >>> async def test_coroutine():
-    ...     conf = {"Example Init":
-    ...             {"plugin": "Init",
-    ...              "messages": [{"id": 42,
-    ...                            "content": "Test Message"},
-    ...                           {"id": 42.42,
-    ...                            "content": "Second Message"}]},
-    ...             "Example Log":
-    ...             {"plugin": "Log",
-    ...              "filter": [{"sender": {"const": "Example Init"}}]}}
-    ...     run_task = asyncio.create_task(run(conf))
-    ...     await asyncio.sleep(0.1)
-    ...     run_task.cancel()
-    >>> asyncio.run(test_coroutine())  # doctest: +NORMALIZE_WHITESPACE
-    Example Log: {'sender': 'Example Init',
-                  'id': 42, 'content': 'Test Message'}
-    Example Log: {'sender': 'Example Init',
-                  'id': 42.42, 'content': 'Second Message'}
-    """
-    message_bus = MessageBus()
-    coroutines = _process_conf(message_bus, conf)
-    try:
-        await asyncio.gather(*coroutines)
-    except asyncio.exceptions.CancelledError:
-        pass
-
-
-async def test(conf: Dict[str, PluginConf],
-               messages: List[Dict[str, Any]],
-               wait: float = 0.0) -> None:
-    """Test configuration of ControlPi system.
-
-    Setup message bus, process given configuration, run message bus and
-    plugins concurrently, send given messages on message bus and print all
-    messages on message bus. Terminate when queue of message bus is empty.
-
-    This function allows to test single plugins or small plugin
-    configurations with minimal boilerplate code:
-    >>> asyncio.run(test(
-    ...     {"Example Init": {"plugin": "Init",
-    ...                       "messages": [{"id": 42,
-    ...                                     "content": "Test Message"},
-    ...                                    {"id": 42.42,
-    ...                                     "content": "Second Message"}]}},
-    ...     [{"target": "Example Init",
-    ...       "command": "execute"}]))  # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Example Init', 'plugin': 'Init',
-             'sends': [{'id': {'const': 42},
-                        'content': {'const': 'Test Message'}},
-                       {'id': {'const': 42.42},
-                        'content': {'const': 'Second Message'}}],
-             'receives': [{'target': {'const': 'Example Init'},
-                           'command': {'const': 'execute'}}]}
-    test(): {'sender': 'Example Init',
-             'id': 42, 'content': 'Test Message'}
-    test(): {'sender': 'Example Init',
-             'id': 42.42, 'content': 'Second Message'}
-    test(): {'sender': 'test()', 'target': 'Example Init',
-             'command': 'execute'}
-    test(): {'sender': 'Example Init',
-             'id': 42, 'content': 'Test Message'}
-    test(): {'sender': 'Example Init',
-             'id': 42.42, 'content': 'Second Message'}
-
-    Similar functionality could be reached by using the Log and Init plugins
-    to print messages and send some messages on the bus, but these would
-    clutter the test configuration and code to stop the indefinitely running
-    bus would have to be added to each and every test.
-
-    Incorrect plugin configurations can also be tested by this:
-    >>> asyncio.run(test(
-    ...     {"Example Init": {"plugin": "Init"}}, []))
-    'messages' is a required property
-    <BLANKLINE>
-    Failed validating 'required' in schema:
-        {'properties': {'messages': {'items': {'type': 'object'},
-                                     'type': 'array'}},
-         'required': ['messages']}
-    <BLANKLINE>
-    On instance:
-        {'plugin': 'Init'}
-    Configuration for 'Example Init' is not valid.
-    """
-    message_bus = MessageBus()
-
-    async def log(message):
-        if ('sender' in message and message['sender'] == '' and
-                'event' in message and message['event'] == 'registered' and
-                'client' in message and message['client'] == 'test()'):
-            # Do not log own registration of 'test()':
-            return
-        print(f"test(): {message}")
-    message_bus.register('test()', 'Test',
-                         [MessageTemplate()], [MessageTemplate()], log)
-
-    coroutines = _process_conf(message_bus, conf)
-    for coroutine in coroutines:
-        asyncio.create_task(coroutine)
-        # Give the created task opportunity to run:
-        await asyncio.sleep(0)
-    for message in messages:
-        await message_bus.send(Message('test()', message))
-        # Give immediate reactions to messages opportunity to happen:
-        await asyncio.sleep(0)
-    await asyncio.sleep(wait)
-    await message_bus._queue.join()
-
-
-
-

Sub-modules

-
-
controlpi.baseplugin
-
-

Define base class for all ControlPi plugins …

-
-
controlpi.messagebus
-
-

Provide an asynchronous message bus …

-
-
controlpi.pluginregistry
-
-

Provide a generic plugin system …

-
-
-
-
-
-
-

Functions

-
-
-async def run(conf: Dict[str, Dict[str, Any]]) ‑> NoneType -
-
-

Run the ControlPi system based on a configuration.

-

Setup message bus, process given configuration, and run message bus and -plugins concurrently and indefinitely.

-

This function is mainly used by main.py to run a ControlPi system -based on a configuration loaded from a configuration JSON file on disk.

-
>>> async def test_coroutine():
-...     conf = {"Example Init":
-...             {"plugin": "Init",
-...              "messages": [{"id": 42,
-...                            "content": "Test Message"},
-...                           {"id": 42.42,
-...                            "content": "Second Message"}]},
-...             "Example Log":
-...             {"plugin": "Log",
-...              "filter": [{"sender": {"const": "Example Init"}}]}}
-...     run_task = asyncio.create_task(run(conf))
-...     await asyncio.sleep(0.1)
-...     run_task.cancel()
->>> asyncio.run(test_coroutine())  # doctest: +NORMALIZE_WHITESPACE
-Example Log: {'sender': 'Example Init',
-              'id': 42, 'content': 'Test Message'}
-Example Log: {'sender': 'Example Init',
-              'id': 42.42, 'content': 'Second Message'}
-
-
- -Expand source code - -
async def run(conf: Dict[str, PluginConf]) -> None:
-    """Run the ControlPi system based on a configuration.
-
-    Setup message bus, process given configuration, and run message bus and
-    plugins concurrently and indefinitely.
-
-    This function is mainly used by __main__.py to run a ControlPi system
-    based on a configuration loaded from a configuration JSON file on disk.
-
-    >>> async def test_coroutine():
-    ...     conf = {"Example Init":
-    ...             {"plugin": "Init",
-    ...              "messages": [{"id": 42,
-    ...                            "content": "Test Message"},
-    ...                           {"id": 42.42,
-    ...                            "content": "Second Message"}]},
-    ...             "Example Log":
-    ...             {"plugin": "Log",
-    ...              "filter": [{"sender": {"const": "Example Init"}}]}}
-    ...     run_task = asyncio.create_task(run(conf))
-    ...     await asyncio.sleep(0.1)
-    ...     run_task.cancel()
-    >>> asyncio.run(test_coroutine())  # doctest: +NORMALIZE_WHITESPACE
-    Example Log: {'sender': 'Example Init',
-                  'id': 42, 'content': 'Test Message'}
-    Example Log: {'sender': 'Example Init',
-                  'id': 42.42, 'content': 'Second Message'}
-    """
-    message_bus = MessageBus()
-    coroutines = _process_conf(message_bus, conf)
-    try:
-        await asyncio.gather(*coroutines)
-    except asyncio.exceptions.CancelledError:
-        pass
-
-
-
-async def test(conf: Dict[str, Dict[str, Any]], messages: List[Dict[str, Any]], wait: float = 0.0) ‑> NoneType -
-
-

Test configuration of ControlPi system.

-

Setup message bus, process given configuration, run message bus and -plugins concurrently, send given messages on message bus and print all -messages on message bus. Terminate when queue of message bus is empty.

-

This function allows to test single plugins or small plugin -configurations with minimal boilerplate code:

-
>>> asyncio.run(test(
-...     {"Example Init": {"plugin": "Init",
-...                       "messages": [{"id": 42,
-...                                     "content": "Test Message"},
-...                                    {"id": 42.42,
-...                                     "content": "Second Message"}]}},
-...     [{"target": "Example Init",
-...       "command": "execute"}]))  # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Example Init', 'plugin': 'Init',
-         'sends': [{'id': {'const': 42},
-                    'content': {'const': 'Test Message'}},
-                   {'id': {'const': 42.42},
-                    'content': {'const': 'Second Message'}}],
-         'receives': [{'target': {'const': 'Example Init'},
-                       'command': {'const': 'execute'}}]}
-test(): {'sender': 'Example Init',
-         'id': 42, 'content': 'Test Message'}
-test(): {'sender': 'Example Init',
-         'id': 42.42, 'content': 'Second Message'}
-test(): {'sender': 'test()', 'target': 'Example Init',
-         'command': 'execute'}
-test(): {'sender': 'Example Init',
-         'id': 42, 'content': 'Test Message'}
-test(): {'sender': 'Example Init',
-         'id': 42.42, 'content': 'Second Message'}
-
-

Similar functionality could be reached by using the Log and Init plugins -to print messages and send some messages on the bus, but these would -clutter the test configuration and code to stop the indefinitely running -bus would have to be added to each and every test.

-

Incorrect plugin configurations can also be tested by this:

-
>>> asyncio.run(test(
-...     {"Example Init": {"plugin": "Init"}}, []))
-'messages' is a required property
-<BLANKLINE>
-Failed validating 'required' in schema:
-    {'properties': {'messages': {'items': {'type': 'object'},
-                                 'type': 'array'}},
-     'required': ['messages']}
-<BLANKLINE>
-On instance:
-    {'plugin': 'Init'}
-Configuration for 'Example Init' is not valid.
-
-
- -Expand source code - -
async def test(conf: Dict[str, PluginConf],
-               messages: List[Dict[str, Any]],
-               wait: float = 0.0) -> None:
-    """Test configuration of ControlPi system.
-
-    Setup message bus, process given configuration, run message bus and
-    plugins concurrently, send given messages on message bus and print all
-    messages on message bus. Terminate when queue of message bus is empty.
-
-    This function allows to test single plugins or small plugin
-    configurations with minimal boilerplate code:
-    >>> asyncio.run(test(
-    ...     {"Example Init": {"plugin": "Init",
-    ...                       "messages": [{"id": 42,
-    ...                                     "content": "Test Message"},
-    ...                                    {"id": 42.42,
-    ...                                     "content": "Second Message"}]}},
-    ...     [{"target": "Example Init",
-    ...       "command": "execute"}]))  # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Example Init', 'plugin': 'Init',
-             'sends': [{'id': {'const': 42},
-                        'content': {'const': 'Test Message'}},
-                       {'id': {'const': 42.42},
-                        'content': {'const': 'Second Message'}}],
-             'receives': [{'target': {'const': 'Example Init'},
-                           'command': {'const': 'execute'}}]}
-    test(): {'sender': 'Example Init',
-             'id': 42, 'content': 'Test Message'}
-    test(): {'sender': 'Example Init',
-             'id': 42.42, 'content': 'Second Message'}
-    test(): {'sender': 'test()', 'target': 'Example Init',
-             'command': 'execute'}
-    test(): {'sender': 'Example Init',
-             'id': 42, 'content': 'Test Message'}
-    test(): {'sender': 'Example Init',
-             'id': 42.42, 'content': 'Second Message'}
-
-    Similar functionality could be reached by using the Log and Init plugins
-    to print messages and send some messages on the bus, but these would
-    clutter the test configuration and code to stop the indefinitely running
-    bus would have to be added to each and every test.
-
-    Incorrect plugin configurations can also be tested by this:
-    >>> asyncio.run(test(
-    ...     {"Example Init": {"plugin": "Init"}}, []))
-    'messages' is a required property
-    <BLANKLINE>
-    Failed validating 'required' in schema:
-        {'properties': {'messages': {'items': {'type': 'object'},
-                                     'type': 'array'}},
-         'required': ['messages']}
-    <BLANKLINE>
-    On instance:
-        {'plugin': 'Init'}
-    Configuration for 'Example Init' is not valid.
-    """
-    message_bus = MessageBus()
-
-    async def log(message):
-        if ('sender' in message and message['sender'] == '' and
-                'event' in message and message['event'] == 'registered' and
-                'client' in message and message['client'] == 'test()'):
-            # Do not log own registration of 'test()':
-            return
-        print(f"test(): {message}")
-    message_bus.register('test()', 'Test',
-                         [MessageTemplate()], [MessageTemplate()], log)
-
-    coroutines = _process_conf(message_bus, conf)
-    for coroutine in coroutines:
-        asyncio.create_task(coroutine)
-        # Give the created task opportunity to run:
-        await asyncio.sleep(0)
-    for message in messages:
-        await message_bus.send(Message('test()', message))
-        # Give immediate reactions to messages opportunity to happen:
-        await asyncio.sleep(0)
-    await asyncio.sleep(wait)
-    await message_bus._queue.join()
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/doc/controlpi/messagebus.html b/doc/controlpi/messagebus.html deleted file mode 100644 index 46bea91..0000000 --- a/doc/controlpi/messagebus.html +++ /dev/null @@ -1,4244 +0,0 @@ - - - - - - -controlpi.messagebus API documentation - - - - - - - - - - - -
-
-
-

Module controlpi.messagebus

-
-
-

Provide an asynchronous message bus.

-

A message is a dictionary with string keys and string, integer, float, -Boolean, dictionary, or list values, where the inner dictionaries again -have string keys and these values and the inner lists also have elements of -these types. All messages have a special key 'sender' with the name of the -sending client as string value, which is set by the constructor:

-
>>> 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'}
-
-

A message template is a mapping from string keys to JSON schemas as values. -A message template matches a message if all keys of the template are -contained in the message and the values in the message validate against the -respective schemas. An empty mapping therefore matches all messages.

-

The bus executes asynchronous callbacks for all messages to be received by -a client. We use a simple callback printing the message in all examples:

-
>>> def callback_for_receiver(receiver):
-...     async def callback(message):
-...         print(f"{receiver}: {message}")
-...     return callback
-
-

Clients can be registered at the bus with a name, lists of message templates -they want to use for sending and receiving and a callback function for -receiving. An empty list of templates means that the client does not want to -send or receive any messages, respectively. A list with an empty template -means that it wants to send arbitrary or receive all messages, respectively:

-
>>> async def setup(bus):
-...     bus.register('Logger', 'Test Plugin',
-...                  [],
-...                  [{}],
-...                  callback_for_receiver('Logger'))
-...     bus.register('Client 1', 'Test Plugin',
-...                  [{'k1': {'type': 'string'}}],
-...                  [{'target': {'const': 'Client 1'}}],
-...                  callback_for_receiver('Client 1'))
-
-

While most clients should always use their own name for sending, this is not -enforced and debugging or management clients could send messages on behalf -of arbitrary client names.

-

The name of a client has to be unique and is not allowed to be empty -(otherwise registration fails).

-

The empty name is used to refer to the bus itself. The bus sends messages -for registrations and deregistrations of clients containing their complete -interface of send and receive templates. This can be used to allow dynamic -(debug) clients to deal with arbitrary configurations of clients. The bus -also reacts to 'get clients' command messages by sending the complete -information of all currently registered clients.

-

Clients can send to the bus with the send function. Each message has to -declare a sender. The send templates of that sender are checked for a -template matching the message:

-
>>> async def send(bus):
-...     print("Sending messages.")
-...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})
-...     await bus.send({'sender': '', 'target': 'Client 1'})
-
-

The run function executes the message bus forever. If we want to stop it, we -have to explicitly cancel the task:

-
>>> async def main():
-...     bus = MessageBus()
-...     await setup(bus)
-...     bus_task = asyncio.create_task(bus.run())
-...     await send(bus)
-...     await asyncio.sleep(0)
-...     bus_task.cancel()
->>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
-Sending messages.
-Logger: {'sender': '', 'event': 'registered',
-         'client': 'Logger', 'plugin': 'Test Plugin',
-         'sends': [], 'receives': [{}]}
-Logger: {'sender': '', 'event': 'registered',
-         'client': 'Client 1', 'plugin': 'Test Plugin',
-         'sends': [{'k1': {'type': 'string'}}],
-         'receives': [{'target': {'const': 'Client 1'}}]}
-Logger: {'sender': 'Client 1', 'k1': 'Test'}
-Logger: {'sender': '', 'target': 'Client 1'}
-Client 1: {'sender': '', 'target': 'Client 1'}
-
-
- -Expand source code - -
"""Provide an asynchronous message bus.
-
-A message is a dictionary with string keys and string, integer, float,
-Boolean, dictionary, or list values, where the inner dictionaries again
-have string keys and these values and the inner lists also have elements of
-these types. All messages have a special key 'sender' with the name of the
-sending client as string value, which is set by the constructor:
->>> 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'}
-
-A message template is a mapping from string keys to JSON schemas as values.
-A message template matches a message if all keys of the template are
-contained in the message and the values in the message validate against the
-respective schemas. An empty mapping therefore matches all messages.
-
-The bus executes asynchronous callbacks for all messages to be received by
-a client. We use a simple callback printing the message in all examples:
->>> def callback_for_receiver(receiver):
-...     async def callback(message):
-...         print(f"{receiver}: {message}")
-...     return callback
-
-Clients can be registered at the bus with a name, lists of message templates
-they want to use for sending and receiving and a callback function for
-receiving. An empty list of templates means that the client does not want to
-send or receive any messages, respectively. A list with an empty template
-means that it wants to send arbitrary or receive all messages, respectively:
->>> async def setup(bus):
-...     bus.register('Logger', 'Test Plugin',
-...                  [],
-...                  [{}],
-...                  callback_for_receiver('Logger'))
-...     bus.register('Client 1', 'Test Plugin',
-...                  [{'k1': {'type': 'string'}}],
-...                  [{'target': {'const': 'Client 1'}}],
-...                  callback_for_receiver('Client 1'))
-
-While most clients should always use their own name for sending, this is not
-enforced and debugging or management clients could send messages on behalf
-of arbitrary client names.
-
-The name of a client has to be unique and is not allowed to be empty
-(otherwise registration fails).
-
-The empty name is used to refer to the bus itself. The bus sends messages
-for registrations and deregistrations of clients containing their complete
-interface of send and receive templates. This can be used to allow dynamic
-(debug) clients to deal with arbitrary configurations of clients. The bus
-also reacts to 'get clients' command messages by sending the complete
-information of all currently registered clients.
-
-Clients can send to the bus with the send function. Each message has to
-declare a sender. The send templates of that sender are checked for a
-template matching the message:
->>> async def send(bus):
-...     print("Sending messages.")
-...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})
-...     await bus.send({'sender': '', 'target': 'Client 1'})
-
-The run function executes the message bus forever. If we want to stop it, we
-have to explicitly cancel the task:
->>> async def main():
-...     bus = MessageBus()
-...     await setup(bus)
-...     bus_task = asyncio.create_task(bus.run())
-...     await send(bus)
-...     await asyncio.sleep(0)
-...     bus_task.cancel()
->>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
-Sending messages.
-Logger: {'sender': '', 'event': 'registered',
-         'client': 'Logger', 'plugin': 'Test Plugin',
-         'sends': [], 'receives': [{}]}
-Logger: {'sender': '', 'event': 'registered',
-         'client': 'Client 1', 'plugin': 'Test Plugin',
-         'sends': [{'k1': {'type': 'string'}}],
-         'receives': [{'target': {'const': 'Client 1'}}]}
-Logger: {'sender': 'Client 1', 'k1': 'Test'}
-Logger: {'sender': '', 'target': 'Client 1'}
-Client 1: {'sender': '', 'target': 'Client 1'}
-"""
-import asyncio
-import json
-import jsonschema  # type: ignore
-
-from typing import Union, Dict, List, Any, Iterable, Callable, Coroutine
-MessageValue = Union[None, str, int, float, bool, Dict[str, Any], List[Any]]
-# Should really be:
-# MessageValue = Union[None, str, int, float, bool,
-#                      Dict[str, 'MessageValue'], List['MessageValue']]
-# But mypy does not support recursion by now:
-# https://github.com/python/mypy/issues/731
-JSONSchema = Union[bool, Dict[str, MessageValue]]
-# Could be even more specific.
-MessageCallback = Callable[['Message'], Coroutine[Any, Any, None]]
-
-
-class Message(Dict[str, MessageValue]):
-    """Define arbitrary message.
-
-    Messages are dictionaries with string keys and values that are strings,
-    integers, floats, Booleans, dictionaries that recursively have string
-    keys and values of any of these types, or lists with elements that have
-    any of these types. These constraints are checked when setting key-value
-    pairs of the message.
-
-    A message has to have a sender, which is set by the constructor:
-    >>> m = Message('Example sender')
-    >>> print(m)
-    {'sender': 'Example sender'}
-
-    A dictionary can be given to the constructor:
-    >>> 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'}
-
-    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'}
-    """
-
-    def __init__(self, sender: str,
-                 init: Dict[str, MessageValue] = None) -> None:
-        """Initialise message.
-
-        Message is initialised with given sender and possibly given
-        key-value pairs:
-        >>> m = Message('Example sender')
-        >>> print(m)
-        {'sender': 'Example sender'}
-        >>> 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
-        if init is not None:
-            self.update(init)
-
-    @staticmethod
-    def check_value(value: MessageValue) -> bool:
-        """Check recursively if a given value is valid.
-
-        None, strings, integers, floats and Booleans are valid:
-        >>> Message.check_value(None)
-        True
-        >>> Message.check_value('Spam')
-        True
-        >>> Message.check_value(42)
-        True
-        >>> Message.check_value(42.42)
-        True
-        >>> Message.check_value(False)
-        True
-
-        Other basic types are not valid:
-        >>> Message.check_value(b'bytes')
-        False
-        >>> Message.check_value(1j)
-        False
-
-        Dictionaries with string keys and recursively valid values are valid:
-        >>> Message.check_value({'str value': 'Spam', 'int value': 42,
-        ...                      'float value': 42.42, 'bool value': False})
-        True
-
-        Empty dictionaries are valid:
-        >>> Message.check_value({})
-        True
-
-        Dictionaries with other keys are not valid:
-        >>> Message.check_value({42: 'int key'})
-        False
-
-        Dictionaries with invalid values are not valid:
-        >>> Message.check_value({'complex value': 1j})
-        False
-
-        Lists with valid elements are valid:
-        >>> Message.check_value(['Spam', 42, 42.42, False])
-        True
-
-        Empty lists are valid:
-        >>> Message.check_value([])
-        True
-
-        Lists with invalid elements are not valid:
-        >>> Message.check_value([1j])
-        False
-        """
-        if value is None:
-            return True
-        elif (isinstance(value, str) or isinstance(value, int) or
-                isinstance(value, float) or isinstance(value, bool)):
-            return True
-        elif isinstance(value, dict):
-            for key in value:
-                if not isinstance(key, str):
-                    return False
-                if not Message.check_value(value[key]):
-                    return False
-            return True
-        elif isinstance(value, list):
-            for element in value:
-                if not Message.check_value(element):
-                    return False
-            return True
-        return False
-
-    def __setitem__(self, key: str, value: MessageValue) -> None:
-        """Check key and value before putting pair into dict.
-
-        >>> m = Message('Example sender')
-        >>> m['key'] = 'value'
-        >>> m['dict'] = {'k1': 'v1', 'k2': 2}
-        >>> print(m)  # doctest: +NORMALIZE_WHITESPACE
-        {'sender': 'Example sender', 'key': 'value',
-         'dict': {'k1': 'v1', 'k2': 2}}
-        >>> m = Message('Example sender')
-        >>> m[42] = 'int key'
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in Message (not a string).
-        >>> m['complex value'] = 1j
-        Traceback (most recent call last):
-          ...
-        TypeError: '1j' is not a valid value in Message.
-        """
-        if not isinstance(key, str):
-            raise TypeError(f"'{key}' is not a valid key in Message"
-                            " (not a string).")
-        if not self.check_value(value):
-            raise TypeError(f"'{value}' is not a valid value in Message.")
-        super().__setitem__(key, value)
-
-    def update(self, *args, **kwargs) -> None:
-        """Override update to use validity checks.
-
-        >>> m = Message('Example sender')
-        >>> m.update({'key 1': 'value 1', 'key 2': 'value 2'})
-        >>> print(m)
-        {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
-        >>> m.update({42: 'int key'})
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in Message (not a string).
-        >>> m.update({'complex value': 1j})
-        Traceback (most recent call last):
-          ...
-        TypeError: '1j' is not a valid value in Message.
-
-        This is also used in __init__:
-        >>> m = Message('Example sender', {'key': 'value'})
-        >>> print(m)
-        {'sender': 'Example sender', 'key': 'value'}
-        >>> m = Message('Example sender', {42: 'int key'})
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in Message (not a string).
-        >>> m = Message('Example sender', {'complex value': 1j})
-        Traceback (most recent call last):
-          ...
-        TypeError: '1j' is not a valid value in Message.
-        """
-        if args:
-            if len(args) > 1:
-                raise TypeError("update expected at most 1 argument,"
-                                f" got {len(args)}")
-            other = dict(args[0])
-            for key in other:
-                self[key] = other[key]
-        for key in kwargs:
-            self[key] = kwargs[key]
-
-    def setdefault(self, key: str, value: MessageValue = None) -> MessageValue:
-        """Override setdefault to use validity checks.
-
-        >>> m = Message('Example sender')
-        >>> m.setdefault('key', 'value 1')
-        'value 1'
-        >>> m.setdefault('key', 'value 2')
-        'value 1'
-        >>> m.setdefault(42, 'int key')
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in Message (not a string).
-        >>> m.setdefault('complex value', 1j)
-        Traceback (most recent call last):
-          ...
-        TypeError: '1j' is not a valid value in Message.
-
-        But __setitem__ is not called if the key is already present:
-        >>> m.setdefault('key', 1j)
-        'value 1'
-        """
-        if key not in self:
-            self[key] = value
-        return self[key]
-
-
-class MessageTemplate(Dict[str, JSONSchema]):
-    """Define a message template.
-
-    A message template is a mapping from string keys to JSON schemas as
-    values:
-    >>> t = MessageTemplate({'key 1': {'const': 'value'},
-    ...                      'key 2': {'type': 'string'}})
-    >>> t['key 3'] = {'type': 'object',
-    ...               'properties': {'key 1': {'type': 'number'},
-    ...                              'key 2': True}}
-
-    A message template matches a message if all keys of the template are
-    contained in the message and the values in the message validate against
-    the respective schemas:
-    >>> t.check(Message('Example Sender',
-    ...                 {'key 1': 'value', 'key 2': 'some string',
-    ...                  'key 3': {'key 1': 42, 'key 2': None}}))
-    True
-
-    An empty mapping therefore matches all messages:
-    >>> t = MessageTemplate()
-    >>> t.check(Message('Example Sender', {'arbitrary': 'content'}))
-    True
-    """
-
-    def __init__(self, init: Dict[str, JSONSchema] = None) -> None:
-        """Initialise message.
-
-        Template is initialised empty or with given key-value pairs:
-        >>> t = MessageTemplate()
-        >>> print(t)
-        {}
-        >>> t = MessageTemplate({'key': {'const': 'value'}})
-        >>> print(t)
-        {'key': {'const': 'value'}}
-        """
-        if init is not None:
-            self.update(init)
-
-    @staticmethod
-    def from_message(message: Message) -> 'MessageTemplate':
-        """Create template from message.
-
-        Template witch constant schemas is created from message:
-        >>> m = Message('Example Sender', {'key': 'value'})
-        >>> t = MessageTemplate.from_message(m)
-        >>> print(t)
-        {'sender': {'const': 'Example Sender'}, 'key': {'const': 'value'}}
-        >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
-        ...                                'list': [None, True, 'string']})
-        >>> t = MessageTemplate.from_message(m)
-        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-        {'sender': {'const': 'Example Sender'},
-         'dict': {'type': 'object',
-                  'properties': {'int': {'const': 42},
-                                 'float': {'const': 42.42}}},
-         'list': {'type': 'array',
-                  'items': [{'const': None},
-                            {'const': True},
-                            {'const': 'string'}]}}
-
-        This is especially useful for clients that send certain fully
-        predefined messages, where the message is given in the configuration
-        and the template for the registration can be constructed by this
-        method.
-        """
-        def schema_from_value(value: MessageValue) -> JSONSchema:
-            schema: JSONSchema = False
-            if value is None:
-                schema = {'const': None}
-            elif (isinstance(value, str) or isinstance(value, int) or
-                    isinstance(value, float) or isinstance(value, bool)):
-                schema = {'const': value}
-            elif isinstance(value, dict):
-                properties = {}
-                for inner_key in value:
-                    inner_value: Message = value[inner_key]
-                    properties[inner_key] = schema_from_value(inner_value)
-                schema = {'type': 'object',
-                          'properties': properties}
-            elif isinstance(value, list):
-                schema = {'type': 'array',
-                          'items': [schema_from_value(element)
-                                    for element in value]}
-            return schema
-        template = MessageTemplate()
-        for key in message:
-            template[key] = schema_from_value(message[key])
-        return template
-
-    def __setitem__(self, key: str, value: JSONSchema) -> None:
-        """Check key and value before putting pair into dict.
-
-        >>> t = MessageTemplate()
-        >>> t['key 1'] = {'const': 'value'}
-        >>> t['key 2'] = {'type': 'string'}
-        >>> t['key 3'] = {'type': 'object',
-        ...               'properties': {'key 1': {'type': 'number'},
-        ...                              'key 2': True}}
-        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
-         'key 3': {'type': 'object',
-                   'properties': {'key 1': {'type': 'number'},
-                                  'key 2': True}}}
-        >>> t[42] = {'const': 'int key'}
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in MessageTemplate (not a string).
-        >>> t['key'] = 'schema'  # doctest: +NORMALIZE_WHITESPACE
-        Traceback (most recent call last):
-          ...
-        TypeError: 'schema' is not a valid value in MessageTemplate
-        (not a valid JSON schema).
-        """
-        if not isinstance(key, str):
-            raise TypeError(f"'{key}' is not a valid key in MessageTemplate"
-                            " (not a string).")
-        try:
-            jsonschema.Draft7Validator.check_schema(value)
-            # Draft7Validator is hardcoded, because _LATEST_VERSION is
-            # non-public in jsonschema and we also perhaps do not want to
-            # upgrade automatically.
-        except jsonschema.exceptions.SchemaError:
-            raise TypeError(f"'{value}' is not a valid value in"
-                            " MessageTemplate (not a valid JSON schema).")
-        super().__setitem__(key, value)
-
-    def update(self, *args, **kwargs) -> None:
-        """Override update to use validity checks.
-
-        >>> t = MessageTemplate()
-        >>> t.update({'key 1': {'const': 'value'},
-        ...           'key 2': {'type': 'string'},
-        ...           'key 3': {'type': 'object',
-        ...                     'properties': {'key 1': {'type': 'number'},
-        ...                                    'key 2': True}}})
-        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
-         'key 3': {'type': 'object',
-                   'properties': {'key 1': {'type': 'number'},
-                                  'key 2': True}}}
-        >>> t.update({42: {'const': 'int key'}})
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in MessageTemplate (not a string).
-        >>> t.update({'key': 'schema'})  # doctest: +NORMALIZE_WHITESPACE
-        Traceback (most recent call last):
-          ...
-        TypeError: 'schema' is not a valid value in MessageTemplate
-        (not a valid JSON schema).
-
-        This is also used in __init__:
-        >>> t = MessageTemplate({'key 1': {'const': 'value'},
-        ...                      'key 2': {'type': 'string'},
-        ...                      'key 3': {'type': 'object',
-        ...                                'properties': {
-        ...                                    'key 1': {'type': 'number'},
-        ...                                    'key 2': True}}})
-        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
-         'key 3': {'type': 'object',
-                   'properties': {'key 1': {'type': 'number'},
-                                  'key 2': True}}}
-        >>> t = MessageTemplate({42: {'const': 'int key'}})
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in MessageTemplate (not a string).
-        >>> t = MessageTemplate({'key': 'schema'})
-        ... # doctest: +NORMALIZE_WHITESPACE
-        Traceback (most recent call last):
-          ...
-        TypeError: 'schema' is not a valid value in MessageTemplate
-        (not a valid JSON schema).
-        """
-        if args:
-            if len(args) > 1:
-                raise TypeError("update expected at most 1 argument,"
-                                f" got {len(args)}")
-            other = dict(args[0])
-            for key in other:
-                self[key] = other[key]
-        for key in kwargs:
-            self[key] = kwargs[key]
-
-    def setdefault(self, key: str, value: JSONSchema = None) -> JSONSchema:
-        """Override setdefault to use validity checks.
-
-        >>> t = MessageTemplate()
-        >>> t.setdefault('key 1', {'const': 'value'})
-        {'const': 'value'}
-        >>> t.setdefault('key 2', {'type': 'string'})
-        {'type': 'string'}
-        >>> t.setdefault('key 3', {'type': 'object',
-        ...                        'properties': {'key 1': {'type': 'number'},
-        ...                                       'key 2': True}})
-        ... # doctest: +NORMALIZE_WHITESPACE
-        {'type': 'object',
-                   'properties': {'key 1': {'type': 'number'},
-                                  'key 2': True}}
-        >>> t.setdefault(42, {'const': 'int key'})
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in MessageTemplate (not a string).
-        >>> t.setdefault('key', 'schema')  # doctest: +NORMALIZE_WHITESPACE
-        Traceback (most recent call last):
-          ...
-        TypeError: 'schema' is not a valid value in MessageTemplate
-        (not a valid JSON schema).
-
-        But __setitem__ is not called if the key is already present:
-        >>> t.setdefault('key 1', 'schema')
-        {'const': 'value'}
-        """
-        if key not in self:
-            if value is not None:
-                self[key] = value
-            else:
-                self[key] = True
-        return self[key]
-
-    def check(self, message: Message) -> bool:
-        """Check message against this template.
-
-        Constant values have to match exactly:
-        >>> t = MessageTemplate({'key': {'const': 'value'}})
-        >>> t.check(Message('Example Sender', {'key': 'value'}))
-        True
-        >>> t.check(Message('Example Sender', {'key': 'other value'}))
-        False
-
-        But for integers, floats with the same value are also valid:
-        >>> t = MessageTemplate({'key': {'const': 42}})
-        >>> t.check(Message('Example Sender', {'key': 42}))
-        True
-        >>> t.check(Message('Example Sender', {'key': 42.0}))
-        True
-
-        Type integer is valid for floats with zero fractional part, but
-        not by floats with non-zero fractional part:
-        >>> t = MessageTemplate({'key': {'type': 'integer'}})
-        >>> t.check(Message('Example Sender', {'key': 42}))
-        True
-        >>> t.check(Message('Example Sender', {'key': 42.0}))
-        True
-        >>> t.check(Message('Example Sender', {'key': 42.42}))
-        False
-
-        Type number is valid for arbitrary ints or floats:
-        >>> t = MessageTemplate({'key': {'type': 'number'}})
-        >>> t.check(Message('Example Sender', {'key': 42}))
-        True
-        >>> t.check(Message('Example Sender', {'key': 42.42}))
-        True
-
-        All keys in template have to be present in message:
-        >>> t = MessageTemplate({'key 1': {'const': 'value'},
-        ...                      'key 2': {'type': 'string'},
-        ...                      'key 3': {'type': 'object',
-        ...                                'properties': {
-        ...                                    'key 1': {'type': 'number'},
-        ...                                    'key 2': True,
-        ...                                    'key 3': False}}})
-        >>> t.check(Message('Example Sender',
-        ...                 {'key 1': 'value', 'key 2': 'some string'}))
-        False
-
-        But for nested objects their properties do not necessarily have
-        to be present:
-        >>> t.check(Message('Example Sender',
-        ...                 {'key 1': 'value', 'key 2': 'some string',
-        ...                  'key 3': {'key 1': 42}}))
-        True
-
-        Schema True matches everything (even None):
-        >>> t.check(Message('Example Sender',
-        ...                 {'key 1': 'value', 'key 2': 'some string',
-        ...                  'key 3': {'key 2': None}}))
-        True
-
-        Schema False matches nothing:
-        >>> t.check(Message('Example Sender',
-        ...                 {'key 1': 'value', 'key 2': 'some string',
-        ...                  'key 3': {'key 3': True}}))
-        False
-
-        Message is valid for the constant template created from it:
-        >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
-        ...                                'list': [None, True, 'string']})
-        >>> t = MessageTemplate.from_message(m)
-        >>> t.check(m)
-        True
-        """
-        for key in self:
-            if key not in message:
-                return False
-            else:
-                validator = jsonschema.Draft7Validator(self[key])
-                for error in validator.iter_errors(message[key]):
-                    return False
-        return True
-
-
-class MessageTemplateRegistry:
-    """Manage a collection of message templates with registered clients.
-
-    A new MessageTemplateRegistry is created by:
-    >>> r = MessageTemplateRegistry()
-
-    Client names (strings) can be registered for message templates, which
-    are mappings from keys to JSON schemas:
-    >>> r.insert({'k1': {'const': 'v1'}}, 'C 1')
-
-    The check function checks if the templates registered for a client
-    match a given message:
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 1', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: True
-    {'k1': 'v1', 'k2': 2}: True
-    {'k1': 'v2', 'k2': 'v1'}: False
-    {'k1': 'v2', 'k2': 2}: False
-
-    Clients can be registered for values validating against arbitrary JSON
-    schemas, e.g. all values of a certain type:
-    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}, 'C 2')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 2', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: False
-    {'k1': 'v1', 'k2': 2}: False
-    {'k1': 'v2', 'k2': 'v1'}: True
-    {'k1': 'v2', 'k2': 2}: False
-    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}, 'C 3')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 3', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: False
-    {'k1': 'v1', 'k2': 2}: False
-    {'k1': 'v2', 'k2': 'v1'}: False
-    {'k1': 'v2', 'k2': 2}: True
-
-    The order of key-value pairs does not have to match the order in the
-    messages and keys can be left out:
-    >>> r.insert({'k2': {'const': 2}}, 'C 4')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 4', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: False
-    {'k1': 'v1', 'k2': 2}: True
-    {'k1': 'v2', 'k2': 'v1'}: False
-    {'k1': 'v2', 'k2': 2}: True
-
-    A registration for an empty template matches all messages:
-    >>> r.insert({}, 'C 5')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 5', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: True
-    {'k1': 'v1', 'k2': 2}: True
-    {'k1': 'v2', 'k2': 'v1'}: True
-    {'k1': 'v2', 'k2': 2}: True
-
-    A client can be registered for multiple templates:
-    >>> r.insert({'k1': {'const': 'v1'}}, 'C 6')
-    >>> r.insert({'k2': {'const': 'v1'}}, 'C 6')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 6', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: True
-    {'k1': 'v1', 'k2': 2}: True
-    {'k1': 'v2', 'k2': 'v1'}: True
-    {'k1': 'v2', 'k2': 2}: False
-
-    Clients can be deregistered again (the result is False if the registry
-    is empty after the deletion):
-    >>> r.insert({'k1': {'const': 'v1'}}, 'C 7')
-    >>> r.delete('C 7')
-    True
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 7', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: False
-    {'k1': 'v1', 'k2': 2}: False
-    {'k1': 'v2', 'k2': 'v1'}: False
-    {'k1': 'v2', 'k2': 2}: False
-
-    The get function returns all clients with registered templates matching
-    a given message:
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.get(m)}")
-    {'k1': 'v1', 'k2': 'v1'}: ['C 5', 'C 1', 'C 6']
-    {'k1': 'v1', 'k2': 2}: ['C 5', 'C 1', 'C 6', 'C 4']
-    {'k1': 'v2', 'k2': 'v1'}: ['C 5', 'C 2', 'C 6']
-    {'k1': 'v2', 'k2': 2}: ['C 5', 'C 3', 'C 4']
-
-    The get_templates function returns all templates for a given client:
-    >>> for c in ['C 1', 'C 2', 'C 3', 'C 4', 'C 5', 'C 6']:
-    ...     print(f"{c}: {r.get_templates(c)}")
-    C 1: [{'k1': {'const': 'v1'}}]
-    C 2: [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
-    C 3: [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
-    C 4: [{'k2': {'const': 2}}]
-    C 5: [{}]
-    C 6: [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
-    """
-
-    def __init__(self) -> None:
-        """Initialise an empty registry.
-
-        >>> r = MessageTemplateRegistry()
-        """
-        self._clients: List[str] = []
-        self._children: Dict[str, Dict[str, MessageTemplateRegistry]] = {}
-
-    def insert(self, template: MessageTemplate, client: str) -> None:
-        """Register a client for a template.
-
-        >>> r = MessageTemplateRegistry()
-        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v1'}}, 'C 1')
-        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v2'}}, 'C 2')
-        >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v1'}}, 'C 3')
-        >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v2'}}, 'C 4')
-        >>> r.insert({}, 'C 5')
-
-        Implementation details:
-        -----------------------
-        The tree nodes on the way to a registered object are used/created
-        in the order given in the message template, which can be used to
-        design more efficient lookups (e.g., putting rarer key-value pairs
-        earlier in the template).
-        >>> r._clients
-        ['C 5']
-        >>> r._children.keys()
-        dict_keys(['k1'])
-        >>> r._children['k1'].keys()
-        dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-        >>> r._children['k1']['{"const": "v1"}']._clients
-        []
-        >>> r._children['k1']['{"const": "v1"}']._children.keys()
-        dict_keys(['k2'])
-        >>> r._children['k1']['{"const": "v1"}']._children['k2'].keys()
-        dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v1"}'])._clients
-        ['C 1']
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v1"}'])._children.keys()
-        dict_keys([])
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v2"}'])._clients
-        ['C 2']
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v2"}'])._children.keys()
-        dict_keys([])
-        >>> r._children['k1']['{"const": "v2"}']._clients
-        []
-        >>> r._children['k1']['{"const": "v2"}']._children.keys()
-        dict_keys(['k2'])
-        >>> r._children['k1']['{"const": "v2"}']._children['k2'].keys()
-        dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-        >>> (r._children['k1']['{"const": "v2"}']
-        ...   ._children['k2']['{"const": "v1"}'])._clients
-        ['C 3']
-        >>> (r._children['k1']['{"const": "v2"}']
-        ...   ._children['k2']['{"const": "v1"}'])._children.keys()
-        dict_keys([])
-        >>> (r._children['k1']['{"const": "v2"}']
-        ...   ._children['k2']['{"const": "v2"}'])._clients
-        ['C 4']
-        >>> (r._children['k1']['{"const": "v2"}']
-        ...   ._children['k2']['{"const": "v2"}'])._children.keys()
-        dict_keys([])
-        """
-        if not template:
-            self._clients.append(client)
-        else:
-            key, schema = next(iter(template.items()))
-            schema_string = json.dumps(schema)
-            reduced_template = MessageTemplate({k: template[k]
-                                                for k in template
-                                                if k != key})
-            if key not in self._children:
-                self._children[key] = {}
-            if schema_string not in self._children[key]:
-                self._children[key][schema_string] = MessageTemplateRegistry()
-            self._children[key][schema_string].insert(reduced_template, client)
-
-    def delete(self, client: str) -> bool:
-        """Unregister a client from all templates.
-
-        >>> r = MessageTemplateRegistry()
-        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v1'}}, 'C 1')
-        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v2'}}, 'C 2')
-        >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v1'}}, 'C 3')
-        >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v2'}}, 'C 4')
-        >>> r.insert({}, 'C 5')
-        >>> r.delete('C 3')
-        True
-        >>> r.delete('C 4')
-        True
-
-        Implementation details:
-        -----------------------
-        If parts of the tree become superfluous by the deletion of the
-        client, they are also completely removed to reduce the lookup
-        effort and keep the tree clean.
-        >>> r._clients
-        ['C 5']
-        >>> r._children.keys()
-        dict_keys(['k1'])
-        >>> r._children['k1'].keys()
-        dict_keys(['{"const": "v1"}'])
-        >>> r._children['k1']['{"const": "v1"}']._clients
-        []
-        >>> r._children['k1']['{"const": "v1"}']._children.keys()
-        dict_keys(['k2'])
-        >>> r._children['k1']['{"const": "v1"}']._children['k2'].keys()
-        dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v1"}'])._clients
-        ['C 1']
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v1"}'])._children.keys()
-        dict_keys([])
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v2"}'])._clients
-        ['C 2']
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v2"}'])._children.keys()
-        dict_keys([])
-        """
-        self._clients = [c for c in self._clients if c != client]
-        new_children: Dict[str, Dict[str, MessageTemplateRegistry]] = {}
-        for key in self._children:
-            new_children[key] = {}
-            for schema in self._children[key]:
-                if self._children[key][schema].delete(client):
-                    new_children[key][schema] = self._children[key][schema]
-            if not new_children[key]:
-                del new_children[key]
-        self._children = new_children
-        if self._clients or self._children:
-            return True
-        return False
-
-    def check(self, client: str, message: Message) -> bool:
-        """Get if a client has a registered template matching a message.
-
-        >>> r = MessageTemplateRegistry()
-        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
-        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-        ...     print(f"{m}: {r.check('Client 1', m)}")
-        {'k1': 'v1', 'k2': 'v1'}: True
-        {'k1': 'v1', 'k2': 'v2'}: True
-        {'k1': 'v2', 'k2': 'v1'}: False
-        {'k1': 'v2', 'k2': 'v2'}: False
-        >>> r.insert({'k2': {'const': 'v2'}}, 'Client 2')
-        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-        ...     print(f"{m}: {r.check('Client 2', m)}")
-        {'k1': 'v1', 'k2': 'v1'}: False
-        {'k1': 'v1', 'k2': 'v2'}: True
-        {'k1': 'v2', 'k2': 'v1'}: False
-        {'k1': 'v2', 'k2': 'v2'}: True
-        """
-        if client in self._clients:
-            return True
-        for key in self._children:
-            if key in message:
-                for schema_string in self._children[key]:
-                    schema = json.loads(schema_string)
-                    validator = jsonschema.Draft7Validator(schema)
-                    validated = True
-                    for error in validator.iter_errors(message[key]):
-                        validated = False
-                    if validated:
-                        child = self._children[key][schema_string]
-                        if child.check(client, message):
-                            return True
-        return False
-
-    def get(self, message: Message) -> List[str]:
-        """Get all clients registered for templates matching a message.
-
-        >>> r = MessageTemplateRegistry()
-        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
-        >>> r.insert({'k2': {'const': 'v2'}}, 'Client 2')
-        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-        ...     print(f"{m}: {r.get(m)}")
-        {'k1': 'v1', 'k2': 'v1'}: ['Client 1']
-        {'k1': 'v1', 'k2': 'v2'}: ['Client 1', 'Client 2']
-        {'k1': 'v2', 'k2': 'v1'}: []
-        {'k1': 'v2', 'k2': 'v2'}: ['Client 2']
-        """
-        result = []
-        for client in self._clients:
-            if client not in result:
-                result.append(client)
-        for key in self._children:
-            if key in message:
-                for schema_string in self._children[key]:
-                    schema = json.loads(schema_string)
-                    validator = jsonschema.Draft7Validator(schema)
-                    validated = True
-                    for error in validator.iter_errors(message[key]):
-                        validated = False
-                    if validated:
-                        child = self._children[key][schema_string]
-                        for client in child.get(message):
-                            if client not in result:
-                                result.append(client)
-        return result
-
-    def get_templates(self, client: str) -> List[MessageTemplate]:
-        """Get all templates for a client.
-
-        >>> r = MessageTemplateRegistry()
-        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
-        >>> r.get_templates('Client 1')
-        [{'k1': {'const': 'v1'}}]
-        >>> r.insert({'k1': {'const': 'v2'},
-        ...           'k2': {'type': 'string'}}, 'Client 2')
-        >>> r.get_templates('Client 2')
-        [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
-        >>> r.insert({'k1': {'const': 'v2'},
-        ...           'k2': {'type': 'integer'}}, 'Client 3')
-        >>> r.get_templates('Client 3')
-        [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
-        >>> r.insert({'k2': {'const': 2}}, 'Client 4')
-        >>> r.get_templates('Client 4')
-        [{'k2': {'const': 2}}]
-        >>> r.insert({}, 'Client 5')
-        >>> r.get_templates('Client 5')
-        [{}]
-        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 6')
-        >>> r.insert({'k2': {'const': 'v1'}}, 'Client 6')
-        >>> r.get_templates('Client 6')
-        [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
-        """
-        result = []
-        if client in self._clients:
-            result.append(MessageTemplate())
-        for key in self._children:
-            for schema_string in self._children[key]:
-                schema = json.loads(schema_string)
-                child = self._children[key][schema_string]
-                for template in child.get_templates(client):
-                    current = MessageTemplate({key: schema})
-                    current.update(template)
-                    result.append(current)
-        return result
-
-
-class BusException(Exception):
-    """Raise for errors in using message bus."""
-
-
-class MessageBus:
-    """Provide an asynchronous message bus.
-
-    The bus executes asynchronous callbacks for all messages to be received
-    by a client. We use a simple callback printing the message in all
-    examples:
-    >>> def callback_for_receiver(receiver):
-    ...     print(f"Creating callback for {receiver}.")
-    ...     async def callback(message):
-    ...         print(f"{receiver}: {message}")
-    ...     return callback
-
-    Clients can be registered at the bus with a name, lists of message
-    templates they want to use for sending and receiving and a callback
-    function for receiving. An empty list of templates means that the
-    client does not want to send or receive any messages, respectively.
-    A list with an empty template means that it wants to send arbitrary
-    or receive all messages, respectively:
-    >>> async def setup(bus):
-    ...     print("Setting up.")
-    ...     bus.register('Logger', 'Test Plugin',
-    ...                  [],
-    ...                  [{}],
-    ...                  callback_for_receiver('Logger'))
-    ...     bus.register('Client 1', 'Test Plugin',
-    ...                  [{'k1': {'type': 'string'}}],
-    ...                  [{'target': {'const': 'Client 1'}}],
-    ...                  callback_for_receiver('Client 1'))
-    ...     bus.register('Client 2', 'Test Plugin',
-    ...                  [{}],
-    ...                  [{'target': {'const': 'Client 2'}}],
-    ...                  callback_for_receiver('Client 2'))
-
-    The bus itself is addressed by the empty string. It sends messages for
-    each registration and deregestration of a client with a key 'event' and
-    a value of 'registered' or 'unregistered', a key 'client' with the
-    client's name as value and for registrations also keys 'sends' and
-    'receives' with all templates registered for the client for sending and
-    receiving.
-
-    Clients can send to the bus with the send function. Each message has to
-    declare a sender. The send templates of that sender are checked for a
-    template matching the message. We cannot prevent arbitrary code from
-    impersonating any sender, but this should only be done in debugging or
-    management situations.
-
-    Messages that are intended for a specific client by convention have a
-    key 'target' with the target client's name as value. Such messages are
-    often commands to the client to do something, which is by convention
-    indicated by a key 'command' with a value that indicates what should be
-    done.
-
-    The bus, for example, reacts to a message with 'target': '' and
-    'command': 'get clients' by sending one message for each currently
-    registered with complete information about its registered send and
-    receive templates.
-
-    >>> async def send(bus):
-    ...     print("Sending messages.")
-    ...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})
-    ...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
-    ...     await bus.send({'sender': '', 'target': '',
-    ...                     'command': 'get clients'})
-
-    The run function executes the message bus forever. If we want to stop
-    it, we have to explicitly cancel the task:
-    >>> async def main():
-    ...     bus = MessageBus()
-    ...     await setup(bus)
-    ...     bus_task = asyncio.create_task(bus.run())
-    ...     await send(bus)
-    ...     await asyncio.sleep(0)
-    ...     bus_task.cancel()
-    >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
-    Setting up.
-    Creating callback for Logger.
-    Creating callback for Client 1.
-    Creating callback for Client 2.
-    Sending messages.
-    Logger: {'sender': '', 'event': 'registered',
-             'client': 'Logger', 'plugin': 'Test Plugin',
-             'sends': [], 'receives': [{}]}
-    Logger: {'sender': '', 'event': 'registered',
-             'client': 'Client 1', 'plugin': 'Test Plugin',
-             'sends': [{'k1': {'type': 'string'}}],
-             'receives': [{'target': {'const': 'Client 1'}}]}
-    Logger: {'sender': '', 'event': 'registered',
-             'client': 'Client 2', 'plugin': 'Test Plugin',
-             'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
-    Logger: {'sender': 'Client 1', 'k1': 'Test'}
-    Logger: {'sender': 'Client 2', 'target': 'Client 1'}
-    Client 1: {'sender': 'Client 2', 'target': 'Client 1'}
-    Logger: {'sender': '', 'target': '', 'command': 'get clients'}
-    Logger: {'sender': '', 'client': 'Logger', 'plugin': 'Test Plugin',
-             'sends': [], 'receives': [{}]}
-    Logger: {'sender': '', 'client': 'Client 1', 'plugin': 'Test Plugin',
-             'sends': [{'k1': {'type': 'string'}}],
-             'receives': [{'target': {'const': 'Client 1'}}]}
-    Logger: {'sender': '', 'client': 'Client 2', 'plugin': 'Test Plugin',
-             'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
-    """
-
-    def __init__(self) -> None:
-        """Initialise a new bus without clients.
-
-        >>> async def main():
-        ...     bus = MessageBus()
-        >>> asyncio.run(main())
-        """
-        self._queue: asyncio.Queue = asyncio.Queue()
-        self._plugins: Dict[str, str] = {}
-        self._send_reg: MessageTemplateRegistry = MessageTemplateRegistry()
-        self._recv_reg: MessageTemplateRegistry = MessageTemplateRegistry()
-        self._callbacks: Dict[str, MessageCallback] = {}
-
-    def register(self, client: str, plugin: str,
-                 sends: Iterable[MessageTemplate],
-                 receives: Iterable[MessageTemplate],
-                 callback: MessageCallback) -> None:
-        """Register a client at the message bus.
-
-        >>> async def callback(message):
-        ...     print(message)
-        >>> async def main():
-        ...     bus = MessageBus()
-        ...     bus.register('Logger', 'Test Plugin',
-        ...                  [],    # send nothing
-        ...                  [{}],  # receive everything
-        ...                  callback)
-        ...     bus.register('Client 1', 'Test Plugin',
-        ...                  [{'k1': {'type': 'string'}}],
-        ...                      # send with key 'k1' and string value
-        ...                  [{'target': {'const': 'Client 1'}}],
-        ...                      # receive for this client
-        ...                  callback)
-        ...     bus.register('Client 2', 'Test Plugin',
-        ...                  [{}],  # send arbitrary
-        ...                  [{'target': {'const': 'Client 2'}}],
-        ...                      # receive for this client
-        ...                  callback)
-        >>> asyncio.run(main())
-        """
-        if not client:
-            raise BusException("Client name is not allowed to be empty.")
-        if client in self._plugins:
-            raise BusException(f"Client '{client}' already registered"
-                               " at message bus.")
-        event = Message('')
-        event['event'] = 'registered'
-        event['client'] = client
-        self._plugins[client] = plugin
-        event['plugin'] = plugin
-        for template in sends:
-            self._send_reg.insert(template, client)
-        event['sends'] = self._send_reg.get_templates(client)
-        for template in receives:
-            self._recv_reg.insert(template, client)
-        event['receives'] = self._recv_reg.get_templates(client)
-        self._callbacks[client] = callback
-        self._queue.put_nowait(event)
-
-    def unregister(self, client: str) -> None:
-        """Unregister a client from the message bus.
-
-        >>> async def callback(message):
-        ...     print(message)
-        >>> async def main():
-        ...     bus = MessageBus()
-        ...     bus.register('Client 1', 'Test Plugin',
-        ...                  [{'k1': {'type': 'string'}}],
-        ...                  [{'target': {'const': 'Client 1'}}],
-        ...                  callback)
-        ...     bus.unregister('Client 1')
-        >>> asyncio.run(main())
-        """
-        if client not in self._plugins:
-            return
-        event = Message('')
-        event['event'] = 'unregistered'
-        event['client'] = client
-        del self._plugins[client]
-        self._send_reg.delete(client)
-        self._recv_reg.delete(client)
-        if client in self._callbacks:
-            del self._callbacks[client]
-        self._queue.put_nowait(event)
-
-    async def run(self) -> None:
-        """Run the message bus forever.
-
-        >>> async def main():
-        ...     bus = MessageBus()
-        ...     bus_task = asyncio.create_task(bus.run())
-        ...     bus_task.cancel()
-        >>> asyncio.run(main())
-        """
-        while True:
-            message = await self._queue.get()
-            if ('target' in message and
-                    message['target'] == '' and
-                    'command' in message and
-                    message['command'] == 'get clients'):
-                for client in self._plugins:
-                    answer = Message('')
-                    answer['client'] = client
-                    answer['plugin'] = self._plugins[client]
-                    answer['sends'] = self._send_reg.get_templates(client)
-                    answer['receives'] = self._recv_reg.get_templates(client)
-                    await self._queue.put(answer)
-            for client in self._recv_reg.get(message):
-                await self._callbacks[client](message)
-            self._queue.task_done()
-
-    async def send(self, message: Message) -> None:
-        """Send a message to the message bus.
-
-        >>> async def callback(message):
-        ...     print(f"Got: {message}")
-        >>> async def main():
-        ...     bus = MessageBus()
-        ...     bus.register('Client 1', 'Test Plugin',
-        ...                  [{'k1': {'type': 'string'}}],
-        ...                  [{'target': {'const': 'Client 1'}}],
-        ...                  callback)
-        ...     bus.register('Client 2', 'Test Plugin',
-        ...                  [{}],
-        ...                  [{'target': {'const': 'Client 2'}}],
-        ...                  callback)
-        ...     bus_task = asyncio.create_task(bus.run())
-        ...     await bus.send({'sender': 'Client 1', 'target': 'Client 2',
-        ...                     'k1': 'Test'})
-        ...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
-        ...     try:
-        ...         await bus.send({'sender': 'Client 1', 'target': 'Client 2',
-        ...                         'k1': 42})
-        ...     except BusException as e:
-        ...         print(e)
-        ...     await asyncio.sleep(0)
-        ...     bus_task.cancel()
-        >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
-        Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
-        not allowed for sender 'Client 1'.
-        Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
-        Got: {'sender': 'Client 2', 'target': 'Client 1'}
-        """
-        assert isinstance(message['sender'], str)
-        sender = message['sender']
-        if sender:
-            if not self._send_reg.check(sender, message):
-                raise BusException(f"Message '{message}' not allowed for"
-                                   f" sender '{sender}'.")
-        await self._queue.put(message)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class Message -(sender: str, init: Dict[str, Union[NoneType, str, int, float, bool, Dict[str, Any], List[Any]]] = None) -
-
-

Define arbitrary message.

-

Messages are dictionaries with string keys and values that are strings, -integers, floats, Booleans, dictionaries that recursively have string -keys and values of any of these types, or lists with elements that have -any of these types. These constraints are checked when setting key-value -pairs of the message.

-

A message has to have a sender, which is set by the constructor:

-
>>> m = Message('Example sender')
->>> print(m)
-{'sender': 'Example sender'}
-
-

A dictionary can be given to the constructor:

-
>>> 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'}
-
-

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'}
-
-

Initialise message.

-

Message is initialised with given sender and possibly given -key-value pairs:

-
>>> m = Message('Example sender')
->>> print(m)
-{'sender': 'Example sender'}
->>> 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'}
-
-
- -Expand source code - -
class Message(Dict[str, MessageValue]):
-    """Define arbitrary message.
-
-    Messages are dictionaries with string keys and values that are strings,
-    integers, floats, Booleans, dictionaries that recursively have string
-    keys and values of any of these types, or lists with elements that have
-    any of these types. These constraints are checked when setting key-value
-    pairs of the message.
-
-    A message has to have a sender, which is set by the constructor:
-    >>> m = Message('Example sender')
-    >>> print(m)
-    {'sender': 'Example sender'}
-
-    A dictionary can be given to the constructor:
-    >>> 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'}
-
-    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'}
-    """
-
-    def __init__(self, sender: str,
-                 init: Dict[str, MessageValue] = None) -> None:
-        """Initialise message.
-
-        Message is initialised with given sender and possibly given
-        key-value pairs:
-        >>> m = Message('Example sender')
-        >>> print(m)
-        {'sender': 'Example sender'}
-        >>> 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
-        if init is not None:
-            self.update(init)
-
-    @staticmethod
-    def check_value(value: MessageValue) -> bool:
-        """Check recursively if a given value is valid.
-
-        None, strings, integers, floats and Booleans are valid:
-        >>> Message.check_value(None)
-        True
-        >>> Message.check_value('Spam')
-        True
-        >>> Message.check_value(42)
-        True
-        >>> Message.check_value(42.42)
-        True
-        >>> Message.check_value(False)
-        True
-
-        Other basic types are not valid:
-        >>> Message.check_value(b'bytes')
-        False
-        >>> Message.check_value(1j)
-        False
-
-        Dictionaries with string keys and recursively valid values are valid:
-        >>> Message.check_value({'str value': 'Spam', 'int value': 42,
-        ...                      'float value': 42.42, 'bool value': False})
-        True
-
-        Empty dictionaries are valid:
-        >>> Message.check_value({})
-        True
-
-        Dictionaries with other keys are not valid:
-        >>> Message.check_value({42: 'int key'})
-        False
-
-        Dictionaries with invalid values are not valid:
-        >>> Message.check_value({'complex value': 1j})
-        False
-
-        Lists with valid elements are valid:
-        >>> Message.check_value(['Spam', 42, 42.42, False])
-        True
-
-        Empty lists are valid:
-        >>> Message.check_value([])
-        True
-
-        Lists with invalid elements are not valid:
-        >>> Message.check_value([1j])
-        False
-        """
-        if value is None:
-            return True
-        elif (isinstance(value, str) or isinstance(value, int) or
-                isinstance(value, float) or isinstance(value, bool)):
-            return True
-        elif isinstance(value, dict):
-            for key in value:
-                if not isinstance(key, str):
-                    return False
-                if not Message.check_value(value[key]):
-                    return False
-            return True
-        elif isinstance(value, list):
-            for element in value:
-                if not Message.check_value(element):
-                    return False
-            return True
-        return False
-
-    def __setitem__(self, key: str, value: MessageValue) -> None:
-        """Check key and value before putting pair into dict.
-
-        >>> m = Message('Example sender')
-        >>> m['key'] = 'value'
-        >>> m['dict'] = {'k1': 'v1', 'k2': 2}
-        >>> print(m)  # doctest: +NORMALIZE_WHITESPACE
-        {'sender': 'Example sender', 'key': 'value',
-         'dict': {'k1': 'v1', 'k2': 2}}
-        >>> m = Message('Example sender')
-        >>> m[42] = 'int key'
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in Message (not a string).
-        >>> m['complex value'] = 1j
-        Traceback (most recent call last):
-          ...
-        TypeError: '1j' is not a valid value in Message.
-        """
-        if not isinstance(key, str):
-            raise TypeError(f"'{key}' is not a valid key in Message"
-                            " (not a string).")
-        if not self.check_value(value):
-            raise TypeError(f"'{value}' is not a valid value in Message.")
-        super().__setitem__(key, value)
-
-    def update(self, *args, **kwargs) -> None:
-        """Override update to use validity checks.
-
-        >>> m = Message('Example sender')
-        >>> m.update({'key 1': 'value 1', 'key 2': 'value 2'})
-        >>> print(m)
-        {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
-        >>> m.update({42: 'int key'})
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in Message (not a string).
-        >>> m.update({'complex value': 1j})
-        Traceback (most recent call last):
-          ...
-        TypeError: '1j' is not a valid value in Message.
-
-        This is also used in __init__:
-        >>> m = Message('Example sender', {'key': 'value'})
-        >>> print(m)
-        {'sender': 'Example sender', 'key': 'value'}
-        >>> m = Message('Example sender', {42: 'int key'})
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in Message (not a string).
-        >>> m = Message('Example sender', {'complex value': 1j})
-        Traceback (most recent call last):
-          ...
-        TypeError: '1j' is not a valid value in Message.
-        """
-        if args:
-            if len(args) > 1:
-                raise TypeError("update expected at most 1 argument,"
-                                f" got {len(args)}")
-            other = dict(args[0])
-            for key in other:
-                self[key] = other[key]
-        for key in kwargs:
-            self[key] = kwargs[key]
-
-    def setdefault(self, key: str, value: MessageValue = None) -> MessageValue:
-        """Override setdefault to use validity checks.
-
-        >>> m = Message('Example sender')
-        >>> m.setdefault('key', 'value 1')
-        'value 1'
-        >>> m.setdefault('key', 'value 2')
-        'value 1'
-        >>> m.setdefault(42, 'int key')
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in Message (not a string).
-        >>> m.setdefault('complex value', 1j)
-        Traceback (most recent call last):
-          ...
-        TypeError: '1j' is not a valid value in Message.
-
-        But __setitem__ is not called if the key is already present:
-        >>> m.setdefault('key', 1j)
-        'value 1'
-        """
-        if key not in self:
-            self[key] = value
-        return self[key]
-
-

Ancestors

-
    -
  • builtins.dict
  • -
  • typing.Generic
  • -
-

Static methods

-
-
-def check_value(value: Union[NoneType, str, int, float, bool, Dict[str, Any], List[Any]]) ‑> bool -
-
-

Check recursively if a given value is valid.

-

None, strings, integers, floats and Booleans are valid:

-
>>> Message.check_value(None)
-True
->>> Message.check_value('Spam')
-True
->>> Message.check_value(42)
-True
->>> Message.check_value(42.42)
-True
->>> Message.check_value(False)
-True
-
-

Other basic types are not valid:

-
>>> Message.check_value(b'bytes')
-False
->>> Message.check_value(1j)
-False
-
-

Dictionaries with string keys and recursively valid values are valid:

-
>>> Message.check_value({'str value': 'Spam', 'int value': 42,
-...                      'float value': 42.42, 'bool value': False})
-True
-
-

Empty dictionaries are valid:

-
>>> Message.check_value({})
-True
-
-

Dictionaries with other keys are not valid:

-
>>> Message.check_value({42: 'int key'})
-False
-
-

Dictionaries with invalid values are not valid:

-
>>> Message.check_value({'complex value': 1j})
-False
-
-

Lists with valid elements are valid:

-
>>> Message.check_value(['Spam', 42, 42.42, False])
-True
-
-

Empty lists are valid:

-
>>> Message.check_value([])
-True
-
-

Lists with invalid elements are not valid:

-
>>> Message.check_value([1j])
-False
-
-
- -Expand source code - -
@staticmethod
-def check_value(value: MessageValue) -> bool:
-    """Check recursively if a given value is valid.
-
-    None, strings, integers, floats and Booleans are valid:
-    >>> Message.check_value(None)
-    True
-    >>> Message.check_value('Spam')
-    True
-    >>> Message.check_value(42)
-    True
-    >>> Message.check_value(42.42)
-    True
-    >>> Message.check_value(False)
-    True
-
-    Other basic types are not valid:
-    >>> Message.check_value(b'bytes')
-    False
-    >>> Message.check_value(1j)
-    False
-
-    Dictionaries with string keys and recursively valid values are valid:
-    >>> Message.check_value({'str value': 'Spam', 'int value': 42,
-    ...                      'float value': 42.42, 'bool value': False})
-    True
-
-    Empty dictionaries are valid:
-    >>> Message.check_value({})
-    True
-
-    Dictionaries with other keys are not valid:
-    >>> Message.check_value({42: 'int key'})
-    False
-
-    Dictionaries with invalid values are not valid:
-    >>> Message.check_value({'complex value': 1j})
-    False
-
-    Lists with valid elements are valid:
-    >>> Message.check_value(['Spam', 42, 42.42, False])
-    True
-
-    Empty lists are valid:
-    >>> Message.check_value([])
-    True
-
-    Lists with invalid elements are not valid:
-    >>> Message.check_value([1j])
-    False
-    """
-    if value is None:
-        return True
-    elif (isinstance(value, str) or isinstance(value, int) or
-            isinstance(value, float) or isinstance(value, bool)):
-        return True
-    elif isinstance(value, dict):
-        for key in value:
-            if not isinstance(key, str):
-                return False
-            if not Message.check_value(value[key]):
-                return False
-        return True
-    elif isinstance(value, list):
-        for element in value:
-            if not Message.check_value(element):
-                return False
-        return True
-    return False
-
-
-
-

Methods

-
-
-def update(self, *args, **kwargs) ‑> NoneType -
-
-

Override update to use validity checks.

-
>>> m = Message('Example sender')
->>> m.update({'key 1': 'value 1', 'key 2': 'value 2'})
->>> print(m)
-{'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
->>> m.update({42: 'int key'})
-Traceback (most recent call last):
-  ...
-TypeError: '42' is not a valid key in Message (not a string).
->>> m.update({'complex value': 1j})
-Traceback (most recent call last):
-  ...
-TypeError: '1j' is not a valid value in Message.
-
-

This is also used in init:

-
>>> m = Message('Example sender', {'key': 'value'})
->>> print(m)
-{'sender': 'Example sender', 'key': 'value'}
->>> m = Message('Example sender', {42: 'int key'})
-Traceback (most recent call last):
-  ...
-TypeError: '42' is not a valid key in Message (not a string).
->>> m = Message('Example sender', {'complex value': 1j})
-Traceback (most recent call last):
-  ...
-TypeError: '1j' is not a valid value in Message.
-
-
- -Expand source code - -
def update(self, *args, **kwargs) -> None:
-    """Override update to use validity checks.
-
-    >>> m = Message('Example sender')
-    >>> m.update({'key 1': 'value 1', 'key 2': 'value 2'})
-    >>> print(m)
-    {'sender': 'Example sender', 'key 1': 'value 1', 'key 2': 'value 2'}
-    >>> m.update({42: 'int key'})
-    Traceback (most recent call last):
-      ...
-    TypeError: '42' is not a valid key in Message (not a string).
-    >>> m.update({'complex value': 1j})
-    Traceback (most recent call last):
-      ...
-    TypeError: '1j' is not a valid value in Message.
-
-    This is also used in __init__:
-    >>> m = Message('Example sender', {'key': 'value'})
-    >>> print(m)
-    {'sender': 'Example sender', 'key': 'value'}
-    >>> m = Message('Example sender', {42: 'int key'})
-    Traceback (most recent call last):
-      ...
-    TypeError: '42' is not a valid key in Message (not a string).
-    >>> m = Message('Example sender', {'complex value': 1j})
-    Traceback (most recent call last):
-      ...
-    TypeError: '1j' is not a valid value in Message.
-    """
-    if args:
-        if len(args) > 1:
-            raise TypeError("update expected at most 1 argument,"
-                            f" got {len(args)}")
-        other = dict(args[0])
-        for key in other:
-            self[key] = other[key]
-    for key in kwargs:
-        self[key] = kwargs[key]
-
-
-
-def setdefault(self, key: str, value: Union[NoneType, str, int, float, bool, Dict[str, Any], List[Any]] = None) ‑> Union[NoneType, str, int, float, bool, Dict[str, Any], List[Any]] -
-
-

Override setdefault to use validity checks.

-
>>> m = Message('Example sender')
->>> m.setdefault('key', 'value 1')
-'value 1'
->>> m.setdefault('key', 'value 2')
-'value 1'
->>> m.setdefault(42, 'int key')
-Traceback (most recent call last):
-  ...
-TypeError: '42' is not a valid key in Message (not a string).
->>> m.setdefault('complex value', 1j)
-Traceback (most recent call last):
-  ...
-TypeError: '1j' is not a valid value in Message.
-
-

But setitem is not called if the key is already present:

-
>>> m.setdefault('key', 1j)
-'value 1'
-
-
- -Expand source code - -
def setdefault(self, key: str, value: MessageValue = None) -> MessageValue:
-    """Override setdefault to use validity checks.
-
-    >>> m = Message('Example sender')
-    >>> m.setdefault('key', 'value 1')
-    'value 1'
-    >>> m.setdefault('key', 'value 2')
-    'value 1'
-    >>> m.setdefault(42, 'int key')
-    Traceback (most recent call last):
-      ...
-    TypeError: '42' is not a valid key in Message (not a string).
-    >>> m.setdefault('complex value', 1j)
-    Traceback (most recent call last):
-      ...
-    TypeError: '1j' is not a valid value in Message.
-
-    But __setitem__ is not called if the key is already present:
-    >>> m.setdefault('key', 1j)
-    'value 1'
-    """
-    if key not in self:
-        self[key] = value
-    return self[key]
-
-
-
-
-
-class MessageTemplate -(init: Dict[str, Union[bool, Dict[str, Union[NoneType, str, int, float, bool, Dict[str, Any], List[Any]]]]] = None) -
-
-

Define a message template.

-

A message template is a mapping from string keys to JSON schemas as -values:

-
>>> t = MessageTemplate({'key 1': {'const': 'value'},
-...                      'key 2': {'type': 'string'}})
->>> t['key 3'] = {'type': 'object',
-...               'properties': {'key 1': {'type': 'number'},
-...                              'key 2': True}}
-
-

A message template matches a message if all keys of the template are -contained in the message and the values in the message validate against -the respective schemas:

-
>>> t.check(Message('Example Sender',
-...                 {'key 1': 'value', 'key 2': 'some string',
-...                  'key 3': {'key 1': 42, 'key 2': None}}))
-True
-
-

An empty mapping therefore matches all messages:

-
>>> t = MessageTemplate()
->>> t.check(Message('Example Sender', {'arbitrary': 'content'}))
-True
-
-

Initialise message.

-

Template is initialised empty or with given key-value pairs:

-
>>> t = MessageTemplate()
->>> print(t)
-{}
->>> t = MessageTemplate({'key': {'const': 'value'}})
->>> print(t)
-{'key': {'const': 'value'}}
-
-
- -Expand source code - -
class MessageTemplate(Dict[str, JSONSchema]):
-    """Define a message template.
-
-    A message template is a mapping from string keys to JSON schemas as
-    values:
-    >>> t = MessageTemplate({'key 1': {'const': 'value'},
-    ...                      'key 2': {'type': 'string'}})
-    >>> t['key 3'] = {'type': 'object',
-    ...               'properties': {'key 1': {'type': 'number'},
-    ...                              'key 2': True}}
-
-    A message template matches a message if all keys of the template are
-    contained in the message and the values in the message validate against
-    the respective schemas:
-    >>> t.check(Message('Example Sender',
-    ...                 {'key 1': 'value', 'key 2': 'some string',
-    ...                  'key 3': {'key 1': 42, 'key 2': None}}))
-    True
-
-    An empty mapping therefore matches all messages:
-    >>> t = MessageTemplate()
-    >>> t.check(Message('Example Sender', {'arbitrary': 'content'}))
-    True
-    """
-
-    def __init__(self, init: Dict[str, JSONSchema] = None) -> None:
-        """Initialise message.
-
-        Template is initialised empty or with given key-value pairs:
-        >>> t = MessageTemplate()
-        >>> print(t)
-        {}
-        >>> t = MessageTemplate({'key': {'const': 'value'}})
-        >>> print(t)
-        {'key': {'const': 'value'}}
-        """
-        if init is not None:
-            self.update(init)
-
-    @staticmethod
-    def from_message(message: Message) -> 'MessageTemplate':
-        """Create template from message.
-
-        Template witch constant schemas is created from message:
-        >>> m = Message('Example Sender', {'key': 'value'})
-        >>> t = MessageTemplate.from_message(m)
-        >>> print(t)
-        {'sender': {'const': 'Example Sender'}, 'key': {'const': 'value'}}
-        >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
-        ...                                'list': [None, True, 'string']})
-        >>> t = MessageTemplate.from_message(m)
-        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-        {'sender': {'const': 'Example Sender'},
-         'dict': {'type': 'object',
-                  'properties': {'int': {'const': 42},
-                                 'float': {'const': 42.42}}},
-         'list': {'type': 'array',
-                  'items': [{'const': None},
-                            {'const': True},
-                            {'const': 'string'}]}}
-
-        This is especially useful for clients that send certain fully
-        predefined messages, where the message is given in the configuration
-        and the template for the registration can be constructed by this
-        method.
-        """
-        def schema_from_value(value: MessageValue) -> JSONSchema:
-            schema: JSONSchema = False
-            if value is None:
-                schema = {'const': None}
-            elif (isinstance(value, str) or isinstance(value, int) or
-                    isinstance(value, float) or isinstance(value, bool)):
-                schema = {'const': value}
-            elif isinstance(value, dict):
-                properties = {}
-                for inner_key in value:
-                    inner_value: Message = value[inner_key]
-                    properties[inner_key] = schema_from_value(inner_value)
-                schema = {'type': 'object',
-                          'properties': properties}
-            elif isinstance(value, list):
-                schema = {'type': 'array',
-                          'items': [schema_from_value(element)
-                                    for element in value]}
-            return schema
-        template = MessageTemplate()
-        for key in message:
-            template[key] = schema_from_value(message[key])
-        return template
-
-    def __setitem__(self, key: str, value: JSONSchema) -> None:
-        """Check key and value before putting pair into dict.
-
-        >>> t = MessageTemplate()
-        >>> t['key 1'] = {'const': 'value'}
-        >>> t['key 2'] = {'type': 'string'}
-        >>> t['key 3'] = {'type': 'object',
-        ...               'properties': {'key 1': {'type': 'number'},
-        ...                              'key 2': True}}
-        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
-         'key 3': {'type': 'object',
-                   'properties': {'key 1': {'type': 'number'},
-                                  'key 2': True}}}
-        >>> t[42] = {'const': 'int key'}
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in MessageTemplate (not a string).
-        >>> t['key'] = 'schema'  # doctest: +NORMALIZE_WHITESPACE
-        Traceback (most recent call last):
-          ...
-        TypeError: 'schema' is not a valid value in MessageTemplate
-        (not a valid JSON schema).
-        """
-        if not isinstance(key, str):
-            raise TypeError(f"'{key}' is not a valid key in MessageTemplate"
-                            " (not a string).")
-        try:
-            jsonschema.Draft7Validator.check_schema(value)
-            # Draft7Validator is hardcoded, because _LATEST_VERSION is
-            # non-public in jsonschema and we also perhaps do not want to
-            # upgrade automatically.
-        except jsonschema.exceptions.SchemaError:
-            raise TypeError(f"'{value}' is not a valid value in"
-                            " MessageTemplate (not a valid JSON schema).")
-        super().__setitem__(key, value)
-
-    def update(self, *args, **kwargs) -> None:
-        """Override update to use validity checks.
-
-        >>> t = MessageTemplate()
-        >>> t.update({'key 1': {'const': 'value'},
-        ...           'key 2': {'type': 'string'},
-        ...           'key 3': {'type': 'object',
-        ...                     'properties': {'key 1': {'type': 'number'},
-        ...                                    'key 2': True}}})
-        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
-         'key 3': {'type': 'object',
-                   'properties': {'key 1': {'type': 'number'},
-                                  'key 2': True}}}
-        >>> t.update({42: {'const': 'int key'}})
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in MessageTemplate (not a string).
-        >>> t.update({'key': 'schema'})  # doctest: +NORMALIZE_WHITESPACE
-        Traceback (most recent call last):
-          ...
-        TypeError: 'schema' is not a valid value in MessageTemplate
-        (not a valid JSON schema).
-
-        This is also used in __init__:
-        >>> t = MessageTemplate({'key 1': {'const': 'value'},
-        ...                      'key 2': {'type': 'string'},
-        ...                      'key 3': {'type': 'object',
-        ...                                'properties': {
-        ...                                    'key 1': {'type': 'number'},
-        ...                                    'key 2': True}}})
-        >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-        {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
-         'key 3': {'type': 'object',
-                   'properties': {'key 1': {'type': 'number'},
-                                  'key 2': True}}}
-        >>> t = MessageTemplate({42: {'const': 'int key'}})
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in MessageTemplate (not a string).
-        >>> t = MessageTemplate({'key': 'schema'})
-        ... # doctest: +NORMALIZE_WHITESPACE
-        Traceback (most recent call last):
-          ...
-        TypeError: 'schema' is not a valid value in MessageTemplate
-        (not a valid JSON schema).
-        """
-        if args:
-            if len(args) > 1:
-                raise TypeError("update expected at most 1 argument,"
-                                f" got {len(args)}")
-            other = dict(args[0])
-            for key in other:
-                self[key] = other[key]
-        for key in kwargs:
-            self[key] = kwargs[key]
-
-    def setdefault(self, key: str, value: JSONSchema = None) -> JSONSchema:
-        """Override setdefault to use validity checks.
-
-        >>> t = MessageTemplate()
-        >>> t.setdefault('key 1', {'const': 'value'})
-        {'const': 'value'}
-        >>> t.setdefault('key 2', {'type': 'string'})
-        {'type': 'string'}
-        >>> t.setdefault('key 3', {'type': 'object',
-        ...                        'properties': {'key 1': {'type': 'number'},
-        ...                                       'key 2': True}})
-        ... # doctest: +NORMALIZE_WHITESPACE
-        {'type': 'object',
-                   'properties': {'key 1': {'type': 'number'},
-                                  'key 2': True}}
-        >>> t.setdefault(42, {'const': 'int key'})
-        Traceback (most recent call last):
-          ...
-        TypeError: '42' is not a valid key in MessageTemplate (not a string).
-        >>> t.setdefault('key', 'schema')  # doctest: +NORMALIZE_WHITESPACE
-        Traceback (most recent call last):
-          ...
-        TypeError: 'schema' is not a valid value in MessageTemplate
-        (not a valid JSON schema).
-
-        But __setitem__ is not called if the key is already present:
-        >>> t.setdefault('key 1', 'schema')
-        {'const': 'value'}
-        """
-        if key not in self:
-            if value is not None:
-                self[key] = value
-            else:
-                self[key] = True
-        return self[key]
-
-    def check(self, message: Message) -> bool:
-        """Check message against this template.
-
-        Constant values have to match exactly:
-        >>> t = MessageTemplate({'key': {'const': 'value'}})
-        >>> t.check(Message('Example Sender', {'key': 'value'}))
-        True
-        >>> t.check(Message('Example Sender', {'key': 'other value'}))
-        False
-
-        But for integers, floats with the same value are also valid:
-        >>> t = MessageTemplate({'key': {'const': 42}})
-        >>> t.check(Message('Example Sender', {'key': 42}))
-        True
-        >>> t.check(Message('Example Sender', {'key': 42.0}))
-        True
-
-        Type integer is valid for floats with zero fractional part, but
-        not by floats with non-zero fractional part:
-        >>> t = MessageTemplate({'key': {'type': 'integer'}})
-        >>> t.check(Message('Example Sender', {'key': 42}))
-        True
-        >>> t.check(Message('Example Sender', {'key': 42.0}))
-        True
-        >>> t.check(Message('Example Sender', {'key': 42.42}))
-        False
-
-        Type number is valid for arbitrary ints or floats:
-        >>> t = MessageTemplate({'key': {'type': 'number'}})
-        >>> t.check(Message('Example Sender', {'key': 42}))
-        True
-        >>> t.check(Message('Example Sender', {'key': 42.42}))
-        True
-
-        All keys in template have to be present in message:
-        >>> t = MessageTemplate({'key 1': {'const': 'value'},
-        ...                      'key 2': {'type': 'string'},
-        ...                      'key 3': {'type': 'object',
-        ...                                'properties': {
-        ...                                    'key 1': {'type': 'number'},
-        ...                                    'key 2': True,
-        ...                                    'key 3': False}}})
-        >>> t.check(Message('Example Sender',
-        ...                 {'key 1': 'value', 'key 2': 'some string'}))
-        False
-
-        But for nested objects their properties do not necessarily have
-        to be present:
-        >>> t.check(Message('Example Sender',
-        ...                 {'key 1': 'value', 'key 2': 'some string',
-        ...                  'key 3': {'key 1': 42}}))
-        True
-
-        Schema True matches everything (even None):
-        >>> t.check(Message('Example Sender',
-        ...                 {'key 1': 'value', 'key 2': 'some string',
-        ...                  'key 3': {'key 2': None}}))
-        True
-
-        Schema False matches nothing:
-        >>> t.check(Message('Example Sender',
-        ...                 {'key 1': 'value', 'key 2': 'some string',
-        ...                  'key 3': {'key 3': True}}))
-        False
-
-        Message is valid for the constant template created from it:
-        >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
-        ...                                'list': [None, True, 'string']})
-        >>> t = MessageTemplate.from_message(m)
-        >>> t.check(m)
-        True
-        """
-        for key in self:
-            if key not in message:
-                return False
-            else:
-                validator = jsonschema.Draft7Validator(self[key])
-                for error in validator.iter_errors(message[key]):
-                    return False
-        return True
-
-

Ancestors

-
    -
  • builtins.dict
  • -
  • typing.Generic
  • -
-

Static methods

-
-
-def from_message(message: Message) ‑> MessageTemplate -
-
-

Create template from message.

-

Template witch constant schemas is created from message:

-
>>> m = Message('Example Sender', {'key': 'value'})
->>> t = MessageTemplate.from_message(m)
->>> print(t)
-{'sender': {'const': 'Example Sender'}, 'key': {'const': 'value'}}
->>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
-...                                'list': [None, True, 'string']})
->>> t = MessageTemplate.from_message(m)
->>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-{'sender': {'const': 'Example Sender'},
- 'dict': {'type': 'object',
-          'properties': {'int': {'const': 42},
-                         'float': {'const': 42.42}}},
- 'list': {'type': 'array',
-          'items': [{'const': None},
-                    {'const': True},
-                    {'const': 'string'}]}}
-
-

This is especially useful for clients that send certain fully -predefined messages, where the message is given in the configuration -and the template for the registration can be constructed by this -method.

-
- -Expand source code - -
@staticmethod
-def from_message(message: Message) -> 'MessageTemplate':
-    """Create template from message.
-
-    Template witch constant schemas is created from message:
-    >>> m = Message('Example Sender', {'key': 'value'})
-    >>> t = MessageTemplate.from_message(m)
-    >>> print(t)
-    {'sender': {'const': 'Example Sender'}, 'key': {'const': 'value'}}
-    >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
-    ...                                'list': [None, True, 'string']})
-    >>> t = MessageTemplate.from_message(m)
-    >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-    {'sender': {'const': 'Example Sender'},
-     'dict': {'type': 'object',
-              'properties': {'int': {'const': 42},
-                             'float': {'const': 42.42}}},
-     'list': {'type': 'array',
-              'items': [{'const': None},
-                        {'const': True},
-                        {'const': 'string'}]}}
-
-    This is especially useful for clients that send certain fully
-    predefined messages, where the message is given in the configuration
-    and the template for the registration can be constructed by this
-    method.
-    """
-    def schema_from_value(value: MessageValue) -> JSONSchema:
-        schema: JSONSchema = False
-        if value is None:
-            schema = {'const': None}
-        elif (isinstance(value, str) or isinstance(value, int) or
-                isinstance(value, float) or isinstance(value, bool)):
-            schema = {'const': value}
-        elif isinstance(value, dict):
-            properties = {}
-            for inner_key in value:
-                inner_value: Message = value[inner_key]
-                properties[inner_key] = schema_from_value(inner_value)
-            schema = {'type': 'object',
-                      'properties': properties}
-        elif isinstance(value, list):
-            schema = {'type': 'array',
-                      'items': [schema_from_value(element)
-                                for element in value]}
-        return schema
-    template = MessageTemplate()
-    for key in message:
-        template[key] = schema_from_value(message[key])
-    return template
-
-
-
-

Methods

-
-
-def update(self, *args, **kwargs) ‑> NoneType -
-
-

Override update to use validity checks.

-
>>> t = MessageTemplate()
->>> t.update({'key 1': {'const': 'value'},
-...           'key 2': {'type': 'string'},
-...           'key 3': {'type': 'object',
-...                     'properties': {'key 1': {'type': 'number'},
-...                                    'key 2': True}}})
->>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-{'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
- 'key 3': {'type': 'object',
-           'properties': {'key 1': {'type': 'number'},
-                          'key 2': True}}}
->>> t.update({42: {'const': 'int key'}})
-Traceback (most recent call last):
-  ...
-TypeError: '42' is not a valid key in MessageTemplate (not a string).
->>> t.update({'key': 'schema'})  # doctest: +NORMALIZE_WHITESPACE
-Traceback (most recent call last):
-  ...
-TypeError: 'schema' is not a valid value in MessageTemplate
-(not a valid JSON schema).
-
-

This is also used in init:

-
>>> t = MessageTemplate({'key 1': {'const': 'value'},
-...                      'key 2': {'type': 'string'},
-...                      'key 3': {'type': 'object',
-...                                'properties': {
-...                                    'key 1': {'type': 'number'},
-...                                    'key 2': True}}})
->>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-{'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
- 'key 3': {'type': 'object',
-           'properties': {'key 1': {'type': 'number'},
-                          'key 2': True}}}
->>> t = MessageTemplate({42: {'const': 'int key'}})
-Traceback (most recent call last):
-  ...
-TypeError: '42' is not a valid key in MessageTemplate (not a string).
->>> t = MessageTemplate({'key': 'schema'})
-... # doctest: +NORMALIZE_WHITESPACE
-Traceback (most recent call last):
-  ...
-TypeError: 'schema' is not a valid value in MessageTemplate
-(not a valid JSON schema).
-
-
- -Expand source code - -
def update(self, *args, **kwargs) -> None:
-    """Override update to use validity checks.
-
-    >>> t = MessageTemplate()
-    >>> t.update({'key 1': {'const': 'value'},
-    ...           'key 2': {'type': 'string'},
-    ...           'key 3': {'type': 'object',
-    ...                     'properties': {'key 1': {'type': 'number'},
-    ...                                    'key 2': True}}})
-    >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-    {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
-     'key 3': {'type': 'object',
-               'properties': {'key 1': {'type': 'number'},
-                              'key 2': True}}}
-    >>> t.update({42: {'const': 'int key'}})
-    Traceback (most recent call last):
-      ...
-    TypeError: '42' is not a valid key in MessageTemplate (not a string).
-    >>> t.update({'key': 'schema'})  # doctest: +NORMALIZE_WHITESPACE
-    Traceback (most recent call last):
-      ...
-    TypeError: 'schema' is not a valid value in MessageTemplate
-    (not a valid JSON schema).
-
-    This is also used in __init__:
-    >>> t = MessageTemplate({'key 1': {'const': 'value'},
-    ...                      'key 2': {'type': 'string'},
-    ...                      'key 3': {'type': 'object',
-    ...                                'properties': {
-    ...                                    'key 1': {'type': 'number'},
-    ...                                    'key 2': True}}})
-    >>> print(t)  # doctest: +NORMALIZE_WHITESPACE
-    {'key 1': {'const': 'value'}, 'key 2': {'type': 'string'},
-     'key 3': {'type': 'object',
-               'properties': {'key 1': {'type': 'number'},
-                              'key 2': True}}}
-    >>> t = MessageTemplate({42: {'const': 'int key'}})
-    Traceback (most recent call last):
-      ...
-    TypeError: '42' is not a valid key in MessageTemplate (not a string).
-    >>> t = MessageTemplate({'key': 'schema'})
-    ... # doctest: +NORMALIZE_WHITESPACE
-    Traceback (most recent call last):
-      ...
-    TypeError: 'schema' is not a valid value in MessageTemplate
-    (not a valid JSON schema).
-    """
-    if args:
-        if len(args) > 1:
-            raise TypeError("update expected at most 1 argument,"
-                            f" got {len(args)}")
-        other = dict(args[0])
-        for key in other:
-            self[key] = other[key]
-    for key in kwargs:
-        self[key] = kwargs[key]
-
-
-
-def setdefault(self, key: str, value: Union[bool, Dict[str, Union[NoneType, str, int, float, bool, Dict[str, Any], List[Any]]]] = None) ‑> Union[bool, Dict[str, Union[NoneType, str, int, float, bool, Dict[str, Any], List[Any]]]] -
-
-

Override setdefault to use validity checks.

-
>>> t = MessageTemplate()
->>> t.setdefault('key 1', {'const': 'value'})
-{'const': 'value'}
->>> t.setdefault('key 2', {'type': 'string'})
-{'type': 'string'}
->>> t.setdefault('key 3', {'type': 'object',
-...                        'properties': {'key 1': {'type': 'number'},
-...                                       'key 2': True}})
-... # doctest: +NORMALIZE_WHITESPACE
-{'type': 'object',
-           'properties': {'key 1': {'type': 'number'},
-                          'key 2': True}}
->>> t.setdefault(42, {'const': 'int key'})
-Traceback (most recent call last):
-  ...
-TypeError: '42' is not a valid key in MessageTemplate (not a string).
->>> t.setdefault('key', 'schema')  # doctest: +NORMALIZE_WHITESPACE
-Traceback (most recent call last):
-  ...
-TypeError: 'schema' is not a valid value in MessageTemplate
-(not a valid JSON schema).
-
-

But setitem is not called if the key is already present:

-
>>> t.setdefault('key 1', 'schema')
-{'const': 'value'}
-
-
- -Expand source code - -
def setdefault(self, key: str, value: JSONSchema = None) -> JSONSchema:
-    """Override setdefault to use validity checks.
-
-    >>> t = MessageTemplate()
-    >>> t.setdefault('key 1', {'const': 'value'})
-    {'const': 'value'}
-    >>> t.setdefault('key 2', {'type': 'string'})
-    {'type': 'string'}
-    >>> t.setdefault('key 3', {'type': 'object',
-    ...                        'properties': {'key 1': {'type': 'number'},
-    ...                                       'key 2': True}})
-    ... # doctest: +NORMALIZE_WHITESPACE
-    {'type': 'object',
-               'properties': {'key 1': {'type': 'number'},
-                              'key 2': True}}
-    >>> t.setdefault(42, {'const': 'int key'})
-    Traceback (most recent call last):
-      ...
-    TypeError: '42' is not a valid key in MessageTemplate (not a string).
-    >>> t.setdefault('key', 'schema')  # doctest: +NORMALIZE_WHITESPACE
-    Traceback (most recent call last):
-      ...
-    TypeError: 'schema' is not a valid value in MessageTemplate
-    (not a valid JSON schema).
-
-    But __setitem__ is not called if the key is already present:
-    >>> t.setdefault('key 1', 'schema')
-    {'const': 'value'}
-    """
-    if key not in self:
-        if value is not None:
-            self[key] = value
-        else:
-            self[key] = True
-    return self[key]
-
-
-
-def check(self, message: Message) ‑> bool -
-
-

Check message against this template.

-

Constant values have to match exactly:

-
>>> t = MessageTemplate({'key': {'const': 'value'}})
->>> t.check(Message('Example Sender', {'key': 'value'}))
-True
->>> t.check(Message('Example Sender', {'key': 'other value'}))
-False
-
-

But for integers, floats with the same value are also valid:

-
>>> t = MessageTemplate({'key': {'const': 42}})
->>> t.check(Message('Example Sender', {'key': 42}))
-True
->>> t.check(Message('Example Sender', {'key': 42.0}))
-True
-
-

Type integer is valid for floats with zero fractional part, but -not by floats with non-zero fractional part:

-
>>> t = MessageTemplate({'key': {'type': 'integer'}})
->>> t.check(Message('Example Sender', {'key': 42}))
-True
->>> t.check(Message('Example Sender', {'key': 42.0}))
-True
->>> t.check(Message('Example Sender', {'key': 42.42}))
-False
-
-

Type number is valid for arbitrary ints or floats:

-
>>> t = MessageTemplate({'key': {'type': 'number'}})
->>> t.check(Message('Example Sender', {'key': 42}))
-True
->>> t.check(Message('Example Sender', {'key': 42.42}))
-True
-
-

All keys in template have to be present in message:

-
>>> t = MessageTemplate({'key 1': {'const': 'value'},
-...                      'key 2': {'type': 'string'},
-...                      'key 3': {'type': 'object',
-...                                'properties': {
-...                                    'key 1': {'type': 'number'},
-...                                    'key 2': True,
-...                                    'key 3': False}}})
->>> t.check(Message('Example Sender',
-...                 {'key 1': 'value', 'key 2': 'some string'}))
-False
-
-

But for nested objects their properties do not necessarily have -to be present:

-
>>> t.check(Message('Example Sender',
-...                 {'key 1': 'value', 'key 2': 'some string',
-...                  'key 3': {'key 1': 42}}))
-True
-
-

Schema True matches everything (even None):

-
>>> t.check(Message('Example Sender',
-...                 {'key 1': 'value', 'key 2': 'some string',
-...                  'key 3': {'key 2': None}}))
-True
-
-

Schema False matches nothing:

-
>>> t.check(Message('Example Sender',
-...                 {'key 1': 'value', 'key 2': 'some string',
-...                  'key 3': {'key 3': True}}))
-False
-
-

Message is valid for the constant template created from it:

-
>>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
-...                                'list': [None, True, 'string']})
->>> t = MessageTemplate.from_message(m)
->>> t.check(m)
-True
-
-
- -Expand source code - -
def check(self, message: Message) -> bool:
-    """Check message against this template.
-
-    Constant values have to match exactly:
-    >>> t = MessageTemplate({'key': {'const': 'value'}})
-    >>> t.check(Message('Example Sender', {'key': 'value'}))
-    True
-    >>> t.check(Message('Example Sender', {'key': 'other value'}))
-    False
-
-    But for integers, floats with the same value are also valid:
-    >>> t = MessageTemplate({'key': {'const': 42}})
-    >>> t.check(Message('Example Sender', {'key': 42}))
-    True
-    >>> t.check(Message('Example Sender', {'key': 42.0}))
-    True
-
-    Type integer is valid for floats with zero fractional part, but
-    not by floats with non-zero fractional part:
-    >>> t = MessageTemplate({'key': {'type': 'integer'}})
-    >>> t.check(Message('Example Sender', {'key': 42}))
-    True
-    >>> t.check(Message('Example Sender', {'key': 42.0}))
-    True
-    >>> t.check(Message('Example Sender', {'key': 42.42}))
-    False
-
-    Type number is valid for arbitrary ints or floats:
-    >>> t = MessageTemplate({'key': {'type': 'number'}})
-    >>> t.check(Message('Example Sender', {'key': 42}))
-    True
-    >>> t.check(Message('Example Sender', {'key': 42.42}))
-    True
-
-    All keys in template have to be present in message:
-    >>> t = MessageTemplate({'key 1': {'const': 'value'},
-    ...                      'key 2': {'type': 'string'},
-    ...                      'key 3': {'type': 'object',
-    ...                                'properties': {
-    ...                                    'key 1': {'type': 'number'},
-    ...                                    'key 2': True,
-    ...                                    'key 3': False}}})
-    >>> t.check(Message('Example Sender',
-    ...                 {'key 1': 'value', 'key 2': 'some string'}))
-    False
-
-    But for nested objects their properties do not necessarily have
-    to be present:
-    >>> t.check(Message('Example Sender',
-    ...                 {'key 1': 'value', 'key 2': 'some string',
-    ...                  'key 3': {'key 1': 42}}))
-    True
-
-    Schema True matches everything (even None):
-    >>> t.check(Message('Example Sender',
-    ...                 {'key 1': 'value', 'key 2': 'some string',
-    ...                  'key 3': {'key 2': None}}))
-    True
-
-    Schema False matches nothing:
-    >>> t.check(Message('Example Sender',
-    ...                 {'key 1': 'value', 'key 2': 'some string',
-    ...                  'key 3': {'key 3': True}}))
-    False
-
-    Message is valid for the constant template created from it:
-    >>> m = Message('Example Sender', {'dict': {'int': 42, 'float': 42.42},
-    ...                                'list': [None, True, 'string']})
-    >>> t = MessageTemplate.from_message(m)
-    >>> t.check(m)
-    True
-    """
-    for key in self:
-        if key not in message:
-            return False
-        else:
-            validator = jsonschema.Draft7Validator(self[key])
-            for error in validator.iter_errors(message[key]):
-                return False
-    return True
-
-
-
-
-
-class MessageTemplateRegistry -
-
-

Manage a collection of message templates with registered clients.

-

A new MessageTemplateRegistry is created by:

-
>>> r = MessageTemplateRegistry()
-
-

Client names (strings) can be registered for message templates, which -are mappings from keys to JSON schemas:

-
>>> r.insert({'k1': {'const': 'v1'}}, 'C 1')
-
-

The check function checks if the templates registered for a client -match a given message:

-
>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-...     print(f"{m}: {r.check('C 1', m)}")
-{'k1': 'v1', 'k2': 'v1'}: True
-{'k1': 'v1', 'k2': 2}: True
-{'k1': 'v2', 'k2': 'v1'}: False
-{'k1': 'v2', 'k2': 2}: False
-
-

Clients can be registered for values validating against arbitrary JSON -schemas, e.g. all values of a certain type:

-
>>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}, 'C 2')
->>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-...     print(f"{m}: {r.check('C 2', m)}")
-{'k1': 'v1', 'k2': 'v1'}: False
-{'k1': 'v1', 'k2': 2}: False
-{'k1': 'v2', 'k2': 'v1'}: True
-{'k1': 'v2', 'k2': 2}: False
->>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}, 'C 3')
->>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-...     print(f"{m}: {r.check('C 3', m)}")
-{'k1': 'v1', 'k2': 'v1'}: False
-{'k1': 'v1', 'k2': 2}: False
-{'k1': 'v2', 'k2': 'v1'}: False
-{'k1': 'v2', 'k2': 2}: True
-
-

The order of key-value pairs does not have to match the order in the -messages and keys can be left out:

-
>>> r.insert({'k2': {'const': 2}}, 'C 4')
->>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-...     print(f"{m}: {r.check('C 4', m)}")
-{'k1': 'v1', 'k2': 'v1'}: False
-{'k1': 'v1', 'k2': 2}: True
-{'k1': 'v2', 'k2': 'v1'}: False
-{'k1': 'v2', 'k2': 2}: True
-
-

A registration for an empty template matches all messages:

-
>>> r.insert({}, 'C 5')
->>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-...     print(f"{m}: {r.check('C 5', m)}")
-{'k1': 'v1', 'k2': 'v1'}: True
-{'k1': 'v1', 'k2': 2}: True
-{'k1': 'v2', 'k2': 'v1'}: True
-{'k1': 'v2', 'k2': 2}: True
-
-

A client can be registered for multiple templates:

-
>>> r.insert({'k1': {'const': 'v1'}}, 'C 6')
->>> r.insert({'k2': {'const': 'v1'}}, 'C 6')
->>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-...     print(f"{m}: {r.check('C 6', m)}")
-{'k1': 'v1', 'k2': 'v1'}: True
-{'k1': 'v1', 'k2': 2}: True
-{'k1': 'v2', 'k2': 'v1'}: True
-{'k1': 'v2', 'k2': 2}: False
-
-

Clients can be deregistered again (the result is False if the registry -is empty after the deletion):

-
>>> r.insert({'k1': {'const': 'v1'}}, 'C 7')
->>> r.delete('C 7')
-True
->>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-...     print(f"{m}: {r.check('C 7', m)}")
-{'k1': 'v1', 'k2': 'v1'}: False
-{'k1': 'v1', 'k2': 2}: False
-{'k1': 'v2', 'k2': 'v1'}: False
-{'k1': 'v2', 'k2': 2}: False
-
-

The get function returns all clients with registered templates matching -a given message:

-
>>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-...     print(f"{m}: {r.get(m)}")
-{'k1': 'v1', 'k2': 'v1'}: ['C 5', 'C 1', 'C 6']
-{'k1': 'v1', 'k2': 2}: ['C 5', 'C 1', 'C 6', 'C 4']
-{'k1': 'v2', 'k2': 'v1'}: ['C 5', 'C 2', 'C 6']
-{'k1': 'v2', 'k2': 2}: ['C 5', 'C 3', 'C 4']
-
-

The get_templates function returns all templates for a given client:

-
>>> for c in ['C 1', 'C 2', 'C 3', 'C 4', 'C 5', 'C 6']:
-...     print(f"{c}: {r.get_templates(c)}")
-C 1: [{'k1': {'const': 'v1'}}]
-C 2: [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
-C 3: [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
-C 4: [{'k2': {'const': 2}}]
-C 5: [{}]
-C 6: [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
-
-

Initialise an empty registry.

-
>>> r = MessageTemplateRegistry()
-
-
- -Expand source code - -
class MessageTemplateRegistry:
-    """Manage a collection of message templates with registered clients.
-
-    A new MessageTemplateRegistry is created by:
-    >>> r = MessageTemplateRegistry()
-
-    Client names (strings) can be registered for message templates, which
-    are mappings from keys to JSON schemas:
-    >>> r.insert({'k1': {'const': 'v1'}}, 'C 1')
-
-    The check function checks if the templates registered for a client
-    match a given message:
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 1', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: True
-    {'k1': 'v1', 'k2': 2}: True
-    {'k1': 'v2', 'k2': 'v1'}: False
-    {'k1': 'v2', 'k2': 2}: False
-
-    Clients can be registered for values validating against arbitrary JSON
-    schemas, e.g. all values of a certain type:
-    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}, 'C 2')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 2', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: False
-    {'k1': 'v1', 'k2': 2}: False
-    {'k1': 'v2', 'k2': 'v1'}: True
-    {'k1': 'v2', 'k2': 2}: False
-    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}, 'C 3')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 3', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: False
-    {'k1': 'v1', 'k2': 2}: False
-    {'k1': 'v2', 'k2': 'v1'}: False
-    {'k1': 'v2', 'k2': 2}: True
-
-    The order of key-value pairs does not have to match the order in the
-    messages and keys can be left out:
-    >>> r.insert({'k2': {'const': 2}}, 'C 4')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 4', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: False
-    {'k1': 'v1', 'k2': 2}: True
-    {'k1': 'v2', 'k2': 'v1'}: False
-    {'k1': 'v2', 'k2': 2}: True
-
-    A registration for an empty template matches all messages:
-    >>> r.insert({}, 'C 5')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 5', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: True
-    {'k1': 'v1', 'k2': 2}: True
-    {'k1': 'v2', 'k2': 'v1'}: True
-    {'k1': 'v2', 'k2': 2}: True
-
-    A client can be registered for multiple templates:
-    >>> r.insert({'k1': {'const': 'v1'}}, 'C 6')
-    >>> r.insert({'k2': {'const': 'v1'}}, 'C 6')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 6', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: True
-    {'k1': 'v1', 'k2': 2}: True
-    {'k1': 'v2', 'k2': 'v1'}: True
-    {'k1': 'v2', 'k2': 2}: False
-
-    Clients can be deregistered again (the result is False if the registry
-    is empty after the deletion):
-    >>> r.insert({'k1': {'const': 'v1'}}, 'C 7')
-    >>> r.delete('C 7')
-    True
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.check('C 7', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: False
-    {'k1': 'v1', 'k2': 2}: False
-    {'k1': 'v2', 'k2': 'v1'}: False
-    {'k1': 'v2', 'k2': 2}: False
-
-    The get function returns all clients with registered templates matching
-    a given message:
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 2},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 2}]:
-    ...     print(f"{m}: {r.get(m)}")
-    {'k1': 'v1', 'k2': 'v1'}: ['C 5', 'C 1', 'C 6']
-    {'k1': 'v1', 'k2': 2}: ['C 5', 'C 1', 'C 6', 'C 4']
-    {'k1': 'v2', 'k2': 'v1'}: ['C 5', 'C 2', 'C 6']
-    {'k1': 'v2', 'k2': 2}: ['C 5', 'C 3', 'C 4']
-
-    The get_templates function returns all templates for a given client:
-    >>> for c in ['C 1', 'C 2', 'C 3', 'C 4', 'C 5', 'C 6']:
-    ...     print(f"{c}: {r.get_templates(c)}")
-    C 1: [{'k1': {'const': 'v1'}}]
-    C 2: [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
-    C 3: [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
-    C 4: [{'k2': {'const': 2}}]
-    C 5: [{}]
-    C 6: [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
-    """
-
-    def __init__(self) -> None:
-        """Initialise an empty registry.
-
-        >>> r = MessageTemplateRegistry()
-        """
-        self._clients: List[str] = []
-        self._children: Dict[str, Dict[str, MessageTemplateRegistry]] = {}
-
-    def insert(self, template: MessageTemplate, client: str) -> None:
-        """Register a client for a template.
-
-        >>> r = MessageTemplateRegistry()
-        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v1'}}, 'C 1')
-        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v2'}}, 'C 2')
-        >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v1'}}, 'C 3')
-        >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v2'}}, 'C 4')
-        >>> r.insert({}, 'C 5')
-
-        Implementation details:
-        -----------------------
-        The tree nodes on the way to a registered object are used/created
-        in the order given in the message template, which can be used to
-        design more efficient lookups (e.g., putting rarer key-value pairs
-        earlier in the template).
-        >>> r._clients
-        ['C 5']
-        >>> r._children.keys()
-        dict_keys(['k1'])
-        >>> r._children['k1'].keys()
-        dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-        >>> r._children['k1']['{"const": "v1"}']._clients
-        []
-        >>> r._children['k1']['{"const": "v1"}']._children.keys()
-        dict_keys(['k2'])
-        >>> r._children['k1']['{"const": "v1"}']._children['k2'].keys()
-        dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v1"}'])._clients
-        ['C 1']
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v1"}'])._children.keys()
-        dict_keys([])
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v2"}'])._clients
-        ['C 2']
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v2"}'])._children.keys()
-        dict_keys([])
-        >>> r._children['k1']['{"const": "v2"}']._clients
-        []
-        >>> r._children['k1']['{"const": "v2"}']._children.keys()
-        dict_keys(['k2'])
-        >>> r._children['k1']['{"const": "v2"}']._children['k2'].keys()
-        dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-        >>> (r._children['k1']['{"const": "v2"}']
-        ...   ._children['k2']['{"const": "v1"}'])._clients
-        ['C 3']
-        >>> (r._children['k1']['{"const": "v2"}']
-        ...   ._children['k2']['{"const": "v1"}'])._children.keys()
-        dict_keys([])
-        >>> (r._children['k1']['{"const": "v2"}']
-        ...   ._children['k2']['{"const": "v2"}'])._clients
-        ['C 4']
-        >>> (r._children['k1']['{"const": "v2"}']
-        ...   ._children['k2']['{"const": "v2"}'])._children.keys()
-        dict_keys([])
-        """
-        if not template:
-            self._clients.append(client)
-        else:
-            key, schema = next(iter(template.items()))
-            schema_string = json.dumps(schema)
-            reduced_template = MessageTemplate({k: template[k]
-                                                for k in template
-                                                if k != key})
-            if key not in self._children:
-                self._children[key] = {}
-            if schema_string not in self._children[key]:
-                self._children[key][schema_string] = MessageTemplateRegistry()
-            self._children[key][schema_string].insert(reduced_template, client)
-
-    def delete(self, client: str) -> bool:
-        """Unregister a client from all templates.
-
-        >>> r = MessageTemplateRegistry()
-        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v1'}}, 'C 1')
-        >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v2'}}, 'C 2')
-        >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v1'}}, 'C 3')
-        >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v2'}}, 'C 4')
-        >>> r.insert({}, 'C 5')
-        >>> r.delete('C 3')
-        True
-        >>> r.delete('C 4')
-        True
-
-        Implementation details:
-        -----------------------
-        If parts of the tree become superfluous by the deletion of the
-        client, they are also completely removed to reduce the lookup
-        effort and keep the tree clean.
-        >>> r._clients
-        ['C 5']
-        >>> r._children.keys()
-        dict_keys(['k1'])
-        >>> r._children['k1'].keys()
-        dict_keys(['{"const": "v1"}'])
-        >>> r._children['k1']['{"const": "v1"}']._clients
-        []
-        >>> r._children['k1']['{"const": "v1"}']._children.keys()
-        dict_keys(['k2'])
-        >>> r._children['k1']['{"const": "v1"}']._children['k2'].keys()
-        dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v1"}'])._clients
-        ['C 1']
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v1"}'])._children.keys()
-        dict_keys([])
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v2"}'])._clients
-        ['C 2']
-        >>> (r._children['k1']['{"const": "v1"}']
-        ...   ._children['k2']['{"const": "v2"}'])._children.keys()
-        dict_keys([])
-        """
-        self._clients = [c for c in self._clients if c != client]
-        new_children: Dict[str, Dict[str, MessageTemplateRegistry]] = {}
-        for key in self._children:
-            new_children[key] = {}
-            for schema in self._children[key]:
-                if self._children[key][schema].delete(client):
-                    new_children[key][schema] = self._children[key][schema]
-            if not new_children[key]:
-                del new_children[key]
-        self._children = new_children
-        if self._clients or self._children:
-            return True
-        return False
-
-    def check(self, client: str, message: Message) -> bool:
-        """Get if a client has a registered template matching a message.
-
-        >>> r = MessageTemplateRegistry()
-        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
-        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-        ...     print(f"{m}: {r.check('Client 1', m)}")
-        {'k1': 'v1', 'k2': 'v1'}: True
-        {'k1': 'v1', 'k2': 'v2'}: True
-        {'k1': 'v2', 'k2': 'v1'}: False
-        {'k1': 'v2', 'k2': 'v2'}: False
-        >>> r.insert({'k2': {'const': 'v2'}}, 'Client 2')
-        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-        ...     print(f"{m}: {r.check('Client 2', m)}")
-        {'k1': 'v1', 'k2': 'v1'}: False
-        {'k1': 'v1', 'k2': 'v2'}: True
-        {'k1': 'v2', 'k2': 'v1'}: False
-        {'k1': 'v2', 'k2': 'v2'}: True
-        """
-        if client in self._clients:
-            return True
-        for key in self._children:
-            if key in message:
-                for schema_string in self._children[key]:
-                    schema = json.loads(schema_string)
-                    validator = jsonschema.Draft7Validator(schema)
-                    validated = True
-                    for error in validator.iter_errors(message[key]):
-                        validated = False
-                    if validated:
-                        child = self._children[key][schema_string]
-                        if child.check(client, message):
-                            return True
-        return False
-
-    def get(self, message: Message) -> List[str]:
-        """Get all clients registered for templates matching a message.
-
-        >>> r = MessageTemplateRegistry()
-        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
-        >>> r.insert({'k2': {'const': 'v2'}}, 'Client 2')
-        >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-        ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-        ...     print(f"{m}: {r.get(m)}")
-        {'k1': 'v1', 'k2': 'v1'}: ['Client 1']
-        {'k1': 'v1', 'k2': 'v2'}: ['Client 1', 'Client 2']
-        {'k1': 'v2', 'k2': 'v1'}: []
-        {'k1': 'v2', 'k2': 'v2'}: ['Client 2']
-        """
-        result = []
-        for client in self._clients:
-            if client not in result:
-                result.append(client)
-        for key in self._children:
-            if key in message:
-                for schema_string in self._children[key]:
-                    schema = json.loads(schema_string)
-                    validator = jsonschema.Draft7Validator(schema)
-                    validated = True
-                    for error in validator.iter_errors(message[key]):
-                        validated = False
-                    if validated:
-                        child = self._children[key][schema_string]
-                        for client in child.get(message):
-                            if client not in result:
-                                result.append(client)
-        return result
-
-    def get_templates(self, client: str) -> List[MessageTemplate]:
-        """Get all templates for a client.
-
-        >>> r = MessageTemplateRegistry()
-        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
-        >>> r.get_templates('Client 1')
-        [{'k1': {'const': 'v1'}}]
-        >>> r.insert({'k1': {'const': 'v2'},
-        ...           'k2': {'type': 'string'}}, 'Client 2')
-        >>> r.get_templates('Client 2')
-        [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
-        >>> r.insert({'k1': {'const': 'v2'},
-        ...           'k2': {'type': 'integer'}}, 'Client 3')
-        >>> r.get_templates('Client 3')
-        [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
-        >>> r.insert({'k2': {'const': 2}}, 'Client 4')
-        >>> r.get_templates('Client 4')
-        [{'k2': {'const': 2}}]
-        >>> r.insert({}, 'Client 5')
-        >>> r.get_templates('Client 5')
-        [{}]
-        >>> r.insert({'k1': {'const': 'v1'}}, 'Client 6')
-        >>> r.insert({'k2': {'const': 'v1'}}, 'Client 6')
-        >>> r.get_templates('Client 6')
-        [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
-        """
-        result = []
-        if client in self._clients:
-            result.append(MessageTemplate())
-        for key in self._children:
-            for schema_string in self._children[key]:
-                schema = json.loads(schema_string)
-                child = self._children[key][schema_string]
-                for template in child.get_templates(client):
-                    current = MessageTemplate({key: schema})
-                    current.update(template)
-                    result.append(current)
-        return result
-
-

Methods

-
-
-def insert(self, template: MessageTemplate, client: str) ‑> NoneType -
-
-

Register a client for a template.

-
>>> r = MessageTemplateRegistry()
->>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v1'}}, 'C 1')
->>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v2'}}, 'C 2')
->>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v1'}}, 'C 3')
->>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v2'}}, 'C 4')
->>> r.insert({}, 'C 5')
-
-

Implementation details:

-

The tree nodes on the way to a registered object are used/created -in the order given in the message template, which can be used to -design more efficient lookups (e.g., putting rarer key-value pairs -earlier in the template).

-
>>> r._clients
-['C 5']
->>> r._children.keys()
-dict_keys(['k1'])
->>> r._children['k1'].keys()
-dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
->>> r._children['k1']['{"const": "v1"}']._clients
-[]
->>> r._children['k1']['{"const": "v1"}']._children.keys()
-dict_keys(['k2'])
->>> r._children['k1']['{"const": "v1"}']._children['k2'].keys()
-dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
->>> (r._children['k1']['{"const": "v1"}']
-...   ._children['k2']['{"const": "v1"}'])._clients
-['C 1']
->>> (r._children['k1']['{"const": "v1"}']
-...   ._children['k2']['{"const": "v1"}'])._children.keys()
-dict_keys([])
->>> (r._children['k1']['{"const": "v1"}']
-...   ._children['k2']['{"const": "v2"}'])._clients
-['C 2']
->>> (r._children['k1']['{"const": "v1"}']
-...   ._children['k2']['{"const": "v2"}'])._children.keys()
-dict_keys([])
->>> r._children['k1']['{"const": "v2"}']._clients
-[]
->>> r._children['k1']['{"const": "v2"}']._children.keys()
-dict_keys(['k2'])
->>> r._children['k1']['{"const": "v2"}']._children['k2'].keys()
-dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
->>> (r._children['k1']['{"const": "v2"}']
-...   ._children['k2']['{"const": "v1"}'])._clients
-['C 3']
->>> (r._children['k1']['{"const": "v2"}']
-...   ._children['k2']['{"const": "v1"}'])._children.keys()
-dict_keys([])
->>> (r._children['k1']['{"const": "v2"}']
-...   ._children['k2']['{"const": "v2"}'])._clients
-['C 4']
->>> (r._children['k1']['{"const": "v2"}']
-...   ._children['k2']['{"const": "v2"}'])._children.keys()
-dict_keys([])
-
-
- -Expand source code - -
def insert(self, template: MessageTemplate, client: str) -> None:
-    """Register a client for a template.
-
-    >>> r = MessageTemplateRegistry()
-    >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v1'}}, 'C 1')
-    >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v2'}}, 'C 2')
-    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v1'}}, 'C 3')
-    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v2'}}, 'C 4')
-    >>> r.insert({}, 'C 5')
-
-    Implementation details:
-    -----------------------
-    The tree nodes on the way to a registered object are used/created
-    in the order given in the message template, which can be used to
-    design more efficient lookups (e.g., putting rarer key-value pairs
-    earlier in the template).
-    >>> r._clients
-    ['C 5']
-    >>> r._children.keys()
-    dict_keys(['k1'])
-    >>> r._children['k1'].keys()
-    dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-    >>> r._children['k1']['{"const": "v1"}']._clients
-    []
-    >>> r._children['k1']['{"const": "v1"}']._children.keys()
-    dict_keys(['k2'])
-    >>> r._children['k1']['{"const": "v1"}']._children['k2'].keys()
-    dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-    >>> (r._children['k1']['{"const": "v1"}']
-    ...   ._children['k2']['{"const": "v1"}'])._clients
-    ['C 1']
-    >>> (r._children['k1']['{"const": "v1"}']
-    ...   ._children['k2']['{"const": "v1"}'])._children.keys()
-    dict_keys([])
-    >>> (r._children['k1']['{"const": "v1"}']
-    ...   ._children['k2']['{"const": "v2"}'])._clients
-    ['C 2']
-    >>> (r._children['k1']['{"const": "v1"}']
-    ...   ._children['k2']['{"const": "v2"}'])._children.keys()
-    dict_keys([])
-    >>> r._children['k1']['{"const": "v2"}']._clients
-    []
-    >>> r._children['k1']['{"const": "v2"}']._children.keys()
-    dict_keys(['k2'])
-    >>> r._children['k1']['{"const": "v2"}']._children['k2'].keys()
-    dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-    >>> (r._children['k1']['{"const": "v2"}']
-    ...   ._children['k2']['{"const": "v1"}'])._clients
-    ['C 3']
-    >>> (r._children['k1']['{"const": "v2"}']
-    ...   ._children['k2']['{"const": "v1"}'])._children.keys()
-    dict_keys([])
-    >>> (r._children['k1']['{"const": "v2"}']
-    ...   ._children['k2']['{"const": "v2"}'])._clients
-    ['C 4']
-    >>> (r._children['k1']['{"const": "v2"}']
-    ...   ._children['k2']['{"const": "v2"}'])._children.keys()
-    dict_keys([])
-    """
-    if not template:
-        self._clients.append(client)
-    else:
-        key, schema = next(iter(template.items()))
-        schema_string = json.dumps(schema)
-        reduced_template = MessageTemplate({k: template[k]
-                                            for k in template
-                                            if k != key})
-        if key not in self._children:
-            self._children[key] = {}
-        if schema_string not in self._children[key]:
-            self._children[key][schema_string] = MessageTemplateRegistry()
-        self._children[key][schema_string].insert(reduced_template, client)
-
-
-
-def delete(self, client: str) ‑> bool -
-
-

Unregister a client from all templates.

-
>>> r = MessageTemplateRegistry()
->>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v1'}}, 'C 1')
->>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v2'}}, 'C 2')
->>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v1'}}, 'C 3')
->>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v2'}}, 'C 4')
->>> r.insert({}, 'C 5')
->>> r.delete('C 3')
-True
->>> r.delete('C 4')
-True
-
-

Implementation details:

-

If parts of the tree become superfluous by the deletion of the -client, they are also completely removed to reduce the lookup -effort and keep the tree clean.

-
>>> r._clients
-['C 5']
->>> r._children.keys()
-dict_keys(['k1'])
->>> r._children['k1'].keys()
-dict_keys(['{"const": "v1"}'])
->>> r._children['k1']['{"const": "v1"}']._clients
-[]
->>> r._children['k1']['{"const": "v1"}']._children.keys()
-dict_keys(['k2'])
->>> r._children['k1']['{"const": "v1"}']._children['k2'].keys()
-dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
->>> (r._children['k1']['{"const": "v1"}']
-...   ._children['k2']['{"const": "v1"}'])._clients
-['C 1']
->>> (r._children['k1']['{"const": "v1"}']
-...   ._children['k2']['{"const": "v1"}'])._children.keys()
-dict_keys([])
->>> (r._children['k1']['{"const": "v1"}']
-...   ._children['k2']['{"const": "v2"}'])._clients
-['C 2']
->>> (r._children['k1']['{"const": "v1"}']
-...   ._children['k2']['{"const": "v2"}'])._children.keys()
-dict_keys([])
-
-
- -Expand source code - -
def delete(self, client: str) -> bool:
-    """Unregister a client from all templates.
-
-    >>> r = MessageTemplateRegistry()
-    >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v1'}}, 'C 1')
-    >>> r.insert({'k1': {'const': 'v1'}, 'k2': {'const': 'v2'}}, 'C 2')
-    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v1'}}, 'C 3')
-    >>> r.insert({'k1': {'const': 'v2'}, 'k2': {'const': 'v2'}}, 'C 4')
-    >>> r.insert({}, 'C 5')
-    >>> r.delete('C 3')
-    True
-    >>> r.delete('C 4')
-    True
-
-    Implementation details:
-    -----------------------
-    If parts of the tree become superfluous by the deletion of the
-    client, they are also completely removed to reduce the lookup
-    effort and keep the tree clean.
-    >>> r._clients
-    ['C 5']
-    >>> r._children.keys()
-    dict_keys(['k1'])
-    >>> r._children['k1'].keys()
-    dict_keys(['{"const": "v1"}'])
-    >>> r._children['k1']['{"const": "v1"}']._clients
-    []
-    >>> r._children['k1']['{"const": "v1"}']._children.keys()
-    dict_keys(['k2'])
-    >>> r._children['k1']['{"const": "v1"}']._children['k2'].keys()
-    dict_keys(['{"const": "v1"}', '{"const": "v2"}'])
-    >>> (r._children['k1']['{"const": "v1"}']
-    ...   ._children['k2']['{"const": "v1"}'])._clients
-    ['C 1']
-    >>> (r._children['k1']['{"const": "v1"}']
-    ...   ._children['k2']['{"const": "v1"}'])._children.keys()
-    dict_keys([])
-    >>> (r._children['k1']['{"const": "v1"}']
-    ...   ._children['k2']['{"const": "v2"}'])._clients
-    ['C 2']
-    >>> (r._children['k1']['{"const": "v1"}']
-    ...   ._children['k2']['{"const": "v2"}'])._children.keys()
-    dict_keys([])
-    """
-    self._clients = [c for c in self._clients if c != client]
-    new_children: Dict[str, Dict[str, MessageTemplateRegistry]] = {}
-    for key in self._children:
-        new_children[key] = {}
-        for schema in self._children[key]:
-            if self._children[key][schema].delete(client):
-                new_children[key][schema] = self._children[key][schema]
-        if not new_children[key]:
-            del new_children[key]
-    self._children = new_children
-    if self._clients or self._children:
-        return True
-    return False
-
-
-
-def check(self, client: str, message: Message) ‑> bool -
-
-

Get if a client has a registered template matching a message.

-
>>> r = MessageTemplateRegistry()
->>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
->>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-...     print(f"{m}: {r.check('Client 1', m)}")
-{'k1': 'v1', 'k2': 'v1'}: True
-{'k1': 'v1', 'k2': 'v2'}: True
-{'k1': 'v2', 'k2': 'v1'}: False
-{'k1': 'v2', 'k2': 'v2'}: False
->>> r.insert({'k2': {'const': 'v2'}}, 'Client 2')
->>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-...     print(f"{m}: {r.check('Client 2', m)}")
-{'k1': 'v1', 'k2': 'v1'}: False
-{'k1': 'v1', 'k2': 'v2'}: True
-{'k1': 'v2', 'k2': 'v1'}: False
-{'k1': 'v2', 'k2': 'v2'}: True
-
-
- -Expand source code - -
def check(self, client: str, message: Message) -> bool:
-    """Get if a client has a registered template matching a message.
-
-    >>> r = MessageTemplateRegistry()
-    >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-    ...     print(f"{m}: {r.check('Client 1', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: True
-    {'k1': 'v1', 'k2': 'v2'}: True
-    {'k1': 'v2', 'k2': 'v1'}: False
-    {'k1': 'v2', 'k2': 'v2'}: False
-    >>> r.insert({'k2': {'const': 'v2'}}, 'Client 2')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-    ...     print(f"{m}: {r.check('Client 2', m)}")
-    {'k1': 'v1', 'k2': 'v1'}: False
-    {'k1': 'v1', 'k2': 'v2'}: True
-    {'k1': 'v2', 'k2': 'v1'}: False
-    {'k1': 'v2', 'k2': 'v2'}: True
-    """
-    if client in self._clients:
-        return True
-    for key in self._children:
-        if key in message:
-            for schema_string in self._children[key]:
-                schema = json.loads(schema_string)
-                validator = jsonschema.Draft7Validator(schema)
-                validated = True
-                for error in validator.iter_errors(message[key]):
-                    validated = False
-                if validated:
-                    child = self._children[key][schema_string]
-                    if child.check(client, message):
-                        return True
-    return False
-
-
-
-def get(self, message: Message) ‑> List[str] -
-
-

Get all clients registered for templates matching a message.

-
>>> r = MessageTemplateRegistry()
->>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
->>> r.insert({'k2': {'const': 'v2'}}, 'Client 2')
->>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-...     print(f"{m}: {r.get(m)}")
-{'k1': 'v1', 'k2': 'v1'}: ['Client 1']
-{'k1': 'v1', 'k2': 'v2'}: ['Client 1', 'Client 2']
-{'k1': 'v2', 'k2': 'v1'}: []
-{'k1': 'v2', 'k2': 'v2'}: ['Client 2']
-
-
- -Expand source code - -
def get(self, message: Message) -> List[str]:
-    """Get all clients registered for templates matching a message.
-
-    >>> r = MessageTemplateRegistry()
-    >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
-    >>> r.insert({'k2': {'const': 'v2'}}, 'Client 2')
-    >>> for m in [{'k1': 'v1', 'k2': 'v1'}, {'k1': 'v1', 'k2': 'v2'},
-    ...           {'k1': 'v2', 'k2': 'v1'}, {'k1': 'v2', 'k2': 'v2'}]:
-    ...     print(f"{m}: {r.get(m)}")
-    {'k1': 'v1', 'k2': 'v1'}: ['Client 1']
-    {'k1': 'v1', 'k2': 'v2'}: ['Client 1', 'Client 2']
-    {'k1': 'v2', 'k2': 'v1'}: []
-    {'k1': 'v2', 'k2': 'v2'}: ['Client 2']
-    """
-    result = []
-    for client in self._clients:
-        if client not in result:
-            result.append(client)
-    for key in self._children:
-        if key in message:
-            for schema_string in self._children[key]:
-                schema = json.loads(schema_string)
-                validator = jsonschema.Draft7Validator(schema)
-                validated = True
-                for error in validator.iter_errors(message[key]):
-                    validated = False
-                if validated:
-                    child = self._children[key][schema_string]
-                    for client in child.get(message):
-                        if client not in result:
-                            result.append(client)
-    return result
-
-
-
-def get_templates(self, client: str) ‑> List[MessageTemplate] -
-
-

Get all templates for a client.

-
>>> r = MessageTemplateRegistry()
->>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
->>> r.get_templates('Client 1')
-[{'k1': {'const': 'v1'}}]
->>> r.insert({'k1': {'const': 'v2'},
-...           'k2': {'type': 'string'}}, 'Client 2')
->>> r.get_templates('Client 2')
-[{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
->>> r.insert({'k1': {'const': 'v2'},
-...           'k2': {'type': 'integer'}}, 'Client 3')
->>> r.get_templates('Client 3')
-[{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
->>> r.insert({'k2': {'const': 2}}, 'Client 4')
->>> r.get_templates('Client 4')
-[{'k2': {'const': 2}}]
->>> r.insert({}, 'Client 5')
->>> r.get_templates('Client 5')
-[{}]
->>> r.insert({'k1': {'const': 'v1'}}, 'Client 6')
->>> r.insert({'k2': {'const': 'v1'}}, 'Client 6')
->>> r.get_templates('Client 6')
-[{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
-
-
- -Expand source code - -
def get_templates(self, client: str) -> List[MessageTemplate]:
-    """Get all templates for a client.
-
-    >>> r = MessageTemplateRegistry()
-    >>> r.insert({'k1': {'const': 'v1'}}, 'Client 1')
-    >>> r.get_templates('Client 1')
-    [{'k1': {'const': 'v1'}}]
-    >>> r.insert({'k1': {'const': 'v2'},
-    ...           'k2': {'type': 'string'}}, 'Client 2')
-    >>> r.get_templates('Client 2')
-    [{'k1': {'const': 'v2'}, 'k2': {'type': 'string'}}]
-    >>> r.insert({'k1': {'const': 'v2'},
-    ...           'k2': {'type': 'integer'}}, 'Client 3')
-    >>> r.get_templates('Client 3')
-    [{'k1': {'const': 'v2'}, 'k2': {'type': 'integer'}}]
-    >>> r.insert({'k2': {'const': 2}}, 'Client 4')
-    >>> r.get_templates('Client 4')
-    [{'k2': {'const': 2}}]
-    >>> r.insert({}, 'Client 5')
-    >>> r.get_templates('Client 5')
-    [{}]
-    >>> r.insert({'k1': {'const': 'v1'}}, 'Client 6')
-    >>> r.insert({'k2': {'const': 'v1'}}, 'Client 6')
-    >>> r.get_templates('Client 6')
-    [{'k1': {'const': 'v1'}}, {'k2': {'const': 'v1'}}]
-    """
-    result = []
-    if client in self._clients:
-        result.append(MessageTemplate())
-    for key in self._children:
-        for schema_string in self._children[key]:
-            schema = json.loads(schema_string)
-            child = self._children[key][schema_string]
-            for template in child.get_templates(client):
-                current = MessageTemplate({key: schema})
-                current.update(template)
-                result.append(current)
-    return result
-
-
-
-
-
-class BusException -(*args, **kwargs) -
-
-

Raise for errors in using message bus.

-
- -Expand source code - -
class BusException(Exception):
-    """Raise for errors in using message bus."""
-
-

Ancestors

-
    -
  • builtins.Exception
  • -
  • builtins.BaseException
  • -
-
-
-class MessageBus -
-
-

Provide an asynchronous message bus.

-

The bus executes asynchronous callbacks for all messages to be received -by a client. We use a simple callback printing the message in all -examples:

-
>>> def callback_for_receiver(receiver):
-...     print(f"Creating callback for {receiver}.")
-...     async def callback(message):
-...         print(f"{receiver}: {message}")
-...     return callback
-
-

Clients can be registered at the bus with a name, lists of message -templates they want to use for sending and receiving and a callback -function for receiving. An empty list of templates means that the -client does not want to send or receive any messages, respectively. -A list with an empty template means that it wants to send arbitrary -or receive all messages, respectively:

-
>>> async def setup(bus):
-...     print("Setting up.")
-...     bus.register('Logger', 'Test Plugin',
-...                  [],
-...                  [{}],
-...                  callback_for_receiver('Logger'))
-...     bus.register('Client 1', 'Test Plugin',
-...                  [{'k1': {'type': 'string'}}],
-...                  [{'target': {'const': 'Client 1'}}],
-...                  callback_for_receiver('Client 1'))
-...     bus.register('Client 2', 'Test Plugin',
-...                  [{}],
-...                  [{'target': {'const': 'Client 2'}}],
-...                  callback_for_receiver('Client 2'))
-
-

The bus itself is addressed by the empty string. It sends messages for -each registration and deregestration of a client with a key 'event' and -a value of 'registered' or 'unregistered', a key 'client' with the -client's name as value and for registrations also keys 'sends' and -'receives' with all templates registered for the client for sending and -receiving.

-

Clients can send to the bus with the send function. Each message has to -declare a sender. The send templates of that sender are checked for a -template matching the message. We cannot prevent arbitrary code from -impersonating any sender, but this should only be done in debugging or -management situations.

-

Messages that are intended for a specific client by convention have a -key 'target' with the target client's name as value. Such messages are -often commands to the client to do something, which is by convention -indicated by a key 'command' with a value that indicates what should be -done.

-

The bus, for example, reacts to a message with 'target': '' and -'command': 'get clients' by sending one message for each currently -registered with complete information about its registered send and -receive templates.

-
>>> async def send(bus):
-...     print("Sending messages.")
-...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})
-...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
-...     await bus.send({'sender': '', 'target': '',
-...                     'command': 'get clients'})
-
-

The run function executes the message bus forever. If we want to stop -it, we have to explicitly cancel the task:

-
>>> async def main():
-...     bus = MessageBus()
-...     await setup(bus)
-...     bus_task = asyncio.create_task(bus.run())
-...     await send(bus)
-...     await asyncio.sleep(0)
-...     bus_task.cancel()
->>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
-Setting up.
-Creating callback for Logger.
-Creating callback for Client 1.
-Creating callback for Client 2.
-Sending messages.
-Logger: {'sender': '', 'event': 'registered',
-         'client': 'Logger', 'plugin': 'Test Plugin',
-         'sends': [], 'receives': [{}]}
-Logger: {'sender': '', 'event': 'registered',
-         'client': 'Client 1', 'plugin': 'Test Plugin',
-         'sends': [{'k1': {'type': 'string'}}],
-         'receives': [{'target': {'const': 'Client 1'}}]}
-Logger: {'sender': '', 'event': 'registered',
-         'client': 'Client 2', 'plugin': 'Test Plugin',
-         'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
-Logger: {'sender': 'Client 1', 'k1': 'Test'}
-Logger: {'sender': 'Client 2', 'target': 'Client 1'}
-Client 1: {'sender': 'Client 2', 'target': 'Client 1'}
-Logger: {'sender': '', 'target': '', 'command': 'get clients'}
-Logger: {'sender': '', 'client': 'Logger', 'plugin': 'Test Plugin',
-         'sends': [], 'receives': [{}]}
-Logger: {'sender': '', 'client': 'Client 1', 'plugin': 'Test Plugin',
-         'sends': [{'k1': {'type': 'string'}}],
-         'receives': [{'target': {'const': 'Client 1'}}]}
-Logger: {'sender': '', 'client': 'Client 2', 'plugin': 'Test Plugin',
-         'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
-
-

Initialise a new bus without clients.

-
>>> async def main():
-...     bus = MessageBus()
->>> asyncio.run(main())
-
-
- -Expand source code - -
class MessageBus:
-    """Provide an asynchronous message bus.
-
-    The bus executes asynchronous callbacks for all messages to be received
-    by a client. We use a simple callback printing the message in all
-    examples:
-    >>> def callback_for_receiver(receiver):
-    ...     print(f"Creating callback for {receiver}.")
-    ...     async def callback(message):
-    ...         print(f"{receiver}: {message}")
-    ...     return callback
-
-    Clients can be registered at the bus with a name, lists of message
-    templates they want to use for sending and receiving and a callback
-    function for receiving. An empty list of templates means that the
-    client does not want to send or receive any messages, respectively.
-    A list with an empty template means that it wants to send arbitrary
-    or receive all messages, respectively:
-    >>> async def setup(bus):
-    ...     print("Setting up.")
-    ...     bus.register('Logger', 'Test Plugin',
-    ...                  [],
-    ...                  [{}],
-    ...                  callback_for_receiver('Logger'))
-    ...     bus.register('Client 1', 'Test Plugin',
-    ...                  [{'k1': {'type': 'string'}}],
-    ...                  [{'target': {'const': 'Client 1'}}],
-    ...                  callback_for_receiver('Client 1'))
-    ...     bus.register('Client 2', 'Test Plugin',
-    ...                  [{}],
-    ...                  [{'target': {'const': 'Client 2'}}],
-    ...                  callback_for_receiver('Client 2'))
-
-    The bus itself is addressed by the empty string. It sends messages for
-    each registration and deregestration of a client with a key 'event' and
-    a value of 'registered' or 'unregistered', a key 'client' with the
-    client's name as value and for registrations also keys 'sends' and
-    'receives' with all templates registered for the client for sending and
-    receiving.
-
-    Clients can send to the bus with the send function. Each message has to
-    declare a sender. The send templates of that sender are checked for a
-    template matching the message. We cannot prevent arbitrary code from
-    impersonating any sender, but this should only be done in debugging or
-    management situations.
-
-    Messages that are intended for a specific client by convention have a
-    key 'target' with the target client's name as value. Such messages are
-    often commands to the client to do something, which is by convention
-    indicated by a key 'command' with a value that indicates what should be
-    done.
-
-    The bus, for example, reacts to a message with 'target': '' and
-    'command': 'get clients' by sending one message for each currently
-    registered with complete information about its registered send and
-    receive templates.
-
-    >>> async def send(bus):
-    ...     print("Sending messages.")
-    ...     await bus.send({'sender': 'Client 1', 'k1': 'Test'})
-    ...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
-    ...     await bus.send({'sender': '', 'target': '',
-    ...                     'command': 'get clients'})
-
-    The run function executes the message bus forever. If we want to stop
-    it, we have to explicitly cancel the task:
-    >>> async def main():
-    ...     bus = MessageBus()
-    ...     await setup(bus)
-    ...     bus_task = asyncio.create_task(bus.run())
-    ...     await send(bus)
-    ...     await asyncio.sleep(0)
-    ...     bus_task.cancel()
-    >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
-    Setting up.
-    Creating callback for Logger.
-    Creating callback for Client 1.
-    Creating callback for Client 2.
-    Sending messages.
-    Logger: {'sender': '', 'event': 'registered',
-             'client': 'Logger', 'plugin': 'Test Plugin',
-             'sends': [], 'receives': [{}]}
-    Logger: {'sender': '', 'event': 'registered',
-             'client': 'Client 1', 'plugin': 'Test Plugin',
-             'sends': [{'k1': {'type': 'string'}}],
-             'receives': [{'target': {'const': 'Client 1'}}]}
-    Logger: {'sender': '', 'event': 'registered',
-             'client': 'Client 2', 'plugin': 'Test Plugin',
-             'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
-    Logger: {'sender': 'Client 1', 'k1': 'Test'}
-    Logger: {'sender': 'Client 2', 'target': 'Client 1'}
-    Client 1: {'sender': 'Client 2', 'target': 'Client 1'}
-    Logger: {'sender': '', 'target': '', 'command': 'get clients'}
-    Logger: {'sender': '', 'client': 'Logger', 'plugin': 'Test Plugin',
-             'sends': [], 'receives': [{}]}
-    Logger: {'sender': '', 'client': 'Client 1', 'plugin': 'Test Plugin',
-             'sends': [{'k1': {'type': 'string'}}],
-             'receives': [{'target': {'const': 'Client 1'}}]}
-    Logger: {'sender': '', 'client': 'Client 2', 'plugin': 'Test Plugin',
-             'sends': [{}], 'receives': [{'target': {'const': 'Client 2'}}]}
-    """
-
-    def __init__(self) -> None:
-        """Initialise a new bus without clients.
-
-        >>> async def main():
-        ...     bus = MessageBus()
-        >>> asyncio.run(main())
-        """
-        self._queue: asyncio.Queue = asyncio.Queue()
-        self._plugins: Dict[str, str] = {}
-        self._send_reg: MessageTemplateRegistry = MessageTemplateRegistry()
-        self._recv_reg: MessageTemplateRegistry = MessageTemplateRegistry()
-        self._callbacks: Dict[str, MessageCallback] = {}
-
-    def register(self, client: str, plugin: str,
-                 sends: Iterable[MessageTemplate],
-                 receives: Iterable[MessageTemplate],
-                 callback: MessageCallback) -> None:
-        """Register a client at the message bus.
-
-        >>> async def callback(message):
-        ...     print(message)
-        >>> async def main():
-        ...     bus = MessageBus()
-        ...     bus.register('Logger', 'Test Plugin',
-        ...                  [],    # send nothing
-        ...                  [{}],  # receive everything
-        ...                  callback)
-        ...     bus.register('Client 1', 'Test Plugin',
-        ...                  [{'k1': {'type': 'string'}}],
-        ...                      # send with key 'k1' and string value
-        ...                  [{'target': {'const': 'Client 1'}}],
-        ...                      # receive for this client
-        ...                  callback)
-        ...     bus.register('Client 2', 'Test Plugin',
-        ...                  [{}],  # send arbitrary
-        ...                  [{'target': {'const': 'Client 2'}}],
-        ...                      # receive for this client
-        ...                  callback)
-        >>> asyncio.run(main())
-        """
-        if not client:
-            raise BusException("Client name is not allowed to be empty.")
-        if client in self._plugins:
-            raise BusException(f"Client '{client}' already registered"
-                               " at message bus.")
-        event = Message('')
-        event['event'] = 'registered'
-        event['client'] = client
-        self._plugins[client] = plugin
-        event['plugin'] = plugin
-        for template in sends:
-            self._send_reg.insert(template, client)
-        event['sends'] = self._send_reg.get_templates(client)
-        for template in receives:
-            self._recv_reg.insert(template, client)
-        event['receives'] = self._recv_reg.get_templates(client)
-        self._callbacks[client] = callback
-        self._queue.put_nowait(event)
-
-    def unregister(self, client: str) -> None:
-        """Unregister a client from the message bus.
-
-        >>> async def callback(message):
-        ...     print(message)
-        >>> async def main():
-        ...     bus = MessageBus()
-        ...     bus.register('Client 1', 'Test Plugin',
-        ...                  [{'k1': {'type': 'string'}}],
-        ...                  [{'target': {'const': 'Client 1'}}],
-        ...                  callback)
-        ...     bus.unregister('Client 1')
-        >>> asyncio.run(main())
-        """
-        if client not in self._plugins:
-            return
-        event = Message('')
-        event['event'] = 'unregistered'
-        event['client'] = client
-        del self._plugins[client]
-        self._send_reg.delete(client)
-        self._recv_reg.delete(client)
-        if client in self._callbacks:
-            del self._callbacks[client]
-        self._queue.put_nowait(event)
-
-    async def run(self) -> None:
-        """Run the message bus forever.
-
-        >>> async def main():
-        ...     bus = MessageBus()
-        ...     bus_task = asyncio.create_task(bus.run())
-        ...     bus_task.cancel()
-        >>> asyncio.run(main())
-        """
-        while True:
-            message = await self._queue.get()
-            if ('target' in message and
-                    message['target'] == '' and
-                    'command' in message and
-                    message['command'] == 'get clients'):
-                for client in self._plugins:
-                    answer = Message('')
-                    answer['client'] = client
-                    answer['plugin'] = self._plugins[client]
-                    answer['sends'] = self._send_reg.get_templates(client)
-                    answer['receives'] = self._recv_reg.get_templates(client)
-                    await self._queue.put(answer)
-            for client in self._recv_reg.get(message):
-                await self._callbacks[client](message)
-            self._queue.task_done()
-
-    async def send(self, message: Message) -> None:
-        """Send a message to the message bus.
-
-        >>> async def callback(message):
-        ...     print(f"Got: {message}")
-        >>> async def main():
-        ...     bus = MessageBus()
-        ...     bus.register('Client 1', 'Test Plugin',
-        ...                  [{'k1': {'type': 'string'}}],
-        ...                  [{'target': {'const': 'Client 1'}}],
-        ...                  callback)
-        ...     bus.register('Client 2', 'Test Plugin',
-        ...                  [{}],
-        ...                  [{'target': {'const': 'Client 2'}}],
-        ...                  callback)
-        ...     bus_task = asyncio.create_task(bus.run())
-        ...     await bus.send({'sender': 'Client 1', 'target': 'Client 2',
-        ...                     'k1': 'Test'})
-        ...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
-        ...     try:
-        ...         await bus.send({'sender': 'Client 1', 'target': 'Client 2',
-        ...                         'k1': 42})
-        ...     except BusException as e:
-        ...         print(e)
-        ...     await asyncio.sleep(0)
-        ...     bus_task.cancel()
-        >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
-        Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
-        not allowed for sender 'Client 1'.
-        Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
-        Got: {'sender': 'Client 2', 'target': 'Client 1'}
-        """
-        assert isinstance(message['sender'], str)
-        sender = message['sender']
-        if sender:
-            if not self._send_reg.check(sender, message):
-                raise BusException(f"Message '{message}' not allowed for"
-                                   f" sender '{sender}'.")
-        await self._queue.put(message)
-
-

Methods

-
-
-def register(self, client: str, plugin: str, sends: Iterable[MessageTemplate], receives: Iterable[MessageTemplate], callback: Callable[[ForwardRef('Message')], Coroutine[Any, Any, NoneType]]) ‑> NoneType -
-
-

Register a client at the message bus.

-
>>> async def callback(message):
-...     print(message)
->>> async def main():
-...     bus = MessageBus()
-...     bus.register('Logger', 'Test Plugin',
-...                  [],    # send nothing
-...                  [{}],  # receive everything
-...                  callback)
-...     bus.register('Client 1', 'Test Plugin',
-...                  [{'k1': {'type': 'string'}}],
-...                      # send with key 'k1' and string value
-...                  [{'target': {'const': 'Client 1'}}],
-...                      # receive for this client
-...                  callback)
-...     bus.register('Client 2', 'Test Plugin',
-...                  [{}],  # send arbitrary
-...                  [{'target': {'const': 'Client 2'}}],
-...                      # receive for this client
-...                  callback)
->>> asyncio.run(main())
-
-
- -Expand source code - -
def register(self, client: str, plugin: str,
-             sends: Iterable[MessageTemplate],
-             receives: Iterable[MessageTemplate],
-             callback: MessageCallback) -> None:
-    """Register a client at the message bus.
-
-    >>> async def callback(message):
-    ...     print(message)
-    >>> async def main():
-    ...     bus = MessageBus()
-    ...     bus.register('Logger', 'Test Plugin',
-    ...                  [],    # send nothing
-    ...                  [{}],  # receive everything
-    ...                  callback)
-    ...     bus.register('Client 1', 'Test Plugin',
-    ...                  [{'k1': {'type': 'string'}}],
-    ...                      # send with key 'k1' and string value
-    ...                  [{'target': {'const': 'Client 1'}}],
-    ...                      # receive for this client
-    ...                  callback)
-    ...     bus.register('Client 2', 'Test Plugin',
-    ...                  [{}],  # send arbitrary
-    ...                  [{'target': {'const': 'Client 2'}}],
-    ...                      # receive for this client
-    ...                  callback)
-    >>> asyncio.run(main())
-    """
-    if not client:
-        raise BusException("Client name is not allowed to be empty.")
-    if client in self._plugins:
-        raise BusException(f"Client '{client}' already registered"
-                           " at message bus.")
-    event = Message('')
-    event['event'] = 'registered'
-    event['client'] = client
-    self._plugins[client] = plugin
-    event['plugin'] = plugin
-    for template in sends:
-        self._send_reg.insert(template, client)
-    event['sends'] = self._send_reg.get_templates(client)
-    for template in receives:
-        self._recv_reg.insert(template, client)
-    event['receives'] = self._recv_reg.get_templates(client)
-    self._callbacks[client] = callback
-    self._queue.put_nowait(event)
-
-
-
-def unregister(self, client: str) ‑> NoneType -
-
-

Unregister a client from the message bus.

-
>>> async def callback(message):
-...     print(message)
->>> async def main():
-...     bus = MessageBus()
-...     bus.register('Client 1', 'Test Plugin',
-...                  [{'k1': {'type': 'string'}}],
-...                  [{'target': {'const': 'Client 1'}}],
-...                  callback)
-...     bus.unregister('Client 1')
->>> asyncio.run(main())
-
-
- -Expand source code - -
def unregister(self, client: str) -> None:
-    """Unregister a client from the message bus.
-
-    >>> async def callback(message):
-    ...     print(message)
-    >>> async def main():
-    ...     bus = MessageBus()
-    ...     bus.register('Client 1', 'Test Plugin',
-    ...                  [{'k1': {'type': 'string'}}],
-    ...                  [{'target': {'const': 'Client 1'}}],
-    ...                  callback)
-    ...     bus.unregister('Client 1')
-    >>> asyncio.run(main())
-    """
-    if client not in self._plugins:
-        return
-    event = Message('')
-    event['event'] = 'unregistered'
-    event['client'] = client
-    del self._plugins[client]
-    self._send_reg.delete(client)
-    self._recv_reg.delete(client)
-    if client in self._callbacks:
-        del self._callbacks[client]
-    self._queue.put_nowait(event)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run the message bus forever.

-
>>> async def main():
-...     bus = MessageBus()
-...     bus_task = asyncio.create_task(bus.run())
-...     bus_task.cancel()
->>> asyncio.run(main())
-
-
- -Expand source code - -
async def run(self) -> None:
-    """Run the message bus forever.
-
-    >>> async def main():
-    ...     bus = MessageBus()
-    ...     bus_task = asyncio.create_task(bus.run())
-    ...     bus_task.cancel()
-    >>> asyncio.run(main())
-    """
-    while True:
-        message = await self._queue.get()
-        if ('target' in message and
-                message['target'] == '' and
-                'command' in message and
-                message['command'] == 'get clients'):
-            for client in self._plugins:
-                answer = Message('')
-                answer['client'] = client
-                answer['plugin'] = self._plugins[client]
-                answer['sends'] = self._send_reg.get_templates(client)
-                answer['receives'] = self._recv_reg.get_templates(client)
-                await self._queue.put(answer)
-        for client in self._recv_reg.get(message):
-            await self._callbacks[client](message)
-        self._queue.task_done()
-
-
-
-async def send(self, message: Message) ‑> NoneType -
-
-

Send a message to the message bus.

-
>>> async def callback(message):
-...     print(f"Got: {message}")
->>> async def main():
-...     bus = MessageBus()
-...     bus.register('Client 1', 'Test Plugin',
-...                  [{'k1': {'type': 'string'}}],
-...                  [{'target': {'const': 'Client 1'}}],
-...                  callback)
-...     bus.register('Client 2', 'Test Plugin',
-...                  [{}],
-...                  [{'target': {'const': 'Client 2'}}],
-...                  callback)
-...     bus_task = asyncio.create_task(bus.run())
-...     await bus.send({'sender': 'Client 1', 'target': 'Client 2',
-...                     'k1': 'Test'})
-...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
-...     try:
-...         await bus.send({'sender': 'Client 1', 'target': 'Client 2',
-...                         'k1': 42})
-...     except BusException as e:
-...         print(e)
-...     await asyncio.sleep(0)
-...     bus_task.cancel()
->>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
-Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
-not allowed for sender 'Client 1'.
-Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
-Got: {'sender': 'Client 2', 'target': 'Client 1'}
-
-
- -Expand source code - -
async def send(self, message: Message) -> None:
-    """Send a message to the message bus.
-
-    >>> async def callback(message):
-    ...     print(f"Got: {message}")
-    >>> async def main():
-    ...     bus = MessageBus()
-    ...     bus.register('Client 1', 'Test Plugin',
-    ...                  [{'k1': {'type': 'string'}}],
-    ...                  [{'target': {'const': 'Client 1'}}],
-    ...                  callback)
-    ...     bus.register('Client 2', 'Test Plugin',
-    ...                  [{}],
-    ...                  [{'target': {'const': 'Client 2'}}],
-    ...                  callback)
-    ...     bus_task = asyncio.create_task(bus.run())
-    ...     await bus.send({'sender': 'Client 1', 'target': 'Client 2',
-    ...                     'k1': 'Test'})
-    ...     await bus.send({'sender': 'Client 2', 'target': 'Client 1'})
-    ...     try:
-    ...         await bus.send({'sender': 'Client 1', 'target': 'Client 2',
-    ...                         'k1': 42})
-    ...     except BusException as e:
-    ...         print(e)
-    ...     await asyncio.sleep(0)
-    ...     bus_task.cancel()
-    >>> asyncio.run(main())  # doctest: +NORMALIZE_WHITESPACE
-    Message '{'sender': 'Client 1', 'target': 'Client 2', 'k1': 42}'
-    not allowed for sender 'Client 1'.
-    Got: {'sender': 'Client 1', 'target': 'Client 2', 'k1': 'Test'}
-    Got: {'sender': 'Client 2', 'target': 'Client 1'}
-    """
-    assert isinstance(message['sender'], str)
-    sender = message['sender']
-    if sender:
-        if not self._send_reg.check(sender, message):
-            raise BusException(f"Message '{message}' not allowed for"
-                               f" sender '{sender}'.")
-    await self._queue.put(message)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/doc/controlpi/pluginregistry.html b/doc/controlpi/pluginregistry.html deleted file mode 100644 index ad9a464..0000000 --- a/doc/controlpi/pluginregistry.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - -controlpi.pluginregistry API documentation - - - - - - - - - - - -
-
-
-

Module controlpi.pluginregistry

-
-
-

Provide a generic plugin system.

-

The class PluginRegistry is initialised with the name of a namespace -package and a base class.

-

All modules in the namespace package are loaded. These modules can be -included in different distribution packages, which allows to dynamically -add plugins to the system without changing any code.

-

Afterwards, all (direct and indirect) subclasses of the base class are -registered as plugins under their class name. Class names should be unique, -which cannot be programmatically enforced.

-
>>> class BasePlugin:
-...     pass
->>> class Plugin1(BasePlugin):
-...     pass
->>> class Plugin2(BasePlugin):
-...     pass
->>> registry = PluginRegistry('importlib', BasePlugin)
-
-

The registry provides a generic mapping interface with the class names as -keys and the classes as values.

-
>>> print(len(registry))
-2
->>> for name in registry:
-...     print(f"{name}: {registry[name]}")
-Plugin1: <class 'pluginregistry.Plugin1'>
-Plugin2: <class 'pluginregistry.Plugin2'>
->>> if 'Plugin1' in registry:
-...     print(f"'Plugin1' is in registry.")
-'Plugin1' is in registry.
->>> p1 = registry['Plugin1']
->>> i1 = p1()
-
-
- -Expand source code - -
"""Provide a generic plugin system.
-
-The class PluginRegistry is initialised with the name of a namespace
-package and a base class.
-
-All modules in the namespace package are loaded. These modules can be
-included in different distribution packages, which allows to dynamically
-add plugins to the system without changing any code.
-
-Afterwards, all (direct and indirect) subclasses of the base class are
-registered as plugins under their class name. Class names should be unique,
-which cannot be programmatically enforced.
-
->>> class BasePlugin:
-...     pass
->>> class Plugin1(BasePlugin):
-...     pass
->>> class Plugin2(BasePlugin):
-...     pass
->>> registry = PluginRegistry('importlib', BasePlugin)
-
-The registry provides a generic mapping interface with the class names as
-keys and the classes as values.
-
->>> print(len(registry))
-2
->>> for name in registry:
-...     print(f"{name}: {registry[name]}")
-Plugin1: <class 'pluginregistry.Plugin1'>
-Plugin2: <class 'pluginregistry.Plugin2'>
->>> if 'Plugin1' in registry:
-...     print(f"'Plugin1' is in registry.")
-'Plugin1' is in registry.
->>> p1 = registry['Plugin1']
->>> i1 = p1()
-"""
-import importlib
-import pkgutil
-import collections.abc
-
-
-class PluginRegistry(collections.abc.Mapping):
-    """Provide a registry for plugins.
-
-    Initialise the registry by loading all modules in the given namespace
-    package and then registering all subclasses of the given base class as
-    plugins (only simulated here – the code for Plugin1 and Plugin2 should
-    be in modules in the given namespace package in real applications):
-    >>> class BasePlugin:
-    ...     pass
-    >>> class Plugin1(BasePlugin):
-    ...     pass
-    >>> class Plugin2(BasePlugin):
-    ...     pass
-    >>> registry = PluginRegistry('importlib', BasePlugin)
-
-    After initialisation, provide a mapping interface to the plugins:
-    >>> print(len(registry))
-    2
-    >>> for name in registry:
-    ...     print(f"{name}: {registry[name]}")
-    Plugin1: <class 'pluginregistry.Plugin1'>
-    Plugin2: <class 'pluginregistry.Plugin2'>
-    >>> if 'Plugin1' in registry:
-    ...     print(f"'Plugin1' is in registry.")
-    'Plugin1' is in registry.
-    """
-
-    def __init__(self, namespace_package: str, base_class: type) -> None:
-        """Initialise registry.
-
-        Import all modules defined in the given namespace package (in any
-        distribution package currently installed in the path). Then register
-        all subclasses of the given base class as plugins.
-
-        >>> class BasePlugin:
-        ...     pass
-        >>> class Plugin1(BasePlugin):
-        ...     pass
-        >>> class Plugin2(BasePlugin):
-        ...     pass
-        >>> registry = PluginRegistry('importlib', BasePlugin)
-        >>> for name in registry._plugins:
-        ...     print(f"{name}: {registry._plugins[name]}")
-        Plugin1: <class 'pluginregistry.Plugin1'>
-        Plugin2: <class 'pluginregistry.Plugin2'>
-        """
-        ns_mod = importlib.import_module(namespace_package)
-        ns_path = ns_mod.__path__  # type: ignore  # mypy issue #1422
-        ns_name = ns_mod.__name__
-        for _, mod_name, _ in pkgutil.iter_modules(ns_path):
-            importlib.import_module(f"{ns_name}.{mod_name}")
-
-        def all_subclasses(cls):
-            result = []
-            for subcls in cls.__subclasses__():
-                result.append(subcls)
-                result.extend(all_subclasses(subcls))
-            return result
-        self._plugins = {cls.__name__: cls
-                         for cls in all_subclasses(base_class)}
-
-    def __len__(self) -> int:
-        """Get number of registered plugins.
-
-        >>> class BasePlugin:
-        ...     pass
-        >>> class Plugin1(BasePlugin):
-        ...     pass
-        >>> class Plugin2(BasePlugin):
-        ...     pass
-        >>> registry = PluginRegistry('importlib', BasePlugin)
-        >>> print(registry.__len__())
-        2
-        """
-        return len(self._plugins)
-
-    def __iter__(self):
-        """Get an iterator of the registered plugins.
-
-        >>> class BasePlugin:
-        ...     pass
-        >>> class Plugin1(BasePlugin):
-        ...     pass
-        >>> class Plugin2(BasePlugin):
-        ...     pass
-        >>> registry = PluginRegistry('importlib', BasePlugin)
-        >>> print(type(registry.__iter__()))
-        <class 'dict_keyiterator'>
-        >>> for name in registry:
-        ...     print(name)
-        Plugin1
-        Plugin2
-        """
-        return iter(self._plugins)
-
-    def __getitem__(self, plugin_name: str) -> type:
-        """Get a registered plugin given its name.
-
-        >>> class BasePlugin:
-        ...     pass
-        >>> class Plugin1(BasePlugin):
-        ...     pass
-        >>> class Plugin2(BasePlugin):
-        ...     pass
-        >>> registry = PluginRegistry('importlib', BasePlugin)
-        >>> print(registry.__getitem__('Plugin1'))
-        <class 'pluginregistry.Plugin1'>
-        >>> print(registry.__getitem__('Plugin2'))
-        <class 'pluginregistry.Plugin2'>
-        >>> for name in registry:
-        ...     print(registry[name])
-        <class 'pluginregistry.Plugin1'>
-        <class 'pluginregistry.Plugin2'>
-        """
-        return self._plugins[plugin_name]
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class PluginRegistry -(namespace_package: str, base_class: type) -
-
-

Provide a registry for plugins.

-

Initialise the registry by loading all modules in the given namespace -package and then registering all subclasses of the given base class as -plugins (only simulated here – the code for Plugin1 and Plugin2 should -be in modules in the given namespace package in real applications):

-
>>> class BasePlugin:
-...     pass
->>> class Plugin1(BasePlugin):
-...     pass
->>> class Plugin2(BasePlugin):
-...     pass
->>> registry = PluginRegistry('importlib', BasePlugin)
-
-

After initialisation, provide a mapping interface to the plugins:

-
>>> print(len(registry))
-2
->>> for name in registry:
-...     print(f"{name}: {registry[name]}")
-Plugin1: <class 'pluginregistry.Plugin1'>
-Plugin2: <class 'pluginregistry.Plugin2'>
->>> if 'Plugin1' in registry:
-...     print(f"'Plugin1' is in registry.")
-'Plugin1' is in registry.
-
-

Initialise registry.

-

Import all modules defined in the given namespace package (in any -distribution package currently installed in the path). Then register -all subclasses of the given base class as plugins.

-
>>> class BasePlugin:
-...     pass
->>> class Plugin1(BasePlugin):
-...     pass
->>> class Plugin2(BasePlugin):
-...     pass
->>> registry = PluginRegistry('importlib', BasePlugin)
->>> for name in registry._plugins:
-...     print(f"{name}: {registry._plugins[name]}")
-Plugin1: <class 'pluginregistry.Plugin1'>
-Plugin2: <class 'pluginregistry.Plugin2'>
-
-
- -Expand source code - -
class PluginRegistry(collections.abc.Mapping):
-    """Provide a registry for plugins.
-
-    Initialise the registry by loading all modules in the given namespace
-    package and then registering all subclasses of the given base class as
-    plugins (only simulated here – the code for Plugin1 and Plugin2 should
-    be in modules in the given namespace package in real applications):
-    >>> class BasePlugin:
-    ...     pass
-    >>> class Plugin1(BasePlugin):
-    ...     pass
-    >>> class Plugin2(BasePlugin):
-    ...     pass
-    >>> registry = PluginRegistry('importlib', BasePlugin)
-
-    After initialisation, provide a mapping interface to the plugins:
-    >>> print(len(registry))
-    2
-    >>> for name in registry:
-    ...     print(f"{name}: {registry[name]}")
-    Plugin1: <class 'pluginregistry.Plugin1'>
-    Plugin2: <class 'pluginregistry.Plugin2'>
-    >>> if 'Plugin1' in registry:
-    ...     print(f"'Plugin1' is in registry.")
-    'Plugin1' is in registry.
-    """
-
-    def __init__(self, namespace_package: str, base_class: type) -> None:
-        """Initialise registry.
-
-        Import all modules defined in the given namespace package (in any
-        distribution package currently installed in the path). Then register
-        all subclasses of the given base class as plugins.
-
-        >>> class BasePlugin:
-        ...     pass
-        >>> class Plugin1(BasePlugin):
-        ...     pass
-        >>> class Plugin2(BasePlugin):
-        ...     pass
-        >>> registry = PluginRegistry('importlib', BasePlugin)
-        >>> for name in registry._plugins:
-        ...     print(f"{name}: {registry._plugins[name]}")
-        Plugin1: <class 'pluginregistry.Plugin1'>
-        Plugin2: <class 'pluginregistry.Plugin2'>
-        """
-        ns_mod = importlib.import_module(namespace_package)
-        ns_path = ns_mod.__path__  # type: ignore  # mypy issue #1422
-        ns_name = ns_mod.__name__
-        for _, mod_name, _ in pkgutil.iter_modules(ns_path):
-            importlib.import_module(f"{ns_name}.{mod_name}")
-
-        def all_subclasses(cls):
-            result = []
-            for subcls in cls.__subclasses__():
-                result.append(subcls)
-                result.extend(all_subclasses(subcls))
-            return result
-        self._plugins = {cls.__name__: cls
-                         for cls in all_subclasses(base_class)}
-
-    def __len__(self) -> int:
-        """Get number of registered plugins.
-
-        >>> class BasePlugin:
-        ...     pass
-        >>> class Plugin1(BasePlugin):
-        ...     pass
-        >>> class Plugin2(BasePlugin):
-        ...     pass
-        >>> registry = PluginRegistry('importlib', BasePlugin)
-        >>> print(registry.__len__())
-        2
-        """
-        return len(self._plugins)
-
-    def __iter__(self):
-        """Get an iterator of the registered plugins.
-
-        >>> class BasePlugin:
-        ...     pass
-        >>> class Plugin1(BasePlugin):
-        ...     pass
-        >>> class Plugin2(BasePlugin):
-        ...     pass
-        >>> registry = PluginRegistry('importlib', BasePlugin)
-        >>> print(type(registry.__iter__()))
-        <class 'dict_keyiterator'>
-        >>> for name in registry:
-        ...     print(name)
-        Plugin1
-        Plugin2
-        """
-        return iter(self._plugins)
-
-    def __getitem__(self, plugin_name: str) -> type:
-        """Get a registered plugin given its name.
-
-        >>> class BasePlugin:
-        ...     pass
-        >>> class Plugin1(BasePlugin):
-        ...     pass
-        >>> class Plugin2(BasePlugin):
-        ...     pass
-        >>> registry = PluginRegistry('importlib', BasePlugin)
-        >>> print(registry.__getitem__('Plugin1'))
-        <class 'pluginregistry.Plugin1'>
-        >>> print(registry.__getitem__('Plugin2'))
-        <class 'pluginregistry.Plugin2'>
-        >>> for name in registry:
-        ...     print(registry[name])
-        <class 'pluginregistry.Plugin1'>
-        <class 'pluginregistry.Plugin2'>
-        """
-        return self._plugins[plugin_name]
-
-

Ancestors

-
    -
  • collections.abc.Mapping
  • -
  • collections.abc.Collection
  • -
  • collections.abc.Sized
  • -
  • collections.abc.Iterable
  • -
  • collections.abc.Container
  • -
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/doc/controlpi_plugins/index.html b/doc/controlpi_plugins/index.html deleted file mode 100644 index 6775c5d..0000000 --- a/doc/controlpi_plugins/index.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - -controlpi_plugins API documentation - - - - - - - - - - - -
-
-
-

Namespace controlpi_plugins

-
-
-
-
-

Sub-modules

-
-
controlpi_plugins.state
-
-

Provide state plugins for all kinds of systems …

-
-
controlpi_plugins.util
-
-

Provide utility plugins for all kinds of systems …

-
-
controlpi_plugins.wait
-
-

Provide waiting/sleeping plugins for all kinds of systems …

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/doc/controlpi_plugins/state.html b/doc/controlpi_plugins/state.html deleted file mode 100644 index 8f33480..0000000 --- a/doc/controlpi_plugins/state.html +++ /dev/null @@ -1,1884 +0,0 @@ - - - - - - -controlpi_plugins.state API documentation - - - - - - - - - - - -
-
-
-

Module controlpi_plugins.state

-
-
-

Provide state plugins for all kinds of systems.

-
    -
  • State represents a Boolean state.
  • -
  • StateAlias translates to another state-like client.
  • -
  • AndState combines several state-like clients by conjunction.
  • -
  • OrState combines several state-like clients by disjunction.
  • -
-

All these plugins use the following conventions:

-
    -
  • If their state changes they send a message containing "event": "changed" -and "state": .
  • -
  • If their state is reported due to a message, but did not change they send -a message containing just "state": .
  • -
  • If they receive a message containing "target": and -"command": "get state" they report their current state.
  • -
  • If State (or any other settable state using these conventions) receives -a message containing "target": , "command": "set state" and -"new state": it changes the state accordingly. If this -was really a change the corresponding event is sent. If it was already in -this state a report message without "event": "changed" is sent.
  • -
  • StateAlias can alias any message bus client using these conventions, not -just State instances. It translates all messages described here in both -directions.
  • -
  • AndState and OrState instances cannot be set.
  • -
  • AndState and OrState can combine any message bus clients using these -conventions, not just State instances. They only react to messages -containing "state" information.
  • -
-
>>> import asyncio
->>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test State": {"plugin": "State"},
-...      "Test State 2": {"plugin": "State"},
-...      "Test StateAlias": {"plugin": "StateAlias",
-...                          "alias for": "Test State 2"},
-...      "Test AndState": {"plugin": "AndState",
-...                        "states": ["Test State", "Test StateAlias"]},
-...      "Test OrState": {"plugin": "OrState",
-...                       "states": ["Test State", "Test StateAlias"]}},
-...     [{"target": "Test AndState",
-...       "command": "get state"},
-...      {"target": "Test OrState",
-...       "command": "get state"},
-...      {"target": "Test State",
-...       "command": "set state", "new state": True},
-...      {"target": "Test StateAlias",
-...       "command": "set state", "new state": True},
-...      {"target": "Test State",
-...       "command": "set state", "new state": False}]))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test State', 'plugin': 'State',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test State'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test State'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test State 2', 'plugin': 'State',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test State 2'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test State 2'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test StateAlias', 'plugin': 'StateAlias',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}},
-                   {'target': {'const': 'Test State 2'},
-                    'command': {'const': 'get state'}},
-                   {'target': {'const': 'Test State 2'},
-                    'command': {'const': 'set state'},
-                    'new state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test StateAlias'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test StateAlias'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test State 2'},
-                       'event': {'const': 'changed'},
-                       'state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test State 2'},
-                       'state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test AndState', 'plugin': 'AndState',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test AndState'},
-                       'command': {'const': 'get state'}},
-                      {'sender': {'const': 'Test State'},
-                       'state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test StateAlias'},
-                       'state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test OrState', 'plugin': 'OrState',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test OrState'},
-                       'command': {'const': 'get state'}},
-                      {'sender': {'const': 'Test State'},
-                       'state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test StateAlias'},
-                       'state': {'type': 'boolean'}}]}
-test(): {'sender': 'test()', 'target': 'Test AndState',
-         'command': 'get state'}
-test(): {'sender': 'Test AndState', 'state': False}
-test(): {'sender': 'test()', 'target': 'Test OrState',
-         'command': 'get state'}
-test(): {'sender': 'Test OrState', 'state': False}
-test(): {'sender': 'test()', 'target': 'Test State',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
-test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test StateAlias',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test StateAlias', 'target': 'Test State 2',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
-test(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True}
-test(): {'sender': 'Test AndState', 'event': 'changed', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test State',
-         'command': 'set state', 'new state': False}
-test(): {'sender': 'Test State', 'event': 'changed', 'state': False}
-test(): {'sender': 'Test AndState', 'event': 'changed', 'state': False}
-
-
- -Expand source code - -
"""Provide state plugins for all kinds of systems.
-
-- State represents a Boolean state.
-- StateAlias translates to another state-like client.
-- AndState combines several state-like clients by conjunction.
-- OrState combines several state-like clients by disjunction.
-
-All these plugins use the following conventions:
-
-- If their state changes they send a message containing "event": "changed"
-  and "state": <new state>.
-- If their state is reported due to a message, but did not change they send
-  a message containing just "state": <current state>.
-- If they receive a message containing "target": <name> and
-  "command": "get state" they report their current state.
-- If State (or any other settable state using these conventions) receives
-  a message containing "target": <name>, "command": "set state" and
-  "new state": <state to be set> it changes the state accordingly. If this
-  was really a change the corresponding event is sent. If it was already in
-  this state a report message without "event": "changed" is sent.
-- StateAlias can alias any message bus client using these conventions, not
-  just State instances. It translates all messages described here in both
-  directions.
-- AndState and OrState instances cannot be set.
-- AndState and OrState can combine any message bus clients using these
-  conventions, not just State instances. They only react to messages
-  containing "state" information.
-
->>> import asyncio
->>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test State": {"plugin": "State"},
-...      "Test State 2": {"plugin": "State"},
-...      "Test StateAlias": {"plugin": "StateAlias",
-...                          "alias for": "Test State 2"},
-...      "Test AndState": {"plugin": "AndState",
-...                        "states": ["Test State", "Test StateAlias"]},
-...      "Test OrState": {"plugin": "OrState",
-...                       "states": ["Test State", "Test StateAlias"]}},
-...     [{"target": "Test AndState",
-...       "command": "get state"},
-...      {"target": "Test OrState",
-...       "command": "get state"},
-...      {"target": "Test State",
-...       "command": "set state", "new state": True},
-...      {"target": "Test StateAlias",
-...       "command": "set state", "new state": True},
-...      {"target": "Test State",
-...       "command": "set state", "new state": False}]))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test State', 'plugin': 'State',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test State'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test State'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test State 2', 'plugin': 'State',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test State 2'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test State 2'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test StateAlias', 'plugin': 'StateAlias',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}},
-                   {'target': {'const': 'Test State 2'},
-                    'command': {'const': 'get state'}},
-                   {'target': {'const': 'Test State 2'},
-                    'command': {'const': 'set state'},
-                    'new state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test StateAlias'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test StateAlias'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test State 2'},
-                       'event': {'const': 'changed'},
-                       'state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test State 2'},
-                       'state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test AndState', 'plugin': 'AndState',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test AndState'},
-                       'command': {'const': 'get state'}},
-                      {'sender': {'const': 'Test State'},
-                       'state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test StateAlias'},
-                       'state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test OrState', 'plugin': 'OrState',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test OrState'},
-                       'command': {'const': 'get state'}},
-                      {'sender': {'const': 'Test State'},
-                       'state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test StateAlias'},
-                       'state': {'type': 'boolean'}}]}
-test(): {'sender': 'test()', 'target': 'Test AndState',
-         'command': 'get state'}
-test(): {'sender': 'Test AndState', 'state': False}
-test(): {'sender': 'test()', 'target': 'Test OrState',
-         'command': 'get state'}
-test(): {'sender': 'Test OrState', 'state': False}
-test(): {'sender': 'test()', 'target': 'Test State',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
-test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test StateAlias',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test StateAlias', 'target': 'Test State 2',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
-test(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True}
-test(): {'sender': 'Test AndState', 'event': 'changed', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test State',
-         'command': 'set state', 'new state': False}
-test(): {'sender': 'Test State', 'event': 'changed', 'state': False}
-test(): {'sender': 'Test AndState', 'event': 'changed', 'state': False}
-"""
-from controlpi import BasePlugin, Message, MessageTemplate
-
-from typing import Dict
-
-
-class State(BasePlugin):
-    """Provide a Boolean state.
-
-    The state of a State plugin instance can be queried with the "get state"
-    command and set with the "set state" command to the new state given by
-    the "new state" key:
-    >>> import asyncio
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test State": {"plugin": "State"}},
-    ...     [{"target": "Test State", "command": "get state"},
-    ...      {"target": "Test State", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State", "command": "get state"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'get state'}
-    test(): {'sender': 'Test State', 'state': False}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'get state'}
-    test(): {'sender': 'Test State', 'state': True}
-    """
-
-    CONF_SCHEMA = True
-    """Schema for State plugin configuration.
-
-    There are no required or optional configuration keys.
-    """
-
-    async def receive(self, message: Message) -> None:
-        """Process commands to get/set state."""
-        if message['command'] == 'get state':
-            await self.bus.send(Message(self.name, {'state': self.state}))
-        elif message['command'] == 'set state':
-            if self.state != message['new state']:
-                assert isinstance(message['new state'], bool)
-                self.state: bool = message['new state']
-                await self.bus.send(Message(self.name,
-                                            {'event': 'changed',
-                                             'state': self.state}))
-            else:
-                await self.bus.send(Message(self.name,
-                                            {'state': self.state}))
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        self.state = False
-        sends = [MessageTemplate({'event': {'const': 'changed'},
-                                  'state': {'type': 'boolean'}}),
-                 MessageTemplate({'state': {'type': 'boolean'}})]
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'get state'}}),
-                    MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'set state'},
-                                     'new state': {'type': 'boolean'}})]
-        self.bus.register(self.name, 'State', sends, receives, self.receive)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-
-class StateAlias(BasePlugin):
-    """Define an alias for another state.
-
-    The "alias for" configuration key gets the name for the other state that
-    is aliased by the StateAlias plugin instance.
-
-    The "get state" and "set state" commands are forwarded to and the
-    "changed" events and "state" messages are forwarded from this other
-    state:
-    >>> import asyncio
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test State": {"plugin": "State"},
-    ...      "Test StateAlias": {"plugin": "StateAlias",
-    ...                          "alias for": "Test State"}},
-    ...     [{"target": "Test State", "command": "get state"},
-    ...      {"target": "Test StateAlias", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test StateAlias", "command": "get state"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test StateAlias', 'plugin': 'StateAlias',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}},
-                       {'target': {'const': 'Test State'},
-                        'command': {'const': 'get state'}},
-                       {'target': {'const': 'Test State'},
-                        'command': {'const': 'set state'},
-                        'new state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test StateAlias'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test StateAlias'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}},
-                          {'sender': {'const': 'Test State'},
-                           'event': {'const': 'changed'},
-                           'state': {'type': 'boolean'}},
-                          {'sender': {'const': 'Test State'},
-                           'state': {'type': 'boolean'}}]}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'get state'}
-    test(): {'sender': 'Test State', 'state': False}
-    test(): {'sender': 'Test StateAlias', 'state': False}
-    test(): {'sender': 'test()', 'target': 'Test StateAlias',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test StateAlias', 'target': 'Test State',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
-    test(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State', 'state': True}
-    test(): {'sender': 'Test StateAlias', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test StateAlias',
-             'command': 'get state'}
-    test(): {'sender': 'Test StateAlias', 'target': 'Test State',
-             'command': 'get state'}
-    test(): {'sender': 'Test State', 'state': True}
-    test(): {'sender': 'Test StateAlias', 'state': True}
-    """
-
-    CONF_SCHEMA = {'properties': {'alias for': {'type': 'string'}},
-                   'required': ['alias for']}
-    """Schema for StateAlias plugin configuration.
-
-    Required configuration key:
-
-    - 'alias for': name of aliased state.
-    """
-
-    async def receive(self, message: Message) -> None:
-        """Translate states from and commands to aliased state."""
-        alias_message = Message(self.name)
-        if ('target' in message and message['target'] == self.name and
-                'command' in message):
-            alias_message['target'] = self.conf['alias for']
-            if message['command'] == 'get state':
-                alias_message['command'] = 'get state'
-                await self.bus.send(alias_message)
-            elif (message['command'] == 'set state' and
-                    'new state' in message):
-                alias_message['command'] = 'set state'
-                alias_message['new state'] = message['new state']
-                await self.bus.send(alias_message)
-        if (message['sender'] == self.conf['alias for'] and
-                'state' in message):
-            if 'event' in message and message['event'] == 'changed':
-                alias_message['event'] = 'changed'
-            alias_message['state'] = message['state']
-            await self.bus.send(alias_message)
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        sends = [MessageTemplate({'target': {'const': self.conf['alias for']},
-                                  'command': {'const': 'get state'}}),
-                 MessageTemplate({'target': {'const': self.conf['alias for']},
-                                  'command': {'const': 'set state'},
-                                  'new state': {'type': 'boolean'}}),
-                 MessageTemplate({'event': {'const': 'changed'},
-                                  'state': {'type': 'boolean'}}),
-                 MessageTemplate({'state': {'type': 'boolean'}})]
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'get state'}}),
-                    MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'set state'},
-                                     'new state': {'type': 'boolean'}}),
-                    MessageTemplate({'sender':
-                                     {'const': self.conf['alias for']},
-                                     'event': {'const': 'changed'},
-                                     'state': {'type': 'boolean'}}),
-                    MessageTemplate({'sender':
-                                     {'const': self.conf['alias for']},
-                                     'state': {'type': 'boolean'}})]
-        self.bus.register(self.name, 'StateAlias',
-                          sends, receives, self.receive)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-
-class AndState(BasePlugin):
-    """Implement conjunction of states.
-
-    The "states" configuration key gets an array of states to be combined.
-    An AndState plugin client reacts to "get state" commands and sends
-    "changed" events when a change in one of the combined states leads to
-    a change for the conjunction:
-    >>> import asyncio
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test State 1": {"plugin": "State"},
-    ...      "Test State 2": {"plugin": "State"},
-    ...      "Test AndState": {"plugin": "AndState",
-    ...                        "states": ["Test State 1", "Test State 2"]}},
-    ...     [{"target": "Test State 1", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State 2", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State 1", "command": "set state",
-    ...       "new state": False},
-    ...      {"target": "Test AndState", "command": "get state"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State 1', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State 1'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State 1'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State 2', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State 2'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State 2'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test AndState', 'plugin': 'AndState',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test AndState'},
-                           'command': {'const': 'get state'}},
-                          {'sender': {'const': 'Test State 1'},
-                           'state': {'type': 'boolean'}},
-                          {'sender': {'const': 'Test State 2'},
-                           'state': {'type': 'boolean'}}]}
-    test(): {'sender': 'test()', 'target': 'Test State 1',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State 2',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
-    test(): {'sender': 'Test AndState', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State 1',
-             'command': 'set state', 'new state': False}
-    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
-    test(): {'sender': 'Test AndState', 'event': 'changed', 'state': False}
-    test(): {'sender': 'test()', 'target': 'Test AndState',
-             'command': 'get state'}
-    test(): {'sender': 'Test AndState', 'state': False}
-    """
-
-    CONF_SCHEMA = {'properties': {'states': {'type': 'array',
-                                             'items': {'type': 'string'}}},
-                   'required': ['states']}
-    """Schema for AndState plugin configuration.
-
-    Required configuration key:
-
-    - 'states': list of names of combined states.
-    """
-
-    async def receive(self, message: Message) -> None:
-        """Process "get state" command and messages of combined states."""
-        if ('target' in message and message['target'] == self.name and
-                'command' in message and message['command'] == 'get state'):
-            await self.bus.send(Message(self.name, {'state': self.state}))
-        if 'state' in message and message['sender'] in self.conf['states']:
-            assert isinstance(message['sender'], str)
-            assert isinstance(message['state'], bool)
-            self.states[message['sender']] = message['state']
-            new_state = all(self.states.values())
-            if self.state != new_state:
-                self.state: bool = new_state
-                await self.bus.send(Message(self.name,
-                                            {'event': 'changed',
-                                             'state': self.state}))
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        sends = [MessageTemplate({'event': {'const': 'changed'},
-                                  'state': {'type': 'boolean'}}),
-                 MessageTemplate({'state': {'type': 'boolean'}})]
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'get state'}})]
-        self.states: Dict[str, bool] = {}
-        for state in self.conf['states']:
-            receives.append(MessageTemplate({'sender': {'const': state},
-                                             'state': {'type': 'boolean'}}))
-            self.states[state] = False
-        self.state = all(self.states.values())
-        self.bus.register(self.name, 'AndState',
-                          sends, receives, self.receive)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-
-class OrState(BasePlugin):
-    """Implement disjunction of states.
-
-    The "states" configuration key gets an array of states to be combined.
-    An OrState plugin client reacts to "get state" commands and sends
-    "changed" events when a change in one of the combined states leads to
-    a change for the disjunction:
-    >>> import asyncio
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test State 1": {"plugin": "State"},
-    ...      "Test State 2": {"plugin": "State"},
-    ...      "Test OrState": {"plugin": "OrState",
-    ...                       "states": ["Test State 1", "Test State 2"]}},
-    ...     [{"target": "Test State 1", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State 2", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State 1", "command": "set state",
-    ...       "new state": False},
-    ...      {"target": "Test OrState", "command": "get state"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State 1', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State 1'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State 1'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State 2', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State 2'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State 2'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test OrState', 'plugin': 'OrState',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test OrState'},
-                           'command': {'const': 'get state'}},
-                          {'sender': {'const': 'Test State 1'},
-                           'state': {'type': 'boolean'}},
-                          {'sender': {'const': 'Test State 2'},
-                           'state': {'type': 'boolean'}}]}
-    test(): {'sender': 'test()', 'target': 'Test State 1',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
-    test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State 2',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State 1',
-             'command': 'set state', 'new state': False}
-    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
-    test(): {'sender': 'test()', 'target': 'Test OrState',
-             'command': 'get state'}
-    test(): {'sender': 'Test OrState', 'state': True}
-    """
-
-    CONF_SCHEMA = {'properties': {'states': {'type': 'array',
-                                             'items': {'type': 'string'}}},
-                   'required': ['states']}
-    """Schema for OrState plugin configuration.
-
-    Required configuration key:
-
-    - 'states': list of names of combined states.
-    """
-
-    async def receive(self, message: Message) -> None:
-        """Process "get state" command and messages of combined states."""
-        if ('target' in message and message['target'] == self.name and
-                'command' in message and message['command'] == 'get state'):
-            await self.bus.send(Message(self.name, {'state': self.state}))
-        if 'state' in message and message['sender'] in self.conf['states']:
-            assert isinstance(message['sender'], str)
-            assert isinstance(message['state'], bool)
-            self.states[message['sender']] = message['state']
-            new_state = any(self.states.values())
-            if self.state != new_state:
-                self.state: bool = new_state
-                await self.bus.send(Message(self.name,
-                                            {'event': 'changed',
-                                             'state': self.state}))
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        sends = [MessageTemplate({'event': {'const': 'changed'},
-                                  'state': {'type': 'boolean'}}),
-                 MessageTemplate({'state': {'type': 'boolean'}})]
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'get state'}})]
-        self.states: Dict[str, bool] = {}
-        for state in self.conf['states']:
-            receives.append(MessageTemplate({'sender': {'const': state},
-                                             'state': {'type': 'boolean'}}))
-            self.states[state] = False
-        self.state = any(self.states.values())
-        self.bus.register(self.name, 'OrState',
-                          sends, receives, self.receive)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class State -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

Provide a Boolean state.

-

The state of a State plugin instance can be queried with the "get state" -command and set with the "set state" command to the new state given by -the "new state" key:

-
>>> import asyncio
->>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test State": {"plugin": "State"}},
-...     [{"target": "Test State", "command": "get state"},
-...      {"target": "Test State", "command": "set state",
-...       "new state": True},
-...      {"target": "Test State", "command": "set state",
-...       "new state": True},
-...      {"target": "Test State", "command": "get state"}]))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test State', 'plugin': 'State',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test State'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test State'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}}]}
-test(): {'sender': 'test()', 'target': 'Test State',
-         'command': 'get state'}
-test(): {'sender': 'Test State', 'state': False}
-test(): {'sender': 'test()', 'target': 'Test State',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test State',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test State',
-         'command': 'get state'}
-test(): {'sender': 'Test State', 'state': True}
-
-
- -Expand source code - -
class State(BasePlugin):
-    """Provide a Boolean state.
-
-    The state of a State plugin instance can be queried with the "get state"
-    command and set with the "set state" command to the new state given by
-    the "new state" key:
-    >>> import asyncio
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test State": {"plugin": "State"}},
-    ...     [{"target": "Test State", "command": "get state"},
-    ...      {"target": "Test State", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State", "command": "get state"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'get state'}
-    test(): {'sender': 'Test State', 'state': False}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'get state'}
-    test(): {'sender': 'Test State', 'state': True}
-    """
-
-    CONF_SCHEMA = True
-    """Schema for State plugin configuration.
-
-    There are no required or optional configuration keys.
-    """
-
-    async def receive(self, message: Message) -> None:
-        """Process commands to get/set state."""
-        if message['command'] == 'get state':
-            await self.bus.send(Message(self.name, {'state': self.state}))
-        elif message['command'] == 'set state':
-            if self.state != message['new state']:
-                assert isinstance(message['new state'], bool)
-                self.state: bool = message['new state']
-                await self.bus.send(Message(self.name,
-                                            {'event': 'changed',
-                                             'state': self.state}))
-            else:
-                await self.bus.send(Message(self.name,
-                                            {'state': self.state}))
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        self.state = False
-        sends = [MessageTemplate({'event': {'const': 'changed'},
-                                  'state': {'type': 'boolean'}}),
-                 MessageTemplate({'state': {'type': 'boolean'}})]
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'get state'}}),
-                    MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'set state'},
-                                     'new state': {'type': 'boolean'}})]
-        self.bus.register(self.name, 'State', sends, receives, self.receive)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-

Ancestors

- -

Class variables

-
-
var CONF_SCHEMA
-
-

Schema for State plugin configuration.

-

There are no required or optional configuration keys.

-
-
-

Methods

-
-
-async def receive(self, message: Message) ‑> NoneType -
-
-

Process commands to get/set state.

-
- -Expand source code - -
async def receive(self, message: Message) -> None:
-    """Process commands to get/set state."""
-    if message['command'] == 'get state':
-        await self.bus.send(Message(self.name, {'state': self.state}))
-    elif message['command'] == 'set state':
-        if self.state != message['new state']:
-            assert isinstance(message['new state'], bool)
-            self.state: bool = message['new state']
-            await self.bus.send(Message(self.name,
-                                        {'event': 'changed',
-                                         'state': self.state}))
-        else:
-            await self.bus.send(Message(self.name,
-                                        {'state': self.state}))
-
-
-
-def process_conf(self) ‑> NoneType -
-
-

Register plugin as bus client.

-
- -Expand source code - -
def process_conf(self) -> None:
-    """Register plugin as bus client."""
-    self.state = False
-    sends = [MessageTemplate({'event': {'const': 'changed'},
-                              'state': {'type': 'boolean'}}),
-             MessageTemplate({'state': {'type': 'boolean'}})]
-    receives = [MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'get state'}}),
-                MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'set state'},
-                                 'new state': {'type': 'boolean'}})]
-    self.bus.register(self.name, 'State', sends, receives, self.receive)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run no code proactively.

-
- -Expand source code - -
async def run(self) -> None:
-    """Run no code proactively."""
-    pass
-
-
-
-
-
-class StateAlias -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

Define an alias for another state.

-

The "alias for" configuration key gets the name for the other state that -is aliased by the StateAlias plugin instance.

-

The "get state" and "set state" commands are forwarded to and the -"changed" events and "state" messages are forwarded from this other -state:

-
>>> import asyncio
->>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test State": {"plugin": "State"},
-...      "Test StateAlias": {"plugin": "StateAlias",
-...                          "alias for": "Test State"}},
-...     [{"target": "Test State", "command": "get state"},
-...      {"target": "Test StateAlias", "command": "set state",
-...       "new state": True},
-...      {"target": "Test State", "command": "set state",
-...       "new state": True},
-...      {"target": "Test StateAlias", "command": "get state"}]))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test State', 'plugin': 'State',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test State'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test State'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test StateAlias', 'plugin': 'StateAlias',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}},
-                   {'target': {'const': 'Test State'},
-                    'command': {'const': 'get state'}},
-                   {'target': {'const': 'Test State'},
-                    'command': {'const': 'set state'},
-                    'new state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test StateAlias'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test StateAlias'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test State'},
-                       'event': {'const': 'changed'},
-                       'state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test State'},
-                       'state': {'type': 'boolean'}}]}
-test(): {'sender': 'test()', 'target': 'Test State',
-         'command': 'get state'}
-test(): {'sender': 'Test State', 'state': False}
-test(): {'sender': 'Test StateAlias', 'state': False}
-test(): {'sender': 'test()', 'target': 'Test StateAlias',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test StateAlias', 'target': 'Test State',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
-test(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test State',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State', 'state': True}
-test(): {'sender': 'Test StateAlias', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test StateAlias',
-         'command': 'get state'}
-test(): {'sender': 'Test StateAlias', 'target': 'Test State',
-         'command': 'get state'}
-test(): {'sender': 'Test State', 'state': True}
-test(): {'sender': 'Test StateAlias', 'state': True}
-
-
- -Expand source code - -
class StateAlias(BasePlugin):
-    """Define an alias for another state.
-
-    The "alias for" configuration key gets the name for the other state that
-    is aliased by the StateAlias plugin instance.
-
-    The "get state" and "set state" commands are forwarded to and the
-    "changed" events and "state" messages are forwarded from this other
-    state:
-    >>> import asyncio
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test State": {"plugin": "State"},
-    ...      "Test StateAlias": {"plugin": "StateAlias",
-    ...                          "alias for": "Test State"}},
-    ...     [{"target": "Test State", "command": "get state"},
-    ...      {"target": "Test StateAlias", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test StateAlias", "command": "get state"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test StateAlias', 'plugin': 'StateAlias',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}},
-                       {'target': {'const': 'Test State'},
-                        'command': {'const': 'get state'}},
-                       {'target': {'const': 'Test State'},
-                        'command': {'const': 'set state'},
-                        'new state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test StateAlias'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test StateAlias'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}},
-                          {'sender': {'const': 'Test State'},
-                           'event': {'const': 'changed'},
-                           'state': {'type': 'boolean'}},
-                          {'sender': {'const': 'Test State'},
-                           'state': {'type': 'boolean'}}]}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'get state'}
-    test(): {'sender': 'Test State', 'state': False}
-    test(): {'sender': 'Test StateAlias', 'state': False}
-    test(): {'sender': 'test()', 'target': 'Test StateAlias',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test StateAlias', 'target': 'Test State',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State', 'event': 'changed', 'state': True}
-    test(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State', 'state': True}
-    test(): {'sender': 'Test StateAlias', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test StateAlias',
-             'command': 'get state'}
-    test(): {'sender': 'Test StateAlias', 'target': 'Test State',
-             'command': 'get state'}
-    test(): {'sender': 'Test State', 'state': True}
-    test(): {'sender': 'Test StateAlias', 'state': True}
-    """
-
-    CONF_SCHEMA = {'properties': {'alias for': {'type': 'string'}},
-                   'required': ['alias for']}
-    """Schema for StateAlias plugin configuration.
-
-    Required configuration key:
-
-    - 'alias for': name of aliased state.
-    """
-
-    async def receive(self, message: Message) -> None:
-        """Translate states from and commands to aliased state."""
-        alias_message = Message(self.name)
-        if ('target' in message and message['target'] == self.name and
-                'command' in message):
-            alias_message['target'] = self.conf['alias for']
-            if message['command'] == 'get state':
-                alias_message['command'] = 'get state'
-                await self.bus.send(alias_message)
-            elif (message['command'] == 'set state' and
-                    'new state' in message):
-                alias_message['command'] = 'set state'
-                alias_message['new state'] = message['new state']
-                await self.bus.send(alias_message)
-        if (message['sender'] == self.conf['alias for'] and
-                'state' in message):
-            if 'event' in message and message['event'] == 'changed':
-                alias_message['event'] = 'changed'
-            alias_message['state'] = message['state']
-            await self.bus.send(alias_message)
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        sends = [MessageTemplate({'target': {'const': self.conf['alias for']},
-                                  'command': {'const': 'get state'}}),
-                 MessageTemplate({'target': {'const': self.conf['alias for']},
-                                  'command': {'const': 'set state'},
-                                  'new state': {'type': 'boolean'}}),
-                 MessageTemplate({'event': {'const': 'changed'},
-                                  'state': {'type': 'boolean'}}),
-                 MessageTemplate({'state': {'type': 'boolean'}})]
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'get state'}}),
-                    MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'set state'},
-                                     'new state': {'type': 'boolean'}}),
-                    MessageTemplate({'sender':
-                                     {'const': self.conf['alias for']},
-                                     'event': {'const': 'changed'},
-                                     'state': {'type': 'boolean'}}),
-                    MessageTemplate({'sender':
-                                     {'const': self.conf['alias for']},
-                                     'state': {'type': 'boolean'}})]
-        self.bus.register(self.name, 'StateAlias',
-                          sends, receives, self.receive)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-

Ancestors

- -

Class variables

-
-
var CONF_SCHEMA
-
-

Schema for StateAlias plugin configuration.

-

Required configuration key:

-
    -
  • 'alias for': name of aliased state.
  • -
-
-
-

Methods

-
-
-async def receive(self, message: Message) ‑> NoneType -
-
-

Translate states from and commands to aliased state.

-
- -Expand source code - -
async def receive(self, message: Message) -> None:
-    """Translate states from and commands to aliased state."""
-    alias_message = Message(self.name)
-    if ('target' in message and message['target'] == self.name and
-            'command' in message):
-        alias_message['target'] = self.conf['alias for']
-        if message['command'] == 'get state':
-            alias_message['command'] = 'get state'
-            await self.bus.send(alias_message)
-        elif (message['command'] == 'set state' and
-                'new state' in message):
-            alias_message['command'] = 'set state'
-            alias_message['new state'] = message['new state']
-            await self.bus.send(alias_message)
-    if (message['sender'] == self.conf['alias for'] and
-            'state' in message):
-        if 'event' in message and message['event'] == 'changed':
-            alias_message['event'] = 'changed'
-        alias_message['state'] = message['state']
-        await self.bus.send(alias_message)
-
-
-
-def process_conf(self) ‑> NoneType -
-
-

Register plugin as bus client.

-
- -Expand source code - -
def process_conf(self) -> None:
-    """Register plugin as bus client."""
-    sends = [MessageTemplate({'target': {'const': self.conf['alias for']},
-                              'command': {'const': 'get state'}}),
-             MessageTemplate({'target': {'const': self.conf['alias for']},
-                              'command': {'const': 'set state'},
-                              'new state': {'type': 'boolean'}}),
-             MessageTemplate({'event': {'const': 'changed'},
-                              'state': {'type': 'boolean'}}),
-             MessageTemplate({'state': {'type': 'boolean'}})]
-    receives = [MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'get state'}}),
-                MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'set state'},
-                                 'new state': {'type': 'boolean'}}),
-                MessageTemplate({'sender':
-                                 {'const': self.conf['alias for']},
-                                 'event': {'const': 'changed'},
-                                 'state': {'type': 'boolean'}}),
-                MessageTemplate({'sender':
-                                 {'const': self.conf['alias for']},
-                                 'state': {'type': 'boolean'}})]
-    self.bus.register(self.name, 'StateAlias',
-                      sends, receives, self.receive)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run no code proactively.

-
- -Expand source code - -
async def run(self) -> None:
-    """Run no code proactively."""
-    pass
-
-
-
-
-
-class AndState -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

Implement conjunction of states.

-

The "states" configuration key gets an array of states to be combined. -An AndState plugin client reacts to "get state" commands and sends -"changed" events when a change in one of the combined states leads to -a change for the conjunction:

-
>>> import asyncio
->>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test State 1": {"plugin": "State"},
-...      "Test State 2": {"plugin": "State"},
-...      "Test AndState": {"plugin": "AndState",
-...                        "states": ["Test State 1", "Test State 2"]}},
-...     [{"target": "Test State 1", "command": "set state",
-...       "new state": True},
-...      {"target": "Test State 2", "command": "set state",
-...       "new state": True},
-...      {"target": "Test State 1", "command": "set state",
-...       "new state": False},
-...      {"target": "Test AndState", "command": "get state"}]))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test State 1', 'plugin': 'State',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test State 1'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test State 1'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test State 2', 'plugin': 'State',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test State 2'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test State 2'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test AndState', 'plugin': 'AndState',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test AndState'},
-                       'command': {'const': 'get state'}},
-                      {'sender': {'const': 'Test State 1'},
-                       'state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test State 2'},
-                       'state': {'type': 'boolean'}}]}
-test(): {'sender': 'test()', 'target': 'Test State 1',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test State 2',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
-test(): {'sender': 'Test AndState', 'event': 'changed', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test State 1',
-         'command': 'set state', 'new state': False}
-test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
-test(): {'sender': 'Test AndState', 'event': 'changed', 'state': False}
-test(): {'sender': 'test()', 'target': 'Test AndState',
-         'command': 'get state'}
-test(): {'sender': 'Test AndState', 'state': False}
-
-
- -Expand source code - -
class AndState(BasePlugin):
-    """Implement conjunction of states.
-
-    The "states" configuration key gets an array of states to be combined.
-    An AndState plugin client reacts to "get state" commands and sends
-    "changed" events when a change in one of the combined states leads to
-    a change for the conjunction:
-    >>> import asyncio
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test State 1": {"plugin": "State"},
-    ...      "Test State 2": {"plugin": "State"},
-    ...      "Test AndState": {"plugin": "AndState",
-    ...                        "states": ["Test State 1", "Test State 2"]}},
-    ...     [{"target": "Test State 1", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State 2", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State 1", "command": "set state",
-    ...       "new state": False},
-    ...      {"target": "Test AndState", "command": "get state"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State 1', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State 1'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State 1'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State 2', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State 2'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State 2'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test AndState', 'plugin': 'AndState',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test AndState'},
-                           'command': {'const': 'get state'}},
-                          {'sender': {'const': 'Test State 1'},
-                           'state': {'type': 'boolean'}},
-                          {'sender': {'const': 'Test State 2'},
-                           'state': {'type': 'boolean'}}]}
-    test(): {'sender': 'test()', 'target': 'Test State 1',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State 2',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
-    test(): {'sender': 'Test AndState', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State 1',
-             'command': 'set state', 'new state': False}
-    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
-    test(): {'sender': 'Test AndState', 'event': 'changed', 'state': False}
-    test(): {'sender': 'test()', 'target': 'Test AndState',
-             'command': 'get state'}
-    test(): {'sender': 'Test AndState', 'state': False}
-    """
-
-    CONF_SCHEMA = {'properties': {'states': {'type': 'array',
-                                             'items': {'type': 'string'}}},
-                   'required': ['states']}
-    """Schema for AndState plugin configuration.
-
-    Required configuration key:
-
-    - 'states': list of names of combined states.
-    """
-
-    async def receive(self, message: Message) -> None:
-        """Process "get state" command and messages of combined states."""
-        if ('target' in message and message['target'] == self.name and
-                'command' in message and message['command'] == 'get state'):
-            await self.bus.send(Message(self.name, {'state': self.state}))
-        if 'state' in message and message['sender'] in self.conf['states']:
-            assert isinstance(message['sender'], str)
-            assert isinstance(message['state'], bool)
-            self.states[message['sender']] = message['state']
-            new_state = all(self.states.values())
-            if self.state != new_state:
-                self.state: bool = new_state
-                await self.bus.send(Message(self.name,
-                                            {'event': 'changed',
-                                             'state': self.state}))
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        sends = [MessageTemplate({'event': {'const': 'changed'},
-                                  'state': {'type': 'boolean'}}),
-                 MessageTemplate({'state': {'type': 'boolean'}})]
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'get state'}})]
-        self.states: Dict[str, bool] = {}
-        for state in self.conf['states']:
-            receives.append(MessageTemplate({'sender': {'const': state},
-                                             'state': {'type': 'boolean'}}))
-            self.states[state] = False
-        self.state = all(self.states.values())
-        self.bus.register(self.name, 'AndState',
-                          sends, receives, self.receive)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-

Ancestors

- -

Class variables

-
-
var CONF_SCHEMA
-
-

Schema for AndState plugin configuration.

-

Required configuration key:

-
    -
  • 'states': list of names of combined states.
  • -
-
-
-

Methods

-
-
-async def receive(self, message: Message) ‑> NoneType -
-
-

Process "get state" command and messages of combined states.

-
- -Expand source code - -
async def receive(self, message: Message) -> None:
-    """Process "get state" command and messages of combined states."""
-    if ('target' in message and message['target'] == self.name and
-            'command' in message and message['command'] == 'get state'):
-        await self.bus.send(Message(self.name, {'state': self.state}))
-    if 'state' in message and message['sender'] in self.conf['states']:
-        assert isinstance(message['sender'], str)
-        assert isinstance(message['state'], bool)
-        self.states[message['sender']] = message['state']
-        new_state = all(self.states.values())
-        if self.state != new_state:
-            self.state: bool = new_state
-            await self.bus.send(Message(self.name,
-                                        {'event': 'changed',
-                                         'state': self.state}))
-
-
-
-def process_conf(self) ‑> NoneType -
-
-

Register plugin as bus client.

-
- -Expand source code - -
def process_conf(self) -> None:
-    """Register plugin as bus client."""
-    sends = [MessageTemplate({'event': {'const': 'changed'},
-                              'state': {'type': 'boolean'}}),
-             MessageTemplate({'state': {'type': 'boolean'}})]
-    receives = [MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'get state'}})]
-    self.states: Dict[str, bool] = {}
-    for state in self.conf['states']:
-        receives.append(MessageTemplate({'sender': {'const': state},
-                                         'state': {'type': 'boolean'}}))
-        self.states[state] = False
-    self.state = all(self.states.values())
-    self.bus.register(self.name, 'AndState',
-                      sends, receives, self.receive)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run no code proactively.

-
- -Expand source code - -
async def run(self) -> None:
-    """Run no code proactively."""
-    pass
-
-
-
-
-
-class OrState -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

Implement disjunction of states.

-

The "states" configuration key gets an array of states to be combined. -An OrState plugin client reacts to "get state" commands and sends -"changed" events when a change in one of the combined states leads to -a change for the disjunction:

-
>>> import asyncio
->>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test State 1": {"plugin": "State"},
-...      "Test State 2": {"plugin": "State"},
-...      "Test OrState": {"plugin": "OrState",
-...                       "states": ["Test State 1", "Test State 2"]}},
-...     [{"target": "Test State 1", "command": "set state",
-...       "new state": True},
-...      {"target": "Test State 2", "command": "set state",
-...       "new state": True},
-...      {"target": "Test State 1", "command": "set state",
-...       "new state": False},
-...      {"target": "Test OrState", "command": "get state"}]))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test State 1', 'plugin': 'State',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test State 1'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test State 1'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test State 2', 'plugin': 'State',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test State 2'},
-                       'command': {'const': 'get state'}},
-                      {'target': {'const': 'Test State 2'},
-                       'command': {'const': 'set state'},
-                       'new state': {'type': 'boolean'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test OrState', 'plugin': 'OrState',
-         'sends': [{'event': {'const': 'changed'},
-                    'state': {'type': 'boolean'}},
-                   {'state': {'type': 'boolean'}}],
-         'receives': [{'target': {'const': 'Test OrState'},
-                       'command': {'const': 'get state'}},
-                      {'sender': {'const': 'Test State 1'},
-                       'state': {'type': 'boolean'}},
-                      {'sender': {'const': 'Test State 2'},
-                       'state': {'type': 'boolean'}}]}
-test(): {'sender': 'test()', 'target': 'Test State 1',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
-test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test State 2',
-         'command': 'set state', 'new state': True}
-test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
-test(): {'sender': 'test()', 'target': 'Test State 1',
-         'command': 'set state', 'new state': False}
-test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
-test(): {'sender': 'test()', 'target': 'Test OrState',
-         'command': 'get state'}
-test(): {'sender': 'Test OrState', 'state': True}
-
-
- -Expand source code - -
class OrState(BasePlugin):
-    """Implement disjunction of states.
-
-    The "states" configuration key gets an array of states to be combined.
-    An OrState plugin client reacts to "get state" commands and sends
-    "changed" events when a change in one of the combined states leads to
-    a change for the disjunction:
-    >>> import asyncio
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test State 1": {"plugin": "State"},
-    ...      "Test State 2": {"plugin": "State"},
-    ...      "Test OrState": {"plugin": "OrState",
-    ...                       "states": ["Test State 1", "Test State 2"]}},
-    ...     [{"target": "Test State 1", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State 2", "command": "set state",
-    ...       "new state": True},
-    ...      {"target": "Test State 1", "command": "set state",
-    ...       "new state": False},
-    ...      {"target": "Test OrState", "command": "get state"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State 1', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State 1'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State 1'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test State 2', 'plugin': 'State',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test State 2'},
-                           'command': {'const': 'get state'}},
-                          {'target': {'const': 'Test State 2'},
-                           'command': {'const': 'set state'},
-                           'new state': {'type': 'boolean'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test OrState', 'plugin': 'OrState',
-             'sends': [{'event': {'const': 'changed'},
-                        'state': {'type': 'boolean'}},
-                       {'state': {'type': 'boolean'}}],
-             'receives': [{'target': {'const': 'Test OrState'},
-                           'command': {'const': 'get state'}},
-                          {'sender': {'const': 'Test State 1'},
-                           'state': {'type': 'boolean'}},
-                          {'sender': {'const': 'Test State 2'},
-                           'state': {'type': 'boolean'}}]}
-    test(): {'sender': 'test()', 'target': 'Test State 1',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True}
-    test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State 2',
-             'command': 'set state', 'new state': True}
-    test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True}
-    test(): {'sender': 'test()', 'target': 'Test State 1',
-             'command': 'set state', 'new state': False}
-    test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False}
-    test(): {'sender': 'test()', 'target': 'Test OrState',
-             'command': 'get state'}
-    test(): {'sender': 'Test OrState', 'state': True}
-    """
-
-    CONF_SCHEMA = {'properties': {'states': {'type': 'array',
-                                             'items': {'type': 'string'}}},
-                   'required': ['states']}
-    """Schema for OrState plugin configuration.
-
-    Required configuration key:
-
-    - 'states': list of names of combined states.
-    """
-
-    async def receive(self, message: Message) -> None:
-        """Process "get state" command and messages of combined states."""
-        if ('target' in message and message['target'] == self.name and
-                'command' in message and message['command'] == 'get state'):
-            await self.bus.send(Message(self.name, {'state': self.state}))
-        if 'state' in message and message['sender'] in self.conf['states']:
-            assert isinstance(message['sender'], str)
-            assert isinstance(message['state'], bool)
-            self.states[message['sender']] = message['state']
-            new_state = any(self.states.values())
-            if self.state != new_state:
-                self.state: bool = new_state
-                await self.bus.send(Message(self.name,
-                                            {'event': 'changed',
-                                             'state': self.state}))
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        sends = [MessageTemplate({'event': {'const': 'changed'},
-                                  'state': {'type': 'boolean'}}),
-                 MessageTemplate({'state': {'type': 'boolean'}})]
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'get state'}})]
-        self.states: Dict[str, bool] = {}
-        for state in self.conf['states']:
-            receives.append(MessageTemplate({'sender': {'const': state},
-                                             'state': {'type': 'boolean'}}))
-            self.states[state] = False
-        self.state = any(self.states.values())
-        self.bus.register(self.name, 'OrState',
-                          sends, receives, self.receive)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-

Ancestors

- -

Class variables

-
-
var CONF_SCHEMA
-
-

Schema for OrState plugin configuration.

-

Required configuration key:

-
    -
  • 'states': list of names of combined states.
  • -
-
-
-

Methods

-
-
-async def receive(self, message: Message) ‑> NoneType -
-
-

Process "get state" command and messages of combined states.

-
- -Expand source code - -
async def receive(self, message: Message) -> None:
-    """Process "get state" command and messages of combined states."""
-    if ('target' in message and message['target'] == self.name and
-            'command' in message and message['command'] == 'get state'):
-        await self.bus.send(Message(self.name, {'state': self.state}))
-    if 'state' in message and message['sender'] in self.conf['states']:
-        assert isinstance(message['sender'], str)
-        assert isinstance(message['state'], bool)
-        self.states[message['sender']] = message['state']
-        new_state = any(self.states.values())
-        if self.state != new_state:
-            self.state: bool = new_state
-            await self.bus.send(Message(self.name,
-                                        {'event': 'changed',
-                                         'state': self.state}))
-
-
-
-def process_conf(self) ‑> NoneType -
-
-

Register plugin as bus client.

-
- -Expand source code - -
def process_conf(self) -> None:
-    """Register plugin as bus client."""
-    sends = [MessageTemplate({'event': {'const': 'changed'},
-                              'state': {'type': 'boolean'}}),
-             MessageTemplate({'state': {'type': 'boolean'}})]
-    receives = [MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'get state'}})]
-    self.states: Dict[str, bool] = {}
-    for state in self.conf['states']:
-        receives.append(MessageTemplate({'sender': {'const': state},
-                                         'state': {'type': 'boolean'}}))
-        self.states[state] = False
-    self.state = any(self.states.values())
-    self.bus.register(self.name, 'OrState',
-                      sends, receives, self.receive)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run no code proactively.

-
- -Expand source code - -
async def run(self) -> None:
-    """Run no code proactively."""
-    pass
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/doc/controlpi_plugins/util.html b/doc/controlpi_plugins/util.html deleted file mode 100644 index ca720bb..0000000 --- a/doc/controlpi_plugins/util.html +++ /dev/null @@ -1,1421 +0,0 @@ - - - - - - -controlpi_plugins.util API documentation - - - - - - - - - - - -
-
-
-

Module controlpi_plugins.util

-
-
-

Provide utility plugins for all kinds of systems.

-
    -
  • Log logs messages on stdout.
  • -
  • Init sends list of messages on startup and on demand.
  • -
  • Alias translates messages to an alias.
  • -
-
>>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test Log": {"plugin": "Log",
-...                   "filter": [{"sender": {"const": "Test Alias"}}]},
-...      "Test Init": {"plugin": "Init",
-...                    "messages": [{"id": 42, "content": "Test Message"}]},
-...      "Test Alias": {"plugin": "Alias",
-...                     "from": {"sender": {"const": "Test Init"},
-...                              "id": {"const": 42}},
-...                     "to": {"id": "translated"}}}, []))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Log', 'plugin': 'Log',
-         'sends': [], 'receives': [{'sender': {'const': 'Test Alias'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Init', 'plugin': 'Init',
-         'sends': [{'id': {'const': 42},
-                    'content': {'const': 'Test Message'}}],
-         'receives': [{'target': {'const': 'Test Init'},
-                       'command': {'const': 'execute'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Alias', 'plugin': 'Alias',
-         'sends': [{'id': {'const': 'translated'}}],
-         'receives': [{'sender': {'const': 'Test Init'},
-                       'id': {'const': 42}}]}
-test(): {'sender': 'Test Init', 'id': 42,
-         'content': 'Test Message'}
-test(): {'sender': 'Test Alias', 'id': 'translated',
-         'content': 'Test Message'}
-Test Log: {'sender': 'Test Alias', 'id': 'translated',
-           'content': 'Test Message'}
-
-
- -Expand source code - -
"""Provide utility plugins for all kinds of systems.
-
-- Log logs messages on stdout.
-- Init sends list of messages on startup and on demand.
-- Alias translates messages to an alias.
-
->>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test Log": {"plugin": "Log",
-...                   "filter": [{"sender": {"const": "Test Alias"}}]},
-...      "Test Init": {"plugin": "Init",
-...                    "messages": [{"id": 42, "content": "Test Message"}]},
-...      "Test Alias": {"plugin": "Alias",
-...                     "from": {"sender": {"const": "Test Init"},
-...                              "id": {"const": 42}},
-...                     "to": {"id": "translated"}}}, []))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Log', 'plugin': 'Log',
-         'sends': [], 'receives': [{'sender': {'const': 'Test Alias'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Init', 'plugin': 'Init',
-         'sends': [{'id': {'const': 42},
-                    'content': {'const': 'Test Message'}}],
-         'receives': [{'target': {'const': 'Test Init'},
-                       'command': {'const': 'execute'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Alias', 'plugin': 'Alias',
-         'sends': [{'id': {'const': 'translated'}}],
-         'receives': [{'sender': {'const': 'Test Init'},
-                       'id': {'const': 42}}]}
-test(): {'sender': 'Test Init', 'id': 42,
-         'content': 'Test Message'}
-test(): {'sender': 'Test Alias', 'id': 'translated',
-         'content': 'Test Message'}
-Test Log: {'sender': 'Test Alias', 'id': 'translated',
-           'content': 'Test Message'}
-"""
-import asyncio
-
-from controlpi import BasePlugin, Message, MessageTemplate
-
-
-class Log(BasePlugin):
-    """Log messages on stdout.
-
-    The "filter" configuration key gets a list of message templates defining
-    the messages that should be logged by the plugin instance.
-
-    In the following example the first and third message match the given
-    template and are logged by the instance "Test Log", while the second
-    message does not match and is only logged by the test, but not by the
-    Log instance:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Log": {"plugin": "Log",
-    ...                   "filter": [{"id": {"const": 42}}]}},
-    ...     [{"id": 42, "message": "Test Message"},
-    ...      {"id": 42.42, "message": "Second Message"},
-    ...      {"id": 42, "message": "Third Message"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test Log', 'plugin': 'Log',
-             'sends': [], 'receives': [{'id': {'const': 42}}]}
-    test(): {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
-    Test Log: {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
-    test(): {'sender': 'test()', 'id': 42.42, 'message': 'Second Message'}
-    test(): {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
-    Test Log: {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
-
-    The "filter" key is required:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Log": {"plugin": "Log"}}, []))
-    'filter' is a required property
-    <BLANKLINE>
-    Failed validating 'required' in schema:
-        {'properties': {'filter': {'items': {'type': 'object'},
-                                   'type': 'array'}},
-         'required': ['filter']}
-    <BLANKLINE>
-    On instance:
-        {'plugin': 'Log'}
-    Configuration for 'Test Log' is not valid.
-
-    The "filter" key has to contain a list of message templates, i.e.,
-    JSON objects:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Log": {"plugin": "Log",
-    ...                   "filter": [42]}}, []))
-    42 is not of type 'object'
-    <BLANKLINE>
-    Failed validating 'type' in schema['properties']['filter']['items']:
-        {'type': 'object'}
-    <BLANKLINE>
-    On instance['filter'][0]:
-        42
-    Configuration for 'Test Log' is not valid.
-    """
-
-    CONF_SCHEMA = {'properties': {'filter': {'type': 'array',
-                                             'items': {'type': 'object'}}},
-                   'required': ['filter']}
-    """Schema for Log plugin configuration.
-
-    Required configuration key:
-
-    - 'filter': list of message templates to be logged.
-    """
-
-    async def log(self, message: Message) -> None:
-        """Log received message on stdout using own name as prefix."""
-        print(f"{self.name}: {message}")
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        self.bus.register(self.name, 'Log', [], self.conf['filter'], self.log)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-
-class Init(BasePlugin):
-    """Send list of messages on startup and on demand.
-
-    The "messages" configuration key gets a list of messages to be sent on
-    startup. The same list is sent in reaction to a message with
-    "target": <name> and "command": "execute".
-
-    In the example, the two configured messages are sent twice, once at
-    startup and a second time in reaction to the "execute" command sent by
-    the test:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Init": {"plugin": "Init",
-    ...                    "messages": [{"id": 42,
-    ...                                  "content": "Test Message"},
-    ...                                 {"id": 42.42,
-    ...                                  "content": "Second Message"}]}},
-    ...     [{"target": "Test Init", "command": "execute"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test Init', 'plugin': 'Init',
-             'sends': [{'id': {'const': 42},
-                        'content': {'const': 'Test Message'}},
-                       {'id': {'const': 42.42},
-                        'content': {'const': 'Second Message'}}],
-             'receives': [{'target': {'const': 'Test Init'},
-                           'command': {'const': 'execute'}}]}
-    test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
-    test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
-    test(): {'sender': 'test()', 'target': 'Test Init', 'command': 'execute'}
-    test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
-    test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
-
-    The "messages" key is required:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Init": {"plugin": "Init"}}, []))
-    'messages' is a required property
-    <BLANKLINE>
-    Failed validating 'required' in schema:
-        {'properties': {'messages': {'items': {'type': 'object'},
-                                     'type': 'array'}},
-         'required': ['messages']}
-    <BLANKLINE>
-    On instance:
-        {'plugin': 'Init'}
-    Configuration for 'Test Init' is not valid.
-
-    The "messages" key has to contain a list of (partial) messages, i.e.,
-    JSON objects:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Init": {"plugin": "Init",
-    ...                    "messages": [42]}}, []))
-    42 is not of type 'object'
-    <BLANKLINE>
-    Failed validating 'type' in schema['properties']['messages']['items']:
-        {'type': 'object'}
-    <BLANKLINE>
-    On instance['messages'][0]:
-        42
-    Configuration for 'Test Init' is not valid.
-    """
-
-    CONF_SCHEMA = {'properties': {'messages': {'type': 'array',
-                                               'items': {'type': 'object'}}},
-                   'required': ['messages']}
-    """Schema for Init plugin configuration.
-
-    Required configuration key:
-
-    - 'messages': list of messages to be sent.
-    """
-
-    async def execute(self, message: Message) -> None:
-        """Send configured messages."""
-        for message in self.conf['messages']:
-            await self.bus.send(Message(self.name, message))
-            # Give immediate reactions to messages opportunity to happen:
-            await asyncio.sleep(0)
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'execute'}})]
-        sends = [MessageTemplate.from_message(message)
-                 for message in self.conf['messages']]
-        self.bus.register(self.name, 'Init', sends, receives, self.execute)
-
-    async def run(self) -> None:
-        """Send configured messages on startup."""
-        for message in self.conf['messages']:
-            await self.bus.send(Message(self.name, message))
-
-
-class Execute(BasePlugin):
-    """Send configurable list of messages on demand.
-
-    An Execute plugin instance receives two kinds of commands.
-    The "set messages" command has a "messages" key with a list of (partial)
-    messages, which are sent by the Execute instance in reaction to an
-    "execute" command.
-
-    In the example, the first command sent by the test sets two messages,
-    which are then sent in reaction to the second command sent by the test:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Execute": {"plugin": "Execute"}},
-    ...     [{"target": "Test Execute", "command": "set messages",
-    ...       "messages": [{"id": 42, "content": "Test Message"},
-    ...                    {"id": 42.42, "content": "Second Message"}]},
-    ...      {"target": "Test Execute", "command": "execute"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test Execute', 'plugin': 'Execute',
-             'sends': [{}],
-             'receives': [{'target': {'const': 'Test Execute'},
-                           'command': {'const': 'set messages'},
-                           'messages': {'type': 'array',
-                                        'items': {'type': 'object'}}},
-                          {'target': {'const': 'Test Execute'},
-                           'command': {'const': 'execute'}}]}
-    test(): {'sender': 'test()', 'target': 'Test Execute',
-             'command': 'set messages',
-             'messages': [{'id': 42, 'content': 'Test Message'},
-                          {'id': 42.42, 'content': 'Second Message'}]}
-    test(): {'sender': 'test()', 'target': 'Test Execute',
-             'command': 'execute'}
-    test(): {'sender': 'Test Execute', 'id': 42,
-             'content': 'Test Message'}
-    test(): {'sender': 'Test Execute', 'id': 42.42,
-             'content': 'Second Message'}
-    """
-
-    CONF_SCHEMA = True
-    """Schema for Execute plugin configuration.
-
-    There are no required or optional configuration keys.
-    """
-
-    async def execute(self, message: Message) -> None:
-        """Set or send configured messages."""
-        if message['command'] == 'set messages':
-            assert isinstance(message['messages'], list)
-            self.messages = list(message['messages'])
-        elif message['command'] == 'execute':
-            for message in self.messages:
-                await self.bus.send(Message(self.name, message))
-                # Give immediate reactions to messages opportunity to happen:
-                await asyncio.sleep(0)
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        self.messages = []
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'set messages'},
-                                     'messages':
-                                     {'type': 'array',
-                                      'items': {'type': 'object'}}}),
-                    MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'execute'}})]
-        sends = [MessageTemplate()]
-        self.bus.register(self.name, 'Execute', sends, receives, self.execute)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-
-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.
-
-    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
-    the "content" keys:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Alias": {"plugin": "Alias",
-    ...                     "from": {"id": {"const": 42}},
-    ...                     "to": {"id": "translated"}}},
-    ...     [{"id": 42, "content": "Test Message"},
-    ...      {"id": 42, "content": "Second Message"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test Alias', 'plugin': 'Alias',
-             'sends': [{'id': {'const': 'translated'}}],
-             'receives': [{'id': {'const': 42}}]}
-    test(): {'sender': 'test()', 'id': 42,
-             'content': 'Test Message'}
-    test(): {'sender': 'Test Alias', 'id': 'translated',
-             'content': 'Test Message'}
-    test(): {'sender': 'test()', 'id': 42,
-             'content': 'Second Message'}
-    test(): {'sender': 'Test Alias', 'id': 'translated',
-             'content': 'Second Message'}
-
-    The "from" and "to" keys are required:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Alias": {"plugin": "Alias"}}, []))
-    'from' is a required property
-    <BLANKLINE>
-    Failed validating 'required' in schema:
-        {'properties': {'from': {'type': 'object'}, 'to': {'type': 'object'}},
-         'required': ['from', 'to']}
-    <BLANKLINE>
-    On instance:
-        {'plugin': 'Alias'}
-    'to' is a required property
-    <BLANKLINE>
-    Failed validating 'required' in schema:
-        {'properties': {'from': {'type': 'object'}, 'to': {'type': 'object'}},
-         'required': ['from', 'to']}
-    <BLANKLINE>
-    On instance:
-        {'plugin': 'Alias'}
-    Configuration for 'Test Alias' is not valid.
-
-    The "from" key has to contain a message template and the "to" key a
-    (partial) message, i.e., both have to be JSON objects:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Alias": {"plugin": "Alias",
-    ...                     "from": 42,
-    ...                     "to": 42}}, []))
-    42 is not of type 'object'
-    <BLANKLINE>
-    Failed validating 'type' in schema['properties']['from']:
-        {'type': 'object'}
-    <BLANKLINE>
-    On instance['from']:
-        42
-    42 is not of type 'object'
-    <BLANKLINE>
-    Failed validating 'type' in schema['properties']['to']:
-        {'type': 'object'}
-    <BLANKLINE>
-    On instance['to']:
-        42
-    Configuration for 'Test Alias' is not valid.
-    """
-
-    CONF_SCHEMA = {'properties': {'from': {'type': 'object'},
-                                  'to': {'type': 'object'}},
-                   'required': ['from', 'to']}
-    """Schema for Alias plugin configuration.
-
-    Required configuration keys:
-
-    - 'from': template of messages to be translated.
-    - 'to': translated message to be sent.
-    """
-
-    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']:
-                alias_message[key] = message[key]
-        await self.bus.send(alias_message)
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        self.bus.register(self.name, 'Alias',
-                          [MessageTemplate.from_message(self.conf['to'])],
-                          [self.conf['from']],
-                          self.alias)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class Log -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

Log messages on stdout.

-

The "filter" configuration key gets a list of message templates defining -the messages that should be logged by the plugin instance.

-

In the following example the first and third message match the given -template and are logged by the instance "Test Log", while the second -message does not match and is only logged by the test, but not by the -Log instance:

-
>>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test Log": {"plugin": "Log",
-...                   "filter": [{"id": {"const": 42}}]}},
-...     [{"id": 42, "message": "Test Message"},
-...      {"id": 42.42, "message": "Second Message"},
-...      {"id": 42, "message": "Third Message"}]))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Log', 'plugin': 'Log',
-         'sends': [], 'receives': [{'id': {'const': 42}}]}
-test(): {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
-Test Log: {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
-test(): {'sender': 'test()', 'id': 42.42, 'message': 'Second Message'}
-test(): {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
-Test Log: {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
-
-

The "filter" key is required:

-
>>> asyncio.run(controlpi.test(
-...     {"Test Log": {"plugin": "Log"}}, []))
-'filter' is a required property
-<BLANKLINE>
-Failed validating 'required' in schema:
-    {'properties': {'filter': {'items': {'type': 'object'},
-                               'type': 'array'}},
-     'required': ['filter']}
-<BLANKLINE>
-On instance:
-    {'plugin': 'Log'}
-Configuration for 'Test Log' is not valid.
-
-

The "filter" key has to contain a list of message templates, i.e., -JSON objects:

-
>>> asyncio.run(controlpi.test(
-...     {"Test Log": {"plugin": "Log",
-...                   "filter": [42]}}, []))
-42 is not of type 'object'
-<BLANKLINE>
-Failed validating 'type' in schema['properties']['filter']['items']:
-    {'type': 'object'}
-<BLANKLINE>
-On instance['filter'][0]:
-    42
-Configuration for 'Test Log' is not valid.
-
-
- -Expand source code - -
class Log(BasePlugin):
-    """Log messages on stdout.
-
-    The "filter" configuration key gets a list of message templates defining
-    the messages that should be logged by the plugin instance.
-
-    In the following example the first and third message match the given
-    template and are logged by the instance "Test Log", while the second
-    message does not match and is only logged by the test, but not by the
-    Log instance:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Log": {"plugin": "Log",
-    ...                   "filter": [{"id": {"const": 42}}]}},
-    ...     [{"id": 42, "message": "Test Message"},
-    ...      {"id": 42.42, "message": "Second Message"},
-    ...      {"id": 42, "message": "Third Message"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test Log', 'plugin': 'Log',
-             'sends': [], 'receives': [{'id': {'const': 42}}]}
-    test(): {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
-    Test Log: {'sender': 'test()', 'id': 42, 'message': 'Test Message'}
-    test(): {'sender': 'test()', 'id': 42.42, 'message': 'Second Message'}
-    test(): {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
-    Test Log: {'sender': 'test()', 'id': 42, 'message': 'Third Message'}
-
-    The "filter" key is required:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Log": {"plugin": "Log"}}, []))
-    'filter' is a required property
-    <BLANKLINE>
-    Failed validating 'required' in schema:
-        {'properties': {'filter': {'items': {'type': 'object'},
-                                   'type': 'array'}},
-         'required': ['filter']}
-    <BLANKLINE>
-    On instance:
-        {'plugin': 'Log'}
-    Configuration for 'Test Log' is not valid.
-
-    The "filter" key has to contain a list of message templates, i.e.,
-    JSON objects:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Log": {"plugin": "Log",
-    ...                   "filter": [42]}}, []))
-    42 is not of type 'object'
-    <BLANKLINE>
-    Failed validating 'type' in schema['properties']['filter']['items']:
-        {'type': 'object'}
-    <BLANKLINE>
-    On instance['filter'][0]:
-        42
-    Configuration for 'Test Log' is not valid.
-    """
-
-    CONF_SCHEMA = {'properties': {'filter': {'type': 'array',
-                                             'items': {'type': 'object'}}},
-                   'required': ['filter']}
-    """Schema for Log plugin configuration.
-
-    Required configuration key:
-
-    - 'filter': list of message templates to be logged.
-    """
-
-    async def log(self, message: Message) -> None:
-        """Log received message on stdout using own name as prefix."""
-        print(f"{self.name}: {message}")
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        self.bus.register(self.name, 'Log', [], self.conf['filter'], self.log)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-

Ancestors

- -

Class variables

-
-
var CONF_SCHEMA
-
-

Schema for Log plugin configuration.

-

Required configuration key:

-
    -
  • 'filter': list of message templates to be logged.
  • -
-
-
-

Methods

-
-
-async def log(self, message: Message) ‑> NoneType -
-
-

Log received message on stdout using own name as prefix.

-
- -Expand source code - -
async def log(self, message: Message) -> None:
-    """Log received message on stdout using own name as prefix."""
-    print(f"{self.name}: {message}")
-
-
-
-def process_conf(self) ‑> NoneType -
-
-

Register plugin as bus client.

-
- -Expand source code - -
def process_conf(self) -> None:
-    """Register plugin as bus client."""
-    self.bus.register(self.name, 'Log', [], self.conf['filter'], self.log)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run no code proactively.

-
- -Expand source code - -
async def run(self) -> None:
-    """Run no code proactively."""
-    pass
-
-
-
-
-
-class Init -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

Send list of messages on startup and on demand.

-

The "messages" configuration key gets a list of messages to be sent on -startup. The same list is sent in reaction to a message with -"target": and "command": "execute".

-

In the example, the two configured messages are sent twice, once at -startup and a second time in reaction to the "execute" command sent by -the test:

-
>>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test Init": {"plugin": "Init",
-...                    "messages": [{"id": 42,
-...                                  "content": "Test Message"},
-...                                 {"id": 42.42,
-...                                  "content": "Second Message"}]}},
-...     [{"target": "Test Init", "command": "execute"}]))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Init', 'plugin': 'Init',
-         'sends': [{'id': {'const': 42},
-                    'content': {'const': 'Test Message'}},
-                   {'id': {'const': 42.42},
-                    'content': {'const': 'Second Message'}}],
-         'receives': [{'target': {'const': 'Test Init'},
-                       'command': {'const': 'execute'}}]}
-test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
-test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
-test(): {'sender': 'test()', 'target': 'Test Init', 'command': 'execute'}
-test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
-test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
-
-

The "messages" key is required:

-
>>> asyncio.run(controlpi.test(
-...     {"Test Init": {"plugin": "Init"}}, []))
-'messages' is a required property
-<BLANKLINE>
-Failed validating 'required' in schema:
-    {'properties': {'messages': {'items': {'type': 'object'},
-                                 'type': 'array'}},
-     'required': ['messages']}
-<BLANKLINE>
-On instance:
-    {'plugin': 'Init'}
-Configuration for 'Test Init' is not valid.
-
-

The "messages" key has to contain a list of (partial) messages, i.e., -JSON objects:

-
>>> asyncio.run(controlpi.test(
-...     {"Test Init": {"plugin": "Init",
-...                    "messages": [42]}}, []))
-42 is not of type 'object'
-<BLANKLINE>
-Failed validating 'type' in schema['properties']['messages']['items']:
-    {'type': 'object'}
-<BLANKLINE>
-On instance['messages'][0]:
-    42
-Configuration for 'Test Init' is not valid.
-
-
- -Expand source code - -
class Init(BasePlugin):
-    """Send list of messages on startup and on demand.
-
-    The "messages" configuration key gets a list of messages to be sent on
-    startup. The same list is sent in reaction to a message with
-    "target": <name> and "command": "execute".
-
-    In the example, the two configured messages are sent twice, once at
-    startup and a second time in reaction to the "execute" command sent by
-    the test:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Init": {"plugin": "Init",
-    ...                    "messages": [{"id": 42,
-    ...                                  "content": "Test Message"},
-    ...                                 {"id": 42.42,
-    ...                                  "content": "Second Message"}]}},
-    ...     [{"target": "Test Init", "command": "execute"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test Init', 'plugin': 'Init',
-             'sends': [{'id': {'const': 42},
-                        'content': {'const': 'Test Message'}},
-                       {'id': {'const': 42.42},
-                        'content': {'const': 'Second Message'}}],
-             'receives': [{'target': {'const': 'Test Init'},
-                           'command': {'const': 'execute'}}]}
-    test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
-    test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
-    test(): {'sender': 'test()', 'target': 'Test Init', 'command': 'execute'}
-    test(): {'sender': 'Test Init', 'id': 42, 'content': 'Test Message'}
-    test(): {'sender': 'Test Init', 'id': 42.42, 'content': 'Second Message'}
-
-    The "messages" key is required:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Init": {"plugin": "Init"}}, []))
-    'messages' is a required property
-    <BLANKLINE>
-    Failed validating 'required' in schema:
-        {'properties': {'messages': {'items': {'type': 'object'},
-                                     'type': 'array'}},
-         'required': ['messages']}
-    <BLANKLINE>
-    On instance:
-        {'plugin': 'Init'}
-    Configuration for 'Test Init' is not valid.
-
-    The "messages" key has to contain a list of (partial) messages, i.e.,
-    JSON objects:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Init": {"plugin": "Init",
-    ...                    "messages": [42]}}, []))
-    42 is not of type 'object'
-    <BLANKLINE>
-    Failed validating 'type' in schema['properties']['messages']['items']:
-        {'type': 'object'}
-    <BLANKLINE>
-    On instance['messages'][0]:
-        42
-    Configuration for 'Test Init' is not valid.
-    """
-
-    CONF_SCHEMA = {'properties': {'messages': {'type': 'array',
-                                               'items': {'type': 'object'}}},
-                   'required': ['messages']}
-    """Schema for Init plugin configuration.
-
-    Required configuration key:
-
-    - 'messages': list of messages to be sent.
-    """
-
-    async def execute(self, message: Message) -> None:
-        """Send configured messages."""
-        for message in self.conf['messages']:
-            await self.bus.send(Message(self.name, message))
-            # Give immediate reactions to messages opportunity to happen:
-            await asyncio.sleep(0)
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'execute'}})]
-        sends = [MessageTemplate.from_message(message)
-                 for message in self.conf['messages']]
-        self.bus.register(self.name, 'Init', sends, receives, self.execute)
-
-    async def run(self) -> None:
-        """Send configured messages on startup."""
-        for message in self.conf['messages']:
-            await self.bus.send(Message(self.name, message))
-
-

Ancestors

- -

Class variables

-
-
var CONF_SCHEMA
-
-

Schema for Init plugin configuration.

-

Required configuration key:

-
    -
  • 'messages': list of messages to be sent.
  • -
-
-
-

Methods

-
-
-async def execute(self, message: Message) ‑> NoneType -
-
-

Send configured messages.

-
- -Expand source code - -
async def execute(self, message: Message) -> None:
-    """Send configured messages."""
-    for message in self.conf['messages']:
-        await self.bus.send(Message(self.name, message))
-        # Give immediate reactions to messages opportunity to happen:
-        await asyncio.sleep(0)
-
-
-
-def process_conf(self) ‑> NoneType -
-
-

Register plugin as bus client.

-
- -Expand source code - -
def process_conf(self) -> None:
-    """Register plugin as bus client."""
-    receives = [MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'execute'}})]
-    sends = [MessageTemplate.from_message(message)
-             for message in self.conf['messages']]
-    self.bus.register(self.name, 'Init', sends, receives, self.execute)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Send configured messages on startup.

-
- -Expand source code - -
async def run(self) -> None:
-    """Send configured messages on startup."""
-    for message in self.conf['messages']:
-        await self.bus.send(Message(self.name, message))
-
-
-
-
-
-class Execute -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

Send configurable list of messages on demand.

-

An Execute plugin instance receives two kinds of commands. -The "set messages" command has a "messages" key with a list of (partial) -messages, which are sent by the Execute instance in reaction to an -"execute" command.

-

In the example, the first command sent by the test sets two messages, -which are then sent in reaction to the second command sent by the test:

-
>>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test Execute": {"plugin": "Execute"}},
-...     [{"target": "Test Execute", "command": "set messages",
-...       "messages": [{"id": 42, "content": "Test Message"},
-...                    {"id": 42.42, "content": "Second Message"}]},
-...      {"target": "Test Execute", "command": "execute"}]))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Execute', 'plugin': 'Execute',
-         'sends': [{}],
-         'receives': [{'target': {'const': 'Test Execute'},
-                       'command': {'const': 'set messages'},
-                       'messages': {'type': 'array',
-                                    'items': {'type': 'object'}}},
-                      {'target': {'const': 'Test Execute'},
-                       'command': {'const': 'execute'}}]}
-test(): {'sender': 'test()', 'target': 'Test Execute',
-         'command': 'set messages',
-         'messages': [{'id': 42, 'content': 'Test Message'},
-                      {'id': 42.42, 'content': 'Second Message'}]}
-test(): {'sender': 'test()', 'target': 'Test Execute',
-         'command': 'execute'}
-test(): {'sender': 'Test Execute', 'id': 42,
-         'content': 'Test Message'}
-test(): {'sender': 'Test Execute', 'id': 42.42,
-         'content': 'Second Message'}
-
-
- -Expand source code - -
class Execute(BasePlugin):
-    """Send configurable list of messages on demand.
-
-    An Execute plugin instance receives two kinds of commands.
-    The "set messages" command has a "messages" key with a list of (partial)
-    messages, which are sent by the Execute instance in reaction to an
-    "execute" command.
-
-    In the example, the first command sent by the test sets two messages,
-    which are then sent in reaction to the second command sent by the test:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Execute": {"plugin": "Execute"}},
-    ...     [{"target": "Test Execute", "command": "set messages",
-    ...       "messages": [{"id": 42, "content": "Test Message"},
-    ...                    {"id": 42.42, "content": "Second Message"}]},
-    ...      {"target": "Test Execute", "command": "execute"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test Execute', 'plugin': 'Execute',
-             'sends': [{}],
-             'receives': [{'target': {'const': 'Test Execute'},
-                           'command': {'const': 'set messages'},
-                           'messages': {'type': 'array',
-                                        'items': {'type': 'object'}}},
-                          {'target': {'const': 'Test Execute'},
-                           'command': {'const': 'execute'}}]}
-    test(): {'sender': 'test()', 'target': 'Test Execute',
-             'command': 'set messages',
-             'messages': [{'id': 42, 'content': 'Test Message'},
-                          {'id': 42.42, 'content': 'Second Message'}]}
-    test(): {'sender': 'test()', 'target': 'Test Execute',
-             'command': 'execute'}
-    test(): {'sender': 'Test Execute', 'id': 42,
-             'content': 'Test Message'}
-    test(): {'sender': 'Test Execute', 'id': 42.42,
-             'content': 'Second Message'}
-    """
-
-    CONF_SCHEMA = True
-    """Schema for Execute plugin configuration.
-
-    There are no required or optional configuration keys.
-    """
-
-    async def execute(self, message: Message) -> None:
-        """Set or send configured messages."""
-        if message['command'] == 'set messages':
-            assert isinstance(message['messages'], list)
-            self.messages = list(message['messages'])
-        elif message['command'] == 'execute':
-            for message in self.messages:
-                await self.bus.send(Message(self.name, message))
-                # Give immediate reactions to messages opportunity to happen:
-                await asyncio.sleep(0)
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        self.messages = []
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'set messages'},
-                                     'messages':
-                                     {'type': 'array',
-                                      'items': {'type': 'object'}}}),
-                    MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'execute'}})]
-        sends = [MessageTemplate()]
-        self.bus.register(self.name, 'Execute', sends, receives, self.execute)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-

Ancestors

- -

Class variables

-
-
var CONF_SCHEMA
-
-

Schema for Execute plugin configuration.

-

There are no required or optional configuration keys.

-
-
-

Methods

-
-
-async def execute(self, message: Message) ‑> NoneType -
-
-

Set or send configured messages.

-
- -Expand source code - -
async def execute(self, message: Message) -> None:
-    """Set or send configured messages."""
-    if message['command'] == 'set messages':
-        assert isinstance(message['messages'], list)
-        self.messages = list(message['messages'])
-    elif message['command'] == 'execute':
-        for message in self.messages:
-            await self.bus.send(Message(self.name, message))
-            # Give immediate reactions to messages opportunity to happen:
-            await asyncio.sleep(0)
-
-
-
-def process_conf(self) ‑> NoneType -
-
-

Register plugin as bus client.

-
- -Expand source code - -
def process_conf(self) -> None:
-    """Register plugin as bus client."""
-    self.messages = []
-    receives = [MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'set messages'},
-                                 'messages':
-                                 {'type': 'array',
-                                  'items': {'type': 'object'}}}),
-                MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'execute'}})]
-    sends = [MessageTemplate()]
-    self.bus.register(self.name, 'Execute', sends, receives, self.execute)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run no code proactively.

-
- -Expand source code - -
async def run(self) -> None:
-    """Run no code proactively."""
-    pass
-
-
-
-
-
-class Alias -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

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.

-

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 -the "content" keys:

-
>>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test Alias": {"plugin": "Alias",
-...                     "from": {"id": {"const": 42}},
-...                     "to": {"id": "translated"}}},
-...     [{"id": 42, "content": "Test Message"},
-...      {"id": 42, "content": "Second Message"}]))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Alias', 'plugin': 'Alias',
-         'sends': [{'id': {'const': 'translated'}}],
-         'receives': [{'id': {'const': 42}}]}
-test(): {'sender': 'test()', 'id': 42,
-         'content': 'Test Message'}
-test(): {'sender': 'Test Alias', 'id': 'translated',
-         'content': 'Test Message'}
-test(): {'sender': 'test()', 'id': 42,
-         'content': 'Second Message'}
-test(): {'sender': 'Test Alias', 'id': 'translated',
-         'content': 'Second Message'}
-
-

The "from" and "to" keys are required:

-
>>> asyncio.run(controlpi.test(
-...     {"Test Alias": {"plugin": "Alias"}}, []))
-'from' is a required property
-<BLANKLINE>
-Failed validating 'required' in schema:
-    {'properties': {'from': {'type': 'object'}, 'to': {'type': 'object'}},
-     'required': ['from', 'to']}
-<BLANKLINE>
-On instance:
-    {'plugin': 'Alias'}
-'to' is a required property
-<BLANKLINE>
-Failed validating 'required' in schema:
-    {'properties': {'from': {'type': 'object'}, 'to': {'type': 'object'}},
-     'required': ['from', 'to']}
-<BLANKLINE>
-On instance:
-    {'plugin': 'Alias'}
-Configuration for 'Test Alias' is not valid.
-
-

The "from" key has to contain a message template and the "to" key a -(partial) message, i.e., both have to be JSON objects:

-
>>> asyncio.run(controlpi.test(
-...     {"Test Alias": {"plugin": "Alias",
-...                     "from": 42,
-...                     "to": 42}}, []))
-42 is not of type 'object'
-<BLANKLINE>
-Failed validating 'type' in schema['properties']['from']:
-    {'type': 'object'}
-<BLANKLINE>
-On instance['from']:
-    42
-42 is not of type 'object'
-<BLANKLINE>
-Failed validating 'type' in schema['properties']['to']:
-    {'type': 'object'}
-<BLANKLINE>
-On instance['to']:
-    42
-Configuration for 'Test Alias' is not valid.
-
-
- -Expand source code - -
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.
-
-    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
-    the "content" keys:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Alias": {"plugin": "Alias",
-    ...                     "from": {"id": {"const": 42}},
-    ...                     "to": {"id": "translated"}}},
-    ...     [{"id": 42, "content": "Test Message"},
-    ...      {"id": 42, "content": "Second Message"}]))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test Alias', 'plugin': 'Alias',
-             'sends': [{'id': {'const': 'translated'}}],
-             'receives': [{'id': {'const': 42}}]}
-    test(): {'sender': 'test()', 'id': 42,
-             'content': 'Test Message'}
-    test(): {'sender': 'Test Alias', 'id': 'translated',
-             'content': 'Test Message'}
-    test(): {'sender': 'test()', 'id': 42,
-             'content': 'Second Message'}
-    test(): {'sender': 'Test Alias', 'id': 'translated',
-             'content': 'Second Message'}
-
-    The "from" and "to" keys are required:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Alias": {"plugin": "Alias"}}, []))
-    'from' is a required property
-    <BLANKLINE>
-    Failed validating 'required' in schema:
-        {'properties': {'from': {'type': 'object'}, 'to': {'type': 'object'}},
-         'required': ['from', 'to']}
-    <BLANKLINE>
-    On instance:
-        {'plugin': 'Alias'}
-    'to' is a required property
-    <BLANKLINE>
-    Failed validating 'required' in schema:
-        {'properties': {'from': {'type': 'object'}, 'to': {'type': 'object'}},
-         'required': ['from', 'to']}
-    <BLANKLINE>
-    On instance:
-        {'plugin': 'Alias'}
-    Configuration for 'Test Alias' is not valid.
-
-    The "from" key has to contain a message template and the "to" key a
-    (partial) message, i.e., both have to be JSON objects:
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test Alias": {"plugin": "Alias",
-    ...                     "from": 42,
-    ...                     "to": 42}}, []))
-    42 is not of type 'object'
-    <BLANKLINE>
-    Failed validating 'type' in schema['properties']['from']:
-        {'type': 'object'}
-    <BLANKLINE>
-    On instance['from']:
-        42
-    42 is not of type 'object'
-    <BLANKLINE>
-    Failed validating 'type' in schema['properties']['to']:
-        {'type': 'object'}
-    <BLANKLINE>
-    On instance['to']:
-        42
-    Configuration for 'Test Alias' is not valid.
-    """
-
-    CONF_SCHEMA = {'properties': {'from': {'type': 'object'},
-                                  'to': {'type': 'object'}},
-                   'required': ['from', 'to']}
-    """Schema for Alias plugin configuration.
-
-    Required configuration keys:
-
-    - 'from': template of messages to be translated.
-    - 'to': translated message to be sent.
-    """
-
-    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']:
-                alias_message[key] = message[key]
-        await self.bus.send(alias_message)
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        self.bus.register(self.name, 'Alias',
-                          [MessageTemplate.from_message(self.conf['to'])],
-                          [self.conf['from']],
-                          self.alias)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-

Ancestors

- -

Class variables

-
-
var CONF_SCHEMA
-
-

Schema for Alias plugin configuration.

-

Required configuration keys:

-
    -
  • 'from': template of messages to be translated.
  • -
  • 'to': translated message to be sent.
  • -
-
-
-

Methods

-
-
-async def alias(self, message: Message) ‑> NoneType -
-
-

Translate and send message.

-
- -Expand source code - -
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']:
-            alias_message[key] = message[key]
-    await self.bus.send(alias_message)
-
-
-
-def process_conf(self) ‑> NoneType -
-
-

Register plugin as bus client.

-
- -Expand source code - -
def process_conf(self) -> None:
-    """Register plugin as bus client."""
-    self.bus.register(self.name, 'Alias',
-                      [MessageTemplate.from_message(self.conf['to'])],
-                      [self.conf['from']],
-                      self.alias)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run no code proactively.

-
- -Expand source code - -
async def run(self) -> None:
-    """Run no code proactively."""
-    pass
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/doc/controlpi_plugins/wait.html b/doc/controlpi_plugins/wait.html deleted file mode 100644 index a5230a5..0000000 --- a/doc/controlpi_plugins/wait.html +++ /dev/null @@ -1,604 +0,0 @@ - - - - - - -controlpi_plugins.wait API documentation - - - - - - - - - - - -
-
-
-

Module controlpi_plugins.wait

-
-
-

Provide waiting/sleeping plugins for all kinds of systems.

-
    -
  • Wait waits for time defined in configuration and sends "finished" event.
  • -
  • GenericWait waits for time defined in "wait" command and sends "finished" -event with "id" string defined in "wait" command.
  • -
-
>>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test Wait": {"plugin": "Wait", "seconds": 0.01},
-...      "Test GenericWait": {"plugin": "GenericWait"}},
-...     [{"target": "Test GenericWait", "command": "wait",
-...       "seconds": 0.02, "id": "Long Wait"},
-...      {"target": "Test Wait", "command": "wait"}], 0.025))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Wait', 'plugin': 'Wait',
-         'sends': [{'event': {'const': 'finished'}}],
-         'receives': [{'target': {'const': 'Test Wait'},
-                       'command': {'const': 'wait'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test GenericWait', 'plugin': 'GenericWait',
-         'sends': [{'event': {'const': 'finished'},
-                    'id': {'type': 'string'}}],
-         'receives': [{'target': {'const': 'Test GenericWait'},
-                       'command': {'const': 'wait'},
-                       'seconds': {'type': 'number'},
-                       'id': {'type': 'string'}}]}
-test(): {'sender': 'test()', 'target': 'Test GenericWait',
-         'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}
-test(): {'sender': 'test()', 'target': 'Test Wait', 'command': 'wait'}
-test(): {'sender': 'Test Wait', 'event': 'finished'}
-test(): {'sender': 'Test GenericWait', 'event': 'finished',
-         'id': 'Long Wait'}
-
-
- -Expand source code - -
"""Provide waiting/sleeping plugins for all kinds of systems.
-
-- Wait waits for time defined in configuration and sends "finished" event.
-- GenericWait waits for time defined in "wait" command and sends "finished"
-  event with "id" string defined in "wait" command.
-
->>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test Wait": {"plugin": "Wait", "seconds": 0.01},
-...      "Test GenericWait": {"plugin": "GenericWait"}},
-...     [{"target": "Test GenericWait", "command": "wait",
-...       "seconds": 0.02, "id": "Long Wait"},
-...      {"target": "Test Wait", "command": "wait"}], 0.025))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test Wait', 'plugin': 'Wait',
-         'sends': [{'event': {'const': 'finished'}}],
-         'receives': [{'target': {'const': 'Test Wait'},
-                       'command': {'const': 'wait'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test GenericWait', 'plugin': 'GenericWait',
-         'sends': [{'event': {'const': 'finished'},
-                    'id': {'type': 'string'}}],
-         'receives': [{'target': {'const': 'Test GenericWait'},
-                       'command': {'const': 'wait'},
-                       'seconds': {'type': 'number'},
-                       'id': {'type': 'string'}}]}
-test(): {'sender': 'test()', 'target': 'Test GenericWait',
-         'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}
-test(): {'sender': 'test()', 'target': 'Test Wait', 'command': 'wait'}
-test(): {'sender': 'Test Wait', 'event': 'finished'}
-test(): {'sender': 'Test GenericWait', 'event': 'finished',
-         'id': 'Long Wait'}
-"""
-import asyncio
-
-from controlpi import BasePlugin, Message, MessageTemplate
-
-
-class Wait(BasePlugin):
-    """Wait for time defined in configuration.
-
-    The "seconds" configuration key gets the number of seconds to wait after
-    receiving a "wait" command before sending the "finished" event:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Long Wait": {"plugin": "Wait", "seconds": 0.02},
-    ...      "Short Wait": {"plugin": "Wait", "seconds": 0.01}},
-    ...     [{"target": "Long Wait", "command": "wait"},
-    ...      {"target": "Short Wait", "command": "wait"}], 0.025))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Long Wait', 'plugin': 'Wait',
-             'sends': [{'event': {'const': 'finished'}}],
-             'receives': [{'target': {'const': 'Long Wait'},
-                           'command': {'const': 'wait'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Short Wait', 'plugin': 'Wait',
-             'sends': [{'event': {'const': 'finished'}}],
-             'receives': [{'target': {'const': 'Short Wait'},
-                           'command': {'const': 'wait'}}]}
-    test(): {'sender': 'test()', 'target': 'Long Wait', 'command': 'wait'}
-    test(): {'sender': 'test()', 'target': 'Short Wait', 'command': 'wait'}
-    test(): {'sender': 'Short Wait', 'event': 'finished'}
-    test(): {'sender': 'Long Wait', 'event': 'finished'}
-    """
-
-    CONF_SCHEMA = {'properties': {'seconds': {'type': 'number'}},
-                   'required': ['seconds']}
-    """Schema for Wait plugin configuration.
-
-    Required configuration key:
-
-    - 'seconds': number of seconds to wait.
-    """
-
-    async def wait(self, message: Message) -> None:
-        """Wait configured time and send "finished" event."""
-        async def wait_coroutine():
-            await asyncio.sleep(self.conf['seconds'])
-            await self.bus.send(Message(self.name, {'event': 'finished'}))
-        # Done in separate task to not block queue awaiting this callback:
-        asyncio.create_task(wait_coroutine())
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'wait'}})]
-        sends = [MessageTemplate({'event': {'const': 'finished'}})]
-        self.bus.register(self.name, 'Wait', sends, receives, self.wait)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-
-class GenericWait(BasePlugin):
-    """Wait for time defined in "wait" command.
-
-    The "wait" command has message keys "seconds" defining the seconds to
-    wait and "id" defining a string to be sent back in the "finished" event
-    after the wait:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test GenericWait": {"plugin": "GenericWait"}},
-    ...     [{"target": "Test GenericWait", "command": "wait",
-    ...       "seconds": 0.02, "id": "Long Wait"},
-    ...      {"target": "Test GenericWait", "command": "wait",
-    ...       "seconds": 0.01, "id": "Short Wait"}], 0.025))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test GenericWait', 'plugin': 'GenericWait',
-             'sends': [{'event': {'const': 'finished'},
-                        'id': {'type': 'string'}}],
-             'receives': [{'target': {'const': 'Test GenericWait'},
-                           'command': {'const': 'wait'},
-                           'seconds': {'type': 'number'},
-                           'id': {'type': 'string'}}]}
-    test(): {'sender': 'test()', 'target': 'Test GenericWait',
-             'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}
-    test(): {'sender': 'test()', 'target': 'Test GenericWait',
-             'command': 'wait', 'seconds': 0.01, 'id': 'Short Wait'}
-    test(): {'sender': 'Test GenericWait', 'event': 'finished',
-             'id': 'Short Wait'}
-    test(): {'sender': 'Test GenericWait', 'event': 'finished',
-             'id': 'Long Wait'}
-    """
-
-    CONF_SCHEMA = True
-    """Schema for GenericWait plugin configuration.
-
-    There are no required or optional configuration keys.
-    """
-
-    async def wait(self, message: Message) -> None:
-        """Wait given time and send "finished" event with given "id"."""
-        async def wait_coroutine():
-            assert isinstance(message['seconds'], float)
-            await asyncio.sleep(message['seconds'])
-            await self.bus.send(Message(self.name, {'event': 'finished',
-                                                    'id': message['id']}))
-        # Done in separate task to not block queue awaiting this callback:
-        asyncio.create_task(wait_coroutine())
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'wait'},
-                                     'seconds': {'type': 'number'},
-                                     'id': {'type': 'string'}})]
-        sends = [MessageTemplate({'event': {'const': 'finished'},
-                                  'id': {'type': 'string'}})]
-        self.bus.register(self.name, 'GenericWait', sends, receives, self.wait)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class Wait -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

Wait for time defined in configuration.

-

The "seconds" configuration key gets the number of seconds to wait after -receiving a "wait" command before sending the "finished" event:

-
>>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Long Wait": {"plugin": "Wait", "seconds": 0.02},
-...      "Short Wait": {"plugin": "Wait", "seconds": 0.01}},
-...     [{"target": "Long Wait", "command": "wait"},
-...      {"target": "Short Wait", "command": "wait"}], 0.025))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Long Wait', 'plugin': 'Wait',
-         'sends': [{'event': {'const': 'finished'}}],
-         'receives': [{'target': {'const': 'Long Wait'},
-                       'command': {'const': 'wait'}}]}
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Short Wait', 'plugin': 'Wait',
-         'sends': [{'event': {'const': 'finished'}}],
-         'receives': [{'target': {'const': 'Short Wait'},
-                       'command': {'const': 'wait'}}]}
-test(): {'sender': 'test()', 'target': 'Long Wait', 'command': 'wait'}
-test(): {'sender': 'test()', 'target': 'Short Wait', 'command': 'wait'}
-test(): {'sender': 'Short Wait', 'event': 'finished'}
-test(): {'sender': 'Long Wait', 'event': 'finished'}
-
-
- -Expand source code - -
class Wait(BasePlugin):
-    """Wait for time defined in configuration.
-
-    The "seconds" configuration key gets the number of seconds to wait after
-    receiving a "wait" command before sending the "finished" event:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Long Wait": {"plugin": "Wait", "seconds": 0.02},
-    ...      "Short Wait": {"plugin": "Wait", "seconds": 0.01}},
-    ...     [{"target": "Long Wait", "command": "wait"},
-    ...      {"target": "Short Wait", "command": "wait"}], 0.025))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Long Wait', 'plugin': 'Wait',
-             'sends': [{'event': {'const': 'finished'}}],
-             'receives': [{'target': {'const': 'Long Wait'},
-                           'command': {'const': 'wait'}}]}
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Short Wait', 'plugin': 'Wait',
-             'sends': [{'event': {'const': 'finished'}}],
-             'receives': [{'target': {'const': 'Short Wait'},
-                           'command': {'const': 'wait'}}]}
-    test(): {'sender': 'test()', 'target': 'Long Wait', 'command': 'wait'}
-    test(): {'sender': 'test()', 'target': 'Short Wait', 'command': 'wait'}
-    test(): {'sender': 'Short Wait', 'event': 'finished'}
-    test(): {'sender': 'Long Wait', 'event': 'finished'}
-    """
-
-    CONF_SCHEMA = {'properties': {'seconds': {'type': 'number'}},
-                   'required': ['seconds']}
-    """Schema for Wait plugin configuration.
-
-    Required configuration key:
-
-    - 'seconds': number of seconds to wait.
-    """
-
-    async def wait(self, message: Message) -> None:
-        """Wait configured time and send "finished" event."""
-        async def wait_coroutine():
-            await asyncio.sleep(self.conf['seconds'])
-            await self.bus.send(Message(self.name, {'event': 'finished'}))
-        # Done in separate task to not block queue awaiting this callback:
-        asyncio.create_task(wait_coroutine())
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'wait'}})]
-        sends = [MessageTemplate({'event': {'const': 'finished'}})]
-        self.bus.register(self.name, 'Wait', sends, receives, self.wait)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-

Ancestors

- -

Class variables

-
-
var CONF_SCHEMA
-
-

Schema for Wait plugin configuration.

-

Required configuration key:

-
    -
  • 'seconds': number of seconds to wait.
  • -
-
-
-

Methods

-
-
-async def wait(self, message: Message) ‑> NoneType -
-
-

Wait configured time and send "finished" event.

-
- -Expand source code - -
async def wait(self, message: Message) -> None:
-    """Wait configured time and send "finished" event."""
-    async def wait_coroutine():
-        await asyncio.sleep(self.conf['seconds'])
-        await self.bus.send(Message(self.name, {'event': 'finished'}))
-    # Done in separate task to not block queue awaiting this callback:
-    asyncio.create_task(wait_coroutine())
-
-
-
-def process_conf(self) ‑> NoneType -
-
-

Register plugin as bus client.

-
- -Expand source code - -
def process_conf(self) -> None:
-    """Register plugin as bus client."""
-    receives = [MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'wait'}})]
-    sends = [MessageTemplate({'event': {'const': 'finished'}})]
-    self.bus.register(self.name, 'Wait', sends, receives, self.wait)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run no code proactively.

-
- -Expand source code - -
async def run(self) -> None:
-    """Run no code proactively."""
-    pass
-
-
-
-
-
-class GenericWait -(bus: MessageBus, name: str, conf: Dict[str, Any]) -
-
-

Wait for time defined in "wait" command.

-

The "wait" command has message keys "seconds" defining the seconds to -wait and "id" defining a string to be sent back in the "finished" event -after the wait:

-
>>> import controlpi
->>> asyncio.run(controlpi.test(
-...     {"Test GenericWait": {"plugin": "GenericWait"}},
-...     [{"target": "Test GenericWait", "command": "wait",
-...       "seconds": 0.02, "id": "Long Wait"},
-...      {"target": "Test GenericWait", "command": "wait",
-...       "seconds": 0.01, "id": "Short Wait"}], 0.025))
-... # doctest: +NORMALIZE_WHITESPACE
-test(): {'sender': '', 'event': 'registered',
-         'client': 'Test GenericWait', 'plugin': 'GenericWait',
-         'sends': [{'event': {'const': 'finished'},
-                    'id': {'type': 'string'}}],
-         'receives': [{'target': {'const': 'Test GenericWait'},
-                       'command': {'const': 'wait'},
-                       'seconds': {'type': 'number'},
-                       'id': {'type': 'string'}}]}
-test(): {'sender': 'test()', 'target': 'Test GenericWait',
-         'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}
-test(): {'sender': 'test()', 'target': 'Test GenericWait',
-         'command': 'wait', 'seconds': 0.01, 'id': 'Short Wait'}
-test(): {'sender': 'Test GenericWait', 'event': 'finished',
-         'id': 'Short Wait'}
-test(): {'sender': 'Test GenericWait', 'event': 'finished',
-         'id': 'Long Wait'}
-
-
- -Expand source code - -
class GenericWait(BasePlugin):
-    """Wait for time defined in "wait" command.
-
-    The "wait" command has message keys "seconds" defining the seconds to
-    wait and "id" defining a string to be sent back in the "finished" event
-    after the wait:
-    >>> import controlpi
-    >>> asyncio.run(controlpi.test(
-    ...     {"Test GenericWait": {"plugin": "GenericWait"}},
-    ...     [{"target": "Test GenericWait", "command": "wait",
-    ...       "seconds": 0.02, "id": "Long Wait"},
-    ...      {"target": "Test GenericWait", "command": "wait",
-    ...       "seconds": 0.01, "id": "Short Wait"}], 0.025))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    test(): {'sender': '', 'event': 'registered',
-             'client': 'Test GenericWait', 'plugin': 'GenericWait',
-             'sends': [{'event': {'const': 'finished'},
-                        'id': {'type': 'string'}}],
-             'receives': [{'target': {'const': 'Test GenericWait'},
-                           'command': {'const': 'wait'},
-                           'seconds': {'type': 'number'},
-                           'id': {'type': 'string'}}]}
-    test(): {'sender': 'test()', 'target': 'Test GenericWait',
-             'command': 'wait', 'seconds': 0.02, 'id': 'Long Wait'}
-    test(): {'sender': 'test()', 'target': 'Test GenericWait',
-             'command': 'wait', 'seconds': 0.01, 'id': 'Short Wait'}
-    test(): {'sender': 'Test GenericWait', 'event': 'finished',
-             'id': 'Short Wait'}
-    test(): {'sender': 'Test GenericWait', 'event': 'finished',
-             'id': 'Long Wait'}
-    """
-
-    CONF_SCHEMA = True
-    """Schema for GenericWait plugin configuration.
-
-    There are no required or optional configuration keys.
-    """
-
-    async def wait(self, message: Message) -> None:
-        """Wait given time and send "finished" event with given "id"."""
-        async def wait_coroutine():
-            assert isinstance(message['seconds'], float)
-            await asyncio.sleep(message['seconds'])
-            await self.bus.send(Message(self.name, {'event': 'finished',
-                                                    'id': message['id']}))
-        # Done in separate task to not block queue awaiting this callback:
-        asyncio.create_task(wait_coroutine())
-
-    def process_conf(self) -> None:
-        """Register plugin as bus client."""
-        receives = [MessageTemplate({'target': {'const': self.name},
-                                     'command': {'const': 'wait'},
-                                     'seconds': {'type': 'number'},
-                                     'id': {'type': 'string'}})]
-        sends = [MessageTemplate({'event': {'const': 'finished'},
-                                  'id': {'type': 'string'}})]
-        self.bus.register(self.name, 'GenericWait', sends, receives, self.wait)
-
-    async def run(self) -> None:
-        """Run no code proactively."""
-        pass
-
-

Ancestors

- -

Class variables

-
-
var CONF_SCHEMA
-
-

Schema for GenericWait plugin configuration.

-

There are no required or optional configuration keys.

-
-
-

Methods

-
-
-async def wait(self, message: Message) ‑> NoneType -
-
-

Wait given time and send "finished" event with given "id".

-
- -Expand source code - -
async def wait(self, message: Message) -> None:
-    """Wait given time and send "finished" event with given "id"."""
-    async def wait_coroutine():
-        assert isinstance(message['seconds'], float)
-        await asyncio.sleep(message['seconds'])
-        await self.bus.send(Message(self.name, {'event': 'finished',
-                                                'id': message['id']}))
-    # Done in separate task to not block queue awaiting this callback:
-    asyncio.create_task(wait_coroutine())
-
-
-
-def process_conf(self) ‑> NoneType -
-
-

Register plugin as bus client.

-
- -Expand source code - -
def process_conf(self) -> None:
-    """Register plugin as bus client."""
-    receives = [MessageTemplate({'target': {'const': self.name},
-                                 'command': {'const': 'wait'},
-                                 'seconds': {'type': 'number'},
-                                 'id': {'type': 'string'}})]
-    sends = [MessageTemplate({'event': {'const': 'finished'},
-                              'id': {'type': 'string'}})]
-    self.bus.register(self.name, 'GenericWait', sends, receives, self.wait)
-
-
-
-async def run(self) ‑> NoneType -
-
-

Run no code proactively.

-
- -Expand source code - -
async def run(self) -> None:
-    """Run no code proactively."""
-    pass
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/doc/index.md b/doc/index.md index d67a64e..faa0e12 100644 --- a/doc/index.md +++ b/doc/index.md @@ -25,9 +25,9 @@ Die ControlPi-Infrastruktur hat zwei Haupt-Bestandteile: Dictionaries bzw. Mappings.) Die generierte Dokumentation des API der grundlegenden Infrastruktur ist -unter [controlpi/](graphit/controlpi/controlpi/) zu finden. +unter [api/controlpi/](graphit/controlpi/api/controlpi/) zu finden. Die Kollektion an grundlegenden Plugins ist unter -[controlpi_plugins/](graphit/controlpi/controlpi_plugins/) dokumentiert. +[api/controlpi_plugins/](graphit/controlpi/api/controlpi_plugins/) dokumentiert. Um Nachrichten zu senden und/oder zu empfangen muss ein Klient am Bus unter einem Namen registriert werden. @@ -481,24 +481,10 @@ editierbar installiert werden: ``` ## Code-Stil, Typ-Checks und Tests -Die Formatierung des Codes und das Vorhandensein und der Stil der -Kommentare werden durch -[`pycodestyle`](https://pycodestyle.pycqa.org/en/latest/) und -[`pydocstyle`](http://www.pydocstyle.org/en/stable/) überprüft. -Die an Funktionen und Variablen annotierten Typ-Informationen werden durch -[`mypy`](http://mypy-lang.org/) gecheckt. -Alle drei Tools können rekursiv ein gesamtes Python-Paket überprüfen: -```sh -(venv)$ pycodestyle /controlpi -(venv)$ pydocstyle /controlpi -(venv)$ mypy /controlpi -``` - -Sie sind als Extra-Requirements in der Datei `setup.py` definiert, sodass -sie mit einem einzigen `pip`-Aufruf installiert werden können: -```sh -(venv)$ pip install --editable [dev] -``` +Die Formatierung des Codes und das Vorhandensein und der Stil der Kommentare +werden durch [`ruff`](https://docs.astral.sh/ruff/) und die an Funktionen und +Variablen annotierten Typ-Informationen werden durch +[`ty`](https://docs.astral.sh/ty/) überprüft. Der Code wird durch in die Dokumentation eingebettete „doctests“ getestet. Diese können für jede Code-Datei einzeln mit dem in der @@ -517,14 +503,10 @@ des Codes mit Tests erhält man mit der zusätzlichen Option `-v`: (venv)$ python -m doctest -v ``` -Außerdem wird durch die `[dev]`-Extras in `setup.py` auch das Tool -[`pdoc`](https://pdoc3.github.io/pdoc/) zur automatischen Generierung von -API-Dokumentation in HTML installiert: +Außerdem wird das Tool +[`pdoc`](https://pdoc.dev/docs/pdoc.html) zur automatischen Generierung von +API-Dokumentation in HTML benutzt: ```sh -(venv)$ pdoc --html --config sort_identifiers=False --force \ - --output-dir doc/ controlpi/ controlpi_plugins/ +(venv)$ pdoc -o doc/api/ controlpi controlpi_plugins ``` -Mit diesem wurden auch die oben verlinkten API-Dokumentationen für -[`controlpi`](graphit/controlpi/controlpi/) und -[`controlpi_plugins`](graphit/controlpi/controlpi_plugins/) -generiert. +Mit diesem wurden auch die oben verlinkten API-Dokumentationen generiert. diff --git a/setup.py b/setup.py index 275a14c..47bed5d 100644 --- a/setup.py +++ b/setup.py @@ -19,14 +19,6 @@ setuptools.setup( "fastjsonschema", "pyinotify", ], - extras_require={ - "dev": [ - "pycodestyle", - "pydocstyle", - "mypy", - "pdoc3", - ] - }, classifiers=[ "Programming Language :: Python", "License :: OSI Approved :: MIT License", -- 2.43.0