Reputation: 1
related to:
Flask 2.2.2
spyne 2.14.0
zeep 4.2.0
I am building a SOAP service using spyne. This service simulates the SOAP API of a production server and thus, must return exactly the same values, as the production server cant be used for testing.
The client application, that runs against both of these servers is based on zeep and works flawlessly.
The SOAP API has among others two methods, that return iterables. These are lists of integers, that also might be empty. When they are empty, the client expects an explicitely empty list.
Following some spyne examples I implemented these methods as python generators, that will simply return (and by definition thus, not be a generator), if there is no data available.
My expectation was, that this should result in an explicitely empty list, as works very well in this stand-alone example:
def gen(size=0):
if not size:
print("returning")
return
else:
print("iterating")
for i in range(size):
yield i
print("size = 0 ->", list(gen(0)))
print("size = 10 ->", list(gen(10)))
having this output:
returning
size = 0 -> []
iterating
size = 10 -> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Implementing such a functionality in the SOAP-method as well using an Array as an Iterable as return type of the SOAP-method crashes when the list is empty, rsp. the method just returns as in the example above.
When not implementing this as a generator but instead simply returning the respectively populated or empty lists, the client shows a list es return value, if there was data, but the value None, if the returned list is empty.
Running this client against the production server, it explicitely returns an empty list instead of a None value.
So, the question in both scenarios (returning plain lists and/or implementing the method as a generator) is:
How do I properly return empty lists from a method using spyne and how can I do that implementing a generator?
Here come the details and code snippets of the current implementation and attempts to get that running:
This is the generator implementation, that should return, if no data is found - as in the above working example. Note, that in the real implementation the data comes from a database cursor - I simplyfied that on purpose for testing the general implementation paradigma:
@rpc(Unicode, Integer, Unicode, Integer, _returns=Iterable(Integer))
def searchMethod(ctx, some, parameters):
#############
# some code
#############
data = list(range(10))
print ("the data:",data)
if (not len(data)):
print ("returning")
return
else:
print ("iterating")
for item in data:
yield item
this works fine, as long as data is not empty:
127.0.0.1 - - [10/Jan/2023 15:21:07] "GET /service?wsdl HTTP/1.1" 200 -
the data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
iterating
127.0.0.1 - - [10/Jan/2023 15:21:07] "POST /service HTTP/1.1" 200 -
as soon as data is empty, the service crashes:
127.0.0.1 - - [10/Jan/2023 15:21:07] "GET /service?wsdl HTTP/1.1" 200 -
the data: []
returning
127.0.0.1 - - [10/Jan/2023 15:19:00] "POST /service HTTP/1.1" 500 -
Traceback (most recent call last):
File "/home/axel/.local/lib/python3.8/site-packages/flask/app.py", line 2548, in __call__
return self.wsgi_app(environ, start_response)
File "/home/axel/.local/lib/python3.8/site-packages/werkzeug/middleware/dispatcher.py", line 78, in __call__
return app(environ, start_response)
File "/home/axel/.local/lib/python3.8/site-packages/spyne/server/wsgi.py", line 310, in __call__
return self.handle_rpc(req_env, start_response)
File "/home/axel/.local/lib/python3.8/site-packages/spyne/server/wsgi.py", line 450, in handle_rpc
first_obj = next(g)
StopIteration
Not implementing this SOAP method as generator like that:
@rpc(Unicode, Integer, Unicode, Integer, _returns=Iterable(Integer))
def searchMethod(ctx, some, parameters):
#############
# some code
#############
data = list(range(0))
print("the data:", data)
return data
results in this output on the client side for a range(10):
axel@workstation:/$ python test_soap_service.py
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
and for a range(0):
axel@workstation:/$ python test_soap_service.py
None
inside the server it still has the proper lists:
127.0.0.1 - - [10/Jan/2023 15:45:45] "GET /service?wsdl HTTP/1.1" 200 -
the data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
127.0.0.1 - - [10/Jan/2023 15:46:11] "GET /service?wsdl HTTP/1.1" 200 -
the data: []
Any helpful hint will be much appreciated, as I am really lost :-(
Thanks in advance
Axel
Upvotes: 0
Views: 466
Reputation: 9300
Just return this when zero list case, it will works.
yield None
I made the search SOAP service by spyne
the searchMethod
has one parameter called size
, It's decide returned search list size.
I tested two size, one for size is zero,other for size is 10.
from spyne import Application, rpc, ServiceBase, Integer, Iterable
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication
class SearchService(ServiceBase):
@rpc(Integer, _returns=Iterable(Integer))
def searchMethod(ctx, size):
data = list(range(size))
print ("the data:",data)
if (not len(data)):
print ("returning")
yield None
else:
print ("iterating")
for item in data:
yield item
application = Application([SearchService], 'services.search.soap',
in_protocol=Soap11(validator='lxml'),
out_protocol=Soap11())
wsgi_application = WsgiApplication(application)
if __name__ == '__main__':
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://127.0.0.1:8000/?wsdl")
server = make_server('127.0.0.1', 8000, wsgi_application)
server.serve_forever()
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="services.search.soap">
<soapenv:Header/>
<soapenv:Body>
<ser:searchMethod>
<!--Optional:-->
<ser:size>0</ser:size>
</ser:searchMethod>
</soapenv:Body>
</soapenv:Envelope>
Response xml
<?xml version='1.0' encoding='UTF-8'?>
<soap11env:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="services.search.soap">
<soap11env:Body>
<tns:searchMethodResponse>
<tns:searchMethodResult>
<tns:integer xsi:nil="true"/>
</tns:searchMethodResult>
</tns:searchMethodResponse>
</soap11env:Body>
</soap11env:Envelope>
Result in Postman
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="services.search.soap">
<soapenv:Header/>
<soapenv:Body>
<ser:searchMethod>
<!--Optional:-->
<ser:size>10</ser:size>
</ser:searchMethod>
</soapenv:Body>
</soapenv:Envelope>
Response xml
<?xml version='1.0' encoding='UTF-8'?>
<soap11env:Envelope xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="services.search.soap">
<soap11env:Body>
<tns:searchMethodResponse>
<tns:searchMethodResult>
<tns:integer>0</tns:integer>
<tns:integer>1</tns:integer>
<tns:integer>2</tns:integer>
<tns:integer>3</tns:integer>
<tns:integer>4</tns:integer>
<tns:integer>5</tns:integer>
<tns:integer>6</tns:integer>
<tns:integer>7</tns:integer>
<tns:integer>8</tns:integer>
<tns:integer>9</tns:integer>
</tns:searchMethodResult>
</tns:searchMethodResponse>
</soap11env:Body>
</soap11env:Envelope>
Result in Postman
Upvotes: 0