Revision 4bc8021f

b/docs/source/_templates/layout.html
1 1
{% extends "!layout.html" %}
2 2
{% block sidebarlogo %}
3 3
<p align="center">
4
  <a href="http://code.google.com/p/ncclient">
4
  <a href="http://schmizz.net/ncclient">
5 5
    <img src="_static/logo.png" width="125" height="125"/>
6 6
  </a>
7 7
</p>
b/docs/source/api.rst
1 1
Complete API documentation
2 2
==========================
3 3

  
4
**TODO**
5

  
6 4
.. toctree::
7 5
    
8 6
    capabilities
b/docs/source/capabilities.rst
1
:mod:`~ncclient.capabilities` -- NETCONF Capabilities
2
=====================================================
3

  
4
.. module:: ncclient.capabilities
5

  
6
.. autofunction:: schemes
7

  
8
.. autoclass:: Capabilities
9
    :members:
b/docs/source/manager.rst
1 1
:mod:`~ncclient.manager` -- High-level API
2 2
==========================================
3 3

  
4
.. module:: ncclient.manager
4
.. automodule:: ncclient.manager
5 5
    :synopsis: High-level API
6 6

  
7
.. data:: CAPABILITIES
7
Module data
8
-----------
8 9

  
9
    List of URI strings representing the client's capabilities. This is used during the initial
10
    capability exchange. Modify this if you need to announce some capability not already included.
10
These attributes control what capabilties are exchanged with the NETCONF server and what operations
11
are available through the `Manager` API.
11 12

  
12
.. data:: OPERATIONS
13
    
14
    Dictionary of method names and corresponding `~ncclient.operations.RPC` classes. `Manager` uses
15
    this to lookup operations, e.g. "get_config" is mapped to `ncclient.operations.GetConfig`. It
16
    is thus possible to add additional operations to the `Manager` API.
13
.. autodata:: CAPABILITIES
14

  
15
.. autodata:: OPERATIONS
17 16

  
18 17
Factory functions
19 18
-----------------
20 19

  
21 20
A `Manager` instance is created using a factory function.
22 21

  
23
.. function:: connect_ssh(host[, port=830, timeout=None, unknown_host_cb=default_unknown_host_cb, username=None, password, key_filename=None, allow_agent=True, look_for_keys=True])
24
    
25
    Initializes a NETCONF session over SSH, and creates a connected `Manager` instance. *host* must
26
    be specified, all the other arguments are optional and depend on the kind of host key
27
    verification and user authentication you want to complete.
28
    
29
    For the purpose of host key verification, on -NIX systems a user's :file:`~/.ssh/known_hosts`
30
    file is automatically considered. The *unknown_host_cb* argument specifies a callback that will
31
    be invoked when the server's host key cannot be verified. See
32
    :func:`~ncclient.transport.ssh.default_known_host_cb` for function signature.
33
    
34
    First, ``publickey`` authentication is attempted. If a specific *key_filename* is specified, it
35
    will be loaded and authentication attempted using it. If *allow_agent* is :const:`True` and an
36
    SSH agent is running, the keys provided by the agent will be tried. If *look_for_keys* is
37
    :const:`True`, keys in the :file:`~/.ssh/id_rsa` and :file:`~.ssh/id_dsa` will be tried. In case
38
    an encrypted key file is encountered, the *password* argument will be used as a decryption
39
    passphrase.
40
    
41
    If ``publickey`` authentication fails and the *password* argument has been supplied,
42
    ``password`` / ``keyboard-interactive`` SSH authentication will be attempted.
43
    
44
    :param host: hostname or address on which to connect
45
    :type host: `string`
46
    
47
    :param port: port on which to connect
48
    :type port: `int`
49
    
50
    :param timeout: timeout for socket connect
51
    :type timeout: `int`
52
    
53
    :param unknown_host_cb: optional; callback that is invoked when host key verification fails
54
    :type unknown_host_cb: `function`
55
    
56
    :param username: username to authenticate with, if not specified the username of the logged-in
57
                       user is used
58
    :type username: `string`
59
    
60
    :param password: password for ``password`` authentication or passphrase for decrypting
61
                       private key files
62
    :type password: `string`
63
    
64
    :param key_filename: location of a private key file on the file system
65
    :type key_filename: `string`
66
    
67
    :param allow_agent: whether to try connecting to SSH agent for keys
68
    :type allow_agent: `bool`
69
    
70
    :param look_for_keys: whether to look in usual locations for keys
71
    :type look_for_keys: `bool`
72
    
73
    :raises: :exc:`~ncclient.transport.SSHUnknownHostError`
74
    :raises: :exc:`~ncclient.transport.AuthenticationError`
75
    
76
    :rtype: `Manager`
77
    
78
.. function:: connect()
22
.. autofunction:: connect_ssh(host[, port=830, timeout=None, unknown_host_cb=default_unknown_host_cb, username=None, password, key_filename=None, allow_agent=True, look_for_keys=True])
79 23

  
80
    Same as :func:`connect_ssh`, since SSH is the default (and currently, the
81
    only) transport.
24
.. autodata:: connect
82 25

  
83 26
Manager
84 27
-------
......
263 206
        Valid values:
264 207
        
265 208
        * ``"all"`` -- any kind of *rpc-error* (error or warning)
266
        * ``"errors"`` -- where the *error-type* attribute says it is an error
209
        * ``"errors"`` -- where the *error-type* element says it is an error
267 210
        * ``"none"`` -- neither
268 211
        
269 212
    .. attribute:: client_capabilities
b/docs/source/operations.rst
1
:mod:`~ncclient.operations` -- Everything RPC
2
=============================================
3

  
4
Base classes
5
------------
6

  
7
.. module:: ncclient.operations
8
    :synopsis: Everything RPC
9

  
10
.. autoclass:: RPC(session[, async=False, timeout=None, raise_mode="none"])
11
    :members: DEPENDS, REPLY_CLS, _assert, _request, request, event, error, reply, raise_mode, is_async, timeout
12

  
13
.. autoclass:: RPCReply
14
    :members: xml, ok, error, errors
15

  
16
.. autoexception:: RPCError
17
    :show-inheritance:
18
    :members: type, severity, tag, path, message, info
19

  
20
Operations
21
----------
22

  
23
*TODO* The operation classes are currently undocumented. See documentation of
24
`~ncclient.manager.Manager` for methods that utilize the operation classes. The parameters accepted
25
by :meth:`~RPC.request` for these classes are the same.
26

  
27
Replies with data
28
-----------------
29

  
30
.. autoclass:: GetReply
31
    :show-inheritance:
32
    :members: data, data_ele, data_xml
33

  
34
Exceptions
35
----------
36

  
37
.. autoexception:: OperationError
38
    :show-inheritance:
39

  
40
.. autoexception:: MissingCapabilityError
41
    :show-inheritance:
42

  
43
.. autoexception:: TimeoutExpiredError
44
    :show-inheritance:
b/docs/source/transport.rst
1
:mod:`~ncclient.transport` -- Transport / Session layer
2
=======================================================
3

  
4
.. module:: ncclient.transport
5
    :synopsis: Transport / Session layer
6

  
7
Base types
8
-----------
9

  
10
.. autoclass:: Session
11
    :members: add_listener, remove_listener, get_listener_instance, client_capabilities, server_capabilities, connected, id
12

  
13
.. autoclass:: SessionListener
14
    :members: callback, errback
15

  
16
SSH session implementation
17
--------------------------
18

  
19
.. automethod:: ssh.default_unknown_host_cb
20

  
21
.. autoclass:: SSHSession
22
    :show-inheritance:
23
    :members: load_known_hosts, close, transport
24

  
25
    .. automethod:: connect(host[, port=830, timeout=None, username=None, password=None, key_filename=None, allow_agent=True, look_for_keys=True])
26

  
27
Errors
28
------
29

  
30
.. autoexception:: TransportError
31
    :show-inheritance:
32

  
33
.. autoexception:: SessionCloseError
34
    :show-inheritance:
35

  
36
.. autoexception:: SSHError
37
    :show-inheritance:
38

  
39
.. autoexception:: AuthenticationError
40
    :show-inheritance:
41

  
42
.. autoexception:: SSHUnknownHostError
43
    :show-inheritance:
44

  
b/docs/source/xml_.rst
1
:mod:`~ncclient.xml_` -- XML Handling
2
=====================================
3

  
4
*TODO*
b/ncclient/capabilities.py
29 29
    return []
30 30

  
31 31
def schemes(url_uri):
32
    """Given a URI that has a *scheme* query string (i.e. *:url* capability
33
    URI), will return a list of supported schemes.
32
    """Given a URI that has a *scheme* query string (i.e. *:url* capability URI), will return a list
33
    of supported schemes.
34 34
    """
35 35
    return url_uri.partition("?scheme=")[2].split(",")
36 36

  
37 37
class Capabilities:
38 38

  
39
    """Represents the set of capabilities available to a NETCONF client or
40
    server.
39
    """Represents the set of capabilities available to a NETCONF client or server. It is initialized
40
    with a list of capability URI's. These can be iterated over.
41 41
    
42
    Presence of a capability can be checked with the *in* operation. In addition
43
    to the URI, for capabilities of the form
44
    *urn:ietf:params:netconf:capability:$name:$version* their shorthand can be
45
    used as a key. For example, for
46
    *urn:ietf:params:netconf:capability:candidate:1.0* the shorthand would be
47
    *:candidate*. If version is significant, use *:candidate:1.0* as key.
42
    Presence of a capability can be checked with the *in* operation. In addition to the URI, for
43
    capabilities of the form *urn:ietf:params:netconf:capability:$name:$version* their shorthand can
44
    be used as a key. For example, for *urn:ietf:params:netconf:capability:candidate:1.0* the
45
    shorthand would be *:candidate*. If version is significant, use *:candidate:1.0* as key.
48 46
    """
49 47
    
50 48
    def __init__(self, capabilities):
51
        "Initializes with a list of capability URI's"
52 49
        self._dict = {}
53 50
        for uri in capabilities:
54 51
            self._dict[uri] = _abbreviate(uri)
......
74 71
        return self._dict.keys()
75 72

  
76 73
    def add(self, uri):
77
        "Add a capability"
74
        "Add a capability."
78 75
        self._dict[uri] = _abbreviate(uri)
79 76

  
80 77
    def remove(self, uri):
81
        "Remove a capability"
78
        "Remove a capability."
82 79
        if key in self._dict:
83 80
            del self._dict[key]
84 81
    
85
    def get_uri(self, shorthand):
86
        for uri, abbrs in self._dict.items():
87
            if shorthand in abbrs:
88
                return uri
82
    #def get_uri(self, shorthand):
83
    #    "Returns the URI that is inferred for a given shorthand."
84
    #    for uri, abbrs in self._dict.items():
85
    #        if shorthand in abbrs:
86
    #            return uri
b/ncclient/manager.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
"Thin layer of abstraction around NCClient"
15
"This module is a thin layer of abstraction around the library. It exposes all core functionality."
16 16

  
17 17
import capabilities
18 18
import operations
......
22 22
logger = logging.getLogger('ncclient.manager')
23 23

  
24 24

  
25
#: :class:`Capabilities` object representing the capabilities currently supported by NCClient
26 25
CAPABILITIES = capabilities.Capabilities([
27 26
    "urn:ietf:params:netconf:base:1.0",
28 27
    "urn:ietf:params:netconf:capability:writable-running:1.0",
......
37 36
    "urn:ietf:params:netconf:capability:interleave:1.0"
38 37
    #'urn:ietf:params:netconf:capability:notification:1.0', # TODO    
39 38
])
40

  
39
"""`~ncclient.capabilities.Capabilities` object representing the client's capabilities. This is used
40
during the initial capability exchange. Modify this if you need to announce some capability not
41
already included.
42
"""
41 43

  
42 44
OPERATIONS = {
43 45
    "get": operations.Get,
......
55 57
    "poweroff_machine": operations.PoweroffMachine,
56 58
    "reboot_machine": operations.RebootMachine
57 59
}
58

  
60
"""Dictionary of method names and corresponding `~ncclient.operations.RPC` subclasses. `Manager`
61
uses this to lookup operations, e.g. "get_config" is mapped to `~ncclient.operations.GetConfig`. It
62
is thus possible to add additional operations to the `Manager` API.
63
"""
59 64

  
60 65
def connect_ssh(*args, **kwds):
66
    """Initializes a NETCONF session over SSH, and creates a connected `Manager` instance. *host*
67
    must be specified, all the other arguments are optional and depend on the kind of host key
68
    verification and user authentication you want to complete.
69
        
70
    For the purpose of host key verification, on -NIX systems a user's :file:`~/.ssh/known_hosts`
71
    file is automatically considered. The *unknown_host_cb* argument specifies a callback that will
72
    be invoked when the server's host key cannot be verified. See
73
    :func:`~ncclient.transport.ssh.default_unknown_host_cb` for function signature.
74
    
75
    First, ``publickey`` authentication is attempted. If a specific *key_filename* is specified, it
76
    will be loaded and authentication attempted using it. If *allow_agent* is :const:`True` and an
77
    SSH agent is running, the keys provided by the agent will be tried. If *look_for_keys* is
78
    :const:`True`, keys in the :file:`~/.ssh/id_rsa` and :file:`~.ssh/id_dsa` will be tried. In case
79
    an encrypted key file is encountered, the *password* argument will be used as a decryption
80
    passphrase.
81
    
82
    If ``publickey`` authentication fails and the *password* argument has been supplied,
83
    ``password`` / ``keyboard-interactive`` SSH authentication will be attempted.
84
    
85
    :param host: hostname or address on which to connect
86
    :type host: `string`
87
    
88
    :param port: port on which to connect
89
    :type port: `int`
90
    
91
    :param timeout: timeout for socket connect
92
    :type timeout: `int`
93
    
94
    :param unknown_host_cb: optional; callback that is invoked when host key verification fails
95
    :type unknown_host_cb: `function`
96
    
97
    :param username: username to authenticate with, if not specified the username of the logged-in user is used
98
    :type username: `string`
99
    
100
    :param password: password for ``password`` authentication or passphrase for decrypting private key files
101
    :type password: `string`
102
    
103
    :param key_filename: location of a private key file on the file system
104
    :type key_filename: `string`
105
    
106
    :param allow_agent: whether to try connecting to SSH agent for keys
107
    :type allow_agent: `bool`
108
    
109
    :param look_for_keys: whether to look in usual locations for keys
110
    :type look_for_keys: `bool`
111
    
112
    :raises: :exc:`~ncclient.transport.SSHUnknownHostError`
113
    :raises: :exc:`~ncclient.transport.AuthenticationError`
114
    
115
    :rtype: `Manager`
116
    """    
