This commit is contained in:
Christophe de Vienne 2012-10-16 13:45:38 +02:00
commit 31b5e0d23d
6 changed files with 145 additions and 45 deletions

View File

@ -1,7 +1,8 @@
from wsme.api import expose, validate from wsme.api import sig, expose, validate
from wsme.root import WSRoot from wsme.root import WSRoot
from wsme.types import wsattr, wsproperty, Unset from wsme.types import wsattr, wsproperty, Unset
__all__ = ['expose', 'validate', __all__ = [
'expose', 'validate', 'sig',
'WSRoot', 'WSRoot',
'wsattr', 'wsproperty', 'Unset'] 'wsattr', 'wsproperty', 'Unset']

View File

@ -26,6 +26,10 @@ def scan_api(controller, path=[]):
yield i yield i
def iswsmefunction(f):
return hasattr(f, '_wsme_definition')
class FunctionArgument(object): class FunctionArgument(object):
""" """
An argument definition of an api entry An argument definition of an api entry
@ -47,34 +51,6 @@ class FunctionArgument(object):
self.datatype = registry.resolve_type(self.datatype) self.datatype = registry.resolve_type(self.datatype)
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
newfunc.__doc__ = func.__doc__
newfunc.__name__ = func.__name__
return newfunc
def isfuncproxy(func):
"""
Returns True if ``func`` is already a function proxy.
"""
return getattr(func, '_is_wsme_funcproxy', False)
class FunctionDefinition(object): class FunctionDefinition(object):
""" """
An api entry definition An api entry definition
@ -92,6 +68,9 @@ class FunctionDefinition(object):
#: The function arguments (list of :class:`FunctionArgument`) #: The function arguments (list of :class:`FunctionArgument`)
self.arguments = [] self.arguments = []
#: If the body carry the datas of a single argument, its type
self.body_type = None
#: True if this function is exposed by a protocol and not in #: True if this function is exposed by a protocol and not in
#: the api tree, which means it is not part of the api. #: the api tree, which means it is not part of the api.
self.protocol_specific = False self.protocol_specific = False
@ -108,12 +87,11 @@ class FunctionDefinition(object):
""" """
Returns the :class:`FunctionDefinition` of a method. Returns the :class:`FunctionDefinition` of a method.
""" """
if not isfuncproxy(func): if not hasattr(func, '_wsme_definition'):
fd = FunctionDefinition(func) fd = FunctionDefinition(func)
func = funcproxy(func)
func._wsme_definition = fd func._wsme_definition = fd
return func, func._wsme_definition return func._wsme_definition
def get_arg(self, name): def get_arg(self, name):
""" """
@ -143,26 +121,39 @@ class expose(object):
def getint(self): def getint(self):
return 1 return 1
""" """
def __init__(self, return_type=None, **options): def __init__(self, return_type=None, body=None, **options):
self.return_type = return_type self.return_type = return_type
self.body_type = body
self.options = options self.options = options
def __call__(self, func): def __call__(self, func):
func, fd = FunctionDefinition.get(func) fd = FunctionDefinition.get(func)
if fd.extra_options is not None: if fd.extra_options is not None:
raise ValueError("This function is already exposed") raise ValueError("This function is already exposed")
fd.return_type = self.return_type fd.return_type = self.return_type
fd.body_type = self.body_type
fd.extra_options = self.options fd.extra_options = self.options
return func return func
class sig(object):
def __init__(self, return_type, *param_types, **options):
self.expose = expose(return_type, **options)
self.validate = validate(*param_types)
def __call__(self, func):
func = self.expose(func)
func = self.validate(func)
return func
class pexpose(object): class pexpose(object):
def __init__(self, return_type=None, contenttype=None): def __init__(self, return_type=None, contenttype=None):
self.return_type = return_type self.return_type = return_type
self.contenttype = contenttype self.contenttype = contenttype
def __call__(self, func): def __call__(self, func):
func, fd = FunctionDefinition.get(func) fd = FunctionDefinition.get(func)
fd.return_type = self.return_type fd.return_type = self.return_type
fd.protocol_specific = True fd.protocol_specific = True
fd.contenttype = self.contenttype fd.contenttype = self.contenttype
@ -186,13 +177,15 @@ class validate(object):
self.param_types = param_types self.param_types = param_types
def __call__(self, func): def __call__(self, func):
func, fd = FunctionDefinition.get(func) fd = FunctionDefinition.get(func)
args, varargs, keywords, defaults = inspect.getargspec( args, varargs, keywords, defaults = inspect.getargspec(func)
func._original_func)
if args[0] == 'self': if args[0] == 'self':
args = args[1:] args = args[1:]
param_types = list(self.param_types)
if fd.body_type is not None:
param_types.append(fd.body_type)
for i, argname in enumerate(args): for i, argname in enumerate(args):
datatype = self.param_types[i] datatype = param_types[i]
mandatory = defaults is None or i < (len(args) - len(defaults)) mandatory = defaults is None or i < (len(args) - len(defaults))
default = None default = None
if not mandatory: if not mandatory:

76
wsme/pecan.py Normal file
View File

@ -0,0 +1,76 @@
import inspect
import sys
import json
import xml.etree.ElementTree as et
import wsme
import wsme.protocols.commons
import wsme.protocols.restjson
import wsme.protocols.restxml
pecan = sys.modules['pecan']
class JSonRenderer(object):
def __init__(self, path, extra_vars):
pass
def render(self, template_path, namespace):
data = wsme.protocols.restjson.tojson(
namespace['datatype'],
namespace['result']
)
return json.dumps(data)
class XMLRenderer(object):
def __init__(self, path, extra_vars):
pass
def render(self, template_path, namespace):
data = wsme.protocols.restxml.toxml(
namespace['datatype'],
'result',
namespace['result']
)
return et.tostring(data)
pecan.templating._builtin_renderers['wsmejson'] = JSonRenderer
pecan.templating._builtin_renderers['wsmexml'] = XMLRenderer
def wsexpose(*args, **kwargs):
pecan_json_decorate = pecan.expose(
template='wsmejson:',
content_type='application/json',
generic=False)
pecan_xml_decorate = pecan.expose(
template='wsmexml:',
content_type='application/xml',
generic=False
)
sig = wsme.sig(*args, **kwargs)
def decorate(f):
sig(f)
funcdef = wsme.api.FunctionDefinition.get(f)
def callfunction(self, *args, **kwargs):
args, kwargs = wsme.protocols.commons.get_args(
funcdef, args, kwargs,
pecan.request.body, pecan.request.content_type
)
result = f(self, *args, **kwargs)
return dict(
datatype=funcdef.return_type,
result=result
)
pecan_json_decorate(callfunction)
pecan_xml_decorate(callfunction)
pecan.util._cfg(callfunction)['argspec'] = inspect.getargspec(f)
return callfunction
return decorate

View File

@ -8,7 +8,6 @@ from wsme.types import iscomplex, list_attributes, Unset
from wsme.types import UserType, ArrayType, DictType, File from wsme.types import UserType, ArrayType, DictType, File
from wsme.utils import parse_isodate, parse_isotime, parse_isodatetime from wsme.utils import parse_isodate, parse_isotime, parse_isodatetime
ARRAY_MAX_SIZE = 1000 ARRAY_MAX_SIZE = 1000
@ -112,3 +111,35 @@ def dict_from_params(datatype, params, path, hit_paths):
(key, from_params(datatype.value_type, (key, from_params(datatype.value_type,
params, '%s[%s]' % (path, key), hit_paths)) params, '%s[%s]' % (path, key), hit_paths))
for key in keys)) for key in keys))
def get_args(funcdef, args, kwargs, body, mimetype):
from wsme.protocols import restjson
from wsme.protocols import restxml
newargs = []
for argdef, arg in zip(funcdef.arguments[:len(args)], args):
newargs.append(from_param(argdef.datatype, arg))
newkwargs = {}
for argname, value in kwargs.items():
newkwargs[argname] = from_param(funcdef.get_arg(argname), value)
if funcdef.body_type is not None:
bodydata = None
if mimetype in restjson.RestJsonProtocol.content_types:
if hasattr(body, 'read'):
jsonbody = restjson.json.load(body)
else:
jsonbody = restjson.json.loads(body)
bodydata = restjson.fromjson(funcdef.body_type, jsonbody)
elif mimetype in restxml.RestXmlProtocol.content_types:
if hasattr(body, 'read'):
xmlbody = restxml.et.parse(body)
else:
xmlbody = restxml.et.fromstring(body)
bodydata = restxml.fromxml(funcdef.body_type, xmlbody)
if bodydata:
if len(newargs) < len(funcdef.arguments):
newkwargs[funcdef.arguments[-1].name] = bodydata
else:
newargs[-1] = bodydata
return newargs, newkwargs

View File

@ -314,13 +314,12 @@ class FunctionDocumenter(autodoc.MethodDocumenter):
@classmethod @classmethod
def can_document_member(cls, member, membername, isattr, parent): def can_document_member(cls, member, membername, isattr, parent):
return isinstance(parent, ServiceDocumenter) \ return isinstance(parent, ServiceDocumenter) \
and wsme.api.isfuncproxy(member) and wsme.api.iswsmefunction(member)
def import_object(self): def import_object(self):
ret = super(FunctionDocumenter, self).import_object() ret = super(FunctionDocumenter, self).import_object()
self.directivetype = 'function' self.directivetype = 'function'
self.object, self.wsme_fd = \ self.wsme_fd = wsme.api.FunctionDefinition.get(self.object)
wsme.api.FunctionDefinition.get(self.object)
self.retann = self.wsme_fd.return_type.__name__ self.retann = self.wsme_fd.return_type.__name__
return ret return ret

View File

@ -31,7 +31,7 @@ def test_pexpose():
def ufunc(self): def ufunc(self):
return u("<p>\xc3\xa9</p>") return u("<p>\xc3\xa9</p>")
func, fd = FunctionDefinition.get(Proto.func) fd = FunctionDefinition.get(Proto.func)
assert fd.return_type is None assert fd.return_type is None
assert fd.protocol_specific assert fd.protocol_specific
assert fd.contenttype == "text/xml" assert fd.contenttype == "text/xml"