Files
deb-python-wsme/wsme/api.py
2011-11-28 18:41:47 +01:00

198 lines
5.4 KiB
Python

import inspect
from wsme.types import register_type
__all__ = ['expose', 'validate']
APIPATH_MAXLEN = 20
def scan_api(controller, path=[]):
"""
Recursively iterate a controller api entries, while setting
their :attr:`FunctionDefinition.path`.
"""
for name in dir(controller):
if name.startswith('_'):
continue
a = getattr(controller, name)
if inspect.ismethod(a):
if hasattr(a, '_wsme_definition'):
yield path + [name], a._wsme_definition
elif inspect.isclass(a):
continue
else:
if len(path) > APIPATH_MAXLEN:
raise ValueError("Path is too long: " + str(path))
for i in scan_api(a, path + [name]):
yield i
class FunctionArgument(object):
"""
An argument definition of an api entry
"""
def __init__(self, name, datatype, mandatory, default):
#: argument name
self.name = name
#: Data type
self.datatype = datatype
#: True if the argument is mandatory
self.mandatory = mandatory
#: Default value if argument is omitted
self.default = default
def funcproxy(func):
"""
A very simple wrapper for exposed function.
It will carry the FunctionDefinition in place of the
decorared function so that a same function can be exposed
several times (for example a parent function can be exposed
in different ways in the children classes).
The returned function also carry a ``_original_func`` attribute
so that it can be inspected if needed.
"""
def newfunc(*args, **kw):
return func(*args, **kw)
newfunc._is_wsme_funcproxy = True
newfunc._original_func = func
return newfunc
def isfuncproxy(func):
"""
Returns True if ``func`` is already a function proxy.
"""
return getattr(func, '_is_wsme_funcproxy', False)
class FunctionDefinition(object):
"""
An api entry definition
"""
def __init__(self, func):
#: Function name
self.name = func.__name__
#: Function documentation
self.doc = func.__doc__
#: Return type
self.return_type = None
#: The function arguments (list of :class:`FunctionArgument`)
self.arguments = []
#: True if this function is exposed by a protocol and not in
#: the api tree, which means it is not part of the api.
self.protocol_specific = False
#: Override the contenttype of the returned value.
#: Make sense only with :attr:`protocol_specific` functions.
self.contenttype = None
#: Dictionnary of protocol-specific options.
self.extra_options = {}
@classmethod
def get(cls, func):
"""
Returns the :class:`FunctionDefinition` of a method.
"""
if not isfuncproxy(func):
fd = FunctionDefinition(func)
func = funcproxy(func)
func._wsme_definition = fd
return func, func._wsme_definition
def get_arg(self, name):
"""
Returns a :class:`FunctionArgument` from its name
"""
for arg in self.arguments:
if arg.name == name:
return arg
return None
class expose(object):
"""
Decorator that expose a function.
:param return_type: Return type of the function
Example::
class MyController(object):
@expose(int)
def getint(self):
return 1
"""
def __init__(self, return_type=None, **options):
self.return_type = return_type
self.options = options
register_type(return_type)
def __call__(self, func):
func, fd = FunctionDefinition.get(func)
if fd.return_type is not None:
raise ValueError("This function is already exposed")
fd.return_type = self.return_type
fd.extra_options = self.options
return func
class pexpose(object):
def __init__(self, return_type=None, contenttype=None):
self.return_type = return_type
self.contenttype = contenttype
register_type(return_type)
def __call__(self, func):
func, fd = FunctionDefinition.get(func)
fd.return_type = self.return_type
fd.protocol_specific = True
fd.contenttype = self.contenttype
return func
class validate(object):
"""
Decorator that define the arguments types of a function.
Example::
class MyController(object):
@expose(str)
@validate(datetime.date, datetime.time)
def format(self, d, t):
return d.isoformat() + ' ' + t.isoformat()
"""
def __init__(self, *param_types):
self.param_types = param_types
def __call__(self, func):
func, fd = FunctionDefinition.get(func)
args, varargs, keywords, defaults = inspect.getargspec(
func._original_func)
if args[0] == 'self':
args = args[1:]
for i, argname in enumerate(args):
datatype = self.param_types[i]
register_type(datatype)
mandatory = defaults is None or i < (len(args) - len(defaults))
default = None
if not mandatory:
default = defaults[i - (len(args) - len(defaults))]
fd.arguments.append(FunctionArgument(argname, datatype,
mandatory, default))
return func