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 if self.api.config.SFA_API_DEBUG:
83 sfa_logger().debug("method.__call__ calling method %s"%methodname)
84 result = self.call(*args, **kwds)
86 runtime = time.time() - start
87 if self.api.config.SFA_API_DEBUG or hasattr(self, 'message'):
88 sfa_logger().debug("method.__call__ %s completed in %02f s (%s)"%(methodname,runtime,getattr(self,'message',"[no-msg]")))
92 except SfaFault, fault:
96 # Prepend caller and method name to expected faults
97 fault.faultString = caller + ": " + self.name + ": " + fault.faultString
98 runtime = time.time() - start
99 sfa_logger().log_exc("Method %s raised an exception"%self.name)
103 def help(self, indent = " "):
105 Text documentation for the method.
108 (min_args, max_args, defaults) = self.args()
110 text = "%s(%s) -> %s\n\n" % (self.name, ", ".join(max_args), xmlrpc_type(self.returns))
112 text += "Description:\n\n"
113 lines = [indent + line.strip() for line in self.__doc__.strip().split("\n")]
114 text += "\n".join(lines) + "\n\n"
116 def param_text(name, param, indent, step):
118 Format a method parameter.
123 # Print parameter name
126 text += name.ljust(param_offset - len(indent))
128 param_offset = len(indent)
130 # Print parameter type
131 param_type = python_type(param)
132 text += xmlrpc_type(param_type) + "\n"
134 # Print parameter documentation right below type
135 if isinstance(param, Parameter):
136 wrapper = textwrap.TextWrapper(width = 70,
137 initial_indent = " " * param_offset,
138 subsequent_indent = " " * param_offset)
139 text += "\n".join(wrapper.wrap(param.doc)) + "\n"
144 # Indent struct fields and mixed types
145 if isinstance(param, dict):
146 for name, subparam in param.iteritems():
147 text += param_text(name, subparam, indent + step, step)
148 elif isinstance(param, Mixed):
149 for subparam in param:
150 text += param_text(name, subparam, indent + step, step)
151 elif isinstance(param, (list, tuple, set)):
152 for subparam in param:
153 text += param_text("", subparam, indent + step, step)
157 text += "Parameters:\n\n"
158 for name, param in zip(max_args, self.accepts):
159 text += param_text(name, param, indent, indent)
161 text += "Returns:\n\n"
162 text += param_text("", self.returns, indent, indent)
170 ((arg1_name, arg2_name, ...),
171 (arg1_name, arg2_name, ..., optional1_name, optional2_name, ...),
172 (None, None, ..., optional1_default, optional2_default, ...))
174 That represents the minimum and maximum sets of arguments that
175 this function accepts and the defaults for the optional arguments.
178 # Inspect call. Remove self from the argument list.
179 max_args = self.call.func_code.co_varnames[1:self.call.func_code.co_argcount]
180 defaults = self.call.func_defaults
184 min_args = max_args[0:len(max_args) - len(defaults)]
185 defaults = tuple([None for arg in min_args]) + defaults
187 return (min_args, max_args, defaults)
189 def type_check(self, name, value, expected, args):
191 Checks the type of the named value against the expected type,
192 which may be a Python type, a typed value, a Parameter, a
193 Mixed type, or a list or dictionary of possibly mixed types,
194 values, Parameters, or Mixed types.
196 Extraneous members of lists must be of the same type as the
197 last specified type. For example, if the expected argument
198 type is [int, bool], then [1, False] and [14, True, False,
199 True] are valid, but [1], [False, 1] and [14, True, 1] are
202 Extraneous members of dictionaries are ignored.
205 # If any of a number of types is acceptable
206 if isinstance(expected, Mixed):
207 for item in expected:
209 self.type_check(name, value, item, args)
211 except SfaInvalidArgument, fault:
215 # If an authentication structure is expected, save it and
216 # authenticate after basic type checking is done.
217 #if isinstance(expected, Auth):
222 # Get actual expected type from within the Parameter structure
223 if isinstance(expected, Parameter):
226 nullok = expected.nullok
227 expected = expected.type
233 expected_type = python_type(expected)
235 # If value can be NULL
236 if value is None and nullok:
239 # Strings are a special case. Accept either unicode or str
240 # types if a string is expected.
241 if expected_type in StringTypes and isinstance(value, StringTypes):
244 # Integers and long integers are also special types. Accept
245 # either int or long types if an int or long is expected.
246 elif expected_type in (IntType, LongType) and isinstance(value, (IntType, LongType)):
249 elif not isinstance(value, expected_type):
250 raise SfaInvalidArgument("expected %s, got %s" % \
251 (xmlrpc_type(expected_type),
252 xmlrpc_type(type(value))),
255 # If a minimum or maximum (length, value) has been specified
256 if expected_type in StringTypes:
257 if min is not None and \
258 len(value.encode(self.api.encoding)) < min:
259 raise SfaInvalidArgument, "%s must be at least %d bytes long" % (name, min)
260 if max is not None and \
261 len(value.encode(self.api.encoding)) > max:
262 raise SfaInvalidArgument, "%s must be at most %d bytes long" % (name, max)
263 elif expected_type in (list, tuple, set):
264 if min is not None and len(value) < min:
265 raise SfaInvalidArgument, "%s must contain at least %d items" % (name, min)
266 if max is not None and len(value) > max:
267 raise SfaInvalidArgument, "%s must contain at most %d items" % (name, max)
269 if min is not None and value < min:
270 raise SfaInvalidArgument, "%s must be > %s" % (name, str(min))
271 if max is not None and value > max:
272 raise SfaInvalidArgument, "%s must be < %s" % (name, str(max))
274 # If a list with particular types of items is expected
275 if isinstance(expected, (list, tuple, set)):
276 for i in range(len(value)):
277 if i >= len(expected):
278 j = len(expected) - 1
281 self.type_check(name + "[]", value[i], expected[j], args)
283 # If a struct with particular (or required) types of items is
285 elif isinstance(expected, dict):
286 for key in value.keys():
288 self.type_check(name + "['%s']" % key, value[key], expected[key], args)
289 for key, subparam in expected.iteritems():
290 if isinstance(subparam, Parameter) and \
291 subparam.optional is not None and \
292 not subparam.optional and key not in value.keys():
293 raise SfaInvalidArgument("'%s' not specified" % key, name)
295 #if auth is not None:
296 # auth.check(self, *args)