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 =
|
packages =
|
||||||
wsme
|
wsme
|
||||||
wsme.protocols
|
wsme.protocols
|
||||||
|
wsme.rest
|
||||||
wsme.tests
|
wsme.tests
|
||||||
|
|
||||||
extra_files =
|
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.root import WSRoot
|
||||||
from wsme.types import wsattr, wsproperty, Unset
|
from wsme.types import wsattr, wsproperty, Unset
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'expose', 'validate', 'sig',
|
'expose', 'validate', 'signature',
|
||||||
'WSRoot',
|
'WSRoot',
|
||||||
'wsattr', 'wsproperty', 'Unset']
|
'wsattr', 'wsproperty', 'Unset'
|
||||||
|
]
|
||||||
|
143
wsme/api.py
143
wsme/api.py
@ -1,31 +1,6 @@
|
|||||||
import functools
|
import functools
|
||||||
import inspect
|
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):
|
def iswsmefunction(f):
|
||||||
return hasattr(f, '_wsme_definition')
|
return hasattr(f, '_wsme_definition')
|
||||||
@ -85,14 +60,6 @@ class FunctionDefinition(object):
|
|||||||
#: If the body carry the datas of a single argument, its type
|
#: If the body carry the datas of a single argument, its type
|
||||||
self.body_type = None
|
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.
|
#: Dictionnary of protocol-specific options.
|
||||||
self.extra_options = None
|
self.extra_options = None
|
||||||
|
|
||||||
@ -121,93 +88,45 @@ class FunctionDefinition(object):
|
|||||||
for arg in self.arguments:
|
for arg in self.arguments:
|
||||||
arg.resolve_type(registry)
|
arg.resolve_type(registry)
|
||||||
|
|
||||||
|
def set_options(self, body=None, **extra_options):
|
||||||
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
|
|
||||||
self.body_type = body
|
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
|
self.options = options
|
||||||
|
|
||||||
def __call__(self, func):
|
def __call__(self, func):
|
||||||
if self.multiple_expose:
|
argspec = getargspec(func)
|
||||||
|
if self.wrap:
|
||||||
func = wrapfunc(func)
|
func = wrapfunc(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.set_options(**self.options)
|
||||||
fd.extra_options = self.options
|
if self.arg_types:
|
||||||
|
fd.set_arg_types(argspec, self.arg_types)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
sig = signature
|
||||||
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
|
|
||||||
|
@ -51,7 +51,7 @@ def wsexpose(*args, **kwargs):
|
|||||||
content_type='application/xml',
|
content_type='application/xml',
|
||||||
generic=False
|
generic=False
|
||||||
)
|
)
|
||||||
sig = wsme.sig(*args, **kwargs)
|
sig = wsme.signature(*args, **kwargs)
|
||||||
|
|
||||||
def decorate(f):
|
def decorate(f):
|
||||||
sig(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
|
import webob
|
||||||
|
|
||||||
from wsme import exc
|
from wsme.exc import ClientSideError, MissingArgument, UnknownFunction
|
||||||
from wsme.protocols import getprotocol
|
from wsme.protocols import getprotocol
|
||||||
from wsme.api import scan_api
|
from wsme.rest import scan_api
|
||||||
from wsme import spore
|
from wsme import spore
|
||||||
import wsme.types
|
import wsme.types
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ class WSRoot(object):
|
|||||||
:rtype: list of (path, :class:`FunctionDefinition`)
|
:rtype: list of (path, :class:`FunctionDefinition`)
|
||||||
"""
|
"""
|
||||||
if self._api is None:
|
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:
|
for path, fdef in self._api:
|
||||||
fdef.resolve_types(self.__registry__)
|
fdef.resolve_types(self.__registry__)
|
||||||
return self._api
|
return self._api
|
||||||
@ -167,7 +167,7 @@ class WSRoot(object):
|
|||||||
context.path = protocol.extract_path(context)
|
context.path = protocol.extract_path(context)
|
||||||
|
|
||||||
if context.path is None:
|
if context.path is None:
|
||||||
raise exc.ClientSideError(u(
|
raise ClientSideError(u(
|
||||||
'The %s protocol was unable to extract a function '
|
'The %s protocol was unable to extract a function '
|
||||||
'path from the request') % protocol.name)
|
'path from the request') % protocol.name)
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ class WSRoot(object):
|
|||||||
|
|
||||||
for arg in context.funcdef.arguments:
|
for arg in context.funcdef.arguments:
|
||||||
if arg.mandatory and arg.name not in kw:
|
if arg.mandatory and arg.name not in kw:
|
||||||
raise exc.MissingArgument(arg.name)
|
raise MissingArgument(arg.name)
|
||||||
|
|
||||||
txn = self.begin()
|
txn = self.begin()
|
||||||
try:
|
try:
|
||||||
@ -186,9 +186,6 @@ class WSRoot(object):
|
|||||||
txn.abort()
|
txn.abort()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if context.funcdef.protocol_specific \
|
|
||||||
and context.funcdef.return_type is None:
|
|
||||||
return result
|
|
||||||
else:
|
else:
|
||||||
# TODO make sure result type == a._wsme_definition.return_type
|
# TODO make sure result type == a._wsme_definition.return_type
|
||||||
return protocol.encode_result(context, result)
|
return protocol.encode_result(context, result)
|
||||||
@ -196,7 +193,7 @@ class WSRoot(object):
|
|||||||
except Exception:
|
except Exception:
|
||||||
e = sys.exc_info()[1]
|
e = sys.exc_info()[1]
|
||||||
infos = self._format_exception(sys.exc_info())
|
infos = self._format_exception(sys.exc_info())
|
||||||
if isinstance(e, exc.ClientSideError):
|
if isinstance(e, ClientSideError):
|
||||||
request.client_errorcount += 1
|
request.client_errorcount += 1
|
||||||
else:
|
else:
|
||||||
request.server_errorcount += 1
|
request.server_errorcount += 1
|
||||||
@ -263,8 +260,6 @@ class WSRoot(object):
|
|||||||
res.status = 500
|
res.status = 500
|
||||||
else:
|
else:
|
||||||
res.status = 200
|
res.status = 200
|
||||||
if request.calls[0].funcdef:
|
|
||||||
res_content_type = request.calls[0].funcdef.contenttype
|
|
||||||
else:
|
else:
|
||||||
res.status = protocol.get_response_status(request)
|
res.status = protocol.get_response_status(request)
|
||||||
res_content_type = protocol.get_response_contenttype(request)
|
res_content_type = protocol.get_response_contenttype(request)
|
||||||
@ -308,7 +303,7 @@ class WSRoot(object):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not hasattr(a, '_wsme_definition'):
|
if not hasattr(a, '_wsme_definition'):
|
||||||
raise exc.UnknownFunction('/'.join(path))
|
raise UnknownFunction('/'.join(path))
|
||||||
|
|
||||||
definition = a._wsme_definition
|
definition = a._wsme_definition
|
||||||
|
|
||||||
@ -317,7 +312,7 @@ class WSRoot(object):
|
|||||||
def _format_exception(self, excinfo):
|
def _format_exception(self, excinfo):
|
||||||
"""Extract informations that can be sent to the client."""
|
"""Extract informations that can be sent to the client."""
|
||||||
error = excinfo[1]
|
error = excinfo[1]
|
||||||
if isinstance(error, exc.ClientSideError):
|
if isinstance(error, ClientSideError):
|
||||||
r = dict(faultcode="Client",
|
r = dict(faultcode="Client",
|
||||||
faultstring=error.faultstring)
|
faultstring=error.faultstring)
|
||||||
log.warning("Client-side error: %s" % r['faultstring'])
|
log.warning("Client-side error: %s" % r['faultstring'])
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# encoding=utf8
|
# encoding=utf8
|
||||||
|
|
||||||
from six import u, b
|
from six import b
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import webtest
|
import webtest
|
||||||
|
|
||||||
from wsme import WSRoot, expose, validate
|
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.api import FunctionArgument, FunctionDefinition
|
||||||
from wsme.types import iscomplex
|
from wsme.types import iscomplex
|
||||||
import wsme.types
|
import wsme.types
|
||||||
@ -15,41 +15,6 @@ import wsme.types
|
|||||||
from wsme.tests.test_protocols import DummyProtocol
|
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):
|
class TestController(unittest.TestCase):
|
||||||
def test_expose(self):
|
def test_expose(self):
|
||||||
class MyWS(WSRoot):
|
class MyWS(WSRoot):
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import urllib
|
|
||||||
|
|
||||||
import wsme.tests.protocol
|
import wsme.tests.protocol
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
except:
|
except:
|
||||||
import json
|
import json # noqa
|
||||||
|
|
||||||
import wsme.protocols.restjson
|
import wsme.protocols.restjson
|
||||||
from wsme.protocols.restjson import fromjson, tojson
|
from wsme.protocols.restjson import fromjson, tojson
|
||||||
from wsme.utils import parse_isodatetime, parse_isotime, parse_isodate
|
from wsme.utils import parse_isodatetime, parse_isotime, parse_isodate
|
||||||
from wsme.types import isusertype, register_type
|
from wsme.types import isusertype, register_type
|
||||||
from wsme.api import expose, validate
|
from wsme.rest import expose, validate
|
||||||
|
|
||||||
|
|
||||||
import six
|
import six
|
||||||
@ -23,7 +22,7 @@ from six import b, u
|
|||||||
if six.PY3:
|
if six.PY3:
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
else:
|
else:
|
||||||
from urllib import urlencode
|
from urllib import urlencode # noqa
|
||||||
|
|
||||||
|
|
||||||
def prepare_value(value, datatype):
|
def prepare_value(value, datatype):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user