Revision 4bc8021f ncclient/operations/rpc.py

b/ncclient/operations/rpc.py
24 24
logger = logging.getLogger("ncclient.operations.rpc")
25 25

  
26 26

  
27
class RPCReply:
28

  
29
    """Represents an *<rpc-reply>*. Only concerns itself with whether the
30
    operation was successful.
31

  
32
    .. note::
33
        If the reply has not yet been parsed there is an implicit, one-time
34
        parsing overhead to accessing the attributes defined by this class and
35
        any subclasses.
36
    """
37

  
38
    def __init__(self, raw):
39
        self._raw = raw
40
        self._parsed = False
41
        self._root = None
42
        self._errors = []
43

  
44
    def __repr__(self):
45
        return self._raw
46

  
47
    def _parsing_hook(self, root):
48
        """Subclass can implement.
49

  
50
        :type root: :class:`~xml.etree.ElementTree.Element`
51
        """
52
        pass
53

  
54
    def parse(self):
55
        """Parse the *<rpc-reply>*"""
56
        if self._parsed:
57
            return
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"))
64
            if error is not None:
65
                for err in root.getiterator(error.tag):
66
                    # Process a particular <rpc-error>
67
                    self._errors.append(RPCError(err))
68
        self._parsing_hook(root)
69
        self._parsed = True
70

  
71
    @property
72
    def xml(self):
73
        "*<rpc-reply>* as returned"
74
        return self._raw
75

  
76
    @property
77
    def ok(self):
78
        "Boolean value indicating if there were no errors."
79
        if not self._parsed:
80
            self.parse()
81
        return not self._errors # empty list => false
82

  
83
    @property
84
    def error(self):
85
        """Short for :attr:`errors` [0]; :const:`None` if there were no errors.
86
        """
87
        if not self._parsed:
88
            self.parse()
89
        if self._errors:
90
            return self._errors[0]
91
        else:
92
            return None
93

  
94
    @property
95
    def errors(self):
96
        """`list` of :class:`RPCError` objects. Will be empty if there were no
97
        *<rpc-error>* elements in reply.
98
        """
99
        if not self._parsed:
100
            self.parse()
101
        return self._errors
102

  
103

  
104 27
class RPCError(OperationError):
105 28

  
106
    """Represents an *<rpc-error>*. It is a type of :exc:`OperationError`
107
    and can be raised like any other exception."""
29
    """Represents an *rpc-error*. It is a type of :exc:`OperationError` and can be raised like any
30
    other exception."""
108 31
    
109 32
    tag_to_attr = {
110 33
        qualify("error-type"): "_type",
......
115 38
        qualify("error-message"): "_message"
116 39
    }
117 40
    
118
    def __init__(self, err):
41
    def __init__(self, raw):
42
        self._raw = raw
119 43
        for attr in RPCError.tag_to_attr.values():
120 44
            setattr(self, attr, None)
121
        for subele in err:
45
        for subele in raw:
122 46
            attr = RPCError.tag_to_attr.get(subele.tag, None)
123 47
            if attr is not None:
124 48
                setattr(self, attr, subele.text if attr != "_info" else to_xml(subele) )
......
130 54
    def to_dict(self):
131 55
        return dict([ (attr[1:], getattr(self, attr)) for attr in RPCError.tag_to_attr.values() ])
132 56
    
57
    "*rpc-error* element as returned."
58
    @property
59
    def xml(self):
60
        return self._raw
61
    
133 62
    @property
134 63
    def type(self):
135
        "`string` representing text of *error-type* element"
64
        "`string` representing text of *error-type* element."
136 65
        return self._type
137 66
    
138 67
    @property
139 68
    def tag(self):
140
        "`string` representing text of *error-tag* element"
69
        "`string` representing text of *error-tag* element."
141 70
        return self._tag
142 71
    
143 72
    @property
144 73
    def severity(self):
145
        "`string` representing text of *error-severity* element"
74
        "`string` representing text of *error-severity* element."
146 75
        return self._severity
147 76
    
148 77
    @property
149 78
    def path(self):
150
        "`string` or :const:`None`; representing text of *error-path* element"
79
        "`string` or :const:`None`; representing text of *error-path* element."
151 80
        return self._path
152 81
    
153 82
    @property
154 83
    def message(self):
155
        "`string` or :const:`None`; representing text of *error-message* element"
84
        "`string` or :const:`None`; representing text of *error-message* element."
156 85
        return self._message
157 86
    
158 87
    @property
159 88
    def info(self):
160
        "`string` (XML) or :const:`None`, representing *error-info* element"
89
        "`string` (XML) or :const:`None`; representing *error-info* element."
161 90
        return self._info
162 91

  
163 92

  
93
class RPCReply:
94

  
95
    """Represents an *rpc-reply*. Only concerns itself with whether the operation was successful.
96

  
97
    .. note::
98
        If the reply has not yet been parsed there is an implicit, one-time parsing overhead to
99
        accessing the attributes defined by this class and any subclasses.
100
    """
101
    
102
    ERROR_CLS = RPCError
103
    "Subclasses can specify a different error class, but it should be a subclass of `RPCError`."
104
    
105
    def __init__(self, raw):
106
        self._raw = raw
107
        self._parsed = False
108
        self._root = None
109
        self._errors = []
110

  
111
    def __repr__(self):
112
        return self._raw
113
    
114
    def parse(self):
115
        "Parses the *rpc-reply*."
116
        if self._parsed: return
117
        root = self._root = to_ele(self._raw) # The <rpc-reply> element
118
        # Per RFC 4741 an <ok/> tag is sent when there are no errors or warnings
119
        ok = root.find(qualify("ok"))
120
        if ok is None:
121
            # Create RPCError objects from <rpc-error> elements
122
            error = root.find(qualify("rpc-error"))
123
            if error is not None:
124
                for err in root.getiterator(error.tag):
125
                    # Process a particular <rpc-error>
126
                    self._errors.append(ERROR_CLS(err))
127
        self._parsed = True
128
    
129
    @property
130
    def xml(self):
131
        "*rpc-reply* element as returned."
132
        return self._raw
133
    
134
    @property
135
    def ok(self):
136
        "Boolean value indicating if there were no errors."
137
        return not self.errors # empty list => false
138
    
139
    @property
140
    def error(self):
141
        "Returns the first `RPCError` and :const:`None` if there were no errors."
142
        self.parse()
143
        if self._errors:
144
            return self._errors[0]
145
        else:
146
            return None
147
    
148
    @property
149
    def errors(self):
150
        """`list` of `RPCError` objects. Will be empty if there were no *rpc-error* elements in
151
        reply."""
152
        self.parse()
153
        return self._errors
154

  
155

  
164 156
class RPCReplyListener(SessionListener): # internal use
165 157
    
166 158
    creation_lock = Lock()
......
212 204

  
213 205

  
214 206
class RPC(object):
215

  
216
    """Base class for all operations.
217

  
218
    Directly corresponds to *<rpc>* requests. Handles making the request, and
219
    taking delivery of the reply.
220
    """
221

  
222
    #: Subclasses can specify their dependencies on capabilities. List of URI's
223
    # or abbreviated names, e.g. ':writable-running'. These are verified at the
224
    # time of object creation. If the capability is not available, a
225
    # :exc:`MissingCapabilityError` is raised.
207
    
208
    """Base class for all operations, directly corresponding to *rpc* requests. Handles making the
209
    request, and taking delivery of the reply."""
210
    
226 211
    DEPENDS = []
227

  
228
    #: Subclasses can specify a different reply class, but it must be a
229
    # subclass of :class:`RPCReply`.
212
    """Subclasses can specify their dependencies on capabilities. List of URI's or abbreviated
213
    names, e.g. ':writable-running'. These are verified at the time of instantiation. If the
214
    capability is not available, a :exc:`MissingCapabilityError` is raised.
215
    """
216
    
230 217
    REPLY_CLS = RPCReply
231

  
218
    "Subclasses can specify a different reply class, but it should be a subclass of `RPCReply`."
219
    
232 220
    def __init__(self, session, async=False, timeout=None, raise_mode="none"):
233 221
        self._session = session
234 222
        try:
......
239 227
        self._async = async
240 228
        self._timeout = timeout
241 229
        self._raise_mode = raise_mode
242
        self._id = uuid1().urn # Keeps things simple instead of having a class attr that has to be locked
230
        self._id = uuid1().urn # Keeps things simple instead of having a class attr with running ID that has to be locked
243 231
        self._listener = RPCReplyListener(session)
244 232
        self._listener.register(self._id, self)
245 233
        self._reply = None
246 234
        self._error = None
247 235
        self._event = Event()
248

  
249
    def _build(self, subele):
250
        # internal
236
    
237
    def _wrap(self, subele):
238
        # internal use
251 239
        ele = new_ele("rpc", {"message-id": self._id}, xmlns=BASE_NS_1_0)
252 240
        ele.append(subele)
253 241
        return to_xml(ele)
254 242

  
255 243
    def _request(self, op):
256
        """Subclasses call this method to make the RPC request.
244
        """Implementations of :meth:`request` call this method to send the request and process the
245
        reply.
257 246
        
258
        In synchronous mode, waits until the reply is received and returns
259
        :class:`RPCReply`.
247
        In synchronous mode, blocks until the reply is received and returns `RPCReply`. Depending on
248
        the :attr:`raise_mode` a *rpc-error* element in the reply may lead to an :exc:`RPCError`
249
        exception.
260 250
        
261
        In asynchronous mode, returns immediately, returning a reference to this
262
        object. The :attr:`event` attribute will be set when the reply has been
263
        received (see :attr:`reply`) or an error occured (see :attr:`error`).
