uh
authorShikhar Bhushan <shikhar@schmizz.net>
Sun, 10 May 2009 23:47:58 +0000 (23:47 +0000)
committerShikhar Bhushan <shikhar@schmizz.net>
Sun, 10 May 2009 23:47:58 +0000 (23:47 +0000)
git-svn-id: http://ncclient.googlecode.com/svn/trunk@102 6dbcf712-26ac-11de-a2f3-1373824ab735

12 files changed:
ncclient/__init__.py
ncclient/content.py
ncclient/glue.py
ncclient/manager.py
ncclient/operations/edit.py
ncclient/operations/retrieve.py
ncclient/operations/rpc/__init__.py [moved from ncclient/rpc/__init__.py with 85% similarity]
ncclient/operations/rpc/listener.py [moved from ncclient/rpc/listener.py with 100% similarity]
ncclient/operations/rpc/reply.py [moved from ncclient/rpc/reply.py with 100% similarity]
ncclient/operations/rpc/rpc.py [moved from ncclient/rpc/rpc.py with 100% similarity]
ncclient/operations/util.py
ncclient/transport/errors.py [deleted file]

index 25dfde8..8b280b7 100644 (file)
 # limitations under the License.
 
 '''
-NOTES
+TODO
 =====
-
-- operations complete
-- parse into dicts??
-- code freeze and reST doc
+* code freeze and reST doc
 '''
 
 import sys
 
+# actually no reason why shouldn't work on 2.5 but that's... untested -- TODO
 if sys.version_info < (2, 6):
     raise RuntimeError('You need Python 2.6+ for this module.')
 
 __version__ = "0.05"
 
-class NCClientError(Exception):
-    pass
-
-class TransportError(NCClientError):
-    pass
-
-class RPCError(NCClientError):
-    pass
-
-class OperationError(NCClientError):
-    pass
-
-class ContentError(NCClientError):
-    pass
index e6b6823..8d4c8c4 100644 (file)
@@ -16,8 +16,6 @@
 
 from xml.etree import cElementTree as ET
 
-iselement = ET.iselement
-element2string = ET.tostring
 
 ### Namespace-related ###
 
@@ -57,7 +55,7 @@ def namespaced_find(ele, tag, workaround=True):
             if found is not None:
                 break
     return found
-    
+
 
 ### Build XML using Python data structures ###
 
@@ -70,7 +68,7 @@ class XMLConverter:
         "TODO: docstring"
         self._root = XMLConverter.build(spec)
     
-    def to_string(self, encoding='utf-8'):
+    def tostring(self, encoding='utf-8'):
         "TODO: docstring"
         xml = ET.tostring(self._root, encoding)
         # some etree versions don't include xml decl with utf-8
@@ -90,7 +88,7 @@ class XMLConverter:
             return spec
         elif isinstance(spec, basestring):
             return ET.XML(spec)
-        ## assume isinstance(spec, dict)
+        # assume isinstance(spec, dict)
         if 'tag' in spec:
             ele = ET.Element(spec.get('tag'), spec.get('attributes', {}))
             ele.text = spec.get('text', '')
@@ -104,11 +102,12 @@ class XMLConverter:
             return ele
         elif 'comment' in spec:
             return ET.Comment(spec.get('comment'))
+        # TODO elif DOM rep
         else:
             raise ContentError('Invalid tree spec')
     
     @staticmethod
-    def from_string(xml):
+    def fromstring(xml):
         return XMLConverter.parse(ET.fromstring(xml))
     
     @staticmethod
@@ -120,3 +119,18 @@ class XMLConverter:
             'tail': root.tail,
             'subtree': [ XMLConverter.parse(child) for child in root.getchildren() ]
         }
+
+## utility functions
+
+iselement = ET.iselement
+
+def isdom(x): return True # TODO
+
+def root_ensured(rep, tag):
+    if isinstance(rep, basestring):
+        rep = ET.XML(rep)
+    err = False
+    if ((iselement(rep) and (rep.tag not in (tag, qualify(tag))) or (isdom(x)))): 
+        raise ArgumentError("Expected root element [%s] not found" % tag)
+    else:
+        return rep
index 51a4098..125aa7f 100644 (file)
@@ -23,7 +23,8 @@ logger = logging.getLogger('ncclient.glue')
 
 
 def parse_root(raw):
-    '''Parse the top-level element from a string representing an XML document.
+    '''Internal use.
+    Parse the top-level element from XML string.
     
     Returns a `(tag, attributes)` tuple, where `tag` is a string representing
     the qualified name of the root element and `attributes` is an
