Revision 179b00d4

b/ncclient/content.py
78 78
        if 'tag' in spec:
79 79
            ele = ET.Element(spec.get('tag'), spec.get('attributes', {}))
80 80
            ele.text = spec.get('text', '')
81
            children = spec.get('children', [])
82
            if isinstance(children, dict):
83
                children = [children]
84
            for child in children:
85
                ele.append(XMLConverter.build(child))
81
            ele.tail = spec.get('tail', '')
82
            subtree = spec.get('subtree', [])
83
            # might not be properly specified as list but may be dict
84
            if isinstance(subtree, dict):
85
                subtree = [subtree]
86
            for subele in subtree:
87
                ele.append(XMLConverter.build(subele))
86 88
            return ele
87 89
        elif 'comment' in spec:
88 90
            return ET.Comment(spec.get('comment'))
89 91
        else:
90
            raise ValueError('Invalid tree spec')
92
            raise ContentError('Invalid tree spec')
93
    
94
    @staticmethod
95
    def from_string(xml):
96
        return XMLConverter.parse(ET.fromstring(xml))
97
    
98
    @staticmethod
99
    def parse(root):
100
        return {
101
            'tag': root.tag,
102
            'attributes': root.attrib,
103
            'text': root.text,
104
            'tail': root.tail,
105
            'subtree': [ XMLConverter.parse(child) for child in root.getchildren() ]
106
        }
b/ncclient/manager.py
37 37
    session.connect(*args, **kwds)
38 38
    return Manager(session)
39 39

  
40
connect = connect_ssh # default
40
connect = connect_ssh # default session type
41 41

  
42 42
class Manager:
43 43
    
44 44
    'Facade for the API'
45 45
    
46
    def __init__(self, session):
47
        self._session = session
46
    RAISE_ALL = 0
47
    RAISE_ERROR = 1
48
    RAISE_NONE = 2
48 49
    
49
    def _get(self, type, *args, **kwds):
50
        op = OPERATIONS[type](self._session)
51
        reply = op.request(*args, **kwds)
52
        if not reply.ok:
53
            raise reply.errors[0]
54
        else:
55
            return reply.data
50
    def __init__(self, session, rpc_error=Manager.RAISE_ERROR):
51
        self._session = session
52
        self._raise = rpc_error
56 53

  
57 54
    def do(self, op, *args, **kwds):
58 55
        op = OPERATIONS[op](self._session)
59 56
        reply = op.request(*args, **kwds)
60 57
        if not reply.ok:
61
            raise reply.errors[0]
58
            if self._raise == Manager.RAISE_ALL:
59
                raise reply.error
60
            elif self._raise == Manager.RAISE_ERROR:
61
                for error in reply.errors:
62
                    if error.severity == 'error':
63
                        raise error
62 64
        return reply
63

  
64
    def locked(self, target='running'):
65
    
66
    def __enter__(self):
67
        pass
68
    
69
    def __exit__(self, *args):
70
        self.close()
71
        return False
72
    
73
    def _get(self, type, *args, **kwds):
74
        reply = self.do(type)
75
        return reply.data
76
    
77
    def locked(self, target):
78
        "For use with 'with'. target is the datastore, e.g. 'candidate'"
65 79
        return operations.LockContext(self._session, target)
66 80
    
67 81
    get = lambda self, *args, **kwds: self._get('get')
......
89 103
    kill_session = lambda self, *args, **kwds: self.do('kill-session', *args, **kwds)
90 104
    
91 105
    def close(self):
92
        try:
106
        try: # try doing it clean
93 107
            self.close_session()
94 108
        except:
95
            self._session.expect_close()
109
            pass
110
        if self._session.connected: # if that didn't work...
96 111
            self._session.close()
b/ncclient/operations/edit.py
19 19

  
20 20
class EditConfig(RPC):
21 21
    
22
    # tested: no
23
    # combed: no
24
    
22 25
    SPEC = {
23 26
        'tag': 'edit-config',
24
        'children': [ ]
27
        'subtree': []
25 28
    }
26 29
    
27 30
    def request(self, target=None, target_url=None, config=None,
28 31
                default_operation=None, test_option=None, error_option=None):
29 32
        util.one_of(target, target_url)
30 33
        spec = EditConfig.SPEC.copy()
31
        params = spec['children']
32
        params.append({'tag': 'target', 'children': util.store_or_url(target, target_url)})
33
        params.append({'tag': 'config', 'children': config})
34
        params = spec['subtree']
35
        params.append({
36
            'tag': 'target',
37
            'subtree': util.store_or_url(target, target_url)
38
            })
39
        params.append({
40
            'tag': 'config',
41
            'subtree': config
42
            })
34 43
        if default_operation is not None:
35
            params.append({'tag': 'default-operation', 'text': default_operation})
44
            params.append({
45
                'tag': 'default-operation',
46
                'text': default_operation
47
                })
36 48
        if test_option is not None:
37
            params.append({'tag': 'test-option', 'text': test_option})
49
            if test_option == '':
50
                self._assert(':validate')
51
            params.append({
52
                'tag': 'test-option',
53
                'text': test_option
54
                })
38 55
        if error_option is not None:
39
            params.append({'tag': 'test-option', 'text': test_option})
56
            if error_option == 'rollback-on-error':
57
                self._assert(':rollback-on-error')
58
            params.append({
59
                'tag': 'error-option',
60
                'text': error_option
61
                })
62

  
40 63

  
41 64
class DeleteConfig(RPC):
42 65
    
66
    # tested: no
67
    
43 68
    SPEC = {
44 69
        'tag': 'delete-config',
45
        'children': [ { 'tag': 'target', 'children': None } ]
70
        'subtree': [ { 'tag': 'target', 'subtree': None } ]
46 71
    }
47 72
    
48 73
    def request(self, target=None, target_url=None):
49 74
        spec = DeleteConfig.SPEC.copy()
50
        spec['children'][0]['children'] = util.store_or_url(target, target_url)
75
        spec['subtree'][0]['subtree'] = util.store_or_url(target, target_url)
51 76
        return self._request(spec)
52 77

  
53 78

  
54 79
class CopyConfig(RPC):
55 80
    
81
    # tested: no
82
    
56 83
    SPEC = {
57 84
        'tag': 'copy-config',
58
        'children': [
59
            { 'tag': 'source', 'children': {'tag': None } },
60
            { 'tag': 'target', 'children': {'tag': None } }
61
        ]
85
        'subtree': []
62 86
    }
63 87
    
64 88
    def request(self, source=None, source_url=None, target=None, target_url=None):
65 89
        spec = CopyConfig.SPEC.copy()
66
        spec['children'][0]['children'] = util.store_or_url(source, source_url)
67
        spec['children'][1]['children'] = util.store_or_url(target, target_url)
90
        spec['subtree'].append({
91
            'tag': 'target',
92
            'subtree': util.store_or_url(source, source_url)
93
            })
94
        spec['subtree'].append({
95
            'tag': 'target',
96
            'subtree': util.store_or_url(target, target_url)
97
            })
68 98
        return self._request(spec)
69 99

  
70 100

  
71 101
class Validate(RPC):
72 102
    
103
    # tested: no
104
    # combed: yes
105
    
73 106
    'config attr shd not include <config> root'
74 107
    
75 108
    DEPENDS = [':validate']
76 109
    
77 110
    SPEC = {
78 111
        'tag': 'validate',
79
        'children': []
112
        'subtree': []
80 113
    }
81 114
    
82 115
    def request(self, source=None, config=None):
83 116
        util.one_of(source, capability)
84 117
        spec = SPEC.copy()
85 118
        if source is not None:
86
            spec['children'].append({
87
                'tag': 'source', 'children': {'tag': source}
88
                })
119
            spec['subtree'].append({
120
                'tag': 'source',
121
                'subtree': {'tag': source}
122
            })
89 123
        else:
90
            spec['children'].append({'tag': 'config', 'children': config})
124
            spec['subtree'].append({
125
                'tag': 'config',
126
                'subtree': config
127
            })
91 128
        return self._request(spec)
92 129

  
93 130

  
94 131
class Commit(RPC):
95 132
    
133
    # tested: no
134
    # combed: yes
135
    
96 136
    DEPENDS = [':candidate']
97 137
    
98
    SPEC = {'tag': 'commit', 'children': [] }
138
    SPEC = { 'tag': 'commit', 'subtree': [] }
99 139
    
100 140
    def _parse_hook(self):
101 141
        pass
......
103 143
    def request(self, confirmed=False, timeout=None):
104 144
        spec = SPEC.copy()
105 145
        if confirmed:
