Revision 3e022b7b

b/ncclient/operations/notification.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
from glue import Listener
16 15

  
17 16
class Notification:
18 17
    
19 18
    pass
20

  
21
class NotificationListener(Listener):
22
    
23
    pass
b/ncclient/operations/reply.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
def parse():
16
    
17
    pass
15
from xml.etree import cElementTree as ET
18 16

  
17
from ncclient.content import multiqualify as _
18
from ncclient.content import unqualify as __
19 19

  
20 20
class RPCReply:
21 21
    
22
    def __init__(self, event):
22
    def __init__(self, raw):
23 23
        self._raw = None
24
        self._errs = None
24
        self._parsed = False
25
        self._errors = []
25 26
    
26
    def __str__(self):
27
    def __repr__(self):
27 28
        return self._raw
28 29
    
29 30
    def parse(self):
30
        if not self._parsed:
31
            ok = RPCReplyParser.parse(self._raw)
32
            for err in errs:
33
                self._errs.append(RPCError(*err))
34
            self._parsed = True
31
        root = ET.fromstring(raw) # <rpc-reply> element
32
        
33
        # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
34
        oktags = _('ok')
35
        for oktag in oktags:
36
            if root.find(oktag) is not None:
37
                return
38
        
39
        # create RPCError objects from <rpc-error> elements
40
        errtags = _('rpc-error')
41
        for errtag in errtags:
42
            for err in root.getiterator(errtag): # a particular <rpc-error>
43
                d = {}
44
                for err_detail in err.getchildren(): # <error-type> etc..
45
                    d[__(err_detail)] = err_detail.text
46
                self._errors.append(RPCError(d))
47
            if self._errors:
48
                break
35 49
    
36 50
    @property
37 51
    def raw(self):
38 52
        return self._raw
39 53
    
40 54
    @property
41
    def parsed(self):
42
        return self._parsed
43
    
44
    @property
45 55
    def ok(self):
46
        return True if self._parsed and not self._errs else False
56
        if not self._parsed:
57
            self.parse()
58
        return bool(self._errors) # empty list = false
47 59
    
48 60
    @property
49 61
    def errors(self):
50
        return self._errs
62
        'List of RPCError objects. Will be empty if no <rpc-error> elements in reply.'
63
        if not self._parsed:
64
            self.parse()
65
        return self._errors
51 66

  
52 67

  
53 68
class RPCError(Exception): # raise it if you like
54 69
    
55
    def __init__(self, raw, err_dict):
56
        self._raw = raw
70
    def __init__(self, err_dict):
57 71
        self._dict = err_dict
58
    
59
    def __str__(self):
60
        # TODO
61
        return self._raw
62
    
63
    def __dict__(self):
64
        return self._dict
72
        if self.message is not None:
73
            Exception.__init__(self, self.message)
74
        else:
75
            Exception.__init__(self)
65 76
    
66 77
    @property
67 78
    def raw(self):
68
        return self._raw
79
        return self._element.tostring()
69 80
    
70 81
    @property
71 82
    def type(self):
72
        return self._dict.get('type', None)
83
        return self.get('error-type', None)
73 84
    
74 85
    @property
75 86
    def severity(self):
76
        return self._dict.get('severity', None)
87
        return self.get('error-severity', None)
77 88
    
78 89
    @property
79 90
    def tag(self):
80
        return self._dict.get('tag', None)
91
        return self.get('error-tag', None)
81 92
    
82 93
    @property
83 94
    def path(self):
84
        return self._dict.get('path', None)
95
        return self.get('error-path', None)
85 96
    
86 97
    @property
87 98
    def message(self):
88
        return self._dict.get('message', None)
99
        return self.get('error-message', None)
89 100
    
90 101
    @property
91 102
    def info(self):
92
        return self._dict.get('info', None)
93

  
103
        return self.get('error-info', None)
94 104

  
95
class RPCReplyListener(Listener):
105
    ## dictionary interface
96 106
    
97
    # TODO - determine if need locking
107
    __getitem__ = lambda self, key: self._dict.__getitem__(key)
98 108
    
99
    # one instance per subject    
100
    def __new__(cls, subject):
101
        instance = subject.get_listener_instance(cls)
102
        if instance is None:
103
            instance = object.__new__(cls)
104
            instance._id2rpc = WeakValueDictionary()
105
            instance._errback = None
106
            subject.add_listener(instance)
107
        return instance
109
    __iter__ = lambda self: self._dict.__iter__()
108 110
    
109
    def __str__(self):
110
        return 'RPCReplyListener'
111
    __contains__ = lambda self, key: self._dict.__contains__(key)
111 112
    
112
    def set_errback(self, errback):
113
        self._errback = errback
114

  
115
    def register(self, msgid, rpc):
116
        self._id2rpc[msgid] = rpc
117
    
118
    def callback(self, root, raw):
119
        tag, attrs = root
120
        if __(tag) != 'rpc-reply':
121
            return
122
        for key in attrs:
123
            if __(key) == 'message-id':
124
                id = attrs[key]
125
                try:
126
                    rpc = self._id2rpc[id]
127
                    rpc.deliver(raw)
128
                except:
129
                    logger.warning('RPCReplyListener.callback: no RPC '
130
                                   + 'registered for message-id: [%s]' % id)
131
                break
132
        else:
133
            logger.warning('<rpc-reply> without message-id received: %s' % raw)
113
    keys = lambda self: self._dict.keys()
114
    