61 117
    session = transport.SSHSession(CAPABILITIES)
62 118
    session.load_known_hosts()
63 119
    session.connect(*args, **kwds)
64 120
    return Manager(session)
65 121

  
66
#: Same as :meth:`connect_ssh`
67 122
connect = connect_ssh
68

  
123
"Same as :func:`connect_ssh`, since SSH is the default (and currently, the only) transport."
69 124

  
70 125
class Manager(object):
71 126

  
b/ncclient/operations/retrieve.py
20 20

  
21 21
class GetReply(RPCReply):
22 22

  
23
    """Adds attributes for the *<data>* element to :class:`RPCReply`, which
24
    pertains to the :class:`Get` and :class:`GetConfig` operations."""
23
    """Adds attributes for the *data* element to `RPCReply`. This pertains to the `Get` and
24
    `GetConfig` operations."""
25 25

  
26 26
    def _parsing_hook(self, root):
27 27
        self._data = None
28 28
        if not self._errors:
29 29
            self._data = root.find(qualify("data"))
30

  
30
    
31 31
    @property
32 32
    def data_ele(self):
33
        "*<data>* element as an :class:`~xml.etree.ElementTree.Element`"
33
        "*data* element as an `~xml.etree.ElementTree.Element`"
34 34
        if not self._parsed:
35 35
            self.parse()
36 36
        return self._data
37 37

  
38 38
    @property
39 39
    def data_xml(self):
40
        "*<data>* element as an XML string"
40
        "*data* element as an XML string"
41 41
        if not self._parsed:
42 42
            self.parse()
43 43
        return to_xml(self._data)
44 44
    
45
    #: Same as :attr:`data_ele`
46 45
    data = data_ele
47
    
48
    #def __repr__(self):
49
    #    return self.data_xml
46
    "Same as :attr:`data_ele`"
50 47

  
51 48

  
52 49
class Get(RPC):
53 50

  
54
    "The *<get>* RPC"
51
    "The *get* RPC."
55 52

  
56 53
    REPLY_CLS = GetReply
57 54

  
......
64 61

  
65 62
class GetConfig(RPC):
66 63

  
67
    "The *<get-config>* RPC"
64
    "The *get-config* RPC."
68 65

  
69 66
    REPLY_CLS = GetReply
70 67

  
b/ncclient/operations/rpc.py
24 24
logger = logging.getLogger("ncclient.operations.rpc")
25 25

  
26 26

  
27
class RPCReply:
28

  
29
    """Represents an *<rpc-reply>*. Only concerns itself with whether the
30
    operation was successful.
31

  
32
    .. note::
33
        If the reply has not yet been parsed there is an implicit, one-time
34
        parsing overhead to accessing the attributes defined by this class and
35
        any subclasses.
36
    """
37

  
38
    def __init__(self, raw):
39
        self._raw = raw
40
        self._parsed = False
41
        self._root = None
42
        self._errors = []
43

  
44
    def __repr__(self):
45
        return self._raw
46

  
47
    def _parsing_hook(self, root):
48
        """Subclass can implement.
49

  
50
        :type root: :class:`~xml.etree.ElementTree.Element`
51
        """
52
        pass
53

  
54
    def parse(self):
55
        """Parse the *<rpc-reply>*"""
56
        if self._parsed:
57
            return
58
        root = self._root = to_ele(self._raw) # The <rpc-reply> element
59
        # Per RFC 4741 an <ok/> tag is sent when there are no errors or warnings
60
        ok = root.find(qualify("ok"))
61
        if ok is None:
62
            # Create RPCError objects from <rpc-error> elements
63
            error = root.find(qualify("rpc-error"))
64
            if error is not None:
65
                for err in root.getiterator(error.tag):
66
                    # Process a particular <rpc-error>
67
                    self._errors.append(RPCError(err))
68
        self._parsing_hook(root)
69
        self._parsed = True
70

  
71
    @property
72
    def xml(self):
73
        "*<rpc-reply>* as returned"
74
        return self._raw
75

  
76
    @property
77
    def ok(self):
78
        "Boolean value indicating if there were no errors."
79
        if not self._parsed:
80
            self.parse()
81
        return not self._errors # empty list => false
82

  
83
    @property
84
    def error(self):
85
        """Short for :attr:`errors` [0]; :const:`None` if there were no errors.
86
        """
87
        if not self._parsed:
88
            self.parse()
89
        if self._errors:
90
            return self._errors[0]
91
        else:
92
            return None
93

  
94
    @property
95
    def errors(self):
96
        """`list` of :class:`RPCError` objects. Will be empty if there were no
97
        *<rpc-error>* elements in reply.
98
        """
99
        if not self._parsed:
100
            self.parse()
101
        return self._errors
102

  
103

  
104 27
class RPCError(OperationError):
105 28

  
106
    """Represents an *<rpc-error>*. It is a type of :exc:`OperationError`
107
    and can be raised like any other exception."""
29
    """Represents an *rpc-error*. It is a type of :exc:`OperationError` and can be raised like any
30
    other exception."""
108 31
    
109 32
    tag_to_attr = {
110 33
        qualify("error-type"): "_type",
......
115 38
        qualify("error-message"): "_message"
116 39
    }
117 40
    
118
    def __init__(self, err):
41
    def __init__(self, raw):
42
        self._raw = raw
119 43
        for attr in RPCError.tag_to_attr.values():
120 44
            setattr(self, attr, None)
121
        for subele in err:
45
        for subele in raw:
122 46
            attr = RPCError.tag_to_attr.get(subele.tag, None)
123 47
            if attr is not None:
124 48
                setattr(self, attr, subele.text if attr != "_info" else to_xml(subele) )
......
130 54
    def to_dict(self):
131 55
        return dict([ (attr[1:], getattr(self, attr)) for attr in RPCError.tag_to_attr.values() ])
132 56
    
57
    "*rpc-error* element as returned."
58
    @property
59
    def xml(self):
60
        return self._raw
61
    
133 62
    @property
134 63
    def type(self):
135
        "`string` representing text of *error-type* element"
64
        "`string` representing text of *error-type* element."
136 65
        return self._type
137 66
    
138 67
    @property
139 68
    def tag(self):
140
        "`string` representing text of *error-tag* element"
69
        "`string` representing text of *error-tag* element."
141 70
        return self._tag
142 71
    
143 72
    @property
144 73
    def severity(self):
145
        "`string` representing text of *error-severity* element"
74
        "`string` representing text of *error-severity* element."
146 75
        return self._severity
147 76
    
148 77
    @property
149 78
    def path(self):
150
        "`string` or :const:`None`; representing text of *error-path* element"
79
        "`string` or :const:`None`; representing text of *error-path* element."
151 80
        return self._path
152 81
    
153 82
    @property
154 83
    def message(self):
155
        "`string` or :const:`None`; representing text of *error-message* element"
84
        "`string` or :const:`None`; representing text of *error-message* element."
156 85
        return self._message
157 86
    
158 87
    @property
159 88
    def info(self):
160
        "`string` (XML) or :const:`None`, representing *error-info* element"
89
        "`string` (XML) or :const:`None`; representing *error-info* element."
161 90
        return self._info
162 91

  
163 92

  
93
class RPCReply:
94

  
95
    """Represents an *rpc-reply*. Only concerns itself with whether the operation was successful.
96

  
97
    .. note::
98
        If the reply has not yet been parsed there is an implicit, one-time parsing overhead to
99
        accessing the attributes defined by this class and any subclasses.
100
    """
101
    
102
    ERROR_CLS = RPCError
103
    "Subclasses can specify a different error class, but it should be a subclass of `RPCError`."
104
    
105
    def __init__(self, raw):
106
        self._raw = raw
107
        self._parsed = False
108
        self._root = None
109
        self._errors = []
110

  
111
    def __repr__(self):
112
        return self._raw
113
    
114
    def parse(self):
115
        "Parses the *rpc-reply*."
116
        if self._parsed: return
117
        root = self._root = to_ele(self._raw) # The <rpc-reply> element
118
        # Per RFC 4741 an <ok/> tag is sent when there are no errors or warnings
119
        ok = root.find(qualify("ok"))
120
        if ok is None:
121
            # Create RPCError objects from <rpc-error> elements
122
            error = root.find(qualify("rpc-error"))
123
            if error is not None:
124
                for err in root.getiterator(error.tag):
125
                    # Process a particular <rpc-error>
126
                    self._errors.append(ERROR_CLS(err))
127
        self._parsed = True
128
    
129
    @property
130
    def xml(self):
131
        "*rpc-reply* element as returned."
132
        return self._raw
133
    
134
    @property
135
    def ok(self):
136
        "Boolean value indicating if there were no errors."
137
        return not self.errors # empty list => false
138
    
139
    @property
140
    def error(self):
141
        "Returns the first `RPCError` and :const:`None` if there were no errors."
142
        self.parse()
143
        if self._errors:
144
            return self._errors[0]
145
        else:
146
            return None
147
    
148
    @property
149
    def errors(self):
150
        """`list` of `RPCError` objects. Will be empty if there were no *rpc-error* elements in
151
        reply."""
152
        self.parse()
153
        return self._errors
154

  
155

  
164 156
class RPCReplyListener(SessionListener): # internal use
165 157
    
166 158
    creation_lock = Lock()
......
212 204

  
213 205

  
214 206
class RPC(object):
215

  
216
    """Base class for all operations.
217

  
218
    Directly corresponds to *<rpc>* requests. Handles making the request, and
219
    taking delivery of the reply.
220
    """
221

  
222
    #: Subclasses can specify their dependencies on capabilities. List of URI's
223
    # or abbreviated names, e.g. ':writable-running'. These are verified at the
224
    # time of object creation. If the capability is not available, a
225
    # :exc:`MissingCapabilityError` is raised.
207
    
208
    """Base class for all operations, directly corresponding to *rpc* requests. Handles making the
209
    request, and taking delivery of the reply."""
210
    
226 211
    DEPENDS = []
227

  
228
    #: Subclasses can specify a different reply class, but it must be a
229
    # subclass of :class:`RPCReply`.
212
    """Subclasses can specify their dependencies on capabilities. List of URI's or abbreviated
213
    names, e.g. ':writable-running'. These are verified at the time of instantiation. If the
214
    capability is not available, a :exc:`MissingCapabilityError` is raised.
215
    """
216
    
230 217
    REPLY_CLS = RPCReply
231

  
218
    "Subclasses can specify a different reply class, but it should be a subclass of `RPCReply`."
219
    
232 220
    def __init__(self, session, async=False, timeout=None, raise_mode="none"):
233 221
        self._session = session
234 222
        try:
......
239 227
        self._async = async
240 228
        self._timeout = timeout
241 229
        self._raise_mode = raise_mode
242
        self._id = uuid1().urn # Keeps things simple instead of having a class attr that has to be locked
230
        self._id = uuid1().urn # Keeps things simple instead of having a class attr with running ID that has to be locked
243 231
        self._listener = RPCReplyListener(session)
244 232
        self._listener.register(self._id, self)
245 233
        self._reply = None
246 234
        self._error = None
247 235
        self._event = Event()
248

  
249
    def _build(self, subele):
250
        # internal
236
    
237
    def _wrap(self, subele):
238
        # internal use
251 239
        ele = new_ele("rpc", {"message-id": self._id}, xmlns=BASE_NS_1_0)
252 240
        ele.append(subele)
253 241
        return to_xml(ele)
254 242

  
255 243
    def _request(self, op):
256
        """Subclasses call this method to make the RPC request.
244
        """Implementations of :meth:`request` call this method to send the request and process the
245
        reply.
257 246
        
258
        In synchronous mode, waits until the reply is received and returns
259
        :class:`RPCReply`.
247
        In synchronous mode, blocks until the reply is received and returns `RPCReply`. Depending on
248
        the :attr:`raise_mode` a *rpc-error* element in the reply may lead to an :exc:`RPCError`
249
        exception.
260 250
        
261
        In asynchronous mode, returns immediately, returning a reference to this
262
        object. The :attr:`event` attribute will be set when the reply has been
263
        received (see :attr:`reply`) or an error occured (see :attr:`error`).
251
        In asynchronous mode, returns immediately, returning *self*. The :attr:`event` attribute
