vici: Add decorators to Python bindings to simplify listening for events

This commit is contained in:
Tobias Brunner
2025-10-07 09:20:57 +02:00
parent a24429c678
commit 4e065a9624
3 changed files with 111 additions and 2 deletions
+25 -2
View File
@@ -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()
@@ -1 +1,2 @@
from .event_listener import EventListener
from .session import Session
@@ -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)