2 # Base class for all GeniAPI functions
16 from types import StringTypes
18 from geni.util.faults import *
19 from geni.util.parameter import Parameter, Mixed, python_type, xmlrpc_type
20 from geni.util.auth import Auth
21 from geni.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 GeniAPI functions. At a minimum, all GeniAPI
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 GeniAPI 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 GeniAPI 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 GeniInvalidAPIMethod, 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 GeniInvalidArgumentCount(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.GENI_API_DEBUG or hasattr(self, 'message'):
95 # XX print to some log file
96 # print >> log, "some output"
101 except GeniFault, fault:
105 # Prepend caller and method name to expected faults
106 fault.faultString = caller + ": " + self.name + ": " + fault.faultString
107 runtime = time.time() - start
109 if self.api.config.GENI_API_DEBUG:
110 # XX print to some log file
111 #print >> log, "Some debugging output"
116 def help(self, indent = " "):
118 Text documentation for the method.
121 (min_args, max_args, defaults) = self.args()
123 text = "%s(%s) -> %s\n\n" % (self.name, ", ".join(max_args), xmlrpc_type(self.returns))
125 text += "Description:\n\n"
126 lines = [indent + line.strip() for line in self.__doc__.strip().split("\n")]
127 text += "\n".join(lines) + "\n\n"
129 def param_text(name, param, indent, step):
131 Format a method parameter.
136 # Print parameter name
139 text += name.ljust(param_offset - len(indent))
141 param_offset = len(indent)
143 # Print parameter type
144 param_type = python_type(param)
145 text += xmlrpc_type(param_type) + "\n"
147 # Print parameter documentation right below type
148 if isinstance(param, Parameter):
149 wrapper = textwrap.TextWrapper(width = 70,
150 initial_indent = " " * param_offset,
151 subsequent_indent = " " * param_offset)
152 text += "\n".join(wrapper.wrap(param.doc)) + "\n"
157 # Indent struct fields and mixed types
158 if isinstance(param, dict):
159 for name, subparam in param.iteritems():
160 text += param_text(name, subparam, indent + step, step)
161 elif isinstance(param, Mixed):
162 for subparam in param:
163 text += param_text(name, subparam, indent + step, step)
164 elif isinstance(param, (list, tuple, set)):
165 for subparam in param:
166 text += param_text("", subparam, indent + step, step)
170 text += "Parameters:\n\n"
171 for name, param in zip(max_args, self.accepts):
172 text += param_text(name, param, indent, indent)
174 text += "Returns:\n\n"
175 text += param_text("", self.returns, indent, indent)
183 ((arg1_name, arg2_name, ...),
184 (arg1_name, arg2_name, ..., optional1_name, optional2_name, ...),
185 (None, None, ..., optional1_default, optional2_default, ...))
187 That represents the minimum and maximum sets of arguments that
188 this function accepts and the defaults for the optional arguments.
191 # Inspect call. Remove self from the argument list.
192 max_args = self.call.func_code.co_varnames[1:self.call.func_code.co_argcount]
193 defaults = self.call.func_defaults
197 min_args = max_args[0:len(max_args) - len(defaults)]
198 defaults = tuple([None for arg in min_args]) + defaults
200 return (min_args, max_args, defaults)
202 def type_check(self, name, value, expected, args):
204 Checks the type of the named value against the expected type,
205 which may be a Python type, a typed value, a Parameter, a
206 Mixed type, or a list or dictionary of possibly mixed types,
207 values, Parameters, or Mixed types.
209 Extraneous members of lists must be of the same type as the
210 last specified type. For example, if the expected argument
211 type is [int, bool], then [1, False] and [14, True, False,
212 True] are valid, but [1], [False, 1] and [14, True, 1] are
215 Extraneous members of dictionaries are ignored.
218 # If any of a number of types is acceptable
219 if isinstance(expected, Mixed):
220 for item in expected:
222 self.type_check(name, value, item, args)
224 except GeniInvalidArgument, fault:
228 # If an authentication structure is expected, save it and
229 # authenticate after basic type checking is done.
230 #if isinstance(expected, Auth):
235 # Get actual expected type from within the Parameter structure
236 if isinstance(expected, Parameter):
239 nullok = expected.nullok
240 expected = expected.type
246 expected_type = python_type(expected)
248 # If value can be NULL
249 if value is None and nullok:
252 # Strings are a special case. Accept either unicode or str
253 # types if a string is expected.
254 if expected_type in StringTypes and isinstance(value, StringTypes):
257 # Integers and long integers are also special types. Accept
258 # either int or long types if an int or long is expected.
259 elif expected_type in (IntType, LongType) and isinstance(value, (IntType, LongType)):
262 elif not isinstance(value, expected_type):
263 raise GeniInvalidArgument("expected %s, got %s" % \
264 (xmlrpc_type(expected_type),
265 xmlrpc_type(type(value))),
268 # If a minimum or maximum (length, value) has been specified
269 if expected_type in StringTypes:
270 if min is not None and \
271 len(value.encode(self.api.encoding)) < min:
272 raise GeniInvalidArgument, "%s must be at least %d bytes long" % (name, min)
273 if max is not None and \
274 len(value.encode(self.api.encoding)) > max:
275 raise GeniInvalidArgument, "%s must be at most %d bytes long" % (name, max)
276 elif expected_type in (list, tuple, set):
277 if min is not None and len(value) < min:
278 raise GeniInvalidArgument, "%s must contain at least %d items" % (name, min)
279 if max is not None and len(value) > max:
280 raise GeniInvalidArgument, "%s must contain at most %d items" % (name, max)
282 if min is not None and value < min:
283 raise GeniInvalidArgument, "%s must be > %s" % (name, str(min))
284 if max is not None and value > max:
285 raise GeniInvalidArgument, "%s must be < %s" % (name, str(max))
287 # If a list with particular types of items is expected
288 if isinstance(expected, (list, tuple, set)):
289 for i in range(len(value)):
290 if i >= len(expected):
291 j = len(expected) - 1
294 self.type_check(name + "[]", value[i], expected[j], args)
296 # If a struct with particular (or required) types of items is
298 elif isinstance(expected, dict):
299 for key in value.keys():
301 self.type_check(name + "['%s']" % key, value[key], expected[key], args)
302 for key, subparam in expected.iteritems():
303 if isinstance(subparam, Parameter) and \
304 subparam.optional is not None and \
305 not subparam.optional and key not in value.keys():
306 raise GeniInvalidArgument("'%s' not specified" % key, name)
308 #if auth is not None:
309 # auth.check(self, *args)