python3 does not leak variable name outside an except as
[plcapi.git] / PLC / Method.py
index 2b062d1..53d1530 100644 (file)
@@ -4,26 +4,20 @@
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
-# $Id: Method.py,v 1.18 2006/11/08 22:11:26 mlhuang Exp $
-#
-
-import xmlrpclib
-from types import *
+#import xmlrpc.client
 import textwrap
 import textwrap
-import os
 import time
 import pprint
 
 import time
 import pprint
 
-from types import StringTypes
-
 from PLC.Faults import *
 from PLC.Parameter import Parameter, Mixed, python_type, xmlrpc_type
 from PLC.Auth import Auth
 from PLC.Faults import *
 from PLC.Parameter import Parameter, Mixed, python_type, xmlrpc_type
 from PLC.Auth import Auth
-from PLC.Debug import profile, log
+#from PLC.Debug import profile
 from PLC.Events import Event, Events
 from PLC.Nodes import Node, Nodes
 from PLC.Persons import Person, Persons
 
 from PLC.Events import Event, Events
 from PLC.Nodes import Node, Nodes
 from PLC.Persons import Person, Persons
 
+# we inherit object because we use new-style classes for legacy methods
 class Method:
     """
     Base class for all PLCAPI functions. At a minimum, all PLCAPI
 class Method:
     """
     Base class for all PLCAPI functions. At a minimum, all PLCAPI
@@ -61,17 +55,22 @@ class Method:
 
         return True
 
 
         return True
 
-    def __init__(self, api):
+    def __init__(self, api, caller=None):
         self.name = self.__class__.__name__
         self.api = api
 
         self.name = self.__class__.__name__
         self.api = api
 
-        # Auth may set this to a Person instance (if an anonymous
-        # method, will remain None).
-        self.caller = None
+        if caller:
+            # let a method call another one by propagating its caller
+            self.caller = caller
+        else:
+            # Auth may set this to a Person instance (if an anonymous
+            # method, will remain None).
+            self.caller = None
+
 
         # API may set this to a (addr, port) tuple if known
         self.source = None
 
         # API may set this to a (addr, port) tuple if known
         self.source = None
-       
+
     def __call__(self, *args, **kwds):
         """
         Main entry point for all PLCAPI functions. Type checks
     def __call__(self, *args, **kwds):
         """
         Main entry point for all PLCAPI functions. Type checks
@@ -79,57 +78,91 @@ class Method:
         """
 
         try:
         """
 
         try:
-           start = time.time()
-            (min_args, max_args, defaults) = self.args()
-                               
-           # Check that the right number of arguments were passed in
-            if len(args) < len(min_args) or len(args) > len(max_args):
-                raise PLCInvalidArgumentCount(len(args), len(min_args), len(max_args))
-
-            for name, value, expected in zip(max_args, args, self.accepts):
-                self.type_check(name, value, expected, args)
-       
-           result = self.call(*args, **kwds)
-           runtime = time.time() - start
+            start = time.time()
+
+            # legacy code cannot be type-checked, due to the way Method.args() works
+            # as of 5.0-rc16 we don't use skip_type_check anymore
+            if not hasattr(self, "skip_type_check"):
+                (min_args, max_args, defaults) = self.args()
+
+                # Check that the right number of arguments were passed in
+                if len(args) < len(min_args) or len(args) > len(max_args):
+                    raise PLCInvalidArgumentCount(len(args), len(min_args), len(max_args))
+
+                for name, value, expected in zip(max_args, args, self.accepts):
+                    self.type_check(name, value, expected, args)
+
+            result = self.call(*args, **kwds)
+            runtime = time.time() - start
+
+            if self.api.config.PLC_API_DEBUG or hasattr(self, 'message'):
+                self.log(None, runtime, *args)
+
+            return result
+
+        except PLCFault as fault:
+
+            caller = ""
+            if isinstance(self.caller, Person):
+                caller = 'person_id %s'  % self.caller['person_id']
+            elif isinstance(self.caller, Node):
+                caller = 'node_id %s'  % self.caller['node_id']
+
+            # Prepend caller and method name to expected faults
+            fault.faultString = caller + ": " +  self.name + ": " + fault.faultString
+            runtime = time.time() - start
 
             if self.api.config.PLC_API_DEBUG:
 
             if self.api.config.PLC_API_DEBUG:
