Completed
Pull Request — master (#223)
by Björn
02:49
created

AsyncSession._on_notification()   A

Complexity

Conditions 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.2963

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 6
ccs 1
cts 3
cp 0.3333
crap 1.2963
rs 9.4285
c 0
b 0
f 0
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
        self._msgpack_stream = msgpack_stream
22
        self._next_request_id = 1
23
        self._pending_requests = {}
24
        self._request_cb = self._notification_cb = None
25
        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
        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
        request_id = self._next_request_id
43
        self._next_request_id = request_id + 1
44
        self._msgpack_stream.send([0, request_id, method, args])
45
        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
        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
        self._request_cb = request_cb
64
        self._notification_cb = notification_cb
65
        self._msgpack_stream.run(self._on_message)
66
        self._request_cb = None
67
        self._notification_cb = None
68
69 6
    def stop(self):
70
        """Stop the event loop."""
71
        self._msgpack_stream.stop()
72
73 6
    def _on_message(self, msg):
74
        try:
75
            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
        debug('received request: %s, %s', msg[2], msg[3])
87
        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
        debug('received response: %s, %s', msg[2], msg[3])
96
        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
        debug('received notification: %s, %s', msg[1], msg[2])
103
        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
        self._msgpack_stream = msgpack_stream
122
        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
        if error:
130
            resp = [1, self._request_id, value, None]
131
        else:
132
            resp = [1, self._request_id, None, value]
133
        debug('sending response to request %d: %s', self._request_id, resp)
134
        self._msgpack_stream.send(resp)
135