1 |
douglas |
1180 |
# DT Wesabe |
2 |
douglas |
1178 |
# |
3 |
|
|
# Douglas Thrift |
4 |
|
|
# |
5 |
|
|
# $Id$ |
6 |
|
|
|
7 |
douglas |
1180 |
from datetime import datetime |
8 |
|
|
import decimal |
9 |
douglas |
1178 |
from M2Crypto import m2urllib2, SSL |
10 |
douglas |
1180 |
import urllib2 |
11 |
|
|
from xml.etree import ElementTree |
12 |
douglas |
1178 |
|
13 |
douglas |
1180 |
EPOCH = datetime.utcfromtimestamp(0) |
14 |
|
|
ISO_8601 = '%Y-%m-%dT%H:%M:%SZ' |
15 |
|
|
|
16 |
|
|
class Account(object): |
17 |
|
|
def __init__(self, account): |
18 |
|
|
self.id = int(account.find('id').text) |
19 |
|
|
self.guid = account.find('guid').text |
20 |
|
|
account_number = account.find('account-number') |
21 |
|
|
self.account_number = int(account_number.text) if account_number is not None else None |
22 |
|
|
self.name = account.find('name').text |
23 |
|
|
financial_institution = account.find('financial-institution') |
24 |
|
|
self.financial_institution = FinancialInstitution(financial_institution) if financial_institution is not None else None |
25 |
|
|
self.account_type = account.find('account-type').text |
26 |
|
|
self.currency = Currency(account.find('currency')) |
27 |
|
|
current_balance = account.find('current-balance') |
28 |
|
|
|
29 |
|
|
assert current_balance.get('type') == 'float' |
30 |
|
|
|
31 |
|
|
self.current_balance = decimal.Decimal(current_balance.text) |
32 |
|
|
last_uploaded_at = account.find('last-uploaded-at') |
33 |
|
|
|
34 |
|
|
if last_uploaded_at is not None: |
35 |
|
|
assert last_uploaded_at.get('type') == 'datetime' |
36 |
|
|
|
37 |
|
|
self.last_uploaded_at = datetime.strptime(last_uploaded_at.text, ISO_8601) |
38 |
|
|
else: |
39 |
|
|
self.last_uploaded_at = EPOCH |
40 |
|
|
|
41 |
|
|
self.txaction_count = int(account.find('txaction-count').text) |
42 |
|
|
oldest_txaction = account.find('oldest-txaction') |
43 |
|
|
|
44 |
|
|
if oldest_txaction is not None: |
45 |
|
|
assert oldest_txaction.get('type') == 'datetime' |
46 |
|
|
|
47 |
|
|
self.oldest_txaction = datetime.strptime(oldest_txaction.text, ISO_8601) |
48 |
|
|
else: |
49 |
|
|
self.oldest_txaction = EPOCH |
50 |
|
|
|
51 |
|
|
newest_txaction = account.find('newest-txaction') |
52 |
|
|
|
53 |
|
|
if newest_txaction is not None: |
54 |
|
|
assert newest_txaction.get('type') == 'datetime' |
55 |
|
|
|
56 |
|
|
self.newest_txaction = datetime.strptime(newest_txaction.text, ISO_8601) |
57 |
|
|
else: |
58 |
|
|
self.newest_txaction = EPOCH |
59 |
|
|
|
60 |
|
|
class Currency(object): |
61 |
|
|
def __init__(self, currency): |
62 |
|
|
self.symbol = currency.get('symbol') |
63 |
|
|
self.separator = currency.get('separator') |
64 |
|
|
self.delimiter = currency.get('delimiter') |
65 |
|
|
self.decimal_places = int(currency.get('decimal_places')) |
66 |
|
|
self.name = currency.text |
67 |
|
|
|
68 |
|
|
class FinancialInstitution(object): |
69 |
|
|
def __init__(self, financial_institution): |
70 |
|
|
self.id = financial_institution.find('id').text |
71 |
|
|
self.name = financial_institution.find('name').text |
72 |
|
|
|
73 |
douglas |
1178 |
class Wesabe(object): |
74 |
douglas |
1180 |
def __init__(self, email, password): |
75 |
douglas |
1178 |
context = SSL.Context() |
76 |
|
|
|
77 |
|
|
context.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth = 9) |
78 |
|
|
context.load_verify_locations('/etc/ssl/cert.pem') |
79 |
|
|
|
80 |
douglas |
1180 |
handler = urllib2.HTTPBasicAuthHandler() |
81 |
douglas |
1178 |
|
82 |
douglas |
1180 |
handler.add_password('RESTRICTED', 'https://www.wesabe.com/', email, password) |
83 |
|
|
handler.add_password('Wesabe Upload API', 'https://api.wesabe.com/rest/upload/statement', email, password) |
84 |
douglas |
1178 |
|
85 |
douglas |
1180 |
self.opener = m2urllib2.build_opener(context, handler) |
86 |
|
|
|
87 |
|
|
def accounts(self): |
88 |
|
|
accounts = ElementTree.parse(self.opener.open('https://www.wesabe.com/accounts.xml')).getroot() |
89 |
douglas |
1178 |
|
90 |
douglas |
1180 |
return map(lambda account: Account(account), accounts.getchildren()) |
91 |
douglas |
1178 |
|
92 |
douglas |
1180 |
def upload(self, account_number, account_type, wesabe_id, data, balance = None): |
93 |
|
|
upload = ElementTree.Element('upload') |
94 |
|
|
statement = ElementTree.SubElement(upload, 'statement') |
95 |
|
|
|
96 |
|
|
statement.set('acctid', str(account_number)) |
97 |
|
|
statement.set('accttype', account_type) |
98 |
|
|
statement.set('wesabe_id', wesabe_id) |
99 |
|
|
|
100 |
|
|
if balance is not None: |
101 |
|
|
statement.set('balance', str(balance)) |
102 |
|
|
|
103 |
|
|
statement.text = data |
104 |
|
|
|
105 |
|
|
self.opener.open('https://api.wesabe.com/rest/upload/statement', ElementTree.tostring(upload, 'UTF-8')) |