Reorganise the decorators. expose and validate are now in wsme.rest, and ws.api.signature becomes the raw decorator to declare a function signature. Got rid of the 'pexpose' decorator, which will be replaced later by a better way.
This commit is contained in:
parent
983ba099d7
commit
4747aa82f1
@ -37,6 +37,7 @@ wsme.protocols =
|
||||
packages =
|
||||
wsme
|
||||
wsme.protocols
|
||||
wsme.rest
|
||||
wsme.tests
|
||||
|
||||
extra_files =
|
||||
|
@ -1,8 +1,10 @@
|
||||
from wsme.api import sig, expose, validate
|
||||
from wsme.api import signature
|
||||
from wsme.rest import expose, validate
|
||||
from wsme.root import WSRoot
|
||||
from wsme.types import wsattr, wsproperty, Unset
|
||||
|
||||
__all__ = [
|
||||
'expose', 'validate', 'sig',
|
||||
'expose', 'validate', 'signature',
|
||||
'WSRoot',
|
||||
'wsattr', 'wsproperty', 'Unset']
|
||||
'wsattr', 'wsproperty', 'Unset'
|
||||
]
|
||||
|
143
wsme/api.py
143
wsme/api.py
@ -1,31 +1,6 @@
|
||||
import functools
|
||||
import inspect
|
||||
|
||||
__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
|
||||
|
||||
|
||||
def iswsmefunction(f):
|
||||
return hasattr(f, '_wsme_definition')
|
||||
@ -85,14 +60,6 @@ class FunctionDefinition(object):
|
||||
#: 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
|
||||
#: 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 = None
|
||||
|
||||
@ -121,93 +88,45 @@ class FunctionDefinition(object):
|
||||
for arg in self.arguments:
|
||||
arg.resolve_type(registry)
|
||||
|
||||
|
||||
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, body=None, multiple_expose=False,
|
||||
**options):
|
||||
self.return_type = return_type
|
||||
def set_options(self, body=None, **extra_options):
|
||||
self.body_type = body
|
||||
self.multiple_expose = multiple_expose
|
||||
self.extra_options = extra_options
|
||||
|
||||
def set_arg_types(self, argspec, arg_types):
|
||||
args, varargs, keywords, defaults = argspec
|
||||
if args[0] == 'self':
|
||||
args = args[1:]
|
||||
arg_types = list(arg_types)
|
||||
if self.body_type is not None:
|
||||
arg_types.append(self.body_type)
|
||||
for i, argname in enumerate(args):
|
||||
datatype = arg_types[i]
|
||||
mandatory = defaults is None or i < (len(args) - len(defaults))
|
||||
default = None
|
||||
if not mandatory:
|
||||
default = defaults[i - (len(args) - len(defaults))]
|
||||
self.arguments.append(FunctionArgument(argname, datatype,
|
||||
mandatory, default))
|
||||
|
||||
|
||||
class signature(object):
|
||||
def __init__(self, *types, **options):
|
||||
self.return_type = types[0] if types else None
|
||||
self.arg_types = types[1:] if len(types) > 1 else None
|
||||
self.wrap = options.pop('wrap', False)
|
||||
self.options = options
|
||||
|
||||
def __call__(self, func):
|
||||
if self.multiple_expose:
|
||||
argspec = getargspec(func)
|
||||
if self.wrap:
|
||||
func = wrapfunc(func)
|
||||
fd = FunctionDefinition.get(func)
|
||||
if fd.extra_options is not None:
|
||||
raise ValueError("This function is already exposed")
|
||||
fd.return_type = self.return_type
|
||||
fd.body_type = self.body_type
|
||||
fd.extra_options = self.options
|
||||
fd.set_options(**self.options)
|
||||
if self.arg_types:
|
||||
fd.set_arg_types(argspec, self.arg_types)
|
||||
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):
|
||||
def __init__(self, return_type=None, contenttype=None):
|
||||
self.return_type = return_type
|
||||
self.contenttype = contenttype
|
||||
|
||||
def __call__(self, 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):
|
||||
fd = FunctionDefinition.get(func)
|
||||
args, varargs, keywords, defaults = getargspec(func)
|
||||
if args[0] == 'self':
|
||||
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):
|
||||
datatype = param_types[i]
|
||||
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
|
||||
sig = signature
|
||||
|
@ -51,7 +51,7 @@ def wsexpose(*args, **kwargs):
|
||||
content_type='application/xml',
|
||||
generic=False
|
||||
)
|
||||
sig = wsme.sig(*args, **kwargs)
|
||||
sig = wsme.signature(*args, **kwargs)
|
||||
|
||||
def decorate(f):
|
||||
sig(f)
|
||||
|
77
wsme/rest/__init__.py
Normal file
77
wsme/rest/__init__.py
Normal file
@ -0,0 +1,77 @@
|
||||
import inspect
|
||||
import wsme.api
|
||||
|
||||
APIPATH_MAXLEN = 20
|
||||
|
||||
|
||||
class expose(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.signature = wsme.api.signature(*args, **kwargs)
|
||||
|
||||
def __call__(self, func):
|
||||
return self.signature(func)
|
||||
|
||||
@classmethod
|
||||
def with_method(self, method, *args, **kwargs):
|
||||
kwargs['method'] = method
|
||||
return expose(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get(cls, *args, **kwargs):
|
||||
return expose.with_method('GET', *args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def post(cls, *args, **kwargs):
|
||||
return expose.with_method('POST', *args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def put(cls, *args, **kwargs):
|
||||
return expose.with_method('PUT', *args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, *args, **kwargs):
|
||||
return expose.with_method('DELETE', *args, **kwargs)
|
||||
|
||||
|
||||
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):
|
||||
argspec = wsme.api.getargspec(func)
|
||||
fd = wsme.api.FunctionDefinition.get(func)
|
||||
fd.set_arg_types(argspec, self.param_types)
|
||||
return func
|
||||
|
||||
|
||||
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 wsme.api.iswsmefunction(a):
|
||||
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
|
21
wsme/root.py
21
wsme/root.py
@ -8,9 +8,9 @@ import six
|
||||
|
||||
import webob
|
||||
|
||||
from wsme import exc
|
||||
from wsme.exc import ClientSideError, MissingArgument, UnknownFunction
|
||||
from wsme.protocols import getprotocol
|
||||
from wsme.api import scan_api
|
||||
from wsme.rest import scan_api
|
||||
from wsme import spore
|
||||
import wsme.types
|
||||
|
||||
@ -125,7 +125,7 @@ class WSRoot(object):
|
||||
:rtype: list of (path, :class:`FunctionDefinition`)
|
||||
"""
|
||||
if self._api is None:
|
||||
self._api = [i for i in self._scan_api(self)]
|
||||
self._api = list(self._scan_api(self))
|
||||
for path, fdef in self._api:
|
||||
fdef.resolve_types(self.__registry__)
|
||||
return self._api
|
||||
@ -167,7 +167,7 @@ class WSRoot(object):
|
||||
context.path = protocol.extract_path(context)
|
||||
|
||||
if context.path is None:
|
||||
raise exc.ClientSideError(u(
|
||||
raise ClientSideError(u(
|
||||
'The %s protocol was unable to extract a function '
|
||||
'path from the request') % protocol.name)
|
||||
|
||||
@ -176,7 +176,7 @@ class WSRoot(object):
|
||||
|
||||
for arg in context.funcdef.arguments:
|
||||
if arg.mandatory and arg.name not in kw:
|
||||
raise exc.MissingArgument(arg.name)
|
||||
raise MissingArgument(arg.name)
|
||||
|
||||
txn = self.begin()
|
||||
try:
|
||||
@ -186,9 +186,6 @@ class WSRoot(object):
|
||||
txn.abort()
|
||||
raise
|
||||
|
||||
if context.funcdef.protocol_specific \
|
||||
and context.funcdef.return_type is None:
|
||||
return result
|
||||
else:
|
||||
# TODO make sure result type == a._wsme_definition.return_type
|
||||
return protocol.encode_result(context, result)
|
||||
@ -196,7 +193,7 @@ class WSRoot(object):
|
||||
except Exception:
|
||||
e = sys.exc_info()[1]
|
||||
infos = self._format_exception(sys.exc_info())
|
||||
if isinstance(e, exc.ClientSideError):
|
||||
if isinstance(e, ClientSideError):
|
||||
request.client_errorcount += 1
|
||||
else:
|
||||
request.server_errorcount += 1
|
||||
@ -263,8 +260,6 @@ class WSRoot(object):
|
||||
res.status = 500
|
||||
else:
|
||||
res.status = 200
|
||||
if request.calls[0].funcdef:
|
||||
res_content_type = request.calls[0].funcdef.contenttype
|
||||
else:
|
||||
res.status = protocol.get_response_status(request)
|
||||
res_content_type = protocol.get_response_contenttype(request)
|
||||
@ -308,7 +303,7 @@ class WSRoot(object):
|
||||
break
|
||||
|
||||
if not hasattr(a, '_wsme_definition'):
|
||||
raise exc.UnknownFunction('/'.join(path))
|
||||
raise UnknownFunction('/'.join(path))
|
||||
|
||||
definition = a._wsme_definition
|
||||
|
||||
@ -317,7 +312,7 @@ class WSRoot(object):
|
||||
def _format_exception(self, excinfo):
|
||||
"""Extract informations that can be sent to the client."""
|
||||
error = excinfo[1]
|
||||
if isinstance(error, exc.ClientSideError):
|
||||
if isinstance(error, ClientSideError):
|
||||
r = dict(faultcode="Client",
|
||||
faultstring=error.faultstring)
|
||||
log.warning("Client-side error: %s" % r['faultstring'])
|
||||
|
@ -1,13 +1,13 @@
|
||||
# encoding=utf8
|
||||
|
||||
from six import u, b
|
||||
from six import b
|
||||
import sys
|
||||
|
||||
import unittest
|
||||
import webtest
|
||||
|
||||
from wsme import WSRoot, expose, validate
|
||||
from wsme.api import scan_api, pexpose
|
||||
from wsme.rest import scan_api
|
||||
from wsme.api import FunctionArgument, FunctionDefinition
|
||||
from wsme.types import iscomplex
|
||||
import wsme.types
|
||||
@ -15,41 +15,6 @@ import wsme.types
|
||||
from wsme.tests.test_protocols import DummyProtocol
|
||||
|
||||
|
||||
def test_pexpose():
|
||||
class Proto(DummyProtocol):
|
||||
def extract_path(self, context):
|
||||
if context.request.path.endswith('ufunc'):
|
||||
return ['_protocol', 'dummy', 'ufunc']
|
||||
else:
|
||||
return ['_protocol', 'dummy', 'func']
|
||||
|
||||
@pexpose(None, "text/xml")
|
||||
def func(self):
|
||||
return "<p></p>"
|
||||
|
||||
@pexpose(None, "text/xml")
|
||||
def ufunc(self):
|
||||
return u("<p>\xc3\xa9</p>")
|
||||
|
||||
fd = FunctionDefinition.get(Proto.func)
|
||||
assert fd.return_type is None
|
||||
assert fd.protocol_specific
|
||||
assert fd.contenttype == "text/xml"
|
||||
|
||||
p = Proto()
|
||||
r = WSRoot()
|
||||
r.addprotocol(p)
|
||||
|
||||
app = webtest.TestApp(r.wsgiapp())
|
||||
|
||||
res = app.get('/func')
|
||||
|
||||
assert res.status_int == 200
|
||||
assert res.body == b("<p></p>"), res.body
|
||||
res = app.get('/ufunc')
|
||||
assert res.unicode_body == u("<p>\xc3\xa9</p>"), res.body
|
||||
|
||||
|
||||
class TestController(unittest.TestCase):
|
||||
def test_expose(self):
|
||||
class MyWS(WSRoot):
|
||||
|
@ -1,20 +1,19 @@
|
||||
import base64
|
||||
import datetime
|
||||
import decimal
|
||||
import urllib
|
||||
|
||||
import wsme.tests.protocol
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except:
|
||||
import json
|
||||
import json # noqa
|
||||
|
||||
import wsme.protocols.restjson
|
||||
from wsme.protocols.restjson import fromjson, tojson
|
||||
from wsme.utils import parse_isodatetime, parse_isotime, parse_isodate
|
||||
from wsme.types import isusertype, register_type
|
||||
from wsme.api import expose, validate
|
||||
from wsme.rest import expose, validate
|
||||
|
||||
|
||||
import six
|
||||
@ -23,7 +22,7 @@ from six import b, u
|
||||
if six.PY3:
|
||||
from urllib.parse import urlencode
|
||||
else:
|
||||
from urllib import urlencode
|
||||
from urllib import urlencode # noqa
|
||||
|
||||
|
||||
def prepare_value(value, datatype):
|
||||
|
Loading…
x
Reference in New Issue
Block a user