Revision 4f650d54 ncclient/operations/rpc.py

b/ncclient/operations/rpc.py
22 22
from errors import OperationError
23 23

  
24 24
import logging
25
logger = logging.getLogger('ncclient.rpc')
25
logger = logging.getLogger('ncclient.operations.rpc')
26 26

  
27 27

  
28 28
class RPCReply:
29
    
30
    'NOTES: memory considerations?? storing both raw xml + ET.Element'
31
    
29

  
32 30
    def __init__(self, raw):
33 31
        self._raw = raw
34 32
        self._parsed = False
35 33
        self._root = None
36 34
        self._errors = []
37
    
35

  
38 36
    def __repr__(self):
39 37
        return self._raw
40
    
41
    def _parsing_hook(self, root):
42
        pass
43
    
38

  
39
    def _parsing_hook(self, root): pass
40

  
44 41
    def parse(self):
45 42
        if self._parsed:
46 43
            return
47 44
        root = self._root = content.xml2ele(self._raw) # <rpc-reply> element
48 45
        # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
49
        ok = content.find(root, 'data', strict=False)
46
        ok = content.find(root, 'data', nslist=[content.BASE_NS, content.CISCO_BS])
50 47
        if ok is not None:
51 48
            logger.debug('parsed [%s]' % ok.tag)
52 49
        else: # create RPCError objects from <rpc-error> elements
53
            error = content.find(root, 'data', strict=False)
50
            error = content.find(root, 'data', nslist=[content.BASE_NS, content.CISCO_BS])
54 51
            if error is not None:
55 52
                logger.debug('parsed [%s]' % error.tag)
56 53
                for err in root.getiterator(error.tag):
......
65 62
                    self._errors.append(RPCError(d))
66 63
        self._parsing_hook(root)
67 64
        self._parsed = True
68
    
65

  
69 66
    @property
70 67
    def xml(self):
71 68
        '<rpc-reply> as returned'
72 69
        return self._raw
73
    
70

  
74 71
    @property
75 72
    def ok(self):
76 73
        if not self._parsed:
77 74
            self.parse()
78 75
        return not self._errors # empty list => false
79
    
76

  
80 77
    @property
81 78
    def error(self):
82 79
        if not self._parsed:
......
85 82
            return self._errors[0]
86 83
        else:
87 84
            return None
88
    
85

  
89 86
    @property
90 87
    def errors(self):
91 88
        'List of RPCError objects. Will be empty if no <rpc-error> elements in reply.'
......
95 92

  
96 93

  
97 94
class RPCError(OperationError): # raise it if you like
98
    
95

  
99 96
    def __init__(self, err_dict):
100 97
        self._dict = err_dict
101 98
        if self.message is not None:
102 99
            OperationError.__init__(self, self.message)
103 100
        else:
104 101
            OperationError.__init__(self)
105
    
102

  
106 103
    @property
107 104
    def type(self):
108 105
        return self.get('error-type', None)
109
    
106

  
110 107
    @property
111 108
    def severity(self):
112 109
        return self.get('error-severity', None)
113
    
110

  
114 111
    @property
115 112
    def tag(self):
116 113
        return self.get('error-tag', None)
117
    
114

  
118 115
    @property
119 116
    def path(self):
120 117
        return self.get('error-path', None)
121
    
118

  
122 119
    @property
123 120
    def message(self):
124 121
        return self.get('error-message', None)
125
    
122

  
126 123
    @property
127 124
    def info(self):
128 125
        return self.get('error-info', None)
129 126

  
130 127
    ## dictionary interface
131
    
128

  
132 129
    __getitem__ = lambda self, key: self._dict.__getitem__(key)
133
    
130

  
134 131
    __iter__ = lambda self: self._dict.__iter__()
135
    
132

  
136 133
    __contains__ = lambda self, key: self._dict.__contains__(key)
137
    
134

  
138 135
    keys = lambda self: self._dict.keys()
139
    
136

  
140 137
    get = lambda self, key, default: self._dict.get(key, default)
141
        
138

  
142 139
    iteritems = lambda self: self._dict.iteritems()
143
    
140

  
144 141
    iterkeys = lambda self: self._dict.iterkeys()
145
    
142

  
146 143
    itervalues = lambda self: self._dict.itervalues()
147
    
144

  
148 145
    values = lambda self: self._dict.values()
149
    
146

  
150 147
    items = lambda self: self._dict.items()
151
    
148

  
152 149
    __repr__ = lambda self: repr(self._dict)
