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

RemoteMap   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 48
Duplicated Lines 0 %

Test Coverage

Coverage 50%
Metric Value
dl 0
loc 48
ccs 13
cts 26
cp 0.5
rs 10
wmc 11

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __getitem__() 0 3 1
A __init__() 0 6 2
A __delitem__() 0 5 2
A __setitem__() 0 5 2
A __contains__() 0 7 2
A get() 0 6 2
1
"""Code shared between the API classes."""
2 6
import functools
3
4
from ..compat import unicode_errors_default
5 6
6
7
class Remote(object):
8
9
    """Base class for Nvim objects(buffer/window/tabpage).
10
11
    Each type of object has it's own specialized class with API wrappers around
12
    the msgpack-rpc session. This implements equality which takes the remote
13
    object handle into consideration.
14 6
    """
15
16
    def __init__(self, session, code_data):
17
        """Initialize from session and code_data immutable object.
18
19
        The `code_data` contains serialization information required for
20 6
        msgpack-rpc calls. It must be immutable for Buffer equality to work.
21 6
        """
22 6
        self._session = session
23 6
        self.code_data = code_data
24
        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...
25 6
        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...
26
                              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...
27
        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...
28 6
                                 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...
29
30 6
    def __eq__(self, other):
31
        """Return True if `self` and `other` are the same object."""
32
        return (hasattr(other, 'code_data') and
33 6
                other.code_data == self.code_data)
34
35 6
    def __hash__(self):
36
        """Return hash based on remote object id."""
37 6
        return self.code_data.__hash__()
38
39 6
    def request(self, name, *args, **kwargs):
40
        """Wrapper for nvim.request."""
41
        return self._session.request(name, self, *args, **kwargs)
42 6
43
44
class RemoteApi(object):
45
46 6
    """Wrapper to allow api methods to be called like python methods."""
47
48 6
    def __init__(self, obj, api_prefix):
49 6
        """Initialize a RemoteApi with object and api prefix."""
50
        self._obj = obj
51 6
        self._api_prefix = api_prefix
52
53 6
    def __getattr__(self, name):
54
        """Return wrapper to named api method."""
55
        return functools.partial(self._obj.request, self._api_prefix + name)
56 6
57
58
class RemoteMap(object):
59
60
    """Represents a string->object map stored in Nvim.
61
62
    This is the dict counterpart to the `RemoteSequence` class, but it is used
63
    as a generic way of retrieving values from the various map-like data
64
    structures present in Nvim.
65
66
    It is used to provide a dict-like API to vim variables and options.
67 6
    """
68
69 6
    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...
70 6
        """Initialize a RemoteMap with session, getter/setter and self_obj."""
71 6
        self._get = functools.partial(obj.request, get_method)
72 6
        self._set = None
73
        if set_method:
74 6
            self._set = functools.partial(obj.request, set_method)
75
76 6
    def __getitem__(self, key):
77
        """Return a map value by key."""
78 6
        return self._get(key)
79
80 6
    def __setitem__(self, key, value):
81
        """Set a map value by key(if the setter was provided)."""
82 6
        if not self._set:
83
            raise TypeError('This dict is read-only')
84 6
        self._set(key, value)
85
86
    def __delitem__(self, key):
87
        """Delete a map value by associating None with the key."""
88
        if not self._set:
89
            raise TypeError('This dict is read-only')
90 6
        return self._set(key, None)
91
92
    def __contains__(self, key):
93
        """Check if key is present in the map."""
94
        try:
95
            self._get(key)
96
            return True
97
        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...
98 6
            return False
99
100
    def get(self, key, default=None):
101
        """Return value for key if present, else a default value."""
102
        try:
103
            return self._get(key)
104
        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...
105
            return default
106 6
107
108
class RemoteSequence(object):
109
110
    """Represents a sequence of objects stored in Nvim.
111
112
    This class is used to wrap msgapck-rpc functions that work on Nvim
113
    sequences(of lines, buffers, windows and tabpages) with an API that
114
    is similar to the one provided by the python-vim interface.
115
116
    For example, the 'buffers' property of the `Nvim class is a RemoteSequence
117
    sequence instance, and the expression `nvim.buffers[0]` is translated to
118
    session.request('vim_get_buffers')[0].
119
120
    It can also receive an optional self_obj that will be passed as first
121
    argument of the request. For example, `tabpage.windows[0]` is translated
122
    to: session.request('tabpage_get_windows', tabpage_instance)[0].
123
124
    One important detail about this class is that all methods will fetch the
125
    sequence into a list and perform the necessary manipulation
126
    locally(iteration, indexing, counting, etc).
127 6
    """
128
129 6
    def __init__(self, session, method):
130
        """Initialize a RemoteSequence with session, method and self_obj."""
131 6
        self._fetch = functools.partial(session.request, method)
132
133 6
    def __len__(self):
134
        """Return the length of the remote sequence."""
135 6
        return len(self._fetch())
136
137 6
    def __getitem__(self, idx):
138 6
        """Return a sequence item by index."""
139
        if not isinstance(idx, slice):
140
            return self._fetch()[idx]
141 6
        return self._fetch()[idx.start:idx.stop]
142
143 6
    def __iter__(self):
144 6
        """Return an iterator for the sequence."""
145 6
        items = self._fetch()
146
        for item in items:
147 6
            yield item
148
149 6
    def __contains__(self, item):
150
        """Check if an item is present in the sequence."""
151
        return item in self._fetch()
152 6
153
154
def _identity(obj, session, method, kind):
0 ignored issues
show
Unused Code introduced by
The argument method seems to be unused.
Loading history...
Unused Code introduced by
The argument session seems to be unused.
Loading history...
Unused Code introduced by
The argument kind seems to be unused.
Loading history...
155
    return obj
156 6
157
158 3
def decode_if_bytes(obj, mode=True):
159 3
    """Decode obj if it is bytes."""
160 3
    if mode is True:
161 3
        mode = unicode_errors_default
162 3
    if isinstance(obj, bytes):
163
        return obj.decode("utf-8", errors=mode)
164
    return obj
165 6
166
167 6
def walk(fn, obj, *args, **kwargs):
168 6
    """Recursively walk an object graph applying `fn`/`args` to objects."""
169 6
    if type(obj) in [list, tuple]:
170 6
        return list(walk(fn, o, *args) for o in obj)
171
    if type(obj) is dict:
172 6
        return dict((walk(fn, k, *args), walk(fn, v, *args)) for k, v in
173
                    obj.items())
174
    return fn(obj, *args, **kwargs)
175