commit 0304f0413030f1cb2ea6226b75fc7fe79a303b7a
Author: Shikhar Bhushan <shikhar@schmizz.net>
Date:   Tue Jul 14 00:40:05 2009 +0000

    most importantly: finally, code in trunk actually works
    
    less significantly :p figured out issue 7
    
    many minor bugfixes... definitely more robust
    
    
    
    git-svn-id: http://ncclient.googlecode.com/svn/trunk@164 6dbcf712-26ac-11de-a2f3-1373824ab735

diff --git a/ncclient/manager.py b/ncclient/manager.py
index a365d81..f1cb766 100644
--- a/ncclient/manager.py
+++ b/ncclient/manager.py
@@ -56,17 +56,15 @@ class Manager(object):
         return False
 
     def __getattr__(self, name):
-        try:
-            op = operations.INDEX[name]
-        except KeyError:
+        op = operations.INDEX.get(name, None)
+        if op is None:
             raise AttributeError
         else:
-            reply = op(self.session,
+            return op(self.session,
                       async=self._async_mode,
-                      timeout=self.timeout,
+                      timeout=self._timeout,
                       raise_mode=self._raise_mode).request
-            return op if self._async_mode else reply
-
+    
     def locked(self, target):
         """Returns a context manager for the *with* statement.
 
@@ -79,10 +77,11 @@ class Manager(object):
     def close(self):
         """Closes the NETCONF session. First does *<close-session>* RPC."""
         try: # try doing it clean
+            self._async_mode = False
             self.close_session()
         except Exception as e:
             logger.debug('error doing <close-session> -- %r' % e)
-        if self._session.connected: # if that didn't work...
+        if self._session.connected: # if that didn't work, this sure will :)
             self._session.close()
 
     @property
diff --git a/ncclient/operations/lock.py b/ncclient/operations/lock.py
index 6b9174a..e5b93d5 100644
--- a/ncclient/operations/lock.py
+++ b/ncclient/operations/lock.py
@@ -75,7 +75,9 @@ class LockContext:
 
     """
     A context manager for the :class:`Lock` / :class:`Unlock` pair of RPC's.
-
+    
+    RPC errors are always raised as exceptions.
+    
     Initialise with session instance (:class:`Session
     <ncclient.transport.Session>`) and lock target (:ref:`source_target`)
     """
@@ -86,7 +88,7 @@ class LockContext:
 
     def __enter__(self):
         reply = Lock(self.session).request(self.target)
-        if not reply.ok:
+        if not reply.ok: # an error locking should definitely always be raised
             raise reply.error
         else:
             return self
diff --git a/ncclient/operations/rpc.py b/ncclient/operations/rpc.py
index fe61e34..1b2a24c 100644
--- a/ncclient/operations/rpc.py
+++ b/ncclient/operations/rpc.py
@@ -188,7 +188,7 @@ class RPCReplyListener(SessionListener):
             instance = object.__new__(cls)
             instance._lock = Lock()
             instance._id2rpc = {}
-            instance._pipelined = session.can_pipeline
+            #instance._pipelined = session.can_pipeline
             session.add_listener(instance)
         return instance
 
@@ -200,29 +200,24 @@ class RPCReplyListener(SessionListener):
         tag, attrs = root
         if xml_.unqualify(tag) != 'rpc-reply':
             return
-        rpc = None
-        for key in attrs:
-            if xml_.unqualify(key) == 'message-id':
-                id = attrs[key]
+        for key in attrs: # in the <rpc-reply> attributes
+            if xml_.unqualify(key) == 'message-id': # if we found msgid attr
+                id = attrs[key] # get the msgid
                 try:
                     with self._lock:
-                        rpc = self._id2rpc.pop(id)
+                        rpc = self._id2rpc.get(id) # the corresponding rpc
+                        logger.debug('delivering to %r' % rpc)
+                        rpc.deliver_reply(raw)
                 except KeyError:
-                    logger.warning('no object registered for message-id: [%s]' % id)
-                except Exception as e:
-                    logger.debug('error - %r' % e)
-                break
+                    raise OperationError('Unknown message-id: %s', id)
+                # no catching other exceptions, fail loudly if must
+                else:
+                    # if no error delivering, can del the reference to the RPC
+                    del self._id2rpc[id]
+                    break
         else:
-            if not self._pipelined:
-                with self._lock:
-                    assert(len(self._id2rpc) == 1)
-                    rpc = self._id2rpc.values()[0]
-                    self._id2rpc.clear()
-            else:
-                logger.warning('<rpc-reply> without message-id received: %s' % raw)
-        logger.debug('delivering to %r' % rpc)
-        rpc.deliver_reply(raw)
-
+            raise OperationError('Could not find "message-id" attribute in <rpc-reply>')
+    
     def errback(self, err):
         try:
             for rpc in self._id2rpc.values():
@@ -281,25 +276,24 @@ class RPC(object):
 
     def _request(self, op):
         """Subclasses call this method to make the RPC request.
-
-        In asynchronous mode, returns an :class:`~threading.Event` which is set
-        when the reply has been received or an error occured. It is prudent,
-        therefore, to check the :attr:`error` attribute before accesing
-        :attr:`reply`.
-
-        Otherwise, waits until the reply is received and returns
+        
+        In synchronous mode, waits until the reply is received and returns
         :class:`RPCReply`.
-
+        
+        In asynchronous mode, returns immediately, returning a reference to this
+        object. The :attr:`event` attribute will be set when the reply has been
+        received (see :attr:`reply`) or an error occured (see :attr:`error`).
+        
         :arg opspec: :ref:`dtree` for the operation
         :type opspec: :obj:`dict` or :obj:`string` or :class:`~xml.etree.ElementTree.Element`
-        :rtype: :class:`~threading.Event` or :class:`RPCReply`
+        :rtype: :class:`RPCReply` (sync) or :class:`RPC` (async)
         """
         logger.debug('request %r with opsepc=%r' % (self, op))
         req = self._build(op)
         self._session.send(req)
         if self._async:
-            logger.debug('async, returning event')
-            return self._event
+            logger.debug('async, returning')
+            return self
         else:
             logger.debug('sync, will wait for timeout=%r' % self._timeout)
             self._event.wait(self._timeout)
@@ -332,17 +326,17 @@ class RPC(object):
         if capability not in self._session.server_capabilities:
             raise MissingCapabilityError('Server does not support [%s]' %
                                          capability)
-
+    
     def deliver_reply(self, raw):
         # internal use
         self._reply = self.REPLY_CLS(raw)
-        self._delivery_hook()
+        #self._delivery_hook() -- usecase?!
         self._event.set()
 
     def deliver_error(self, err):
         # internal use
         self._error = err
-        self._delivery_hook()
+        #self._delivery_hook() -- usecase?!
         self._event.set()
 
     @property
@@ -353,11 +347,7 @@ class RPC(object):
     @property
     def error(self):
         """:exc:`Exception` type if an error occured or :const:`None`.
-
-        This attribute should be checked if the request was made asynchronously,
-        so that it can be determined if :attr:`event` being set is because of a
-        reply or error.
-
+        
         .. note::
             This represents an error which prevented a reply from being
             received. An *<rpc-error>* does not fall in that category -- see
diff --git a/ncclient/transport/session.py b/ncclient/transport/session.py
index fd12341..d503494 100644
--- a/ncclient/transport/session.py
+++ b/ncclient/transport/session.py
@@ -51,18 +51,15 @@ class Session(Thread):
             listeners = list(self._listeners)
         for l in listeners:
             logger.debug('dispatching message to %r' % l)
-            try:
-                l.callback(root, raw)
-            except Exception as e:
-                logger.warning('[error] %r' % e)
-
+            l.callback(root, raw) # no try-except; fail loudly if you must!
+    
     def _dispatch_error(self, err):
         with self._lock:
             listeners = list(self._listeners)
         for l in listeners:
             logger.debug('dispatching error to %r' % l)
-            try:
-                l.errback(err)
+            try: # here we can be more considerate with catching exceptions
+                l.errback(err) 
             except Exception as e:
                 logger.warning('error dispatching to %r: %r' % (l, e))
 
