In Python, how to set _soapheaders for Zeep using Dictionaries?

∥☆過路亽.° 提交于 2019-12-17 17:11:24

问题


In working with a SOAP api, the wsdl spec describes the api key passed in the header in a complex namespaced structure as well as additional non-namespaced XML that relates to a paging mechanism for accessing bulk results successively:

Specification:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="https://webservice_address_here">
  <soapenv:Header>
    <ns:apiKey>
      <api_key>***</api_key>
    </ns:apiKey>
    <pager>
      <page>1</page>
      <per_page>100</per_page>
    </pager>
  </soapenv:Header>
</soapenv:Envelope>

The answer, How to set soap headers in zeep when header has multiple elements, describes a similar scenario, without the namespace "ns" but with "acm." I have not been successful in using this method.

This works, allowing access to the api but without the pager making it mostly useless for any methods that return more than 100 results:

from zeep import Client, xsd

# Generate the header structure
header = xsd.Element(
    '{wsdl}AuthenticateRequest',
    xsd.ComplexType([xsd.Element("{wsdl}api_key", xsd.String())])
)

# Insert values into header placeholders
self._header_value = header(api_key=self.api_key)

This does not work:

from zeep import Client, xsd

# Generate the header structure
header = xsd.Element(
    'Header',
    xsd.ComplexType([
        xsd.Element(
            '{wsdl}AuthenticateRequest',
            xsd.ComplexType([
                xsd.Element('{wsdl}api_key', xsd.String()),
            ])
        ),
        xsd.Element(
            'pager',
            xsd.ComplexType([
                xsd.Element('page', xsd.String()),
                xsd.Element('per_page', xsd.String()),
            ])
        ),
    ])
)

# ERROR HERE: Insert values into header placeholders
self._header_value = header(api_key=self.api_key, pager={'page':1,'per_page':100})

Error: TypeError: ComplexType() got an unexpected keyword argument 'api_key'. Signature: AuthenticateRequest: {api_key: xsd:string}, pager: {page: xsd:string, per_page: xsd:string}

This also does not work:

header = xsd.Element(
    '{wsdl}AuthenticateRequest',
    xsd.ComplexType([xsd.Element("{wsdl}api_key", xsd.String())]),
    xsd.Element(
        'pager',
        xsd.ComplexType([
            xsd.Element('page', xsd.String()),
            xsd.Element('per_page', xsd.String()),
        ])
    )
)

# ERROR HERE: Insert values into header placeholders
self._header_value = header(api_key=self.api_key, pager={"page":1,"per_page":100})

'pager' is not defined in the wsdl but the server expects that it could be there.

TypeError: ComplexType() got an unexpected keyword argument 'pager'. Signature: api_key: xsd:string

What is the simplest way using Zeep to set the namespace api_key and non-namespaced complex pager element?


回答1:


I find it's easier to work with Zeep if we have a valid and complete WSDL.

A simple API service WSDL that expects an element without namespace would import a schema with no namespace like this:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://tempuri.org/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://tempuri.org/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" >
  <wsdl:types>
    <s:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/">
      <s:import schemaLocation="namespaceLessElement.xsd"/>
      <s:element name="Api" minOccurs="0" maxOccurs="1">
      </s:element>
      <s:element name="ApiResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="1" maxOccurs="1" name="ApiResult" type="s:int"/>
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:element name="apiKey">
        <s:complexType>
          <s:sequence>
            <s:element name="api_key" type="s:string"></s:element>
          </s:sequence>
        </s:complexType>
      </s:element>
    </s:schema>
  </wsdl:types>
  <wsdl:message name="ApiSoapIn">
    <wsdl:part name="parameters" element="tns:Api"/>
  </wsdl:message>
  <wsdl:message name="ApiSoapOut">
    <wsdl:part name="parameters" element="tns:ApiResponse"/>
  </wsdl:message>
  <wsdl:message name="ApiKeyHeader">
    <wsdl:part name="ApiKeyHeaderParam" element="tns:apiKey"/>
  </wsdl:message>
  <wsdl:message name="PagerHeader">
    <wsdl:part name="PagerHeaderParam" ref="pager"/>
  </wsdl:message>
  <wsdl:portType name="ApiSoap">
    <wsdl:operation name="Api">
      <wsdl:documentation>This is a test WebService. Returns a number</wsdl:documentation>
      <wsdl:input message="tns:ApiSoapIn"/>
      <wsdl:output message="tns:ApiSoapOut"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="ApiSoap" type="tns:ApiSoap">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="Api">
      <soap:operation soapAction="http://tempuri.org/Api" style="document"/>
      <wsdl:input>
        <soap:header message="tns:ApiKeyHeader" part="ApiKeyHeaderParam" use="literal"/>
        <soap:header message="tns:PagerHeader" part="PagerHeaderParam" use="literal"/>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="ApiTest">
    <wsdl:port name="ApiSoap" binding="tns:ApiSoap">
      <soap:address location="http://superpc:8082/"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

With namespaceLessElement.xsd:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<s:schema xmlns:s="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified">
  <s:element name="pager">
    <s:complexType>
      <s:sequence>
        <s:element name="page" type="s:int"></s:element>
        <s:element name="per_page" type="s:int"></s:element>
      </s:sequence>
    </s:complexType>
  </s:element>
</s:schema>

Note how the operation definition that expects header values points to correct messages:

<wsdl:operation name="Api">
  <soap:operation soapAction="http://tempuri.org/Api" style="document"/>
  <wsdl:input>
    <soap:header message="tns:ApiKeyHeader" part="ApiKeyHeaderParam" use="literal"/>
    <soap:header message="tns:PagerHeader" part="PagerHeaderParam" use="literal"/>
    <soap:body use="literal"/>
  </wsdl:input>
  <wsdl:output>
    <soap:body use="literal"/>
  </wsdl:output>
</wsdl:operation>

and these in turn reference correct elements:

<wsdl:message name="ApiKeyHeader">
  <wsdl:part name="ApiKeyHeaderParam" element="tns:apiKey"/>
</wsdl:message>
<wsdl:message name="PagerHeader">
  <wsdl:part name="PagerHeaderParam" ref="pager"/>
</wsdl:message>

You should check in the WSDL of your web service that the operation describes both headers and that it includes a schema definition for both elements. In the example WSDL the service namespace is targetNamespace="http://tempuri.org/" but this should point to your web service URL.

So assuming your WSDL is valid and complete, we need to define the Client pointing to the WSDL and then set the header values using the _soapheaders parameter, similar to the method I used here but building the content reference. Zeep can take care of the different namespaces but I found issues with empty ones:

transport = Transport(cache=SqliteCache())
self.Test = Client(wsdl='http://my-endpoint.com/production.svc?wsdl', transport=transport)

# Header objects
apiKey_header = xsd.Element(
    '{http://tempuri.org/}apiKey',
    xsd.ComplexType([
        xsd.Element(
            'api_key', xsd.String()
        )
    ])
)

pager_header = xsd.Element(
    'pager',
    xsd.ComplexType([
        xsd.Element(
            'page', xsd.Integer()
        ),
        xsd.Element(
            'per_page', xsd.Integer()
        )
    ])
)

apiKey_header_value = apiKey_header( api_key=key)
pager_header_value = pager_header( page=page, per_page=perpage)

# Request
response = self.Test.service.Api( _soapheaders=[apiKey_header_value, pager_header_value] )

logger.debug("Result={1}".format(response))

# Prints: Result=2 (or whatever value the test API sends)

EDIT: Example of generated XML request:

<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
   <soap-env:Header>
      <ns0:apiKey xmlns:ns0="http://tempuri.org/">
         <api_key>1230011</api_key>
      </ns0:apiKey>
      <pager>
         <page>2</page>
         <per_page>10</per_page>
      </pager>
   </soap-env:Header>
   <soap-env:Body>
      <ns0:Api xmlns:ns0="http://tempuri.org/"/>
   </soap-env:Body>
</soap-env:Envelope>

Make sure that the header that has a namespace is defined with the correct URL.

If you still have problems it may mean your WSDL does not define all elements or that it's not linking correctly to external XSDs. In those cases one option is to save a local copy os the WSDL and linked XSDs, then edit the files to fix references and then point Zeep to that local file instead.



来源:https://stackoverflow.com/questions/54201921/in-python-how-to-set-soapheaders-for-zeep-using-dictionaries

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!