Skip to content

Commit c334ed6

Browse files
committed
Add OpenSIPSEventHandler to manage multiple events
This new class is a wrapper that can handle mutliple subscriptions.
1 parent adb8108 commit c334ed6

File tree

9 files changed

+149
-69
lines changed

9 files changed

+149
-69
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
build/
22
opensips.egg-info/
3+
__pycache__/

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ Currently, the following packages are available:
1818
pip install .
1919
```
2020

21+
or from PyPI:
22+
23+
```bash
24+
pip install opensips
25+
```
26+
2127
2. Import the package in your Python code:
2228

2329
```python
2430
from opensips.mi import OpenSIPSMI, OpenSIPSMIException
25-
from opensips.event import OpenSIPSEvent, OpenSIPSEventException
31+
from opensips.event import OpenSIPSEvent, OpenSIPSEventException, OpenSIPSEventHandler
2632
```
2733

2834
3. Use the methods provided by the modules:
@@ -38,19 +44,20 @@ Currently, the following packages are available:
3844

3945
```python
4046
mi_connector = OpenSIPSMI('http', url='http://localhost:8888/mi')
41-
event = OpenSIPSEvent(mi_connector, 'datagram', ip='127.0.0.1', port=50012)
47+
hdl = OpenSIPSEventHandler(mi_connector, 'datagram', ip='127.0.0.1', port=50012)
4248
4349
def some_callback(message):
4450
# do something with the message
4551
pass
46-
52+
53+
ev: OpenSIPSEvent = None
4754
try:
48-
event.subscribe('E_PIKE_BLOCKED', some_callback)
55+
event = hdl.subscribe('E_PIKE_BLOCKED', some_callback)
4956
except OpenSIPSEventException as e:
5057
# handle the exception
5158
5259
try:
53-
event.unsubscribe('E_PIKE_BLOCKED')
60+
ev.unsubscribe('E_PIKE_BLOCKED')
5461
except OpenSIPSEventException as e:
5562
# handle the exception
5663
```

docs/event.md

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,45 @@
11
# OpenSIPS Python Packages - Event Interface
22

3-
This package can be used to subscribe to OpenSIPS Event Interface events.
3+
This package can be used to subscribe to OpenSIPS events.
44

55
## Supported backend protocols
66

77
The following event transport protocols are supported:
8-
* `datagram` - uses either UDP or UNIX datagram to receive notifications for subscribed events. If using UDP, the `ip` and `port` parameters are required. If using UNIX datagram, the `socket_path` parameter is required.
9-
* `stream` - uses TCP to communicate with the Event Interface. Requires the `ip` and `port` parameters to be set.
8+
* `datagram` (Default) - uses either UDP or UNIX datagram to receive notifications for subscribed events. By default, the UDP protocol is used with the `ip` and `port` parameters set to `127.0.0.1` and `50060` respectively, but you can tune them to your needs.
9+
To use the UNIX datagram, set the `socket_path` parameter.
10+
* `stream` - uses TCP to communicate with the Event Interface. Default values for `ip` and `port` are `127.0.0.1` and `50060` respectively, but you can change them as needed.
1011

1112
## How to use
1213

13-
To instantiate the `OpenSIPSEvent` class, you need to provide a MI connector, the backend protocol and the required parameters in a key-value format. Then `subscribe` and `unsubscribe` methods can be used to manage the subscriptions.
14+
To subscribe to events, you must instantiate an `OpenSIPSEventHandler`. This class can be used to subscribe and unsubscribe from events. It uses an `OpenSIPSMI` object to communicate with the OpenSIPS MI interface. By default, a MI connector is created with the `fifo` type, but you can set it as a parameter in the constructor. As said before, the default transport protocol is `datagram`, but you can change it by setting the `_type` parameter.
15+
16+
Next step is to create an `OpenSIPSEvent` object. This can be done by calling the `subscribe` method of the `OpenSIPSEventHandler` object or by creating an `OpenSIPSEvent` object directly. The `subscribe` method will return an `OpenSIPSEvent` object.
17+
18+
To unsubscribe from an event, you can call the `unsubscribe` method of the `OpenSIPSEvent` object or the `unsubscribe` method of the `OpenSIPSEventHandler` object.
1419

1520
```python
21+
from opensips.mi import OpenSIPSMI, OpenSIPSMIException
22+
from opensips.event import OpenSIPSEvent, OpenSIPSEventException
23+
24+
# simple way
25+
hdl = OpenSIPSEventHandler()
26+
27+
# tuned way
1628
mi_connector = OpenSIPSMI('http', url='http://localhost:8888/mi')
17-
event = OpenSIPSEvent(mi_connector, 'datagram', ip='127.0.0.1', port=50012)
29+
hdl = OpenSIPSEventHandler(mi_connector, 'datagram', ip='127.0.0.1', port=50012)
1830

1931
try:
20-
event.subscribe('E_PIKE_BLOCKED', some_callback)
32+
ev = hdl.subscribe('E_PIKE_BLOCKED', some_callback)
2133
except OpenSIPSEventException as e:
2234
# handle the exception
2335

