Skip to content

Commit 356690e

Browse files
committed
documentation + setupt + pylint fixes
1 parent e0f7c83 commit 356690e

4 files changed

Lines changed: 188 additions & 126 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
*~
2+
*.pyc
23
consumer_oauth.json
34
*.pkl
45
*bank*

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,29 @@
11
# splitwise-csv
2-
Upload expenses to splitwise from a csv file.
2+
Upload expenses to splitwise from a csv file.
3+
4+
##API Keys
5+
You will need to obtain your own API keys from Splitwise.
6+
https://secure.splitwise.com/apps/new
7+
Set the "Callback URL" to "localhost:5000"
8+
Store your consumer key and consumer secret in src/consumer_oauth.json like so:
9+
```
10+
{
11+
"consumer_key":"YOUR CONSUMER KEY",
12+
"consumer_secret":"YOUR CONSUMER SECRET"
13+
}
14+
```
15+
16+
##Prerequesites
17+
`pip install -r setup/requirements.txt`
18+
19+
##Usage
20+
###First Usage
21+
`python groupsplit.py <transactions csv> <splitwise group name>`
22+
You will need a Display to authorize this application by entering your login information in the browser spawned from here.
23+
It will ask you a few questions to determine which columns have the date, amount and description, etc. You can save the answers for future use. Then you go through each transaction one by one marking them to be split (or not). Finally it uploads them to your splitwise.
24+
###Normal Usage
25+
`python groupsplit.py <transactions csv> <splitwise group name>`
26+
It will skip right to going through your transactions one by one, so long as you have agreed to it remembering your csv layout
27+
##Resetting
28+
Just delete any or all of the .pkl (pickeled) files.
29+
If you start to use a new csv layout you have to delete the csv_settings.pkl file

setup/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Flask==0.10.1
2+
money==1.3.0
3+
oauthlib==1.0.3
4+
requests==2.9.1

src/groupsplit.py

100755100644
Lines changed: 155 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
1-
#! /usr/bin/python
2-
3-
import requests
1+
import os
42
import csv
53
import sys
6-
import oauthlib.oauth1
7-
import pprint
8-
import webbrowser
9-
import subprocess
10-
import os
11-
import pickle
124
import json
5+
import pickle
6+
import pprint
137
import urllib
8+
import requests
9+
import subprocess
10+
import webbrowser
11+
import oauthlib.oauth1
12+
from money import Money
1413
from pprint import pprint
1514
from datetime import datetime
1615

17-
if os.path.isfile("consumer_oauth.json"):
18-
with open("consumer_oauth.json", 'rb') as f:
19-
consumer = json.load(f)
20-
ckey=consumer['consumer_key']
21-
csecret = consumer['consumer_secret']
22-
else:
23-
with open("consumer_oauth.json", 'wb') as f:
24-
json.dump({'consumer_key':'YOUR KEY HERE',
25-
'consumer_secret':'YOUR SECRET HERE'}, f)
26-
exit("go to https://secure.splitwise.com/oauth_clients to obtain your keys. place them in consumer_oauth.json")
16+
def get_client_auth():
17+
if os.path.isfile("consumer_oauth.json"):
18+
with open("consumer_oauth.json", 'rb') as oauth_file:
19+
consumer = json.load(oauth_file)
20+
ckey = consumer['consumer_key']
21+
csecret = consumer['consumer_secret']
22+
else:
23+
with open("consumer_oauth.json", 'wb') as oauth_file:
24+
json.dump({'consumer_key':'YOUR KEY HERE',
25+
'consumer_secret':'YOUR SECRET HERE'}, oauth_file)
26+
exit("go to https://secure.splitwise.com/oauth_clients to obtain your keys."+
27+
"place them in consumer_oauth.json")
28+
return ckey, csecret
2729

2830
def get_client():
29-
client = oauthlib.oauth1.Client( ckey, client_secret=csecret )
30-
uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_request_token", http_method='POST')
31+
ckey, csecret = get_client_auth()
32+
client = oauthlib.oauth1.Client(ckey, client_secret=csecret)
33+
uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_request_token",
34+
http_method='POST')
3135
r = requests.post(uri, headers=headers, data=body)
3236
resp = r.text.split('&')
3337
oauth_token = resp[0].split('=')[1]
@@ -36,114 +40,140 @@ def get_client():
3640

3741
webbrowser.open_new(uri)
3842

39-
p = subprocess.Popen(['./server.py'], stdout=subprocess.PIPE, shell=True)
40-
stdout, stderr = p.communicate()
43+
proc = subprocess.Popen(['./server.py'], stdout=subprocess.PIPE, shell=True)
44+
stdout, stderr = proc.communicate()
45+
if stderr:
46+
exit(stderr)
4147
client = oauthlib.oauth1.Client(ckey, client_secret=csecret,
42-
resource_owner_key=oauth_token, resource_owner_secret=oauth_secret,
48+
resource_owner_key=oauth_token,
49+
resource_owner_secret=oauth_secret,
4350
verifier=stdout.strip())
4451

45-
uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_access_token", http_method='POST')
46-
r = requests.post(uri, headers=headers, data=body)
47-
resp = r.text.split('&')
48-
oauth_token = resp[0].split('=')[1]
49-
oauth_secret = resp[1].split('=')[1]
52+
uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_access_token",
53+
http_method='POST')
54+
resp = requests.post(uri, headers=headers, data=body)
55+
tokens = resp.text.split('&')
56+
oauth_token = tokens[0].split('=')[1]
57+
oauth_secret = tokens[1].split('=')[1]
5058
client = oauthlib.oauth1.Client(ckey, client_secret=csecret,
51-
resource_owner_key=oauth_token, resource_owner_secret=oauth_secret,
59+
resource_owner_key=oauth_token,
60+
resource_owner_secret=oauth_secret,
5261
verifier=stdout.strip())
53-
with open('oauth_client.pkl', 'wb') as f:
54-
pickle.dump(client, f)
62+
with open('oauth_client.pkl', 'wb') as pkl:
63+
pickle.dump(client, pkl)
5564
return client
5665

57-
58-
if os.path.isfile("oauth_client.pkl"):
59-
with open('oauth_client.pkl', 'rb') as f:
60-
client = pickle.load(f)
61-
else:
62-
client = get_client()
63-
64-
#uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_expenses?limit=3", http_method='GET')
65-
#r = requests.get(uri, headers=headers, data=body)
66-
#r = json.loads(r.text)
67-
68-
uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_current_user", http_method='GET')
69-
r = requests.get(uri, headers=headers, data=body)
70-
r = json.loads(r.text)
71-
myId = r['user']['id']
72-
73-
uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_groups", http_method='GET')
74-
r = requests.get(uri, headers=headers, data=body)
75-
r = json.loads(r.text)
76-
numFound = 0
77-
gid=''
78-
members={}
79-
for group in r['groups']:
80-
if group['name'].lower() == sys.argv[2].lower():
81-
gid = group['id']
82-
members=[ m['id'] for m in group['members'] if m['id'] != myId ]
83-
numFound += 1
84-
85-
print members
86-
87-
if numFound > 1:
88-
exit("More than 1 group found")
89-
elif numFound < 1:
90-
exit("No matching group")
91-
elif len( members ) < 1:
92-
exit("No members in group")
93-
with open(sys.argv[1], 'rb') as csvfile:
94-
reader = csv.reader(csvfile)
95-
rows = [ x for x in reader ]
96-
97-
98-
print "These are the first two rows of your csv"
99-
print '\n'.join( [ str(t) for t in rows[0:2] ] )
100-
print 'Colnum numbers start at 0'
101-
dateCol = input("Which column has the date?")
102-
amountCol = input("Which column has the amount?")
103-
descCol = input("Which column has the description?")
104-
titleRow = raw_input("Does first row have titles? [Y/n]").lower() != 'n'
105-
if titleRow:
106-
rows = rows[1:]
107-
transactions = [ { "date":datetime.strftime(datetime.strptime(r[dateCol],"%m/%d/%y"), "%Y-%m-%dT%H:%M:%SZ"),
108-
"amount":-1 * float(r[amountCol]),
109-
"desc":r[descCol] }
110-
for r in rows if float(r[amountCol]) < 0 ]
111-
splits = []
112-
for t in transactions:
113-
if raw_input("%s at %s $%s. Split? [y/N]" % (t['date'], t['desc'], t['amount'])).lower() == 'y':
114-
splits.append(t)
115-
116-
117-
118-
for s in splits:
119-
numPeople=len(members) + 1
120-
totalCents = int(s['amount'] * 100)
121-
baseAmount = totalCents // numPeople
122-
extraCents = totalCents - numPeople * baseAmount
123-
params = {
124-
"payment": 'false',
125-
"cost": s["amount"],
126-
"description": s["desc"],
127-
"date": s["date"],
128-
"group_id":gid,
129-
"users__0__user_id":myId,
130-
"users__0__paid_share":s["amount"],
131-
"users__0__owed_share":baseAmount/100.0,
132-
}
133-
for i in range(len(members)):
134-
params['users__%s__user_id' % (i+1)] = members[i]
135-
params['users__%s__paid_share' % (i+1)] = 0
136-
params['users__%s__owed_share' % (i+1)] = (baseAmount + 1)/100.0 if extraCents else baseAmount/100.0
137-
extraCents-=1
138-
params = urllib.urlencode(params)
139-
uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/create_expense?%s" % params, http_method='POST')
140-
print "Request"
141-
print uri
142-
print "Headers:"
143-
print headers
144-
print "Body:"
145-
print body
146-
r = requests.post(uri, headers=headers, data=params)
147-
resp = json.loads(r.text)
148-
print "Response:"
149-
pprint(resp)
66+
def split(total, num_people):
67+
base = total * 100 // num_people / 100
68+
extra = total - num_people * base
69+
assert base * num_people + extra == total, "InternalError:" + \
70+
" something doesnt add up here: %d * %d + %d != %d" %(base, num_people, extra, total)
71+
return base, extra
72+
73+
def api_call(url, http_method, client):
74+
uri, headers, body = client.sign(url, http_method=http_method)
75+
resp = requests.request(http_method, uri, headers=headers, data=body)
76+
return resp.json()
77+
78+
def main():
79+
if os.path.isfile("oauth_client.pkl"):
80+
with open('oauth_client.pkl', 'rb') as oauth_pkl:
81+
client = pickle.load(oauth_pkl)
82+
else:
83+
client = get_client()
84+
85+
resp = api_call("https://secure.splitwise.com/api/v3.0/get_current_user", 'GET', client)
86+
my_id = resp['user']['id']
87+
88+
resp = api_call("https://secure.splitwise.com/api/v3.0/get_groups", 'GET', client)
89+
num_found = 0
90+
gid = ''
91+
members = {}
92+
93+
for group in resp['groups']:
94+
if group['name'].lower() == sys.argv[2].lower():
95+
gid = group['id']
96+
members = [m['id'] for m in group['members'] if m['id'] != my_id]
97+
num_found += 1
98+
99+
if num_found > 1:
100+
exit("More than 1 group found")
101+
elif num_found < 1:
102+
exit("No matching group")
103+
elif len(members) < 1:
104+
exit("No members in group")
105+
106+
with open(sys.argv[1], 'rb') as csvfile:
107+
reader = csv.reader(csvfile)
108+
rows = [x for x in reader]
109+
110+
111+
if os.path.isfile("csv_settings.pkl"):
112+
with open('csv_settings.pkl', 'rb') as f:
113+
date_col, amount_col, desc_col, has_title_row, local_currency, remember = pickle.load(f)
114+
115+
else:
116+
print "These are the first two rows of your csv"
117+
print '\n'.join([str(t) for t in rows[0:2]])
118+
print 'Colnum numbers start at 0'
119+
date_col = input("Which column has the date?")
120+
amount_col = input("Which column has the amount?")
121+
desc_col = input("Which column has the description?")
122+
has_title_row = raw_input("Does first row have titles? [Y/n]").lower() != 'n'
123+
local_currency = raw_input("What currency were these transactions made in?")
124+
test = Money("1.00", local_currency) #pylint: disable=W0612
125+
remember = raw_input("Remember these settings? [Y/n]").lower() != 'n'
126+
127+
if remember:
128+
with open("csv_settings.pkl", "wb") as pkl:
129+
csv_settings = (date_col, amount_col, desc_col, has_title_row, local_currency, remember)
130+
pickle.dump(csv_settings, pkl)
131+
132+
if has_title_row:
133+
rows = rows[1:]
134+
transactions = [{"date":datetime.strftime(datetime.strptime(r[date_col], "%m/%d/%y"), "%Y-%m-%dT%H:%M:%SZ"),
135+
"amount":-1 * Money(r[amount_col], local_currency),
136+
"desc":r[desc_col]}
137+
for r in rows if float(r[amount_col]) < 0]
138+
splits = []
139+
for t in transactions:
140+
if raw_input("%s at %s $%s. Split? [y/N]" % (t['date'], t['desc'], t['amount'])).lower() == 'y':
141+
splits.append(t)
142+
143+
144+
print "Uploading %d splits" % len(splits)
145+
one_cent = Money("0.01", local_currency)
146+
for s in splits:
147+
num_people = len(members) + 1
148+
base, extra = split(s['amount'], num_people)
149+
params = {
150+
"payment": 'false',
151+
"cost": s["amount"].amount,
152+
"description": s["desc"],
153+
"date": s["date"],
154+
"group_id": gid,
155+
"currency_code": local_currency,
156+
"users__0__user_id": my_id,
157+
"users__0__paid_share": s["amount"].amount,
158+
"users__0__owed_share": base.amount,
159+
}
160+
for i in range(len(members)):
161+
params['users__%s__user_id' % (i+1)] = members[i]
162+
params['users__%s__paid_share' % (i+1)] = 0
163+
params['users__%s__owed_share' % (i+1)] = (base + one_cent).amount if extra else base.amount
164+
extra -= one_cent
165+
paramsStr = urllib.urlencode(params)
166+
uri = "https://secure.splitwise.com/api/v3.0/create_expense?%s" % (paramsStr)
167+
resp = api_call(uri, 'POST', client)
168+
if resp["errors"]:
169+
print "URI:"
170+
print uri
171+
pprint(resp)
172+
else:
173+
sys.stdout.write(".")
174+
sys.stdout.flush()
175+
sys.stdout.write("\n")
176+
sys.stdout.flush()
177+
178+
if __name__ == "__main__":
179+
main()

0 commit comments

Comments
 (0)