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