suds-community / suds

Suds is a lightweight SOAP python client for consuming Web Services. A community fork of the jurko fork.
https://suds.readthedocs.io/
GNU Lesser General Public License v3.0
172 stars 54 forks source link

How to pass XML string to SOAP call using Suds for Python3 #30

Closed gcobr closed 2 years ago

gcobr commented 4 years ago

Using suds-py3.

When loading the following WSDL:

https://clientdev.inteflow.com.au/inteport/DecisionGateway.asmx?WSDL

I get:

(DecisionWebGatewaySoap12)
         Methods (38):
            [...]
            ExecuteXMLRequest(_sRequestXML _sRequestXML, )

The types list contains all this, but nothing called _sRequestXML (which should be treated as a string, I reckon):

   Types (141):
      AccessGroup
      Address
      ns3:AddressInformation
      Applicant
      ns2:Array
      ArrayOfAccessGroup
      ArrayOfApplicant
      ns3:ArrayOfAttachment
      ArrayOfCriteria
      ns3:ArrayOfCustomField
      ns3:ArrayOfDocumentPDF
      ns3:ArrayOfDocumentStatus
      ns3:ArrayOfFormDataXfdfField
      ArrayOfMerchantCommission
      ArrayOfMerchantOperator
      ArrayOfMerchantParameter
      ArrayOfMerchantProductGroup
      ArrayOfMerchantProductv2
      ArrayOfOperatorAccessGroup
      ArrayOfOperatorMerchant
      ArrayOfOperatorProductGroup
      ns3:ArrayOfRecipientStatus
      ArrayOfString
      ns3:ArrayOfString
      ns3:ArrayOfTabStatus
      ArrayOfURL
      ns3:Attachment
      Authentication
      ns3:AuthenticationStatus
      BasicInsuranceProduct
      BasicMerchant
      BasicMerchantOperator
      BasicOperator
      CompanyDetails
      Contact
      CreateInsuranceProductResponseData
      CreateMerchantResponseData
      Criteria
      ns3:CustomField
      ns3:CustomFieldType
      ns3:CustomTabType
      ns3:DOBInformation
      DateFormat
      DefaultValue
      ns3:DeliveryMethod
      ns3:DocuSignEnvelopeInformation
      ns3:DocumentPDF
      ns3:DocumentStatus
      ns3:DocumentType
      ns2:ENTITIES
      ns2:ENTITY
      ns3:EnvelopeStatus
      ns3:EnvelopeStatusCode
      ns3:EventResult
      ns3:EventStatusCode
      ExecuteResponse
      FinanceRequest
      Financial
      ns3:FormData
      ns3:FormDataXfdf
      ns3:FormDataXfdfField
      ns2:ID
      ns3:IDCheckInformation
      ns2:IDREF
      ns2:IDREFS
      IndividualDetails
      LimitedLoginResponseData
      Merchant
      MerchantCommission
      MerchantDetails
      MerchantOperator
      MerchantParameter
      MerchantProductGroup
      MerchantProductv2
      ns2:NCName
      ns2:NMTOKEN
      ns2:NMTOKENS
      ns2:NOTATION
      ns2:Name
      Operator
      OperatorAccessGroup
      OperatorDetails
      OperatorFlag
      OperatorMerchant
      OperatorProductGroup
      Options
      Payload
      PayloadSubmit
      ns2:QName
      ns3:RecipientStatus
      ns3:RecipientStatusCode
      ns3:RecipientStatusEsignAgreementInformation
      ns3:RecipientTypeCode
      RequestData
      RequestResponseData
      ResponseData
      ns3:SSN4Information
      SecondaryLogin
      ns3:SigningLocationCode
      StatusCheck
      ns1:StringArray
      ns2:Struct
      ns3:TabStatus
      ns3:TabTypeCode
      URL
      ns3:VaultingDetails
      ns2:anyURI
      ns2:arrayCoordinate
      ns2:base64
      ns2:base64Binary
      ns2:boolean
      ns2:byte
      ns2:date
      ns2:dateTime
      ns2:decimal
      ns2:double
      ns2:duration
      ns2:float
      ns2:gDay
      ns2:gMonth
      ns2:gMonthDay
      ns2:gYear
      ns2:gYearMonth
      ns2:hexBinary
      ns2:int
      ns2:integer
      ns2:language
      ns2:long
      ns2:negativeInteger
      ns2:nonNegativeInteger
      ns2:nonPositiveInteger
      ns2:normalizedString
      ns2:positiveInteger
      ns2:short
      ns2:string
      ns2:time
      ns2:token
      ns2:unsignedByte
      ns2:unsignedInt
      ns2:unsignedLong
      ns2:unsignedShort