index d8297e2..87b8113 100644 (file)
@@ -41,7 +41,7 @@ connect = connect_ssh # default session type
 
 class Manager:
     
-    'Facade for the API'
+    "Thin layer of abstraction for the ncclient API."
     
     RAISE_ALL = 0
     RAISE_ERROR = 1
@@ -51,7 +51,7 @@ class Manager:
         self._session = session
         self._raise = rpc_error
 
-    def do(self, op, *args, **kwds):
+    def rpc(self, op, *args, **kwds):
         op = OPERATIONS[op](self._session)
         reply = op.request(*args, **kwds)
         if not reply.ok:
@@ -75,9 +75,10 @@ class Manager:
         return reply.data
     
     def locked(self, target):
-        "For use with 'with'. target is the datastore, e.g. 'candidate'"
+        "Returns a context manager for use withthe 'with' statement.
+       `target` is the datastore to lock, e.g. 'candidate'"
         return operations.LockContext(self._session, target)
-    
+     
     get = lambda self, *args, **kwds: self._get('get')
     
     get_config = lambda self, *args, **kwds: self._get('get-config')
@@ -109,3 +110,18 @@ class Manager:
             pass
         if self._session.connected: # if that didn't work...
             self._session.close()
+
+    @property
+    def session(self, session):
+       return self._session
+    
+    def get_capabilities(self, whose):
+       if whose in ('manager', 'client'):
+           return self._session._client_capabilities
+       elif whose in ('agent', 'server')
+           return self._session._server_capabilities
+
+   
+    @property
+    def capabilities(self):
+        return self._session._client_capabilities
index 4a209c7..c7967eb 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from ncclient.rpc import RPC
 from ncclient.content import iselement
 
+from rpc import RPC
+
 import util
 
-"""
-"""
 
-# NOTES
-# - consider class for helping define <config> for EditConfig??
+"Operations related to configuration editing"
 
 
 class EditConfig(RPC):
     
     # tested: no
-    # combed: no
+    # combed: yes
     
-    SPEC = {
-        'tag': 'edit-config',
-        'subtree': []
-    }
+    SPEC = {'tag': 'edit-config', 'subtree': []}
     
     def request(self, target=None, target_url=None, config=None,
                 default_operation=None, test_option=None, error_option=None):
@@ -41,12 +36,9 @@ class EditConfig(RPC):
         subtree = spec['subtree']
         subtree.append({
             'tag': 'target',
-            'subtree': util.store_or_url(target, target_url)
-            })
-        subtree.append({
-            'tag': 'config',
-            'subtree': config
+            'subtree': util.store_or_url(target, target_url, self._assert)
             })
+        subtree.append(config)
         if default_operation is not None:
             subtree.append({
                 'tag': 'default-operation',
@@ -72,14 +64,14 @@ class DeleteConfig(RPC):
     # tested: no
     # combed: yes
     
-    SPEC = {
-        'tag': 'delete-config',
-        'subtree': [ { 'tag': 'target', 'subtree': None } ]
-    }
+    SPEC = {'tag': 'delete-config', 'subtree': []}
     
     def request(self, target=None, target_url=None):
         spec = DeleteConfig.SPEC.copy()
-        spec['subtree'][0]['subtree'] = util.store_or_url(target, target_url)
+        spec['subtree'].append({
+            'tag': 'target',
+            'subtree': util.store_or_url(target, target_url, self._assert)
+            })
         return self._request(spec)
 
 
@@ -88,20 +80,17 @@ class CopyConfig(RPC):
     # tested: no
     # combed: yes
     
-    SPEC = {
-        'tag': 'copy-config',
-        'subtree': []
-    }
+    SPEC = {'tag': 'copy-config', 'subtree': []}
     
     def request(self, source=None, source_url=None, target=None, target_url=None):
         spec = CopyConfig.SPEC.copy()
         spec['subtree'].append({
-            'tag': 'target',
-            'subtree': util.store_or_url(source, source_url)
+            'tag': 'source',
+            'subtree': util.store_or_url(source, source_url, self._assert)
             })
         spec['subtree'].append({
             'tag': 'target',
-            'subtree': util.store_or_url(target, target_url)
+            'subtree': util.store_or_url(target, target_url, self._assert)
             })
         return self._request(spec)
 
@@ -115,24 +104,18 @@ class Validate(RPC):
     
     DEPENDS = [':validate']
     
-    SPEC = {
-        'tag': 'validate',
-        'subtree': []
-    }
+    SPEC = {'tag': 'validate', 'subtree': []}
     
-    def request(self, source=None, config=None):
-        util.one_of(source, capability)
-        spec = SPEC.copy()
-        if source is not None:
+    def request(self, source=None, source_url=None, config=None):
+        util.one_of(source, source_url, config)
+        spec = Validate.SPEC.copy()
+        if config is None:
             spec['subtree'].append({
                 'tag': 'source',
-                'subtree': {'tag': source}
+                'subtree': util.store_or_url(source, source_url, self._assert)
             })
         else:
-            spec['subtree'].append({
-                'tag': 'config',
-                'subtree': config
-            })
+            spec['subtree'].append(config)
         return self._request(spec)
 
 
@@ -143,7 +126,7 @@ class Commit(RPC):
     
     DEPENDS = [':candidate']
     
-    SPEC = { 'tag': 'commit', 'subtree': [] }
+    SPEC = {'tag': 'commit', 'subtree': []}
     
     def _parse_hook(self):
         pass
@@ -151,6 +134,7 @@ class Commit(RPC):
     def request(self, confirmed=False, timeout=None):
         spec = SPEC.copy()
         if confirmed:
+            self._assert(':confirmed-commit')
             spec['subtree'].append({'tag': 'confirmed'})
             if timeout is not None:
                 spec['subtree'].append({
index ddea328..0463490 100644 (file)
@@ -60,7 +60,7 @@ class Get(RPC):
     def request(self, filter=None):
         spec = Get.SPEC.copy()
         if filter is not None:
-            spec['subtree'].append(util.build_filter(*filter))
+            spec['subtree'].append(content.rootchecked(filter. 'filter', 'type'))
         return self._request(spec)
 
 class GetConfig(RPC):
@@ -87,5 +87,6 @@ class GetConfig(RPC):
             'subtree': util.store_or_url(source, source_url)
             })
         if filter is not None:
-            spec['subtree'].append(util.build_filter(*filter))
+            spec['subtree'].append(content.rootchecked(filter, 'filter', 'type'))
         return self._request(spec)
+
similarity index 85%
rename from ncclient/rpc/__init__.py
rename to ncclient/operations/rpc/__init__.py
index c6fccea..3ae85ae 100644 (file)
 from rpc import RPC
 from reply import RPCReply, RPCError
 
-import ncclient
-
-class ReplyTimeoutError(ncclient.RPCError):
-    pass
-
 __all__ = [
     'RPC',
     'RPCReply',
-    'RPCError',
-    'ReplyTimeoutError'
+    'RPCError'
 ]
index 4168e1d..cbf37b9 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-'Boilerplate'
+'Boilerplate ugliness'
 
 from ncclient import OperationError
+from ncclient.content import qualify as _
+from ncclient.content import ensure_root
 
-from . import MissingCapabilityError
+from ncclient.errors import MissingCapabilityError, ArgumentError
 
 def one_of(*args):
     'Verifies that only one of the arguments is not None'
@@ -29,23 +31,15 @@ def one_of(*args):
                 return
     raise OperationError('Insufficient parameters')
 
-def store_or_url(store, url):
+def store_or_url(store, url, capcheck_func=None):
     one_of(store, url)
     node = {}
     if store is not None:
         node['tag'] = store
     else:
+        if capcheck_func is not None:
+            capcheck_func(':url') # hmm.. schema check? deem overkill for now
         node['tag'] = 'url'
         node['text'] = url
     return node
 
-def build_filter(type, criteria):
-    filter = {
-        'tag': 'filter',
-        'attributes': {'type': type}
-    }
-    if type == 'xpath':
-        filter['attributes']['select'] = criteria
-    else:
-        filter['subtree'] = [criteria]
-    return filter
diff --git a/ncclient/transport/errors.py b/ncclient/transport/errors.py
deleted file mode 100644 (file)
index bc96f2b..0000000
+++ /dev/null
@@ -1,45 +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.
-
-"TODO: docstrings"
-
-from ncclient import NCClientError
-
-class TransportError(NCClientError):
-    pass
-
-class AuthenticationError(TransportError):
-    pass
-
-class SessionCloseError(TransportError):
-    
-    def __init__(self, in_buf, out_buf=None):
-        msg = 'Unexpected session close.'
-        if in_buf:
-            msg += ' IN_BUFFER: {%s}' % in_buf
-        if out_buf:
-            msg += ' OUT_BUFFER: {%s}' % out_buf
-        SSHError.__init__(self, msg)
-
-class SSHError(TransportError):
-    pass
-
-class SSHUnknownHostError(SSHError):
-    
-    def __init__(self, hostname, key):
-        from binascii import hexlify
-        SSHError(self, 'Unknown host key [%s] for [%s]'
-                 % (hexlify(key.get_fingerprint()), hostname))
-        self.hostname = hostname
-        self.key = key