commit 8b4b993690980700f9eda5fc7f0f08d3614c5c5a
Author: Shikhar Bhushan <shikhar@schmizz.net>
Date:   Thu Apr 23 06:30:21 2009 +0000

    fixes to session layer
    
    git-svn-id: http://ncclient.googlecode.com/svn/trunk@48 6dbcf712-26ac-11de-a2f3-1373824ab735

diff --git a/ncclient/capabilities.py b/ncclient/capabilities.py
deleted file mode 100644
index a446fb8..0000000
--- a/ncclient/capabilities.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#    http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-class Capabilities:
-    
-    def __init__(self, capabilities=None):
-        self._dict = {}
-        if isinstance(capabilities, dict):
-            self._dict = capabilities
-        elif isinstance(capabilities, list):
-            for uri in capabilities:
-                self._dict[uri] = Capabilities.guess_shorthand(uri)
-    
-    def __contains__(self, key):
-        return ( key in self._dict ) or ( key in self._dict.values() )
-    
-    def __iter__(self):
-        return self._dict.keys().__iter__()
-    
-    def __repr__(self):
-        return repr(self._dict.keys())
-    
-    def add(self, uri, shorthand=None):
-        if shorthand is None:
-            shorthand = Capabilities.guess_shorthand(uri)
-        self._dict[uri] = shorthand
-    
-    set = add
-    
-    def remove(self, key):
-        if key in self._dict:
-            del self._dict[key]
-        else:
-            for uri in self._dict:
-                if self._dict[uri] == key:
-                    del self._dict[uri]
-                    break
-    
-    @staticmethod
-    def guess_shorthand(uri):
-        if uri.startswith('urn:ietf:params:netconf:capability:'):
-            return (':' + uri.split(':')[5])
-
-    
-CAPABILITIES = Capabilities([
-    'urn:ietf:params:netconf:base:1.0',
-    'urn:ietf:params:netconf:capability:writable-running:1.0',
-    'urn:ietf:params:netconf:capability:candidate:1.0',
-    'urn:ietf:params:netconf:capability:confirmed-commit:1.0',
-    'urn:ietf:params:netconf:capability:rollback-on-error:1.0',
-    'urn:ietf:params:netconf:capability:startup:1.0',
-    'urn:ietf:params:netconf:capability:url:1.0',
-    'urn:ietf:params:netconf:capability:validate:1.0',
-    'urn:ietf:params:netconf:capability:xpath:1.0',
-    'urn:ietf:params:netconf:capability:notification:1.0',
-    'urn:ietf:params:netconf:capability:interleave:1.0'
-    ])
-
-if __name__ == "__main__":
-    assert(':validate' in CAPABILITIES) # test __contains__
\ No newline at end of file
diff --git a/ncclient/content/__init__.py b/ncclient/content/__init__.py
index 81283ae..9238d4d 100644
--- a/ncclient/content/__init__.py
+++ b/ncclient/content/__init__.py
@@ -12,4 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-'This module serves as an XML abstraction layer'
\ No newline at end of file
+'This module serves as an XML abstraction layer'
+
+import logging
+logger = logging.getLogger('ncclient.content')
\ No newline at end of file
diff --git a/ncclient/content/builders.py b/ncclient/content/builders.py
index a773ba5..e887da4 100644
--- a/ncclient/content/builders.py
+++ b/ncclient/content/builders.py
@@ -23,7 +23,7 @@ class TreeBuilder:
     '''
     
     def __init__(self, spec):
-        self._root = Builder.build(spec)
+        self._root = TreeBuilder.build(spec)
         
     def to_string(self, encoding='utf-8'):
         return ET.tostring(self._root, encoding)
@@ -39,7 +39,7 @@ class TreeBuilder:
             ele = ET.Element(spec.get('tag'), spec.get('attributes', {}))
             ele.text = spec.get('text', '')
             for child in spec.get('children', []):
-                ele.append(Builder.build(child))
+                ele.append(TreeBuilder.build(child))
             return ele
         elif spec.has_key('comment'):
             return ET.Comment(spec.get('comment'))
@@ -53,7 +53,7 @@ class HelloBuilder:
     def build(capabilities, encoding='utf-8'):
         children = [{'tag': 'capability', 'text': uri } for uri in capabilities]
         spec = {
-            'tag': _('hello', Hello.NS),
+            'tag': _('hello', BASE_NS),
             'children': [{
                         'tag': 'capabilities',
                         'children': children
@@ -65,11 +65,18 @@ class HelloBuilder:
 class RPCBuilder:
     
     @staticmethod
+    def build(msgid, op, encoding='utf-8'):
+        if isinstance(opspec, basestring):
+            return build_from_string(msgid, op, encoding)
+        else:
+            return build_from_spec(msgid, op, encoding)
+    
+    @staticmethod
     def build_from_spec(msgid, opspec, encoding='utf-8'):
         if isinstance(opspec, dict):
             opspec = [opspec]
         return TreeBuilder({
-                'tag': _('rpc', RPC.NS),
+                'tag': _('rpc', BASE_NS),
                 'attributes': {'message-id': msgid},
                 'children': opspec
                 }).to_string(encoding)
diff --git a/ncclient/content/common.py b/ncclient/content/common.py
index 443b810..08d021f 100644
--- a/ncclient/content/common.py
+++ b/ncclient/content/common.py
@@ -12,11 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
+
 def qualify(tag, namespace=None):
     'Returns qualified name of form `{namespace}tag`'
     if namespace is None:
         return tag
     else:
         return '{%s}%s' % (namespace, tag)
-
-BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
\ No newline at end of file
diff --git a/ncclient/content/parsers.py b/ncclient/content/parsers.py
index e518a6b..2e02c4a 100644
--- a/ncclient/content/parsers.py
+++ b/ncclient/content/parsers.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from xml.etree import cElementTree as ElementTree
+from xml.etree import cElementTree as ET
 
 from common import BASE_NS
 from common import qualify as _
diff --git a/ncclient/operations/__init__.py b/ncclient/operations/__init__.py
index 0cafe5f..fcfe19b 100644
--- a/ncclient/operations/__init__.py
+++ b/ncclient/operations/__init__.py
@@ -14,24 +14,25 @@
 
 'NETCONF Remote Procedure Calls (RPC) and protocol operations'
 
-from ncclient import content
-from ncclient.capabilities import CAPABILITIES
-from rpc import RPC, RPCReply, RPCError
+import logging
+logger = logging.getLogger('ncclient.operations')
 
-from retrieve import Get, GetConfig
-from edit import EditConfig, DeleteConfig
-from session import CloseSession, KillSession
-from lock import Lock, Unlock
-from notification import CreateSubscription
-
-__all__ = [
-    'Get',
-    'GetConfig',
-    'EditConfig',
-    'DeleteConfig',
-    'Lock',
-    'Unlock',
-    'CloseSession',
-    'KillSession',
-    'CreateSubscription',
-    ]
\ No newline at end of file
+#from ncclient.session import CAPABILITIES
+#
+#from retrieve import Get, GetConfig
+#from edit import EditConfig, DeleteConfig
+#from session import CloseSession, KillSession
+#from lock import Lock, Unlock
+#from notification import CreateSubscription
+#
+#__all__ = [
+#    'Get',
+#    'GetConfig',
+#    'EditConfig',
+#    'DeleteConfig',
+#    'Lock',
+#    'Unlock',
+#    'CloseSession',
+#    'KillSession',
+#    'CreateSubscription',
+#    ]
\ No newline at end of file
diff --git a/ncclient/operations/listener.py b/ncclient/operations/listener.py
index 1a7362d..5e16a6f 100644
--- a/ncclient/operations/listener.py
+++ b/ncclient/operations/listener.py
@@ -1,4 +1,20 @@
-#!/usr/bin/env python
+# Copyright 2009 Shikhar Bhushan
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from weakref import WeakValueDictionary
+
+from ncclient.content.parsers import RootParser
 
 _listeners = WeakValueDictionary()
 
diff --git a/ncclient/operations/rpc.py b/ncclient/operations/rpc.py
index deb7d81..b075f8a 100644
--- a/ncclient/operations/rpc.py
+++ b/ncclient/operations/rpc.py
@@ -17,14 +17,8 @@
 from threading import Event, Lock
 from uuid import uuid1
 
-_listeners = WeakValueDictionary()
-
-def get_listener(session):
-    try:
-        return _listeners[session]
-    except KeyError:
-        _listeners[session] = MessageListener()
-        return _listeners[session]
+from listener import get_listener
+from ncclient.content.builders import RPCBuilder
 
 class RPC:
     
@@ -36,14 +30,13 @@ class RPC:
         self._reply_event = Event()
         self.listener.register(self._id, self)
         session.add_listener(self.listener)
-
+    
     def _response_cb(self, reply):
         self._reply = reply
         self._event.set()
     
-    def _do_request(self, operation):
-        'operation is xml string'
-        self._session.send(content.RPC.make(self._id, operation))
+    def _do_request(self, op):
+        self._session.send(RPCBuilder.build(self._id, op))
         if not self._async:
             self._reply_event.wait()
         return self._reply
@@ -85,37 +78,3 @@ class RPCReply:
         pass
     
 
-class MessageListener:
-    
-    def __init__(self):
-        # {message-id: RPC}
-        self._rpc = WeakValueDictionary()
-        # if the session gets closed by remote endpoint,
-        # need to know if it is an error event or was requested through
-        # a NETCONF operation i.e. CloseSession
-        self._expecting_close = False
-        # other recognized names and behavior on receiving them
-        self._recognized = []
-    
-    def __str__(self):
-        return 'MessageListener'
-    
-    def expect_close(self):
-        self._expecting_close = True
-    
-    def register(self, id, op):
-        self._id2rpc[id] = op
-    
-    ### Events
-    
-    def reply(self, raw):
-        pass
-    
-    def error(self, err):
-        from ncclient.session.session import SessionCloseError
-        if err is SessionCloseError:
-            logger.debug('session closed by remote endpoint, expecting_close=%s' %
-                         self._expecting_close)
-            if not self._expecting_close:
-                raise err
-
diff --git a/ncclient/session/__init__.py b/ncclient/session/__init__.py
index 9360967..1c757ce 100644
--- a/ncclient/session/__init__.py
+++ b/ncclient/session/__init__.py
@@ -12,11 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from session import SessionError, SessionCloseError
+import logging
+logger = logging.getLogger('ncclient.session')
+
+from session import DebugListener, SessionError, SessionCloseError
 from ssh import SSHSession
+from capabilities import CAPABILITIES, Capabilities
 
 __all__ = [
+    'DebugListener'
+    'Session'
     'SSHSession',
     'SessionError',
     'SessionCloseError',
+    'Capabilities',
+    'CAPABILITIES'
 ]
\ No newline at end of file
diff --git a/ncclient/session/capabilities.py b/ncclient/session/capabilities.py
new file mode 100644
index 0000000..a446fb8
--- /dev/null
+++ b/ncclient/session/capabilities.py
@@ -0,0 +1,71 @@
+# Copyright 2009 Shikhar Bhushan
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Capabilities:
+    
+    def __init__(self, capabilities=None):
+        self._dict = {}
+        if isinstance(capabilities, dict):
+            self._dict = capabilities
+        elif isinstance(capabilities, list):
+            for uri in capabilities:
+                self._dict[uri] = Capabilities.guess_shorthand(uri)
+    
+    def __contains__(self, key):
+        return ( key in self._dict ) or ( key in self._dict.values() )
+    
+    def __iter__(self):
+        return self._dict.keys().__iter__()
+    
+    def __repr__(self):
+        return repr(self._dict.keys())
+    
+    def add(self, uri, shorthand=None):
+        if shorthand is None:
+            shorthand = Capabilities.guess_shorthand(uri)
+        self._dict[uri] = shorthand
+    
+    set = add
+    
+    def remove(self, key):
+        if key in self._dict:
+            del self._dict[key]
+        else:
+            for uri in self._dict:
+                if self._dict[uri] == key:
+                    del self._dict[uri]
+                    break
+    
+    @staticmethod
+    def guess_shorthand(uri):
+        if uri.startswith('urn:ietf:params:netconf:capability:'):
+            return (':' + uri.split(':')[5])
+
+    
+CAPABILITIES = Capabilities([
+    'urn:ietf:params:netconf:base:1.0',
+    'urn:ietf:params:netconf:capability:writable-running:1.0',
+    'urn:ietf:params:netconf:capability:candidate:1.0',
+    'urn:ietf:params:netconf:capability:confirmed-commit:1.0',
+    'urn:ietf:params:netconf:capability:rollback-on-error:1.0',
+    'urn:ietf:params:netconf:capability:startup:1.0',
+    'urn:ietf:params:netconf:capability:url:1.0',
+    'urn:ietf:params:netconf:capability:validate:1.0',
+    'urn:ietf:params:netconf:capability:xpath:1.0',
+    'urn:ietf:params:netconf:capability:notification:1.0',
+    'urn:ietf:params:netconf:capability:interleave:1.0'
+    ])
+
+if __name__ == "__main__":
+    assert(':validate' in CAPABILITIES) # test __contains__
\ No newline at end of file
diff --git a/ncclient/session/session.py b/ncclient/session/session.py
index 6523e21..40208c4 100644
--- a/ncclient/session/session.py
+++ b/ncclient/session/session.py
@@ -13,12 +13,12 @@
 # limitations under the License.
 
 import logging
-from threading import Thread, Event
+from threading import Thread, Lock, Event
 from Queue import Queue
 
 from capabilities import Capabilities, CAPABILITIES
 
-logger = logging.getLogger('ncclient.session.session')
+logger = logging.getLogger('ncclient.session')
 
 class SessionError(Exception):
     
@@ -26,13 +26,18 @@ class SessionError(Exception):
 
 class SessionCloseError(SessionError):
     
-    def __str__(self):
-        return 'RECEIVED: %s | UNSENT: %s' % (self._in_buf, self._out_buf)
-    
     def __init__(self, in_buf, out_buf=None):
         SessionError.__init__(self)
         self._in_buf, self._out_buf = in_buf, out_buf
-
+        
+    def __str__(self):
+        msg = 'Session closed by remote endpoint.'
+        if self._in_buf:
+            msg += '\nIN_BUFFER: %s' % self._in_buf
+        if self._out_buf:
+            msg += '\nOUT_BUFFER: %s' % self._out_buf
+        return msg
+    
 class Session(Thread):
     
     def __init__(self):
@@ -42,7 +47,7 @@ class Session(Thread):
         self._id = None # session-id
         self._q = Queue()
         self._connected = False # to be set/cleared by subclass implementation
-        self._listeners = set(listeners)
+        self._listeners = set([])
         self._lock = Lock()
     
     def _post_connect(self):
@@ -67,7 +72,7 @@ class Session(Thread):
         proceed.wait()
         # received hello message or an error happened
         self.remove_listener(listener)
-        if self._error:
+        if error:
             self._close()
             raise self._error
     
@@ -135,7 +140,7 @@ class Session(Thread):
 class HelloListener:
     
     def __init__(self, init_cb, error_cb):
-        self._init_cb, self._error_cb = reply_cb, error_cb
+        self._init_cb, self._error_cb = init_cb, error_cb
     
     def __str__(self):
         return 'HelloListener'
@@ -153,3 +158,15 @@ class HelloListener:
     
     def error(self, err):
         self._error_cb(err)
+
+
+class DebugListener:
+    
+    def __str__(self):
+        return 'DebugListener'
+    
+    def reply(self, raw):
+        logger.debug('DebugListener:reply:%s' % raw)
+    
+    def error(self, err):
+        logger.debug('DebugListener:error:%s' % err)
diff --git a/ncclient/session/ssh.py b/ncclient/session/ssh.py
index 159f17a..30a8fc2 100644
--- a/ncclient/session/ssh.py
+++ b/ncclient/session/ssh.py
@@ -12,17 +12,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import logging
 from cStringIO import StringIO
 from os import SEEK_CUR
 import socket
 
 import paramiko
 
+from . import logger
 from session import Session, SessionError, SessionCloseError
 
-logger = logging.getLogger('ncclient.ssh')
-
 BUF_SIZE = 4096
 MSG_DELIM = ']]>]]>'
 
