在 Python 中,如何使用字典为 Zeep 设置 _soapheaders?

Posted

技术标签:

【中文标题】在 Python 中,如何使用字典为 Zeep 设置 _soapheaders?【英文标题】:In Python, how to set _soapheaders for Zeep using Dictionaries? 【发布时间】:2019-01-15 15:27:33 【问题描述】:

在使用 SOAP api 时,wsdl 规范描述了在复杂命名空间结构的标头中传递的 api 密钥,以及与用于连续访问批量结果的分页机制相关的附加非命名空间 XML:

规格

<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>

答案How to set soap headers in zeep when header has multiple elements 描述了一个类似的场景,没有命名空间“ns”但有“acm”。我没有成功使用这种方法。

这可行,允许访问 api 但没有分页器,这使得它对于返回超过 100 个结果的任何方法几乎无用:

from zeep import Client, xsd

# Generate the header structure
header = xsd.Element(
    'wsdlAuthenticateRequest',
    xsd.ComplexType([xsd.Element("wsdlapi_key", xsd.String())])
)

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

这不起作用:

from zeep import Client, xsd

# Generate the header structure
header = xsd.Element(
    'Header',
    xsd.ComplexType([
        xsd.Element(
            'wsdlAuthenticateRequest',
            xsd.ComplexType([
                xsd.Element('wsdlapi_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)

错误:TypeError:ComplexType() 获得了意外的关键字参数“api_key”。签名:AuthenticateRequest:api_key:xsd:string,寻呼机:page:xsd:string,per_page:xsd:string

这也不起作用:

header = xsd.Element(
    'wsdlAuthenticateRequest',
    xsd.ComplexType([xsd.Element("wsdlapi_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' 未在 wsdl 中定义,但服务器希望它可能存在。

TypeError:ComplexType() 获得了意外的关键字参数“寻呼机”。 签名:api_key: xsd:string

使用 Zeep 设置命名空间 api_key 和非命名空间复杂分页器元素的最简单方法是什么?

【问题讨论】:

你看过了吗:***.com/questions/42963114/… 是的,我已尝试更改该解决方案以适应我的 API 模式,但没有成功。我上面列出的第二个失败的例子就是建立在这个答案之上的。不过我会再试一次。 您能否分享 WSDL 以更好地测试可能的解决方案? @Markoan 我希望我可以,它是专有的和广泛的,并且通过合同禁止共享。问题开头列出的规范是服务器所期望的。它可以在没有 pager 元素的情况下工作,但是如果假设我可以在格式正确的 soapheader 中发送 page 2 和 per_page 1000,服务器将响应 page 和 per_page 节点的更改。 @Markoan 所以归根结底就是用 Zeep 以正确的格式创建规范。 【参考方案1】:

我发现如果我们有一个有效且完整的 WSDL,使用 Zeep 会更容易。

需要一个没有命名空间的元素的简单 API 服务 WSDL 会导入一个没有命名空间的模式,如下所示:

<?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>

使用 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>

注意期望标头值的操作定义如何指向正确的消息:

<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:message name="ApiKeyHeader">
  <wsdl:part name="ApiKeyHeaderParam" element="tns:apiKey"/>
</wsdl:message>
<wsdl:message name="PagerHeader">
  <wsdl:part name="PagerHeaderParam" ref="pager"/>
</wsdl:message>

您应该在 Web 服务的 WSDL 中检查该操作是否描述了两个标头,并且它包含两个元素的架构定义。在示例 WSDL 中,服务命名空间是 targetNamespace="http://tempuri.org/",但这应该指向您的 Web 服务 URL。

因此假设您的 WSDL 有效且完整,我们需要定义指向 WSDL 的客户端,然后使用 _soapheaders 参数设置标头值,类似于我使用 here 的方法,但构建内容引用。 Zeep 可以处理不同的命名空间,但我发现空的命名空间存在问题:

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)

编辑:生成的 XML 请求示例:

<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>

确保使用正确的 URL 定义具有命名空间的标头。

如果您仍然有问题,这可能意味着您的 WSDL 没有定义所有元素,或者它没有正确链接到外部 XSD。在这些情况下,一种选择是将本地副本保存到 WSDL 和链接的 XSD,然后编辑文件以修复引用,然后将 Zeep 指向该本地文件。

【讨论】:

感谢您的广泛回答。我相信这也对其他人有用。我已将其标记为已接受。 当我的 client.service.X 没有标头时,如何将其指向本地文件?

以上是关于在 Python 中,如何使用字典为 Zeep 设置 _soapheaders?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用递归方法更新python字典

带有zeep的python SOAP,requests.exception:403客户端错误

Soap 请求在 Soapui 中运行良好,但在 python 中运行良好 - ZEEP - SUDS

使用zeep / python创建XML序列

如何使用 ZEEP 更改 SOAP 请求中的端点地址

我需要创建一个使用 ZEEP 或 REQUEST 模块调用 SOAP GET 方法的 python 脚本