The API I am working with requires that I add a POST body when requesting the wsdl client. I'm using Zeep so it will look like this:
client = Client(wsdl=url, settings=s)
How would I add a post body at this point? I checked the settings documentation and it doesn't include a way for me to that. Is there an alternative way?
If that's not possible by the library, is it possible for me to make the request using the standard python request library and use the Zeep client to process the response?
UPDATE
Here is a sample payload I need to include. Unfortunately, I can't shared the exact URL I using.
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:int="http://acme.com/integration/">
<soap:Header>
<int:AcmeNetOptions>
<int:ImposeConcurrencyId>true</int:ImposeConcurrencyId>
<int:UpdateLastModified>true</int:UpdateLastModified>
<int:CanDeleteMissingEntity>true</int:CanDeleteMissingEntity>
<int:LockOnDataRetrieval>Default</int:LockOnDataRetrieval>
</int:AcmeNetOptions>
</soap:Header>
<soap:Body>
<int:Login>
<int:username>username</int:username>
<int:password>password</int:password>
<int:companyname>company</int:companyname>
</int:Login>
</soap:Body>
Related
here is my xml:
<?xml version="1.0"?>
<soapenv:Envelope>
<soapenv:Header xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<wsse:Security soap:mustUnderstand="1" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-1" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>USERNAME</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">1234</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<root xmlns="http://xmlns.oracle.com/Enterprise/tools/schema/InfoRtRequest.v1">
<EMAIL>david</EMAIL>
</root>
</soapenv:Body>
</soapenv:Envelope>
here is my demo:
wsdl = ''
client = Client(
wsdl,
wsse=UsernameToken('USERNAME', '1234'))
response = client.service.get_method(
EMAIL='david')
it raised VadlidationError:
ValidationError: Missing element OPRID (root.OPRID)
I don't know why, please give me some help, thanks.
zeep is advanced library to handle SOAP communications in python. You should provide wsdl file, so that your issue can be better analyzed.
But by looking into the xml request you have provided, it seems the authentication is been done using headers and data is been sent in body. Similar to the usecase i have recently fixed. Refer my xml request of my use case below.
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Header>
<ns0:myheaders xmlns:ns0="xxxxxx_stackoverflow_mask_xxxxxx">
<ns0:username>xxxxxx_stackoverflow_mask_xxxxxx</ns0:username>
<ns0:password>xxxxxx_stackoverflow_mask_xxxxxx</ns0:password>
</ns0:myheaders>
</soap-env:Header>
<soap-env:Body>
<ns0:Search02c xmlns:ns0="xxxxxx_stackoverflow_mask_xxxxxx">
<ns0:name>
<ns0:title>Mr</ns0:title>
<ns0:forename>Srikanth</ns0:forename>
<ns0:surname>Badveli</ns0:surname>
</ns0:name>
</ns0:Search02c>
</soap-env:Body>
</soap-env:Envelope>
For the above xml, the code is as follows
from zeep import Client
header_credentials = {'username':'xxxxx','password':'xxxxx'}
tac_data = {'name': {'title':'xxxxx','forename':'xxxxx','surname':'xxxxx'}}
client = Client(wsdl=wsdl)
response = client.service.Search02c(tac_data, _soapheaders={'callcreditheaders':header_credentials})
In the above code, "Search02c" is the operation name for the service. Operation name can be found while inspecting the wsdl file. In my usecase "Search02c" accepts 2 arguments which are body and header."tac_data" is the dictionary of the xml body(not header) and "header_credentials" is the dictionary of the credentials. Your use case might accept single argument clubbing header and body. The arguments structure can be found after operation name in the inspected wsdl file.
You can find the operation name and its structure in the end of the output by running this in your command prompt.
python -mzeep wsdl_file_path.wsdl
The operation for my wsdl file is below.
Operations:
Search02c(searchDefinition: tac_data, _soapheaders={'headers': header_credentials}) -> outputResult: ns1:output
Remember, zeep only accepts dictionary as input data and provides dictionary as output. If you like to receive response as xml, use raw_response=True in the client settings.
For more information, please refer zeep documentation
I am trying to access the Webservice of the Business Register of the Swiss Federal Government (https://www.bfs.admin.ch/bfs/de/home/register/unternehmensregister/unternehmens-identifikationsnummer/uid-register/uid-schnittstellen.assetdetail.1760903.html)
However their documentation is so complex and weird that I have no clue on how to build the request that it works.
I am rather familiar with REST APIs but SOAP is new to me.
Using SoapUI I managed to build a working request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:uid="http://www.uid.admin.ch/xmlns/uid-wse" xmlns:ns="http://www.ech.ch/xmlns/eCH-0108-f/3" xmlns:ns1="http://www.ech.ch/xmlns/eCH-0098-f/3" xmlns:ns2="http://www.ech.ch/xmlns/eCH-0097-f/2" xmlns:ns3="http://www.ech.ch/xmlns/eCH-0046-f/3" xmlns:ns4="http://www.ech.ch/xmlns/eCH-0044-f/4" xmlns:ns5="http://www.ech.ch/xmlns/eCH-0010-f/6" xmlns:ns6="http://www.ech.ch/xmlns/eCH-0007-f/6">
<soapenv:Header/>
<soapenv:Body>
<uid:Search>
<uid:searchParameters>
<ns:organisation>
<ns1:organisationIdentification>
<ns2:organisationName>Beekeeper</ns2:organisationName>
</ns1:organisationIdentification>
</ns:organisation>
</uid:searchParameters>
</uid:Search>
</soapenv:Body>
</soapenv:Envelope>
But I am failing to implement this request in Python. Whatever tutorials I look up i find things such as
request_data = client.factory.create('s1:CityWeatherRequest')
request_data.City = "Stockholm"
What would the methods be to build the request as above?
How do I create nested requests to SOAP?
You should have a look at pyxb
When you have built up Python bindings based on wsdl schema or xsd files, you can execute the code below to make the request. The wsld shows what your actions will be.
from pyxb.utils.six.moves.urllib import request as urllib_request
import pyxb.bundles.wssplat.soap11 as soapenv
# Create a SOAP envelope from XML
myenv = soapenv.Envelope(soapenv.Body(myxmlreq))
# Create URL, headers and body for the HTTP request
url="http://www.test.com"
headers = {'Content-Type': 'text/xml;charset=UTF-8',
'Accept-Encoding': 'gzip,deflate',
'SOAPAction': "http://test.com/API/Action", # See documentation to learn about the actions
'Connection':'close'} # set what your server accepts
body = myenv.toxml("utf-8")
# Create the request, send it and receive the response as XML
uri = urllib_request.Request(url,data=body,headers=headers)
rxml = urllib_request.urlopen(uri).read() # This is the response XML
I am using Suds to access Sharepoint lists through soap, but I am having some trouble with malformed soap.
I am using the following code:
from suds.client import Client
from suds.sax.element import Element
from suds.sax.attribute import Attribute
from suds.transport.https import WindowsHttpAuthenticated
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger('suds.client').setLevel(logging.DEBUG)
ntlm = WindowsHttpAuthenticated(username='somedomain\\username', password='password')
url = "http://somedomain/sites/somesite/someothersite/somethirdsite/_vti_bin/Lists.asmx?WSDL"
client = Client(url, transport=ntlm)
result = client.service.GetListCollection()
print repr(result)
Every time I run this, I get the result Error 400 Bad request. As I have debugging enabled I can see the resulting envelope:
<SOAP-ENV:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://schemas.microsoft.com/sharepoint/soap/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<ns0:Body>
<ns1:GetListCollection/>
</ns0:Body>
</SOAP-ENV:Envelope>
...with this error message:
DEBUG:suds.client:http failed:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Bad Request</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Bad Request</h2>
<hr><p>HTTP Error 400. The request is badly formed.</p>
</BODY></HTML>
Running the same WSDL (and raw envelope data as well) through SoapUI the request returns with values as expected. Can anyone see any obvious reason why I get the different results with Suds as SoapUI and how I can correct this?
UPDATE: after testing the exact same code on a different Sharepoint site (i.e. not a subsubsubsite with whitespace in its name) and with Java (JAX-WS, which also had issues with the same site, though, different issues) it appears as if it works as expected. As a result I wonder if one of two details may be the reason for these problems:
SOAP implementations have some issues with subsubsubsites in Sharepoint?
SOAP implementations have some issues with whitespace in its name, even if using %20 as a replacement?
I still have the need to use the original URL with those issues, so any input would be highly appreciated. I assume that since SoapUI worked with the original url, it should be possible to correct whatever is wrong.
I think I narrowed down the issue, and it is specific to suds (possibly other SOAP implementations as well). Your bullet point:
SOAP implementations have some issues with whitespace in its name, even if using %20 as a replacement?
That's spot on. Turning up debug logging for suds allowed me to grab the endpoint, envelope, and headers. Mimicking the exact same call using cURL returns a valid response, but suds it throws the bad request.
The issue is that suds takes your WSDL (url parameter) and parses it, but it doesn't include the URL encoded string. This leads to debug messages like this:
DEBUG:suds.transport.http:opening (https://sub.site.com/sites/Site Collection with Spaces/_vti_bin/UserGroup.asmx?WSDL)
<snip>
TransportError: HTTP Error 400: Bad Request
Piping this request through a fiddler proxy showed that it was running the request against the URL https://sub.site.com/sites/Site due to the way it parses the WSDL. The issue is that you aren't passing the location parameter to suds.client.Client. The following code gives me valid responses every time:
from ntlm3 import ntlm
from suds.client import Client
from suds.transport.https import WindowsHttpAuthenticated
# URL without ?WSDL
url = 'https://sub.site.com/sites/Site%20Collection%20with%20Spaces/_vti_bin/Lists.asmx'
# Create NTLM transport handler
transport = WindowsHttpAuthenticated(username='foo',
password='bar')
# We use FBA, so this forces it to challenge us with
# a 401 so WindowsHttpAuthenticated can take over.
msg = ("%s\\%s" % ('DOM', 'foo'))
auth = 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(msg).decode('ascii')
# Create the client and append ?WSDL to the URL.
client = Client(url=(url + "?WSDL"),
location=url,
transport=transport)
# Add the NTLM header to force negotiation.
header = {'Authorization': auth}
client.set_options(headers=header)
One caveat: Using quote from urllib works, but you cannot encode the entire URL or it fails to recognize the URL. You are better off just doing a replace on spaces with %20.
url = url.replace(' ','%20')
Hope this keeps someone else from banging their head against the wall.
Is it possible to use Python's requests library to send a SOAP request?
It is indeed possible.
Here is an example calling the Weather SOAP Service using plain requests lib:
import requests
url="http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL"
#headers = {'content-type': 'application/soap+xml'}
headers = {'content-type': 'text/xml'}
body = """<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns0="http://ws.cdyne.com/WeatherWS/" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<ns1:Body><ns0:GetWeatherInformation/></ns1:Body>
</SOAP-ENV:Envelope>"""
response = requests.post(url,data=body,headers=headers)
print response.content
Some notes:
The headers are important. Most SOAP requests will not work without the correct headers. application/soap+xml is probably the more correct header to use (but the weatherservice prefers text/xml
This will return the response as a string of xml - you would then need to parse that xml.
For simplicity I have included the request as plain text. But best practise would be to store this as a template, then you can load it using jinja2 (for example) - and also pass in variables.
For example:
from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader('myapp', 'templates'))
template = env.get_template('soaprequests/WeatherSericeRequest.xml')
body = template.render()
Some people have mentioned the suds library. Suds is probably the more correct way to be interacting with SOAP, but I often find that it panics a little when you have WDSLs that are badly formed (which, TBH, is more likely than not when you're dealing with an institution that still uses SOAP ;) ).
You can do the above with suds like so:
from suds.client import Client
url="http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL"
client = Client(url)
print client ## shows the details of this service
result = client.service.GetWeatherInformation()
print result
Note: when using suds, you will almost always end up needing to use the doctor!
Finally, a little bonus for debugging SOAP; TCPdump is your friend. On Mac, you can run TCPdump like so:
sudo tcpdump -As 0
This can be helpful for inspecting the requests that actually go over the wire.
The above two code snippets are also available as gists:
SOAP Request with requests
SOAP Request with suds
Adding up to the last answer, make sure you add to the headers the following attributes:
headers={"Authorization": f"bearer {token}", "SOAPAction": "http://..."}
The authorization is meant when you need some token to access the SOAP API,
Otherwise, the SOAPAction is the action you are going to perform with the data you are sending in,
So if you don't need Authorization, then you could pop that out of the headers,
That worked pretty fine for me,
I'm trying to use the Zimbra SOAP API from Python to programmatically modify & cleanup my contacts but I'm having trouble getting started.
What I have so far is:
from SOAPpy import SOAPProxy
url = 'https://zimbra/service/soap'
auth = {"account": "xxxxx", "password": "xxxxx"}
zimbra = SOAPProxy(url, 'urn:zimbra')
zimbraAuth = SOAPProxy(url, "urn:zimbraAccount")
zimbraMail = SOAPProxy(url, "urn:zimbraMail")
response = zimbraAuth.AuthRequest(**auth)
authToken = response.authToken
I've logged in successfully, but can't pass this authToken in further requests.
My understanding is that any zimbraMail requests need to have a header in the urn:zimbra namespace with the authToken set there, but being new to SOAP I have no idea how to do so.
I'm not married to using SOAPpy so any example code for another library would be well appreciated.
I'd like to nod in the direction of Python-Zimbra, a python library for Zimbra, which does all that for you.
There is also an higher level, and pythonic library: zimsoap.
Using raw SOAP or python-zimbra or zimsoap depends realy on what level of flexibility/ease you need (although I will not suggest using SOAP directly, as python-zimbra can issue any possible SOAP request to zimbra.