fe69530a0b3c73a77e9acdc279b371bf64a48c1b
[ncclient] / ncclient / operations / rpc.py
1 # Copyright 2009 Shikhar Bhushan
2 #
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
6 #
7 #    http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 from threading import Event, Lock
16 from uuid import uuid1
17 from weakref import WeakValueDictionary
18
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
23
24 from . import logger
25 from reply import RPCReply
26
27
28 class RPC(object):
29     
30     def __init__(self, session, async=False):
31         self._session = session
32         self._async = async
33         self._id = uuid1().urn
34         self._listener = RPCReplyListener(session)
35         self._listener.register(self._id, self)
36         self._reply = None
37         self._reply_event = Event()
38     
39     def _build(self, op, encoding='utf-8'):
40         if isinstance(op, dict):
41             return self.build_from_spec(self._id, op, encoding)
42         else:
43             return self.build_from_string(self._id, op, encoding)
44     
45     def _request(self, op):
46         req = self._build(op)
47         self._session.send(req)
48         if self._async:
49             return self._reply_event
50         else:
51             self._reply_event.wait()
52             self._reply.parse()
53             return self._reply
54     
55     def _set_reply(self, raw):
56         self._reply = RPCReply(raw)
57     
58     def _set_reply_event(self):
59         self._reply_event.set()
60     
61     def _delivery_hook(self):
62         'For subclasses'
63         pass
64     
65     def deliver(self, raw):
66         self._set_reply(raw)
67         self._delivery_hook()
68         self._set_reply_event()
69     
70     @property
71     def has_reply(self):
72         return self._reply_event.isSet()
73     
74     @property
75     def reply(self):
76         return self._reply
77     
78     @property
79     def is_async(self):
80         return self._async
81     
82     @property
83     def id(self):
84         return self._id
85     
86     @property
87     def session(self):
88         return self._session
89     
90     @property
91     def reply_event(self):
92         return self._reply_event
93     
94     @staticmethod
95     def build_from_spec(msgid, opspec, encoding='utf-8'):
96         "TODO: docstring"
97         spec = {
98             'tag': _('rpc'),
99             'attributes': {'message-id': msgid},
100             'children': opspec
101             }
102         return TreeBuilder(spec).to_string(encoding)
103     
104     @staticmethod
105     def build_from_string(msgid, opstr, encoding='utf-8'):
106         "TODO: docstring"
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)
111
112
113 class RPCReplyListener(Listener):
114     
115     # TODO - determine if need locking
116     
117     # one instance per subject    
118     def __new__(cls, subject):
119         instance = subject.get_listener_instance(cls)
120         if instance is None:
121             instance = object.__new__(cls)
122             instance._id2rpc = WeakValueDictionary()
123             instance._errback = None
124             subject.add_listener(instance)
125         return instance
126     
127     def __str__(self):
128         return 'RPCReplyListener'
129     
130     def set_errback(self, errback):
131         self._errback = errback
132
133     def register(self, id, rpc):
134         self._id2rpc[id] = rpc
135     
136     def callback(self, root, raw):
137         tag, attrs = root
138         if __(tag) != 'rpc-reply':
139             return
140         for key in attrs:
141             if __(key) == 'message-id':
142                 id = attrs[key]
143                 try:
144                     rpc = self._id2rpc[id]
145                     rpc.deliver(raw)
146                 except KeyError:
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)
153                 break
154         else:
155             logger.warning('<rpc-reply> without message-id received: %s' % raw)
156     
157     def errback(self, err):
158         if self._errback is not None:
159             self._errback(err)