""" Copyright 2009, Linden Research, Inc. See NOTICE.md for previous contributors Copyright 2021, Salad Dais All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ import asyncio import logging from hippolyzer.lib.base.helpers import create_logged_task LOG = logging.getLogger(__name__) class Event: """ an object containing data which will be passed out to all subscribers """ def __init__(self, name=None): self.subscribers = [] self.name = name def subscribe(self, handler, *args, one_shot=False, predicate=None, **kwargs): """ establish the subscribers (handlers) to this event """ handler_tup = (handler, args, kwargs, one_shot, predicate) assert handler_tup not in self.subscribers self.subscribers.append(handler_tup) return self @staticmethod def _handler_key(handler): return handler[:3] def unsubscribe(self, handler, *args, **kwargs): """ remove the subscriber (handler) to this event """ did_remove = False for registered in reversed(self.subscribers): if self._handler_key(registered) == (handler, args, kwargs): self.subscribers.remove(registered) did_remove = True if not did_remove: raise ValueError(f"Handler {handler!r} is not subscribed to this event.") return self def _create_async_wrapper(self, handler, args, inner_args, kwargs): # Note that unsubscription may be delayed due to asyncio scheduling :) async def _run_handler_wrapper(): unsubscribe = await handler(args, *inner_args, **kwargs) if unsubscribe: _ = self.unsubscribe(handler, *inner_args, **kwargs) return _run_handler_wrapper def notify(self, args): for subscriber in self.subscribers[:]: handler, inner_args, kwargs, one_shot, predicate = subscriber if predicate and not predicate(args): continue if one_shot: self.unsubscribe(handler, *inner_args, **kwargs) if asyncio.iscoroutinefunction(handler): create_logged_task(self._create_async_wrapper(handler, args, inner_args, kwargs)(), self.name, LOG) else: try: if handler(args, *inner_args, **kwargs) and not one_shot: self.unsubscribe(handler, *inner_args, **kwargs) except: # One handler failing shouldn't prevent notification of other handlers. LOG.exception(f"Failed in handler for {self.name}") def __len__(self): return len(self.subscribers) def clear_subscribers(self): self.subscribers.clear() __iadd__ = subscribe __isub__ = unsubscribe __call__ = notify