Structured types basically work with rest+json
This commit is contained in:
parent
8fe674922f
commit
ee40938d64
@ -3,6 +3,8 @@ syntax: glob
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
*.swp
|
||||||
|
~*
|
||||||
.coverage
|
.coverage
|
||||||
|
|
||||||
syntax: regexp
|
syntax: regexp
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
from controller import *
|
from controller import expose, validate, WSRoot
|
||||||
|
from types import wsattr, wsproperty
|
||||||
|
|
||||||
import restjson
|
import restjson
|
||||||
|
@ -3,6 +3,7 @@ import traceback
|
|||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
from wsme import exc
|
from wsme import exc
|
||||||
|
from wsme.types import register_type
|
||||||
|
|
||||||
__all__ = ['expose', 'validate', 'WSRoot']
|
__all__ = ['expose', 'validate', 'WSRoot']
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ def register_protocol(protocol):
|
|||||||
class expose(object):
|
class expose(object):
|
||||||
def __init__(self, return_type=None):
|
def __init__(self, return_type=None):
|
||||||
self.return_type = return_type
|
self.return_type = return_type
|
||||||
|
register_type(return_type)
|
||||||
|
|
||||||
def __call__(self, func):
|
def __call__(self, func):
|
||||||
fd = FunctionDefinition.get(func)
|
fd = FunctionDefinition.get(func)
|
||||||
|
@ -13,10 +13,10 @@ except ImportError:
|
|||||||
def prepare_encode(value, datatype):
|
def prepare_encode(value, datatype):
|
||||||
if datatype in wsme.types.pod_types:
|
if datatype in wsme.types.pod_types:
|
||||||
return value
|
return value
|
||||||
if datatype in wsme.types.structured_types:
|
if wsme.types.isstructured(datatype):
|
||||||
d = dict()
|
d = dict()
|
||||||
for name, datatype, mandatory in wsme.types.list_attributes(datatype):
|
for name, attr in wsme.types.list_attributes(datatype):
|
||||||
d[name] = prepare_encode(getattr(value, name), datatype)
|
d[name] = prepare_encode(getattr(value, name), attr.datatype)
|
||||||
return d
|
return d
|
||||||
if datatype in wsme.types.dt_types:
|
if datatype in wsme.types.dt_types:
|
||||||
return value.isoformat()
|
return value.isoformat()
|
||||||
|
@ -12,6 +12,7 @@ from wsme import *
|
|||||||
|
|
||||||
warnings.filterwarnings('ignore', module='webob.dec')
|
warnings.filterwarnings('ignore', module='webob.dec')
|
||||||
|
|
||||||
|
|
||||||
class CallException(RuntimeError):
|
class CallException(RuntimeError):
|
||||||
def __init__(self, faultcode, faultstring, debuginfo):
|
def __init__(self, faultcode, faultstring, debuginfo):
|
||||||
self.faultcode = faultcode
|
self.faultcode = faultcode
|
||||||
@ -22,6 +23,21 @@ class CallException(RuntimeError):
|
|||||||
return 'faultcode=%s, faultstring=%s, debuginfo=%s' % (
|
return 'faultcode=%s, faultstring=%s, debuginfo=%s' % (
|
||||||
self.faultcode, self.faultstring, self.debuginfo)
|
self.faultcode, self.faultstring, self.debuginfo)
|
||||||
|
|
||||||
|
|
||||||
|
class NestedInner(object):
|
||||||
|
aint = int
|
||||||
|
|
||||||
|
def __init__(self, aint=None):
|
||||||
|
self.aint = aint
|
||||||
|
|
||||||
|
|
||||||
|
class NestedOuter(object):
|
||||||
|
inner = NestedInner
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.inner = NestedInner(0)
|
||||||
|
|
||||||
|
|
||||||
class ReturnTypes(object):
|
class ReturnTypes(object):
|
||||||
@expose(str)
|
@expose(str)
|
||||||
def getstr(self):
|
def getstr(self):
|
||||||
@ -55,12 +71,18 @@ class ReturnTypes(object):
|
|||||||
def getdate(self):
|
def getdate(self):
|
||||||
return datetime.datetime(1994, 1, 26, 12, 0, 0)
|
return datetime.datetime(1994, 1, 26, 12, 0, 0)
|
||||||
|
|
||||||
|
@expose(NestedOuter)
|
||||||
|
def getnested(self):
|
||||||
|
n = NestedOuter()
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
class WithErrors(object):
|
class WithErrors(object):
|
||||||
@expose()
|
@expose()
|
||||||
def divide_by_zero(self):
|
def divide_by_zero(self):
|
||||||
1 / 0
|
1 / 0
|
||||||
|
|
||||||
|
|
||||||
class WSTestRoot(WSRoot):
|
class WSTestRoot(WSRoot):
|
||||||
returntypes = ReturnTypes()
|
returntypes = ReturnTypes()
|
||||||
witherrors = WithErrors()
|
witherrors = WithErrors()
|
||||||
@ -116,4 +138,6 @@ class ProtocolTestCase(unittest.TestCase):
|
|||||||
r = self.call('returntypes/getfloat')
|
r = self.call('returntypes/getfloat')
|
||||||
assert r == 3.14159265, r
|
assert r == 3.14159265, r
|
||||||
|
|
||||||
|
def test_return_nested(self):
|
||||||
|
r = self.call('returntypes/getnested')
|
||||||
|
assert r == {'inner': {'aint': 0}}, r
|
||||||
|
@ -17,6 +17,7 @@ class TestRestJson(wsme.tests.protocol.ProtocolTestCase):
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
expect_errors=True)
|
expect_errors=True)
|
||||||
|
print "Received:", res.body
|
||||||
r = json.loads(res.body)
|
r = json.loads(res.body)
|
||||||
if 'result' in r:
|
if 'result' in r:
|
||||||
return r['result']
|
return r['result']
|
||||||
@ -25,6 +26,5 @@ class TestRestJson(wsme.tests.protocol.ProtocolTestCase):
|
|||||||
r['faultcode'],
|
r['faultcode'],
|
||||||
r['faultstring'],
|
r['faultstring'],
|
||||||
r.get('debuginfo'))
|
r.get('debuginfo'))
|
||||||
|
|
||||||
return json.loads(res.body)
|
|
||||||
|
|
||||||
|
return json.loads(res.body)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from wsme import types
|
from wsme import types
|
||||||
|
|
||||||
|
|
||||||
class TestTypes(unittest.TestCase):
|
class TestTypes(unittest.TestCase):
|
||||||
def test_flat_type(self):
|
def test_flat_type(self):
|
||||||
class Flat(object):
|
class Flat(object):
|
||||||
@ -12,12 +13,58 @@ class TestTypes(unittest.TestCase):
|
|||||||
types.register_type(Flat)
|
types.register_type(Flat)
|
||||||
|
|
||||||
assert len(Flat._wsme_attributes) == 4
|
assert len(Flat._wsme_attributes) == 4
|
||||||
|
attrs = Flat._wsme_attributes
|
||||||
|
print attrs
|
||||||
|
|
||||||
|
assert attrs[0][0] == 'aint'
|
||||||
|
assert isinstance(attrs[0][1], types.wsattr)
|
||||||
|
assert attrs[0][1].datatype == int
|
||||||
|
assert attrs[0][1].mandatory == False
|
||||||
|
assert attrs[1][0] == 'astr'
|
||||||
|
assert attrs[2][0] == 'auni'
|
||||||
|
assert attrs[3][0] == 'afloat'
|
||||||
|
|
||||||
def test_private_attr(self):
|
def test_private_attr(self):
|
||||||
class WithPrivateAttrs(object):
|
class WithPrivateAttrs(object):
|
||||||
_private = 12
|
_private = 12
|
||||||
|
|
||||||
types.register_type(WithPrivateAttrs)
|
types.register_type(WithPrivateAttrs)
|
||||||
|
|
||||||
assert len(WithPrivateAttrs._wsme_attributes) == 0
|
assert len(WithPrivateAttrs._wsme_attributes) == 0
|
||||||
|
|
||||||
|
def test_wsproperty(self):
|
||||||
|
class WithWSProp(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._aint = 0
|
||||||
|
|
||||||
|
def get_aint(self):
|
||||||
|
return self._aint
|
||||||
|
|
||||||
|
def set_aint(self, value):
|
||||||
|
self._aint = value
|
||||||
|
|
||||||
|
aint = types.wsproperty(int, get_aint, set_aint, mandatory=True)
|
||||||
|
|
||||||
|
types.register_type(WithWSProp)
|
||||||
|
|
||||||
|
assert len(WithWSProp._wsme_attributes) == 1
|
||||||
|
a = WithWSProp._wsme_attributes[0][1]
|
||||||
|
assert a.datatype == int
|
||||||
|
assert a.mandatory
|
||||||
|
|
||||||
|
o = WithWSProp()
|
||||||
|
o.aint = 12
|
||||||
|
|
||||||
|
assert o.aint == 12
|
||||||
|
|
||||||
|
def test_nested(self):
|
||||||
|
class Inner(object):
|
||||||
|
aint = int
|
||||||
|
|
||||||
|
class Outer(object):
|
||||||
|
inner = Inner
|
||||||
|
|
||||||
|
types.register_type(Outer)
|
||||||
|
|
||||||
|
assert hasattr(Inner, '_wsme_attributes')
|
||||||
|
assert len(Inner._wsme_attributes) == 1
|
||||||
|
@ -13,10 +13,15 @@ native_types = pod_types + dt_types + extra_types
|
|||||||
structured_types = []
|
structured_types = []
|
||||||
|
|
||||||
|
|
||||||
|
def isstructured(datatype):
|
||||||
|
return hasattr(datatype, '_wsme_attributes')
|
||||||
|
|
||||||
|
|
||||||
class wsproperty(property):
|
class wsproperty(property):
|
||||||
def __init__(self, datatype, fget, fset=None,
|
def __init__(self, datatype, fget, fset=None,
|
||||||
mandatory=False, doc=None):
|
mandatory=False, doc=None):
|
||||||
property.__init__(self, fget, fset, doc)
|
property.__init__(self, fget, fset)
|
||||||
|
self.datatype = datatype
|
||||||
self.mandatory = mandatory
|
self.mandatory = mandatory
|
||||||
|
|
||||||
|
|
||||||
@ -26,32 +31,81 @@ class wsattr(object):
|
|||||||
self.mandatory = mandatory
|
self.mandatory = mandatory
|
||||||
|
|
||||||
|
|
||||||
|
def iswsattr(attr):
|
||||||
|
if inspect.isfunction(attr) or inspect.ismethod(attr):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def sort_attributes(class_, attributes):
|
||||||
|
"""Sort a class attributes list.
|
||||||
|
|
||||||
|
3 mechanisms are attempted :
|
||||||
|
|
||||||
|
#. Look for a _wsme_attr_order attribute on the class_. This allow
|
||||||
|
to define an arbitrary order of the attributes (usefull for
|
||||||
|
generated types).
|
||||||
|
|
||||||
|
#. Access the object source code to find the declaration order.
|
||||||
|
|
||||||
|
#. Sort by alphabetically"""
|
||||||
|
|
||||||
|
attrs = dict(attributes)
|
||||||
|
if hasattr(class_, '_wsme_attr_order'):
|
||||||
|
names_order = class_._wsme_attr_order
|
||||||
|
else:
|
||||||
|
names = attrs.keys()
|
||||||
|
names_order = []
|
||||||
|
try:
|
||||||
|
lines = []
|
||||||
|
for line in inspect.getsourcelines(class_)[0]:
|
||||||
|
line = line.strip().replace(" ", "")
|
||||||
|
if '=' in line:
|
||||||
|
aname = line[:line.index('=')]
|
||||||
|
if aname in names and aname not in names_order:
|
||||||
|
names_order.append(aname)
|
||||||
|
assert len(names_order) == len(names)
|
||||||
|
except IOError, e:
|
||||||
|
names_order = list(names)
|
||||||
|
names_order.sort()
|
||||||
|
|
||||||
|
attributes[:] = [(name, attrs[name]) for name in names_order]
|
||||||
|
|
||||||
|
|
||||||
def inspect_class(class_):
|
def inspect_class(class_):
|
||||||
|
"""Extract a list of (name, wsattr|wsproperty) for the given class_"""
|
||||||
attributes = []
|
attributes = []
|
||||||
for name in dir(class_):
|
for name, attr in inspect.getmembers(class_, iswsattr):
|
||||||
if name.startswith('_'):
|
if name.startswith('_'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
attr = getattr(class_, name)
|
|
||||||
if inspect.isfunction(attr):
|
|
||||||
continue
|
|
||||||
if inspect.ismethod(attr):
|
|
||||||
continue
|
|
||||||
if not isinstance(attr, wsattr):
|
|
||||||
attrdef = wsattr(attr)
|
|
||||||
else:
|
|
||||||
attrdef = attr
|
|
||||||
|
|
||||||
attributes.append((name, wsattr))
|
if isinstance(attr, wsattr):
|
||||||
|
attrdef = attr
|
||||||
|
elif isinstance(attr, wsproperty):
|
||||||
|
attrdef = attr
|
||||||
|
else:
|
||||||
|
if attr not in native_types and inspect.isclass(attr):
|
||||||
|
print name, attr
|
||||||
|
register_type(attr)
|
||||||
|
attrdef = wsattr(attr)
|
||||||
|
|
||||||
|
attributes.append((name, attrdef))
|
||||||
|
sort_attributes(class_, attributes)
|
||||||
return attributes
|
return attributes
|
||||||
|
|
||||||
|
|
||||||
def register_type(class_):
|
def register_type(class_):
|
||||||
if hasattr(class_, '_wsme_attributes'):
|
if class_ is None or \
|
||||||
|
class_ in native_types or \
|
||||||
|
hasattr(class_, '_wsme_attributes'):
|
||||||
return
|
return
|
||||||
|
|
||||||
class_._wsme_attributes = inspect_class(class_)
|
class_._wsme_attributes = inspect_class(class_)
|
||||||
|
|
||||||
structured_types.append(weakref.ref(class_))
|
structured_types.append(weakref.ref(class_))
|
||||||
|
|
||||||
def list_attributes(class_):
|
|
||||||
return class_._wsme_attributes
|
|
||||||
|
|
||||||
|
def list_attributes(class_):
|
||||||
|
if not hasattr(class_, '_wsme_attributes'):
|
||||||
|
register_type(class_)
|
||||||
|
return class_._wsme_attributes
|
||||||
|
Loading…
x
Reference in New Issue
Block a user