commit dd225c7a0a0d41d6059bef55570c32d823908248
Author: Shikhar Bhushan <shikhar@schmizz.net>
Date:   Sat May 23 22:09:40 2009 +0000

    * :power-control (liberouter) support
    * manager API nicer, but no docstrings now
    * versioned namespace constants in xml_
    * manager does not raise RPCError anymore, need to move this into RPC somehow
    
    
    
    git-svn-id: http://ncclient.googlecode.com/svn/trunk@154 6dbcf712-26ac-11de-a2f3-1373824ab735

diff --git a/ncclient/capabilities.py b/ncclient/capabilities.py
index bf730ed..473dece 100644
--- a/ncclient/capabilities.py
+++ b/ncclient/capabilities.py
@@ -12,6 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+_capability_map = {
+    'urn:liberouter:params:netconf:capability:power-control:1.0':
+        [':power-control', ':power-control:1.0']
+}
+
 def _abbreviate(uri):
     if uri.startswith('urn:ietf:params:netconf:'):
         splitted = uri.split(':')
@@ -19,10 +24,9 @@ def _abbreviate(uri):
             return [ ':' + splitted[5], ':' + splitted[5] + ':' + splitted[6] ]
         elif ':base:' in uri:
             return [ ':base', ':base' + ':'+ splitted[5] ]
-        else:
-            return []
-    else:
-        return []
+    elif uri in _capability_map:
+        return _capability_map[uri]
+    return []
 
 def schemes(url_uri):
     """Given a URI that has a *scheme* query string (i.e. *:url* capability
@@ -87,8 +91,10 @@ class Capabilities:
         """
         return key in self
 
-    def get_uris(self, shorthand):
-        return [uri for uri, abbrs in self._dict.items() if shorthand in abbrs]
+    def get_uri(self, shorthand):
+        for uri, abbrs in self._dict.items():
+            if shorthand in abbrs:
+                return uri
 
 #: :class:`Capabilities` object representing the capabilities currently supported by NCClient
 CAPABILITIES = Capabilities([
@@ -101,6 +107,7 @@ CAPABILITIES = Capabilities([
     'urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file,https,sftp',
     'urn:ietf:params:netconf:capability:validate:1.0',
     'urn:ietf:params:netconf:capability:xpath:1.0',
+    'urn:liberouter:params:netconf:capability:power-control:1.0'
     #'urn:ietf:params:netconf:capability:notification:1.0', # TODO
     #'urn:ietf:params:netconf:capability:interleave:1.0' # theoretically already supported
 ])
diff --git a/ncclient/manager.py b/ncclient/manager.py
index a43eb4e..755c09e 100644
--- a/ncclient/manager.py
+++ b/ncclient/manager.py
@@ -23,7 +23,10 @@ logger = logging.getLogger('ncclient.manager')
 
 def connect_ssh(*args, **kwds):
     """Connect to NETCONF server over SSH. See :meth:`SSHSession.connect()
-    <ncclient.transport.SSHSession.connect>` for function signature."""
+    <ncclient.transport.SSHSession.connect>` for argument details.
+
+    :rtype: :class:`Manager`
+    """
     session = transport.SSHSession(capabilities.CAPABILITIES)
     session.load_known_hosts()
     session.connect(*args, **kwds)
@@ -32,31 +35,15 @@ def connect_ssh(*args, **kwds):
 #: Same as :meth:`connect_ssh`
 connect = connect_ssh
 
-#: Raise all :class:`~ncclient.operations.rpc.RPCError`
-RAISE_ALL = 0
-#: Only raise when *error-severity* is "error" i.e. no warnings
-RAISE_ERR = 1
-#: Don't raise any
-RAISE_NONE = 2
-
-class Manager:
+class Manager(object):
 
-    """API for NETCONF operations. Currently only supports making synchronous
-    RPC requests.
+    """API for NETCONF operations.
 
     It is also a context manager, so a :class:`Manager` instance can be used
     with the *with* statement. The session is closed when the context ends. """
 
     def __init__(self, session):
         self._session = session
-        self._raise = RAISE_ALL
-
-    def set_rpc_error_action(self, action):
-        """Specify the action to take when an *<rpc-error>* element is encountered.
-
-        :arg action: one of :attr:`RAISE_ALL`, :attr:`RAISE_ERR`, :attr:`RAISE_NONE`
-        """
-        self._raise = action
 
     def __enter__(self):
         return self
@@ -65,53 +52,11 @@ class Manager:
         self.close()
         return False
 
-    def do(self, op, *args, **kwds):
-        op = operations.OPERATIONS[op](self._session)
-        reply = op.request(*args, **kwds)
-        if not reply.ok:
-            if self._raise == RAISE_ALL:
-                raise reply.error
-            elif self._raise == RAISE_ERR:
-                for error in reply.errors:
-                    if error.severity == 'error':
-                        raise error
-        return reply
-
-    #: :see: :meth:`Get.request() <ncclient.operations.Get.request>`
-    get = lambda self, *args, **kwds: self.do('get', *args, **kwds)
-
-    #: :see: :meth:`GetConfig.request() <ncclient.operations.GetConfig.request>`
-    get_config = lambda self, *args, **kwds: self.do('get-config', *args, **kwds)
-
-    #: :see: :meth:`EditConfig.request() <ncclient.operations.EditConfig.request>`
-    edit_config = lambda self, *args, **kwds: self.do('edit-config', *args, **kwds)
-
-    #: :see: :meth:`CopyConfig.request() <ncclient.operations.CopyConfig.request>`
-    copy_config = lambda self, *args, **kwds: self.do('copy-config', *args, **kwds)
-
-    #: :see: :meth:`GetConfig.request() <ncclient.operations.Validate.request>`
-    validate = lambda self, *args, **kwds: self.do('validate', *args, **kwds)
-
-    #: :see: :meth:`Commit.request() <ncclient.operations.Commit.request>`
-    commit = lambda self, *args, **kwds: self.do('commit', *args, **kwds)
-
-    #: :see: :meth:`DiscardChanges.request() <ncclient.operations.DiscardChanges.request>`
-    discard_changes = lambda self, *args, **kwds: self.do('discard-changes', *args, **kwds)
-
-    #: :see: :meth:`DeleteConfig.request() <ncclient.operations.DeleteConfig.request>`
-    delete_config = lambda self, *args, **kwds: self.do('delete-config', *args, **kwds)
-
-    #: :see: :meth:`Lock.request() <ncclient.operations.Lock.request>`
-    lock = lambda self, *args, **kwds: self.do('lock', *args, **kwds)
-
-    #: :see: :meth:`DiscardChanges.request() <ncclient.operations.Unlock.request>`
-    unlock = lambda self, *args, **kwds: self.do('unlock', *args, **kwds)
-
-    #: :see: :meth:`CloseSession.request() <ncclient.operations.CloseSession.request>`
-    close_session = lambda self, *args, **kwds: self.do('close-session', *args, **kwds)
-
-    #: :see: :meth:`KillSession.request() <ncclient.operations.KillSession.request>`
-    kill_session = lambda self, *args, **kwds: self.do('kill-session', *args, **kwds)
+    def __getattr__(self, name):
+        try:
+            return operations.INDEX[name](self.session).request
+        except KeyError:
+            raise AttributeError
 
     def locked(self, target):
         """Returns a context manager for the *with* statement.
diff --git a/ncclient/operations/__init__.py b/ncclient/operations/__init__.py
index 91ee2be..7648891 100644
--- a/ncclient/operations/__init__.py
+++ b/ncclient/operations/__init__.py
@@ -14,25 +14,30 @@
 
 from errors import OperationError, TimeoutExpiredError, MissingCapabilityError
 from rpc import RPC, RPCReply, RPCError
+
+# rfc4741 ops
 from retrieve import Get, GetConfig, GetReply
 from edit import EditConfig, CopyConfig, DeleteConfig, Validate, Commit, DiscardChanges
 from session import CloseSession, KillSession
 from lock import Lock, Unlock, LockContext
-#from subscribe import CreateSubscription
+# others...
+from flowmon import PoweroffMachine, RebootMachine
 
-OPERATIONS = {
+INDEX = {
     'get': Get,
-    'get-config': GetConfig,
-    'edit-config': EditConfig,
-    'copy-config': CopyConfig,
+    'get_config': GetConfig,
+    'edit_config': EditConfig,
+    'copy_config': CopyConfig,
     'validate': Validate,
     'commit': Commit,
-    'discard-changes': DiscardChanges,
-    'delete-config': DeleteConfig,
+    'discard_changes': DiscardChanges,
+    'delete_config': DeleteConfig,
     'lock': Lock,
     'unlock': Unlock,
-    'close-session': CloseSession,
-    'kill-session': KillSession,
+    'close_session': CloseSession,
+    'kill_session': KillSession,
+    'poweroff_machine': PoweroffMachine,
+    'reboot_machine': RebootMachine
 }
 
 __all__ = [
@@ -51,6 +56,8 @@ __all__ = [
     'DeleteConfig',
     'Lock',
     'Unlock',
+    'PoweroffMachine',
+    'RebootMachine',
     'LockContext',
     'CloseSession',
     'KillSession',
diff --git a/ncclient/operations/edit.py b/ncclient/operations/edit.py
index 0937c21..003dce5 100644
--- a/ncclient/operations/edit.py
+++ b/ncclient/operations/edit.py
@@ -27,8 +27,6 @@ logger = logging.getLogger('ncclient.operations.edit')
 
 class EditConfig(RPC):
 
-    # TESTED
-
     "*<edit-config>* RPC"
 
     SPEC = {'tag': 'edit-config', 'subtree': []}
@@ -79,8 +77,6 @@ class EditConfig(RPC):
 
 class DeleteConfig(RPC):
 
-    # TESTED
-
     "*<delete-config>* RPC"
 
     SPEC = {'tag': 'delete-config', 'subtree': []}
@@ -99,8 +95,6 @@ class DeleteConfig(RPC):
 
 class CopyConfig(RPC):
 
-    # TESTED
-
     "*<copy-config>* RPC"
 
     SPEC = {'tag': 'copy-config', 'subtree': []}
@@ -123,8 +117,6 @@ class CopyConfig(RPC):
 
 class Validate(RPC):
 
-    # TESTED
-
     "*<validate>* RPC. Depends on the *:validate* capability."
 
     DEPENDS = [':validate']
@@ -153,8 +145,6 @@ class Validate(RPC):
 
 class Commit(RPC):
 
-    # TESTED
-
     "*<commit>* RPC. Depends on the *:candidate* capability."
 
     DEPENDS = [':candidate']
diff --git a/ncclient/operations/lock.py b/ncclient/operations/lock.py
index c6cb848..6b9174a 100644
--- a/ncclient/operations/lock.py
+++ b/ncclient/operations/lock.py
@@ -16,27 +16,13 @@
 
 from copy import deepcopy
 
-from rpc import RPC, RPCReply, RPCError
+from rpc import RPC
 
-#class LockReply(RPCReply):
-#
-#    ERROR_CLS = LockDeniedError
-#
-#class LockDeniedError(RPCError):
-#
-#    def __new__(cls, err_dict):
-#        if rpcerr['tag'] != 'lock-denied':
-#            return RPCError(err_dict)
-#        else:
-#            return object.__new__(LockDeniedError)
-#
-#    def __init__(self, err_dict):
-#        RPCError.__init__(self, err_dict)
+# TODO:
+# should have some way to parse session-id from a lock-denied error
 
 class Lock(RPC):
 
-    # TESTED
-
     "*<lock>* RPC"
 
     SPEC = {
@@ -63,8 +49,6 @@ class Lock(RPC):
 
 class Unlock(RPC):
 
-    # TESTED
-
     "*<unlock>* RPC"
 
     SPEC = {
@@ -89,8 +73,6 @@ class Unlock(RPC):
 
 class LockContext:
 
-    # TESTED
-
     """
     A context manager for the :class:`Lock` / :class:`Unlock` pair of RPC's.
 
diff --git a/ncclient/operations/retrieve.py b/ncclient/operations/retrieve.py
index 4859c5c..37b6d69 100644
--- a/ncclient/operations/retrieve.py
+++ b/ncclient/operations/retrieve.py
@@ -21,17 +21,13 @@ import util
 
 class GetReply(RPCReply):
 
-    # TESTED
-
     """Adds attributes for the *<data>* element to :class:`RPCReply`, which
     pertains to the :class:`Get` and :class:`GetConfig` operations."""
 
     def _parsing_hook(self, root):
         self._data = None
         if not self._errors:
-            self._data = xml_.find(root, 'data',
-                                      nslist=[xml_.BASE_NS,
-                                              xml_.CISCO_BS])
+            self._data = xml_.find(root, 'data', nslist=xml_.NSLIST)
 
     @property
     def data_ele(self):
@@ -58,8 +54,6 @@ class GetReply(RPCReply):
 
 class Get(RPC):
 
-    # TESTED
-
     "The *<get>* RPC"
 
     SPEC = {'tag': 'get', 'subtree': []}
@@ -80,8 +74,6 @@ class Get(RPC):
 
 class GetConfig(RPC):
 
-    # TESTED
-
     "The *<get-config>* RPC"
 
     SPEC = {'tag': 'get-config', 'subtree': []}
diff --git a/ncclient/operations/rpc.py b/ncclient/operations/rpc.py
index 9142788..68994a4 100644
--- a/ncclient/operations/rpc.py
+++ b/ncclient/operations/rpc.py
@@ -57,11 +57,11 @@ class RPCReply:
             return
         root = self._root = xml_.xml2ele(self._raw) # <rpc-reply> element
         # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
-        ok = xml_.find(root, 'ok', nslist=[xml_.BASE_NS, xml_.CISCO_BS])
+        ok = xml_.find(root, 'ok', nslist=xml_.NSLIST)
         if ok is not None:
             logger.debug('parsed [%s]' % ok.tag)
         else: # create RPCError objects from <rpc-error> elements
-            error = xml_.find(root, 'rpc-error', nslist=[xml_.BASE_NS, xml_.CISCO_BS])
+            error = xml_.find(root, 'rpc-error', nslist=xml_.NSLIST)
             if error is not None:
                 logger.debug('parsed [%s]' % error.tag)
                 for err in root.getiterator(error.tag):
@@ -272,7 +272,7 @@ class RPC(object):
         spec = {
             'tag': 'rpc',
             'attrib': {
-                'xmlns': xml_.BASE_NS,
+                'xmlns': xml_.BASE_NS_1_0,
                 'message-id': self._id
                 },
             'subtree': [ opspec ]
@@ -314,7 +314,7 @@ class RPC(object):
     def request(self, *args, **kwds):
         """Subclasses implement this method. Here, the operation is constructed
         in :ref:`dtree`, and the result of :meth:`_request` returned."""
-        raise NotImplementedError
+        return self._request(self.SPEC)
 
     def _delivery_hook(self):
         """Subclasses can implement this method. Will be called after
diff --git a/ncclient/operations/session.py b/ncclient/operations/session.py
index acca887..7c4b517 100644
--- a/ncclient/operations/session.py
+++ b/ncclient/operations/session.py
@@ -20,8 +20,6 @@ from rpc import RPC
 
 class CloseSession(RPC):
 
-    # TESTED
-
     "*<close-session>* RPC. The connection to NETCONF server is also closed."
 
     SPEC = { 'tag': 'close-session' }
@@ -29,10 +27,6 @@ class CloseSession(RPC):
     def _delivery_hook(self):
         self.session.close()
 
-    def request(self):
-        ":seealso: :ref:`return`"
-        return self._request(CloseSession.SPEC)
-
 
 class KillSession(RPC):
 
diff --git a/ncclient/operations/subscribe.py b/ncclient/operations/subscribe.py
index 4b3d7ff..300cd72 100644
--- a/ncclient/operations/subscribe.py
+++ b/ncclient/operations/subscribe.py
@@ -12,22 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from rpc import RPC
+class Notification:
+    pass
 
-#from ncclient.xml import qualify as _
-#from ncclient.transport import SessionListener
-#
-#NOTIFICATION_NS = 'urn:ietf:params:xml:ns:netconf:notification:1.0'
-#
-## TODO when can actually test it...
-#
-#class CreateSubscription(RPC):
-#    # tested: no
-#
-#    SPEC = {
-#        'tag': _('create-subscription', NOTIFICATION_NS)
-#    }
-#
-#class Notification: pass
-#
-#class NotificationListener(SessionListener): pass
+class CreateSubscription:
+    pass
+
+class NotificationListener:
+    pass
diff --git a/ncclient/transport/session.py b/ncclient/transport/session.py
index 18b516b..49a41c3 100644
--- a/ncclient/transport/session.py
+++ b/ncclient/transport/session.py
@@ -227,7 +227,7 @@ class HelloHandler(SessionListener):
         "Given a list of capability URI's returns <hello> message XML string"
         spec = {
             'tag': 'hello',
-            'attrib': {'xmlns': xml_.BASE_NS},
+            'attrib': {'xmlns': xml_.BASE_NS_1_0},
             'subtree': [{
                 'tag': 'capabilities',
                 'subtree': # this is fun :-)
diff --git a/ncclient/xml_.py b/ncclient/xml_.py
index bf40b64..cd1f8f0 100644
--- a/ncclient/xml_.py
+++ b/ncclient/xml_.py
@@ -30,15 +30,17 @@ class ContentError(NCClientError):
 ### Namespace-related
 
 #: Base NETCONF namespace
-BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
+BASE_NS_1_0 = 'urn:ietf:params:xml:ns:netconf:base:1.0'
 #: ... and this is BASE_NS according to Cisco devices tested
-CISCO_BS = 'urn:ietf:params:netconf:base:1.0'
+CISCO_BS_1_0 = 'urn:ietf:params:netconf:base:1.0'
 #: namespace for Tail-f data model
 TAILF_AAA_1_1 = 'http://tail-f.com/ns/aaa/1.1'
 #: namespace for Tail-f data model
 TAILF_EXECD_1_1 = 'http://tail-f.com/ns/execd/1.1'
 #: namespace for Cisco data model
-CISCO_CPI_10 = 'http://www.cisco.com/cpi_10/schema'
+CISCO_CPI_1_0 = 'http://www.cisco.com/cpi_10/schema'
+#: namespace for Flowmon data model
+FLOWMON_1_0 = 'http://www.liberouter.org/ns/netopeer/flowmon/1.0'
 
 try:
     register_namespace = ET.register_namespace
@@ -48,20 +50,24 @@ except AttributeError:
         # cElementTree uses ElementTree's _namespace_map, so that's ok
         ElementTree._namespace_map[uri] = prefix
 
-register_namespace('netconf', BASE_NS)
-register_namespace('aaa', TAILF_AAA_1_1)
-register_namespace('execd', TAILF_EXECD_1_1)
-register_namespace('cpi', CISCO_CPI_10)
+prefix_map = {
+    BASE_NS_1_0: 'nc',
+    TAILF_AAA_1_1: 'aaa',
+    TAILF_EXECD_1_1: 'execd',
+    CISCO_CPI_1_0: 'cpi',
+    FLOWMON_1_0: 'fm',
+}
 
+for (ns, pre) in prefix_map.items():
+    register_namespace(pre, ns)
 
-qualify = lambda tag, ns=BASE_NS: tag if ns is None else '{%s}%s' % (ns, tag)
+qualify = lambda tag, ns=BASE_NS_1_0: tag if ns is None else '{%s}%s' % (ns, tag)
 
-#: Deprecated
-multiqualify = lambda tag, nslist=(BASE_NS, CISCO_BS): [qualify(tag, ns) for ns in nslist]
+multiqualify = lambda tag, nslist=(BASE_NS_1_0, CISCO_BS_1_0): [qualify(tag, ns) for ns in nslist]
 
 unqualify = lambda tag: tag[tag.rfind('}')+1:]
 
-### XML with Python data structures
+### XML representations
 
 class DictTree:
 
@@ -169,6 +175,9 @@ xml2ele = XML.Element
 
 iselement = ET.iselement
 
+
+NSLIST = [BASE_NS_1_0, CISCO_BS_1_0]
+
 def find(ele, tag, nslist=[]):
     """If *nslist* is empty, same as :meth:`xml.etree.ElementTree.Element.find`.
     If it is not, *tag* is interpreted as an unqualified name and qualified
