spyne
spyne copied to clipboard
SOAP Fault detail does not accept ComplexModel/Raw lxml element may be required
Hello, First of all: working with spyne has been pretty straightforward; it seems to be the most advanced framework for python to write soap (and other) services with!
Problem description / My PoV
For my current project, a requirement specifies that I must send a list of complex objects in the detail-field of the Fault. I can actually do with only one element, so the limitation of only one element in the detail-field is not that much of a problem; but: the complex object I'm supposed to send contains attributes; this is not possible to do with a plain dict. Unfortunately, I have absolutely no influence on these requirements :-( I worked around this by using a deprecated, not-documented feature, namely manually building the lxml tree and using that for the detail-field.
Actual request:
The "accept raw lxml elements as detail" feature is commented as deprecated (https://github.com/arskom/spyne/blob/master/spyne/protocol/xml.py#L877) - can you please keep it until another solution/option is in place?
Of course I'm open for other suggestions/approaches on how to solve this issue on my side.
The "accept raw lxml elements as detail" feature is commented as deprecated (https://github.com/arskom/spyne/blob/master/spyne/protocol/xml.py#L877) - can you please keep it until another solution/option is in place?
It'll be there until Spyne 3. I don't know when if ever there will be a Spyne 3
Thank you for the information, I guess I'll just need to be on the lookout for a major release then.
Of course I'm open for other suggestions/approaches on how to solve this issue on my side.
I'm aware of this issue but I did not have time to think about a an api or method about this. At worst you can patch the outgoing document via ctx.out_body_doc
I'll look into that when the need arises; for now, I'm happy assembling an lxml.etree.
I have to admit though, that I wondered why it doesn't accept a ComplexModel? My best guess is, that this is not trivially implemented
You can always use get_object_as_xml
if your want to create the lxml Element
s automatically.
I seem to remember that subclassing fault object used to work. Maybe you can do:
class SomeException(Fault):
detail = SomeComplexObject
though I'm not sure whether that'd work.
I tried to do the subclassing, but that didn't work out; Here's the minified example:
#!/usr/bin/env python3
from spyne.model.fault import Fault
from spyne.model.primitive import Unicode
from spyne.model.complex import ComplexModel, XmlAttribute, XmlData
from spyne.protocol.soap import Soap11
from spyne import Application, rpc, ServiceBase
from spyne.server.wsgi import WsgiApplication
class TComplex(ComplexModel):
attr = XmlAttribute(Unicode)
data = XmlData(Unicode)
class TFault(Fault):
detail = TComplex
class TService(ServiceBase):
@rpc(_returns=Unicode)
def fail_to_say_hello(ctx):
tcompl = TComplex()
tcompl.attr = 'attr-val'
tcompl.data = 'data-val'
raise TFault('Server', detail=tcompl)
app = Application([TService], 'https://example.namespace.com', in_protocol=Soap11(), out_protocol=Soap11())
wsgi_app = WsgiApplication(app)
import logging
from wsgiref.simple_server import make_server
logging.basicConfig(level=logging.INFO)
logging.getLogger('spyne.protocol.xml').setLevel(logging.INFO)
logging.info("listening to http://127.0.0.1:8000")
logging.info("wsdl is at: http://localhost:8000/?wsdl")
server = make_server('127.0.0.1', 8000, wsgi_app)
server.serve_forever()
curl-command:
-> curl 'http://127.0.0.1:8000' -d'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="https://example.namespace.com">
<soapenv:Header/>
<soapenv:Body>
<ser:fail_to_say_hello/>
</soapenv:Body>
</soapenv:Envelope>'
A server error occurred. Please contact the administrator.
For completeness the stacktrace (the home-path has been shortened and a project name redacted):
-> ./fault_test.py
INFO:root:listening to http://127.0.0.1:8000
INFO:root:wsdl is at: http://localhost:8000/?wsdl
ERROR:spyne.application:Fault(Server: 'TFault')
Traceback (most recent call last):
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/application.py", line 151, in process_request
ctx.out_object = self.call_wrapper(ctx)
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/application.py", line 235, in call_wrapper
retval = ctx.descriptor.service_class.call_wrapper(ctx)
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/service.py", line 209, in call_wrapper
return ctx.function(ctx, *args)
File "./fault_test.py", line 31, in fail_to_say_hello
raise TFault('Server', detail=tcompl)
TFault: Fault(Server: 'TFault')
Traceback (most recent call last):
File "~/pyenvs/<redacted>/lib/python3.6/wsgiref/handlers.py", line 137, in run
self.result = application(self.environ, self.start_response)
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/server/wsgi.py", line 244, in __call__
return self.handle_rpc(req_env, start_response)
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/server/wsgi.py", line 365, in handle_rpc
start_response)
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/server/wsgi.py", line 323, in handle_error
self.get_out_string(p_ctx)
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/server/_base.py", line 122, in get_out_string_pull
ret = ctx.out_protocol.serialize(ctx, message=ProtocolBase.RESPONSE)
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/protocol/soap/soap11.py", line 288, in serialize
out_body_doc, self.app.interface.get_tns())
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/protocol/xml.py", line 463, in to_parent
return handler(ctx, cls, inst, parent, ns, *args, **kwargs)
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/protocol/xml.py", line 845, in fault_to_parent
return self._fault_to_parent_impl(ctx, cls, inst, parent, ns, subelts)
File "~/pyenvs/<redacted>/lib/python3.6/site-packages/spyne/protocol/xml.py", line 832, in _fault_to_parent_impl
raise TypeError('Fault detail Must be dict, got', type(inst.detail))
TypeError: ('Fault detail Must be dict, got', <class '__main__.TComplex'>)
127.0.0.1 - - [03/Sep/2018 10:04:14] "POST / HTTP/1.1" 500 59
^CTraceback (most recent call last):
File "./fault_test.py", line 47, in <module>
server.serve_forever()
File "~/pyenvs/<redacted>/lib/python3.6/socketserver.py", line 236, in serve_forever
ready = selector.select(poll_interval)
File "~/pyenvs/<redacted>/lib/python3.6/selectors.py", line 376, in select
fd_event_list = self._poll.poll(timeout)
KeyboardInterrupt
Using get_object_as_xml
seems to work kind of within this minified example, but not within my project; I guess I'll need to double-check some things there.
However, there is some small anomaly in the resulting XML-Doc:
<?xml version='1.0' encoding='UTF-8'?>
<soap11env:Envelope xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/">
<soap11env:Body>
<soap11env:Fault>
<faultcode>
soap11env:Server
</faultcode>
<faultstring>
TFault
</faultstring>
<faultactor></faultactor>
<detail>
<TComplex attr="attr-val">
data-val
</TComplex>
</detail>
<detail/> <==== ## double detail element
</soap11env:Fault>
</soap11env:Body>
</soap11env:Envelope>
Looking back at my terminal history, the second, empty </detail>
element is also there when I constructed the lxml.etree manually