Skip to content

Commit bde1092

Browse files
committed
add some exceptions handling
1 parent f15f292 commit bde1092

File tree

7 files changed

+66
-50
lines changed

7 files changed

+66
-50
lines changed

exchanges/base.py

+23-16
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
from enum import Enum
55
from logging import getLogger
66

7+
import aiohttp
78
from aiohttp import ClientSession
89

9-
from exchanges.exceptions import WrongContentTypeException
10+
from exchanges.exceptions import WrongContentTypeException, BaseExchangeException, InvalidResponseException
1011

1112
Order = namedtuple('Order', 'exchange_id order_id type pair price amount state')
1213

@@ -45,31 +46,32 @@ def __init__(self, key, secret):
4546
def check_keys(cls, api: str, secret: str) -> bool:
4647
return cls.api_regex.match(api) and cls.secret_regex.match(secret)
4748

48-
@staticmethod
49-
async def post(url: str, headers: dict = None, data: dict = None) -> dict:
50-
async with ClientSession() as s:
51-
resp = await s.post(url, data=data, headers=headers)
52-
return await resp.json()
53-
54-
async def get(self, url: str, headers: dict = None) -> dict:
49+
async def request(self, url, headers, method='get', data=None):
5550
attempt, delay = 1, 1
5651
async with ClientSession() as s:
52+
session_method = s.__getattribute__(method.lower())
5753
while True:
5854
try:
59-
resp = await s.get(url, headers=headers)
55+
resp = await session_method(url=url, headers=headers, data=data)
6056
if resp.content_type != 'application/json':
61-
raise WrongContentTypeException(
62-
f'Unexpected content type {resp.content_type!r}. URL: {url}, headers: {headers}'
63-
)
64-
except WrongContentTypeException as e:
65-
getLogger().error(f'attempt {attempt}/{self.attempts_limit}, next attempt in {delay} seconds')
57+
raise WrongContentTypeException(f'Unexpected content type {resp.content_type!r} at URL {url}.')
58+
json_resp = await resp.json()
59+
self._raise_if_error(json_resp)
60+
return json_resp
61+
except (aiohttp.client_exceptions.ClientResponseError, BaseExchangeException) as e:
62+
getLogger().error(f'attempt {attempt}/{self.attempts_limit}, next in {delay} seconds...')
6663
getLogger().exception(e)
6764
attempt += 1
6865
if attempt > self.attempts_limit:
69-
return {}
66+
raise InvalidResponseException(e)
7067
await asyncio.sleep(delay)
7168
delay *= 2
72-
return await resp.json()
69+
70+
async def post(self, url: str, headers: dict = None, data: dict = None) -> dict:
71+
return await self.request(url, headers, 'post', data)
72+
73+
async def get(self, url: str, headers: dict = None) -> dict:
74+
return await self.request(url, headers)
7375

7476
@abstractmethod
7577
async def order_history(self) -> [str, ]:
@@ -96,3 +98,8 @@ def _get_ticker_url(self, pair):
9698
def _order_state(order: dict) -> State:
9799
'''Returns state of the api order.'''
98100

101+
@abstractmethod
102+
def _raise_if_error(self, response: dict) -> bool:
103+
'''Raises BaseExchangeException if there is an errors in API response.
104+
:raises BaseExchangeException:
105+
'''

exchanges/bittrex.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ async def order_info(self, order_id: str) -> dict:
3636
params = {'uuid': order_id}
3737
headers, url = self.get_headers_url(method_url, **params)
3838
resp = await self.get(url, headers)
39-
if not resp['success']:
40-
raise BittrexApiException(resp['message'])
4139
order = resp['result']
4240
return Order(
4341
self.api_id,
@@ -75,3 +73,7 @@ def get_headers_url(self, method_url, **params):
7573
})
7674
url = f'{method_url}?{urlencode(params)}'
7775
return {'apisign': hmac.new(self._secret.encode(), url.encode(), hashlib.sha512).hexdigest()}, url
76+
77+
def _raise_if_error(self, response: dict):
78+
if not response['success']:
79+
raise BittrexApiException(response['message'])

exchanges/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,9 @@ class BaseExchangeException(BaseException):
22
pass
33

44

5+
class InvalidResponseException(BaseExchangeException):
6+
pass
7+
8+
59
class WrongContentTypeException(BaseExchangeException):
610
pass

exchanges/kraken.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ async def order_history(self) -> [str, ]:
5454
}
5555

5656
resp = await self.post(self.BASE_URL + method_url, headers, data)
57-
if resp['error']:
58-
raise KrakenApiException('\n'.join(resp['error']))
5957

6058
return {order_id for order_id in resp['result']['closed']}
6159

@@ -73,8 +71,6 @@ async def order_info(self, order_id: str) -> Order:
7371
}
7472

