WSDLのelementにattributeを追加し、Python + ZeepでSOAPのエンベロープを作成する

swaRefの仕様書を眺めていたところ、

<?xml version='1.0' ?>
<SOAP-ENV:Envelope
        xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
        <claim:insurance_claim_auto id="insurance_claim_document_id"
            xmlns:claim="http://schemas.risky-stuff.com/Auto-Claim">
            <theSignedForm href="cid:claim061400a.tiff@claiming-it.com"/>
            <theCrashPhoto href="cid:claim061400a.jpeg@claiming-it.com"/>
            <!-- ... more claim details go here... -->
        </claim:insurance_claim_auto>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

となっていました。

気になったのは

<theSignedForm href="cid:claim061400a.tiff@claiming-it.com"/>

のように、 element theSignedForm に、attribute href があったことです。

 
elementにattributeのあるWSDLを書いたことがないため、今回

  • WSDLで element に attribute を追加する方法
  • Zeepで、 attributeに値を設定する方法

をそれぞれ調べてみました。

 
目次

 

環境

 

WSDLのelementにattributeを追加する方法

WSDLXMLなので、XMLでの追加方法を調べてみました。

属性を追加する場合は、xsd:attribute要素を使用します。

複数要素を定義する4つの基本形を覚えよう:SEのためのXML Schema入門(2) - @IT

とのことなので、WSDL内の型定義を

<xsd:element name="RequestInterface">
    <xsd:complexType>
        <xsd:sequence>
            <xsd:element minOccurs="0" name="image"/>
        </xsd:sequence>

        <!-- 追加 -->
        <xsd:attribute name="href" type="xsd:string" />
    </xsd:complexType>
</xsd:element>

として、 image elementに href を追加できないか試してみました。

 
結果は、

<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
  <soap-env:Body>
    <ns0:RequestInterface xmlns:ns0="http://example.com/HelloWorld" href="ham_spam"/>
  </soap-env:Body>
</soap-env:Envelope>

と、その親 RequestInterface elementに付きました。

 
RequestInterfaceの子の image element に追加できないかを調べたところ、同じページに

ref属性を記述した場合、要素の構造については別の場所で宣言します。そして、ref属性の値に記されている要素を参照します。

とありました。

 
そのため、WSDL

<!-- 名前空間 my (任意の名前で可)を追加 -->
<wsdl:definitions
...
        xmlns:my="http://example.com/HelloWorld"
        targetNamespace="http://example.com/HelloWorld">

    <wsdl:types>
        <!-- ここのtargetNamespaceも適当に設定(先ほどのと同じでもOK) -->
        <xsd:schema elementFormDefault="qualified" targetNamespace="http://example.com/HelloWorld">
            <xsd:element name="RequestInterface">
                <xsd:complexType>
                    <xsd:sequence>
                        <!-- このelementにattributeを設定するため、refで別の型を参照させる -->
                        <!-- SOAP UIで動作させるため、名前空間(my)を付与して、参照先を明確にする -->
                        <xsd:element minOccurs="0" ref="my:image"/>
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>

            <!-- attributeを付けたい型の定義-->
            <xsd:element name="image">
                <xsd:complexType>
                    <xsd:attribute name="href" type="xsd:string" />
                </xsd:complexType>
            </xsd:element>

として試してみます。

 
なお、WSDL中のコメントにも記載しましたが、 ref 属性を使うときは、

  • wsdl:definitions に、my という名前空間の定義を追加
  • ref="my:image" のように名前空間付きで定義

を行います。名前空間がない場合、SOAP UIでWSDLをimportする際、エラーとなってしまいます。

参考:xml - What does the ref attribute on an element in an XSD do? - Stack Overflow

 
実行結果は

<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
  <soap-env:Body>
    <ns0:RequestInterface xmlns:ns0="http://example.com/HelloWorld">
      <ns0:image href="foo_bar"/>
    </ns0:RequestInterface>
  </soap-env:Body>
</soap-env:Envelope>

と、image elementに href attribute が追加されました。

 
ちなみに、

<xsd:element name="RequestInterface">
    <xsd:complexType>
        <xsd:sequence>
            <xsd:element minOccurs="0" name="image"/>

            <!-- sequenceの中にattributeを追加 -->
            <xsd:attribute name="href" type="xsd:string" />
        </xsd:sequence>
    </xsd:complexType>
</xsd:element>

と、 <xsd:sequence> の中に <xsd:attribute> を入れると、以下のエラーになります。

zeep.exceptions.XMLParseError: Unexpected element {http://www.w3.org/2001/XMLSchema}attribute in xsd:sequence

 

WSDLのattributeに、Zeepから値を設定する

WSDLでelementにattributeを付けられたものの、どうすればZeepから値を与えられるのかが分かりませんでした。

調べてみたところ、Stack Overflowに回答があったため、それを参考に実装してみます。
Python Zeep - how to set attributes for element - Stack Overflow

 

親要素のattributeを設定する

client.service.requestMessage(href='ham_spam') と、attribute名の引数に対し、設定したい値を渡します。

history_plugin = HistoryPlugin()
child_wsdl = BASE_PATH.joinpath('root_attribute.wsdl')
client = Client(str(child_wsdl), plugins=[history_plugin])

# Zeepと同様、requests_mockを使って、POSTをMockする
# https://github.com/mvantellingen/python-zeep/blob/3.2.0/tests/integration/test_http_post.py#L15
with requests_mock.mock() as m:
    m.post('http://localhost:9500/attributeBindingSoap11', text='<root>mocked!</root>')

    # requestMessage()の結果がWSDLの内容と異なるため、常にXMLSyntaxErrorが出る
    # 今回は送信したSOAPエンベロープの値を見たいので、例外は無視する
    try:
        response = client.service.requestMessage(href='ham_spam')
    except XMLSyntaxError:
        pass

    print(etree.tostring(history_plugin.last_sent['envelope'],
                         pretty_print=True, encoding='unicode'))

 
実行すると、親要素の RequestInterfacehref に値が設定されました。

<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
  <soap-env:Body>
    <ns0:RequestInterface xmlns:ns0="http://example.com/HelloWorld" href="ham_spam"/>
  </soap-env:Body>
</soap-env:Envelope>

 

子要素のattributeを設定する

親要素との変更点は、 client.service.requestMessage(image={'href': 'foo_bar'}) と、element名の引数に対し、属性名のdictを渡すことだけです。

history_plugin = HistoryPlugin()
child_wsdl = BASE_PATH.joinpath('child_attribute.wsdl')
client = Client(str(child_wsdl), plugins=[history_plugin])

with requests_mock.mock() as m:
    m.post('http://localhost:9501/attributeBindingSoap11', text='<root>mocked!</root>')
    try:

        # image elementの要素をdictで渡す
        response = client.service.requestMessage(image={'href': 'foo_bar'})

    except XMLSyntaxError:
        pass

    print(etree.tostring(history_plugin.last_sent['envelope'],
                         pretty_print=True, encoding='unicode'))

 
実行すると、子要素の imagehref に値が設定されました。

<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
  <soap-env:Body>
    <ns0:RequestInterface xmlns:ns0="http://example.com/HelloWorld">
      <ns0:image href="foo_bar"/>
    </ns0:RequestInterface>
  </soap-env:Body>
</soap-env:Envelope>

 

ソースコード

GitHubに上げました。 wsdl_attribute/ ディレクトリの中が今回のファイルです。
https://github.com/thinkAmi-sandbox/python_zeep-sample