commit b2d60e4941052d5a1359846cd266fc282f95bc47
Author: Shikhar Bhushan <shikhar@schmizz.net>
Date:   Thu Aug 19 21:41:10 2010 +0100

    looking at this code and trying things out after a long time. lots of cleanups and fixes.

diff --git a/ncclient/capabilities.py b/ncclient/capabilities.py
index 2087336..30b1d75 100644
--- a/ncclient/capabilities.py
+++ b/ncclient/capabilities.py
@@ -12,20 +12,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-#_capability_map = {
-#    "urn:liberouter:params:netconf:capability:power-control:1.0":
-#        [":power-control", ":power-control:1.0"]
-#}
-
 def _abbreviate(uri):
     if uri.startswith("urn:ietf:params:netconf:"):
         splitted = uri.split(":")
         if ":capability:" in uri:
-            return [ ":" + splitted[5], ":" + splitted[5] + ":" + splitted[6] ]
+            name, version = splitted[5], splitted[6]
+            return [ ":" + name, ":" + name + ":" + version ]
         elif ":base:" in uri:
             return [ ":base", ":base" + ":" + splitted[5] ]
-    #elif uri in _capability_map:
-    #    return _capability_map[uri]
     return []
 
 def schemes(url_uri):
@@ -62,14 +56,11 @@ class Capabilities:
         return len(self._dict)
 
     def __iter__(self):
-        return self._dict.keys().__iter__()
+        return self._dict.iterkeys()
 
     def __repr__(self):
         return repr(self._dict.keys())
 
-    def __list__(self):
-        return self._dict.keys()
-
     def add(self, uri):
         "Add a capability."
         self._dict[uri] = _abbreviate(uri)
@@ -77,10 +68,4 @@ class Capabilities:
     def remove(self, uri):
         "Remove a capability."
         if key in self._dict:
-            del self._dict[key]
-    
-    #def get_uri(self, shorthand):
-    #    "Returns the URI that is inferred for a given shorthand."
-    #    for uri, abbrs in self._dict.items():
-    #        if shorthand in abbrs:
-    #            return uri
+            del self._dict[key]
\ No newline at end of file
diff --git a/ncclient/manager.py b/ncclient/manager.py
index a508eff..758d1d9 100644
--- a/ncclient/manager.py
+++ b/ncclient/manager.py
@@ -19,6 +19,7 @@ import operations
 import transport
 
 import logging
+
 logger = logging.getLogger('ncclient.manager')
 
 CAPABILITIES = [
@@ -33,11 +34,8 @@ CAPABILITIES = [
     "urn:ietf:params:netconf:capability:xpath:1.0",
     "urn:liberouter:params:netconf:capability:power-control:1.0"
     "urn:ietf:params:netconf:capability:interleave:1.0"
-    #'urn:ietf:params:netconf:capability:notification:1.0', # TODO    
 ]
-"""A list of URI's representing the client's capabilities. This is used during the initial
-capability exchange. Modify this if you need to announce some capability not already included.
-"""
+"A list of URI's representing the client's capabilities. This is used during the initial capability exchange. Modify this if you need to announce some capability not already included."
 
 OPERATIONS = {
     "get": operations.Get,
@@ -55,19 +53,12 @@ OPERATIONS = {
     "poweroff_machine": operations.PoweroffMachine,
     "reboot_machine": operations.RebootMachine
 }
-"""Dictionary of method names and corresponding `~ncclient.operations.RPC` subclasses. It is used to
-lookup operations, e.g. "get_config" is mapped to `~ncclient.operations.GetConfig`. It is thus
-possible to add additional operations to the `Manager` API."""
+"""Dictionary of method names and corresponding `~ncclient.operations.RPC` subclasses. It is used to lookup operations, e.g. "get_config" is mapped to `~ncclient.operations.GetConfig`. It is thus possible to add additional operations to the `Manager` API."""
 
 def connect_ssh(*args, **kwds):
-    """Initializes a NETCONF session over SSH, and creates a connected `Manager` instance. *host*
-    must be specified, all the other arguments are optional and depend on the kind of host key
-    verification and user authentication you want to complete.
+    """Initializes a NETCONF session over SSH, and creates a connected `Manager` instance. *host* must be specified, all the other arguments are optional and depend on the kind of host key verification and user authentication you want to complete.
         
-    For the purpose of host key verification, on -NIX systems a user's :file:`~/.ssh/known_hosts`
-    file is automatically considered. The *unknown_host_cb* argument specifies a callback that will
-    be invoked when the server's host key cannot be verified. See
-    :func:`~ncclient.transport.ssh.default_unknown_host_cb` for function signature.
+    For the purpose of host key verification, on -NIX systems a user's :file:`~/.ssh/known_hosts` file is automatically considered. The *unknown_host_cb* argument specifies a callback that will be invoked when the server's host key cannot be verified. See :func:`~ncclient.transport.ssh.default_unknown_host_cb` for function signature.
     
     First, ``publickey`` authentication is attempted. If a specific *key_filename* is specified, it
     will be loaded and authentication attempted using it. If *allow_agent* is :const:`True` and an
@@ -76,8 +67,7 @@ def connect_ssh(*args, **kwds):
     an encrypted key file is encountered, the *password* argument will be used as a decryption
     passphrase.
     
-    If ``publickey`` authentication fails and the *password* argument has been supplied,
-    ``password`` / ``keyboard-interactive`` SSH authentication will be attempted.
+    If ``publickey`` authentication fails and the *password* argument has been supplied, ``password`` / ``keyboard-interactive`` SSH authentication will be attempted.
     
     :param host: hostname or address on which to connect
     :type host: `string`
@@ -110,7 +100,7 @@ def connect_ssh(*args, **kwds):
     :raises: :exc:`~ncclient.transport.AuthenticationError`
     
     :rtype: `Manager`
-    """    
+    """
     session = transport.SSHSession(capabilities.Capabilities(CAPABILITIES))
     session.load_known_hosts()
     session.connect(*args, **kwds)
@@ -119,34 +109,47 @@ def connect_ssh(*args, **kwds):
 connect = connect_ssh
 "Same as :func:`connect_ssh`, since SSH is the default (and currently, the only) transport."
 
+class OpExecutor(type):
+    def __new__(cls, name, bases, attrs):
+        def make_wrapper(op_cls):
+            def wrapper(self, *args, **kwds):
+                return self.execute(op_cls, *args, **kwds)
+            wrapper.func_doc = op_cls.request.func_doc
+            return wrapper
+        for op_name, op_cls in OPERATIONS.iteritems():
+            attrs[op_name] = make_wrapper(op_cls)
+        return super(OpExecutor, cls).__new__(cls, name, bases, attrs)
+
 class Manager(object):
 
+    __metaclass__ = OpExecutor
+
+    RAISE_NONE = 0
+    RAISE_ERRORS = 1
+    RAISE_ALL = 2
+
     def __init__(self, session):
         self._session = session
         self._async_mode = False
         self._timeout = None
-        self._raise_mode = 'all'
+        self._raise_mode = self.RAISE_ALL
 
     def __enter__(self):
         return self
 
-    def __exit__(self, *argss):
+    def __exit__(self, *args):
         self.close_session()
         return False
 
-    def __getattr__(self, name):
-        op = OPERATIONS.get(name, None)
-        if op is None:
-            raise AttributeError
-        else:
-            return op(self._session,
-                      async=self._async_mode,
-                      timeout=self._timeout,
-                      raise_mode=self._raise_mode).request
-    
+    def execute(self, cls, *args, **kwds):
+        return cls(self._session,
+                   async=self._async_mode,
+                   timeout=self._timeout,
+                   raise_mode=self._raise_mode).request(*args, **kwds)
+
     def locked(self, target):
         return operations.LockContext(self._session, target)
-    
+
     @property
     def client_capabilities(self):
         return self._session._client_capabilities
@@ -167,7 +170,7 @@ class Manager(object):
         self._async_mode = mode
 
     def set_raise_mode(self, mode):
-        assert(choice in ("all", "errors", "none"))
+        assert(choice in (self.RAISE_NONE, self.RAISE_ERRORS, self.RAISE_ALL))
         self._raise_mode = mode
 
     async_mode = property(fget=lambda self: self._async_mode, fset=set_async_mode)
diff --git a/ncclient/operations/edit.py b/ncclient/operations/edit.py
index 2f93b34..28e9cfb 100644
--- a/ncclient/operations/edit.py
+++ b/ncclient/operations/edit.py
@@ -27,8 +27,7 @@ class EditConfig(RPC):
 
     "*<edit-config>* RPC"
     
-    def request(self, target, config, default_operation=None, test_option=None,
-                error_option=None):
+    def request(self, target, config, default_operation=None, test_option=None, error_option=None):
         node = new_ele("edit-config")
         node.append(util.datastore_or_url("target", target, self._assert))
         if error_option is not None:
@@ -95,7 +94,6 @@ class Commit(RPC):
             self._assert(":confirmed-commit")
             sub_ele(node, "confirmed")
             if timeout is not None:
-                # TODO check if timeout is a valid integer?
                 sub_ele(node, "confirm-timeout").text = timeout
         return self._request(node)
 
diff --git a/ncclient/operations/flowmon.py b/ncclient/operations/flowmon.py
index fa3fee0..242430c 100644
--- a/ncclient/operations/flowmon.py
+++ b/ncclient/operations/flowmon.py
@@ -26,7 +26,7 @@ class PoweroffMachine(RPC):
 
     DEPENDS = ["urn:liberouter:params:netconf:capability:power-control:1.0"]
     
-    def request(self, target):
+    def request(self):
         return self._request(new_ele(qualify("poweroff-machine", PC_URN)))
 
 class RebootMachine(RPC):
@@ -35,5 +35,5 @@ class RebootMachine(RPC):
 
     DEPENDS = ["urn:liberouter:params:netconf:capability:power-control:1.0"]
 
-    def request(self, target):
+    def request(self):
         return self._request(new_ele(qualify("reboot-machine", PC_URN)))
diff --git a/ncclient/operations/lock.py b/ncclient/operations/lock.py
index 3a1a2c7..94f9f29 100644
--- a/ncclient/operations/lock.py
+++ b/ncclient/operations/lock.py
@@ -28,7 +28,7 @@ class Lock(RPC):
     
     def request(self, target):
         node = new_ele("lock")
-        sub_ele(sub_ele(node, "target"), "running")
+        sub_ele(sub_ele(node, "target"), target)
         return self._request(node)
 
 
@@ -38,7 +38,7 @@ class Unlock(RPC):
     
     def request(self, target):
         node = new_ele("unlock")
-        sub_ele(sub_ele(node, "target"), "running")
+        sub_ele(sub_ele(node, "target"), target)
         return self._request(node)
 
 
diff --git a/ncclient/operations/rpc.py b/ncclient/operations/rpc.py
index 3fd7f2d..fd71602 100644
--- a/ncclient/operations/rpc.py
+++ b/ncclient/operations/rpc.py
@@ -54,9 +54,9 @@ class RPCError(OperationError):
     def to_dict(self):
         return dict([ (attr[1:], getattr(self, attr)) for attr in RPCError.tag_to_attr.values() ])
     
-    "*rpc-error* element as returned."
     @property
     def xml(self):
+        "*rpc-error* element as returned."
         return self._raw
     
     @property
@@ -123,8 +123,12 @@ class RPCReply:
             if error is not None:
                 for err in root.getiterator(error.tag):
                     # Process a particular <rpc-error>
-                    self._errors.append(ERROR_CLS(err))
+                    self._errors.append(self.ERROR_CLS(err))
+        self._parsing_hook(root)
         self._parsed = True
+
+    def _parsing_hook(self, root):
+        pass
     
     @property
     def xml(self):
@@ -264,8 +268,7 @@ class RPC(object):
             logger.debug('Async request, returning %r', self)
             return self
         else:
-            logger.debug('Sync request, will wait for timeout=%r' %
-                         self._timeout)
+            logger.debug('Sync request, will wait for timeout=%r' % self._timeout)
             self._event.wait(self._timeout)
             if self._event.isSet():
                 if self._error:
@@ -283,7 +286,7 @@ class RPC(object):
             else:
                 raise TimeoutExpiredError
 
-    def request(self, *args, **kwds):
+    def request(self):
         """Subclasses must implement this method. Typically only the request needs to be built as an
         `~xml.etree.ElementTree.Element` and everything else can be handed off to
         :meth:`_request`."""
@@ -294,8 +297,7 @@ class RPC(object):
         server, before making a request that requires it. A :exc:`MissingCapabilityError` will be
         raised if the capability is not available."""
         if capability not in self._session.server_capabilities:
-            raise MissingCapabilityError('Server does not support [%s]' %
-                                         capability)
+            raise MissingCapabilityError('Server does not support [%s]' % capability)
     
     def deliver_reply(self, raw):
         # internal use
diff --git a/ncclient/transport/errors.py b/ncclient/transport/errors.py
index 84aa986..ec95c68 100644
--- a/ncclient/transport/errors.py
+++ b/ncclient/transport/errors.py
@@ -36,7 +36,6 @@ class SSHError(TransportError):
 class SSHUnknownHostError(SSHError):
 
     def __init__(self, host, fingerprint):
-        SSHError.__init__(self, 'Unknown host key [%s] for [%s]'
-                          % (fingerprint, host))
+        SSHError.__init__(self, 'Unknown host key [%s] for [%s]' % (fingerprint, host))
         self.host = host
         self.fingerprint = fingerprint
diff --git a/ncclient/xml_.py b/ncclient/xml_.py
index 0ea2bb3..7867b60 100644
--- a/ncclient/xml_.py
+++ b/ncclient/xml_.py
@@ -71,8 +71,7 @@ def to_xml(ele, encoding="UTF-8"):
     return xml if xml.startswith('<?xml') else '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml)
 
 def to_ele(x):
-    """Convert XML to `~xml.etree.ElementTree.Element`. If passed an
-    `~xml.etree.ElementTree.Element` simply returns that.
+    """Convert XML to `~xml.etree.ElementTree.Element`. If passed an `~xml.etree.ElementTree.Element` simply returns that.
     
     :arg x: the XML document or element
     :type x: `string` or `~xml.etree.ElementTree.Element`
