Skip to content

Commit 92aaeda

Browse files
author
Evan Borgstrom
committed
Refactor for style & simplicity. Also fix TaxRates.
PEP-8 & PEP-20 Our original implementation was very complicated as it went through many revisions while we were building it. This simplifies and refactors the Manager class. Also, TaxRate needed to be listed in MULTI_LINES so that all the rates would be returned.
1 parent 239d656 commit 92aaeda

File tree

1 file changed

+76
-73
lines changed

1 file changed

+76
-73
lines changed

xero/__init__.py

Lines changed: 76 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,27 @@ class XeroExceptionUnknown(XeroException):
2222
pass
2323

2424
class Manager(object):
25-
2625
DECORATED_METHODS = ('get', 'save', 'filter', 'all', 'put')
27-
DATETIME_FIELDS = (u'UpdatedDateUTC',)
28-
BOOLEAN_FIELDS = (u'IsSupplier', u'IsCustomer')
29-
MULTY_LINES = (u'LineItem', u'Phone', u'Address')
26+
DATETIME_FIELDS = (u'UpdatedDateUTC',)
27+
BOOLEAN_FIELDS = (u'IsSupplier', u'IsCustomer')
28+
MULTI_LINES = (u'LineItem', u'Phone', u'Address', 'TaxRate')
3029
PLURAL_EXCEPTIONS = {'Addresse':'Address'}
3130

3231
def __init__(self, name, client):
33-
self.client = client
34-
self.__name__ = name
35-
self.__list_word = name[:len(name)-1].title()
36-
self.__set_decorators()
32+
self.client = client
33+
self.name = name
34+
35+
# setup our singular variants of the name
36+
# only if the name ends in 0
37+
if name[-1] == "s":
38+
self.singular = name[:len(name)-1]
39+
else:
40+
self.singular = name
3741

38-
def __set_decorators(self):
3942
for method_name in self.DECORATED_METHODS:
4043
method = getattr(self, method_name)
4144
setattr(self, method_name, self.__get_data(method))
4245

43-
def get_url_postfix(self):
44-
return self.__name__.title()
45-
46-
def get_not_plural(self):
47-
return self.__name__[:len(self.__name__)-1].title()
48-
4946
def walk_dom(self, dom):
5047
tree_list = tuple()
5148
for node in dom.childNodes:
@@ -67,13 +64,13 @@ def convert_to_dict(self, deep_list):
6764

6865
if len(data) == 1:
6966
out[key] = data[0]
70-
elif len(data) > 1 and key in self.MULTY_LINES and out:
67+
elif len(data) > 1 and key in self.MULTI_LINES and out:
7168
out += (self.convert_to_dict(data),)
72-
elif len(data) > 1 and key in self.MULTY_LINES:
69+
elif len(data) > 1 and key in self.MULTI_LINES:
7370
out = (self.convert_to_dict(data),)
74-
elif len(data) > 1 and key == self.__list_word and out:
71+
elif len(data) > 1 and key == self.singular and out:
7572
out += (self.convert_to_dict(data),)
76-
elif len(data) > 1 and key == self.__list_word:
73+
elif len(data) > 1 and key == self.singular:
7774
out = (self.convert_to_dict(data),)
7875
elif len(data) > 1:
7976
out[key] = self.convert_to_dict(data)
@@ -122,7 +119,7 @@ def dict_to_xml( self, root_elm, dict_data ):
122119

123120
elif _list_data:
124121
for _d in _data:
125-
_plural_name = self.PLURAL_EXCEPTIONS.get(_plural_name, _plural_name)
122+
_plural_name = self.PLURAL_EXCEPTIONS.get(_plural_name, _plural_name)
126123
__elm = self.dict_to_xml(SubElement(_elm, _plural_name), _d)
127124

128125
else:
@@ -131,31 +128,32 @@ def dict_to_xml( self, root_elm, dict_data ):
131128
return root_elm
132129

133130
def __prepare_data__for_save(self, data):
134-
name = self.get_url_postfix()
135131
if isinstance(data, list) or isinstance(data, tuple):
136-
root_elm = Element(name)
132+
root_elm = Element(self.name)
137133
for d in data:
138-
sub_elm = SubElement(root_elm, self.get_not_plural())
134+
sub_elm = SubElement(root_elm, self.singular)
139135
self.dict_to_xml(sub_elm, d)
140136
else:
141-
root_elm = self.dict_to_xml(Element(self.get_not_plural()), data)
137+
root_elm = self.dict_to_xml(Element(self.singular), data)
142138

143139
return tostring(root_elm)
144140

145141
def __get_results(self, data):
146-
name = self.get_url_postfix()
147142
response = data[u'Response']
148-
result = response.get(name, {})
149-
single = name[:len(name)-1]
150-
return result if isinstance(result, tuple) else result[single] \
151-
if result.has_key(single) else None
143+
result = response.get(self.name, {})
144+
145+
if isinstance(result, tuple):
146+
return result
147+
148+
if isinstance(result, dict) and result.has_key(self.singular):
149+
return result[self.singular]
152150

153151
def __get_data(self, func):
154152
def wrapper(*args, **kwargs):
155153
req_args = func(*args, **kwargs)
156154
response = self.client.request(*req_args)
157-
body = response[1]
158-
headers = response[0]
155+
body = response[1]
156+
headers = response[0]
159157
if headers['status'] == '200':
160158
if headers['content-type'] == 'application/pdf':
161159
return body
@@ -186,76 +184,81 @@ def wrapper(*args, **kwargs):
186184
return wrapper
187185

188186
def get(self, id, headers=None):
189-
name = self.get_url_postfix()
190-
uri = '/'.join([XERO_API_URL, name, id])
187+
uri = '/'.join([XERO_API_URL, self.name, id])
191188
return uri, 'GET', None, headers
192189

193-
def __save_data(self, data, method='PUT'):
194-
headers = {"Content-Type" :
195-
"application/x-www-form-urlencoded; charset=utf-8"}
196-
name = self.get_url_postfix()
197-
uri = '/'.join([XERO_API_URL, name])
190+
def save(self, data, method='post'):
191+
headers = {
192+
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"
193+
}
194+
uri = '/'.join([XERO_API_URL, self.name])
198195
body = 'xml='+urllib.quote(self.__prepare_data__for_save(data))
199196
return uri, method, body, headers
200197

201-
def save(self, data):
202-
return self.__save_data(data, method='post')
203-
204198
def put(self, data):
205-
return self.__save_data(data, method='PUT')
206-
207-
def get_filter_params(self, key, val):
208-
if key in self.BOOLEAN_FIELDS:
209-
return 'true' if val else 'false'
210-
elif key in self.DATETIME_FIELDS:
211-
return val.isoformat()
212-
else:
213-
return '"%s"' % str(val)
199+
return self.save(data, method='PUT')
214200

215201
def prepare_filtering_date(self, val):
216-
isdt = isinstance(val, datetime)
217-
val = val.strftime('%a, %d %b %Y %H:%M:%S GMT') if isdt else '"%s"' % val
218-
return {'If-Modified-Since' : val}
202+
if isinstance(val, datetime):
203+
val = val.strftime('%a, %d %b %Y %H:%M:%S GMT')
204+
else:
205+
val = '"%s"' % val
206+
return {'If-Modified-Since': val}
219207

220208
def filter(self, **kwargs):
221209
headers = None
222-
name = self.get_url_postfix()
223-
uri = '/'.join([XERO_API_URL, name])
210+
uri = '/'.join([XERO_API_URL, self.name])
224211
if kwargs:
225212
if kwargs.has_key('Since'):
226213
val = kwargs['Since']
227214
headers = self.prepare_filtering_date(val)
228215
del kwargs['Since']
229216

230-
params = ['%s==%s' % (key.replace('_','.'),
231-
self.get_filter_params(key, kwargs[key])) \
232-
for key in kwargs.keys()]
217+
def get_filter_params():
218+
if key in self.BOOLEAN_FIELDS:
219+
return 'true' if kwargs[key] else 'false'
220+
elif key in self.DATETIME_FIELDS:
221+
return kwargs[key].isoformat()
222+
else:
223+
return '"%s"' % str(kwargs[key])
224+
225+
def generate_param(key):
226+
return '%s==%s' % (
227+
key.replace('_','.'),
228+
get_filter_params()
229+
)
230+
231+
params = [generate_param(key) for key in kwargs.keys()]
233232

234233
if params:
235234
uri += '?where=' + urllib.quote('&&'.join(params))
236235

237236
return uri, 'GET', None, headers
238237

239238
def all(self):
240-
name = self.get_url_postfix()
241-
uri = '/'.join([XERO_API_URL, name])
239+
uri = '/'.join([XERO_API_URL, self.name])
242240
return uri, 'GET', None, None
243241

244242
class Xero(object):
245-
""" Main object for retriving data from XERO """
243+
"""
244+
An ORM interface to the Xero API
245+
246+
This has only been tested with the Private API
247+
"""
246248

247-
INSTANCES__LIST = (u'Contacts', u'Accounts', u'CreditNotes',
248-
u'Currencies', u'Invoices', u'Organisation',
249-
u'Payments', u'TaxRates', u'TrackingCategories')
249+
OBJECT_LIST = (u'Contacts', u'Accounts', u'CreditNotes',
250+
u'Currencies', u'Invoices', u'Organisation',
251+
u'Payments', u'TaxRates', u'TrackingCategories')
250252

251253
def __init__(self, consumer_key, consumer_secret, privatekey):
252-
self.consumer_key = consumer_key
253-
self.consumer_secret = consumer_secret
254-
self.privatekey = privatekey
255-
self.client = XeroPrivateClient(consumer_key, consumer_secret,
256-
privatekey)
257-
self.__set_managers(self.client)
258-
259-
def __set_managers(self, client):
260-
for name in self.INSTANCES__LIST:
254+
# instantiate our private api client
255+
client = XeroPrivateClient(consumer_key,
256+
consumer_secret,
257+
privatekey)
258+
259+
# iterate through the list of objects we support, for
260+
# each of them create an attribute on our self that is
261+
# the lowercase name of the object and attach it to an
262+
# instance of a Manager object to operate on it
263+
for name in self.OBJECT_LIST:
261264
setattr(self, name.lower(), Manager(name, client))

0 commit comments

Comments
 (0)