From caa190be2fc46b923099cca12b7a841d5a924c7d Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 26 Oct 2012 01:40:39 +0300 Subject: [PATCH 01/10] unify soap tests. --- spyne/test/protocol/test_soap.py | 148 +++++++++++++++++++++++ spyne/test/test_soap.py | 199 ------------------------------- 2 files changed, 148 insertions(+), 199 deletions(-) delete mode 100755 spyne/test/test_soap.py diff --git a/spyne/test/protocol/test_soap.py b/spyne/test/protocol/test_soap.py index 0c18b428c..dbca4fc14 100755 --- a/spyne/test/protocol/test_soap.py +++ b/spyne/test/protocol/test_soap.py @@ -38,11 +38,16 @@ from spyne.protocol.soap import Soap11 from spyne.service import ServiceBase +from spyne.protocol.soap import _from_soap +from spyne.protocol.soap import _parse_xml_string + Application.transport = 'test' + def start_response(code, headers): print(code, headers) + class Address(ComplexModel): __namespace__ = "TestService" @@ -169,5 +174,148 @@ def test_multiple_return(self): self.assertEqual(response_data[1], 'b') self.assertEqual(response_data[2], 'c') + +class TestSoap(unittest.TestCase): + def test_simple_message(self): + m = ComplexModel.produce( + namespace=None, + type_name='myMessage', + members={'s': String, 'i': Integer} + ) + m.resolve_namespace(m, 'test') + + m_inst = m(s="a", i=43) + + e = etree.Element('test') + Soap11().to_parent_element(m, m_inst, m.get_namespace(), e) + e=e[0] + + self.assertEquals(e.tag, '{%s}myMessage' % m.get_namespace()) + + self.assertEquals(e.find('{%s}s' % m.get_namespace()).text, 'a') + self.assertEquals(e.find('{%s}i' % m.get_namespace()).text, '43') + + values = Soap11().from_element(m, e) + + self.assertEquals('a', values.s) + self.assertEquals(43, values.i) + + def test_href(self): + # the template. Start at pos 0, some servers complain if + # xml tag is not in the first line. + envelope_string = ''' + + + + + + + + + + + somemachine + someuser + + + machine2 + user2 + + +''' + + root, xmlids = _parse_xml_string(envelope_string, 'utf8') + header, payload = _from_soap(root, xmlids) + + # quick and dirty test href reconstruction + self.assertEquals(len(payload[0]), 2) + + def test_namespaces(self): + m = ComplexModel.produce( + namespace="some_namespace", + type_name='myMessage', + members={'s': String, 'i': Integer}, + ) + + mi = m() + mi.s = 'a' + + e = etree.Element('test') + Soap11().to_parent_element(m, mi, m.get_namespace(), e) + e=e[0] + + self.assertEquals(e.tag, '{some_namespace}myMessage') + + def test_class_to_parent_element(self): + m = ComplexModel.produce( + namespace=None, + type_name='myMessage', + members={'p': Person} + ) + + m.resolve_namespace(m, "punk") + + m_inst = m() + m_inst.p = Person() + m_inst.p.name = 'steve-o' + m_inst.p.age = 2 + m_inst.p.addresses = [] + + element=etree.Element('test') + Soap11().to_parent_element(m, m_inst, m.get_namespace(), element) + element=element[0] + + self.assertEquals(element.tag, '{%s}myMessage' % m.get_namespace()) + self.assertEquals(element[0].find('{%s}name' % Person.get_namespace()).text, + 'steve-o') + self.assertEquals(element[0].find('{%s}age' % Person.get_namespace()).text, '2') + self.assertEquals( + len(element[0].find('{%s}addresses' % Person.get_namespace())), 0) + + p1 = Soap11().from_element(m,element)[0] + + self.assertEquals(p1.name, m_inst.p.name) + self.assertEquals(p1.age, m_inst.p.age) + self.assertEquals(p1.addresses, []) + + def test_to_parent_element_nested(self): + m = ComplexModel.produce( + namespace=None, + type_name='myMessage', + members={'p':Person} + ) + + m.resolve_namespace(m, "m") + + p = Person() + p.name = 'steve-o' + p.age = 2 + p.addresses = [] + + for i in range(0, 100): + a = Address() + a.street = '123 happy way' + a.zip = i + a.laditude = '45.22' + a.longitude = '444.234' + p.addresses.append(a) + + m_inst = m(p=p) + + element=etree.Element('test') + Soap11().to_parent_element(m, m_inst, m.get_namespace(), element) + element=element[0] + + self.assertEquals('{%s}myMessage' % m.get_namespace(), element.tag) + + addresses = element[0].find('{%s}addresses' % Person.get_namespace()) + self.assertEquals(100, len(addresses)) + self.assertEquals('0', addresses[0].find('{%s}zip' % Address.get_namespace()).text) + if __name__ == '__main__': unittest.main() diff --git a/spyne/test/test_soap.py b/spyne/test/test_soap.py deleted file mode 100755 index 83e6b21c3..000000000 --- a/spyne/test/test_soap.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python -# -# spyne - Copyright (C) Spyne contributors. -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -# - -import unittest - -from lxml import etree - -from spyne.model.complex import ComplexModel - -from spyne.model.complex import Array -from spyne.model.primitive import DateTime -from spyne.model.primitive import Float -from spyne.model.primitive import Integer -from spyne.model.primitive import String - -from spyne.model.complex import ComplexModel as Message -from spyne.protocol.soap import Soap11 -from spyne.protocol.soap import _from_soap -from spyne.protocol.soap import _parse_xml_string - -class Address(ComplexModel): - street = String - city = String - zip = Integer - since = DateTime - laditude = Float - longitude = Float - -Address.resolve_namespace(Address, "punk") - -class Person(ComplexModel): - name = String - birthdate = DateTime - age = Integer - addresses = Array(Address) - titles = Array(String) - -Person.resolve_namespace(Person, "punk") - -class TestSoap(unittest.TestCase): - def test_simple_message(self): - m = Message.produce( - namespace=None, - type_name='myMessage', - members={'s': String, 'i': Integer} - ) - m.resolve_namespace(m, 'test') - - m_inst = m(s="a", i=43) - - e = etree.Element('test') - Soap11().to_parent_element(m, m_inst, m.get_namespace(), e) - e=e[0] - - self.assertEquals(e.tag, '{%s}myMessage' % m.get_namespace()) - - self.assertEquals(e.find('{%s}s' % m.get_namespace()).text, 'a') - self.assertEquals(e.find('{%s}i' % m.get_namespace()).text, '43') - - values = Soap11().from_element(m, e) - - self.assertEquals('a', values.s) - self.assertEquals(43, values.i) - - def test_href(self): - # the template. Start at pos 0, some servers complain if - # xml tag is not in the first line. - envelope_string = ''' - - - - - - - - - - - somemachine - someuser - - - machine2 - user2 - - -''' - - root, xmlids = _parse_xml_string(envelope_string, 'utf8') - header, payload = _from_soap(root, xmlids) - - # quick and dirty test href reconstruction - self.assertEquals(len(payload[0]), 2) - - def test_namespaces(self): - m = Message.produce( - namespace="some_namespace", - type_name='myMessage', - members={'s': String, 'i': Integer}, - ) - - mi = m() - mi.s = 'a' - - e = etree.Element('test') - Soap11().to_parent_element(m, mi, m.get_namespace(), e) - e=e[0] - - self.assertEquals(e.tag, '{some_namespace}myMessage') - - def test_class_to_parent_element(self): - m = Message.produce( - namespace=None, - type_name='myMessage', - members={'p': Person} - ) - - m.resolve_namespace(m, "punk") - - m_inst = m() - m_inst.p = Person() - m_inst.p.name = 'steve-o' - m_inst.p.age = 2 - m_inst.p.addresses = [] - - element=etree.Element('test') - Soap11().to_parent_element(m, m_inst, m.get_namespace(), element) - element=element[0] - - self.assertEquals(element.tag, '{%s}myMessage' % m.get_namespace()) - self.assertEquals(element[0].find('{%s}name' % Person.get_namespace()).text, - 'steve-o') - self.assertEquals(element[0].find('{%s}age' % Person.get_namespace()).text, '2') - self.assertEquals( - len(element[0].find('{%s}addresses' % Person.get_namespace())), 0) - - p1 = Soap11().from_element(m,element)[0] - - self.assertEquals(p1.name, m_inst.p.name) - self.assertEquals(p1.age, m_inst.p.age) - self.assertEquals(p1.addresses, []) - - def test_to_parent_element_nested(self): - m = Message.produce( - namespace=None, - type_name='myMessage', - members={'p':Person} - ) - - m.resolve_namespace(m, "m") - - p = Person() - p.name = 'steve-o' - p.age = 2 - p.addresses = [] - - for i in range(0, 100): - a = Address() - a.street = '123 happy way' - a.zip = i - a.laditude = '45.22' - a.longitude = '444.234' - p.addresses.append(a) - - m_inst = m(p=p) - - element=etree.Element('test') - Soap11().to_parent_element(m, m_inst, m.get_namespace(), element) - element=element[0] - - self.assertEquals('{%s}myMessage' % m.get_namespace(), element.tag) - - addresses = element[0].find('{%s}addresses' % Person.get_namespace()) - self.assertEquals(100, len(addresses)) - self.assertEquals('0', addresses[0].find('{%s}zip' % Address.get_namespace()).text) - -if __name__ == '__main__': - unittest.main() From a6bd5e3ec47ddcd02c15bb61d7311ba5d0126684 Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 26 Oct 2012 02:07:58 +0300 Subject: [PATCH 02/10] fix _body_style='bare' --- spyne/interface/_base.py | 3 +- spyne/protocol/xml/model.py | 4 +- spyne/test/test_service.py | 73 +++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/spyne/interface/_base.py b/spyne/interface/_base.py index e9f150a89..c96acf5e4 100644 --- a/spyne/interface/_base.py +++ b/spyne/interface/_base.py @@ -279,7 +279,8 @@ def get_namespace_prefix(self, ns): def add_class(self, cls): if issubclass(cls, Alias): - if issubclass(cls._target, ComplexModelBase): + t = cls._target + if t is not None and issubclass(t, ComplexModelBase): self.add_class(cls._target) return diff --git a/spyne/protocol/xml/model.py b/spyne/protocol/xml/model.py index 380c9af0e..e4c34ac20 100644 --- a/spyne/protocol/xml/model.py +++ b/spyne/protocol/xml/model.py @@ -188,7 +188,9 @@ def alias_to_parent_element(prot, cls, value, tns, parent_elt, name=None): if name is None: name = cls.get_type_name() - prot.to_parent_element(cls._target, value._target, tns, parent_elt, name) + t = cls._target + if t is not None: + prot.to_parent_element(t, value._target, tns, parent_elt, name) @nillable_element diff --git a/spyne/test/test_service.py b/spyne/test/test_service.py index 515aa5213..36a2b7643 100755 --- a/spyne/test/test_service.py +++ b/spyne/test/test_service.py @@ -1,3 +1,4 @@ +import spyne.const.suffix #!/usr/bin/env python # # spyne - Copyright (C) Spyne contributors. @@ -26,6 +27,7 @@ import spyne.model.primitive from lxml import etree +from StringIO import StringIO from spyne.application import Application from spyne.auxproc.sync import SyncAuxProc @@ -41,21 +43,26 @@ from spyne.server.wsgi import WsgiApplication from spyne.service import ServiceBase + Application.transport = 'test' + def start_response(code, headers): print(code, headers) + class MultipleMethods1(ServiceBase): @srpc(String) def multi(s): return "%r multi 1" % s + class MultipleMethods2(ServiceBase): @srpc(String) def multi(s): return "%r multi 2" % s + class TestMultipleMethods(unittest.TestCase): def test_single_method(self): try: @@ -211,6 +218,7 @@ def some_call(ctx): query = '/senv:Envelope/senv:Header/tns:RespHeader/tns:Elem1/text()' assert len(elt.xpath(query, namespaces=nsmap)) == 0 + class TestNativeTypes(unittest.TestCase): def test_native_types(self): for t in spyne.model.primitive.NATIVE_MAP: @@ -231,5 +239,70 @@ def some_call(ctx, arg): nt, = nt._type_info.values() assert issubclass(nt, spyne.model.primitive.NATIVE_MAP[t]) + +class TestBodyStyle(unittest.TestCase): + def test_bare_empty_output(self): + class SomeService(ServiceBase): + @rpc(String, _body_style='bare') + def some_call(ctx, s): + assert s == 'abc' + + app = Application([SomeService], 'tns', in_protocol=Soap11(), + out_protocol=Soap11(cleanup_namespaces=True)) + + req = """ + + + abc + + + """ + + server = WsgiApplication(app) + resp = etree.fromstring(''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'GET', + 'wsgi.input': StringIO(req) + }, start_response, "http://null"))) + + print etree.tostring(resp, pretty_print=True) + + assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' + assert len(resp[0]) == 0 + + def test_bare_empty_input(self): + class SomeService(ServiceBase): + @rpc(_body_style='bare', _returns=String) + def some_call(ctx): + return 'abc' + + app = Application([SomeService], 'tns', in_protocol=Soap11(), + out_protocol=Soap11(cleanup_namespaces=True)) + + req = """ + + + + + + """ + + server = WsgiApplication(app) + resp = etree.fromstring(''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'GET', + 'wsgi.input': StringIO(req) + }, start_response, "http://null"))) + + print etree.tostring(resp, pretty_print=True) + + assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' + assert resp[0][0].tag == '{tns}some_call' + spyne.const.suffix.RESPONSE_SUFFIX + assert resp[0][0].text == 'abc' + if __name__ == '__main__': unittest.main() From adb0572a531302aad2f3123506b1cbe6c18b9305 Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 26 Oct 2012 03:05:28 +0300 Subject: [PATCH 03/10] ignorable. --- spyne/model/complex.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/spyne/model/complex.py b/spyne/model/complex.py index e6a6c27cc..4164f1dfe 100644 --- a/spyne/model/complex.py +++ b/spyne/model/complex.py @@ -574,13 +574,11 @@ def resolve_namespace(cls, default_ns): def produce(namespace, type_name, members): """Lets you create a class programmatically.""" - cls_dict = {} - - cls_dict['__namespace__'] = namespace - cls_dict['__type_name__'] = type_name - cls_dict['_type_info'] = TypeInfo(members) - - return ComplexModelMeta(type_name, (ComplexModel,), cls_dict) + return ComplexModelMeta(type_name, (ComplexModel,), { + '__namespace__': namespace, + '__type_name__': type_name, + '_type_info': TypeInfo(members), + }) @staticmethod def alias(type_name, namespace, target): From 18a9989c07d45e84dcd08c5c91c7c9d27617a475 Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 26 Oct 2012 03:11:32 +0300 Subject: [PATCH 04/10] add a _body_style='bare' test that fails with complex return types. --- spyne/test/test_service.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/spyne/test/test_service.py b/spyne/test/test_service.py index 36a2b7643..341ec22f5 100755 --- a/spyne/test/test_service.py +++ b/spyne/test/test_service.py @@ -304,5 +304,37 @@ def some_call(ctx): assert resp[0][0].tag == '{tns}some_call' + spyne.const.suffix.RESPONSE_SUFFIX assert resp[0][0].text == 'abc' + def test_bare_complex_output(self): + class SomeService(ServiceBase): + @rpc(_body_style='bare', _returns=Array(String)) + def some_call(ctx): + return ['abc', 'def'] + + app = Application([SomeService], 'tns', in_protocol=Soap11(), + out_protocol=Soap11(cleanup_namespaces=True)) + + req = """ + + + + + + """ + + server = WsgiApplication(app) + resp = etree.fromstring(''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'GET', + 'wsgi.input': StringIO(req) + }, start_response, "http://null"))) + + print etree.tostring(resp, pretty_print=True) + + assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' + assert resp[0][0].tag == '{tns}some_call' + spyne.const.suffix.RESPONSE_SUFFIX + assert resp[0][0].text == 'abc' + if __name__ == '__main__': unittest.main() From 8012f941bff841738aa423dbd1b9b2aa0cde4cf4 Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 26 Oct 2012 12:23:20 +0300 Subject: [PATCH 05/10] minor. --- spyne/decorator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spyne/decorator.py b/spyne/decorator.py index 92c2ea0f8..0339069b2 100644 --- a/spyne/decorator.py +++ b/spyne/decorator.py @@ -59,7 +59,7 @@ def _produce_input_message(f, params, _in_message_name, _in_variable_names, no_c if _in_message_name.startswith("{"): ns = _in_message_name[1:].partition("}")[0] - message=ComplexModel.produce(type_name=_in_message_name, namespace=ns, + message = ComplexModel.produce(type_name=_in_message_name, namespace=ns, members=in_params) message.__namespace__ = ns @@ -122,14 +122,14 @@ def _produce_output_message(f, func_name, kparams): if _out_message_name.startswith("{"): ns = _out_message_name[1:].partition("}")[0] - if _body_style == 'wrapped': + if _body_style == 'bare' and _returns is not None: + message = ComplexModel.alias(_out_message_name, ns, _returns) + else: message = ComplexModel.produce(type_name=_out_message_name, namespace=ns, members=out_params) message.__namespace__ = ns # FIXME: is this necessary? - else: - message = ComplexModel.alias(_out_message_name, ns, _returns) return message From 8e044b551498aa5a526db404189fe299cc72ecbe Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 26 Oct 2012 13:00:27 +0300 Subject: [PATCH 06/10] Actually use the ARRAY_SUFFIX constant. --- spyne/model/complex.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spyne/model/complex.py b/spyne/model/complex.py index 4164f1dfe..760b496a8 100644 --- a/spyne/model/complex.py +++ b/spyne/model/complex.py @@ -37,6 +37,7 @@ from spyne.const import xml_ns as namespace from spyne.const import MAX_STRING_FIELD_LENGTH from spyne.const import MAX_ARRAY_ELEMENT_NUM +from spyne.const.suffix import ARRAY_SUFFIX from spyne.const.suffix import TYPE_SUFFIX from spyne.util import sanitize_args @@ -671,7 +672,8 @@ def __new__(cls, serializer, **kwargs): else: member_name = serializer.get_type_name() if cls.__type_name__ is None: - cls.__type_name__ = '%sArray' % serializer.get_type_name() + cls.__type_name__ = '%s%s' % (serializer.get_type_name(), + ARRAY_SUFFIX) retval.__type_name__ = '%sArray' % member_name retval._type_info = {member_name: serializer} From 16311ea2f234adc8e96df22ecbdabd73f5ebe3d3 Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 26 Oct 2012 13:33:13 +0300 Subject: [PATCH 07/10] make _body_style='bare' work for soap with various type combinations. --- spyne/interface/_base.py | 4 +-- spyne/interface/xml_schema/model.py | 2 +- spyne/model/complex.py | 20 +++++------ spyne/protocol/xml/model.py | 14 ++++++-- spyne/test/test_service.py | 56 ++++++++++++++++++++++++++--- 5 files changed, 76 insertions(+), 20 deletions(-) diff --git a/spyne/interface/_base.py b/spyne/interface/_base.py index c96acf5e4..31bce3877 100644 --- a/spyne/interface/_base.py +++ b/spyne/interface/_base.py @@ -279,9 +279,9 @@ def get_namespace_prefix(self, ns): def add_class(self, cls): if issubclass(cls, Alias): - t = cls._target + t, = cls._type_info.values() if t is not None and issubclass(t, ComplexModelBase): - self.add_class(cls._target) + self.add_class(t) return if self.has_class(cls): diff --git a/spyne/interface/xml_schema/model.py b/spyne/interface/xml_schema/model.py index 6e7ca3f1f..dbc7e196c 100644 --- a/spyne/interface/xml_schema/model.py +++ b/spyne/interface/xml_schema/model.py @@ -164,7 +164,7 @@ def complex_add(document, cls): def alias_add(document, cls): element = etree.Element('{%s}element' % _ns_xsd) element.set('name', cls.get_type_name()) - element.set('type', cls._target.get_type_name_ns(document.interface)) + element.set('type', cls.__alias__.get_type_name_ns(document.interface)) document.add_element(cls, element) diff --git a/spyne/model/complex.py b/spyne/model/complex.py index 760b496a8..b634cb995 100644 --- a/spyne/model/complex.py +++ b/spyne/model/complex.py @@ -589,17 +589,13 @@ def alias(type_name, namespace, target): borrow the target's _type_info. """ - cls_dict = {} + retval = Alias.customize() - cls_dict['__namespace__'] = namespace - cls_dict['__type_name__'] = type_name - cls_dict['_target'] = target + retval.__type_name__ = type_name + retval.__namespace__ = namespace + retval._type_info = {"_target": target} - ti = getattr(target, '_type_info', None) - if ti is not None: - cls_dict['_type_info'] = ti - - return ComplexModelMeta(type_name, (Alias,), cls_dict) + return retval @classmethod def customize(cls, **kwargs): @@ -647,10 +643,11 @@ class ComplexModel(ComplexModelBase): __metaclass__ = ComplexModelMeta -class Array(ComplexModel): +class Array(ComplexModelBase): """This class generates a ComplexModel child that has one attribute that has the same name as the serialized class. It's contained in a Python list. """ + __metaclass__ = ComplexModelMeta def __new__(cls, serializer, **kwargs): retval = cls.customize(**kwargs) @@ -718,9 +715,10 @@ class Iterable(Array): """ -class Alias(ComplexModel): +class Alias(ComplexModelBase): """Different type_name, same _type_info.""" + __metaclass__ = ComplexModelMeta def log_repr(obj, cls=None): """Use this function if you want to echo a ComplexModel subclass. It will diff --git a/spyne/protocol/xml/model.py b/spyne/protocol/xml/model.py index e4c34ac20..5a4546459 100644 --- a/spyne/protocol/xml/model.py +++ b/spyne/protocol/xml/model.py @@ -174,6 +174,7 @@ def get_members_etree(prot, cls, inst, parent): attr_parent = parent.find("{%s}%s"%(cls.__namespace__,a_of)) v.marshall(k,subvalue,attr_parent) + @nillable_value def complex_to_parent_element(prot, cls, value, tns, parent_elt, name=None): if name is None: @@ -188,9 +189,18 @@ def alias_to_parent_element(prot, cls, value, tns, parent_elt, name=None): if name is None: name = cls.get_type_name() - t = cls._target + (k,t), = cls._type_info.items() if t is not None: - prot.to_parent_element(t, value._target, tns, parent_elt, name) + subvalue = getattr(value, k, None) + mo = t.Attributes.max_occurs + + if subvalue is not None and mo > 1: + for sv in subvalue: + prot.to_parent_element(t, sv, tns, parent_elt, name) + + # Don't include empty values for non-nillable optional attributes. + elif subvalue is not None or t.Attributes.min_occurs > 0: + prot.to_parent_element(t, subvalue, tns, parent_elt, name) @nillable_element diff --git a/spyne/test/test_service.py b/spyne/test/test_service.py index 341ec22f5..19c7e692c 100755 --- a/spyne/test/test_service.py +++ b/spyne/test/test_service.py @@ -1,3 +1,4 @@ +from spyne.const.suffix import RESPONSE_SUFFIX import spyne.const.suffix #!/usr/bin/env python # @@ -22,6 +23,9 @@ # Most of the service tests are performed through the interop tests. # +import logging +logging.basicConfig(level=logging.DEBUG) + import unittest import spyne.model.primitive @@ -241,7 +245,7 @@ def some_call(ctx, arg): class TestBodyStyle(unittest.TestCase): - def test_bare_empty_output(self): + def test_soap_bare_empty_output(self): class SomeService(ServiceBase): @rpc(String, _body_style='bare') def some_call(ctx, s): @@ -270,9 +274,11 @@ def some_call(ctx, s): print etree.tostring(resp, pretty_print=True) assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' - assert len(resp[0]) == 0 + assert len(resp[0]) == 1 + assert resp[0][0].tag == '{tns}some_call'+ RESPONSE_SUFFIX + assert len(resp[0][0]) == 0 - def test_bare_empty_input(self): + def test_soap_bare_empty_input(self): class SomeService(ServiceBase): @rpc(_body_style='bare', _returns=String) def some_call(ctx): @@ -304,7 +310,7 @@ def some_call(ctx): assert resp[0][0].tag == '{tns}some_call' + spyne.const.suffix.RESPONSE_SUFFIX assert resp[0][0].text == 'abc' - def test_bare_complex_output(self): + def test_soap_bare_wrapped_array_output(self): class SomeService(ServiceBase): @rpc(_body_style='bare', _returns=Array(String)) def some_call(ctx): @@ -332,9 +338,51 @@ def some_call(ctx): print etree.tostring(resp, pretty_print=True) + assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' + assert resp[0][0].tag == '{tns}some_call' + spyne.const.suffix.RESPONSE_SUFFIX + assert resp[0][0][0].text == 'abc' + assert resp[0][0][1].text == 'def' + + def test_soap_bare_unwrapped_array_output(self): + class SomeService(ServiceBase): + @rpc(_body_style='bare', _returns=String(max_occurs='unbounded')) + def some_call(ctx): + return ['abc', 'def'] + + app = Application([SomeService], 'tns', in_protocol=Soap11(), + out_protocol=Soap11(cleanup_namespaces=True)) + + req = """ + + + + + + """ + + server = WsgiApplication(app) + resp = etree.fromstring(''.join(server({ + 'QUERY_STRING': '', + 'PATH_INFO': '/call', + 'REQUEST_METHOD': 'GET', + 'wsgi.input': StringIO(req) + }, start_response, "http://null"))) + + print etree.tostring(resp, pretty_print=True) + assert resp[0].tag == '{http://schemas.xmlsoap.org/soap/envelope/}Body' assert resp[0][0].tag == '{tns}some_call' + spyne.const.suffix.RESPONSE_SUFFIX assert resp[0][0].text == 'abc' + assert resp[0][1].text == 'def' + + # here's the expected output: + # + # + # abc + # def + # + # if __name__ == '__main__': unittest.main() From dde28355e0224fa73f09c1a675a7eb8c9da24657 Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 26 Oct 2012 14:44:29 +0300 Subject: [PATCH 08/10] minor. --- .../test/interface/{test_bare_interface.py => test_interface.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spyne/test/interface/{test_bare_interface.py => test_interface.py} (100%) diff --git a/spyne/test/interface/test_bare_interface.py b/spyne/test/interface/test_interface.py similarity index 100% rename from spyne/test/interface/test_bare_interface.py rename to spyne/test/interface/test_interface.py From 844d4cd475e7f079d5bc980b05e501679751fb76 Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 26 Oct 2012 14:45:02 +0300 Subject: [PATCH 09/10] remove bare version of the say_hello function from helloworld example for the sake of simplicity. --- examples/helloworld_soap.py | 13 ------------- examples/helloworld_soap_suds_client.py | 3 +-- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/examples/helloworld_soap.py b/examples/helloworld_soap.py index 34e3a26cd..5df7401dc 100755 --- a/examples/helloworld_soap.py +++ b/examples/helloworld_soap.py @@ -77,19 +77,6 @@ def say_hello(name, times): for i in range(times): yield u'Hello, %s' % name - @srpc(Unicode, Integer, _returns=Iterable(Unicode), _soap_body_style='document',_body_style='bare') - def say_hello_2(name, times): - ''' - Docstrings for service methods appear as documentation in the wsdl - what fun - @param name the name to say hello to - @param the number of times to say hello - @return the completed array - ''' - - for i in range(times): - yield u'Hello, %s' % name - if __name__=='__main__': from wsgiref.simple_server import make_server diff --git a/examples/helloworld_soap_suds_client.py b/examples/helloworld_soap_suds_client.py index fe0e2c408..0c43635f8 100755 --- a/examples/helloworld_soap_suds_client.py +++ b/examples/helloworld_soap_suds_client.py @@ -31,7 +31,6 @@ from suds.client import Client -client = Client('http://localhost:7789/?wsdl') +client = Client('http://localhost:7789/?wsdl', cache=None) print client.service.say_hello(u'Jérôme', 5) -print client.service.say_hello2(u'Jérôme', 5) From 1a43a0eda1834da38daee88bda742a7fe3356c79 Mon Sep 17 00:00:00 2001 From: Burak Arslan Date: Fri, 26 Oct 2012 15:33:40 +0300 Subject: [PATCH 10/10] make wsdl generation for Alias work, add relevant tests. --- spyne/interface/_base.py | 6 -- spyne/interface/xml_schema/model.py | 3 +- .../test/interface/wsdl/test_default_wsdl.py | 61 +++++++++++++++---- spyne/test/interop/server/_service.py | 14 +++++ spyne/test/interop/test_suds.py | 32 ++++++++++ 5 files changed, 96 insertions(+), 20 deletions(-) diff --git a/spyne/interface/_base.py b/spyne/interface/_base.py index 31bce3877..f39041c87 100644 --- a/spyne/interface/_base.py +++ b/spyne/interface/_base.py @@ -278,12 +278,6 @@ def get_namespace_prefix(self, ns): return pref def add_class(self, cls): - if issubclass(cls, Alias): - t, = cls._type_info.values() - if t is not None and issubclass(t, ComplexModelBase): - self.add_class(t) - return - if self.has_class(cls): return diff --git a/spyne/interface/xml_schema/model.py b/spyne/interface/xml_schema/model.py index dbc7e196c..cc09de268 100644 --- a/spyne/interface/xml_schema/model.py +++ b/spyne/interface/xml_schema/model.py @@ -162,9 +162,10 @@ def complex_add(document, cls): def alias_add(document, cls): + t, = cls._type_info.values() element = etree.Element('{%s}element' % _ns_xsd) element.set('name', cls.get_type_name()) - element.set('type', cls.__alias__.get_type_name_ns(document.interface)) + element.set('type', t.get_type_name_ns(document.interface)) document.add_element(cls, element) diff --git a/spyne/test/interface/wsdl/test_default_wsdl.py b/spyne/test/interface/wsdl/test_default_wsdl.py index 025e24817..7ca9a0d90 100755 --- a/spyne/test/interface/wsdl/test_default_wsdl.py +++ b/spyne/test/interface/wsdl/test_default_wsdl.py @@ -23,11 +23,16 @@ import unittest +from spyne.application import Application + from spyne.test.interface.wsdl import AppTestWrapper from spyne.test.interface.wsdl import build_app from spyne.test.interface.wsdl.defult_services import TDefaultPortService from spyne.test.interface.wsdl.defult_services import TDefaultPortServiceMultipleMethods +from spyne.const.suffix import RESPONSE_SUFFIX +from spyne.const.suffix import ARRAY_SUFFIX + class TestDefaultWSDLBehavior(unittest.TestCase): def _default_service(self, app_wrapper, service_name): @@ -152,20 +157,50 @@ def test_default_binding_methods(self): ['echo_default_port_service'] ) - def test_default_binding_methods_multiple(self): - app = build_app( - [TDefaultPortServiceMultipleMethods()], - 'DefaultBindingMethodsTns', - 'MultipleDefaultBindMethodsApp' - ) + def test_bare(self): + ns = { + 'wsdl':'http://schemas.xmlsoap.org/wsdl/', + 'xs':'http://www.w3.org/2001/XMLSchema', + } + + from spyne.model.complex import Array + from spyne.model.primitive import String + from spyne.protocol.soap import Soap11 + from spyne.service import ServiceBase + from spyne.decorator import srpc + from spyne.interface.wsdl import Wsdl11 + + from lxml import etree + + class InteropBare(ServiceBase): + @srpc(String, _returns=String, _body_style='bare') + def echo_simple_bare(ss): + return ss + + @srpc(Array(String), _returns=Array(String), _body_style='bare') + def echo_complex_bare(ss): + return ss + + app = Application([InteropBare], tns='tns', + in_protocol=Soap11(), out_protocol=Soap11()) + app.transport = 'None' + + wsdl = Wsdl11(app.interface) + wsdl.build_interface_document('url') + wsdl = etree.fromstring(wsdl.get_interface_document()) + + schema = wsdl.xpath( + '/wsdl:definitions/wsdl:types/xs:schema[@targetNamespace]', + namespaces=ns + ) + assert len(schema[0].xpath( + 'xs:complexType[@name="string%s"]' % ARRAY_SUFFIX, namespaces=ns)) > 0 + elts = schema[0].xpath( + 'xs:element[@name="echo_complex_bare%s"]' % RESPONSE_SUFFIX, namespaces=ns) + + assert len(elts) > 0 + assert elts[0].attrib['type'] == 'tns:stringArray' - wrapper = AppTestWrapper(app) - - self._default_binding_methods( - wrapper, - 3, - ['echo_one', 'echo_two', 'echo_three'] - ) if __name__ == '__main__': unittest.main() diff --git a/spyne/test/interop/server/_service.py b/spyne/test/interop/server/_service.py index ea643f64f..e6e8cc070 100644 --- a/spyne/test/interop/server/_service.py +++ b/spyne/test/interop/server/_service.py @@ -294,6 +294,19 @@ def echo_attachment(a): def echo_attachment_array(aa): return aa +class InteropBare(ServiceBase): + @srpc(String, _returns=String, _body_style='bare') + def echo_simple_bare(ss): + return ss + + @srpc(Array(String), _returns=Array(String), _body_style='bare') + def echo_complex_bare(ss): + return ss + + @srpc(String(max_occurs='unbounded'), _returns=String(max_occurs='unbounded'), _body_style='bare') + def echo_complex_bare_unwrapped_array(ss): + return ss + class InteropException(ServiceBase): @srpc() def python_exception(): @@ -379,4 +392,5 @@ def custom_messages(s): InteropServiceWithHeader, InteropServiceWithComplexHeader, InteropException, + InteropBare, ] diff --git a/spyne/test/interop/test_suds.py b/spyne/test/interop/test_suds.py index 717d47d57..0b1f6c255 100755 --- a/spyne/test/interop/test_suds.py +++ b/spyne/test/interop/test_suds.py @@ -358,5 +358,37 @@ def test_custom_messages(self): assert ret == 'test' + def test_echo_simple_bare(self): + ret = self.client.service.echo_simple_bare("test") + + assert ret == 'test' + + def test_echo_complex_bare(self): + val = ['abc','def'] + ia = self.client.factory.create('stringArray') + ia.string.extend(val) + ret = self.client.service.echo_complex_bare(ia) + + assert ret == val + + def test_echo_complex_bare_unwrapped_array(self): + val = ['abc','def'] + ret = self.client.service.echo_complex_bare_unwrapped_array(val) + + # + # Here's the soap response to this request. + # + # + # + # abc + # def + # + # + + # FIXME: I'm not sure whether this is this the right behavior given the + # above output. + assert ret == val[0] + if __name__ == '__main__': unittest.main()