1 # Copyright 2009 Shikhar Bhushan
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 from threading import Event, Lock
16 from uuid import uuid1
17 from weakref import WeakValueDictionary
19 from ncclient.content import TreeBuilder
20 from ncclient.content import qualify as _
21 from ncclient.content import unqualify as __
22 from ncclient.glue import Listener
25 from reply import RPCReply
30 def __init__(self, session, async=False):
31 self._session = session
33 self._id = uuid1().urn
34 self._listener = RPCReplyListener(session)
35 self._listener.register(self._id, self)
37 self._reply_event = Event()
39 def _build(self, op, encoding='utf-8'):
40 if isinstance(op, dict):
41 return self.build_from_spec(self._id, op, encoding)
43 return self.build_from_string(self._id, op, encoding)
45 def _request(self, op):
47 self._session.send(req)
49 return self._reply_event
51 self._reply_event.wait()
55 def _set_reply(self, raw):
56 self._reply = RPCReply(raw)
58 def _set_reply_event(self):
59 self._reply_event.set()
61 def _delivery_hook(self):
65 def deliver(self, raw):
68 self._set_reply_event()
72 return self._reply_event.isSet()
91 def reply_event(self):
92 return self._reply_event
95 def build_from_spec(msgid, opspec, encoding='utf-8'):
99 'attributes': {'message-id': msgid},
102 return TreeBuilder(spec).to_string(encoding)
105 def build_from_string(msgid, opstr, encoding='utf-8'):
107 decl = '<?xml version="1.0" encoding="%s"?>' % encoding
108 doc = (u'<rpc message-id="%s" xmlns="%s">%s</rpc>' %
109 (msgid, BASE_NS, opstr)).encode(encoding)
110 return '%s%s' % (decl, doc)
113 class RPCReplyListener(Listener):
115 # TODO - determine if need locking
117 # one instance per subject
118 def __new__(cls, subject):
119 instance = subject.get_listener_instance(cls)
121 instance = object.__new__(cls)
122 instance._id2rpc = WeakValueDictionary()
123 instance._errback = None
124 subject.add_listener(instance)
128 return 'RPCReplyListener'
130 def set_errback(self, errback):
131 self._errback = errback
133 def register(self, id, rpc):
134 self._id2rpc[id] = rpc
136 def callback(self, root, raw):
138 if __(tag) != 'rpc-reply':
141 if __(key) == 'message-id':
144 rpc = self._id2rpc[id]
147 logger.warning('[RPCReplyListener.callback] no RPC '
148 + 'registered for message-id: [%s]' % id)
149 logger.debug('[RPCReplyListener.callback] registered: %r '
150 % dict(self._id2rpc))
151 except Exception as e:
152 logger.debug('[RPCReplyListener.callback] error - %r' % e)
155 logger.warning('<rpc-reply> without message-id received: %s' % raw)
157 def errback(self, err):
158 if self._errback is not None: