Skip to content

Instantly share code, notes, and snippets.

@jose-lpa
Last active October 13, 2015 21:38
Show Gist options
  • Save jose-lpa/4259581 to your computer and use it in GitHub Desktop.
Save jose-lpa/4259581 to your computer and use it in GitHub Desktop.
An (intended to be generic) Python XML client. It can be customized for different types of simple RESTful XML APIs.
from lxml import etree
import requests
class XMLClient(object):
"""
Generic XML client. All the different provider's API implementations must
inherit from it to create its own client implementation.
"""
def __init__(self, username, password, url):
self.username = username
self.password = password
self.url = url
def _envelope(self, resource):
"""
Creates the envelope for the request. One subclass which implements
the XML client for a specific API will override this method and make a
call to `super` to generate the root of the XML document. Then, it can
create the envelope for the corresponding API, adding the username and
the password or whatever
:param resource: Root of the XML document. Usually, this is the name
of the API resource to be called.
:return: `etree.Element` object representing the root of the document.
"""
root = etree.Element(resource)
return root
def _build_xml(self, resource, params=None):
"""
Builds the XML to be sent to provider API.
:return: String containing the XML request.
"""
root = self._envelope(resource)
def read_params(params, parent, position=None):
if position:
position_related = etree.SubElement(parent, position)
else:
position_related = parent
for key in params:
if isinstance(params[key], dict):
read_params(params[key], position_related, position=key)
elif isinstance(params[key], list):
for element in params[key]:
read_params(element, position_related, key)
else:
if position:
related = etree.SubElement(position_related, key)
else:
related = etree.SubElement(root, key)
if isinstance(params[key], tuple):
related.text = unicode(params[key][0])
for attribute in params[key][1]:
related.set(attribute, params[key][1][attribute])
else:
related.text = unicode(params[key])
# Adding parameters to the specific request.
if params:
read_params(params, root)
return etree.tostring(root, encoding='iso-8859-1',
xml_declaration=True)
def request(self, endpoint, resource, params=None):
"""
Performs a request in the remote API using the credentials and URL set
up in settings.
It's intended to override this method in subclasses, since each API
will require a different POST request, this means a different Python
dictionary in the `data` argument of the `requests.post()` call.
:param endpoint: The endpoint of the resource in the API.
:param resource: Name of the resource.
:param params: A dictionary of XML parameters included in the request.
Dictionary must be built in this way: Each key in the dictionary will
be a string representing the XML parameter to be sent (e.g.
'LanguageCode'). The value of each key can be a single string with the
value of the XML parameter or a tuple, containing two elements: the
value of the XML parameter and another dictionary containing the
parameter attributes. This dictionary is totally optional, since not
every parameter in this XML API has extra attributes.
:return: HTTP object containing a string with the full XML response.
"""
xml = self._build_xml(resource, params=params)
response = requests.post(self.url + endpoint, data=xml)
return response
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment