From ee40938d6450a7925528d0969c24ef327541d2df Mon Sep 17 00:00:00 2001 From: Christophe de Vienne Date: Tue, 20 Sep 2011 12:28:15 +0200 Subject: [PATCH] Structured types basically work with rest+json --- .hgignore | 2 + wsme/__init__.py | 4 +- wsme/controller.py | 2 + wsme/restjson.py | 6 +-- wsme/tests/protocol.py | 26 ++++++++++- wsme/tests/test_restjson.py | 4 +- wsme/tests/test_types.py | 49 ++++++++++++++++++++- wsme/types.py | 86 ++++++++++++++++++++++++++++++------- 8 files changed, 155 insertions(+), 24 deletions(-) diff --git a/.hgignore b/.hgignore index 2d5b9aa..5e3e5a7 100644 --- a/.hgignore +++ b/.hgignore @@ -3,6 +3,8 @@ syntax: glob *.pyc *.pyo *.egg-info +*.swp +~* .coverage syntax: regexp diff --git a/wsme/__init__.py b/wsme/__init__.py index 31b424a..8fc3479 100644 --- a/wsme/__init__.py +++ b/wsme/__init__.py @@ -1,2 +1,4 @@ -from controller import * +from controller import expose, validate, WSRoot +from types import wsattr, wsproperty + import restjson diff --git a/wsme/controller.py b/wsme/controller.py index 5ab4dd2..650ed01 100644 --- a/wsme/controller.py +++ b/wsme/controller.py @@ -3,6 +3,7 @@ import traceback import weakref from wsme import exc +from wsme.types import register_type __all__ = ['expose', 'validate', 'WSRoot'] @@ -54,6 +55,7 @@ def register_protocol(protocol): class expose(object): def __init__(self, return_type=None): self.return_type = return_type + register_type(return_type) def __call__(self, func): fd = FunctionDefinition.get(func) diff --git a/wsme/restjson.py b/wsme/restjson.py index db47226..3cc0887 100644 --- a/wsme/restjson.py +++ b/wsme/restjson.py @@ -13,10 +13,10 @@ except ImportError: def prepare_encode(value, datatype): if datatype in wsme.types.pod_types: return value - if datatype in wsme.types.structured_types: + if wsme.types.isstructured(datatype): d = dict() - for name, datatype, mandatory in wsme.types.list_attributes(datatype): - d[name] = prepare_encode(getattr(value, name), datatype) + for name, attr in wsme.types.list_attributes(datatype): + d[name] = prepare_encode(getattr(value, name), attr.datatype) return d if datatype in wsme.types.dt_types: return value.isoformat() diff --git a/wsme/tests/protocol.py b/wsme/tests/protocol.py index 69f48aa..273cf5b 100644 --- a/wsme/tests/protocol.py +++ b/wsme/tests/protocol.py @@ -12,6 +12,7 @@ from wsme import * warnings.filterwarnings('ignore', module='webob.dec') + class CallException(RuntimeError): def __init__(self, faultcode, faultstring, debuginfo): self.faultcode = faultcode @@ -22,6 +23,21 @@ class CallException(RuntimeError): return 'faultcode=%s, faultstring=%s, debuginfo=%s' % ( 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): @expose(str) def getstr(self): @@ -55,12 +71,18 @@ class ReturnTypes(object): def getdate(self): return datetime.datetime(1994, 1, 26, 12, 0, 0) + @expose(NestedOuter) + def getnested(self): + n = NestedOuter() + return n + class WithErrors(object): @expose() def divide_by_zero(self): 1 / 0 + class WSTestRoot(WSRoot): returntypes = ReturnTypes() witherrors = WithErrors() @@ -116,4 +138,6 @@ class ProtocolTestCase(unittest.TestCase): r = self.call('returntypes/getfloat') assert r == 3.14159265, r - + def test_return_nested(self): + r = self.call('returntypes/getnested') + assert r == {'inner': {'aint': 0}}, r diff --git a/wsme/tests/test_restjson.py b/wsme/tests/test_restjson.py index 0ac1dc8..8a14fe6 100644 --- a/wsme/tests/test_restjson.py +++ b/wsme/tests/test_restjson.py @@ -17,6 +17,7 @@ class TestRestJson(wsme.tests.protocol.ProtocolTestCase): 'Content-Type': 'application/json', }, expect_errors=True) + print "Received:", res.body r = json.loads(res.body) if 'result' in r: return r['result'] @@ -25,6 +26,5 @@ class TestRestJson(wsme.tests.protocol.ProtocolTestCase): r['faultcode'], r['faultstring'], r.get('debuginfo')) - - return json.loads(res.body) + return json.loads(res.body) diff --git a/wsme/tests/test_types.py b/wsme/tests/test_types.py index 9a46fd7..87d66bc 100644 --- a/wsme/tests/test_types.py +++ b/wsme/tests/test_types.py @@ -1,6 +1,7 @@ import unittest from wsme import types + class TestTypes(unittest.TestCase): def test_flat_type(self): class Flat(object): @@ -12,12 +13,58 @@ class TestTypes(unittest.TestCase): types.register_type(Flat) 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): class WithPrivateAttrs(object): _private = 12 - + types.register_type(WithPrivateAttrs) 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 diff --git a/wsme/types.py b/wsme/types.py index 7da1ab7..d83d5f5 100644 --- a/wsme/types.py +++ b/wsme/types.py @@ -13,10 +13,15 @@ native_types = pod_types + dt_types + extra_types structured_types = [] +def isstructured(datatype): + return hasattr(datatype, '_wsme_attributes') + + class wsproperty(property): def __init__(self, datatype, fget, fset=None, mandatory=False, doc=None): - property.__init__(self, fget, fset, doc) + property.__init__(self, fget, fset) + self.datatype = datatype self.mandatory = mandatory @@ -26,32 +31,81 @@ class wsattr(object): 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_): + """Extract a list of (name, wsattr|wsproperty) for the given class_""" attributes = [] - for name in dir(class_): + for name, attr in inspect.getmembers(class_, iswsattr): if name.startswith('_'): 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 + def register_type(class_): - if hasattr(class_, '_wsme_attributes'): + if class_ is None or \ + class_ in native_types or \ + hasattr(class_, '_wsme_attributes'): return class_._wsme_attributes = inspect_class(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