fixes
authorShikhar Bhushan <shikhar@schmizz.net>
Wed, 6 May 2009 22:30:31 +0000 (22:30 +0000)
committerShikhar Bhushan <shikhar@schmizz.net>
Wed, 6 May 2009 22:30:31 +0000 (22:30 +0000)
git-svn-id: http://ncclient.googlecode.com/svn/trunk@98 6dbcf712-26ac-11de-a2f3-1373824ab735

17 files changed:
ncclient/__init__.py
ncclient/content.py
ncclient/manager.py
ncclient/operations/__init__.py
ncclient/operations/edit.py
ncclient/operations/retrieve.py
ncclient/operations/subscribe.py [moved from ncclient/operations/notification.py with 97% similarity]
ncclient/operations/util.py
ncclient/rpc/__init__.py
ncclient/rpc/listener.py
ncclient/rpc/reply.py
ncclient/rpc/rpc.py
ncclient/transport/__init__.py
ncclient/transport/errors.py
ncclient/transport/hello.py
ncclient/transport/session.py
ncclient/transport/ssh.py

index d8e0543..80d2732 100644 (file)
@@ -38,7 +38,7 @@ class NCClientError(Exception):
 class TransportError(NCClientError):
     pass
 
-class OperationError(NCClientError):
+class RPCError(NCClientError):
     pass
 
 class OperationError(NCClientError):
index 8bcb75c..8ce888b 100644 (file)
@@ -16,6 +16,7 @@
 
 from xml.etree import cElementTree as ET
 
+iselement = ET.iselement
 
 ### Namespace-related ###
 
@@ -58,11 +59,8 @@ class XMLConverter:
         xml = ET.tostring(self._root, encoding)
         # some etree versions don't include xml decl with utf-8
         # this is a problem with some devices
