Zeep使用疑问:是否必须构建嵌套字典,有无其他方案?
关于Zeep实现复杂SOAP请求的替代方案
问题描述
我有一个在Postman中测试通过的SOAP请求XML,现在需要在Python项目中用Zeep实现。我之前用过Java的SOAP工具(十多年前),刚接触Zeep。目前了解到的方式是构建嵌套字典传入Zeep Client,但想知道有没有其他更优雅的实现方式?
测试通过的XML示例
<?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:bsvc="urn:com.workday/bsvc" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <soapenv:Header> <wsse:Security soapenv:mustUnderstand="1"> <wsse:UsernameToken> <wsse:Username>ISU_Get_Worker_Photo@mycompany_preview</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">xxxxxxxxxxx</wsse:Password> </wsse:UsernameToken> </wsse:Security> </soapenv:Header> <soapenv:Body> <bsvc:Change_Work_Contact_Information_Request bsvc:version="v42.0"> <bsvc:Business_Process_Parameters> <bsvc:Auto_Complete>true</bsvc:Auto_Complete> <bsvc:Run_Now>true</bsvc:Run_Now> </bsvc:Business_Process_Parameters> <bsvc:Change_Work_Contact_Information_Data> <bsvc:Event_Effective_Date>2023-10-25</bsvc:Event_Effective_Date> <bsvc:Person_Reference> <bsvc:ID bsvc:type="Employee_ID">9476</bsvc:ID> </bsvc:Person_Reference> <bsvc:Person_Contact_Information_Data> <bsvc:Person_Phone_Information_Data bsvc:Replace_All="false"> <bsvc:Phone_Information_Data bsvc:Delete="false"> <bsvc:Phone_Data> <bsvc:Device_Type_Reference bsvc:Descriptor="?"> <bsvc:ID bsvc:type="Phone_Device_Type_ID">Landline</bsvc:ID> </bsvc:Device_Type_Reference> <bsvc:Country_Code_Reference bsvc:Descriptor="?"> <bsvc:ID bsvc:type="Country_Phone_Code_ID">USA_1</bsvc:ID> </bsvc:Country_Code_Reference> <bsvc:Complete_Phone_Number>8444877900</bsvc:Complete_Phone_Number> <bsvc:Extension></bsvc:Extension> </bsvc:Phone_Data> <bsvc:Usage_Data bsvc:Public="true"> <bsvc:Type_Data bsvc:Primary="true"> <bsvc:Type_Reference bsvc:Descriptor="?"> <bsvc:ID bsvc:type="Communication_Usage_Type_ID">Work</bsvc:ID> </bsvc:Type_Reference> </bsvc:Type_Data> </bsvc:Usage_Data> </bsvc:Phone_Information_Data> </bsvc:Person_Phone_Information_Data> </bsvc:Person_Contact_Information_Data> </bsvc:Change_Work_Contact_Information_Data> </bsvc:Change_Work_Contact_Information_Request> </soapenv:Body> </soapenv:Envelope>
当前理解的实现方式
import zeep, os from zeep import Client from zeep.wsse.username import UsernameToken hostname = 'host' tenant_name = 'mycompany' employee_id = '1234' user = os.getenv('user') password = os.getenv('password') url = f'https://{hostname}.workday.com/ccx/service/{tenant_name}/Human_Resources/v36.2?wsdl' client = Client(url, wsse=UsernameToken(user, password)) request_dict = { # 嵌套字典对应所有XML标签 "Business_Process_Parameters": { "Auto_Complete": True, "Run_Now": True }, "Change_Work_Contact_Information_Data": { "Event_Effective_Date": "2023-10-25", "Person_Reference": { "ID": [{"value": "9476", "type": "Employee_ID"}] }, # 其他嵌套字段... } } client.service.Change_Work_Contact_Information_Request(request_dict)
我想知道有没有像简单示例那样的对象式调用方式,而不是全靠嵌套字典?
解决方案
不需要完全依赖嵌套字典,Zeep提供了多种更结构化的方式来构建复杂请求:
1. 使用类型工厂(Type Factory)构建强类型对象
Zeep会解析WSDL中的所有数据类型,你可以通过client.get_type()获取对应类型,然后创建对象并赋值,这种方式能利用WSDL的类型校验,减少错误。
示例代码:
import zeep, os from zeep import Client from zeep.wsse.username import UsernameToken hostname = 'host' tenant_name = 'mycompany' user = os.getenv('user') password = os.getenv('password') url = f'https://{hostname}.workday.com/ccx/service/{tenant_name}/Human_Resources/v36.2?wsdl' client = Client(url, wsse=UsernameToken(user, password)) # 获取请求所需的各类数据类型 BusinessProcessParams = client.get_type('ns0:Business_Process_ParametersType') ChangeWorkContactData = client.get_type('ns0:Change_Work_Contact_Information_DataDataType') PersonReference = client.get_type('ns0:RoleObjectIDType') PhoneInformationData = client.get_type('ns0:Person_Phone_Information_DataType') PhoneData = client.get_type('ns0:Phone_DataType') DeviceTypeRef = client.get_type('ns0:Phone_Device_Type_ReferenceType') CountryCodeRef = client.get_type('ns0:Country_Phone_Code_ReferenceType') UsageData = client.get_type('ns0:Communication_Usage_Information_DataType') TypeData = client.get_type('ns0:Communication_Usage_Type_DataType') TypeRef = client.get_type('ns0:Communication_Usage_Type_ReferenceType') # 逐层构建对象 business_params = BusinessProcessParams(Auto_Complete=True, Run_Now=True) # 构建ID对象(Workday的ID通常为列表形式) person_id = PersonReference(ID=[{'value': '9476', 'type': 'Employee_ID'}]) device_type_id = DeviceTypeRef(ID=[{'value': 'Landline', 'type': 'Phone_Device_Type_ID'}]) country_code_id = CountryCodeRef(ID=[{'value': 'USA_1', 'type': 'Country_Phone_Code_ID'}]) type_ref = TypeRef(ID=[{'value': 'Work', 'type': 'Communication_Usage_Type_ID'}]) type_data = TypeData(Type_Reference=type_ref, Primary=True) usage_data = UsageData(Type_Data=[type_data], Public=True) phone_data = PhoneData( Device_Type_Reference=device_type_id, Country_Code_Reference=country_code_id, Complete_Phone_Number='8444877900', Extension='' ) phone_info_data = PhoneInformationData( Phone_Data=phone_data, Usage_Data=usage_data, Delete=False ) person_phone_data = client.get_type('ns0:Person_Phone_Information_DataType')( Phone_Information_Data=[phone_info_data], Replace_All=False ) contact_data = ChangeWorkContactData( Event_Effective_Date='2023-10-25', Person_Reference=person_id, Person_Contact_Information_Data={ 'Person_Phone_Information_Data': person_phone_data } ) # 发起请求 response = client.service.Change_Work_Contact_Information_Request( Business_Process_Parameters=business_params, Change_Work_Contact_Information_Data=contact_data, version='v42.0' )
2. 混合使用关键字参数与嵌套字典
对于复杂层级,可以直接传入关键字参数,嵌套部分用字典,兼顾简洁与可读性:
import zeep, os from zeep import Client from zeep.wsse.username import UsernameToken hostname = 'host' tenant_name = 'mycompany' user = os.getenv('user') password = os.getenv('password') url = f'https://{hostname}.workday.com/ccx/service/{tenant_name}/Human_Resources/v36.2?wsdl' client = Client(url, wsse=UsernameToken(user, password)) # 直接用关键字参数传递顶层字段,嵌套部分用字典 response = client.service.Change_Work_Contact_Information_Request( version='v42.0', Business_Process_Parameters={ 'Auto_Complete': True, 'Run_Now': True }, Change_Work_Contact_Information_Data={ 'Event_Effective_Date': '2023-10-25', 'Person_Reference': { 'ID': [{'value': '9476', 'type': 'Employee_ID'}] }, 'Person_Contact_Information_Data': { 'Person_Phone_Information_Data': { 'Replace_All': False, 'Phone_Information_Data': [{ 'Delete': False, 'Phone_Data': { 'Device_Type_Reference': { 'ID': [{'value': 'Landline', 'type': 'Phone_Device_Type_ID'}] }, 'Country_Code_Reference': { 'ID': [{'value': 'USA_1', 'type': 'Country_Phone_Code_ID'}] }, 'Complete_Phone_Number': '8444877900', 'Extension': '' }, 'Usage_Data': { 'Public': True, 'Type_Data': [{ 'Primary': True, 'Type_Reference': { 'ID': [{'value': 'Work', 'type': 'Communication_Usage_Type_ID'}] } }] } }] } } } )
3. 直接发送原始XML(应急可用,不推荐)
如果不想重构请求结构,可以通过Zeep的transport直接发送原始XML,但会失去Zeep的类型校验和WSDL解析优势:
import zeep, os from zeep import Client, Transport from zeep.wsse.username import UsernameToken import requests hostname = 'host' tenant_name = 'mycompany' user = os.getenv('user') password = os.getenv('password') url = f'https://{hostname}.workday.com/ccx/service/{tenant_name}/Human_Resources/v36.2' # 替换变量后的原始XML raw_xml = """<?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:bsvc="urn:com.workday/bsvc" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <soapenv:Header> <wsse:Security soapenv:mustUnderstand="1"> <wsse:UsernameToken> <wsse:Username>{user}</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">{password}</wsse:Password> </wsse:UsernameToken> </wsse:Security> </soapenv:Header> <soapenv:Body> <bsvc:Change_Work_Contact_Information_Request bsvc:version="v42.0"> <bsvc:Business_Process_Parameters> <bsvc:Auto_Complete>true</bsvc:Auto_Complete> <bsvc:Run_Now>true</bsvc:Run_Now> </bsvc:Business_Process_Parameters> <bsvc:Change_Work_Contact_Information_Data> <bsvc:Event_Effective_Date>2023-10-25</bsvc:Event_Effective_Date> <bsvc:Person_Reference> <bsvc:ID bsvc:type="Employee_ID">9476</bsvc:ID> </bsvc:Person_Reference> <bsvc:Person_Contact_Information_Data> <bsvc:Person_Phone_Information_Data bsvc:Replace_All="false"> <bsvc:Phone_Information_Data bsvc:Delete="false"> <bsvc:Phone_Data> <bsvc:Device_Type_Reference bsvc:Descriptor="?"> <bsvc:ID bsvc:type="Phone_Device_Type_ID">Landline</bsvc:ID> </bsvc:Device_Type_Reference> <bsvc:Country_Code_Reference bsvc:Descriptor="?"> <bsvc:ID bsvc:type="Country_Phone_Code_ID">USA_1</bsvc:ID> </bsvc:Country_Code_Reference> <bsvc:Complete_Phone_Number>8444877900</bsvc:Complete_Phone_Number> <bsvc:Extension></bsvc:Extension> </bsvc:Phone_Data> <bsvc:Usage_Data bsvc:Public="true"> <bsvc:Type_Data bsvc:Primary="true"> <bsvc:Type_Reference bsvc:Descriptor="?"> <bsvc:ID bsvc:type="Communication_Usage_Type_ID">Work</bsvc:ID> </bsvc:Type_Reference> </bsvc:Type_Data> </bsvc:Usage_Data> </bsvc:Phone_Information_Data> </bsvc:Person_Phone_Information_Data> </bsvc:Person_Contact_Information_Data> </bsvc:Change_Work_Contact_Information_Data> </bsvc:Change_Work_Contact_Information_Request> </soapenv:Body> </soapenv:Envelope>""".format(user=user, password=password) # 构建transport并发送请求 transport = Transport(session=requests.Session()) response = transport.post( url, raw_xml, headers={'Content-Type': 'text/xml; charset=utf-8'} ) # 解析响应 parsed_response = client.create_message(client.service, 'Change_Work_Contact_Information_Request', envelope=response.content)
内容的提问来源于stack exchange,提问作者jb62
相关产品推荐
相关产品推荐

