2 # Base class for all SfaAPI functions
11 from types import StringTypes
17 from sfa.util.sfalogging import sfa_logger
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
22 # we inherit object because we use new-style classes for legacy methods
23 class Method (object):
25 Base class for all SfaAPI functions. At a minimum, all SfaAPI
26 functions must define:
28 interfaces = [allowed interfaces]
29 accepts = [Parameter(arg1_type, arg1_doc), Parameter(arg2_type, arg2_doc), ...]
30 returns = Parameter(return_type, return_doc)
31 call(arg1, arg2, ...): method body
33 Argument types may be Python types (e.g., int, bool, etc.), typed
34 values (e.g., 1, True, etc.), a Parameter, or lists or
35 dictionaries of possibly mixed types, values, and/or Parameters
36 (e.g., [int, bool, ...] or {'arg1': int, 'arg2': bool}).
38 Once function decorators in Python 2.4 are fully supported,
39 consider wrapping calls with accepts() and returns() functions
40 instead of performing type checking manually.
48 def call(self, *args):
50 Method body for all SfaAPI functions. Must override.
56 def __init__(self, api):
57 self.name = self.__class__.__name__
60 # Auth may set this to a Person instance (if an anonymous
61 # method, will remain None).
64 # API may set this to a (addr, port) tuple if known
67 def __call__(self, *args, **kwds):
69 Main entry point for all SfaAPI functions. Type checks
70 arguments, authenticates, and executes call().
75 methodname = self.name
76 if not self.api.interface or self.api.interface not in self.interfaces:
77 raise SfaInvalidAPIMethod(methodname, self.api.interface)
79 # legacy code cannot be type-checked, due to the way Method.args() works
80 if not hasattr(self,"skip_typecheck"):
81 (min_args, max_args, defaults) = self.args()
83 # Check that the right number of arguments were passed in
84 if len(args) < len(min_args) or len(args) > len(max_args):
85 raise SfaInvalidArgumentCount(len(args), len(min_args), len(max_args))
87 for name, value, expected in zip(max_args, args, self.accepts):
88 self.type_check(name, value, expected, args)
90 result = self.call(*args, **kwds)
91 runtime = time.time() - start
93 if self.api.config.SFA_API_DEBUG or hasattr(self, 'message'):
94 msg=getattr(self,'message',"method %s completed in %02f s"%(methodname,runtime))
99 except SfaFault, fault:
103 # Prepend caller and method name to expected faults
104 fault.faultString = caller + ": " + self.name + ": " + fault.faultString
105 runtime = time.time() - start
106 sfa_logger.log_exc("Method %s raised an exception"%self.name)
110 def help(self, indent = " "):
112 Text documentation for the method.
115 (min_args, max_args, defaults) = self.args()
117 text = "%s(%s) -> %s\n\n" % (self.name, ", ".join(max_args), xmlrpc_type(self.returns))
119 text += "Description:\n\n"
120 lines = [indent + line.strip() for line in self.__doc__.strip().split("\n")]
121 text += "\n".join(lines) + "\n\n"
123 def param_text(name, param, indent, step):
125 Format a method parameter.
130 # Print parameter name
133 text += name.ljust(param_offset - len(indent))
135 param_offset = len(indent)
137 # Print parameter type
138 param_type = python_type(param)
139 text += xmlrpc_type(param_type) + "\n"
141 # Print parameter documentation right below type
142 if isinstance(param, Parameter):
143 wrapper = textwrap.TextWrapper(width = 70,
144 initial_indent = " " * param_offset,
145 subsequent_indent = " " * param_offset)
146 text += "\n".join(wrapper.wrap(param.doc)) + "\n"
151 # Indent struct fields and mixed types
152 if isinstance(param, dict):
153 for name, subparam in param.iteritems():
154 text += param_text(name, subparam, indent + step, step)
155 elif isinstance(param, Mixed):
156 for subparam in param:
157 text += param_text(name, subparam, indent + step, step)
158 elif isinstance(param, (list, tuple, set)):
159 for subparam in param:
160 text += param_text("", subparam, indent + step, step)
164 text += "Parameters:\n\n"
165 for name, param in zip(max_args, self.accepts):
166 text += param_text(name, param, indent, indent)
168 text += "Returns:\n\n"
169 text += param_text("", self.returns, indent, indent)
177 ((arg1_name, arg2_name, ...),
178 (arg1_name, arg2_name, ..., optional1_name, optional2_name, ...),
179 (None, None, ..., optional1_default, optional2_default, ...))
181 That represents the minimum and maximum sets of arguments that
182 this function accepts and the defaults for the optional arguments.
185 # Inspect call. Remove self from the argument list.
186 max_args = self.call.func_code.co_varnames[1:self.call.func_code.co_argcount]
187 defaults = self.call.func_defaults
191 min_args = max_args[0:len(max_args) - len(defaults)]
192 defaults = tuple([None for arg in min_args]) + defaults
194 return (min_args, max_args, defaults)
196 def type_check(self, name, value, expected, args):
198 Checks the type of the named value against the expected type,
199 which may be a Python type, a typed value, a Parameter, a
200 Mixed type, or a list or dictionary of possibly mixed types,
201 values, Parameters, or Mixed types.
203 Extraneous members of lists must be of the same type as the
204 last specified type. For example, if the expected argument
205 type is [int, bool], then [1, False] and [14, True, False,
206 True] are valid, but [1], [False, 1] and [14, True, 1] are
209 Extraneous members of dictionaries are ignored.
212 # If any of a number of types is acceptable
213 if isinstance(expected, Mixed):
214 for item in expected:
216 self.type_check(name, value, item, args)
218 except SfaInvalidArgument, fault:
222 # If an authentication structure is expected, save it and
223 # authenticate after basic type checking is done.
224 #if isinstance(expected, Auth):
229 # Get actual expected type from within the Parameter structure
230 if isinstance(expected, Parameter):
233 nullok = expected.nullok
234 expected = expected.type
240 expected_type = python_type(expected)
242 # If value can be NULL
243 if value is None and nullok:
246 # Strings are a special case. Accept either unicode or str
247 # types if a string is expected.
248 if expected_type in StringTypes and isinstance(value, StringTypes):
251 # Integers and long integers are also special types. Accept
252 # either int or long types if an int or long is expected.
253 elif expected_type in (IntType, LongType) and isinstance(value, (IntType, LongType)):
256 elif not isinstance(value, expected_type):
257 raise SfaInvalidArgument("expected %s, got %s" % \
258 (xmlrpc_type(expected_type),
259 xmlrpc_type(type(value))),
262 # If a minimum or maximum (length, value) has been specified
263 if expected_type in StringTypes:
264 if min is not None and \
265 len(value.encode(self.api.encoding)) < min:
266 raise SfaInvalidArgument, "%s must be at least %d bytes long" % (name, min)
267 if max is not None and \
268 len(value.encode(self.api.encoding)) > max:
269 raise SfaInvalidArgument, "%s must be at most %d bytes long" % (name, max)
270 elif expected_type in (list, tuple, set):
271 if min is not None and len(value) < min:
272 raise SfaInvalidArgument, "%s must contain at least %d items" % (name, min)
273 if max is not None and len(value) > max:
274 raise SfaInvalidArgument, "%s must contain at most %d items" % (name, max)
276 if min is not None and value < min:
277 raise SfaInvalidArgument, "%s must be > %s" % (name, str(min))
278 if max is not None and value > max:
279 raise SfaInvalidArgument, "%s must be < %s" % (name, str(max))
281 # If a list with particular types of items is expected
282 if isinstance(expected, (list, tuple, set)):
283 for i in range(len(value)):
284 if i >= len(expected):
285 j = len(expected) - 1
288 self.type_check(name + "[]", value[i], expected[j], args)
290 # If a struct with particular (or required) types of items is
292 elif isinstance(expected, dict):
293 for key in value.keys():
295 self.type_check(name + "['%s']" % key, value[key], expected[key], args)
296 for key, subparam in expected.iteritems():
297 if isinstance(subparam, Parameter) and \
298 subparam.optional is not None and \
299 not subparam.optional and key not in value.keys():
300 raise SfaInvalidArgument("'%s' not specified" % key, name)
302 #if auth is not None:
303 # auth.check(self, *args)