如何在Spyne的AnyDict返回结果中添加xsi:type="xsd:string"?
问题:为Spyne SOAP服务的AnyDict返回字段添加xsi:type属性
问题背景
我使用Spyne框架开发SOAP服务,需要为返回结果中AnyDict类型的字段添加xsi:type="xsd:string"属性。当前SOAP响应里的<state>和<err_msg>标签缺少该属性,期望为其补全。
当前SOAP响应
<soap11env:Envelope xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://localhost/nusoap"> <soap11env:Body> <tns:ERespons> <tns:EResponsRecord> <state>failed</state> <err_msg>Nastala chyba</err_msg> </tns:EResponsRecord> </tns:ERespons> </soap11env:Body> </soap11env:Envelope>
期望SOAP响应
<soap11env:Envelope xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://localhost/nusoap"> <soap11env:Body> <tns:ERespons> <tns:EResponsRecord> <state xsi:type="xsd:string">failed</state> <err_msg xsi:type="xsd:string">Nastala chyba</err_msg> </tns:EResponsRecord> </tns:ERespons> </soap11env:Body> </soap11env:Envelope>
现有服务代码
class AddEntryEC(ServiceBase): EntryObject.__namespace__ = 'Entry.soap' __out_header__ = EntryObject @rpc( AnyDict, _out_message_name = 'ERespons', _out_variable_name = 'EResponsRecord', _returns=AnyDict ) def AddEntry(ctx, data): data = get_object_as_dict(data) try : ctx.app.db_tool.set_args(data) res = ctx.app.db_tool.insert_data() return res except Exception as e: logging.exception(e) return {'state' : 'failed', 'err_msg' : 'Nastala chyba'}
应用声明代码
application = MyApplication([AddEntryEC], 'http://localhost/nusoap', in_protocol=Soap11(validator='soft'), out_protocol=Soap11())
解决方案
方案1:用ComplexModel替代AnyDict(推荐)
AnyDict属于动态类型,Spyne不会为其字段自动生成明确的类型属性。最规范的做法是定义一个ComplexModel来明确字段类型,框架会自动处理xsi:type属性:
from spyne import ComplexModel, Unicode # 定义返回结果的模型 class EResponsRecord(ComplexModel): __namespace__ = 'http://localhost/nusoap' # 与服务命名空间保持一致 state = Unicode err_msg = Unicode # 修改服务代码 class AddEntryEC(ServiceBase): EntryObject.__namespace__ = 'Entry.soap' __out_header__ = EntryObject @rpc( AnyDict, _out_message_name = 'ERespons', _out_variable_name = 'EResponsRecord', _returns=EResponsRecord # 替换为自定义模型 ) def AddEntry(ctx, data): data = get_object_as_dict(data) try : ctx.app.db_tool.set_args(data) res = ctx.app.db_tool.insert_data() # 将字典转换为模型实例返回 return EResponsRecord(**res) except Exception as e: logging.exception(e) # 返回模型实例而非字典 return EResponsRecord(state='failed', err_msg='Nastala chyba')
方案2:自定义SOAP序列化器(应急方案)
如果无法替换AnyDict,可以自定义序列化器,强制为指定字段添加xsi:type属性:
from spyne.protocol.soap import Soap11 from lxml import etree class CustomSoap11(Soap11): def serialize(self, ctx): super().serialize(ctx) # 定义命名空间 xsi_ns = "http://www.w3.org/2001/XMLSchema-instance" xsd_ns = "http://www.w3.org/2001/XMLSchema" # 为信封添加xsd命名空间声明 ctx.out_document.attrib["xmlns:xsd"] = xsd_ns # 遍历目标标签添加属性 for elem in ctx.out_document.xpath('//state | //err_msg'): elem.set(f"{{{xsi_ns}}}type", "xsd:string") # 修改应用声明,使用自定义协议 application = MyApplication( [AddEntryEC], 'http://localhost/nusoap', in_protocol=Soap11(validator='soft'), out_protocol=CustomSoap11() )
说明
- 方案1符合SOAP规范,通过明确的模型定义让框架自动处理类型逻辑,后续维护更简单。
- 方案2属于临时 workaround,适合无法修改返回类型的场景,需要注意XPath匹配的准确性,避免影响其他字段。
内容的提问来源于stack exchange,提问作者Vova
相关产品推荐
相关产品推荐

