import asyncio import importlib import logging from dataclasses import dataclass, field from threading import RLock from typing import Optional, Any from ..bus import Bus from ..config import Config from ..utils import get_enabled_plugins logger = logging.getLogger('platypush:context') @dataclass class Context: """ Data class to hold the context of the application. """ # backend_name -> backend_instance backends: dict = field(default_factory=dict) # plugin_name -> plugin_instance plugins: dict = field(default_factory=dict) # Reference to the main application bus bus: Optional[Bus] = None _ctx = Context() # # Map: backend_name -> backend_instance # backends = {} # # Map: plugin_name -> plugin_instance # plugins = {} # Map: plugin_name -> init_lock to make sure that a plugin isn't initialized # multiple times plugins_init_locks = {} # Reference to the main application bus # main_bus = None def get_context() -> Context: """ Get the current application context. """ return _ctx def register_backends(bus=None, global_scope=False, **kwargs): """ Initialize the backend objects based on the configuration and returns a name -> backend_instance map. Params: bus -- If specific (it usually should), the messages processed by the backends will be posted on this bus. kwargs -- Any additional key-value parameters required to initialize the backends """ if bus: _ctx.bus = bus if global_scope: backends = _ctx.backends else: backends = {} for (name, cfg) in Config.get_backends().items(): module = importlib.import_module('platypush.backend.' + name) # e.g. backend.pushbullet main class: PushbulletBackend cls_name = '' for token in module.__name__.title().split('.')[2:]: cls_name += token.title() cls_name += 'Backend' try: b = getattr(module, cls_name)(bus=bus, **cfg, **kwargs) backends[name] = b except AttributeError as e: logger.warning('No such class in %s: %s', module.__name__, cls_name) raise RuntimeError(e) from e return backends def register_plugins(bus=None): """ Register and start all the ``RunnablePlugin`` configured implementations. """ from ..plugins import RunnablePlugin for plugin in get_enabled_plugins().values(): if isinstance(plugin, RunnablePlugin): plugin.bus = bus plugin.start() def get_backend(name): """Returns the backend instance identified by name if it exists""" return _ctx.backends.get(name) def get_plugin(plugin_name, reload=False): """ Registers a plugin instance by name if not registered already, or returns the registered plugin instance. """ if plugin_name not in plugins_init_locks: plugins_init_locks[plugin_name] = RLock() if plugin_name in _ctx.plugins and not reload: return _ctx.plugins[plugin_name] try: plugin = importlib.import_module('platypush.plugins.' + plugin_name) except ImportError as e: logger.warning('No such plugin: %s', plugin_name) raise RuntimeError(e) from e # e.g. plugins.music.mpd main class: MusicMpdPlugin cls_name = '' for token in plugin_name.split('.'): cls_name += token.title() cls_name += 'Plugin' plugin_conf = ( Config.get_plugins()[plugin_name] if plugin_name in Config.get_plugins() else {} ) if 'disabled' in plugin_conf: if plugin_conf['disabled'] is True: return None del plugin_conf['disabled'] if 'enabled' in plugin_conf: if plugin_conf['enabled'] is False: return None del plugin_conf['enabled'] try: plugin_class = getattr(plugin, cls_name) except AttributeError as e: logger.warning('No such class in %s: %s [error: %s]', plugin_name, cls_name, e) raise RuntimeError(e) from e with plugins_init_locks[plugin_name]: if _ctx.plugins.get(plugin_name) and not reload: return _ctx.plugins[plugin_name] _ctx.plugins[plugin_name] = plugin_class(**plugin_conf) return _ctx.plugins[plugin_name] def get_bus() -> Bus: """ Get or register the main application bus. """ from platypush.bus.redis import RedisBus if _ctx.bus: return _ctx.bus _ctx.bus = RedisBus() return _ctx.bus def get_or_create_event_loop() -> asyncio.AbstractEventLoop: """ Get or create a new event loop """ try: loop = asyncio.get_event_loop() except (DeprecationWarning, RuntimeError): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) return loop class Variable: """ Utility class to wrap platform variables in your custom scripts. Usage: .. code-block:: python # Pass `persisted=False` to get/set an in-memory variable # on the Redis instance (default: the variable is # persisted on the internal database) var = Variable('myvar') value = var.get() var.set('new value') """ def __init__(self, name: str, persisted: bool = True): self.name = name self._persisted = persisted def get(self) -> Optional[Any]: plugin = get_plugin('variable') getter = getattr(plugin, 'get' if self._persisted else 'mget') return getter(self.name).output.get(self.name) def set(self, value: Any): plugin = get_plugin('variable') setter = getattr(plugin, 'set' if self._persisted else 'mset') setter(**{self.name: value}) # vim:sw=4:ts=4:et: