Revision 9667bcb2 ncclient/operations/rpc.py

b/ncclient/operations/rpc.py
15 15
from threading import Event, Lock
16 16
from uuid import uuid1
17 17

  
18
from ncclient import xml_
18
from ncclient.xml_ import *
19 19
from ncclient.transport import SessionListener
20 20

  
21 21
from errors import OperationError, TimeoutExpiredError, MissingCapabilityError
22 22

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

  
26 26

  
27 27
class RPCReply:
......
55 55
        """Parse the *<rpc-reply>*"""
56 56
        if self._parsed:
57 57
            return
58
        root = self._root = xml_.xml2ele(self._raw) # <rpc-reply> element
59
        # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
60
        ok = xml_.find(root, 'ok', nslist=xml_.NSLIST)
61
        if ok is not None:
62
            logger.debug('parsed [%s]' % ok.tag)
63
        else: # create RPCError objects from <rpc-error> elements
64
            error = xml_.find(root, 'rpc-error', nslist=xml_.NSLIST)
58
        root = self._root = to_ele(self._raw) # The <rpc-reply> element
59
        # Per RFC 4741 an <ok/> tag is sent when there are no errors or warnings
60
        ok = root.find(qualify("ok"))
61
        if ok is None:
62
            # Create RPCError objects from <rpc-error> elements
63
            error = root.find(qualify("rpc-error"))
65 64
            if error is not None:
66
                logger.debug('parsed [%s]' % error.tag)
67 65
                for err in root.getiterator(error.tag):
68
                    # process a particular <rpc-error>
69
                    d = {}
70
                    for err_detail in err.getchildren(): # <error-type> etc..
71
                        tag = xml_.unqualify(err_detail.tag)
72
                        if tag != 'error-info':
73
                            d[tag] = err_detail.text.strip()
74
                        else:
75
                            d[tag] = xml_.ele2xml(err_detail)
76
                    self._errors.append(RPCError(d))
66
                    # Process a particular <rpc-error>
67
                    self._errors.append(RPCError(err))
77 68
        self._parsing_hook(root)
78 69
        self._parsed = True
79 70

  
......
110 101
        return self._errors
111 102

  
112 103

  
113
class RPCError(OperationError): # raise it if you like
104
class RPCError(OperationError):
114 105

  
115
    """Represents an *<rpc-error>*. It is an instance of :exc:`OperationError`
106
    """Represents an *<rpc-error>*. It is a type of :exc:`OperationError`
