2 # Base class for all SfaAPI functions
9 from sfa.util.sfalogging import logger
10 from sfa.util.faults import SfaFault, SfaInvalidAPIMethod, SfaInvalidArgumentCount, SfaInvalidArgument
12 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(
74 len(args), len(min_args), len(max_args))
76 for name, value, expected in zip(max_args, args, self.accepts):
77 self.type_check(name, value, expected, args)
79 logger.debug("method.__call__ [%s] : BEG %s" % (
80 self.api.interface, methodname))
81 result = self.call(*args, **kwds)
83 runtime = time.time() - start
84 logger.debug("method.__call__ [%s] : END %s in %02f s (%s)" %
85 (self.api.interface, methodname, runtime, getattr(self, 'message', "[no-msg]")))
89 except SfaFault as fault:
93 # Prepend caller and method name to expected faults
94 fault.faultString = caller + ": " + self.name + ": " + fault.faultString
95 runtime = time.time() - start
96 logger.log_exc("Method %s raised an exception" % self.name)
99 def help(self, indent=" "):
101 Text documentation for the method.
104 (min_args, max_args, defaults) = self.args()
106 text = "%s(%s) -> %s\n\n" % (self.name,
107 ", ".join(max_args), xmlrpc_type(self.returns))
109 text += "Description:\n\n"
110 lines = [indent + line.strip()
111 for line in self.__doc__.strip().split("\n")]
112 text += "\n".join(lines) + "\n\n"
114 def param_text(name, param, indent, step):
116 Format a method parameter.
121 # Print parameter name
124 text += name.ljust(param_offset - len(indent))
126 param_offset = len(indent)
128 # Print parameter type
129 param_type = python_type(param)
130 text += xmlrpc_type(param_type) + "\n"
132 # Print parameter documentation right below type
133 if isinstance(param, Parameter):
134 wrapper = textwrap.TextWrapper(width=70,
135 initial_indent=" " * param_offset,
136 subsequent_indent=" " * param_offset)
137 text += "\n".join(wrapper.wrap(param.doc)) + "\n"
142 # Indent struct fields and mixed types
143 if isinstance(param, dict):
144 for name, subparam in param.items():
145 text += param_text(name, subparam, indent + step, step)
146 elif isinstance(param, Mixed):
147 for subparam in param:
148 text += param_text(name, subparam, indent + step, step)
149 elif isinstance(param, (list, tuple, set)):
150 for subparam in param:
151 text += param_text("", subparam, indent + step, step)
155 text += "Parameters:\n\n"
156 for name, param in zip(max_args, self.accepts):
157 text += param_text(name, param, indent, indent)
159 text += "Returns:\n\n"
160 text += param_text("", self.returns, indent, indent)
168 ((arg1_name, arg2_name, ...),
169 (arg1_name, arg2_name, ..., optional1_name, optional2_name, ...),
170 (None, None, ..., optional1_default, optional2_default, ...))
172 That represents the minimum and maximum sets of arguments that
173 this function accepts and the defaults for the optional arguments.
176 # Inspect call. Remove self from the argument list.
177 max_args = self.call.__code__.co_varnames[
178 1:self.call.__code__.co_argcount]
179 defaults = self.call.__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 as 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 issubclass(expected_type, str) and isinstance(value, str):
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 is int and isinstance(value, int):
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 issubclass(expected_type, str):
256 if min is not None and \
257 len(value.encode(self.api.encoding)) < min:
258 raise SfaInvalidArgument(
259 "%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(
263 "%s must be at most %d bytes long" % (name, max))
264 elif expected_type in (list, tuple, set):
265 if min is not None and len(value) < min:
266 raise SfaInvalidArgument(
267 "%s must contain at least %d items" % (name, min))
268 if max is not None and len(value) > max:
269 raise SfaInvalidArgument(
270 "%s must contain at most %d items" % (name, max))
272 if min is not None and value < min:
273 raise SfaInvalidArgument("%s must be > %s" % (name, str(min)))
274 if max is not None and value > max:
275 raise SfaInvalidArgument("%s must be < %s" % (name, str(max)))
277 # If a list with particular types of items is expected
278 if isinstance(expected, (list, tuple, set)):
279 for i in range(len(value)):
280 if i >= len(expected):
281 j = len(expected) - 1
284 self.type_check(name + "[]", value[i], expected[j], args)
286 # If a struct with particular (or required) types of items is
288 elif isinstance(expected, dict):
289 for key in list(value.keys()):
291 self.type_check(name + "['%s']" %
292 key, value[key], expected[key], args)
293 for key, subparam in expected.items():
294 if isinstance(subparam, Parameter) and \
295 subparam.optional is not None and \
296 not subparam.optional and key not in list(value.keys()):
297 raise SfaInvalidArgument("'%s' not specified" % key, name)
299 # if auth is not None:
300 # auth.check(self, *args)