Revision d771dffc

b/ncclient/capabilities.py
20 20
    """
21 21
    
22 22
    def __init__(self, capabilities=None):
23
        "TODO: docstring"
24 23
        self._dict = {}
25 24
        if isinstance(capabilities, dict):
26 25
            self._dict = capabilities
......
29 28
                self._dict[uri] = Capabilities.guess_shorthand(uri)
30 29
    
31 30
    def __contains__(self, key):
32
        "TODO: docstring"
33 31
        return ( key in self._dict ) or ( key in self._dict.values() )
34 32
    
35 33
    def __iter__(self):
36
        "TODO: docstring"
37 34
        return self._dict.keys().__iter__()
38 35
    
39 36
    def __repr__(self):
40
        "TODO: docstring"
41 37
        return repr(self._dict.keys())
42 38
    
43 39
    def __list__(self):
44 40
        return self._dict.keys()
45 41
    
46 42
    def add(self, uri, shorthand=None):
47
        "TODO: docstring"
48 43
        if shorthand is None:
49 44
            shorthand = Capabilities.guess_shorthand(uri)
50 45
        self._dict[uri] = shorthand
......
52 47
    set = add
53 48
    
54 49
    def remove(self, key):
55
        "TODO: docstring"
56 50
        if key in self._dict:
57 51
            del self._dict[key]
58 52
        else:
......
63 57
    
64 58
    @staticmethod
65 59
    def guess_shorthand(uri):
66
        "TODO: docstring"
67 60
        if uri.startswith('urn:ietf:params:netconf:capability:'):
68 61
            return (':' + uri.split(':')[5])
69 62

  
......
74 67
    'urn:ietf:params:netconf:capability:confirmed-commit:1.0',
75 68
    'urn:ietf:params:netconf:capability:rollback-on-error:1.0',
76 69
    'urn:ietf:params:netconf:capability:startup:1.0',
77
    'urn:ietf:params:netconf:capability:url:1.0',
70
    'urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file',
78 71
    'urn:ietf:params:netconf:capability:validate:1.0',
79 72
    'urn:ietf:params:netconf:capability:xpath:1.0',
80 73
    'urn:ietf:params:netconf:capability:notification:1.0',
b/ncclient/content.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
"TODO: docstring"
16

  
17 15
from xml.etree import cElementTree as ET
18 16

  
19 17
from ncclient import NCClientError
......
44 42

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

  
47
### Other utility functions
48

  
49
iselement = ET.iselement
50

  
51
def namespaced_find(ele, tag, strict=False):
52
    """In strict mode, doesn't work around Cisco implementations sending incorrectly
53
    namespaced XML. Supply qualified name if using strict mode.
54
    """
55
    found = None
56
    if strict:
57
        found = ele.find(tag)
58
    else:
59
        for qname in multiqualify(tag):
60
            found = ele.find(qname)
61
            if found is not None:
62
                break
63
    return found
64

  
65
def parse_root(raw):
66
    '''Parse the top-level element from XML string.
67
    
68
    Returns a `(tag, attributes)` tuple, where `tag` is a string representing
69
    the qualified name of the root element and `attributes` is an
70
    `{attribute: value}` dictionary.
71
    '''
72
    fp = StringIO(raw[:1024]) # this is a guess but start element beyond 1024 bytes would be a bit absurd
73
    for event, element in ET.iterparse(fp, events=('start',)):
74
        return (element.tag, element.attrib)
75

  
76
def root_ensured(rep, req_tag, req_attrs=None):
77
    rep = to_element(rep)
78
    if rep.tag not in (req_tag, qualify(req_tag)):
79
        raise ContentError("Required root element [%s] not found" % req_tag)
80
    if req_attrs is not None:
81
        pass # TODO
82
    return rep
83

  
84 45
### XML with Python data structures
85 46

  
86 47
dtree2ele = DictTree.Element
......
146 107
    @staticmethod
147 108
    def Element(xml):
148 109
        return ET.fromstring(xml)
110

  
111
### Other utility functions
112

  
113
iselement = ET.iselement
114

  
115
def find(ele, tag, strict=False):
116
    """In strict mode, doesn't workaround Cisco implementations sending incorrectly
117
    namespaced XML. Supply qualified tag name if using strict mode.
118
    """
119
    if strict:
120
        return ele.find(tag)
121
    else:
122
        for qname in multiqualify(tag):
123
            found = ele.find(qname)
124
            if found is not None:
125
                return found
126

  
127
def parse_root(raw):
128
    '''Parse the top-level element from XML string.
129
    
130
    Returns a `(tag, attributes)` tuple, where `tag` is a string representing
131
    the qualified name of the root element and `attributes` is an
132
    `{attribute: value}` dictionary.
133
    '''
134
    fp = StringIO(raw[:1024]) # this is a guess but start element beyond 1024 bytes would be a bit absurd
135
    for event, element in ET.iterparse(fp, events=('start',)):
136
        return (element.tag, element.attrib)
137

  
138
def validated_element(rep, tag, attrs=None):
139
    ele = dtree2ele(rep)
140
    if ele.tag not in (tag, qualify(tag)):
141
        raise ContentError("Required root element [%s] not found" % tag)
142
    if attrs is not None:
143
        for req in attrs:
144
            for attr in ele.attrib:
145
                if unqualify(attr) == req:
146
                    break
147
            else:
148
                raise ContentError("Required attribute [%s] not found in element [%s]" % (req, req_tag))
149
    return ele
b/ncclient/manager.py
41 41

  
42 42
class Manager:
43 43
    
44
    "Thin layer of abstraction for the ncclient API."
44
    "Thin layer of abstraction for the API."
45 45
    
46
    RAISE_ALL = 0
47
    RAISE_ERROR = 1
48
    RAISE_NONE = 2
46
    RAISE_ALL, RAISE_ERROR, RAISE_NONE = range(3)
49 47
    
50
    def __init__(self, session, rpc_error=Manager.RAISE_ERROR):
48
    def __init__(self, session, rpc_errors=Manager.RAISE_ALL):
51 49
        self._session = session
52 50
        self._raise = rpc_error
53 51

  
54
    def rpc(self, op, *args, **kwds):
52
    def do(self, op, *args, **kwds):
55 53
        op = OPERATIONS[op](self._session)
56 54
        reply = op.request(*args, **kwds)
57 55
        if not reply.ok:
......
70 68
        self.close()
71 69
        return False
72 70
    
73
    def _get(self, type, *args, **kwds):
74
        reply = self.do(type)
75
        return reply.data
76
    
77 71
    def locked(self, target):
78
        "Returns a context manager for use withthe 'with' statement.
79
	`target` is the datastore to lock, e.g. 'candidate'"
72
        """Returns a context manager for use withthe 'with' statement.
73
        `target` is the datastore to lock, e.g. 'candidate
