Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial commit with xml and an oauth2 token request helper #84

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions tapioca/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from .exceptions import (
ResponseProcessException, ClientError, ServerError)
from .serializers import SimpleSerializer
from .xml_helpers import (
input_branches_to_xml_bytestring, xml_string_to_etree_elt_dict)


def generate_wrapper_from_adapter(adapter_class):
Expand Down Expand Up @@ -109,3 +111,27 @@ def format_data_to_request(self, data):
def response_to_native(self, response):
if response.content.strip():
return response.json()


class XMLAdapterMixin(object):

def get_request_kwargs(self, api_params, *args, **kwargs):
arguments = super(XMLAdapterMixin, self).get_request_kwargs(
api_params, *args, **kwargs)

if 'headers' not in arguments:
# allows user to override for formats like 'application/atom+xml'
arguments['headers'] = {}
arguments['headers']['Content-Type'] = 'application/xml'
return arguments

def format_data_to_request(self, data):
if data:
return input_branches_to_xml_bytestring(data)

def response_to_native(self, response):
if response.content.strip():
if 'xml' in response.headers['content-type']:
return {'xml': response.content,
'dict': xml_string_to_etree_elt_dict(response.content)}
return {'text': response.text}
49 changes: 49 additions & 0 deletions tapioca/oauth2_token_requester.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from requests_oauthlib import OAuth2Session


class Oauth2TokenRequester(object):

def __init__(self,
client_id,
client_secret,
redirect_uri,
authorization_base_url,
obtain_token_url,
scope_list,
**auth_base_url_kwargs):

self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
self.authorization_base_url = authorization_base_url
self.obtain_token_url = obtain_token_url
self.scope_list = scope_list
self.auth_base_url_kwargs = auth_base_url_kwargs

def authorize_app(self):
'''
Uses requests_oauthlib to request a token and response.
Requires your app to have a redirect_uri set up.

Usage:
o = Oauth2TokenRequester(**kwargs) # '**kwargs' for brevity.
authorization_url = o.authorize_app()
# go to authorization_url to perform authorization with third party
# record redirect_response
token = o.get_token(redirect_response)
'''
self.oauth = OAuth2Session(self.client_id,
scope=self.scope_list,
redirect_uri=self.redirect_uri)
authorization_url, state = self.oauth.authorization_url(self.authorization_base_url,
**self.auth_base_url_kwargs)

return authorization_url

def get_token(self, redirect_response):

self.oauth.fetch_token(self.obtain_token_url,
authorization_response=redirect_response,
client_secret=self.client_secret)

return self.oauth.token
114 changes: 114 additions & 0 deletions tapioca/xml_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from xml.etree import ElementTree
from collections import Mapping
from operator import methodcaller


def flat_dict_to_etree_elt_dict(dict_to_convert, _depth=0):
'''
Convert a special flat format to a dictionary that can be readily mapped to etree.Element.

The special flat format is intended to be similar to XML arguments and easy to type while
reading XML related API documents.

This format does not allow for combinations of text, subelements, and tails. It supports
either text or subelements. If you need more flexibility, use the more general etree_elt_dict
format with the etree_elt_dict_to_xml() method.

A single root node is required.
Double quotes in attribute values are stripped and optional.

Example Input:
{'root|attr1="val 1"': {'subroot1|attr1="sub val 1"': 'sub text 1',
'subroot2|attr2="sub val 2"': 'sub text 2'}}
'''
if not _depth and len(dict_to_convert) > 1:
raise Exception('Multiple root nodes detected, please check input has only one root node.')
node_list = []
for k, v in dict_to_convert.items():
etree_elt_dict = {}
etree_elt_dict['tag'], attrib_list = [k.split('|')[0], k.split('|')[1:]]
etree_elt_dict['attrib'] = {k: v.replace('"', '') for k, v
in map(methodcaller('split', '='), attrib_list)}

# no support for text and subelements for any single node.
if isinstance(v, Mapping):
etree_elt_dict['text'] = None
etree_elt_dict['sub_elts'] = flat_dict_to_etree_elt_dict(dict_to_convert=v,
_depth=_depth + 1)
elif isinstance(v, str):
etree_elt_dict['text'] = v
etree_elt_dict['sub_elts'] = None
else:
raise Exception('Child element not of type Mapping or String')

etree_elt_dict['tail'] = None
node_list.append(etree_elt_dict)
if not node_list:
node_list = None
return node_list if _depth else node_list[0]


def _etree_elt_list_to_xml(etree_elt_list):
'''
Helper method to handle lists of etree_elt_dicts.
'''
return_list = []
# todo: test for required keys
for etree_elt_dict in etree_elt_list:
if not isinstance(etree_elt_dict, Mapping):
raise Exception('Structure must be a Mapping object')
node = ElementTree.Element(etree_elt_dict['tag'],
attrib=etree_elt_dict['attrib'])
node.text = etree_elt_dict['text']
node.tail = etree_elt_dict['tail']
if etree_elt_dict['sub_elts']:
node.extend(_etree_elt_list_to_xml(etree_elt_dict['sub_elts']))
return_list.append(node)
return return_list


def etree_elt_dict_to_xml(etree_elt_dict):
'''
Converts an etree_elt_dict into XML. There must be a single root node.
An etree_elt_dict is designed to readily map onto ElementTree.Element.
'''
if not isinstance(etree_elt_dict, Mapping):
raise Exception('Structure must be a Mapping object')
return ElementTree.tostring(_etree_elt_list_to_xml([etree_elt_dict])[0], encoding='utf-8')


def _etree_node_to_etree_elt_dict(etree_node):
# for output
etree_elt_dict = {}
etree_elt_dict['tag'] = etree_node.tag
etree_elt_dict['attrib'] = etree_node.attrib
etree_elt_dict['text'] = etree_node.text
etree_elt_dict['tail'] = etree_node.tail

sub_elts = [_etree_node_to_etree_elt_dict(n) for n in etree_node]
if sub_elts:
etree_elt_dict['sub_elts'] = sub_elts
else:
etree_elt_dict['sub_elts'] = None
return etree_elt_dict


def xml_string_to_etree_elt_dict(xml_string):
# for output
return _etree_node_to_etree_elt_dict(ElementTree.fromstring(xml_string))


def input_branches_to_xml_bytestring(data):
if type(data) == ElementTree.Element:
return ElementTree.tostring(data, encoding='utf-8')
elif type(data) == str:
return data.encode('utf-8')
elif type(data) == bytes:
return data
elif type(data) == dict:
if 'tag' in data.keys():
return etree_elt_dict_to_xml(data)
else:
return etree_elt_dict_to_xml(flat_dict_to_etree_elt_dict(data))
else:
raise Exception('Format not recognized')
Loading