Completed
Push — master ( e3a139...2cb624 )
by Björn
04:21
created

neovim.msgpack_rpc.Session._on_request()   A

Complexity

Conditions 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.1105
Metric Value
cc 3
dl 0
loc 17
ccs 10
cts 13
cp 0.7692
crap 3.1105
rs 9.4285

1 Method

Rating   Name   Duplication   Size   Complexity  
A neovim.msgpack_rpc.Session.handler() 0 11 2
1
"""Synchronous msgpack-rpc session layer."""
2 6
import logging
3 6
from collections import deque
4
5 6
from traceback import format_exc
6
7 6
import greenlet
0 ignored issues
show
Configuration introduced by
The import greenlet could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
8
9 6
logger = logging.getLogger(__name__)
10 6
error, debug, info, warn = (logger.error, logger.debug, logger.info,
11
                            logger.warning,)
12
13
14 6
class Session(object):
15
16
    """Msgpack-rpc session layer that uses coroutines for a synchronous API.
17
18
    This class provides the public msgpack-rpc API required by this library.
19
    It uses the greenlet module to handle requests and notifications coming
20
    from Nvim with a synchronous API.
21
    """
22
23 6
    def __init__(self, async_session):
24
        """Wrap `async_session` on a synchronous msgpack-rpc interface."""
25 6
        self._async_session = async_session
26 6
        self._request_cb = self._notification_cb = None
27 6
        self._pending_messages = deque()
28 6
        self._is_running = False
29 6
        self._setup_exception = None
30
31 6
    def threadsafe_call(self, fn, *args, **kwargs):
32
        """Wrapper around `AsyncSession.threadsafe_call`."""
33 6
        def handler():
34 6
            fn(*args, **kwargs)
35
36 6
        def greenlet_wrapper():
37 6
            gr = greenlet.greenlet(handler)
38 6
            gr.switch()
39
40 6
        self._async_session.threadsafe_call(greenlet_wrapper)
41
42 6
    def next_message(self):
43
        """Block until a message(request or notification) is available.
44
45
        If any messages were previously enqueued, return the first in queue.
46
        If not, run the event loop until one is received.
47
        """
48 6
        if self._is_running:
49
            raise Exception('Event loop already running')
50 6
        if self._pending_messages:
51 6
            return self._pending_messages.popleft()
52 6
        self._async_session.run(self._enqueue_request_and_stop,
53
                                self._enqueue_notification_and_stop)
54 6
        if self._pending_messages:
55 6
            return self._pending_messages.popleft()
56
57 6
    def request(self, method, *args, **kwargs):
58
        """Send a msgpack-rpc request and block until as response is received.
59
60
        If the event loop is running, this method must have been called by a
61
        request or notification handler running on a greenlet. In that case,
62
        send the quest and yield to the parent greenlet until a response is
63
        available.
64
65
        When the event loop is not running, it will perform a blocking request
66
        like this:
67
        - Send the request
68
        - Run the loop until the response is available
69
        - Put requests/notifications received while waiting into a queue
70
71
        If the `async` flag is present and True, a asynchronous notification is
72
        sent instead. This will never block, and the return value or error is
73
        ignored.
74
        """
75 6
        async = kwargs.get('async', False)
76 6
        if async:
77 6
            self._async_session.notify(method, args)
78 6
            return
79
80 6
        if self._is_running:
81 6
            v = self._yielding_request(method, args)
82
        else:
83 6
            v = self._blocking_request(method, args)
84 6
        if not v:
85
            # EOF
86
            raise IOError('EOF')
87 6
        err, rv = v
0 ignored issues
show
Bug introduced by
The tuple unpacking with sequence defined at line 149 seems to be unbalanced; 2 value(s) for 0 label(s)

This happens when the amount of values does not equal the amount of labels:

a, b = ("a", "b", "c")  # only 2 labels for 3 values
Loading history...
88 6
        if err:
89 6
            info("'Received error: %s", err)
90 6
            raise self.error_wrapper(err)
0 ignored issues
show
Bug introduced by
The Instance of Session does not seem to have a member named error_wrapper.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
91 6
        return rv
92
93 6
    def run(self, request_cb, notification_cb, setup_cb=None):
94
        """Run the event loop to receive requests and notifications from Nvim.
95
96
        Like `AsyncSession.run()`, but `request_cb` and `notification_cb` are
97
        inside greenlets.
98
        """
99 6
        self._request_cb = request_cb
100 6
        self._notification_cb = notification_cb
101 6
        self._is_running = True
102 6
        self._setup_exception = None
103
104 6
        def on_setup():
105 6
            try:
106 6
                setup_cb()
107
            except Exception as e:
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...
108
                self._setup_exception = e
109
                self.stop()
110
111 6
        if setup_cb:
112
            # Create a new greenlet to handle the setup function
113 6
            gr = greenlet.greenlet(on_setup)
114 6
            gr.switch()
115
116 6
        if self._setup_exception:
117
            error('Setup error: {0}'.format(self._setup_exception))
118
            raise self._setup_exception
0 ignored issues
show
Bug introduced by
Raising NoneType while only classes or instances are allowed
Loading history...
119
120
        # Process all pending requests and notifications
121 6
        while self._pending_messages:
122
            msg = self._pending_messages.popleft()
123
            getattr(self, '_on_{0}'.format(msg[0]))(*msg[1:])
124 6
        self._async_session.run(self._on_request, self._on_notification)
125 6
        self._is_running = False
126 6
        self._request_cb = None
127 6
        self._notification_cb = None
128
129 6
        if self._setup_exception:
130
            raise self._setup_exception
0 ignored issues
show
Bug introduced by
Raising NoneType while only classes or instances are allowed
Loading history...
131
132 6
    def stop(self):
133
        """Stop the event loop."""
134 6
        self._async_session.stop()
135
136 6
    def _yielding_request(self, method, args):
137 6
        gr = greenlet.getcurrent()
138 6
        parent = gr.parent
139
140 6
        def response_cb(err, rv):
141 6
            debug('response is available for greenlet %s, switching back', gr)
142 6
            gr.switch(err, rv)
143
144 6
        self._async_session.request(method, args, response_cb)
145 6
        debug('yielding from greenlet %s to wait for response', gr)
146 6
        return parent.switch()
147
148 6
    def _blocking_request(self, method, args):
149 6
        result = []
150
151 6
        def response_cb(err, rv):
152 6
            result.extend([err, rv])
153 6
            self.stop()
154
155 6
        self._async_session.request(method, args, response_cb)
156 6
        self._async_session.run(self._enqueue_request,
157
                                self._enqueue_notification)
158 6
        return result
159
160 6
    def _enqueue_request_and_stop(self, name, args, response):
161
        self._enqueue_request(name, args, response)
162
        self.stop()
163
164 6
    def _enqueue_notification_and_stop(self, name, args):
165 6
        self._enqueue_notification(name, args)
166 6
        self.stop()
167
168 6
    def _enqueue_request(self, name, args, response):
169
        self._pending_messages.append(('request', name, args, response,))
170
171 6
    def _enqueue_notification(self, name, args):
172 6
        self._pending_messages.append(('notification', name, args,))
173
174 6
    def _on_request(self, name, args, response):
175 6
        def handler():
176 6
            try:
177 6
                rv = self._request_cb(name, args)
178 6
                debug('greenlet %s finished executing, ' +
179
                      'sending %s as response', gr, rv)
180 6
                response.send(rv)
181
            except Exception as err:
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...
182
                warn("error caught while processing request '%s %s': %s", name,
183
                     args, format_exc())
184
                response.send(repr(err) + "\n" + format_exc(5), error=True)
185 6
            debug('greenlet %s is now dying...', gr)
186
187
        # Create a new greenlet to handle the request
188 6
        gr = greenlet.greenlet(handler)
189 6
        debug('received rpc request, greenlet %s will handle it', gr)
190 6
        gr.switch()
191
192 6
    def _on_notification(self, name, args):
193
        def handler():
194
            try:
195
                self._notification_cb(name, args)
196
                debug('greenlet %s finished executing', gr)
197
            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...
198
                warn("error caught while processing notification '%s %s': %s",
199
                     name, args, format_exc())
200
            debug('greenlet %s is now dying...', gr)
201
202
        gr = greenlet.greenlet(handler)
203
        debug('received rpc notification, greenlet %s will handle it', gr)
204
        gr.switch()
205