diff --git a/spyne/interface/xml_schema/defn.py b/spyne/interface/xml_schema/defn.py index a22fb7c54..4e9a06833 100644 --- a/spyne/interface/xml_schema/defn.py +++ b/spyne/interface/xml_schema/defn.py @@ -98,6 +98,10 @@ class Choice(SchemaBase): elements = Element.customize(max_occurs="unbounded", sub_name="element") +class All(SchemaBase): + elements = Element.customize(max_occurs="unbounded", sub_name="element") + + class Sequence(SchemaBase): elements = Element.customize(max_occurs="unbounded", sub_name="element") choices = Choice.customize(max_occurs="unbounded", sub_name="choice") @@ -121,6 +125,7 @@ class ComplexType(SchemaBase): attributes = Attribute.customize(max_occurs="unbounded", sub_name="attribute") choice = Choice + all = All class Include(SchemaBase): diff --git a/spyne/interface/xml_schema/model.py b/spyne/interface/xml_schema/model.py index 6b491baab..a10b878f9 100644 --- a/spyne/interface/xml_schema/model.py +++ b/spyne/interface/xml_schema/model.py @@ -183,6 +183,7 @@ def complex_add(document, cls, tags): deferred = deque() choice_tags = defaultdict(lambda: etree.Element(XSD('choice'))) + all_tags = defaultdict(lambda: etree.Element(XSD('all'))) for k, v in type_info.items(): assert isinstance(k, string_types) @@ -253,14 +254,27 @@ def complex_add(document, cls, tags): doc = etree.SubElement(annotation, XSD('documentation')) doc.text = v_doc_text - if a.xml_choice_group is None: - sequence.append(member) - else: + if all([a.xml_choice_group, a.xml_all_group]): + raise ValueError("Cannot specify xml_choice_group and xml_all_group" + " at the same time. Class: %r. Groups: %r" + % (cls.__name__, + [a.xml_choice_group, a.xml_all_group])) + elif a.xml_choice_group is not None: choice_tags[a.xml_choice_group].append(member) + elif a.xml_all_group is not None: + all_tags[a.xml_all_group].append(member) + else: + sequence.append(member) sequence.extend(choice_tags.values()) - if len(sequence) > 0: + if len(sequence) == 0: + if len(all_tags) > 1: + raise ValueError("Cannot specify more than one xml_all_group" + " at the same time. Class: %r. Groups: %r" + % (cls.__name__, [key for key in all_tags.keys()])) + sequence_parent.extend(all_tags.values()) + else: sequence_parent.append(sequence) _ext_elements = dict() diff --git a/spyne/interface/xml_schema/parser.py b/spyne/interface/xml_schema/parser.py index b0307d87f..f4c61023d 100644 --- a/spyne/interface/xml_schema/parser.py +++ b/spyne/interface/xml_schema/parser.py @@ -478,6 +478,11 @@ def process_element(e): for e in ch.elements: process_element(e) + if c.all is not None: + if c.all.elements is not None: + for e in c.all.elements: + process_element(e) + if c.choice is not None: if c.choice.elements is not None: for e in c.choice.elements: diff --git a/spyne/model/_base.py b/spyne/model/_base.py index 124a4264a..4b298455c 100644 --- a/spyne/model/_base.py +++ b/spyne/model/_base.py @@ -369,7 +369,12 @@ class Attributes(object): xml_choice_group = None """When not None, shares the same tag with fields with the same - xml_choice_group value. + xml_choice_group value. Cannot be used in conjunction with xml_all_group. + """ + + xml_all_group = None + """When not None, shares the same tag with fields with the same + xml_all_group value. Cannot be used in conjunction with xml_choice_group. """ index = None diff --git a/spyne/test/interface/test_xml_schema.py b/spyne/test/interface/test_xml_schema.py index 416353905..86ad930dd 100755 --- a/spyne/test/interface/test_xml_schema.py +++ b/spyne/test/interface/test_xml_schema.py @@ -77,6 +77,89 @@ def wooo(ctx): '/xs:sequence/xs:choice/xs:element[@name="one"]', namespaces={'xs': NS_XSD})) > 0 + def test_all_tag(self): + class SomeObject(ComplexModel): + __namespace__ = "flag_ns" + + usa = Boolean(xml_all_group="flags") + can = Boolean(xml_all_group="flags") + ident = Unicode + + class SomeService(Service): + @rpc(_returns=SomeObject) + def wooo(ctx): + return SomeObject() + + Application([SomeService], + tns='flag.ns', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + + docs = get_schema_documents([SomeObject]) + doc = docs['tns'] + print(etree.tostring(doc, pretty_print=True)) + assert len(doc.xpath('/xs:schema/xs:complexType[@name="SomeObject"]' + '/xs:sequence/xs:element[@name="ident"]', + namespaces={'xs': NS_XSD})) > 0 + assert len(doc.xpath('/xs:schema/xs:complexType[@name="SomeObject"]' + '/xs:any/xs:element[@name="usa"]', + namespaces={'xs': NS_XSD})) == 0 + + def test_all_tag2(self): + class SomeObject(ComplexModel): + __namespace__ = "flag_ns" + + ident = Unicode + usa = Boolean(xml_all_group="flags") + can = Boolean(xml_all_group="flags") + + class SomeService(Service): + @rpc(_returns=SomeObject) + def wooo(ctx): + return SomeObject() + + Application([SomeService], + tns='flag.ns', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + + docs = get_schema_documents([SomeObject]) + doc = docs['tns'] + print(etree.tostring(doc, pretty_print=True)) + assert len(doc.xpath('/xs:schema/xs:complexType[@name="SomeObject"]' + '/xs:sequence/xs:element[@name="ident"]', + namespaces={'xs': NS_XSD})) > 0 + assert len(doc.xpath('/xs:schema/xs:complexType[@name="SomeObject"]' + '/xs:any/xs:element[@name="usa"]', + namespaces={'xs': NS_XSD})) == 0 + + def test_all_tag3(self): + class SomeObject(ComplexModel): + __namespace__ = "flag_ns" + + usa = Boolean(xml_all_group="flags") + can = Boolean(xml_all_group="flags") + + class SomeService(Service): + @rpc(_returns=SomeObject) + def wooo(ctx): + return SomeObject() + + Application([SomeService], + tns='flag.ns', + in_protocol=Soap11(validator='lxml'), + out_protocol=Soap11() + ) + + docs = get_schema_documents([SomeObject]) + doc = docs['tns'] + print(etree.tostring(doc, pretty_print=True)) + assert len(doc.xpath('/xs:schema/xs:complexType[@name="SomeObject"]' + '/xs:any/xs:element[@name="usa"]', + namespaces={'xs': NS_XSD})) > 0 + def test_customized_class_with_empty_subclass(self): class SummaryStatsOfDouble(ComplexModel): _type_info = [('Min', XmlAttribute(Integer, use='required')),