From: Tobias Brunner Date: Thu, 2 Oct 2025 14:25:58 +0000 (+0200) Subject: vici: Add decorators to Python bindings to simplify listening for events X-Git-Tag: 6.0.3rc1~5^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4e065a96242fbbf7dc09a4bd004d6239303ffc9a;p=thirdparty%2Fstrongswan.git vici: Add decorators to Python bindings to simplify listening for events --- diff --git a/src/libcharon/plugins/vici/python/README.rst b/src/libcharon/plugins/vici/python/README.rst index 3990f63002..b9c0cebcb4 100644 --- a/src/libcharon/plugins/vici/python/README.rst +++ b/src/libcharon/plugins/vici/python/README.rst @@ -7,8 +7,8 @@ side implementation of the VICI protocol, well suited to script automated tasks in a reliable way. -Example Usage -------------- +Basic Usage +----------- .. code-block:: python @@ -22,3 +22,26 @@ Example Usage >>> s.get_pools() OrderedDict([('p1', OrderedDict([('base', b'10.0.0.0'), ('size', b'254'), ('online', b'0'), ('offline', b'0')]))]) + +Event Handling +-------------- + +Either use the convenient decorators provided by EventListener or directly call +listen() on a Session object to loop over received events and dispatch them +manually. + +.. code-block:: python + + >>> import vici + >>> s = vici.Session() + >>> l = vici.EventListener(s) + >>> @l.on_events(['ike-updown', 'ike-rekey']) + ... def ike_events(name, data): + ... """Handle event with given 'name' and 'data'.""" + ... print(name, data) + ... + >>> @l.on_events(['child-updown', 'child-rekey']) + ... def child_events(name, data): + ... print(name, data) + ... + >>> l.listen() diff --git a/src/libcharon/plugins/vici/python/vici/__init__.py b/src/libcharon/plugins/vici/python/vici/__init__.py index d314325b6c..12f84b3667 100644 --- a/src/libcharon/plugins/vici/python/vici/__init__.py +++ b/src/libcharon/plugins/vici/python/vici/__init__.py @@ -1 +1,2 @@ +from .event_listener import EventListener from .session import Session diff --git a/src/libcharon/plugins/vici/python/vici/event_listener.py b/src/libcharon/plugins/vici/python/vici/event_listener.py new file mode 100644 index 0000000000..45030bbb60 --- /dev/null +++ b/src/libcharon/plugins/vici/python/vici/event_listener.py @@ -0,0 +1,85 @@ +from functools import wraps + + +class EventListener(object): + def __init__(self, session=None): + """Create an event listener instance, which provides decorator methods + to make listening for events and the disconnection of the vici session + more convenient. + + The session is optional here, but one must be set via + :func:`~set_session()` before calling :func:`~listen()`. + + :param session: optional vici session to use + :type session: :class:`~vici.session.Session` or None + """ + self.event_map = {} + self.disconnect_list = [] + self.session = session + + def set_session(self, session): + """Set the session that's used to listen for events. Only has an effect + when set before calling :func:`~listen()`. + + :param session: vici session to use + :type session: :class:`~vici.session.Session` + """ + self.session = session + + def on_events(self, events): + """Decorator to mark a function as a listener for specific events. + + The decorated function is expected to receive the name of the event and + the data as arguments. + + :param events: events to register and call decorated function for + :type events: list + :return: decorator function + :rtype: any + """ + def decorator(func): + self.event_map.update({event: func for event in events}) + + @wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper + return decorator + + def on_disconnected(self): + """Decorator to mark a function as a listener for when the daemon + disconnects the vici session. This listener instance is passed to the + decorated function. + + :return: decorator function + :rtype: any + """ + def decorator(func): + self.disconnect_list.append(func) + + @wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + return wrapper + return decorator + + def listen(self): + """Dispatch events registered via decorators of this instance. + + This method does not return unless the daemon disconnects or an + exception occurs. + + An active session has to be set before calling this. After getting + disconnected, a new session may be set via :func:`~set_session()` + before calling this again. + """ + try: + if self.session is None: + return + for label, event in self.session.listen(self.event_map.keys()): + name = label.decode() + if name in self.event_map: + self.event_map[name](name, event) + except IOError: + for func in self.disconnect_list: + func(self)