Revision 65c6a607

b/ncclient/content.py
21 21
class ContentError(NCClientError):
22 22
    pass
23 23

  
24
### Namespace-related ###
24
### Namespace-related
25 25

  
26 26
BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
27 27
# and this is BASE_NS according to cisco devices...
......
38 38
# we'd like BASE_NS to be prefixed as "netconf"
39 39
register_namespace('netconf', BASE_NS)
40 40

  
41
qualify = lambda tag, ns=BASE_NS: '{%s}%s' % (ns, tag)
41
qualify = lambda tag, ns=BASE_NS: tag if ns is None else '{%s}%s' % (ns, tag)
42 42

  
43
# i would have written a def if lambdas weren't so much fun
44
multiqualify = lambda tag, nslist=(BASE_NS, CISCO_BS): [qualify(tag, ns)
45
                                                        for ns in nslist]
43
multiqualify = lambda tag, nslist=(BASE_NS, CISCO_BS): [qualify(tag, ns) for ns in nslist]
46 44

  
47 45
unqualify = lambda tag: tag[tag.rfind('}')+1:]
48 46

  
49
### XML with Python data structures
50

  
51
def to_element(spec):
52
    "TODO: docstring"
53
    if iselement(spec):
54
        return spec
55
    elif isinstance(spec, basestring):
56
        return ET.XML(spec)
57
    if not isinstance(spec, dict):
58
        raise ContentError("Invalid tree spec")
59
    if 'tag' in spec:
60
        ele = ET.Element(spec.get('tag'), spec.get('attributes', {}))
61
        ele.text = spec.get('text', '')
62
        ele.tail = spec.get('tail', '')
63
        subtree = spec.get('subtree', [])
64
        # might not be properly specified as list but may be dict
65
        if isinstance(subtree, dict):
66
            subtree = [subtree]
67
        for subele in subtree:
68
            ele.append(XMLConverter.build(subele))
69
        return ele
70
    elif 'comment' in spec:
71
        return ET.Comment(spec.get('comment'))
72
    else:
73
        raise ContentError('Invalid tree spec')
74

  
75
def from_xml(xml):
76
    return ET.fromstring(xml)
77

  
78
def to_xml(repr, encoding='utf-8'):
79
    "TODO: docstring"
80
    xml = ET.tostring(to_element(repr), encoding)
81
    # some etree versions don't include xml decl with utf-8
82
    # this is a problem with some devices
