在 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?的主要内容,如果未能解决你的问题,请参考以下文章
带有zeep的python SOAP,requests.exception:403客户端错误