198 lines
5.4 KiB
Python
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
|