Completed
Pull Request — master (#199)
by Björn
03:00
created

AsyncSession._on_invalid_message()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.4218
Metric Value
cc 1
dl 0
loc 4
ccs 1
cts 4
cp 0.25
crap 1.4218
rs 10
1
"""Asynchronous msgpack-rpc handling in the event loop pipeline."""
2 6
import logging
3 6
from traceback import format_exc
4
5
6 6
logger = logging.getLogger(__name__)
7 6
debug, info, warn = (logger.debug, logger.info, logger.warning,)
8
9
10 6
class AsyncSession(object):
11
12
    """Asynchronous msgpack-rpc layer that wraps a msgpack stream.
13
14
    This wraps the msgpack stream interface for reading/writing msgpack
15
    documents and exposes an interface for sending and receiving msgpack-rpc
16
    requests and notifications.
17
    """
18
19 6
    def __init__(self, msgpack_stream):
20
        """Wrap `msgpack_stream` on a msgpack-rpc interface."""
21 6
        self._msgpack_stream = msgpack_stream
22 6
        self._next_request_id = 1
23 6
        self._pending_requests = {}
24 6
        self._request_cb = self._notification_cb = None
25 6
        self._handlers = {
26
            0: self._on_request,
27
            1: self._on_response,
28
            2: self._on_notification
29
        }
30
31 6
    def threadsafe_call(self, fn):
32
        """Wrapper around `MsgpackStream.threadsafe_call`."""
33 6
        self._msgpack_stream.threadsafe_call(fn)
34
35 6
    def request(self, method, args, response_cb):
36
        """Send a msgpack-rpc request to Nvim.
37
38
        A msgpack-rpc with method `method` and argument `args` is sent to
39
        Nvim. The `response_cb` function is called with when the response
40
        is available.
41
        """
42 6
        request_id = self._next_request_id
43 6
        self._next_request_id = request_id + 1
44 6
        self._msgpack_stream.send([0, request_id, method, args])
45 6
        self._pending_requests[request_id] = response_cb
46
47 6
    def notify(self, method, args):
48
        """Send a msgpack-rpc notification to Nvim.
49
50
        A msgpack-rpc with method `method` and argument `args` is sent to
51
        Nvim. This will have the same effect as a request, but no response
52
        will be recieved
53
        """
54 6
        self._msgpack_stream.send([2, method, args])
55
56 6
    def run(self, request_cb, notification_cb):
57
        """Run the event loop to receive requests and notifications from Nvim.
58
59
        While the event loop is running, `request_cb` and `_notification_cb`
60
        will be called whenever requests or notifications are respectively
61
        available.
62
        """
63 6
        self._request_cb = request_cb
64 6
        self._notification_cb = notification_cb
65 6
        self._msgpack_stream.run(self._on_message)
66 6
        self._request_cb = None
67 6
        self._notification_cb = None
68
69 6
    def stop(self):
70
        """Stop the event loop."""
71 6
        self._msgpack_stream.stop()
72
73 6
    def _on_message(self, msg):
74 6
        try:
75 6
            self._handlers.get(msg[0], self._on_invalid_message)(msg)
76
        except Exception:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
77
            err_str = format_exc(5)
78
            warn(err_str)
79
            self._msgpack_stream.send([1, 0, err_str, None])
80
81 6
    def _on_request(self, msg):
82
        # request
83
        #   - msg[1]: id
84
        #   - msg[2]: method name
85
        #   - msg[3]: arguments
86 6
        debug('received request: %s, %s', msg[2], msg[3])
87 6
        self._request_cb(msg[2], msg[3], Response(self._msgpack_stream,
88
                                                  msg[1]))
89
90 6
    def _on_response(self, msg):
91
        # response to a previous request:
92
        #   - msg[1]: the id
93
        #   - msg[2]: error(if any)
94
        #   - msg[3]: result(if not errored)
95 6
        debug('received response: %s, %s', msg[2], msg[3])
96 6
        self._pending_requests.pop(msg[1])(msg[2], msg[3])
97
98 6
    def _on_notification(self, msg):
99
        # notification/event
100
        #   - msg[1]: event name
101
        #   - msg[2]: arguments
102 6
        debug('received notification: %s, %s', msg[1], msg[2])
103 6
        self._notification_cb(msg[1], msg[2])
104
105 6
    def _on_invalid_message(self, msg):
106
        error = 'Received invalid message %s' % msg
107
        warn(error)
108
        self._msgpack_stream.send([1, 0, error, None])
109
110
111 6
class Response(object):
112
113
    """Response to a msgpack-rpc request that came from Nvim.
114
115
    When Nvim sends a msgpack-rpc request, an instance of this class is
116
    created for remembering state required to send a response.
117
    """
118
119 6
    def __init__(self, msgpack_stream, request_id):
120
        """Initialize the Response instance."""
121 6
        self._msgpack_stream = msgpack_stream
122 6
        self._request_id = request_id
123
124 6
    def send(self, value, error=False):
125
        """Send the response.
126
127
        If `error` is True, it will be sent as an error.
128
        """
129 6
        if error:
130 6
            resp = [1, self._request_id, value, None]
131
        else:
132 6
            resp = [1, self._request_id, None, value]
133 6
        debug('sending response to request %d: %s', self._request_id, resp)
134
        self._msgpack_stream.send(resp)
135