Tags: Python3; zeep; SOAP; WSDL; suds
前几天 Python3 下使用 suds 调用 Web Service,引用 wsdl 时出现类型错误。 得另外找一款 Python3 下的 SOAP Client 库了。Web Service 及 SOAP 协议红火的年代,Python3 还没出世。Python 的 SOAP 库基本上在 Python2 的时代就停止维护了。
根据这两个链接1 2,调查了一下,大约 zeep 是目前 Python3 下 SOAP Client 的第一选择吧。
suds-jurko
好几年没动了。WSDL 是 Web Services Description Language
,以 XML 格式描述了 Web Service 的接口信息。
通常象 C#这样的强类型语言,又有完善的 IDE 支持,开发不需要关心 WSDL 的内容,通过 IDE 导入,对象浏览即可差不多大致了解接口。
但是 python 是动态语言,有时候还是需要大概看一下 wsdl 的内容,了解接口名称,参数等信息。
WSDL 是 xml 格式,会涉及到 xml 的 namespace 概念。
一份 XML 中可能会引用多个标准组织的 schema,namespace 避免不同 schema 引入的元素命名冲突。
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://tempuri.org/" targetNamespace="http://tempuri.org/">
xmlns
属性声明了引入的名字空间。上例中,xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
, 引入了名字空间 http://schemas.xmlsoap.org/wsdl/
,并为该名字空间分配前缀名(prefix) wsdl
。
有名字空间前缀后,就可以通过 prefix:localpart
的方式使用 schema 里定义的名称。比如 wsdl:definitions
就是 http://schemas.xmlsoap.org/wsdl/
名字空间下定义的 definitions 元素。
wsdl:definitions 被称为 qualified name
。qualified name 在 xml 解析器中扩展成一个序对(namespace URI, local name),称为 expanded name
。一种非官方定义的,但被接受的表示法3: {http://schemas.xmlsoap.org/wsdl/}definitions
targetNamespace
属性用来声明本 schema 文档所定义的元素所在的名字空间。 xmlns:tns="http://tempuri.org/" targetNamespace="http://tempuri.org/"
这句,
targetNamespace 声明这份 WSDL 文档定义的 element, data type, 所在名字空间为 http://tempuri.org/
,xmlns:tns 又声明,在本文档中,使用前缀 tns
表示名字空间 http://tempuri.org/
。
targetNamespace 相当于 python 中 package 命名,xmlns 相当于 import package as alias
浏览 WSDL 文件,看接口信息不太直观。zeep 提供一个方法查看 WSDL 接口信息。首先,pip 安装 zeep。
注:下面有些示例代码直接 copy 自官方文档。
python -m zeep http://www.soapclient.com/xml/soapresponder.wsdl
该命令打印 wsdl 接口信息,可以把它保存在 txt 文件中,以便后面查看。输出的信息中 Prefixes 和 Global types,后面创建参数数据类型时可以用到。Global elements 或 Service 的 Port 节可以看到所有可用函数 /方法。
from zeep import Client
client = Client('http://my-wsdl/wsdl')
client.service.myoperation()
client.service 属性返回一个默认的 ServiceProxy 实例,自动连接了 WSDL 中第一个 Service 的第一个 Port。ServiceProxy 包装了 Web Service 调用。
Web Service 中定义的各种参数数据类型,可以使用 dict 的方式,构造并传递。更方便的方法,通过辅助方法创建。
from zeep import Client
client = Client('http://my-enterprise-endpoint.com')
order_type = client.get_type('ns0:Order')
order = order_type(number='1234', price=99)
client.service.submit_order(user_id=1, order=order)
get_type 返回的是类对象。这里传入的数据类型名需要使用 WSDL 中的 qualified name,即带上前缀。ns0
是默认名字空间分配的前缀。也可使用 namespace 的扩展形式,例如:{http://my-wsdl/}Order。
名字空间,名字空间前缀,数据类型信息可从上面提到的命令中查看。
也可以使用 type_factory 创建数据对象:
from zeep import Client
client = Client('http://my-enterprise-endpoint.com')
factory = client.type_factory('ns0')
user = factory.User(id=1, name='John')
order = factory.Order(number='1234', price=99)
client.service.submit_order(user=user, order=order)
一个常见的场景是开发环境的 Web Service,部署到生产环境后 Web Service 地址变成生产环境地址。
简单的办法,是 Client 创建对象时指定新的 wsdl 引用地址,新的 wsdl 包含了新的 Port 服务器地址定义。
如果引入的 wsdl 和实际 web service 地址不同,比如把 wsdl 保存在项目文件中,从文件中引入。可以通过 create_service 创建新的 ServiceProxy 实例。
service2=client.create_service('{http://my-wsdl/wsdl}myoperation','http://my-ws/ws')
create_service 第一个参数是 WSDL 中的 binding 名,第二个参数是服务调用地址。注意这边的 binding 名要带上 namespace 的扩展形式。文档上说使用 QName,实验来下使用前缀形式不行。 用上面查看 WSDL 接口信息的命令,输出的 binding 名也是扩展形式。
Client 对象还提供了一个 bind 方法(不要和 WSDL 中的 binding 搞混了)创建 ServiceProxy 实例。WSDL 中定义了多个 Service/Port,zeep 默认使用 WSDL 中第一个 Service/Port,如果想用其他的 Service/Port 使用 bind 方法创建新 ServiceProxy。
service2 = client.bind('SecondService', 'Port12')
zeep 使用 request
, 可以构造request.session,传入 zeep。如果使用 socks proxy 需要安装 request 的 socks 依赖。
from zeep.import Client
client = Client(
'http://my.own.sslhost.local/service?WSDL')
client.transport.session.proxies = {
'http': 'foo.bar:3128',
'https': 'foo.bar:3128',
}
例如,修改 HTTP 的 User-Agent
from zeep import Client,Settings
settings=Settings(extra_http_headers={'User-Agent': 'Mozilla/5.0'})
client=Client('http://my-wsdl/wsdl',settings=settings)
client.service.myoperation()
settings 支持 context manager,可以通过 with 语句,临时改变选项。
from zeep import Client
from zeep import xsd
client = Client('http://my-endpoint.com/production.svc?wsdl')
with client.settings(raw_response=True):
response = client.service.myoperation()
# response is now a regular requests.Response object
assert response.status_code == 200
assert response.content
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.