-        if encoding == 'utf-8':
-            return ((u'<?xml version="1.0" encoding="utf-8"?>'
-                     % encoding).encode(encoding) + xml)
-        else:
-            return xml
+        return (xml if xml.startswith('<?xml')
+                else '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml))
     
     @property
     def tree(self):
@@ -76,14 +74,15 @@ class XMLConverter:
             return spec
         elif isinstance(spec, basestring):
             return ET.XML(spec)
-        # assume isinstance(spec, dict)
-        elif 'tag' in spec:
+        ## assume isinstance(spec, dict)
+        if 'tag' in spec:
             ele = ET.Element(spec.get('tag'), spec.get('attributes', {}))
-            ele.text = str(spec.get('text', ''))
+            ele.text = spec.get('text', '')
             children = spec.get('children', [])
-            if isinstance(children, dict): children = [children]
+            if isinstance(children, dict):
+                children = [children]
             for child in children:
-                ET.SubElement(ele, TreeBuilder.build(child))
+                ele.append(XMLConverter.build(child))
             return ele
         elif 'comment' in spec:
             return ET.Comment(spec.get('comment'))
index 8dbc141..4cc6018 100644 (file)
@@ -16,10 +16,6 @@ import capabilities
 import operations
 import transport
 
-SESSION_TYPES = {
-    'ssh': transport.SSHSession
-}
-
 OPERATIONS = {
     'get': operations.Get,
     'get-config': operations.GetConfig,
@@ -35,36 +31,66 @@ OPERATIONS = {
     'kill-session': operations.KillSession,
 }
 
-class Manager(type):
+def connect_ssh(*args, **kwds):
+    session = transport.SSHSession(capabilities.CAPABILITIES)
+    session.load_system_host_keys()
+    session.connect(*args, **kwds)
+    return Manager(session)
+
+connect = connect_ssh # default
+
+class Manager:
     
     'Facade for the API'
     
-    def connect(self, session_type, *args, **kwds):
-        self._session = SESSION_TYPES[session_type](capabilities.CAPABILITIES)
-        self._session.connect(*args, **kwds)
-    
-    def __getattr__(self, name):
-        name = name.replace('_', '-')
-        if name in OPERATIONS:
-            return OPERATIONS[name](self._session).request
-        else:
-            raise AttributeError
-    
-    def get(self, *args, **kwds):
-        g = operations.Get(self._session)
-        reply = g.request(*args, **kwds)
-        if reply.errors:
-            raise RPCError(reply.errors)
-        else:
-            return reply.data
+    def __init__(self, session):
+        self._session = session
     
-    def get_config(self, *args, **kwds):
-        gc = operations.GetConfig(self._session)
-        reply = gc.request(*args, **kwds)
-        if reply.errors:
-            raise RPCError(reply.errors)
+    def _get(self, type, *args, **kwds):
+        op = OPERATIONS[type](self._session)
+        reply = op.request(*args, **kwds)
+        if not reply.ok:
+            raise reply.errors[0]
         else:
             return reply.data
 
+    def request(op, *args, **kwds):
+        op = OPERATIONS[op](self._session)
+        reply = op.request(*args, **kwds)
+        if not reply.ok:
+            raise reply.errors[0]
+        return reply
+
     def locked(self, target='running'):
         return LockContext(self._session, target)
+    
+    get = lambda self, *args, **kwds: self._get('get')
+    
+    get_config = lambda self, *args, **kwds: self._get('get-config')
+    
+    edit_config = lambda self, *args, **kwds: self.request('edit-config', *args, **kwds)
+    
+    copy_config = lambda self, *args, **kwds: self.request('copy-config', *args, **kwds)
+    
+    validate = lambda self, *args, **kwds: self.request('validate', *args, **kwds)
+    
+    commit = lambda self, *args, **kwds: self.request('commit', *args, **kwds)
+    
+    discard_changes = lambda self, *args, **kwds: self.request('discard-changes', *args, **kwds)
+    
+    delete_config = lambda self, *args, **kwds: self.request('delete-config', *args, **kwds)
+    
+    lock = lambda self, *args, **kwds: self.request('lock', *args, **kwds)
+    
+    unlock = lambda self, *args, **kwds: self.request('unlock', *args, **kwds)
+    
+    close_session = lambda self, *args, **kwds: self.request('close-session', *args, **kwds)
+    
+    kill_session = lambda self, *args, **kwds: self.request('kill-session', *args, **kwds)
+    
+    def close(self):
+        try:
+            self.close_session()
+        except:
+            self._session.expect_close()
+            self._session.close()
index 65cde12..3c3e60d 100644 (file)
 
 'NETCONF protocol operations'
 
-import logging
-logger = logging.getLogger('ncclient.operations')
+from ncclient import NCClientError
+
+class OperationError(NCClientError):
+    pass
+
+class MissingCapabilityError(OperationError):
+    pass
 
 from retrieve import Get, GetConfig
 from edit import EditConfig, CopyConfig, DeleteConfig, Validate, Commit, DiscardChanges
@@ -23,7 +28,10 @@ from session import CloseSession, KillSession
 from lock import Lock, Unlock
 from subscribe import CreateSubscription
 
+
 __all__ = [
+    'OperationError',
+    'MissingCapabilityError',
     'Get',
     'GetConfig',
     'EditConfig',
@@ -37,5 +45,4 @@ __all__ = [
     'CloseSession',
     'KillSession',
     'CreateSubscription',
-    # hmm
 ]
index 1d66afb..00dc3a5 100644 (file)
@@ -12,8 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from ncclient.capabilities import URI
 from ncclient.rpc import RPC
+from ncclient.content import iselement
 
 import util
 
@@ -21,16 +21,24 @@ class EditConfig(RPC):
     
     SPEC = {
         'tag': 'edit-config',
-        'children': [
-            { 'target': None }
-        ]
+        'children': [ ]
     }
     
-    def request(self):
-        pass
+    def request(self, target=None, target_url=None, config=None,
+                default_operation=None, test_option=None, error_option=None):
+        util.one_of(target, target_url)
+        spec = EditConfig.SPEC.copy()
+        params = spec['children']
+        params.append({'tag': 'target', 'children': util.store_or_url(target, target_url)})
+        params.append({'tag': 'config', 'children': config})
+        if default_operation is not None:
+            params.append({'tag': 'default-operation', 'text': default_operation})
+        if test_option is not None:
+            params.append({'tag': 'test-option', 'text': test_option})
+        if error_option is not None:
+            params.append({'tag': 'test-option', 'text': test_option})
 
-
-class DeleteConfig(RPC): # x
+class DeleteConfig(RPC):
     
     SPEC = {
         'tag': 'delete-config',
@@ -43,7 +51,7 @@ class DeleteConfig(RPC): # x
         return self._request(spec)
 
 
-class CopyConfig(RPC): # x
+class CopyConfig(RPC):
     
     SPEC = {
         'tag': 'copy-config',
@@ -60,7 +68,9 @@ class CopyConfig(RPC): # x
         return self._request(spec)
 
 
-class Validate(RPC): # xxxxx
+class Validate(RPC):
+    
+    'config attr shd not include <config> root'
     
     DEPENDS = [':validate']
     
@@ -70,42 +80,18 @@ class Validate(RPC): # xxxxx
     }
     
     def request(self, source=None, config=None):
-        #self.either_or(source, config)
-        #
-        #if source is None and config is None:
-        #    raise OperationError('Insufficient parameters')
-        #if source is not None and config is not None:
-        #    raise OperationError('Too many parameters')
-        #spec = Validate.SPEC.copy()
-        #
         util.one_of(source, capability)
+        spec = SPEC.copy()
         if source is not None:
             spec['children'].append({
-                'tag': 'source',
-                'children': {'tag': source}
+                'tag': 'source', 'children': {'tag': source}
                 })
-        #
-        #else:
-        #    if isinstance(config, dict):
-        #        if config['tag'] != 'config':
-        #            child['tag'] = 'config'
-        #            child['children'] = config
-        #        else:
-        #            child = config
-        #    elif isinstance(config, Element):
-        #        pass
-        #    else:
-        #        from xml.etree import cElementTree as ET
-        #        ele = ET.XML(unicode(config))
-        #        if __(ele.tag) != 'config':
-        #            pass
-        #        else:
-        #            pass
-        #    spec['children'].append(child)
-        #
+        else:
+            spec['children'].append({'tag': 'config', 'children': config})
         return self._request(spec)
 
-class Commit(RPC): # x
+
+class Commit(RPC):
     
     DEPENDS = [':candidate']
     
@@ -128,7 +114,7 @@ class Commit(RPC): # x
         return self._request(Commit.SPEC)
 
 
-class DiscardChanges(RPC): # x
+class DiscardChanges(RPC):
     
     DEPENDS = [':candidate']
     
index 8c2e62a..e747d1f 100644 (file)
@@ -25,7 +25,12 @@ def build_filter(spec, type, criteria):
         filter['attributes']['select'] = criteria
     return filter
 
-class Get(RPC): # xx
+class GetReply(RPCReply):
+    
+    def parse(self):
+        RPCReply.parse(self)
+
+class Get(RPC):
     
     SPEC = {
         'tag': 'get',
@@ -54,7 +59,7 @@ class GetConfig(RPC): # xx
         'children': [ { 'tag': 'source', 'children': {'tag': None } } ]
     }
     
-    REPLY_CLS = GetConfigReply
+    REPLY_CLS = GetReply
     
     def request(self, source=None, source_url=None, filter=None):
         self._one_of(source, source_url)
@@ -70,8 +75,3 @@ class GetConfig(RPC): # xx
             #    self._assert(':xpath')
             spec['children'].append(build_filter(*filter))
         return self._request(spec)
-
-class GetReply(RPCReply):
-    
-    def parse(self):
-        RPCReply.parse(self)
similarity index 97%
rename from ncclient/operations/notification.py
rename to ncclient/operations/subscribe.py
index c4c82fb..b98d870 100644 (file)
@@ -14,7 +14,7 @@
 
 # TODO when can actually test it...
 
-from rpc import RPC
+from ncclient.rpc import RPC
 
 from ncclient.glue import Listener
 from ncclient.content import qualify as _
index abe86a3..653640c 100644 (file)
@@ -1,11 +1,10 @@
 #!/usr/bin/env python
 
-'boilerplate'
+'Boilerplate'
 
 from ncclient import OperationError
 
-class MissingCapabilityError(OperationError):
-    pass
+from . import MissingCapabilityError
 
 def one_of(self, *args):
     for i, arg in enumerate(args):
@@ -20,7 +19,7 @@ def one_of(self, *args):
 
 def assert_capability(key, capabilities):
     if key not in capabilities:
-        raise MissingCapabilityError
+        raise MissingCapabilityError('[%s] capability is required for this operation' % key)
 
 
 def store_or_url(store, url):
index b0de5ad..c6fccea 100644 (file)
 # limitations under the License.
 
 from rpc import RPC
-from reply import RPCReply
+from reply import RPCReply, RPCError
 
-from ncclient import RPCError
+import ncclient
 
-class ReplyTimeoutError(RPCError): pass
+class ReplyTimeoutError(ncclient.RPCError):
+    pass
 
 __all__ = [
     'RPC',
     'RPCReply',
+    'RPCError',
     'ReplyTimeoutError'
 ]
index 965bf85..c25a1a8 100644 (file)
@@ -15,6 +15,9 @@
 from threading import Lock
 from weakref import WeakValueDictionary
 
+from ncclient.glue import Listener
+from ncclient.content import unqualify as __
+
 import logging
 logger = logging.getLogger('ncclient.rpc.listener')
 
index 47b9725..1d56dc6 100644 (file)
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import ncclient
+
 from xml.etree import cElementTree as ET
 
 from ncclient.content import multiqualify as _
@@ -32,7 +34,9 @@ class RPCReply:
         return self._raw
     
     def parse(self):
-        if self._parsed: return
+        if self._parsed:
+            return
+        
         root = self._root = ET.fromstring(self._raw) # <rpc-reply> element
         
         if __(root.tag) != 'rpc-reply':
@@ -60,10 +64,6 @@ class RPCReply:
                 if self._errors:
                     break
         
-        if self.ok:
-            # TODO: store children in some way...
-            pass
-        
         self._parsed = True
     
     @property
@@ -86,14 +86,14 @@ class RPCReply:
         return self._errors
 
 
-class RPCError(Exception): # raise it if you like
+class RPCError(ncclient.RPCError): # raise it if you like
     
     def __init__(self, err_dict):
         self._dict = err_dict
         if self.message is not None:
-            Exception.__init__(self, self.message)
+            ncclient.RPCError.__init__(self, self.message)
         else:
-            Exception.__init__(self)
+            ncclient.RPCError.__init__(self)
     
     @property
     def raw(self):
index 2c5c202..83e31f7 100644 (file)
@@ -16,7 +16,7 @@ from threading import Event, Lock
 from uuid import uuid1
 from weakref import WeakValueDictionary
 
-from ncclient.content import TreeBuilder
+from ncclient.content import XMLConverter
 from ncclient.content import qualify as _
 from ncclient.content import unqualify as __
 from ncclient.glue import Listener
@@ -49,14 +49,14 @@ class RPC(object):
         self._reply = None
         self._reply_event = Event()
     
-    def _build(opspec, encoding='utf-8'):
+    def _build(self, opspec, encoding='utf-8'):
         "TODO: docstring"
         spec = {
             'tag': _('rpc'),
             'attributes': {'message-id': self._id},
             'children': opspec
             }
-        return TreeBuilder(spec).to_string(encoding)
+        return XMLConverter(spec).to_string(encoding)
     
     def _request(self, op):
         req = self._build(op)
index 1e3fa8c..024b8fd 100644 (file)
 
 from ssh import SSHSession
 
-__all__ = ['SSHSession']
\ No newline at end of file
+__all__ = [
+    'TransportError',
+    'AuthenticationError',
+    'SessionCloseError',
+    'SSHError',
+    'SSHUnknownHostError',
+    'SSHSession'
+]
\ No newline at end of file
index 70e91bd..bc96f2b 100644 (file)
 
 "TODO: docstrings"
 
-from ncclient import TransportError
+from ncclient import NCClientError
+
+class TransportError(NCClientError):
+    pass
 
 class AuthenticationError(TransportError):
     pass
index 576be48..7bcde2c 100644 (file)
@@ -17,7 +17,7 @@
 from xml.etree import cElementTree as ET
 
 from ncclient.glue import Listener
-from ncclient.content import TreeBuilder, BASE_NS
+from ncclient.content import XMLConverter, BASE_NS
 from ncclient.content import qualify as _
 from ncclient.content import unqualify as __
 
@@ -43,7 +43,7 @@ class HelloHandler(Listener):
         self._error_cb(err)
     
     @staticmethod
-    def build(capabilities, encoding='utf-8'):
+    def build(capabilities):
         "Given a list of capability URI's returns encoded <hello> message"
         spec = {
             'tag': _('hello', BASE_NS),
@@ -53,7 +53,7 @@ class HelloHandler(Listener):
                     [{ 'tag': 'capability', 'text': uri} for uri in capabilities]
                 }]
             }
-        return TreeBuilder(spec).to_string(encoding)
+        return XMLConverter(spec).to_string()
     
     @staticmethod
     def parse(raw):
index 56c57bb..c8de673 100644 (file)
@@ -15,7 +15,7 @@
 from threading import Event
 from Queue import Queue
 
-from ncclient.capabilities import Capabilities, CAPABILITIES
+from ncclient.capabilities import Capabilities
 from ncclient.glue import Subject
 
 from hello import HelloHandler
@@ -27,12 +27,12 @@ class Session(Subject):
     
     "TODO: docstring"
     
-    def __init__(self):
+    def __init__(self, capabilities):
         "Subclass constructor should call this"
         Subject.__init__(self)
         self.setName('session')
         self._q = Queue()
-        self._client_capabilities = CAPABILITIES
+        self._client_capabilities = capabilities
         self._server_capabilities = None # yet
         self._id = None # session-id
         self._connected = False # to be set/cleared by subclass implementation
index eef0421..ee8f95e 100644 (file)
@@ -32,8 +32,8 @@ TICK = 0.1
 
 class SSHSession(Session):
     
-    def __init__(self):
-        Session.__init__(self)
+    def __init__(self, *args, **kwds):
+        Session.__init__(self, *args, **kwds)
         self._host_keys = paramiko.HostKeys()
         self._system_host_keys = paramiko.HostKeys()
         self._transport = None