I am using a Python script to receive an XML response from a SOAP web service, and I'd like to extract specific values from the XML response. I'm trying to use the 'untangle' library, but keep getting the following error:
AttributeError: 'None' has no attribute 'Envelope'
Below is a sample of my code. I'm trying to extract the RequestType value from the below
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header/>
<soap:Body>
<Response>\n
<RequestType>test</RequestType>
</Response>
</soap:Body>
</soap:Envelope>
Sample use of untangle
parsed_xml = untangle.parse(xml)
print(parsed_xml.Envelope.Response.RequestType.cdata)
I've also tried parsed_xml.Envelope.Body.Response.RequestType.cdata
This will solve your problem, assuming you want to extract 'test'. By the way, i think your response should not have 'soap:Header/':
import xmltodict
stack_d = xmltodict.parse(response.content)
stack_d['soap:Envelope']['soap:Body']['Response']['RequestType']
I think you will find the xml.etree library to be more usable in this context.
import requests
from xml.etree import ElementTree
Then we need to define the namespaces for the SOAP Response
namespaces = {
'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
'a': 'http://www.etis.fskab.se/v1.0/ETISws',
}
dom = Element.tree.fromstring(response.context)
Then simply find all the DOMs
names = dom.findall('./soap:Body',namespaces)
Related
I'm trying to connect to a RESTful API and I'm hacing problems when building the XML request, for that I'm using Elementree library.
I have an example of the XML I have to send in the request. From that example a build a model and then write the different attributes by code. But the output XML is not exactly like the example I was given and I'm unable to connect to the API.
This is the example I have:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetLoc xmlns="http://abc/Getloc">
<request>
<Access>
<string xmlns="http://bcd/Arrays"></string>
</Access>
<Details xsi:type="Request">
<Postcode ></Postcode >
</Details>
<UserConsent>Yes</UserConsent>
</request>
</GetLoc>
</soap:Body>
</soap:Envelope>
This is my code:
tree = ET.parse('model.xml')
root = tree.getroot()
ns = {'loc':'http://abc/Getloc',\
'arr':http://bcd/Arrays',\
'soapenv':'http://schemas.xmlsoap.org/soap/envelope/', \
'xsi':"http://www.w3.org/2001/XMLSchema-instance", \
xsd': "http://www.w3.org/2001/XMLSchema"}
tree.find('.//arr:string', ns).text = 'THC'
tree.find('.//Postcode ', ns).text = '15478'
This is the output XML (SOAP):
<ns0:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://abc/Getloc" xmlns:ns2="http://bcd/Arrays" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ns0:Body>
<ns1:GetLoc >
<ns1:request>
<ns1:Access>
<ns2:string>THC</ns2:string>
</ns1:Access>
<ns1:Details xsi:type="Request">
<ns1:Postcode >15478</ns1:Postcode >
</ns1:Details>
<ns1:UserConsent>Yes</ns1:UserConsent>
</ns1:request>
</ns1:GetLoc >
</ns0:Body>
</ns0:Envelope>
With the example (first above) I have no problem when connecting to the API. However with the second one I get and error:
" status="Service Not Found. The request may have been sent to an invalid URL, or intended for an unsupported operation." xmlns:l7="http://www.layer7tech.com/ws/policy/fault"/>"
Both XML are sent to the same URL with the same headers and auth. I see both XML equivalent so I was expecting same behavior. I don't understand why it isn't working.
EDIT: The output XML needs to be like
<ns0:Envelope xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://abc/Getloc" xmlns:ns2="http://bcd/Arrays" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ns0:Body>
<ns1:GetLoc >
<ns1:request>
<ns1:Access>
<ns2:string>THC</ns2:string>
</ns1:Access>
<ns1:Details xsi:type="ns1:Request">
<ns1:Postcode >15478</ns1:Postcode >
</ns1:Details>
<ns1:UserConsent>Yes</ns1:UserConsent>
</ns1:request>
</ns1:GetLoc >
</ns0:Body>
</ns0:Envelope>
But I don't know hoy to change the code to get: xsi:type="ns1:Request"
Finally I found the solution myself.
The solution is in here (an incredibly complete article), since I was already using ElementTree. You may find other solutions like using lxml library.
So, for ElementTree I just need to use my own parser instead of the standard ElementTree.parse('file.xml').
The xsi attribute name is handled by the parser, but the parser doesn’t know that the attribute happens to contain a qualified name as well, so it leaves it as is. To be able to handle such a format, you can use a custom parser that knows how to handle certain attributes and elements, or keep track of the prefix mapping for each element.
To do the latter, you can use the iterparse parser, and ask it to report “start-ns” and “end-ns” events. The following snippet adds an ns_map attribute to each element which contains the prefix/URI mapping that applies to that specific element:
def parse_map(file):
events = "start", "start-ns", "end-ns"
root = None
ns_map = []
for event, elem in ET.iterparse(file, events):
if event == "start-ns":
ns_map.append(elem)
elif event == "end-ns":
ns_map.pop()
elif event == "start":
if root is None:
root = elem
elem.ns_map = dict(ns_map)
return ET.ElementTree(root)
This is basically the same question as here:
creating any object with python zeep
I am working on a Python script that includes SOAP API calls. Mostly these are working but I am having a problem with one. I have SOAPUI example working, it looks like this:
soap ui that works
<!-- language: lang-xml -->
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="Urn:ApiService" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<soapenv:Header/>
<soapenv:Body>
<urn:getAllOperationalDevices soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<in0 xsi:type="api:ResourceIdentityInfo" xmlns:api="http://api.common.configmgr.powerup.com">
<resourceKey xsi:type="soapenc:string">0a053ebeddf9e53f53e125d364010000</resourceKey>
<resourceName xsi:type="soapenc:string">Customer1</resourceName>
<resourceType xsi:type="soapenc:string">NETWORK</resourceType>
</in0>
<in1 xsi:type="fil:FilterInfo" xmlns:fil="http://filter.api.common.configmgr.powerup.com">
<filterComponents>
<FilterExpressionInfo xsi:type="ns:FilterExpressionInfo" xmlns:ns="http://filter.api.common.configmgr.powerup.com">
<attributeName>managementIpAddress</attributeName>
<value xsi:type="xsd:string">10.10.10.10</value>
<operator>==</operator>
</FilterExpressionInfo>
</filterComponents>
</in1>
</urn:getAllOperationalDevices>
</soapenv:Body>
</soapenv:Envelope>
I see in the WSDL that the filter value is
AnyType:
And I see in the zeep documentation that there is special handling for AnyType, with the example I copied in my code below:
zeep documentation example of AnyType
client = Client('http://my-entrprisy-endpoint.com')
value = xsd.AnyObject(xsd.String(), 'foobar')
client.service.submit_something(user_id=1, my_string=value)
My attempt to create this request in zeep looks like:
my python that returns error
print("API: getAllOperationalDevices")
in0 = {'resourceKey':nkey,'resourceName':nname,'resourceType':ntype}
value = xsd.AnyObject(xsd.String(),dmgmtip)
filter = {'attributeName':'managementIpAddress','value':value,'operator':'=='}
feInfo = {'FilterExpressionInfo' : [filter]}
filComp = {'filterComponents' : feInfo}
result = client.service.getAllOperationalDevices(in0,filComp)
I think this should be pretty close but I get the error below:
my python error
File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/zeep/xsd/elements/any.py", line 181, in validate
raise exceptions.ValidationError("Missing element for Any")
My attempts to use zeep's client.service.get_element and client.service.get_type are failing because the namespace is not recognized, seems like it should be 'urn:getAllOperationalDevices' but evidently it is not.
If anyone can give me a nudge in the right direction I'd appreciate it!
I am working on a project to extract data via this API (http://www.yourmembership.com/company/api-reference/). The API requires that the calls be made via XML. I have successfully been able to make calls and retrieve data back via HTTP response object using the 'requests' library in python.
My question is I want to develop a function that will iterate through IDs from one API call and plug them into another function which will make iterative API calls to get more data back about each ID.
For example the script I have now gives me a dump of all event IDs:
import requests
xml ="""
<?xml version="1.0" encoding="UTF-8"?>
<YourMembership>
<Version>2.25</Version>
<ApiKey></ApiKey>
<CallID>008</CallID>
<></>
<SaPasscode></SaPasscode>
<Call Method = "Sa.Events.All.GetIDs">
<StartDate>2017/01/1</StartDate>
<EndDate>2017/12/31</EndDate>
</Call>
</YourMembership>
"""
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
r = requests.post('https://api.yourmembership.com', data=xml,headers=headers)
print(r.text)
The response looks like this(no child objects)for 'print(r.text)':
<EventID>12345</EventID>
<EventID>67890</EventID>
<EventID>24680</EventID>
<EventID>13579</EventID>
<EventID>08642</EventID>
How can I take these event_IDs and iterate through the response object and plug them into this function below(function is not complete, rough draft) and get info for each event_ID by making iterative XML API calls by somehow plugging values for the field ?
import requests
def xml_event_info():
xml ="""
<?xml version="1.0" encoding="UTF-8"?>
<YourMembership>
<Version>2.25</Version>
<ApiKey>xxx-xxx</ApiKey>
<CallID>001</CallID>
<></>
<SaPasscode>xxxx</SaPasscode>
<Call Method = "Sa.Events.Event.Get">
<EventID>12345</EventID>
</Call>
</YourMembership>
"""
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
r = requests.post('https://api.yourmembership.com', data=xml,headers=headers)
print(r.text)
Thank you in advance, please let me know if my question does not make sense.
EDIT:
<YourMembership_Response>
<ErrCode>0</ErrCode>
<ExtendedErrorInfo></ExtendedErrorInfo>
<Sa.Events.All.GetIDs>
<EventID>98765</EventID>
</Sa.Events.All.GetIDs>
</YourMembership_Response>
Consider interfacing Python's built-in etree to parse the YM_Response and extract the EventID text as seen in example:
import xml.etree.ElementTree as et
txt="""
<YourMembership_Response>
<ErrCode>0</ErrCode>
<ExtendedErrorInfo></ExtendedErrorInfo>
<Sa.Events.All.GetIDs>
<EventID>98765</EventID>
</Sa.Events.All.GetIDs>
</YourMembership_Response>
"""
dom = et.fromstring(txt)
for i in dom.iterfind('.//EventID'):
print(i.text)
# 98765
Altogether, iteratively call your method passing Event IDs as a parameter that is then string formatted to XML string. See curly brace inside XML string and then .format in the requests line:
import requests
import xml.etree.ElementTree as et
def xml_event_info(eventID):
xml ='''
<?xml version="1.0" encoding="UTF-8"?>
<YourMembership>
<Version>2.25</Version>
<ApiKey>xxx-xxx</ApiKey>
<CallID>001</CallID>
<></>
<SaPasscode>xxxx</SaPasscode>
<Call Method = "Sa.Events.Event.Get">
<EventID>{}</EventID>
</Call>
</YourMembership>
'''
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
r = requests.post('https://api.yourmembership.com',
data=xml.format(eventID), headers=headers)
print(r.text)
xml ='''
<?xml version="1.0" encoding="UTF-8"?>
<YourMembership>
<Version>2.25</Version>
<ApiKey>xxxxx</ApiKey>
<CallID>008</CallID>
<></>
<SaPasscode>xxxx</SaPasscode>
<Call Method = "Sa.Events.All.GetIDs">
<StartDate>2017/01/1</StartDate>
<EndDate>2017/12/31</EndDate>
</Call>
</YourMembership>
'''
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
r = requests.post('https://api.yourmembership.com', data=xml, headers=headers)
# BUILD XML TREE OBJECT
dom = et.fromstring(r.text)
# PARSE EVENT ID TEXT AND PASS INTO FUNCTION
for i in dom.iterfind('.//EventID'):
xml_event_info(i.text)
I'm trying to locate fields in a SOAP xml file using lxml (3.6.0)
...
<soap:Body>
<Request xmlns="http://localhost/">
<Test>
<field1>hello</field1>
<field2>world</field2>
</Test>
</Request>
</soap:Body>
...
In this example I'm trying to find field1 and field2.
I need to add a path to the search term, to find the field:
print (myroot.find(".//{http://localhost/}field1").tag) # prints 'field1'
without it, I don't find anything
print (myroot.find("field1").tag) # finds 'None'
Is there any other way to search for the field tag (here field1) without giving path info?
Full example below:
from lxml import etree
example = """<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body><Request xmlns="http://localhost/">
<Test><field1>hello</field1><field2>world</field2></Test>
</Request></soap:Body></soap:Envelope>
"""
myroot = etree.fromstring(example)
# this works
print (myroot.find(".//{http://localhost/}field1").text)
print (myroot.find(".//{http://localhost/}field2").text)
# this fails
print (myroot.find(".//field1").text)
print (myroot.find("field1").text)
Comment: The input of the SOAP request is given, I can't change any of it in real live to make things easier.
There is a way to ignore namespace when selecting element using XPath, but that isn't a good practice. Namespace is there for a reason. Anyway, there is a cleaner way to reference element in namespace i.e by using namespace prefix that was mapped to the namespace uri, instead of using the actual namespace uri every time :
.....
>>> ns = {'d': 'http://localhost/'}
>>> print (myroot.find(".//d:field1", ns).text)
hello
>>> print (myroot.find(".//d:field2", ns).text)
world
I have an xml string that I need to parse in python that looks like this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<PostLoadsResponse xmlns="http://webservices.truckstop.com/v11">
<PostLoadsResult xmlns:a="http://schemas.datacontract.org/2004/07/WebServices.Objects" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Errors xmlns="http://schemas.datacontract.org/2004/07/WebServices">
<Error>
<ErrorMessage>Invalid Location</ErrorMessage>
</Error>
</Errors>
</PostLoadsResult>
</PostLoadsResponse>
</s:Body>
</s:Envelope>'
I'm having trouble using xmltree to get to the error message of this tree without something like:
import xml.etree.ElementTree as ET
ET.fromstring(text).findall('{http://schemas.xmlsoap.org/soap/envelope/}Body')[0].getchildren()[0].getchildren()[0].getchildren()
You need to handle namespaces and you can do it with xml.etree.ElementTree:
tree = ET.fromstring(data)
namespaces = {
's': 'http://schemas.xmlsoap.org/soap/envelope/',
'd': "http://schemas.datacontract.org/2004/07/WebServices"
}
print(tree.find(".//d:ErrorMessage", namespaces=namespaces).text)
Prints Invalid Location.
Using the partial XPath support:
ET.fromstring(text).find('.//{http://schemas.datacontract.org/2004/07/WebServices}ErrorMessage')
That will instruct it to find the first element named ErrorMessage with namespace http://schemas.datacontract.org/2004/07/WebServices at any depth.
However, it may be faster to use something like
ET.fromstring(text).find('{http://schemas.xmlsoap.org/soap/envelope/}Body').find('{http://webservices.truckstop.com/v11}PostLoadsResponse').find('{http://webservices.truckstop.com/v11}PostLoadsResult').find('{http://schemas.datacontract.org/2004/07/WebServices}Errors').find('{http://schemas.datacontract.org/2004/07/WebServices}Error').find('{http://schemas.datacontract.org/2004/07/WebServices}ErrorMessage'
If you know your message will always contain those elements.
You can use the getiterator method on the tree to iterate through the items in it. You can check the tag on each item to see if it's the right one.
>>> err = [node.text for node in tree.getiterator() if node.tag.endswith('ErrorMessage')]
>>> err
['Invalid Location']