83
    return (xml if xml.startswith('<?xml')
84
            else '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml))
85

  
86

  
87
## Utility functions
47
### Other utility functions
88 48

  
89 49
iselement = ET.iselement
90 50

  
......
103 63
    return found
104 64

  
105 65
def parse_root(raw):
106
    '''Internal use.
107
    Parse the top-level element from XML string.
66
    '''Parse the top-level element from XML string.
108 67
    
109 68
    Returns a `(tag, attributes)` tuple, where `tag` is a string representing
110 69
    the qualified name of the root element and `attributes` is an
......
121 80
    if req_attrs is not None:
122 81
        pass # TODO
123 82
    return rep
83

  
84
### XML with Python data structures
85

  
86
dtree2ele = DictTree.Element
87
dtree2xml = DictTree.XML
88
ele2dtree = Element.DictTree
89
ele2xml = Element.XML
90
xml2dtree = XML.DictTree
91
xml2ele = XML.Element
92

  
93
class DictTree:
94

  
95
    @staticmethod
96
    def Element(spec):
97
        if iselement(spec):
98
            return spec
99
        elif isinstance(spec, basestring):
100
            return XML.Element(spec)
101
        if not isinstance(spec, dict):
102
            raise ContentError("Invalid tree spec")
103
        if 'tag' in spec:
104
            ele = ET.Element(spec.get('tag'), spec.get('attributes', {}))
105
            ele.text = spec.get('text', '')
106
            ele.tail = spec.get('tail', '')
107
            subtree = spec.get('subtree', [])
108
            # might not be properly specified as list but may be dict
109
            if isinstance(subtree, dict):
110
                subtree = [subtree]
111
            for subele in subtree:
112
                ele.append(DictTree.Element(subele))
113
            return ele
114
        elif 'comment' in spec:
115
            return ET.Comment(spec.get('comment'))
116
        else:
117
            raise ContentError('Invalid tree spec')
118
    
119
    @staticmethod
120
    def XML(spec):
121
        Element.XML(DictTree.Element(spec))
122

  
123
class Element:
124
    
125
    @staticmethod
126
    def DictTree(ele):
127
        return {
128
            'tag': ele.tag,
129
            'attributes': ele.attrib,
130
            'text': ele.text,
131
            'tail': ele.tail,
132
            'subtree': [ Element.DictTree(child) for child in root.getchildren() ]
133
        }
134
    
135
    @staticmethod
136
    def XML(ele, encoding='utf-8'):
137
        xml = ET.tostring(ele, encoding)
138
        return xml if xml.startswith('<?xml') else '<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, xml)
139

  
140
class XML:
141
    
142
    @staticmethod
143
    def DictTree(ele):
144
        return Element.DictTree(Element.XML(ele))
145
    
146
    @staticmethod
147
    def Element(xml):
148
        return ET.fromstring(xml)
b/ncclient/operations/__init__.py
16 16

  
17 17
from ncclient import NCClientError
18 18

  
19

  
20 19
from rpc import RPC, RPCError
21 20
from errors import MissingCapabilityError
22 21
from retrieve import Get, GetConfig
......
25 24
from lock import Lock, Unlock, LockContext
26 25
from subscribe import CreateSubscription
27 26

  
28

  
29 27
__all__ = [
30 28
    'RPC',
29
    'RPCReply',
31 30
    'RPCError',
32 31
    'Get',
33 32
    'GetConfig',
b/ncclient/operations/rpc.py
16 16
from uuid import uuid1
17 17
from weakref import WeakValueDictionary
18 18

  
19
import ncclient.content
19
from ncclient import content
20 20

  
21 21
from reply import RPCReply
22 22

  
23 23
import logging
24 24
logger = logging.getLogger('ncclient.rpc')
25 25

  
26

  
27 26
class RPC(object):
28 27
    
29 28
    DEPENDS = []
......
40 39
            pass        
41 40
        self._async = async
42 41
        self._timeout = timeout
42
        # keeps things simple instead of having a class attr that has to be locked
43 43
        self._id = uuid1().urn
44 44
        self._listener = RPCReplyListener(session)
45 45
        self._listener.register(self._id, self)
......
53 53
            'attributes': {'message-id': self._id},
54 54
            'subtree': opspec
55 55
            }
56
        return XMLConverter(spec).to_string(encoding)
56
        return content.dtree2xml(encoding)
57 57
    
58 58
    def _request(self, op):
59 59
        req = self._build(op)
......
125 125
        return self._raw
126 126
    
127 127
    def parse(self):
128
        if self._parsed: return
129
        root = self._root = content.to_element(self._raw) # <rpc-reply> element
128
        if self._parsed:
129
            return
130
        root = self._root = content.xml2ele(self._raw) # <rpc-reply> element
130 131
        # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
131 132
        ok = content.namespaced_find(root, 'ok')
132 133
        if ok is not None:
b/ncclient/transport/errors.py
14 14

  
15 15
"TODO: docstrings"
16 16

  
17
from ncclient.errors import TransportError
17
from ncclient import NCClientError
18

  
19
class TransportError(NClientError):
20
    pass
18 21

  
19 22
class AuthenticationError(TransportError):
20 23
    pass
b/ncclient/transport/hello.py
42 42
        "Given a list of capability URI's returns encoded <hello> message"
43 43
        spec = {
44 44
            'tag': content.qualify('hello'),
45
            'children': [{
45
            'subtree': [{
46 46
                'tag': 'capabilities',
47 47
                'children': # this is fun :-)
48
                    [{ 'tag': 'capability', 'text': uri} for uri in capabilities]
48
                    [{'tag': 'capability', 'text': uri} for uri in capabilities]
49 49
                }]
50 50
            }
51 51
        return content.to_xml(spec)
......
54 54
    def parse(raw):
55 55
        "Returns tuple of ('session-id', ['capability_uri', ...])"
56 56
        sid, capabilities = 0, []
57
        root = content.from_xml(raw)
58
        for child in root['children']:
57
        root = content.xml2ele(raw)
58
        for child in root.getchildren():
59 59
            tag = content.unqualify(child['tag'])
60 60
            if tag == 'session-id':
61
                sid = child['text']
61
                sid = child.text
62 62
            elif tag == 'capabilities':
63
                for cap in child['children']:
64
                    if content.unqualify(cap['text']) == 'capability':
65
                        capabilities.append(cap['text'])
63
                for cap in child.getchildren():
64
                    if content.unqualify(cap.tag) == 'capability':
65
                        capabilities.append(cap.text)
66 66
        return sid, capabilities
b/ncclient/transport/session.py
42 42
        logger.debug('%r created: client_capabilities=%r' %
43 43
                     (self, self._client_capabilities))
44 44
    
45
    def _post_connect(self):
46
        "Greeting stuff"
47
        init_event = Event()
48
        error = [None] # so that err_cb can bind error[0]. just how it is.
49
        # callbacks
50
        def ok_cb(id, capabilities):
51
            self._id = id
52
            self._server_capabilities = Capabilities(capabilities)
53
            init_event.set()
54
        def err_cb(err):
55
            error[0] = err
56
            init_event.set()
57
        listener = HelloHandler(ok_cb, err_cb)
58
        self.add_listener(listener)
59
        self.send(HelloHandler.build(self._client_capabilities))
60
        logger.debug('starting main loop')
61
        self.start()
62
        # we expect server's hello message
63
        init_event.wait()
64
        # received hello message or an error happened
65
        self.remove_listener(listener)
66
        if error[0]:
67
            raise error[0]
68
        logger.info('initialized: session-id=%s | server_capabilities=%s' %
69
                     (self._id, self._server_capabilities))
70
    
71 45
    def _dispatch_message(self, raw):
72 46
        "TODO: docstring"
73 47
        try:
......
95 69
            except Exception as e:
96 70
                logger.warning('error %r' % e)
97 71
    
72
    def _post_connect(self):
73
        "Greeting stuff"
74
        init_event = Event()
75
        error = [None] # so that err_cb can bind error[0]. just how it is.
76
        # callbacks
77
        def ok_cb(id, capabilities):
78
            self._id = id
79
            self._server_capabilities = Capabilities(capabilities)
80
            init_event.set()
81
        def err_cb(err):
82
            error[0] = err
83
            init_event.set()
84
        listener = HelloHandler(ok_cb, err_cb)
85
        self.add_listener(listener)
86
        self.send(HelloHandler.build(self._client_capabilities))
87
        logger.debug('starting main loop')
88
        self.start()
89
        # we expect server's hello message
90
        init_event.wait()
91
        # received hello message or an error happened
92
        self.remove_listener(listener)
93
        if error[0]:
94
            raise error[0]
95
        logger.info('initialized: session-id=%s | server_capabilities=%s' %
96
                     (self._id, self._server_capabilities))
97
    
98 98
    def add_listener(self, listener):
99 99
        "TODO: docstring"
100 100
        logger.debug('installing listener %r' % listener)

Also available in: Unified diff