-               self.log(0, runtime, *args)
-               
-           return result
-
-        except PLCFault, fault:
-            # Prepend method name to expected faults
-            fault.faultString = self.name + ": " + fault.faultString
-           runtime = time.time() - start
-           self.log(fault.faultCode, runtime, *args)
+                self.log(fault, runtime, *args)
+
             raise fault
 
             raise fault
 
-    def log(self, fault_code, runtime, *args):
+    def log(self, fault, runtime, *args):
+        """
+        Log the transaction
         """
         """
-        Log the transaction 
-        """    
 
 
-       # Do not log system or Get calls
-        if self.name.startswith('system') or self.name.startswith('Get'):
+        # Do not log system or Get calls
+        #if self.name.startswith('system') or self.name.startswith('Get'):
+        #    return False
+        # Do not log ReportRunlevel
+        if self.name.startswith('system'):
+            return False
+        if self.name.startswith('ReportRunlevel'):
             return False
 
         # Create a new event
         event = Event(self.api)
             return False
 
         # Create a new event
         event = Event(self.api)
-        event['fault_code'] = fault_code
+        event['fault_code'] = 0
+        if fault:
+            event['fault_code'] = fault.faultCode
         event['runtime'] = runtime
 
         # Redact passwords and sessions
         event['runtime'] = runtime
 
         # Redact passwords and sessions
-        if args and isinstance(args[0], dict):
-            for password in 'AuthString', 'session':
-                if args[0].has_key(password):
-                    auth = args[0].copy()
-                    auth[password] = "Removed by API"
-                    args = (auth,) + args[1:]
+        newargs = args
+        if args:
+            newargs = []
+            for arg in args:
+                if not isinstance(arg, dict):
+                    newargs.append(arg)
+                    continue
+                # what type of auth this is
+                if 'AuthMethod' in arg:
+                    auth_methods = ['session', 'password', 'capability',
+                                    'gpg', 'hmac', 'anonymous']
+                    auth_method = arg['AuthMethod']
+                    if auth_method in auth_methods:
+                        event['auth_type'] = auth_method
+                for password in 'AuthString', 'session', 'password':
+                    if password in arg:
+                        arg = arg.copy()
+                        arg[password] = "Removed by API"
+                newargs.append(arg)
 
         # Log call representation
         # XXX Truncate to avoid DoS
 
         # Log call representation
         # XXX Truncate to avoid DoS
-        event['call'] = self.name + pprint.saferepr(args)
-       event['call_name'] = self.name
+        event['call'] = self.name + pprint.saferepr(newargs)
+        event['call_name'] = self.name
 
         # Both users and nodes can call some methods
         if isinstance(self.caller, Person):
 
         # Both users and nodes can call some methods
         if isinstance(self.caller, Person):
@@ -137,17 +170,24 @@ class Method:
         elif isinstance(self.caller, Node):
             event['node_id'] = self.caller['node_id']
 
         elif isinstance(self.caller, Node):
             event['node_id'] = self.caller['node_id']
 
-        event.sync(commit = False)
+        event.sync(commit=False)
 
 
-        # XXX object_ids is currently defined as a class variable
-        if hasattr(self, 'object_ids'):
-            for object_id in self.object_ids:
-                event.add_object(object_id, commit = False)
+        if hasattr(self, 'event_objects') and isinstance(self.event_objects, dict):
+            for key in self.event_objects.keys():
+                for object_id in self.event_objects[key]:
+                    event.add_object(key, object_id, commit=False)
+
+
+        # Set the message for this event
+        if fault:
+            event['message'] = fault.faultString
+        elif hasattr(self, 'message'):
+            event['message'] = self.message
 
         # Commit
 
         # Commit
-        event.sync(commit = True)
+        event.sync()
 
 
-    def help(self, indent = "  "):
+    def help(self, indent="  "):
         """
         Text documentation for the method.
         """
         """
         Text documentation for the method.
         """
@@ -187,9 +227,10 @@ class Method:
 
             # Print parameter documentation right below type
             if isinstance(param, Parameter):
 
             # Print parameter documentation right below type
             if isinstance(param, Parameter):
-                wrapper = textwrap.TextWrapper(width = 70,
-                                               initial_indent = " " * param_offset,
-                                               subsequent_indent = " " * param_offset)
+                wrapper = textwrap.TextWrapper(
+                    width=70,
+                    initial_indent=" " * param_offset,
+                    subsequent_indent = " " * param_offset)
                 text += "\n".join(wrapper.wrap(param.doc)) + "\n"
                 param = param.type
 
                 text += "\n".join(wrapper.wrap(param.doc)) + "\n"
                 param = param.type
 
@@ -197,7 +238,7 @@ class Method:
 
             # Indent struct fields and mixed types
             if isinstance(param, dict):
 
             # Indent struct fields and mixed types
             if isinstance(param, dict):
-                for name, subparam in param.iteritems():
+                for name, subparam in param.items():
                     text += param_text(name, subparam, indent + step, step)
             elif isinstance(param, Mixed):
                 for subparam in param:
                     text += param_text(name, subparam, indent + step, step)
             elif isinstance(param, Mixed):
                 for subparam in param:
@@ -230,14 +271,14 @@ class Method:
         """
 
         # Inspect call. Remove self from the argument list.
         """
 
         # Inspect call. Remove self from the argument list.
-        max_args = self.call.func_code.co_varnames[1:self.call.func_code.co_argcount]
-        defaults = self.call.func_defaults
+        max_args = self.call.__code__.co_varnames[1:self.call.__code__.co_argcount]
+        defaults = self.call.__defaults__
         if defaults is None:
             defaults = ()
 
         min_args = max_args[0:len(max_args) - len(defaults)]
         defaults = tuple([None for arg in min_args]) + defaults
         if defaults is None:
             defaults = ()
 
         min_args = max_args[0:len(max_args) - len(defaults)]
         defaults = tuple([None for arg in min_args]) + defaults
-        
+
         return (min_args, max_args, defaults)
 
     def type_check(self, name, value, expected, args):
         return (min_args, max_args, defaults)
 
     def type_check(self, name, value, expected, args):
@@ -246,7 +287,7 @@ class Method:
         which may be a Python type, a typed value, a Parameter, a
         Mixed type, or a list or dictionary of possibly mixed types,
         values, Parameters, or Mixed types.
         which may be a Python type, a typed value, a Parameter, a
         Mixed type, or a list or dictionary of possibly mixed types,
         values, Parameters, or Mixed types.
-        
+
         Extraneous members of lists must be of the same type as the
         last specified type. For example, if the expected argument
         type is [int, bool], then [1, False] and [14, True, False,
         Extraneous members of lists must be of the same type as the
         last specified type. For example, if the expected argument
         type is [int, bool], then [1, False] and [14, True, False,
@@ -257,14 +298,16 @@ class Method:
         """
 
         # If any of a number of types is acceptable
         """
 
         # If any of a number of types is acceptable
-        if isinstance(expected, Mixed):
+        # try them one by one, if one succeeds then it's fine
+        if expected and isinstance(expected, Mixed):
+            to_raise = None
             for item in expected:
                 try:
                     self.type_check(name, value, item, args)
                     return
             for item in expected:
                 try:
                     self.type_check(name, value, item, args)
                     return
-                except PLCInvalidArgument, fault:
-                    pass
-            raise fault
+                except PLCInvalidArgument as fault:
+                    to_raise = fault
+            raise to_raise
 
         # If an authentication structure is expected, save it and
         # authenticate after basic type checking is done.
 
         # If an authentication structure is expected, save it and
         # authenticate after basic type checking is done.
@@ -292,12 +335,12 @@ class Method:
 
         # Strings are a special case. Accept either unicode or str
         # types if a string is expected.
 
         # Strings are a special case. Accept either unicode or str
         # types if a string is expected.
-        if expected_type in StringTypes and isinstance(value, StringTypes):
+        if expected_type is str and isinstance(value, str):
             pass
 
         # Integers and long integers are also special types. Accept
         # either int or long types if an int or long is expected.
             pass
 
         # Integers and long integers are also special types. Accept
         # either int or long types if an int or long is expected.
-        elif expected_type in (IntType, LongType) and isinstance(value, (IntType, LongType)):
+        elif expected_type is int and isinstance(value, int):
             pass
 
         elif not isinstance(value, expected_type):
             pass
 
         elif not isinstance(value, expected_type):
@@ -307,23 +350,23 @@ class Method:
                                      name)
 
         # If a minimum or maximum (length, value) has been specified
                                      name)
 
         # If a minimum or maximum (length, value) has been specified
