Search code examples
pythonsoapspyne

Spyne: request where input parameters have different namespaces


I found this question: https://mail.python.org/pipermail/soap/2013-June/001120.html I have the same problem, and can't find an answer. Please help.

I am in the process of implementing some existing WSDL in spyne, and I'm running into a problem where I have requests that contain multiple namespaces. For example, I have a request that looks like:

<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header>
  </soapenv:Header>
  <soapenv:Body>
    <a:Foo xmlns:a="www.example.com/schema/a" AttrA="a1" AttrB="b2">
      <b:Baz xmlns:b="www.example.com/schema/b" AttrC="c3"/>
      <a:Bar>blah</a:Bar>
    </a:Foo>
  </soapenv:Body>
</soapenv:Envelope>

When I send this request, I get the following back:

<?xml version='1.0' encoding='utf-8'?>
<senv:Envelope xmlns:senv="schemas.xmlsoap.org/soap/envelope/">
  <senv:Body>
    <senv:Fault>
      <faultcode>senv:Client.SchemaValidationError</faultcode>
      <faultstring>
      <string>:1:0:ERROR:SCHEMASV:SCHEMAV_ELEMENT_CONTENT:
      Element '{www.example.com/schema/b}Baz': This element
      is not expected. Expected is one of (
      {www.example.com/schema/a}Baz,
      {www.example.com/schema/a}Bar ).</faultstring>
      <faultactor></faultactor>
    </senv:Fault>
  </senv:Body>
</senv:Envelope>

I've been looking at the documentation and setting options, and so far nothing has gotten me past this bump. Is this currently possible with spyne? Do I need to do more and parse the in_document? Any input would be greatly appreciated.

An for more detail the code I've been messing with:

from spyne.model.primitive import Unicode
from spyne.model.complex import Iterable, XmlAttribute, ComplexModel,
ComplexModelMeta, ComplexModelBase
from spyne.service import ServiceBase
from spyne.protocol.soap import Soap11
from spyne.application import Application
from spyne.decorator import srpc, rpc

class BazBase(ComplexModelBase):
    __namespace__ = "www.example.com/schema/b"
    __metaclass__ = ComplexModelMeta

class Baz(BazBase):
    Thing = Unicode
    AttrC = XmlAttribute(Unicode)

class FooService(ServiceBase):
    __namespace__ = "www.example.com/schema/a"

    @rpc(XmlAttribute(Unicode), XmlAttribute(Unicode), Baz, Unicode,
_returns=Iterable(Unicode))
    def Foo(ctx, AttrA, AttrB, Baz, Bar):
        yield 'Hello, %s' % Bar

app = Application([FooService],
    "www.example.com/schema/a",
    in_protocol=Soap11(validator='lxml'),
    out_protocol=Soap11(),
)

Thanks!


Solution

  • So, here's how it's supposed to work:

    from spyne import Unicode, Iterable, XmlAttribute, ComplexModel, \
        ServiceBase, Application, rpc
    from spyne.protocol.soap import Soap11
    
    
    NS_B = "www.example.com/schema/b"
    
    
    class Baz(ComplexModel):
        __namespace__ = NS_B
    
        Thing = Unicode
        AttrC = XmlAttribute(Unicode)
    
    
    class FooCustomRequest(ComplexModel):
        AttrA = XmlAttribute(Unicode)
        AttrB = XmlAttribute(Unicode)
        Bar = Baz.customize(sub_ns=NS_B)
        Baz = Unicode
    
    
    class FooService(ServiceBase):
        @rpc(FooCustomRequest, _returns = Iterable(Unicode),
             _body_style='bare')
        def Foo(ctx, req):
            AttrA, AttrB, Baz, Bar = \
                req.AttrA, req.AttrB, req.Baz, req.Bar
            yield 'Hello, %s' % Bar
    
    
    application = Application([FooService],
        tns="www.example.com/schema/a",
        in_protocol=Soap11(validator='soft'),
        out_protocol=Soap11(),
    )
    

    But it doesn't. This generates the following object definition:

    <xs:complexType name="FooCustomRequest">
        <xs:sequence>
            <xs:element name="Bar" type="s0:Baz" minOccurs="0" nillable="true"/>
            <xs:element name="Baz" type="xs:string" minOccurs="0"
                        nillable="true"/>
        </xs:sequence>
        <xs:attribute name="AttrA" type="xs:string"/>
        <xs:attribute name="AttrB" type="xs:string"/>
    </xs:complexType>
    

    As you see, the sub_ns declaration we make above is ignored by Spyne's schema generator. <Addendum> My Xml is rusty but after further research this seems to be the case by design -- as the name attribute of xs:element can not have a namespace prefix (ie it's a NCName), it's not possible to model the kind of document your client is sending you using the Xml Schema technology and friends. Your best bet at this point is soft validation, if you can't persuade your client to send a "correct" request. </Addendum>

    So validator='lxml' will never accept your document. However, validator='soft' will and you can use it until this bug is fixed in Spyne.

    I can confirm the following request works:

    <SOAP-ENV:Envelope xmlns:b="www.example.com/schema/b"
                       xmlns:a="www.example.com/schema/a"
                       xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
      <SOAP-ENV:Header/>
      <soapenv:Body>
        <a:Foo AttrA="attr-a">
          <b:Bar AttrC="attr-c">
            <b:Thing>thing</b:Thing>
          </b:Bar>
          <a:Baz>baz</a:Baz>
        </a:Foo>
      </soapenv:Body>
    </SOAP-ENV:Envelope>
    

    If you can file an issue at https://github.com/arskom/spyne with the XSD fragment this needs to generate, I can fix it.

    <Addendum2>
    I'm persuaded that a schema can only define elements in its targetNamespace. It should be possible to have immediate children of a complexType from another namespace by using an <element ref="b:Baz" /> but that's nothing more than a theory. Again, if you know the kind of schema document this needs to generate, please file an issue. Otherwise the workaround with soft validation is your best bet at the moment.
    </Addendum2>