Then, when I do:

v = client.service.ExecuteXMLRequest("<inteflow><something>value</something></inteflow>")

The SOAP string I passed to ExecuteXMLRequest is not set as the value of ns1:ExecuteXMLRequest. Instead, the element is empty:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://decisionintellect.com/inteport/">
   <SOAP-ENV:Header/>
   <ns0:Body>
      <ns1:ExecuteXMLRequest/>
   </ns0:Body>
</SOAP-ENV:Envelope>

I need Suds to produce an envelope like this instead:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://decisionintellect.com/inteport/">
   <SOAP-ENV:Header/>
   <ns0:Body>
      <ns1:ExecuteXMLRequest>
            <ns1:_sRequestXML>
                  <inteflow><something>value</something></inteflow>
            </ns1:_sRequestXML>
      </ns1:ExecuteXMLRequest>
   </ns0:Body>
</SOAP-ENV:Envelope>

Is there a problem with the WSDL? Should I pass the parameter in some other way?

Full example:

import ssl

from suds.client import Client

if hasattr(ssl, '_create_unverified_context'):
    ssl._create_default_https_context = ssl._create_unverified_context

url = 'https://clientdev.inteflow.com.au/inteport/DecisionGateway.asmx?WSDL'
client = Client(url)
client.set_options(port='DecisionWebGatewaySoap12')

print(client)

v = client.service.ExecuteXMLRequest("<inteflow><something>value</something></inteflow>")
print(client.last_sent())

print(v)
phillbaker commented 4 years ago

@gcobr if you're using suds-py3, that fork of the upstream suds repo has diverged substantially from this repo which is based on the suds-jurko fork.

phillbaker commented 4 years ago

@gcobr for what it's worth, I've seen similar oddities in other APIs. One way to accomplish sending the raw XML is to use the __inject key. For example:

v = client.service.ExecuteXMLRequest(__inject={'msg':b"<inteflow><something>value</something></inteflow>"})

You might also be able to construct the XML and pass it in via the suds.sax.text.Raw type.

gcobr commented 4 years ago

@gcobr for what it's worth, I've seen similar oddities in other APIs. One way to accomplish sending the raw XML is to use the __inject key. For example:

v = client.service.ExecuteXMLRequest(__inject={'msg':b"<inteflow><something>value</something></inteflow>"})

You might also be able to construct the XML and pass it in via the suds.sax.text.Raw type.

@phillbaker I experimented with __inject, and it replaces the entire SOAP envelope instead of injecting the desired content into the body.

I also tried client.service.ExecuteXMLRequest(suds.sax.text.Raw("<inteflow><something>value</something></inteflow>")), which doesn't inject anything into the body either.

phillbaker commented 4 years ago

@gcobr you can also write a plugin that hooks in to the marshalling process, see the example in: https://github.com/suds-community/suds#messageplugin and https://bitbucket.org/jurko/suds/issues/138/sax-element-prexif-namespaces-handling#comment-44111703

ciscomonkey commented 4 years ago

The way I do this is:

from suds.sax.text import Raw

fields = ''
for k, v in iter(customFields.items()):
  if v:
    field = Raw(f'<customField identifier="{k}">{v}</customField>')
  else:
    field = Raw(f'<customField identifier="{k}"/>')

  fields += field

self.service.methodName(auth=self.auth, customFields=fields)
phillbaker commented 4 years ago

Another option:

from suds.client import _SoapClient, Client
client = Client(...)
method = client.wsdl.services[0].ports[0].methods[operation_name]
message = method.binding.input.get_message(method, args, kwargs) # or build your own
sc = SoapClient(method.client, method)
sc.send(message)

History of this in suds-jurko/suds: