Skip to content
This repository was archived by the owner on Sep 9, 2022. It is now read-only.

Commit b069536

Browse files
committed
Merge many improvements by @haakonvt
1 parent ab6d049 commit b069536

File tree

7 files changed

+123
-119
lines changed

7 files changed

+123
-119
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,7 @@ venv.bak/
104104
.mypy_cache/
105105

106106
# Specific for this project
107-
exchange_history.csv
107+
exchange_history.csv
108+
109+
# VSCode
110+
.vscode/

revolut/__init__.py

Lines changed: 65 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
This package allows you to communicate with your Revolut accounts
44
"""
55

6-
import requests
7-
import json
86
import base64
9-
from urllib.parse import urljoin
107
from datetime import datetime
8+
import json
9+
import requests
10+
from urllib.parse import urljoin
1111

1212
__version__ = '0.0.6' # Should be the same in setup.py
1313

@@ -41,7 +41,7 @@
4141
}
4242

4343

44-
class Amount(object):
44+
class Amount:
4545
""" Class to handle the Revolut amount with currencies """
4646
def __init__(self, currency, revolut_amount=None, real_amount=None):
4747
if currency not in _AVAILABLE_CURRENCIES:
@@ -53,6 +53,7 @@ def __init__(self, currency, revolut_amount=None, real_amount=None):
5353
raise TypeError(type(revolut_amount))
5454
self.revolut_amount = revolut_amount
5555
self.real_amount = self.get_real_amount()
56+
5657
elif real_amount is not None:
5758
if type(real_amount) not in [float, int]:
5859
raise TypeError(type(real_amount))
@@ -100,7 +101,7 @@ def get_revolut_amount(self):
100101
return int(self.real_amount*scale)
101102

102103

103-
class Transaction(object):
104+
class Transaction:
104105
""" Class to handle an exchange transaction """
105106
def __init__(self, from_amount, to_amount, date):
106107
if type(from_amount) != Amount:
@@ -119,7 +120,7 @@ def __str__(self):
119120
self.to_amount))
120121

121122

122-
class Client(object):
123+
class Client:
123124
""" Do the requests with the Revolut servers """
124125
def __init__(self, token, device_id):
125126
self.session = requests.session()
@@ -133,7 +134,7 @@ def __init__(self, token, device_id):
133134

134135
def _get(self, url, expected_status_code=200):
135136
ret = self.session.get(url=url, headers=self.headers)
136-
if (ret.status_code != expected_status_code):
137+
if ret.status_code != expected_status_code:
137138
raise ConnectionError(
138139
'Status code {status} for url {url}\n{content}'.format(
139140
status=ret.status_code, url=url, content=ret.text))
@@ -142,15 +143,15 @@ def _get(self, url, expected_status_code=200):
142143
def _post(self, url, post_data, expected_status_code=200):
143144
ret = self.session.post(url=url,
144145
headers=self.headers,
145-
data=post_data)
146-
if (ret.status_code != expected_status_code):
146+
json=post_data)
147+
if ret.status_code != expected_status_code:
147148
raise ConnectionError(
148149
'Status code {status} for url {url}\n{content}'.format(
149150
status=ret.status_code, url=url, content=ret.text))
150151
return ret
151152

152153

153-
class Revolut(object):
154+
class Revolut:
154155
def __init__(self, token, device_id):
155156
self.client = Client(token=token, device_id=device_id)
156157

@@ -162,14 +163,14 @@ def get_account_balances(self):
162163

163164
account_balances = []
164165
for raw_account in raw_accounts.get("pockets"):
165-
account_balance = {}
166-
account_balance["balance"] = raw_account.get("balance")
167-
account_balance["currency"] = raw_account.get("currency")
168-
account_balance["type"] = raw_account.get("type")
169-
account_balance["state"] = raw_account.get("state")
170-
# name is present when the account is a vault (type = SAVINGS)
171-
account_balance["vault_name"] = raw_account.get("name", "")
172-
account_balances.append(account_balance)
166+
account_balances.append({
167+
"balance": raw_account.get("balance"),
168+
"currency": raw_account.get("currency"),
169+
"type": raw_account.get("type"),
170+
"state": raw_account.get("state"),
171+
# name is present when the account is a vault (type = SAVINGS)
172+
"vault_name": raw_account.get("name", ""),
173+
})
173174
self.account_balances = Accounts(account_balances)
174175
return self.account_balances
175176

@@ -178,7 +179,7 @@ def quote(self, from_amount, to_currency):
178179
raise TypeError("from_amount must be with the Amount type")
179180

180181
if to_currency not in _AVAILABLE_CURRENCIES:
181-
raise KeyError(to_currency)
182+
raise KeyError(to_currency)
182183

183184
url_quote = urljoin(_URL_QUOTE, '{}{}?amount={}&side=SELL'.format(
184185
from_amount.currency,
@@ -195,12 +196,14 @@ def exchange(self, from_amount, to_currency, simulate=False):
195196
raise TypeError("from_amount must be with the Amount type")
196197

197198
if to_currency not in _AVAILABLE_CURRENCIES:
198-
raise KeyError(to_currency)
199+
raise KeyError(to_currency)
199200

200-
data = '{"fromCcy":"%s","fromAmount":%d,"toCcy":"%s","toAmount":null}'\
201-
% (from_amount.currency,
202-
from_amount.revolut_amount,
203-
to_currency)
201+
data = {
202+
"fromCcy": from_amount.currency,
203+
"fromAmount": from_amount.revolut_amount,
204+
"toCcy": to_currency,
205+
"toAmount": None,
206+
}
204207

205208
if simulate:
206209
# Because we don't want to exchange currencies
@@ -244,7 +247,7 @@ def exchange(self, from_amount, to_currency, simulate=False):
244247
return exchange_transaction
245248

246249

247-
class Account(object):
250+
class Account:
248251
""" Class to handle an account """
249252
def __init__(self, account_type, balance, state, vault_name):
250253
self.account_type = account_type # CURRENT, SAVINGS
@@ -270,65 +273,69 @@ def __str__(self):
270273
balance=str(self.balance))
271274

272275

273-
class Accounts(object):
276+
class Accounts:
274277
""" Class to handle the account balances """
278+
275279
def __init__(self, account_balances):
276280
self.raw_list = account_balances
277-
self.list = []
278-
for account in self.raw_list:
279-
self.list.append(Account(account_type=account.get("type"),
280-
balance=Amount(
281-
currency=account.get("currency"),
282-
revolut_amount=account.get("balance")),
283-
state=account.get("state"),
284-
vault_name=account.get("vault_name")))
281+
self.list = [
282+
Account(
283+
account_type=account.get("type"),
284+
balance=Amount(
285+
currency=account.get("currency"),
286+
revolut_amount=account.get("balance"),
287+
),
288+
state=account.get("state"),
289+
vault_name=account.get("vault_name"),
290+
)
291+
for account in self.raw_list
292+
]
285293

286294
def get_account_by_name(self, account_name):
287295
""" Get an account by its name """
288-
found_account = None
289-
for account in self:
296+
for account in self.list:
290297
if account.name == account_name:
291-
found_account = account
292-
break
293-
return found_account
298+
return account
294299

295300
def __len__(self):
296301
return len(self.list)
297302

298303
def __getitem__(self, key):
299-
""" Méthod to access the object as a list
304+
""" Method to access the object as a list
300305
(ex : accounts[1]) """
301306
return self.list[key]
302307

303308
def csv(self, lang="fr"):
304-
if lang == "fr":
309+
lang_is_fr = lang == "fr"
310+
if lang_is_fr:
305311
csv_str = "Nom du compte;Solde;Devise"
306312
else:
307313
csv_str = "Account name,Balance,Currency"
308314

315+
# Europe uses 'comma' as decimal separator,
316+
# so it can't be used as delimiter:
317+
delimiter = ";" if lang_is_fr else ","
318+
309319
for account in self.list:
310320
if account.state == _ACTIVE_ACCOUNT: # Do not print INACTIVE
311-
csv_str += ('\n{account_name},{balance},{currency}'.format(
312-
account_name=account.name,
313-
currency=account.balance.currency,
314-
balance=account.balance.real_amount_str))
315-
if lang == "fr":
316-
# In the French Excel, csv are like "pi;3,14" (not "pi,3.14")
317-
csv_str = csv_str.replace(",", ";").replace(".", ",")
318-
return csv_str
321+
csv_str += "\n" + delimiter.join((
322+
account.name,
323+
account.balance.real_amount_str,
324+
account.balance.currency,
325+
))
326+
327+
return csv_str.replace(".", ",") if lang_is_fr else csv_str
319328

320329

321330
def get_token_step1(device_id, phone, password, simulate=False):
322331
""" Function to obtain a Revolut token (step 1 : send a code by sms) """
323-
if simulate:
324-
ret = ""
325-
else:
332+
if not simulate:
326333
c = Client(device_id=device_id, token=_DEFAULT_TOKEN_FOR_SIGNIN)
327-
data = '{"phone":"%s","password": "%s"}' % (phone, password)
328-
ret = c._post(url=_URL_GET_TOKEN_STEP1,
329-
post_data=data,
330-
expected_status_code=204)
331-
return ret
334+
data = {"phone": phone, "password": password}
335+
return c._post(url=_URL_GET_TOKEN_STEP1,
336+
json=data,
337+
expected_status_code=204)
338+
return ""
332339

333340

334341
def get_token_step2(device_id, phone, sms_code, simulate=False):
@@ -353,7 +360,7 @@ def get_token_step2(device_id, phone, sms_code, simulate=False):
353360
else:
354361
c = Client(device_id=device_id, token=_DEFAULT_TOKEN_FOR_SIGNIN)
355362
sms_code = sms_code.replace("-", "") # If the user would put -
356-
data = '{"phone":"%s","code": "%s"}' % (phone, sms_code)
363+
data = {"phone": phone, "code": sms_code}
357364
ret = c._post(url=_URL_GET_TOKEN_STEP2, post_data=data)
358365
raw_get_token = json.loads(ret.text)
359366

revolut_bot/__init__.py

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
"""
55

66
import csv
7-
from revolut import Amount, Transaction
87
from datetime import datetime
98
import io
109

10+
from revolut import Amount, Transaction
11+
1112
_CSV_COLUMNS = ["date", "hour", "from_amount", "from_currency",
1213
"to_amount", "to_currency"]
1314

@@ -20,12 +21,10 @@ def csv_to_dict(csv_str, separator=","):
2021
[{'a': '1', 'b': '2', 'c': '3'}, {'a': '4', 'b': '5', 'c': '6'}]
2122
>>> csv_to_dict("a;b;c\\n1;2;3", separator=";")
2223
[{'a': '1', 'b': '2', 'c': '3'}]"""
23-
dict_list = []
2424
reader = csv.DictReader(io.StringIO(csv_str), delimiter=separator)
25-
for line in reader:
26-
dict_list.append(dict(line))
27-
# By default, DictReader returns OrderedDict => convert to dict
28-
return dict_list
25+
26+
# By default, DictReader returns OrderedDict => convert to dict:
27+
return list(map(dict, reader))
2928

3029

3130
def append_dict_to_csv(filename, dict_obj, separator=",",
@@ -37,25 +36,23 @@ def append_dict_to_csv(filename, dict_obj, separator=",",
3736
fieldnames=col_names,
3837
lineterminator='\n') # To avoid '^M'
3938
writer.writerow(dict_obj)
40-
return
4139

4240

4341
def convert_Transaction_to_dict(transaction_obj):
44-
tr_dict = {}
45-
tr_dict["date"] = transaction_obj.date.strftime("%d/%m/%Y")
46-
tr_dict["hour"] = transaction_obj.date.strftime("%H:%M:%S")
47-
tr_dict["from_amount"] = transaction_obj.from_amount.real_amount
48-
tr_dict["from_currency"] = transaction_obj.from_amount.currency
49-
tr_dict["to_amount"] = transaction_obj.to_amount.real_amount
50-
tr_dict["to_currency"] = transaction_obj.to_amount.currency
51-
return tr_dict
42+
return {
43+
"date": transaction_obj.date.strftime("%d/%m/%Y"),
44+
"hour": transaction_obj.date.strftime("%H:%M:%S"),
45+
"from_amount": transaction_obj.from_amount.real_amount,
46+
"from_currency": transaction_obj.from_amount.currency,
47+
"to_amount": transaction_obj.to_amount.real_amount,
48+
"to_currency": transaction_obj.to_amount.currency,
49+
}
5250

5351

5452
def update_historyfile(filename, exchange_transaction):
5553
""" Update the history file with an exchange transaction """
5654
tr_dict = convert_Transaction_to_dict(transaction_obj=exchange_transaction)
5755
append_dict_to_csv(filename=filename, dict_obj=tr_dict)
58-
return
5956

6057

6158
def read_file_to_str(filename):
@@ -69,19 +66,14 @@ def get_last_transactions_from_csv(filename="exchange_history.csv",
6966
csv_str = read_file_to_str(filename=filename)
7067
last_transactions = csv_to_dict(csv_str=csv_str, separator=separator)
7168

72-
tr_list = []
73-
for tr in last_transactions:
74-
tr_obj = dict_transaction_to_Transaction(tr)
75-
tr_list.append(tr_obj)
76-
77-
return tr_list
69+
return list(map(dict_transaction_to_Transaction, last_transactions))
7870

7971

8072
def dict_transaction_to_Transaction(tr_dict):
8173
""" Converts a transaction dictionnary to a Transaction object """
82-
if set(list(tr_dict.keys())) != set(_CSV_COLUMNS):
74+
if set(tr_dict) != set(_CSV_COLUMNS):
8375
raise TypeError("Columns expected : {}\n{} received".format(
84-
_CSV_COLUMNS, list(tr_dict.keys())))
76+
_CSV_COLUMNS, list(tr_dict)))
8577
str_date = "{} {}".format(tr_dict["date"],
8678
tr_dict["hour"])
8779
tr = Transaction(from_amount=Amount(

0 commit comments

Comments
 (0)