Last active
October 13, 2015 21:38
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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