115
    get = lambda self, key, default: self._dict.get(key, default)
116
        
117
    iteritems = lambda self: self._dict.iteritems()
118
    
119
    iterkeys = lambda self: self._dict.iterkeys()
120
    
121
    itervalues = lambda self: self._dict.itervalues()
122
    
123
    values = lambda self: self._dict.values()
124
    
125
    items = lambda self: self._dict.items()
134 126
    
135
    def errback(self, err):
136
        logger.error('RPCReplyListener.errback: %r' % err)
137
        if self._errback is not None:
138
            self._errback(err)
127
    __repr__ = lambda self: repr(self._dict)
b/ncclient/operations/rpc.py
15 15
from threading import Event, Lock
16 16
from uuid import uuid1
17 17

  
18
from . import logger
19 18
from ncclient.content import TreeBuilder, BASE_NS
20
from reply import RPCReply, RPCReplyListener
19
from ncclient.glue import Listener
20

  
21
from . import logger
22
from reply import RPCReply
23

  
21 24

  
22 25
class RPC(object):
23 26
    
......
26 29
        self._id = uuid1().urn
27 30
        self._listener = RPCReplyListener(session)
28 31
        self._listener.register(self._id, self)
29
        self._reply = RPCReply()
32
        self._reply = None
30 33
        self._reply_event = Event()
31 34
    
32 35
    def _build(self, op, encoding='utf-8'):
33 36
        if isinstance(op, dict):
34 37
            return self.build_from_spec(self._id, op, encoding)
35
        elif isinstance(op, basestring): 
36
            return self.build_from_string(self._id, op, encoding)
37 38
        else:
38
            raise ValueError('Inappropriate value of tree spec.')
39
            return self.build_from_string(self._id, op, encoding)
39 40
    
40 41
    def _request(self, op):
41 42
        req = self._build(op)
42 43
        self._session.send(req)
43
        if reply_event is not None: # if we were provided an Event to use
44
            self._reply_event = reply_event
45
        else: # otherwise, block till response received and return it
46
            self._reply_event = Event()
44
        if async:
47 45
            self._reply_event.wait()
48 46
            self._reply.parse()
49
        return self._reply
47
            return self._reply
50 48
    
51
    def request(self, *args, **kwds):
52
        raise NotImplementedError
49
    def deliver(self, raw):
50
        self._reply = RPCReply(raw)
51
        self._reply_event.set()
53 52
    
54 53
    @property
55 54
    def has_reply(self):
56
        try:
57
            return self._reply_event.isSet()
58
        except TypeError: # reply_event is None
59
            return False
55
        return self._reply_event.isSet()
60 56
    
61 57
    @property
62 58
    def reply(self):
......
70 66
    def session(self):
71 67
        return self._session
72 68
    
69
    @property
70
    def reply_event(self):
71
        return self._reply_event
72
    
73 73
    @staticmethod
74 74
    def build_from_spec(msgid, opspec, encoding='utf-8'):
75 75
        "TODO: docstring"
......
87 87
        doc = (u'<rpc message-id="%s" xmlns="%s">%s</rpc>' %
88 88
               (msgid, BASE_NS, opstr)).encode(encoding)
89 89
        return '%s%s' % (decl, doc)
90

  
91

  
92
class RPCReplyListener(Listener):
93
    
94
    # TODO - determine if need locking
95
    
96
    # one instance per subject    
97
    def __new__(cls, subject):
98
        instance = subject.get_listener_instance(cls)
99
        if instance is None:
100
            instance = object.__new__(cls)
101
            instance._id2rpc = WeakValueDictionary()
102
            instance._errback = None
103
            subject.add_listener(instance)
104
        return instance
105
    
106
    def __str__(self):
107
        return 'RPCReplyListener'
108
    
109
    def set_errback(self, errback):
110
        self._errback = errback
111

  
112
    def register(self, msgid, rpc):
113
        self._id2rpc[msgid] = rpc
114
    
115
    def callback(self, root, raw):
116
        tag, attrs = root
117
        if __(tag) != 'rpc-reply':
118
            return
119
        for key in attrs:
120
            if __(key) == 'message-id':
121
                id = attrs[key]
122
                try:
123
                    rpc = self._id2rpc[id]
124
                    rpc.deliver(raw)
125
                except:
126
                    logger.warning('RPCReplyListener.callback: no RPC '
127
                                   + 'registered for message-id: [%s]' % id)
128
                break
129
        else:
130
            logger.warning('<rpc-reply> without message-id received: %s' % raw)
131
    
132
    def errback(self, err):
133
        logger.error('RPCReplyListener.errback: %r' % err)
134
        if self._errback is not None:
135
            self._errback(err)
b/ncclient/operations/session.py
25 25
    
26 26
    def deliver(self, reply):
27 27
        RPC.deliver(self, reply)
28
        # can't be too huge a reply, should be ok to parse in callback
29
        self._reply.parse()
30
        if self._reply.ok:
28
        # can't be too huge, should be ok to parse in callback
29
        if self._reply.ok: # (implicitly parse)
31 30
            self._session.expect_close()
32 31
        self._session.close()
33 32
    
b/ncclient/operations/subscribe.py
14 14

  
15 15
from rpc import RPC
16 16

  
17
from ncclient.glue import Listener
18

  
19
from notification import Notification
20

  
17 21
class CreateSubscription(RPC):    
22
    
18 23
    pass
19 24

  
25

  
26
class NotificationListener(Listener):
27
    
28
    pass

Also available in: Unified diff