153 150

  
154 151

  
155 152
class RPCReplyListener(SessionListener):
156
    
153

  
157 154
    # one instance per session
158 155
    def __new__(cls, session):
159 156
        instance = session.get_listener_instance(cls)
......
164 161
            instance._pipelined = session.can_pipeline
165 162
            session.add_listener(instance)
166 163
        return instance
167
    
164

  
168 165
    def register(self, id, rpc):
169 166
        with self._lock:
170 167
            self._id2rpc[id] = rpc
171
    
168

  
172 169
    def callback(self, root, raw):
173 170
        tag, attrs = root
174 171
        if content.unqualify(tag) != 'rpc-reply':
......
195 192
                logger.warning('<rpc-reply> without message-id received: %s' % raw)
196 193
        logger.debug('delivering to %r' % rpc)
197 194
        rpc.deliver(raw)
198
    
195

  
199 196
    def errback(self, err):
200 197
        for rpc in self._id2rpc.values():
201 198
            rpc.error(err)
202 199

  
203 200

  
204 201
class RPC(object):
205
    
202

  
206 203
    DEPENDS = []
207 204
    REPLY_CLS = RPCReply
208
    
205

  
209 206
    def __init__(self, session, async=False, timeout=None):
210 207
        if not session.can_pipeline:
211 208
            raise UserWarning('Asynchronous mode not supported for this device/session')
......
214 211
            for cap in self.DEPENDS:
215 212
                self._assert(cap)
216 213
        except AttributeError:
217
            pass        
214
            pass
218 215
        self._async = async
219 216
        self._timeout = timeout
220 217
        # keeps things simple instead of having a class attr that has to be locked
......
223 220
        self._listener = RPCReplyListener(session)
224 221
        self._listener.register(self._id, self)
225 222
        self._reply = None
223
        self._error = None
226 224
        self._reply_event = Event()
227
    
225

  
228 226
    def _build(self, opspec):
229 227
        "TODO: docstring"
230 228
        spec = {
231 229
            'tag': content.qualify('rpc'),
232
            'attributes': {'message-id': self._id},
230
            'attrib': {'message-id': self._id},
233 231
            'subtree': opspec
234 232
            }
235 233
        return content.dtree2xml(spec)
236
    
234

  
237 235
    def _request(self, op):
238 236
        req = self._build(op)
239 237
        self._session.send(req)
240 238
        if self._async:
241
            return (self._reply_event, self._error_event)
239
            return self._reply_event
242 240
        else:
243 241
            self._reply_event.wait(self._timeout)
244
            if self._reply_event.is_set():
242
            if self._reply_event.isSet():
245 243
                if self._error:
246 244
                    raise self._error
247 245
                self._reply.parse()
248 246
                return self._reply
249 247
            else:
250 248
                raise ReplyTimeoutError
251
    
249

  
252 250
    def request(self):
253 251
        return self._request(self.SPEC)
254
    
252

  
255 253
    def _delivery_hook(self):
256 254
        'For subclasses'
257 255
        pass
258
    
256

  
259 257
    def _assert(self, capability):
260 258
        if capability not in self._session.server_capabilities:
261 259
            raise MissingCapabilityError('Server does not support [%s]' % cap)
262
    
260

  
263 261
    def deliver(self, raw):
264 262
        self._reply = self.REPLY_CLS(raw)
265 263
        self._delivery_hook()
266 264
        self._reply_event.set()
267
    
265

  
268 266
    def error(self, err):
269 267
        self._error = err
270 268
        self._reply_event.set()
271
    
269

  
272 270
    @property
273 271
    def has_reply(self):
274 272
        return self._reply_event.is_set()
275
    
273

  
276 274
    @property
277 275
    def reply(self):
276
        if self.error:
277
            raise self._error
278 278
        return self._reply
279
    
279

  
280 280
    @property
281 281
    def id(self):
282 282
        return self._id
283
    
283

  
284 284
    @property
285 285
    def session(self):
286 286
        return self._session
287
    
287

  
288 288
    @property
289 289
    def reply_event(self):
290 290
        return self._reply_event
291
    
291

  
292 292
    def set_async(self, bool): self._async = bool
293 293
    async = property(fget=lambda self: self._async, fset=set_async)
294
    
294

  
295 295
    def set_timeout(self, timeout): self._timeout = timeout
296 296
    timeout = property(fget=lambda self: self._timeout, fset=set_timeout)

Also available in: Unified diff