252
        will be set when the reply has been received (see :attr:`reply`) or an error occured (see
253
        :attr:`error`).
264 254
        
265
        :type opspec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
266
        :rtype: :class:`RPCReply` (sync) or :class:`RPC` (async)
255
        :param op: operation to be requested
256
        :type ops: `~xml.etree.ElementTree.Element`
257
        
258
        :rtype: `RPCReply` (sync) or `RPC` (async)
267 259
        """
268 260
        logger.info('Requesting %r' % self.__class__.__name__)
269
        req = self._build(op)
261
        req = self._wrap(op)
270 262
        self._session.send(req)
271 263
        if self._async:
272 264
            logger.debug('Async request, returning %r', self)
......
292 284
                raise TimeoutExpiredError
293 285

  
294 286
    def request(self, *args, **kwds):
295
        "Subclasses implement this method."
296
        return self._request(self.SPEC)
287
        """Subclasses must implement this method. Typically only the request needs to be built as an
288
        `~xml.etree.ElementTree.Element` and everything else can be handed off to
289
        :meth:`_request`."""
290
        pass
297 291
    
298 292
    def _assert(self, capability):
299
        """Subclasses can use this method to verify that a capability is available
300
        with the NETCONF server, before making a request that requires it. A
301
        :exc:`MissingCapabilityError` will be raised if the capability is not
302
        available."""
293
        """Subclasses can use this method to verify that a capability is available with the NETCONF
294
        server, before making a request that requires it. A :exc:`MissingCapabilityError` will be
295
        raised if the capability is not available."""
303 296
        if capability not in self._session.server_capabilities:
304 297
            raise MissingCapabilityError('Server does not support [%s]' %
305 298
                                         capability)
......
313 306
        # internal use
314 307
        self._error = err
315 308
        self._event.set()
316

  
309
    
317 310
    @property
318 311
    def reply(self):
319
        ":class:`RPCReply` element if reply has been received or :const:`None`"
312
        "`RPCReply` element if reply has been received or :const:`None`"
320 313
        return self._reply
321

  
314
    
322 315
    @property
323 316
    def error(self):
324 317
        """:exc:`Exception` type if an error occured or :const:`None`.
325 318
        
326 319
        .. note::
327
            This represents an error which prevented a reply from being
328
            received. An *<rpc-error>* does not fall in that category -- see
329
            :class:`RPCReply` for that.
320
            This represents an error which prevented a reply from being received. An *rpc-error*
321
            does not fall in that category -- see `RPCReply` for that.
330 322
        """
331 323
        return self._error
332

  
324
    
333 325
    @property
334 326
    def id(self):
335
        "The *message-id* for this RPC"
327
        "The *message-id* for this RPC."
336 328
        return self._id
337

  
329
    
338 330
    @property
339 331
    def session(self):
340
        """The :class:`~ncclient.transport.Session` object associated with this
341
        RPC"""
332
        "The `~ncclient.transport.Session` object associated with this RPC."
342 333
        return self._session
343 334

  
344 335
    @property
345 336
    def event(self):
346
        """:class:`~threading.Event` that is set when reply has been received or
347
        error occured."""
337
        """`~threading.Event` that is set when reply has been received or when an error preventing
338
        delivery of the reply occurs.
339
        """
348 340
        return self._event
349 341

  
350 342
    def set_async(self, async=True):
351
        """Set asynchronous mode for this RPC."""
352 343
        self._async = async
353 344
        if async and not session.can_pipeline:
354 345
            raise UserWarning('Asynchronous mode not supported for this device/session')
......
358 349
        self._raise_mode = mode
359 350

  
360 351
    def set_timeout(self, timeout):
361
        """Set the timeout for synchronous waiting; defining how long the RPC
362
        request will block on a reply before raising an error. Irrelevant for
363
        asynchronous usage."""
364 352
        self._timeout = timeout
365 353

  
366
    #: Whether this RPC is asynchronous
354
    raise_mode = property(fget=lambda self: self._raise_mode, fset=set_raise_mode)
355
    """Depending on this exception raising mode, an *rpc-error* in the reply may be raised as
356
    :exc:`RPCError` exceptions. Valid values:
357
    
358
    * ``"all"`` -- any kind of *rpc-error* (error or warning)
359
    * ``"errors"`` -- when the *error-type* element says it is an error
360
    * ``"none"`` -- neither
361
    """
362
    
367 363
    is_async = property(fget=lambda self: self._async, fset=set_async)
368

  
369
    #: Timeout for synchronous waiting
364
    """Specifies whether this RPC will be / was requested asynchronously. By default RPC's are
