commit 583c11f6d8e12a824fb22eef347a9c7de99462f4
Author: Shikhar Bhushan <shikhar@schmizz.net>
Date:   Wed May 13 14:49:47 2009 +0000

    git-svn-id: http://ncclient.googlecode.com/svn/trunk@115 6dbcf712-26ac-11de-a2f3-1373824ab735

diff --git a/ncclient/transport/__init__.py b/ncclient/transport/__init__.py
index 024b8fd..51c4a15 100644
--- a/ncclient/transport/__init__.py
+++ b/ncclient/transport/__init__.py
@@ -12,15 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"TODO: docstring"
+"Transport layer"
 
+from session import Session, SessionListener
 from ssh import SSHSession
+from errors import *
 
 __all__ = [
+    'Session',
+    'SessionListener',
+    'SSHSession',
     'TransportError',
     'AuthenticationError',
     'SessionCloseError',
     'SSHError',
-    'SSHUnknownHostError',
-    'SSHSession'
+    'SSHUnknownHostError'
 ]
\ No newline at end of file
diff --git a/ncclient/transport/errors.py b/ncclient/transport/errors.py
index 56f7c32..683129d 100644
--- a/ncclient/transport/errors.py
+++ b/ncclient/transport/errors.py
@@ -12,8 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"TODO: docstrings"
-
 from ncclient import NCClientError
 
 class TransportError(NCClientError):
diff --git a/ncclient/transport/hello.py b/ncclient/transport/hello.py
index 55e605b..7bc956f 100644
--- a/ncclient/transport/hello.py
+++ b/ncclient/transport/hello.py
@@ -14,57 +14,7 @@
 
 "All to do with NETCONF <hello> messages"
 
-from ncclient import content
 
-class HelloHandler:
-    
-    def __init__(self, init_cb, error_cb):
-        self._init_cb = init_cb
-        self._error_cb = error_cb
-    
-    def callback(self, root, raw):
-        if content.unqualify(root[0]) == 'hello':
-            try:
-                id, capabilities = HelloHandler.parse(raw)
-            except Exception as e:
-                self._error_cb(e)
-            else:
-                self._init_cb(id, capabilities)
-    
-    def errback(self, err):
-        self._error_cb(err)
-    
-    @staticmethod
-    def build(capabilities):
-        "Given a list of capability URI's returns encoded <hello> message"
-        spec = {
-            'tag': content.qualify('hello'),
-            'subtree': [{
-                'tag': 'capabilities',
-                'subtree': # this is fun :-)
-                    [{'tag': 'capability', 'text': uri} for uri in capabilities]
-                }]
-            }
-        return content.dtree2xml(spec)
-    
-    @staticmethod
-    def parse(raw):
-        "Returns tuple of ('session-id', ['capability_uri', ...])"
-        sid, capabilities = 0, []
-        root = content.xml2ele(raw)
-        for child in root.getchildren():
-            tag = content.unqualify(child.tag)
-            if tag == 'session-id':
-                sid = child.text
-            elif tag == 'capabilities':
-                for cap in child.getchildren():
-                    if content.unqualify(cap.tag) == 'capability':
-                        capabilities.append(cap.text)
-        return sid, capabilities
 
-'''
-from ncclient.capabilities import CAPABILITIES
-from ncclient.transport.hello import HelloHandler
+from session import SessionListener
 
-print HelloHandler.build(CAPABILITIES)
-'''
\ No newline at end of file
diff --git a/ncclient/transport/session.py b/ncclient/transport/session.py
index 54bf304..e43395f 100644
--- a/ncclient/transport/session.py
+++ b/ncclient/transport/session.py
@@ -15,26 +15,20 @@
 from Queue import Queue
 from threading import Thread, Lock, Event
 
+from ncclient import content
 from ncclient.capabilities import Capabilities
-from ncclient.content import parse_root
-
-from hello import HelloHandler
 
 import logging
 logger = logging.getLogger('ncclient.transport.session')
 
-
 class Session(Thread):
     
-    "TODO: docstring"
-    
     def __init__(self, capabilities):
-        "Subclass constructor should call this"
         Thread.__init__(self)
-        self.setDaemon(True)
-        self._listeners = set() # TODO(?) weakref
+        self.set_daemon(True)
+        self._listeners = set() # 3.0's weakset ideal
         self._lock = Lock()
-        self.setName('session')
+        self.set_name('session')
         self._q = Queue()
         self._client_capabilities = capabilities
         self._server_capabilities = None # yet
@@ -46,7 +40,7 @@ class Session(Thread):
     def _dispatch_message(self, raw):
         "TODO: docstring"
         try:
-            root = parse_root(raw)
+            root = content.parse_root(raw)
         except Exception as e:
             logger.error('error parsing dispatch message: %s' % e)
             return
@@ -77,7 +71,7 @@ class Session(Thread):
         # callbacks
         def ok_cb(id, capabilities):
             self._id = id
-            self._server_capabilities = Capabilities(capabilities)
+            self._server_capabilities = capabilities
             init_event.set()
         def err_cb(err):
             error[0] = err
@@ -97,36 +91,30 @@ class Session(Thread):
                      (self._id, self._server_capabilities))
     
     def add_listener(self, listener):
-        "TODO: docstring"
         logger.debug('installing listener %r' % listener)
+        if not isinstance(listener, SessionListener):
+            raise SessionError("Listener must be a SessionListener type")
         with self._lock:
             self._listeners.add(listener)
     
     def remove_listener(self, listener):
-        "TODO: docstring"
         logger.debug('discarding listener %r' % listener)
         with self._lock:
             self._listeners.discard(listener)
     
     def get_listener_instance(self, cls):
-        '''This is useful when we want to maintain one listener of a particular
-        type per subject i.e. a multiton.
-        '''
         with self._lock:
             for listener in self._listeners:
                 if isinstance(listener, cls):
                     return listener
     
     def connect(self, *args, **kwds):
-        "Subclass implements"
         raise NotImplementedError
 
     def run(self):
-        "Subclass implements"
         raise NotImplementedError
     
     def send(self, message):
-        "TODO: docstring"
         logger.debug('queueing %s' % message)
         self._q.put(message)
     
@@ -146,8 +134,65 @@ class Session(Thread):
     
     @property
     def id(self):
+        "`session-id` if session is initialized, :const:`None` otherwise"
         return self._id
     
     @property
     def can_pipeline(self):
         return True
+
+
+class SessionListener(object):
+    
+    def callback(self, root, raw):
+        raise NotImplementedError
+    
+    def errback(self, ex):
+        raise NotImplementedError
+
+
+class HelloHandler(SessionListener):
+    
+    def __init__(self, init_cb, error_cb):
+        self._init_cb = init_cb
+        self._error_cb = error_cb
+    
+    def callback(self, root, raw):
+        if content.unqualify(root[0]) == 'hello':
+            try:
+                id, capabilities = HelloHandler.parse(raw)
+            except Exception as e:
+                self._error_cb(e)
+            else:
+                self._init_cb(id, capabilities)
+    
+    def errback(self, err):
+        self._error_cb(err)
+    
+    @staticmethod
+    def build(capabilities):
+        "Given a list of capability URI's returns <hello> message XML string"
+        spec = {
+            'tag': content.qualify('hello'),
+            'subtree': [{
+                'tag': 'capabilities',
+                'subtree': # this is fun :-)
+                    [{'tag': 'capability', 'text': uri} for uri in capabilities]
+                }]
+            }
+        return content.dtree2xml(spec)
+    
+    @staticmethod
+    def parse(raw):
+        "Returns tuple of (session-id (str), capabilities (Capabilities)"
+        sid, capabilities = 0, []
+        root = content.xml2ele(raw)
+        for child in root.getchildren():
+            tag = content.unqualify(child.tag)
+            if tag == 'session-id':
+                sid = child.text
+            elif tag == 'capabilities':
+                for cap in child.getchildren():
+                    if content.unqualify(cap.tag) == 'capability':
+                        capabilities.append(cap.text)
+        return sid, Capabilities(capabilities)
diff --git a/ncclient/transport/ssh.py b/ncclient/transport/ssh.py
index 419de67..41122da 100644
--- a/ncclient/transport/ssh.py
+++ b/ncclient/transport/ssh.py
@@ -32,8 +32,10 @@ TICK = 0.1
 
 class SSHSession(Session):
     
-    def __init__(self, *args, **kwds):
-        Session.__init__(self, *args, **kwds)
+    "A NETCONF SSH session, per :rfc: 4742"
+    
+    def __init__(self, capabilities):
+        Session.__init__(self, capabilities)
         self._host_keys = paramiko.HostKeys()
         self._system_host_keys = paramiko.HostKeys()
         self._transport = None
@@ -89,9 +91,6 @@ class SSHSession(Session):
         self._parsing_state = expect
         self._parsing_pos = self._buffer.tell()
     
-    def expect_close(self):
-        self._expecting_close = True
-    
     def load_system_host_keys(self, filename=None):
         if filename is None:
             filename = os.path.expanduser('~/.ssh/known_hosts')
@@ -115,31 +114,30 @@ class SSHSession(Session):
     
     def save_host_keys(self, filename):
         f = open(filename, 'w')
-        for hostname, keys in self._host_keys.iteritems():
+        for host, keys in self._host_keys.iteritems():
             for keytype, key in keys.iteritems():
-                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
+                f.write('%s %s %s\n' % (host, keytype, key.get_base64()))
         f.close()    
     
     def close(self):
-        self.expect_close()
+        self._expecting_close = True
         if self._transport.is_active():
             self._transport.close()
         self._connected = False
     
-    def connect(self, hostname, port=830, timeout=None,
+    def connect(self, host, port=830, timeout=None,
                 unknown_host_cb=None, username=None, password=None,
                 key_filename=None, allow_agent=True, look_for_keys=True):
-        
         assert(username is not None)
         
         for (family, socktype, proto, canonname, sockaddr) in \
-        socket.getaddrinfo(hostname, port):
-            if socktype==socket.SOCK_STREAM:
+        socket.getaddrinfo(host, port):
+            if socktype == socket.SOCK_STREAM:
                 af = family
                 addr = sockaddr
                 break
         else:
-            raise SSHError('No suitable address family for %s' % hostname)
+            raise SSHError('No suitable address family for %s' % host)
         sock = socket.socket(af, socket.SOCK_STREAM)
         sock.settimeout(timeout)
         sock.connect(addr)
@@ -153,13 +151,13 @@ class SSHSession(Session):
         
         # host key verification
         server_key = t.get_remote_server_key()
-        known_host = self._host_keys.check(hostname, server_key) or \
-                        self._system_host_keys.check(hostname, server_key)
+        known_host = self._host_keys.check(host, server_key) or \
+                        self._system_host_keys.check(host, server_key)
         
         if unknown_host_cb is None:
             unknown_host_cb = lambda *args: False
-        if not known_host and not unknown_host_cb(hostname, server_key):
-                raise SSHUnknownHostError(hostname, server_key)
+        if not known_host and not unknown_host_cb(host, server_key):
+                raise SSHUnknownHostError(host, server_key)
         
         if key_filename is None:
             key_filenames = []
@@ -283,10 +281,6 @@ class SSHSession(Session):
     
     @property
     def transport(self):
-        '''Get underlying paramiko transport object; this is provided so methods
-        like set_keepalive can be called on it. See paramiko.Transport
-        documentation for details.
-        '''
         return self._transport
     
     @property