116 107
    and can be raised like any other exception."""
117 108

  
118
    def __init__(self, err_dict):
119
        self._dict = err_dict
109
    def __init__(self, err):
110
        self._type = None
111
        self._severity = None
112
        self._info = None
113
        self._tag = None
114
        self._path = None
115
        self._message = None
116
        for subele in err:
117
            if subele.tag == qualify("error-tag"):
118
                self._tag = subele.text
119
            elif subele.tag == qualify("error-severity"):
120
                self._severity = subele.text
121
            elif subele.tag == qualify("error-info"):
122
                self._info = subele.text
123
            elif subele.tag == qualify("error-path"):
124
                self._path = subele.text
125
            elif subele.tag == qualify("error-message"):
126
                self._message = subele.text
120 127
        if self.message is not None:
121 128
            OperationError.__init__(self, self.message)
122 129
        else:
......
125 132
    @property
126 133
    def type(self):
127 134
        "`string` representing text of *error-type* element"
128
        return self.get('error-type', None)
135
        return self._type
129 136

  
130 137
    @property
131 138
    def severity(self):
132 139
        "`string` representing text of *error-severity* element"
133
        return self.get('error-severity', None)
140
        return self._severity
134 141

  
135 142
    @property
136 143
    def tag(self):
137 144
        "`string` representing text of *error-tag* element"
138
        return self.get('error-tag', None)
145
        return self._tag
139 146

  
140 147
    @property
141 148
    def path(self):
142 149
        "`string` or :const:`None`; representing text of *error-path* element"
143
        return self.get('error-path', None)
150
        return self._path
144 151

  
145 152
    @property
146 153
    def message(self):
147 154
        "`string` or :const:`None`; representing text of *error-message* element"
148
        return self.get('error-message', None)
155
        return self._message
149 156

  
150 157
    @property
151 158
    def info(self):
152 159
        "`string` (XML) or :const:`None`, representing *error-info* element"
153
        return self.get('error-info', None)
154

  
155
    ## dictionary interface
156

  
157
    __getitem__ = lambda self, key: self._dict.__getitem__(key)
158

  
159
    __iter__ = lambda self: self._dict.__iter__()
160

  
161
    __contains__ = lambda self, key: self._dict.__contains__(key)
162

  
163
    keys = lambda self: self._dict.keys()
164

  
165
    get = lambda self, key, default: self._dict.get(key, default)
166

  
167
    iteritems = lambda self: self._dict.iteritems()
168

  
169
    iterkeys = lambda self: self._dict.iterkeys()
170

  
171
    itervalues = lambda self: self._dict.itervalues()
172

  
173
    values = lambda self: self._dict.values()
174

  
175
    items = lambda self: self._dict.items()
176

  
177
    __repr__ = lambda self: repr(self._dict)
160
        return self._info
178 161

  
179 162

  
180 163
class RPCReplyListener(SessionListener):
......
198 181

  
199 182
    def callback(self, root, raw):
200 183
        tag, attrs = root
201
        if xml_.unqualify(tag) != 'rpc-reply':
184
        if tag != qualify("rpc-reply"):
202 185
            return
203 186
        for key in attrs: # in the <rpc-reply> attributes
204
            if xml_.unqualify(key) == 'message-id': # if we found msgid attr
187
            logger.debug("key=%s" % key)
188
            if key == "message-id": # if we found msgid attr
205 189
                id = attrs[key] # get the msgid
206
                try:
207
                    with self._lock:
208
                        rpc = self._id2rpc.get(id) # the corresponding rpc
209
                        logger.debug('delivering to %r' % rpc)
190
                with self._lock:
191
                    try:                    
192
                        rpc = self._id2rpc[id] # the corresponding rpc
193
                        logger.debug("Delivering to %r" % rpc)
210 194
                        rpc.deliver_reply(raw)
211
                except KeyError:
212
                    raise OperationError('Unknown message-id: %s', id)
213
                # no catching other exceptions, fail loudly if must
214
                else:
215
                    # if no error delivering, can del the reference to the RPC
216
                    del self._id2rpc[id]
217
                    break
195
                    except KeyError:
196
                        raise OperationError("Unknown message-id: %s", id)
197
                    # no catching other exceptions, fail loudly if must
198
                    else:
199
                        # if no error delivering, can del the reference to the RPC
200
                        del self._id2rpc[id]
201
                        break
218 202
        else:
219
            raise OperationError('Could not find "message-id" attribute in <rpc-reply>')
203
            raise OperationError("Could not find 'message-id' attribute in <rpc-reply>")
220 204
    
221 205
    def errback(self, err):
222 206
        try:
......
244 228
    # subclass of :class:`RPCReply`.
245 229
    REPLY_CLS = RPCReply
246 230

  
247
    def __init__(self, session, async=False, timeout=None, raise_mode='none'):
231
    def __init__(self, session, async=False, timeout=None, raise_mode="none"):
248 232
        self._session = session
249 233
        try:
250 234
            for cap in self.DEPENDS:
......
254 238
        self._async = async
255 239
        self._timeout = timeout
256 240
        self._raise_mode = raise_mode
257
        # keeps things simple instead of having a class attr that has to be locked
258
        self._id = uuid1().urn
241
        self._id = uuid1().urn # Keeps things simple instead of having a class attr that has to be locked
259 242
        self._listener = RPCReplyListener(session)
260 243
        self._listener.register(self._id, self)
261 244
        self._reply = None
262 245
        self._error = None
263 246
        self._event = Event()
264 247

  
265
    def _build(self, opspec):
248
    def _build(self, subele):
266 249
        # internal
267
        spec = {
268
            'tag': 'rpc',
269
            'attrib': {
270
                'xmlns': xml_.BASE_NS_1_0,
271
                'message-id': self._id
272
                },
273
            'subtree': [ opspec ]
274
            }
275
        return xml_.dtree2xml(spec)
250
        ele = new_ele("rpc", {"message-id": self._id}, xmlns=BASE_NS_1_0)
251
        ele.append(subele)
252
        return to_xml(ele)
276 253

  
277 254
    def _request(self, op):
278 255
        """Subclasses call this method to make the RPC request.
......
288 265
        :type opspec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
289 266
        :rtype: :class:`RPCReply` (sync) or :class:`RPC` (async)
290 267
        """
291
        logger.debug('request %r with opsepc=%r' % (self, op))
268
        logger.info('Requesting %r' % self.__class__.__name__)
292 269
        req = self._build(op)
293 270
        self._session.send(req)
294 271
        if self._async:
295
            logger.debug('async, returning')
272
            logger.debug('Async request, returning %r', self)
296 273
            return self
297 274
        else:
298
            logger.debug('sync, will wait for timeout=%r' % self._timeout)
275
            logger.debug('Sync request, will wait for timeout=%r' %
276
                         self._timeout)
299 277
            self._event.wait(self._timeout)
300 278
            if self._event.isSet():
301 279
                if self._error:
......
330 308
    def deliver_reply(self, raw):
331 309
        # internal use
332 310
        self._reply = self.REPLY_CLS(raw)
333
        #self._delivery_hook() -- usecase?!
334 311
        self._event.set()
335 312

  
336 313
    def deliver_error(self, err):
337 314
        # internal use
338 315
        self._error = err
339
        #self._delivery_hook() -- usecase?!
340 316
        self._event.set()
341 317

  
342 318
    @property
......
379 355
            raise UserWarning('Asynchronous mode not supported for this device/session')
380 356

  
381 357
    def set_raise_mode(self, mode):
382
        assert(choice in ('all', 'errors', 'none'))
358
        assert(choice in ("all", "errors", "none"))
383 359
        self._raise_mode = mode
384 360

  
385 361
    def set_timeout(self, timeout):
386
        """Set the timeout for synchronous waiting defining how long the RPC
387
        request will block on a reply before raising an error."""
362
        """Set the timeout for synchronous waiting; defining how long the RPC
363
        request will block on a reply before raising an error. Irrelevant for
364
        asynchronous usage."""
388 365
        self._timeout = timeout
389 366

  
390 367
    #: Whether this RPC is asynchronous
391
    async = property(fget=lambda self: self._async, fset=set_async)
368
    is_async = property(fget=lambda self: self._async, fset=set_async)
392 369

  
393 370
    #: Timeout for synchronous waiting
394 371
    timeout = property(fget=lambda self: self._timeout, fset=set_timeout)

Also available in: Unified diff