36+
# or create an OpenSIPSEvent object directly
37+
ev = OpenSIPSEvent(hdl, 'E_PIKE_BLOCKED', some_callback)
38+
2439
try:
25-
event.unsubscribe('E_PIKE_BLOCKED')
40+
ev.unsubscribe()
41+
# or
42+
hdl.unsubscribe('E_PIKE_BLOCKED')
2643
except OpenSIPSEventException as e:
2744
# handle the exception
2845
```

opensips/event/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@
1919

2020
""" Event package of OpenSIPS """
2121

22-
from .subscriber import OpenSIPSEvent, OpenSIPSEventException
22+
from .event import OpenSIPSEvent, OpenSIPSEventException
23+
from .handler import OpenSIPSEventHandler

opensips/event/__main__.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import signal
2626
import argparse
2727
from opensips.mi import OpenSIPSMI
28-
from opensips.event import OpenSIPSEvent, OpenSIPSEventException
28+
from opensips.event import OpenSIPSEventHandler, OpenSIPSEventException
2929

3030
parser = argparse.ArgumentParser()
3131

@@ -100,10 +100,10 @@ def main():
100100
elif args.type == 'datagram':
101101
mi = OpenSIPSMI('datagram', datagram_ip=args.ip, datagram_port=args.port)
102102
else:
103-
print(f'Unknownt type: {args.type}')
103+
print(f'Unknown type: {args.type}')
104104
sys.exit(1)
105105

106-
ev = OpenSIPSEvent(mi, args.transport, ip=args.listen_ip, port=args.listen_port)
106+
hdl = OpenSIPSEventHandler(mi, args.transport, ip=args.listen_ip, port=args.listen_port)
107107

108108
def event_handler(message):
109109
""" Event handler callback """
@@ -113,9 +113,11 @@ def event_handler(message):
113113
except json.JSONDecodeError as e:
114114
print(f"Failed to decode JSON: {e}")
115115

116+
ev = None
117+
116118
def timer(*_):
117119
""" Timer to notify when the event expires """
118-
ev.unsubscribe(args.event)
120+
ev.unsubscribe()
119121
sys.exit(0) # successful
120122

121123
if args.expire:
@@ -126,7 +128,7 @@ def timer(*_):
126128
signal.signal(signal.SIGTERM, timer)
127129

128130
try:
129-
ev.subscribe(args.event, event_handler, expire=args.expire)
131+
ev = hdl.subscribe(args.event, event_handler, args.expire)
130132
except OpenSIPSEventException as e:
131133
print(e)
132134
sys.exit(1)

opensips/event/datagram.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,9 @@ def __init__(self, **kwargs):
3333

3434
if "unix_path" in kwargs:
3535
self.sock_name = kwargs["unix_path"]
36-
elif "ip" in kwargs and "port" in kwargs:
37-
self.ip = kwargs["ip"]
38-
self.port = int(kwargs["port"])
3936
else:
40-
raise ValueError("ip and port or unix_path is required for Datagram connector")
37+
self.ip = kwargs.get("ip", "127.0.0.1")
38+
self.port = int(kwargs.get("port", 50060))
4139

4240
def create(self):
4341
if self.ip is not None:
Lines changed: 23 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121
""" Module that implements OpenSIPS Event behavior """
2222

2323
from threading import Thread, Event
24-
from ..mi import OpenSIPSMI, OpenSIPSMIException
25-
from .datagram import Datagram
26-
from .stream import Stream
24+
from ..mi import OpenSIPSMIException
2725

2826
class OpenSIPSEventException(Exception):
2927
""" Exceptions generated by OpenSIPS Events """
@@ -32,19 +30,26 @@ class OpenSIPSEvent():
3230

3331
""" Implementation of the OpenSIPS Event """
3432

35-
def __init__(self, mi: OpenSIPSMI, _type: str, **kwargs):
36-
self.mi = mi
37-
self.kwargs = kwargs
33+
def __init__(self, handler, name: str, callback, expire=None):
34+
self._handler = handler
35+
self.name = name
36+
self.callback = callback
3837
self.thread = None
3938
self.thread_stop = Event()
4039
self.thread_stop.clear()
4140

42-
if _type == "datagram":
43-
self.socket = Datagram(**kwargs)
44-
elif _type == "stream":
45-
self.socket = Stream(**kwargs)
46-
else:
47-
raise ValueError("Invalid event type")
41+
try:
42+
self.socket = self._handler.__new_socket__()
43+
self._handler.__mi_subscribe__(self.name, self.socket.create(), expire)
44+
self._handler.events[self.name] = self
45+
self.thread = Thread(target=self.handle, args=(callback,))
46+
self.thread.start()
47+
except OpenSIPSEventException as e:
48+
raise e
49+
except OpenSIPSMIException as e:
50+
raise e
51+
except ValueError as e:
52+
raise OpenSIPSEventException("Invalid arguments for socket creation: {}".format(e))
4853

4954
def handle(self, callback):
5055
""" Handles the event callbacks """
@@ -53,36 +58,14 @@ def handle(self, callback):
5358
if data:
5459
callback(data)
5560

56-
def subscribe(self, event: str, callback, expire=None):
57-
""" Subscribes for an event """
61+
def unsubscribe(self):
62+
""" Unsubscribes the event """
5863
try:
59-
sock_name = self.socket.create()
60-
if expire is None:
61-
ret_val = self.mi.execute("event_subscribe", [event, sock_name])
62-
else:
63-
ret_val = self.mi.execute("event_subscribe", [event, sock_name, expire])
64-
65-
if ret_val != "OK":
66-
raise OpenSIPSEventException("Failed to subscribe to event")
67-
68-
self.thread = Thread(target=self.handle, args=(callback,))
69-
self.thread.start()
70-
71-
except OpenSIPSMIException as e:
72-
raise e
73-
except Exception as e:
74-
raise OpenSIPSEventException(f"Failed to subscribe to event: {e}") from e
75-
76-
def unsubscribe(self, event: str):
77-
""" Unsubscribes for an event """
78-
try:
79-
ret_val = self.mi.execute("event_subscribe", [event, self.socket.sock_name, 0])
80-
81-
if ret_val != "OK":
82-
raise OpenSIPSEventException("Failed to unsubscribe from event")
83-
64+
self._handler.__mi_unsubscribe__(self.name, self.socket.sock_name)
8465
self.stop()
85-
66+
del self._handler.events[self.name]
67+
except OpenSIPSEventException as e:
68+
raise e
8669
except OpenSIPSMIException as e:
8770
raise e
8871

opensips/event/handler.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env python
2+
##
3+
## This file is part of the OpenSIPS Python Package
4+
## (see https://github.com/OpenSIPS/python-opensips).
5+
##
6+
## This program is free software: you can redistribute it and/or modify
7+
## it under the terms of the GNU General Public License as published by
8+
## the Free Software Foundation, either version 3 of the License, or
9+
## (at your option) any later version.
10+
##
11+
## This program is distributed in the hope that it will be useful,
12+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
## GNU General Public License for more details.
15+
##
16+
## You should have received a copy of the GNU General Public License
17+
## along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
##
19+
20+
""" Module that implements an OpenSIPS Event Handler to manage events subscriptions """
21+
22+
from ..mi import OpenSIPSMI, OpenSIPSMIException
23+
from .event import OpenSIPSEvent, OpenSIPSEventException
24+
from .datagram import Datagram
25+
from .stream import Stream
26+
27+
class OpenSIPSEventHandler():
28+
29+
""" Implementation of the OpenSIPS Event Handler"""
30+
31+
def __init__(self, mi: OpenSIPSMI = None, _type: str = None, **kwargs):
32+
if mi:
33+
self.mi = mi
34+
else:
35+
self.mi = OpenSIPSMI()
36+
if _type:
37+
self._type = _type
38+
else:
39+
self._type = "datagram"
40+
self.kwargs = kwargs
41+
self.events = {str: OpenSIPSEvent}
42+
43+
def __new_socket__(self):
44+
if self._type == "datagram":
45+
return Datagram(**self.kwargs)
46+
elif self._type == "stream":
47+
return Stream(**self.kwargs)
48+
else:
49+
raise ValueError("Invalid event type")
50+
51+
def subscribe(self, event_name: str, callback, expire=None):
52+
return OpenSIPSEvent(self, event_name, callback, expire)
53+
54+
def unsubscribe(self, event_name: str):
55+
self.events[event_name].unsubscribe()
56+
57+
def __mi_subscribe__(self, event_name: str, sock_name: str, expire=None):
58+
try:
59+
if expire is None:
60+
ret_val = self.mi.execute("event_subscribe", [event_name, sock_name])
61+
else:
62+
ret_val = self.mi.execute("event_subscribe", [event_name, sock_name, expire])
63+
64+
if ret_val != "OK":
65+
raise OpenSIPSEventException("Failed to subscribe to event")
66+
except OpenSIPSMIException as e:
67+
raise e
68+
69+
def __mi_unsubscribe__(self, event_name: str, sock_name: str):
70+
try:
71+
ret_val = self.mi.execute("event_subscribe", [event_name, sock_name, 0])
72+
73+
if ret_val != "OK":
74+
raise OpenSIPSEventException("Failed to unsubscribe from event")
75+
except OpenSIPSMIException as e:
76+
raise e

opensips/event/stream.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,8 @@ class Stream(GenericSocket):
2727
""" TCP/Stream implementation of a socket """
2828

2929
def __init__(self, **kwargs):
30-
if "ip" not in kwargs:
31-
raise ValueError("ip is required for Stream connector")
32-
if "port" not in kwargs:
33-
raise ValueError("port is required for Stream connector")
34-
35-
self.ip = kwargs["ip"]
36-
self.port = int(kwargs["port"])
30+
self.ip = kwargs.get("ip", "127.0.0.1")
31+
self.port = int(kwargs.get("port", 50060))
3732
self.sock = None
3833
self.sock_name = None
3934

0 commit comments

Comments
 (0)