7573
resp = await self.post(self.BASE_URL + method_url, headers, data)
76-
if resp['error']:
77-
raise KrakenApiException('\n'.join(resp['error']))
7874

7975
order = resp['result'][order_id]
8076
descr = order['descr']
@@ -121,3 +117,7 @@ async def parse_pair(self, pair):
121117
raise AmbiguousResultException(f'More than 1 result to pair {pair}: {pairs}')
122118
_, pair_info = result.popitem()
123119
return f"{pair_info['base']}-{pair_info['quote']}"
120+
121+
def _raise_if_error(self, response: dict) -> bool:
122+
if response['error']:
123+
raise KrakenApiException('\n'.join(response['error']))

exchanges/liqui.py

+13-26
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import asyncio
21
import hashlib
32
import hmac
43
import re
5-
from logging import getLogger
64
from time import time
75
from urllib.parse import urlencode
86

9-
import aiohttp
10-
117
from exchanges.base import BaseApi, Order, State
128
from exchanges.exceptions import BaseExchangeException
139

@@ -52,30 +48,21 @@ def _get_ticker_url(self, pair):
5248
return f'https://liqui.io/#/exchange/{cur_from}_{cur_to}'
5349

5450
async def _tapi(self, **params):
55-
attempt, delay = 1, 1
56-
while True:
57-
try:
58-
params['nonce'] = int(time())
59-
data = await self.post(
60-
'https://api.liqui.io/tapi',
61-
headers={'Key': self._key, 'Sign': self._sign(params)},
62-
data=params
63-
)
64-
if 'error' in data:
65-
if data['error'] == 'no orders':
66-
raise NoOrdersException(data['error'])
67-
raise LiquiApiException(data['error'])
68-
return data.get('return', data)
69-
except (LiquiApiException, aiohttp.client_exceptions.ClientResponseError) as e:
70-
getLogger().error(f'attempt {attempt}/{self.attempts_limit}, next attempt in {delay} seconds')
71-
getLogger().exception(e)
72-
attempt += 1
73-
if attempt > self.attempts_limit:
74-
return {}
75-
await asyncio.sleep(delay)
76-
delay *= 2
51+
params['nonce'] = int(time())
52+
resp = await self.post(
53+
'https://api.liqui.io/tapi',
54+
headers={'Key': self._key, 'Sign': self._sign(params)},
55+
data=params
56+
)
57+
return resp.get('return', resp)
7758

7859
def _sign(self, data):
7960
if isinstance(data, dict):
8061
data = urlencode(data)
8162
return hmac.new(self._secret.encode(), data.encode(), hashlib.sha512).hexdigest()
63+
64+
def _raise_if_error(self, response: dict):
65+
if 'error' in response:
66+
if response['error'] == 'no orders':
67+
raise NoOrdersException(response['error'])
68+
raise LiquiApiException(response['error'])

order_checker.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import settings
88
from exchanges import exchange_apis
99
from exchanges.base import state_text
10+
from exchanges.exceptions import BaseExchangeException
1011

1112

1213
class OrderChecker:
@@ -24,7 +25,12 @@ async def check(self):
2425
api = exchange_api(api_key, secret_key)
2526

2627
db_orders = await db.get_order_ids(exchange_id, uid)
27-
api_orders = await api.order_history()
28+
try:
29+
api_orders = await api.order_history()
30+
except BaseExchangeException as e:
31+
getLogger().error(f'Error while parsing exchange {exchange_name!r} of user id {uid}, skipping...')
32+
getLogger().exception(e)
33+
continue
2834

2935
new_orders = api_orders - db_orders
3036

@@ -36,7 +42,15 @@ async def check(self):
3642
await db.add_orders((uid, exchange_id, order_id) for order_id in new_orders)
3743

3844
for order_id in new_orders:
39-
order = await api.order_info(order_id)
45+
try:
46+
order = await api.order_info(order_id)
47+
except BaseExchangeException as e:
48+
getLogger().error(
49+
f'Error while fetching order {order_id!r} of user id {uid} '
50+
f'at exchange {exchange_name!r}, skipping...'
51+
)
52+
getLogger().exception(e)
53+
continue
4054
state = state_text[order.state]
4155
getLogger().info(f'Order {order_id} of user {uid} at exchange {exchange_name!r} '
4256
f'with id {exchange_id} is {state}.')

settings.py

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66
DATABASE_URL = os.environ['DATABASE_URL']
77

88
CHECK_INTERVAL = int(os.environ['NOTIFY_BOT_CHECK_INTERVAL']) # seconds
9+
10+
REQUEST_ATTEMPTS_LIMIT = int(os.environ['NOTIFY_BOT_ATTEMPTS_LIMIT'])

0 commit comments

Comments
 (0)