106
            self._assert(':confirmed-commit')
107
            children = spec['children']
108
            children.append({'tag': 'confirmed'})
146
            spec['subtree'].append({'tag': 'confirmed'})
109 147
            if timeout is not None:
110
                children.append({
148
                spec['subtree'].append({
111 149
                    'tag': 'confirm-timeout',
112 150
                    'text': timeout
113 151
                })
114 152
        return self._request(Commit.SPEC)
115 153

  
116 154

  
155
class ConfirmedCommit(Commit):
156
    "psuedo-op"
157
    
158
    # tested: no
159
    # combed: yes
160
    
161
    DEPENDS = [':candidate', ':confirmed-commit']
162
    
163
    def request(self, timeout=None):
164
        "Commit changes; requireing that a confirming commit follow"
165
        return Commit.request(self, confirmed=True, timeout=timeout)
166
    
167
    def confirm(self):
168
        "Make the confirming commit"
169
        return Commit.request(self, confirmed=True)
170

  
171

  
117 172
class DiscardChanges(RPC):
118 173
    
174
    # tested: no
175
    # combed: yes
176
    
119 177
    DEPENDS = [':candidate']
120 178
    
121 179
    SPEC = {'tag': 'discard-changes'}
122
    
123
    request = lambda self: self._request(DiscardChanges.SPEC)
b/ncclient/operations/lock.py
1
# Copyright 2009 Shikhar Bhushan
1
# Copyright 2h009 Shikhar Bhushan
2 2
#
3 3
# Licensed under the Apache License, Version 2.0 (the "License");
4 4
# you may not use this file except in compliance with the License.
......
16 16

  
17 17
from ncclient.rpc import RPC
18 18

  
19
import util
20

  
21 19
class Lock(RPC):
22 20
    
21
    # tested: no
22
    
23 23
    SPEC = {
24 24
        'tag': 'lock',
25
        'children': {
25
        'subtree': {
26 26
            'tag': 'target',
27
            'children': {'tag': None }
27
            'subtree': {'tag': None }
28 28
        }
29 29
    }
30 30
    
31
    def request(self, target='running'):
32
        if target=='candidate':
33
            self._assert(':candidate')
31
    def request(self, target):
34 32
        spec = Lock.SPEC.copy()
35
        spec['children']['children']['tag'] = target
33
        spec['subtree']['subtree']['tag'] = target
36 34
        return self._request(spec)
37 35

  
38 36

  
39 37
class Unlock(RPC):
40 38
    
39
    # tested: no
40
    
41 41
    SPEC = {
42 42
        'tag': 'unlock',
43
        'children': {
43
        'subtree': {
44 44
            'tag': 'target',
45
            'children': {'tag': None }
45
            'subtree': {'tag': None }
46 46
        }
47 47
    }
48 48
    
49
    def request(self, target='running'):
50
        if target=='candidate':
51
            self._assert(':candidate')
49
    def request(self, target):
52 50
        spec = Unlock.SPEC.copy()
53
        spec['children']['children']['tag'] = target
51
        spec['subtree']['subtree']['tag'] = target
54 52
        return self._request(spec)
55 53

  
56 54

  
57 55
class LockContext:
58
        
59
    def __init__(self, session, target='running'):
56
    
57
    # tested: no
58
    
59
    def __init__(self, session, target):
60 60
        self.session = session
61 61
        self.target = target
62
        
62
    
63 63
    def __enter__(self):
64
        Lock(self.session).request(self.target)
65
        return self
64
        reply = Lock(self.session).request(self.target)
65
        if not reply.ok:
66
            raise reply.error
67
        else:
68
            return self
66 69
    
67
    def __exit__(self, t, v, tb):
68
        Unlock(self.session).request(self.target)
70
    def __exit__(self, *args):
71
        reply = Unlock(session).request(self.target)
72
        if not reply.ok:
73
            raise reply.error
69 74
        return False
b/ncclient/operations/retrieve.py
18 18

  
19 19
class GetReply(RPCReply):
20 20
    
21
    # tested: no
22
    
23
    def __init__(self, *args, **kwds):
24
        RPCReply.__init__(self, *args, **kwds)
25
        self._data = None
26
    
21 27
    def parse(self):
22 28
        RPCReply.parse(self)
29
        if self.ok:
30
            self.root.find('data')
23 31
    
24 32
    @property
25 33
    def data(self):
26
        return None
34
        return ET.tostring(self._data)
27 35

  
28 36
class Get(RPC):
29 37
    
38
    # tested: no
39
    
30 40
    SPEC = {
31 41
        'tag': 'get',
32
        'children': []
42
        'subtree': []
33 43
    }
34 44
    
35 45
    REPLY_CLS = GetReply
......
37 47
    def request(self, filter=None):
38 48
        spec = Get.SPEC.copy()
39 49
        if filter is not None:
40
            spec['children'].append(util.build_filter(*filter))
50
            spec['subtree'].append(util.build_filter(*filter))
41 51
        return self._request(spec)
42 52

  
43 53
class GetConfig(RPC):
44 54
    
45 55
    SPEC = {
46 56
        'tag': 'get-config',
47
        'children': []
57
        'subtree': []
48 58
    }
49 59
    
50 60
    REPLY_CLS = GetReply
......
52 62
    def request(self, source=None, source_url=None, filter=None):
53 63
        util.one_of(source, source_url)
54 64
        spec = GetConfig.SPEC.copy()
55
        children = spec['children']
56
        children.append({'tag': 'source', 'children': util.store_or_url(source, source_url)})
65
        subtree = spec['subtree']
66
        subtree.append({'tag': 'source', 'subtree': util.store_or_url(source, source_url)})
57 67
        if filter is not None:
58
            children.append(util.build_filter(*filter))
68
            subtree.append(util.build_filter(*filter))
59 69
        return self._request(spec)
b/ncclient/operations/session.py
16 16

  
17 17
from ncclient.rpc import RPC
18 18

  
19
class CloseSession(RPC): # x
20
    
21
    'CloseSession is always synchronous'
19
class CloseSession(RPC):
20
    # tested: no
21
    # combed: yes
22 22
    
23 23
    SPEC = { 'tag': 'close-session' }
24 24
    
......
26 26
        if self.reply.ok:
27 27
            self.session.expect_close()
28 28
        self.session.close()
29
    
30
    def request(self):
31
        return self._request(CloseSession.SPEC)
32 29

  
33 30

  
34
class KillSession(RPC): # x
31
class KillSession(RPC):
32
    # tested: no
35 33
    
36 34
    SPEC = {
37 35
        'tag': 'kill-session',
38
        'children': { 'tag': 'session-id', 'text': None}
36
        'subtree': []
39 37
    }
40 38
    
41 39
    def request(self, session_id):
40
        spec = KillSession.SPEC.copy()
42 41
        if not isinstance(session_id, basestring): # just making sure...
43 42
            session_id = str(session_id)
44
        spec = KillSession.SPEC.copy()
45
        spec['children'][0]['text'] = session_id
43
        spec['subtree'].append({
44
            'tag': 'session-id',
45
            'text': session_id
46
        })
46 47
        return self._request(spec)
b/ncclient/operations/subscribe.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
# TODO when can actually test it...
16

  
17 15
from ncclient.rpc import RPC
18 16

  
19 17
from ncclient.glue import Listener
......
21 19

  
22 20
NOTIFICATION_NS = 'urn:ietf:params:xml:ns:netconf:notification:1.0'
23 21

  
22
# TODO when can actually test it...
23

  
24 24
class CreateSubscription(RPC):    
25
    # tested: no
25 26
    
26 27
    SPEC = {
27
        'tag': _('create-subscription', NOTIFICATION_NS),
28
        'startTime': None,
29
        'stream': None
28
        'tag': _('create-subscription', NOTIFICATION_NS)
30 29
    }
31 30

  
32
class Notification:
33
    pass
31
class Notification: pass
34 32

  
35
class NotificationListener(Listener):
36
    pass
33
class NotificationListener(Listener): pass
b/ncclient/operations/util.py
6 6

  
7 7
from . import MissingCapabilityError
8 8

  
9
def one_of(self, *args):
9

  
10
def one_of(*args):
11
    'Verifies that only one of the arguments is not None'
10 12
    for i, arg in enumerate(args):
11 13
        if arg is not None:
12 14
            for argh in args[i+1:]:
......
17 19
    raise OperationError('Insufficient parameters')
18 20

  
19 21

  
20
def assert_capability(key, capabilities):
21
    if key not in capabilities:
22
        raise MissingCapabilityError('[%s] capability is required for this operation' % key)
23

  
24

  
25 22
def store_or_url(store, url):
26 23
    one_of(store, url)
27 24
    node = {}
......
32 29
        node['text'] = url
33 30
    return node
34 31

  
35
def build_filter(spec, type, criteria):
32

  
33
def build_filter(type, criteria):
36 34
    filter = {
37 35
        'tag': 'filter',
38 36
        'attributes': {'type': type}
39 37
    }
40
    if type == 'subtree':
41
        filter['children'] = [criteria]
42
    elif type == 'xpath':
38
    if type == 'xpath':
43 39
        filter['attributes']['select'] = criteria
40
    else:
41
        filter['subtree'] = [criteria]
44 42
    return filter
b/ncclient/rpc/reply.py
24 24

  
25 25
class RPCReply:
26 26
    
27
    'NOTES: memory considerations?? storing both raw xml + ET.Element'
28
    
27 29
    def __init__(self, raw):
28 30
        self._raw = raw
29 31
        self._parsed = False
......
67 69
        self._parsed = True
68 70
    
69 71
    @property
70
    def raw(self):
72
    def content_xml(self):
73
        '<rpc-reply> subtree'
74
        if not self._parsed: self.parse()
75
        return ''.join([ET.tostring(ele) for ele in self._root.getchildren()])
76
    
77
    @property
78
    def content_element(self):
79
        if not self._parsed: self.parse()
80
        return self._root.getchildren()
81
    
82
    @property
83
    def root_xml(self):
84
        '<rpc-reply> as returned'
71 85
        return self._raw
72 86
    
73 87
    @property
74
    def root(self):
88
    def root_element(self):
89
        if not self._parsed: self.parse()
75 90
        return self._root
76 91
    
77 92
    @property
......
80 95
        return not self._errors # empty list => false
81 96
    
82 97
    @property
98
    def error(self):
99
        if not self._parsed: self.parse()
100
        if self._errors:
101
            return self._errors[0]
102
        else:
103
            return None
104
    
105
    @property
83 106
    def errors(self):
84 107
        'List of RPCError objects. Will be empty if no <rpc-error> elements in reply.'
85 108
        if not self._parsed: self.parse()
b/ncclient/rpc/rpc.py
54 54
        spec = {
55 55
            'tag': _('rpc'),
56 56
            'attributes': {'message-id': self._id},
57
            'children': opspec
57
            'subtree': opspec
58 58
            }
59 59
        return XMLConverter(spec).to_string(encoding)
60 60
    
......
71 71
            else:
72 72
                raise ReplyTimeoutError
73 73
    
74
    def request(self):
75
        return self._request(self.SPEC)
76
    
74 77
    def _delivery_hook(self):
75 78
        'For subclasses'
76 79
        pass
b/ncclient/transport/hello.py
14 14

  
15 15
"All to do with NETCONF <hello> messages"
16 16

  
17
from xml.etree import cElementTree as ET
18

  
19 17
from ncclient.glue import Listener
20 18
from ncclient.content import XMLConverter, BASE_NS
21 19
from ncclient.content import qualify as _
......
59 57
    def parse(raw):
60 58
        "Returns tuple of ('session-id', ['capability_uri', ...])"
61 59
        sid, capabilities = 0, []
62
        root = ET.fromstring(raw)
63
        for child in root.getchildren():
64
            if __(child.tag) == 'session-id':
65
                sid = child.text
66
            elif __(child.tag) == 'capabilities':
67
                for cap in child.getiterator(_('capability', BASE_NS)):
68
                    capabilities.append(cap.text)
69
                # cisco doesn't namespace hello message
70
                for cap in child.getiterator('capability'): 
71
                    capabilities.append(cap.text)
60
        root = XMLConverter.from_string(raw)
61
        for child in root['children']:
62
            if __(child['tag']) == 'session-id':
63
                sid = child['text']
64
            elif __(child['tag']) == 'capabilities':
65
                for cap in child['children']:
66
                    if __(cap['text']) == 'capability':
67
                        capabilities.append(cap['text'])
72 68
        return sid, capabilities
b/ncclient/transport/ssh.py
121 121
        f.close()    
122 122
    
123 123
    def close(self):
124
        self.expect_close()
124 125
        if self._transport.is_active():
125 126
            self._transport.close()
126 127
        self._connected = False

Also available in: Unified diff