74
        """
80 75
        return operations.LockContext(self._session, target)
81 76
     
82
    get = lambda self, *args, **kwds: self._get('get')
77
    get = lambda self, *args, **kwds: self.do('get', *args, **kwds).data
83 78
    
84
    get_config = lambda self, *args, **kwds: self._get('get-config')
79
    get_config = lambda self, *args, **kwds: self.do('get-config', *args, **kwds).data
85 80
    
86 81
    edit_config = lambda self, *args, **kwds: self.do('edit-config', *args, **kwds)
87 82
    
......
110 105
            pass
111 106
        if self._session.connected: # if that didn't work...
112 107
            self._session.close()
113

  
108
    
114 109
    @property
115 110
    def session(self, session):
116
	return self._session
111
        return self._session
117 112
    
118 113
    def get_capabilities(self, whose):
119
	if whose in ('manager', 'client'):
120
	    return self._session._client_capabilities
121
	elif whose in ('agent', 'server')
122
	    return self._session._server_capabilities
123

  
124
   
114
        if whose in ('manager', 'client'):
115
            return self._session._client_capabilities
116
        elif whose in ('agent', 'server'):
117
            return self._session._server_capabilities
118
    
125 119
    @property
126 120
    def capabilities(self):
127 121
        return self._session._client_capabilities
b/ncclient/operations/__init__.py
14 14

  
15 15
'NETCONF protocol operations'
16 16

  
17
from ncclient import NCClientError
18

  
19
from rpc import RPC, RPCError
20
from errors import MissingCapabilityError
17
from errors import OperationError, MissingCapabilityError
18
from rpc import RPCError
21 19
from retrieve import Get, GetConfig
22 20
from edit import EditConfig, CopyConfig, DeleteConfig, Validate, Commit, DiscardChanges
23 21
from session import CloseSession, KillSession
24 22
from lock import Lock, Unlock, LockContext
25 23
from subscribe import CreateSubscription
26 24

  
25
OPERATIONS = {
26
    'get': Get,
27
    'get-config': GetConfig,
28
    'edit-config': EditConfig,
29
    'copy-config': CopyConfig,
30
    'validate': Validate,
31
    'commit': Commit,
32
    'discard-changes': DiscardChanges,
33
    'delete-config': DeleteConfig,
34
    'lock': Lock,
35
    'unlock': Unlock,
36
    'close_session': CloseSession,
37
    'kill-session': KillSession,
38
}
39

  
27 40
__all__ = [
28
    'RPC',
29
    'RPCReply',
30 41
    'RPCError',
42
    'OPERATIONS',
31 43
    'Get',
32 44
    'GetConfig',
33 45
    'EditConfig',
b/ncclient/operations/edit.py
27 27
    
28 28
    SPEC = {'tag': 'edit-config', 'subtree': []}
29 29
    
30
    def request(self, target=None, target_url=None, config=None,
31
                default_operation=None, test_option=None, error_option=None):
32
        util.one_of(target, target_url)
30
    def request(self, target=None, config=None, default_operation=None,
31
                test_option=None, error_option=None):
32
        util.one_of(target, config)
33 33
        spec = EditConfig.SPEC.copy()
34 34
        subtree = spec['subtree']
35
        subtree.append({
36
            'tag': 'target',
37
            'subtree': util.store_or_url(target, target_url, self._assert)
38
            })
39
        subtree.append(content.root_ensured(config, 'config'))
35
        subtree.append(util.store_or_url('target', target, self._assert))
36
        subtree.append(content.validated_root(config, 'config'))
40 37
        if default_operation is not None:
41 38
            subtree.append({
42 39
                'tag': 'default-operation',
......
64 61
    
65 62
    SPEC = {'tag': 'delete-config', 'subtree': []}
66 63
    
67
    def request(self, target=None, target_url=None):
64
    def request(self, target):
68 65
        spec = DeleteConfig.SPEC.copy()
69
        spec['subtree'].append({
70
            'tag': 'target',
71
            'subtree': util.store_or_url(target, target_url, self._assert)
72
            })
66
        spec['subtree'].append(util.store_or_url('source', source, self._assert))
73 67
        return self._request(spec)
74 68

  
75 69

  
......
80 74
    
81 75
    SPEC = {'tag': 'copy-config', 'subtree': []}
82 76
    
83
    def request(self, source=None, source_url=None, target=None, target_url=None):
77
    def request(self, source, target):
84 78
        spec = CopyConfig.SPEC.copy()
85
        spec['subtree'].append({
86
            'tag': 'source',
87
            'subtree': util.store_or_url(source, source_url, self._assert)
88
            })
89
        spec['subtree'].append({
90
            'tag': 'target',
91
            'subtree': util.store_or_url(target, target_url, self._assert)
92
            })
79
        spec['subtree'].append(util.store_or_url('source', source, self._assert))
80
        spec['subtree'].append(util.store_or_url('target', source, self._assert))
93 81
        return self._request(spec)
94 82

  
95 83

  
......
104 92
    
105 93
    SPEC = {'tag': 'validate', 'subtree': []}
106 94
    
107
    def request(self, source=None, source_url=None, config=None):
108
        util.one_of(source, source_url, config)
95
    def request(self, source=None, config=None):
96
        util.one_of(source, config)
109 97
        spec = Validate.SPEC.copy()
110 98
        if config is None:
111
            spec['subtree'].append({
112
                'tag': 'source',
113
                'subtree': util.store_or_url(source, source_url, self._assert)
114
            })
99
            spec['subtree'].append(util.store_or_url('source', source, self._assert))
115 100
        else:
116
            spec['subtree'].append(content.root_ensured(config, 'config'))
101
            spec['subtree'].append(content.validated_root(config, 'config'))
117 102
        return self._request(spec)
118 103

  
119 104

  
......
142 127
        return self._request(Commit.SPEC)
143 128

  
144 129

  
130
class DiscardChanges(RPC):
131
    
132
    # tested: no
133
    # combed: yes
134
    
135
    DEPENDS = [':candidate']
136
    
137
    SPEC = {'tag': 'discard-changes'}
138

  
139

  
145 140
class ConfirmedCommit(Commit):
146 141
    "psuedo-op"
147 142
    
......
157 152
    def confirm(self):
158 153
        "Make the confirming commit"
159 154
        return Commit.request(self, confirmed=True)
160

  
161

  
162
class DiscardChanges(RPC):
163
    
164
    # tested: no
165
    # combed: yes
166
    
167
    DEPENDS = [':candidate']
168 155
    
169
    SPEC = {'tag': 'discard-changes'}
170

  
156
    def discard(self):
157
        return DiscardChanges(self.session, self.async, self.timeout).request()
b/ncclient/operations/errors.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

  
1 15
from ncclient import NCClientError
2 16

  
3 17
class OperationError(NCClientError):
b/ncclient/operations/retrieve.py
28 28
    def _parsing_hook(self, root):
29 29
        self._data = None
30 30
        if not self._errors:
31
            self._data = content.namespaced_find(root, 'data')
31
            self._data = content.find(root, 'data')
32 32
    
33 33
    @property
34
    def data_element(self):
34
    def data(self):
35 35
        if not self._parsed:
36 36
            self.parse()
37 37
        return self._data
38
    
39
    @property
40
    def data_xml(self):
41
        return content.element2string(self.data_element)
42
    
43
    data = data_element
44 38

  
45 39
class Get(RPC):
46 40
    
......
57 51
    def request(self, filter=None):
58 52
        spec = Get.SPEC.copy()
59 53
        if filter is not None:
60
            spec['subtree'].append(util.build_filter(filter)))
54
            spec['subtree'].append(util.build_filter(filter))
61 55
        return self._request(spec)
62 56

  
57

  
63 58
class GetConfig(RPC):
64 59

  
65 60
    # tested: no
......
72 67
    
73 68
    REPLY_CLS = GetReply
74 69
    
75
    def request(self, source=None, source_url=None, filter=None):
70
    def request(self, source, filter=None):
76 71
        """
77 72
        `filter` has to be a tuple of (type, criteria)
78 73
        The type may be one of 'xpath' or 'subtree'
79 74
        The criteria may be an ElementTree.Element, an XML fragment, or tree specification
80 75
        """
81 76
        spec = GetConfig.SPEC.copy()
82
        spec['subtree'].append({
83
            'tag': 'source',
84
            'subtree': util.store_or_url(source, source_url)
85
            })
77
        spec['subtree'].append(util.store_or_url('source', source, self._assert))
86 78
        if filter is not None:
87 79
            spec['subtree'].append(util.build_filter(filter))
88 80
        return self._request(spec)
89

  
b/ncclient/operations/rpc.py
18 18

  
19 19
from ncclient import content
20 20

  
21
from reply import RPCReply
21
from errors import OperationError
22 22

  
23 23
import logging
24 24
logger = logging.getLogger('ncclient.rpc')
......
129 129
            return
130 130
        root = self._root = content.xml2ele(self._raw) # <rpc-reply> element
131 131
        # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
132
        ok = content.namespaced_find(root, 'ok')
132
        ok = content.find(root, 'ok')
133 133
        if ok is not None:
134 134
            logger.debug('parsed [%s]' % ok.tag)
135 135
        else: # create RPCError objects from <rpc-error> elements
136
            error = content.namespaced_find(root, 'rpc-error')
136
            error = content.find(root, 'rpc-error')
137 137
            if error is not None:
138 138
                logger.debug('parsed [%s]' % error.tag)
139 139
                for err in root.getiterator(error.tag):
......
141 141
                    d = {}
142 142
                    for err_detail in err.getchildren(): # <error-type> etc..
143 143
                        tag = content.unqualify(err_detail.tag)
144
                        d[tag] = (err_detail.text.strip() if tag != 'error-info'
145
                                  else content.ele2string(err_detail, 'utf-8'))
144
                        if tag != 'error-info':
145
                            d[tag] = err_detail.text.strip()
146
                        else:
147
                            d[tag] = content.ele2xml(err_detail)
146 148
                    self._errors.append(RPCError(d))
147 149
        self._parsing_hook(root)
148 150
        self._parsed = True
......
175 177
        return self._errors
176 178

  
177 179

  
178
class RPCError(ncclient.RPCError): # raise it if you like
180
class RPCError(OperationError): # raise it if you like
179 181
    
180 182
    def __init__(self, err_dict):
181 183
        self._dict = err_dict
182 184
        if self.message is not None:
183
            ncclient.RPCError.__init__(self, self.message)
185
            OperationError.__init__(self, self.message)
184 186
        else:
185
            ncclient.RPCError.__init__(self)
186
    
187
    @property
188
    def raw(self):
189
        return self._element.tostring()
187
            OperationError.__init__(self)
190 188
    
191 189
    @property
192 190
    def type(self):
b/ncclient/operations/session.py
22 22
    
23 23
    SPEC = { 'tag': 'close-session' }
24 24
    
25
    def _delivery_hook(self)
25
    def _delivery_hook(self):
26 26
        self.session.close()
27 27

  
28 28

  
......
44 44
            'text': session_id
45 45
        })
46 46
        return self._request(spec)
47

  
b/ncclient/operations/util.py
14 14

  
15 15
'Boilerplate ugliness'
16 16

  
17
from ncclient import OperationError
18
from ncclient.content import qualify as _
19
from ncclient.content import root_ensured
17
from ncclient import content
20 18

  
21
from errors import MissingCapabilityError
19
from errors import OperationError, MissingCapabilityError
22 20

  
23 21
def one_of(*args):
24 22
    'Verifies that only one of the arguments is not None'
......
31 29
                return
32 30
    raise OperationError('Insufficient parameters')
33 31

  
34
def store_or_url(store, url, capcheck=None):
35
    one_of(store, url)
36
    node = {}
37
    if store is not None:
38
        node['tag'] = store
39
    else:
32
def store_or_url(wha, loc, capcheck=None):
33
    node = { 'tag': wha, 'subtree': {} }
34
    if '://' in loc: # e.g. http://, file://, ftp://
40 35
        if capcheck is not None:
41
            capcheck(':url') # hmm.. schema check? deem overkill for now
42
        node['tag'] = 'url'
43
        node['text'] = url
36
            capcheck(':url') # url schema check at some point!
37
        node['subtree']['tag'] = 'url'
38
        node['subtree']['text'] = loc
39
    else:
40
        node['subtree']['tag'] = loc
44 41
    return node
45 42

  
46 43
def build_filter(spec, capcheck=None):
......
51 48
            'tag': 'filter',
52 49
            'attributes': {'type': type},
53 50
            'subtree': criteria
54
       }
51
        }
55 52
    else:
56
        rep = root_ensure(spec, 'filter', 'type')
53
        rep = content.validated_element(spec, 'filter', 'type')
57 54
        try:
58 55
            type = rep['type']
59 56
        except KeyError:
60
            type = ele[qualify('type'))
61
    if type == 'xpath' and capcheck_func is not None:
62
        capcheck_func(':xpath')
57
            type = ele[content.qualify('type')]
58
    if type == 'xpath' and capcheck is not None:
59
        capcheck(':xpath')
63 60
    return rep
64

  
b/ncclient/transport/hello.py
16 16

  
17 17
from ncclient import content
18 18

  
19
class HelloHandler(Listener):
19
class HelloHandler:
20 20
    
21 21
    def __init__(self, init_cb, error_cb):
22 22
        self._init_cb = init_cb
23 23
        self._error_cb = error_cb
24 24
    
25
    def __str__(self):
26
        return 'HelloListener'
27
    
28 25
    def callback(self, root, raw):
29 26
        if content.unqualify(root[0]) == 'hello':
30 27
            try:

Also available in: Unified diff