2 # Base class for all SfaAPI functions
8 from types import StringTypes
14 from sfa.util.sfalogging import sfa_logger
15 from sfa.util.faults import *
16 from sfa.util.parameter import Parameter, Mixed, python_type, xmlrpc_type
17 from sfa.trust.auth import Auth
21 Base class for all SfaAPI functions. At a minimum, all SfaAPI
22 functions must define:
24 interfaces = [allowed interfaces]
25 accepts = [Parameter(arg1_type, arg1_doc), Parameter(arg2_type, arg2_doc), ...]
26 returns = Parameter(return_type, return_doc)
27 call(arg1, arg2, ...): method body
29 Argument types may be Python types (e.g., int, bool, etc.), typed
30 values (e.g., 1, True, etc.), a Parameter, or lists or
31 dictionaries of possibly mixed types, values, and/or Parameters
32 (e.g., [int, bool, ...] or {'arg1': int, 'arg2': bool}).
34 Once function decorators in Python 2.4 are fully supported,
35 consider wrapping calls with accepts() and returns() functions
36 instead of performing type checking manually.
44 def call(self, *args):
46 Method body for all SfaAPI functions. Must override.
50 def __init__(self, api):
51 self.name = self.__class__.__name__
54 # Auth may set this to a Person instance (if an anonymous
55 # method, will remain None).
58 # API may set this to a (addr, port) tuple if known
61 def __call__(self, *args, **kwds):
63 Main entry point for all SfaAPI functions. Type checks
64 arguments, authenticates, and executes call().
69 methodname = self.name
70 if not self.api.interface or self.api.interface not in self.interfaces:
71 raise SfaInvalidAPIMethod(methodname, self.api.interface)
73 (min_args, max_args, defaults) = self.args()
75 # Check that the right number of arguments were passed in
76 if len(args) < len(min_args) or len(args) > len(max_args):
77 raise SfaInvalidArgumentCount(len(args), len(min_args), len(max_args))
79 for name, value, expected in zip(max_args, args, self.accepts):
80 self.type_check(name, value, expected, args)
82 result = self.call(*args, **kwds)
83 runtime = time.time() - start
85 if self.api.config.SFA_API_DEBUG or hasattr(self, 'message'):
86 msg=getattr(self,'message',"method %s completed in %02f s"%(methodname,runtime))
87 sfa_logger().debug(msg)
91 except SfaFault, fault:
95 # Prepend caller and method name to expected faults
96 fault.faultString = caller + ": " + self.name + ": " + fault.faultString
97 runtime = time.time() - start
98 sfa_logger().log_exc("Method %s raised an exception"%self.name)
102 def help(self, indent = " "):
104 Text documentation for the method.
107 (min_args, max_args, defaults) = self.args()
109 text = "%s(%s) -> %s\n\n" % (self.name, ", ".join(max_args), xmlrpc_type(self.returns))
111 text += "Description:\n\n"
112 lines = [indent + line.strip() for line in self.__doc__.strip().split("\n")]
113 text += "\n".join(lines) + "\n\n"
115 def param_text(name, param, indent, step):
117 Format a method parameter.
122 # Print parameter name
125 text += name.ljust(param_offset - len(indent))
127 param_offset = len(indent)
129 # Print parameter type
130 param_type = python_type(param)
131 text += xmlrpc_type(param_type) + "\n"
133 # Print parameter documentation right below type
134 if isinstance(param, Parameter):
135 wrapper = textwrap.TextWrapper(width = 70,
136 initial_indent = " " * param_offset,
137 subsequent_indent = " " * param_offset)
138 text += "\n".join(wrapper.wrap(param.doc)) + "\n"
143 # Indent struct fields and mixed types
144 if isinstance(param, dict):
145 for name, subparam in param.iteritems():
146 text += param_text(name, subparam, indent + step, step)
147 elif isinstance(param, Mixed):
148 for subparam in param:
149 text += param_text(name, subparam, indent + step, step)
150 elif isinstance(param, (list, tuple, set)):
151 for subparam in param:
152 text += param_text("", subparam, indent + step, step)
156 text += "Parameters:\n\n"
157 for name, param in zip(max_args, self.accepts):
158 text += param_text(name, param, indent, indent)
160 text += "Returns:\n\n"
161 text += param_text("", self.returns, indent, indent)
169 ((arg1_name, arg2_name, ...),
170 (arg1_name, arg2_name, ..., optional1_name, optional2_name, ...),
171 (None, None, ..., optional1_default, optional2_default, ...))
173 That represents the minimum and maximum sets of arguments that
174 this function accepts and the defaults for the optional arguments.
177 # Inspect call. Remove self from the argument list.
178 max_args = self.call.func_code.co_varnames[1:self.call.func_code.co_argcount]
179 defaults = self.call.func_defaults
183 min_args = max_args[0:len(max_args) - len(defaults)]
184 defaults = tuple([None for arg in min_args]) + defaults
186 return (min_args, max_args, defaults)
188 def type_check(self, name, value, expected, args):
190 Checks the type of the named value against the expected type,
191 which may be a Python type, a typed value, a Parameter, a
192 Mixed type, or a list or dictionary of possibly mixed types,
193 values, Parameters, or Mixed types.
195 Extraneous members of lists must be of the same type as the
196 last specified type. For example, if the expected argument
197 type is [int, bool], then [1, False] and [14, True, False,
198 True] are valid, but [1], [False, 1] and [14, True, 1] are
201 Extraneous members of dictionaries are ignored.
204 # If any of a number of types is acceptable
205 if isinstance(expected, Mixed):
206 for item in expected:
208 self.type_check(name, value, item, args)
210 except SfaInvalidArgument, fault:
214 # If an authentication structure is expected, save it and
215 # authenticate after basic type checking is done.
216 #if isinstance(expected, Auth):
221 # Get actual expected type from within the Parameter structure
222 if isinstance(expected, Parameter):
225 nullok = expected.nullok
226 expected = expected.type
232 expected_type = python_type(expected)
234 # If value can be NULL
235 if value is None and nullok:
238 # Strings are a special case. Accept either unicode or str
239 # types if a string is expected.
240 if expected_type in StringTypes and isinstance(value, StringTypes):
243 # Integers and long integers are also special types. Accept
244 # either int or long types if an int or long is expected.
245 elif expected_type in (IntType, LongType) and isinstance(value, (IntType, LongType)):
248 elif not isinstance(value, expected_type):
249 raise SfaInvalidArgument("expected %s, got %s" % \
250 (xmlrpc_type(expected_type),
251 xmlrpc_type(type(value))),
254 # If a minimum or maximum (length, value) has been specified
255 if expected_type in StringTypes:
256 if min is not None and \
257 len(value.encode(self.api.encoding)) < min:
258 raise SfaInvalidArgument, "%s must be at least %d bytes long" % (name, min)
259 if max is not None and \
260 len(value.encode(self.api.encoding)) > max:
261 raise SfaInvalidArgument, "%s must be at most %d bytes long" % (name, max)
262 elif expected_type in (list, tuple, set):
263 if min is not None and len(value) < min:
264 raise SfaInvalidArgument, "%s must contain at least %d items" % (name, min)
265 if max is not None and len(value) > max:
266 raise SfaInvalidArgument, "%s must contain at most %d items" % (name, max)
268 if min is not None and value < min:
269 raise SfaInvalidArgument, "%s must be > %s" % (name, str(min))
270 if max is not None and value > max:
271 raise SfaInvalidArgument, "%s must be < %s" % (name, str(max))
273 # If a list with particular types of items is expected
274 if isinstance(expected, (list, tuple, set)):
275 for i in range(len(value)):
276 if i >= len(expected):
277 j = len(expected) - 1
280 self.type_check(name + "[]", value[i], expected[j], args)
282 # If a struct with particular (or required) types of items is
284 elif isinstance(expected, dict):
285 for key in value.keys():
287 self.type_check(name + "['%s']" % key, value[key], expected[key], args)
288 for key, subparam in expected.iteritems():
289 if isinstance(subparam, Parameter) and \
290 subparam.optional is not None and \
291 not subparam.optional and key not in value.keys():
292 raise SfaInvalidArgument("'%s' not specified" % key, name)
294 #if auth is not None:
295 # auth.check(self, *args)