2 # Base class for all SfaAPI functions
7 from types import IntType, LongType, StringTypes
10 from sfa.util.sfalogging import logger
11 from sfa.util.faults import SfaFault, SfaInvalidAPIMethod, SfaInvalidArgumentCount, SfaInvalidArgument
13 from sfa.storage.parameter import Parameter, Mixed, python_type, xmlrpc_type
17 Base class for all SfaAPI functions. At a minimum, all SfaAPI
18 functions must define:
20 interfaces = [allowed interfaces]
21 accepts = [Parameter(arg1_type, arg1_doc), Parameter(arg2_type, arg2_doc), ...]
22 returns = Parameter(return_type, return_doc)
23 call(arg1, arg2, ...): method body
25 Argument types may be Python types (e.g., int, bool, etc.), typed
26 values (e.g., 1, True, etc.), a Parameter, or lists or
27 dictionaries of possibly mixed types, values, and/or Parameters
28 (e.g., [int, bool, ...] or {'arg1': int, 'arg2': bool}).
30 Once function decorators in Python 2.4 are fully supported,
31 consider wrapping calls with accepts() and returns() functions
32 instead of performing type checking manually.
40 def call(self, *args):
42 Method body for all SfaAPI functions. Must override.
46 def __init__(self, api):
47 self.name = self.__class__.__name__
50 # Auth may set this to a Person instance (if an anonymous
51 # method, will remain None).
54 # API may set this to a (addr, port) tuple if known
57 def __call__(self, *args, **kwds):
59 Main entry point for all SFA API functions. Type checks
60 arguments, authenticates, and executes call().
65 methodname = self.name
66 if not self.api.interface or self.api.interface not in self.interfaces:
67 raise SfaInvalidAPIMethod(methodname, self.api.interface)
69 (min_args, max_args, defaults) = self.args()
71 # Check that the right number of arguments were passed in
72 if len(args) < len(min_args) or len(args) > len(max_args):
73 raise SfaInvalidArgumentCount(len(args), len(min_args), len(max_args))
75 for name, value, expected in zip(max_args, args, self.accepts):
76 self.type_check(name, value, expected, args)
78 logger.debug("method.__call__ [%s] : BEG %s"%(self.api.interface,methodname))
79 result = self.call(*args, **kwds)
81 runtime = time.time() - start
82 logger.debug("method.__call__ [%s] : END %s in %02f s (%s)"%\
83 (self.api.interface,methodname,runtime,getattr(self,'message',"[no-msg]")))
87 except SfaFault, fault:
91 # Prepend caller and method name to expected faults
92 fault.faultString = caller + ": " + self.name + ": " + fault.faultString
93 runtime = time.time() - start
94 logger.log_exc("Method %s raised an exception"%self.name)
98 def help(self, indent = " "):
100 Text documentation for the method.
103 (min_args, max_args, defaults) = self.args()
105 text = "%s(%s) -> %s\n\n" % (self.name, ", ".join(max_args), xmlrpc_type(self.returns))
107 text += "Description:\n\n"
108 lines = [indent + line.strip() for line in self.__doc__.strip().split("\n")]
109 text += "\n".join(lines) + "\n\n"
111 def param_text(name, param, indent, step):
113 Format a method parameter.
118 # Print parameter name
121 text += name.ljust(param_offset - len(indent))
123 param_offset = len(indent)
125 # Print parameter type
126 param_type = python_type(param)
127 text += xmlrpc_type(param_type) + "\n"
129 # Print parameter documentation right below type
130 if isinstance(param, Parameter):
131 wrapper = textwrap.TextWrapper(width = 70,
132 initial_indent = " " * param_offset,
133 subsequent_indent = " " * param_offset)
134 text += "\n".join(wrapper.wrap(param.doc)) + "\n"
139 # Indent struct fields and mixed types
140 if isinstance(param, dict):
141 for name, subparam in param.iteritems():
142 text += param_text(name, subparam, indent + step, step)
143 elif isinstance(param, Mixed):
144 for subparam in param:
145 text += param_text(name, subparam, indent + step, step)
146 elif isinstance(param, (list, tuple, set)):
147 for subparam in param:
148 text += param_text("", subparam, indent + step, step)
152 text += "Parameters:\n\n"
153 for name, param in zip(max_args, self.accepts):
154 text += param_text(name, param, indent, indent)
156 text += "Returns:\n\n"
157 text += param_text("", self.returns, indent, indent)
165 ((arg1_name, arg2_name, ...),
166 (arg1_name, arg2_name, ..., optional1_name, optional2_name, ...),
167 (None, None, ..., optional1_default, optional2_default, ...))
169 That represents the minimum and maximum sets of arguments that
170 this function accepts and the defaults for the optional arguments.
173 # Inspect call. Remove self from the argument list.
174 max_args = self.call.func_code.co_varnames[1:self.call.func_code.co_argcount]
175 defaults = self.call.func_defaults
179 min_args = max_args[0:len(max_args) - len(defaults)]
180 defaults = tuple([None for arg in min_args]) + defaults
182 return (min_args, max_args, defaults)
184 def type_check(self, name, value, expected, args):
186 Checks the type of the named value against the expected type,
187 which may be a Python type, a typed value, a Parameter, a
188 Mixed type, or a list or dictionary of possibly mixed types,
189 values, Parameters, or Mixed types.
191 Extraneous members of lists must be of the same type as the
192 last specified type. For example, if the expected argument
193 type is [int, bool], then [1, False] and [14, True, False,
194 True] are valid, but [1], [False, 1] and [14, True, 1] are
197 Extraneous members of dictionaries are ignored.
200 # If any of a number of types is acceptable
201 if isinstance(expected, Mixed):
202 for item in expected:
204 self.type_check(name, value, item, args)
206 except SfaInvalidArgument, fault:
210 # If an authentication structure is expected, save it and
211 # authenticate after basic type checking is done.
212 #if isinstance(expected, Auth):
217 # Get actual expected type from within the Parameter structure
218 if isinstance(expected, Parameter):
221 nullok = expected.nullok
222 expected = expected.type
228 expected_type = python_type(expected)
230 # If value can be NULL
231 if value is None and nullok:
234 # Strings are a special case. Accept either unicode or str
235 # types if a string is expected.
236 if expected_type in StringTypes and isinstance(value, StringTypes):
239 # Integers and long integers are also special types. Accept
240 # either int or long types if an int or long is expected.
241 elif expected_type in (IntType, LongType) and isinstance(value, (IntType, LongType)):
244 elif not isinstance(value, expected_type):
245 raise SfaInvalidArgument("expected %s, got %s" % \
246 (xmlrpc_type(expected_type), xmlrpc_type(type(value))),
249 # If a minimum or maximum (length, value) has been specified
250 if expected_type in StringTypes:
251 if min is not None and \
252 len(value.encode(self.api.encoding)) < min:
253 raise SfaInvalidArgument, "%s must be at least %d bytes long" % (name, min)
254 if max is not None and \
255 len(value.encode(self.api.encoding)) > max:
256 raise SfaInvalidArgument, "%s must be at most %d bytes long" % (name, max)
257 elif expected_type in (list, tuple, set):
258 if min is not None and len(value) < min:
259 raise SfaInvalidArgument, "%s must contain at least %d items" % (name, min)
260 if max is not None and len(value) > max:
261 raise SfaInvalidArgument, "%s must contain at most %d items" % (name, max)
263 if min is not None and value < min:
264 raise SfaInvalidArgument, "%s must be > %s" % (name, str(min))
265 if max is not None and value > max:
266 raise SfaInvalidArgument, "%s must be < %s" % (name, str(max))
268 # If a list with particular types of items is expected
269 if isinstance(expected, (list, tuple, set)):
270 for i in range(len(value)):
271 if i >= len(expected):
272 j = len(expected) - 1
275 self.type_check(name + "[]", value[i], expected[j], args)
277 # If a struct with particular (or required) types of items is
279 elif isinstance(expected, dict):
280 for key in value.keys():
282 self.type_check(name + "['%s']" % key, value[key], expected[key], args)
283 for key, subparam in expected.iteritems():
284 if isinstance(subparam, Parameter) and \
285 subparam.optional is not None and \
286 not subparam.optional and key not in value.keys():
287 raise SfaInvalidArgument("'%s' not specified" % key, name)
289 #if auth is not None:
290 # auth.check(self, *args)