Completed
Pull Request — master (#169)
by Björn
24:24
created

neovim.msgpack_rpc.Session._yielding_request()   A

Complexity

Conditions 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2
Metric Value
cc 2
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 2
rs 9.4285

1 Method

Rating   Name   Duplication   Size   Complexity  
A neovim.msgpack_rpc.Session.response_cb() 0 3 1
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
            try:
35
                fn(*args, **kwargs)
36 6
            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...
37 6
                warn("error caught while excecuting async callback\n%s\n",
38 6
                     format_exc())
39
40 6
        def greenlet_wrapper():
41
            gr = greenlet.greenlet(handler)
42 6
            gr.switch()
43
44
        self._async_session.threadsafe_call(greenlet_wrapper)
45
46
    def next_message(self):
47
        """Block until a message(request or notification) is available.
48 6
49
        If any messages were previously enqueued, return the first in queue.
50 6
        If not, run the event loop until one is received.
51 6
        """
52 6
        if self._is_running:
53
            raise Exception('Event loop already running')
54 6
        if self._pending_messages:
55 6
            return self._pending_messages.popleft()
56
        self._async_session.run(self._enqueue_request_and_stop,
57 6
                                self._enqueue_notification_and_stop)
58
        if self._pending_messages:
59
            return self._pending_messages.popleft()
60
61
    def request(self, method, *args, **kwargs):
62
        """Send a msgpack-rpc request and block until as response is received.
63
64
        If the event loop is running, this method must have been called by a
65
        request or notification handler running on a greenlet. In that case,
66
        send the quest and yield to the parent greenlet until a response is
67
        available.
68
69
        When the event loop is not running, it will perform a blocking request
70
        like this:
71
        - Send the request
72
        - Run the loop until the response is available
73
        - Put requests/notifications received while waiting into a queue
74
75 6
        If the `async` flag is present and True, a asynchronous notification is
76 6
        sent instead. This will never block, and the return value or error is
77 6
        ignored.
78 6
        """
79
        async = kwargs.get('async', False)
80 6
        if async:
81 6
            self._async_session.notify(method, args)
82
            return
83 6
84 6
        if self._is_running:
85
            v = self._yielding_request(method, args)
86
        else:
87 6
            v = self._blocking_request(method, args)
88 6
        if not v:
89 6
            # EOF
90 6
            raise IOError('EOF')
91 6
        err, rv = v
0 ignored issues
show
Bug introduced by
The tuple unpacking with sequence defined at line 153 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...
92
        if err:
93 6
            info("'Received error: %s", err)
94
            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...
95
        return rv
96
97
    def run(self, request_cb, notification_cb, setup_cb=None):
98
        """Run the event loop to receive requests and notifications from Nvim.
99 6
100 6
        Like `AsyncSession.run()`, but `request_cb` and `notification_cb` are
101 6
        inside greenlets.
102 6
        """
103
        self._request_cb = request_cb
104 6
        self._notification_cb = notification_cb
105 6
        self._is_running = True
106 6
        self._setup_exception = None
107
108
        def on_setup():
109
            try:
110
                setup_cb()
111 6
            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...
112
                self._setup_exception = e
113 6
                self.stop()
114 6
115
        if setup_cb:
116 6
            # Create a new greenlet to handle the setup function
117
            gr = greenlet.greenlet(on_setup)
118
            gr.switch()
119
120
        if self._setup_exception:
121 6
            error('Setup error: {0}'.format(self._setup_exception))
122
            raise self._setup_exception
0 ignored issues
show
Bug introduced by
Raising NoneType while only classes or instances are allowed
Loading history...
123
124 6
        # Process all pending requests and notifications
125 6
        while self._pending_messages:
126 6
            msg = self._pending_messages.popleft()
127 6
            getattr(self, '_on_{0}'.format(msg[0]))(*msg[1:])
128
        self._async_session.run(self._on_request, self._on_notification)
129 6
        self._is_running = False
130
        self._request_cb = None
131
        self._notification_cb = None
132 6
133
        if self._setup_exception:
134 6
            raise self._setup_exception
0 ignored issues
show
Bug introduced by
Raising NoneType while only classes or instances are allowed
Loading history...
135
136 6
    def stop(self):
137 6
        """Stop the event loop."""
138 6
        self._async_session.stop()
139
140 6
    def _yielding_request(self, method, args):
141 6
        gr = greenlet.getcurrent()
142 6
        parent = gr.parent
143
144 6
        def response_cb(err, rv):
145 6
            debug('response is available for greenlet %s, switching back', gr)
146 6
            gr.switch(err, rv)
147
148 6
        self._async_session.request(method, args, response_cb)
149 6
        debug('yielding from greenlet %s to wait for response', gr)
150
        return parent.switch()
151 6
152 6
    def _blocking_request(self, method, args):
153 6
        result = []
154
155 6
        def response_cb(err, rv):
156 6
            result.extend([err, rv])
157
            self.stop()
158 6
159
        self._async_session.request(method, args, response_cb)
160 6
        self._async_session.run(self._enqueue_request,
161
                                self._enqueue_notification)
162
        return result
163
164 6
    def _enqueue_request_and_stop(self, name, args, response):
165 6
        self._enqueue_request(name, args, response)
166 6
        self.stop()
167
168 6
    def _enqueue_notification_and_stop(self, name, args):
169
        self._enqueue_notification(name, args)
170
        self.stop()
171 6
172 6
    def _enqueue_request(self, name, args, response):
173
        self._pending_messages.append(('request', name, args, response,))
174 6
175 6
    def _enqueue_notification(self, name, args):
176 6
        self._pending_messages.append(('notification', name, args,))
177 6
178 6
    def _on_request(self, name, args, response):
179
        def handler():
180 6
            try:
181
                rv = self._request_cb(name, args)
182
                debug('greenlet %s finished executing, ' +
183
                      'sending %s as response', gr, rv)
184
                response.send(rv)
185 6
            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...
186
                warn("error caught while processing request '%s %s': %s", name,
187
                     args, format_exc())
188 6
                response.send(repr(err) + "\n" + format_exc(5), error=True)
189 6
            debug('greenlet %s is now dying...', gr)
190 6
191
        # Create a new greenlet to handle the request
192 6
        gr = greenlet.greenlet(handler)
193
        debug('received rpc request, greenlet %s will handle it', gr)
194
        gr.switch()
195
196
    def _on_notification(self, name, args):
197
        def handler():
198
            try:
199
                self._notification_cb(name, args)
200
                debug('greenlet %s finished executing', gr)
201
            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...
202
                warn("error caught while processing notification '%s %s': %s",
203
                     name, args, format_exc())
204
205
            debug('greenlet %s is now dying...', gr)
206
207
        gr = greenlet.greenlet(handler)
208
        debug('received rpc notification, greenlet %s will handle it', gr)
209
        gr.switch()
210