365
    synchronous.
366
    """
367
    
370 368
    timeout = property(fget=lambda self: self._timeout, fset=set_timeout)
369
    """Timeout in seconds for synchronous waiting defining how long the RPC request will block on a
370
    reply before raising :exc:`TimeoutExpiredError`. By default there is no timeout, represented by
371
    :const:`None`.
372
    
373
    Irrelevant for asynchronous usage.
374
    """
b/ncclient/transport/ssh.py
25 25
from session import Session
26 26

  
27 27
import logging
28
logger = logging.getLogger('ncclient.transport.ssh')
28
logger = logging.getLogger("ncclient.transport.ssh")
29 29

  
30 30
BUF_SIZE = 4096
31 31
MSG_DELIM = "]]>]]>"
32 32
TICK = 0.1
33 33

  
34 34
def default_unknown_host_cb(host, fingerprint):
35
    """An `unknown host callback` returns :const:`True` if it finds the key
36
    acceptable, and :const:`False` if not.
35
    """An `unknown host callback` returns :const:`True` if it finds the key acceptable, and
36
    :const:`False` if not.
37 37

  
38
    This default callback always returns :const:`False`, which would lead to
39
    :meth:`connect` raising a :exc:`SSHUnknownHost` exception.
40

  
41
    Supply another valid callback if you need to verify the host key
42
    programatically.
38
    This default callback always returns :const:`False`, which would lead to :meth:`connect` raising
39
    a :exc:`SSHUnknownHost` exception.
40
    
41
    Supply another valid callback if you need to verify the host key programatically.
43 42

  
44 43
    :arg host: the hostname that needs to be verified
45
    :type host: string
44
    :type host: `string`
46 45

  
47
    :arg fingerprint: a hex string representing the host key fingerprint
48
    :type fingerprint: string
46
    :arg fingerprint: a hex string representing the host key fingerprint, colon-delimited e.g. *4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21*
47
    :type fingerprint: `string`
49 48
    """
50 49
    return False
51 50

  
......
71 70
        self._parsing_pos = 0
72 71
    
73 72
    def _parse(self):
74
        '''Messages ae delimited by MSG_DELIM. The buffer could have grown by a
73
        """Messages ae delimited by MSG_DELIM. The buffer could have grown by a
75 74
        maximum of BUF_SIZE bytes everytime this method is called. Retains state
76 75
        across method calls and if a byte has been read it will not be
77
        considered again. '''
76
        considered again.
77
        """
78 78
        delim = MSG_DELIM
79 79
        n = len(delim) - 1
80 80
        expect = self._parsing_state
......
115 115
        self._parsing_pos = self._buffer.tell()
116 116

  
117 117
    def load_known_hosts(self, filename=None):
118
        """Load host keys from a :file:`known_hosts`-style file. Can be called multiple
119
        times.
118
        """Load host keys from a :file:`known_hosts`-style file. Can be called multiple times.
120 119

  
121 120
        If *filename* is not specified, looks in the default locations i.e.
122 121
        :file:`~/.ssh/known_hosts` and :file:`~/ssh/known_hosts` for Windows.
......
144 143
                unknown_host_cb=default_unknown_host_cb,
145 144
                username=None, password=None,
146 145
                key_filename=None, allow_agent=True, look_for_keys=True):
147
        """Connect via SSH and initialize the NETCONF session. First attempts
148
        the publickey authentication method and then password authentication.
146
        """Connect via SSH and initialize the NETCONF session. First attempts the publickey
147
        authentication method and then password authentication.
149 148

  
150
        To disable attemting publickey authentication altogether, call with
151
        *allow_agent* and *look_for_keys* as :const:`False`.
149
        To disable attemting publickey authentication altogether, call with *allow_agent* and
150
        *look_for_keys* as :const:`False`.
152 151

  
153 152
        :arg host: the hostname or IP address to connect to
154 153
        :type host: `string`
......
156 155
        :arg port: by default 830, but some devices use the default SSH port of 22 so this may need to be specified
157 156
        :type port: `int`
158 157

  
159
        :arg timeout: an optional timeout for the TCP handshake
158
        :arg timeout: an optional timeout for socket connect
160 159
        :type timeout: `int`
161 160

  
162
        :arg unknown_host_cb: called when a host key is not recognized
161
        :arg unknown_host_cb: called when the server host key is not recognized
163 162
        :type unknown_host_cb: see :meth:`signature <ssh.default_unknown_host_cb>`
164 163

  
165 164
        :arg username: the username to use for SSH authentication

Also available in: Unified diff