251
        In asynchronous mode, returns immediately, returning *self*. The :attr:`event` attribute
252
        will be set when the reply has been received (see :attr:`reply`) or an error occured (see
253
        :attr:`error`).
264 254
        
265
        :type opspec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
266
        :rtype: :class:`RPCReply` (sync) or :class:`RPC` (async)
255
        :param op: operation to be requested
256
        :type ops: `~xml.etree.ElementTree.Element`
257
        
258
        :rtype: `RPCReply` (sync) or `RPC` (async)
267 259
        """
268 260
        logger.info('Requesting %r' % self.__class__.__name__)
269
        req = self._build(op)
261
        req = self._wrap(op)
270 262
        self._session.send(req)
271 263
        if self._async:
272 264
            logger.debug('Async request, returning %r', self)
......
292 284
                raise TimeoutExpiredError
293 285

  
294 286
    def request(self, *args, **kwds):
295
        "Subclasses implement this method."
296
        return self._request(self.SPEC)
287
        """Subclasses must implement this method. Typically only the request needs to be built as an
288
        `~xml.etree.ElementTree.Element` and everything else can be handed off to
289
        :meth:`_request`."""
290
        pass
297 291
    
298 292
    def _assert(self, capability):
299
        """Subclasses can use this method to verify that a capability is available
300
        with the NETCONF server, before making a request that requires it. A
301
        :exc:`MissingCapabilityError` will be raised if the capability is not
302
        available."""
293
        """Subclasses can use this method to verify that a capability is available with the NETCONF
294
        server, before making a request that requires it. A :exc:`MissingCapabilityError` will be
295
        raised if the capability is not available."""
303 296
        if capability not in self._session.server_capabilities:
304 297
            raise MissingCapabilityError('Server does not support [%s]' %
305 298
                                         capability)
......
313 306
        # internal use
314 307
        self._error = err
315 308
        self._event.set()
316

  
309
    
317 310
    @property
318 311
    def reply(self):
319
        ":class:`RPCReply` element if reply has been received or :const:`None`"
312
        "`RPCReply` element if reply has been received or :const:`None`"
320 313
        return self._reply
321

  
314
    
322 315
    @property
323 316
    def error(self):
324 317
        """:exc:`Exception` type if an error occured or :const:`None`.
325 318
        
326 319
        .. note::
327
            This represents an error which prevented a reply from being
328
            received. An *<rpc-error>* does not fall in that category -- see
329
            :class:`RPCReply` for that.
320
            This represents an error which prevented a reply from being received. An *rpc-error*
321
            does not fall in that category -- see `RPCReply` for that.
330 322
        """
331 323
        return self._error
332

  
324
    
333 325
    @property
334 326
    def id(self):
335
        "The *message-id* for this RPC"
327
        "The *message-id* for this RPC."
336 328
        return self._id
337

  
329
    
338 330
    @property
339 331
    def session(self):
340
        """The :class:`~ncclient.transport.Session` object associated with this
341
        RPC"""
332
        "The `~ncclient.transport.Session` object associated with this RPC."
342 333
        return self._session
343 334

  
344 335
    @property
345 336
    def event(self):
346
        """:class:`~threading.Event` that is set when reply has been received or
347
        error occured."""
337
        """`~threading.Event` that is set when reply has been received or when an error preventing
338
        delivery of the reply occurs.
339
        """
348 340
        return self._event
349 341

  
350 342
    def set_async(self, async=True):
351
        """Set asynchronous mode for this RPC."""
352 343
        self._async = async
353 344
        if async and not session.can_pipeline:
354 345
            raise UserWarning('Asynchronous mode not supported for this device/session')
......
358 349
        self._raise_mode = mode
359 350

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

  
366
    #: Whether this RPC is asynchronous
354
    raise_mode = property(fget=lambda self: self._raise_mode, fset=set_raise_mode)
355
    """Depending on this exception raising mode, an *rpc-error* in the reply may be raised as
356
    :exc:`RPCError` exceptions. Valid values:
357
    
358
    * ``"all"`` -- any kind of *rpc-error* (error or warning)
359
    * ``"errors"`` -- when the *error-type* element says it is an error
360
    * ``"none"`` -- neither
361
    """
362
    
367 363
    is_async = property(fget=lambda self: self._async, fset=set_async)
368

  
369
    #: Timeout for synchronous waiting
364
    """Specifies whether this RPC will be / was requested asynchronously. By default RPC's are
365
    synchronous.
366
    """
367
    
370 368
    timeout = property(fget=lambda self: self._timeout, fset=set_timeout)
369
    """Timeout in seconds for synchronous waiting defining how long the RPC request will block on a
370
    reply before raising :exc:`TimeoutExpiredError`. By default there is no timeout, represented by
371
    :const:`None`.
372
    
373
    Irrelevant for asynchronous usage.
374
    """

Also available in: Unified diff