2 # Base class for all SfaAPI functions
11 from types import StringTypes
17 import sfa.util.sfalogging
18 from sfa.util.faults import *
19 from sfa.util.parameter import Parameter, Mixed, python_type, xmlrpc_type
20 from sfa.trust.auth import Auth
21 #from sfa.util.debug import profile, log
23 # we inherit object because we use new-style classes for legacy methods
24 class Method (object):
26 Base class for all SfaAPI functions. At a minimum, all SfaAPI
27 functions must define:
29 interfaces = [allowed interfaces]
30 accepts = [Parameter(arg1_type, arg1_doc), Parameter(arg2_type, arg2_doc), ...]
31 returns = Parameter(return_type, return_doc)
32 call(arg1, arg2, ...): method body
34 Argument types may be Python types (e.g., int, bool, etc.), typed
35 values (e.g., 1, True, etc.), a Parameter, or lists or
36 dictionaries of possibly mixed types, values, and/or Parameters
37 (e.g., [int, bool, ...] or {'arg1': int, 'arg2': bool}).
39 Once function decorators in Python 2.4 are fully supported,
40 consider wrapping calls with accepts() and returns() functions
41 instead of performing type checking manually.
49 def call(self, *args):
51 Method body for all SfaAPI functions. Must override.
57 def __init__(self, api):
58 self.name = self.__class__.__name__
61 # Auth may set this to a Person instance (if an anonymous
62 # method, will remain None).
65 # API may set this to a (addr, port) tuple if known
68 def __call__(self, *args, **kwds):
70 Main entry point for all SfaAPI functions. Type checks
71 arguments, authenticates, and executes call().
76 methodname = self.name
77 if not self.api.interface or self.api.interface not in self.interfaces:
78 raise SfaInvalidAPIMethod(methodname, self.api.interface)
80 # legacy code cannot be type-checked, due to the way Method.args() works
81 if not hasattr(self,"skip_typecheck"):
82 (min_args, max_args, defaults) = self.args()
84 # Check that the right number of arguments were passed in
85 if len(args) < len(min_args) or len(args) > len(max_args):
86 raise SfaInvalidArgumentCount(len(args), len(min_args), len(max_args))
88 for name, value, expected in zip(max_args, args, self.accepts):
89 self.type_check(name, value, expected, args)
91 result = self.call(*args, **kwds)
92 runtime = time.time() - start
94 if self.api.config.SFA_API_DEBUG or hasattr(self, 'message'):
95 msg=getattr(self,'message',"method %s completed"%methodname)
96 sfa.util.sfalogging.logger.info(msg)
97 # XX print to some log file
98 # print >> log, "some output"
102 except SfaFault, fault:
106 # Prepend caller and method name to expected faults
107 fault.faultString = caller + ": " + self.name + ": " + fault.faultString
108 runtime = time.time() - start
109 # if self.api.config.SFA_API_DEBUG:
110 # traceback.print_exc()
111 sfa.util.sfalogging.log_exc("Method %s raised an exception"%self.name)
115 def help(self, indent = " "):
117 Text documentation for the method.
120 (min_args, max_args, defaults) = self.args()
122 text = "%s(%s) -> %s\n\n" % (self.name, ", ".join(max_args), xmlrpc_type(self.returns))
124 text += "Description:\n\n"
125 lines = [indent + line.strip() for line in self.__doc__.strip().split("\n")]
126 text += "\n".join(lines) + "\n\n"
128 def param_text(name, param, indent, step):
130 Format a method parameter.
135 # Print parameter name
138 text += name.ljust(param_offset - len(indent))
140 param_offset = len(indent)
142 # Print parameter type
143 param_type = python_type(param)
144 text += xmlrpc_type(param_type) + "\n"
146 # Print parameter documentation right below type
147 if isinstance(param, Parameter):
148 wrapper = textwrap.TextWrapper(width = 70,
149 initial_indent = " " * param_offset,
150 subsequent_indent = " " * param_offset)
151 text += "\n".join(wrapper.wrap(param.doc)) + "\n"
156 # Indent struct fields and mixed types
157 if isinstance(param, dict):
158 for name, subparam in param.iteritems():
159 text += param_text(name, subparam, indent + step, step)
160 elif isinstance(param, Mixed):
161 for subparam in param:
162 text += param_text(name, subparam, indent + step, step)
163 elif isinstance(param, (list, tuple, set)):
164 for subparam in param:
165 text += param_text("", subparam, indent + step, step)
169 text += "Parameters:\n\n"
170 for name, param in zip(max_args, self.accepts):
171 text += param_text(name, param, indent, indent)
173 text += "Returns:\n\n"
174 text += param_text("", self.returns, indent, indent)
182 ((arg1_name, arg2_name, ...),
183 (arg1_name, arg2_name, ..., optional1_name, optional2_name, ...),
184 (None, None, ..., optional1_default, optional2_default, ...))
186 That represents the minimum and maximum sets of arguments that
187 this function accepts and the defaults for the optional arguments.
190 # Inspect call. Remove self from the argument list.
191 max_args = self.call.func_code.co_varnames[1:self.call.func_code.co_argcount]
192 defaults = self.call.func_defaults
196 min_args = max_args[0:len(max_args) - len(defaults)]
197 defaults = tuple([None for arg in min_args]) + defaults
199 return (min_args, max_args, defaults)
201 def type_check(self, name, value, expected, args):
203 Checks the type of the named value against the expected type,
204 which may be a Python type, a typed value, a Parameter, a
205 Mixed type, or a list or dictionary of possibly mixed types,
206 values, Parameters, or Mixed types.
208 Extraneous members of lists must be of the same type as the
209 last specified type. For example, if the expected argument
210 type is [int, bool], then [1, False] and [14, True, False,
211 True] are valid, but [1], [False, 1] and [14, True, 1] are
214 Extraneous members of dictionaries are ignored.
217 # If any of a number of types is acceptable
218 if isinstance(expected, Mixed):
219 for item in expected:
221 self.type_check(name, value, item, args)
223 except SfaInvalidArgument, fault:
227 # If an authentication structure is expected, save it and
228 # authenticate after basic type checking is done.
229 #if isinstance(expected, Auth):
234 # Get actual expected type from within the Parameter structure
235 if isinstance(expected, Parameter):
238 nullok = expected.nullok
239 expected = expected.type
245 expected_type = python_type(expected)
247 # If value can be NULL
248 if value is None and nullok:
251 # Strings are a special case. Accept either unicode or str
252 # types if a string is expected.
253 if expected_type in StringTypes and isinstance(value, StringTypes):
256 # Integers and long integers are also special types. Accept
257 # either int or long types if an int or long is expected.
258 elif expected_type in (IntType, LongType) and isinstance(value, (IntType, LongType)):
261 elif not isinstance(value, expected_type):
262 raise SfaInvalidArgument("expected %s, got %s" % \
263 (xmlrpc_type(expected_type),
264 xmlrpc_type(type(value))),
267 # If a minimum or maximum (length, value) has been specified
268 if expected_type in StringTypes:
269 if min is not None and \
270 len(value.encode(self.api.encoding)) < min:
271 raise SfaInvalidArgument, "%s must be at least %d bytes long" % (name, min)
272 if max is not None and \
273 len(value.encode(self.api.encoding)) > max:
274 raise SfaInvalidArgument, "%s must be at most %d bytes long" % (name, max)
275 elif expected_type in (list, tuple, set):
276 if min is not None and len(value) < min:
277 raise SfaInvalidArgument, "%s must contain at least %d items" % (name, min)
278 if max is not None and len(value) > max:
279 raise SfaInvalidArgument, "%s must contain at most %d items" % (name, max)
281 if min is not None and value < min:
282 raise SfaInvalidArgument, "%s must be > %s" % (name, str(min))
283 if max is not None and value > max:
284 raise SfaInvalidArgument, "%s must be < %s" % (name, str(max))
286 # If a list with particular types of items is expected
287 if isinstance(expected, (list, tuple, set)):
288 for i in range(len(value)):
289 if i >= len(expected):
290 j = len(expected) - 1
293 self.type_check(name + "[]", value[i], expected[j], args)
295 # If a struct with particular (or required) types of items is
297 elif isinstance(expected, dict):
298 for key in value.keys():
300 self.type_check(name + "['%s']" % key, value[key], expected[key], args)
301 for key, subparam in expected.iteritems():
302 if isinstance(subparam, Parameter) and \
303 subparam.optional is not None and \
304 not subparam.optional and key not in value.keys():
305 raise SfaInvalidArgument("'%s' not specified" % key, name)
307 #if auth is not None:
308 # auth.check(self, *args)