-        if expected_type in StringTypes:
+        if expected_type is str:
             if min is not None and \
                len(value.encode(self.api.encoding)) < min:
             if min is not None and \
                len(value.encode(self.api.encoding)) < min:
-                raise PLCInvalidArgument, "%s must be at least %d bytes long" % (name, min)
+                raise PLCInvalidArgument("%s must be at least %d bytes long" % (name, min))
             if max is not None and \
                len(value.encode(self.api.encoding)) > max:
             if max is not None and \
                len(value.encode(self.api.encoding)) > max:
-                raise PLCInvalidArgument, "%s must be at most %d bytes long" % (name, max)
+                raise PLCInvalidArgument("%s must be at most %d bytes long" % (name, max))
         elif expected_type in (list, tuple, set):
             if min is not None and len(value) < min:
         elif expected_type in (list, tuple, set):
             if min is not None and len(value) < min:
-                raise PLCInvalidArgument, "%s must contain at least %d items" % (name, min)
+                raise PLCInvalidArgument("%s must contain at least %d items" % (name, min))
             if max is not None and len(value) > max:
             if max is not None and len(value) > max:
-                raise PLCInvalidArgument, "%s must contain at most %d items" % (name, max)
+                raise PLCInvalidArgument("%s must contain at most %d items" % (name, max))
         else:
             if min is not None and value < min:
         else:
             if min is not None and value < min:
-                raise PLCInvalidArgument, "%s must be > %s" % (name, str(min))
+                raise PLCInvalidArgument("%s must be > %s" % (name, str(min)))
             if max is not None and value > max:
             if max is not None and value > max:
-                raise PLCInvalidArgument, "%s must be < %s" % (name, str(max))
+                raise PLCInvalidArgument("%s must be < %s" % (name, str(max)))
 
         # If a list with particular types of items is expected
         if isinstance(expected, (list, tuple, set)):
 
         # If a list with particular types of items is expected
         if isinstance(expected, (list, tuple, set)):
@@ -337,13 +380,13 @@ class Method:
         # If a struct with particular (or required) types of items is
         # expected.
         elif isinstance(expected, dict):
         # If a struct with particular (or required) types of items is
         # expected.
         elif isinstance(expected, dict):
-            for key in value.keys():
+            for key in list(value.keys()):
                 if key in expected:
                     self.type_check(name + "['%s']" % key, value[key], expected[key], args)
                 if key in expected:
                     self.type_check(name + "['%s']" % key, value[key], expected[key], args)
-            for key, subparam in expected.iteritems():
+            for key, subparam in expected.items():
                 if isinstance(subparam, Parameter) and \
                    subparam.optional is not None and \
                 if isinstance(subparam, Parameter) and \
                    subparam.optional is not None and \
-                   not subparam.optional and key not in value.keys():
+                   not subparam.optional and key not in list(value.keys()):
                     raise PLCInvalidArgument("'%s' not specified" % key, name)
 
         if auth is not None:
                     raise PLCInvalidArgument("'%s' not specified" % key, name)
 
         if auth is not None: