2

I'm trying to create a soap server from WSDL file. I'm using Python 3 and Spyne for generate it. This server need to be specific, because the client already exists. I need that my WSDL request looks like the following

   <soapenv:Body>
      <pus:receiveEvents>
         <!--Optional:-->
         <eventQueryResult>
            <!--Optional:-->
            <queryId>?</queryId>
            <!--Optional:-->
            <queryStatus>?</queryStatus>
            <!--Zero or more repetitions:-->
            <events>
               <!--Optional:-->
               <eid>?</eid>
               <!--Optional:-->
               <eventMessage>?</eventMessage>
               <!--Optional:-->
               <eventSeverity>?</eventSeverity>
               <!--Optional:-->
               <eventTime>?</eventTime>
               <!--Optional:-->
               <eventTypeName>?</eventTypeName>
               <!--Optional:-->
               <meterId>?</meterId>
            </events>
            <subscriptionId>?</subscriptionId>
         </eventQueryResult>
      </pus:receiveEvents>
   </soapenv:Body>

But, this is my WSDL request:

   <soapenv:Body>
      <pus:receiveEvents>
         <!--Optional:-->
         <pus:eventQueryResult>
            <!--Optional:-->
            <pus:queryId>?</pus:queryId>
            <!--Optional:-->
            <pus:queryStatus>?</pus:queryStatus>
         </pus:eventQueryResult>
         <!--Optional:-->
         <pus:events>
            <!--Optional:-->
            <pus:eid>?</pus:eid>
            <!--Optional:-->
            <pus:eventMessage>?</pus:eventMessage>
            <!--Optional:-->
            <pus:eventSeverity>?</pus:eventSeverity>
            <!--Optional:-->
            <pus:eventTime>?</pus:eventTime>
            <!--Optional:-->
            <pus:eventTypeName>?</pus:eventTypeName>
            <!--Optional:-->
            <pus:meterId>?</pus:meterId>
         </pus:events>
         <pus:subscriptionId>
            <!--Optional:-->
            <pus:subscriptionId>?</pus:subscriptionId>
         </pus:subscriptionId>
      </pus:receiveEvents>
   </soapenv:Body>

This is the code that I wrote

    class eventQueryResult(ComplexModel):
    __namespace__ = 'http://pushevent.nbapi.cgms.cisco.com/'

    queryId = String
    queryStatus = String

class events(ComplexModel):
    __namespace__ = 'http://pushevent.nbapi.cgms.cisco.com/'   

    eid = String
    eventMessage = String 
    eventSeverity = String
    eventTime = Long
    eventTypeName = String
    meterId = String

class subscriptionId(ComplexModel):
    __namespace__ = 'http://pushevent.nbapi.cgms.cisco.com/'

    subscriptionId = Long

class EventPushService(ServiceBase):
    __tns__ = 'http://pushevent.nbapi.cgms.cisco.com/'
    __wsdl__ = 'http://schemas.xmlsoap.org/wsdl/'

    @rpc(eventQueryResult.customize(min_occurs=0), events.customize(min_occurs=0), subscriptionId.customize(min_occurs=1,nillable = True), _returns=ResponseData)
    def receiveEvents(ctx, eventQueryResult, events, subscriptionId):
        eid = events.eid

        return print(eid)

I need that eventQueryResult contains events, but I don't know how. The documentation of Spyne doesn't help me with that.

Thanks for your comments

Estefanía
  • 23
  • 5

1 Answers1

0

There's a few things to work on here. The code is pretty incomplete, so some assumptions were made. This is pretty close to the spyne sample app, matched up with what what could be determined from the code snippets you posted.

When you are creating the classes to model your data, you want the eventQueryResult to include events. Arrays are document here. That code would look more like:

class events(ComplexModel):
    eid = String
    eventMessage = String
    eventSeverity = String
    eventTime = Long
    eventTypeName = String
    meterId = String

class eventQueryResult(ComplexModel):
    queryId = String
    queryStatus = String
    events = Array(events)

When you're specifying a RPC, the name of the function is the soapAction, and first is the model of the object the client will be sending you. Here the whole payload is modeled as one object. Bare means that the payload will not be wrapped in an array. Depending on your client, that may need to change.

    @rpc(eventQueryResult, _body_style='bare', _returns=someResponse)

someResponse is the model of the response you want to send back to the client. I didn't really know what you wanted there, how about an array of the events that were accepted? The entire script looks like:

import logging
from spyne import Application, rpc, ServiceBase, \
    String, Long
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication
from spyne.model.complex import ComplexModel, Array

logging.basicConfig(level="WARNING")

class event(ComplexModel):
    eid = String
    eventMessage = String
    eventSeverity = String
    eventTime = Long
    eventTypeName = String
    meterId = String

class eventQueryResult(ComplexModel):
    queryId = String
    queryStatus = String
    events = Array(event)

class someResponse(ComplexModel):
    eventIds = Array(String)

class EventPushService(ServiceBase):
    @rpc(eventQueryResult, _body_style='bare', _returns=someResponse)
    def receiveEvents(ctx, req):
        print(req)

        return someResponse(eventIds = [ i.eid for i in req.events ] )

application = Application([EventPushService],
    tns='http://pushevent.nbapi.cgms.cisco.com/',
    in_protocol=Soap11(validator='lxml'),
    out_protocol=Soap11()
)

if __name__ == '__main__':
    # You can use any Wsgi server. Here, we chose
    # Python's built-in wsgi server but you're not
    # supposed to use it in production.

    from wsgiref.simple_server import make_server
    wsgi_app = WsgiApplication(application)
    server = make_server('0.0.0.0', 80, wsgi_app)
    server.serve_forever()

I was testing with a payload that looks like

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://pushevent.nbapi.cgms.cisco.com/">
  <soapenv:Header/>
  <soapenv:Body>
    <tns:receiveEvents>
      <tns:queryId>100</tns:queryId>
      <tns:queryStatus>Success</tns:queryStatus>
      <tns:events>
        <tns:event>
          <tns:eid>someId</tns:eid>
          <tns:eventMessage>someEventMessage</tns:eventMessage>
          <tns:eventSeverity>someEventSeverity</tns:eventSeverity>
          <tns:eventTime>1614974343</tns:eventTime>
          <tns:eventTypeName>eventType</tns:eventTypeName>
          <tns:meterId>meterStayOnTheMeter</tns:meterId>
        </tns:event>
        <tns:event>
          <tns:eid>someId2</tns:eid>
          <tns:eventMessage>someEventMessage2</tns:eventMessage>
          <tns:eventSeverity>someEventSeverity2</tns:eventSeverity>
          <tns:eventTime>1614974344</tns:eventTime>
          <tns:eventTypeName>eventType2</tns:eventTypeName>
          <tns:meterId>meterStayOnTheMeter2</tns:meterId>
        </tns:event>
      </tns:events>
    </tns:receiveEvents>
  </soapenv:Body>
</soapenv:Envelope>

I was using a curl command like curl localhost -d @payload -H 'SOAPAction: "receiveEvents"' -H "Content-type: text/xml; charset='UTF-8'"

  • Hi Patrick, Thank's for replying. I know that the code exposed were very incomplete, but I thought that it would be sufficient for explain the problem and yes, it was because you answer had very helpful for me. Now I have new ideas for solve the problem. But, I have a new question, do you know what is the meaning of prefix and how can I modify it or documentation for learning about that? – Estefanía Mar 09 '21 at 14:47
  • @Estefanía In your example, `pus` is the xml name space. The prefix doesn't matter provided it references the correct namespace in the object passed. ` ` and ` ` Are functionally equivalent, and your client / server should deserialize them correctly. – Patrick Flaherty Mar 09 '21 at 19:17
  • Thank you, I didn't found information about that. Now is clear for me. I really appreciate your answer. – Estefanía Mar 09 '21 at 19:31
  • @Estefanía I've never had a problem with the auto generated xmlns, so I've never investigated customizing it further. I've used [build_interface_document](http://spyne.io/docs/2.10/reference/server.html#spyne.server.wsgi.WsgiApplication) to deal with http vs https in my services though, and it looks like you may be able to write your own handler there to override the data coming back from the wsdl generator. You probably shouldn't have to unless you have a real clunky client talking to your sever. – Patrick Flaherty Mar 09 '21 at 19:42
  • The client already exists and I'm trying to do my server as similar as I can. Thank you Patrick, this link will be important in my solution. – Estefanía Mar 09 '21 at 20:54