Completed
Pull Request — master (#209)
by
unknown
25:21
created

RemoteSequenceAsMap.items()   A

Complexity

Conditions 2

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 2
ccs 0
cts 0
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
"""Code shared between the API classes."""
2 6
import functools
3
import collections
4 6
5
from ..compat import unicode_errors_default
6
7 6
8
class Remote(object):
9
10
    """Base class for Nvim objects(buffer/window/tabpage).
11
12
    Each type of object has it's own specialized class with API wrappers around
13
    the msgpack-rpc session. This implements equality which takes the remote
14
    object handle into consideration.
15
    """
16 6
17
    def __init__(self, session, code_data):
18
        """Initialize from session and code_data immutable object.
19
20
        The `code_data` contains serialization information required for
21
        msgpack-rpc calls. It must be immutable for Buffer equality to work.
22 6
        """
23 6
        self._session = session
24 6
        self.code_data = code_data
25 6
        self.api = RemoteApi(self, self._api_prefix)
0 ignored issues
show
Bug introduced by
The Instance of Remote does not seem to have a member named _api_prefix.

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...
26
        self.vars = RemoteMap(self, self._api_prefix + 'get_var',
0 ignored issues
show
Bug introduced by
The Instance of Remote does not seem to have a member named _api_prefix.

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...
27 6
                              self._api_prefix + 'set_var')
0 ignored issues
show
Bug introduced by
The Instance of Remote does not seem to have a member named _api_prefix.

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...
28
        self.options = RemoteMap(self, self._api_prefix + 'get_option',
0 ignored issues
show
Bug introduced by
The Instance of Remote does not seem to have a member named _api_prefix.

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...
29
                                 self._api_prefix + 'set_option')
0 ignored issues
show
Bug introduced by
The Instance of Remote does not seem to have a member named _api_prefix.

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...
30 6
31
    def __eq__(self, other):
32 6
        """Return True if `self` and `other` are the same object."""
33
        return (hasattr(other, 'code_data') and
34
                other.code_data == self.code_data)
35 6
36
    def __hash__(self):
37 6
        """Return hash based on remote object id."""
38
        return self.code_data.__hash__()
39 6
40
    def request(self, name, *args, **kwargs):
41 6
        """Wrapper for nvim.request."""
42
        return self._session.request(name, self, *args, **kwargs)
43
44 6
45
class RemoteApi(object):
46
47
    """Wrapper to allow api methods to be called like python methods."""
48 6
49
    def __init__(self, obj, api_prefix):
50 6
        """Initialize a RemoteApi with object and api prefix."""
51 6
        self._obj = obj
52
        self._api_prefix = api_prefix
53 6
54
    def __getattr__(self, name):
55 6
        """Return wrapper to named api method."""
56
        return functools.partial(self._obj.request, self._api_prefix + name)
57
58 6
59
class RemoteMap(object):
60
61
    """Represents a string->object map stored in Nvim.
62
63
    This is the dict counterpart to the `RemoteSequence` class, but it is used
64
    as a generic way of retrieving values from the various map-like data
65
    structures present in Nvim.
66
67
    It is used to provide a dict-like API to vim variables and options.
68
    """
69 6
70
    def __init__(self, obj, get_method, set_method=None, self_obj=None):
0 ignored issues
show
Unused Code introduced by
The argument self_obj seems to be unused.
Loading history...
71 6
        """Initialize a RemoteMap with session, getter/setter and self_obj."""
72 6
        self._get = functools.partial(obj.request, get_method)
73 6
        self._set = None
74 6
        if set_method:
75
            self._set = functools.partial(obj.request, set_method)
76 6
77
    def __getitem__(self, key):
78 6
        """Return a map value by key."""
79
        return self._get(key)
80 6
81
    def __setitem__(self, key, value):
82 6
        """Set a map value by key(if the setter was provided)."""
83
        if not self._set:
84 6
            raise TypeError('This dict is read-only')
85
        self._set(key, value)
86 6
87
    def __delitem__(self, key):
88
        """Delete a map value by associating None with the key."""
89
        if not self._set:
90
            raise TypeError('This dict is read-only')
91
        return self._set(key, None)
92 6
93
    def __contains__(self, key):
94
        """Check if key is present in the map."""
95
        try:
96
            self._get(key)
97
            return True
98
        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...
99
            return False
100 6
101
    def get(self, key, default=None):
102
        """Return value for key if present, else a default value."""
103
        try:
104
            return self._get(key)
105
        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...
106
            return default
107
108 6
109
class RemoteSequence(object):
110
111
    """Represents a sequence of objects stored in Nvim.
112
113
    This class is used to wrap msgapck-rpc functions that work on Nvim
114
    sequences(of lines, buffers, windows and tabpages) with an API that
115
    is similar to the one provided by the python-vim interface.
116
117
    For example, the 'windows' property of the `Nvim` class is a RemoteSequence
118
    sequence instance, and the expression `nvim.windows[0]` is translated to
119
    session.request('vim_get_windows')[0].
120
121
    It can also receive an optional self_obj that will be passed as first
122
    argument of the request. For example, `tabpage.windows[0]` is translated
123
    to: session.request('tabpage_get_windows', tabpage_instance)[0].
124
125
    One important detail about this class is that all methods will fetch the
126
    sequence into a list and perform the necessary manipulation
127
    locally(iteration, indexing, counting, etc).
128
    """
129 6
130
    def __init__(self, session, method):
131 6
        """Initialize a RemoteSequence with session, method and self_obj."""
132
        self._fetch = functools.partial(session.request, method)
133 6
134
    def __len__(self):
135 6
        """Return the length of the remote sequence."""
136
        return len(self._fetch())
137 6
138
    def __getitem__(self, idx):
139 6
        """Return a sequence item by index."""
140 6
        if not isinstance(idx, slice):
141 5
            return self._fetch()[idx]
142
        return self._fetch()[idx.start:idx.stop]
143 6
144
    def __iter__(self):
145 6
        """Return an iterator for the sequence."""
146 6
        items = self._fetch()
147 6
        for item in items:
148
            yield item
149 6
150
    def __contains__(self, item):
151 6
        """Check if an item is present in the sequence."""
152
        return item in self._fetch()
153
154 6
155
class RemoteSequenceAsMap(collections.Mapping):
156
  """Represents a sparse sequence of objects stored in Nvim as a mapping object.
157
158 6
  Similar to `RemoteSequence`, this class is used to wrap msgpack-rpc functions
159
  that work on Nvim objects that are meant to represent a map. Unlike
160 3
  `RemoteMap`, the remote API only needs to expose a 'get sequence' method.
161 3
162 3
  The remote sequence is treated as being volatile. However, the abstract
163 3
  mapping methods are implemented in such a way that iterating over a keys,
164 3
  values, or items don't make redundant RPCs.
165
  """
166
167 6
  def __init__(self, session, method, key=None):
168
    def NumberPropertyAsKey(o):
169 6
      return o.number
170 6
171 6
    self._fetch = functools.partial(session.request, method)
172 6
    self._key = key if key else NumberPropertyAsKey
173
174 6
  def __len__(self):
175
    return len(self._fetch())
176
177
  def __getitem__(self, key):
178
    for i in self._fetch():
179
      if self._key(i) == key:
180
        return i
181
    raise KeyError("No element matching key {}".format(key))
182
183
  def __iter__(self):
184
    return self.keys().__iter__()
185
186
  def keys(self):
187
    return [self._key(i) for i in self._fetch()]
188
189
  def iterkeys(self):
190
    return self.keys().__iter__()
191
192
  def itervalues(self):
193
    return self.values().__iter__()
194
195
  def iteritems(self):
196
    return self.items().__iter__()
197
198
  def items(self):
199
    return [(self._key(i), i) for i in self._fetch()]
200
201
  def values(self):
202
    return self._fetch()
203
204
205
def _identity(obj, session, method, kind):
0 ignored issues
show
Unused Code introduced by
The argument session seems to be unused.
Loading history...
Unused Code introduced by
The argument method seems to be unused.
Loading history...
Unused Code introduced by
The argument kind seems to be unused.
Loading history...
206
    return obj
207
208
209
def decode_if_bytes(obj, mode=True):
210
    """Decode obj if it is bytes."""
211
    if mode is True:
212
        mode = unicode_errors_default
213
    if isinstance(obj, bytes):
214
        return obj.decode("utf-8", errors=mode)
215
    return obj
216
217
218
def walk(fn, obj, *args, **kwargs):
219
    """Recursively walk an object graph applying `fn`/`args` to objects."""
220
    if type(obj) in [list, tuple]:
221
        return list(walk(fn, o, *args) for o in obj)
222
    if type(obj) is dict:
223
        return dict((walk(fn, k, *args), walk(fn, v, *args)) for k, v in
224
                    obj.items())
225
    return fn(obj, *args, **kwargs)
226