-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathrest_client.py
More file actions
135 lines (121 loc) · 5.95 KB
/
rest_client.py
File metadata and controls
135 lines (121 loc) · 5.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import warnings
from time import sleep
from typing import Dict
from typing import List
from typing import Optional
from typing import Union
from urllib import parse
from simplejson.errors import JSONDecodeError
try:
import simplejson as json
except ImportError:
import json
from requests.exceptions import HTTPError
from valr_python.exceptions import IncompleteOrderWarning
from valr_python.exceptions import RESTAPIException
from valr_python.exceptions import TooManyRequestsWarning
from valr_python.rest_base import MethodClientABC
from valr_python.utils import DecimalEncoder
from valr_python.utils import _get_valr_headers
__all__ = ('Client',)
class Client(MethodClientABC):
"""Synchronous Python SDK for the VALR REST API.
>>> from valr_python import Client
>>> from valr_python.exceptions import IncompleteOrderWarning
>>>
>>> c = Client(api_key='api_key', api_secret='api_secret')
>>> c.rate_limiting_support = True # honour HTTP 429 "Retry-After" header values
>>> limit_order = {
... "side": "SELL",
... "quantity": 0.1,
... "price": 10000,
... "pair": "BTCZAR",
... "post_only": True,
... }
>>> try:
... res = c.post_limit_order(**limit_order)
... order_id = res['id']
... print(order_id)
... except IncompleteOrderWarning as w: # HTTP 202 Accepted handling for incomplete orders
... order_id = w.data['id']
... print(order_id)
... except Exception as e:
... print(e)
"558f5e0a-ffd1-46dd-8fae-763d93fa2f25"
>>>
"""
def _do(self, method: str, path: str, data: Optional[Dict] = None, params: Optional[Dict] = None,
is_authenticated: bool = False, subaccount_id: str = '') -> Optional[Union[List, Dict]]:
"""Executes API request and returns the response.
Includes HTTP 429 handling by honouring VALR's 429 Retry-After header cool-down.
"""
headers = {}
if data:
data = json.dumps(data, cls=DecimalEncoder) # serialize decimals as str
headers["Content-Type"] = "application/json"
params_str = parse.urlencode(params, safe=":") if params else None
if is_authenticated:
# todo - fix data processing in valr headers
headers.update(_get_valr_headers(api_key=self.api_key, api_secret=self.api_secret, method=method,
path=f'{path}?{params_str}' if params_str else path, data=data,
subaccount_id=subaccount_id))
url = self._base_url + '/' + path.lstrip('/')
args = dict(timeout=self._timeout, data=data, headers=headers)
if params_str:
args['params'] = params_str
res = self._session.request(method, url, **args)
try:
res.raise_for_status()
if res.text:
e = res.json()
self._raise_for_api_error(e)
# provide warning with bundled response dict for incomplete transactions
if res.status_code == 202:
warnings.warn(IncompleteOrderWarning(data=e, message="Order processing incomplete"))
return e
else:
return res.raise_for_status()
except HTTPError as he:
print(he)
if res.status_code == 429:
if self._rate_limiting_support:
try:
retry_after = float(res.headers['Retry-After'])
warnings.warn(f"HTTP 429 response received. Applying Retry-After {retry_after}sec back-off",
TooManyRequestsWarning)
sleep(retry_after)
return self._do(method=method, path=path, is_authenticated=is_authenticated, data=data)
except (KeyError, ValueError):
raise RESTAPIException(res.status_code,
f'valr-python: HTTP 429 processing failed. '
f'HTTP ({res.status_code}): {res.headers}')
else:
# avoid JSONDecodeError - VALR 429 response has html body
raise he
e = res.json()
self._raise_for_api_error(e)
# bubble HTTP errors that VALR API doesn't report on
raise he
except JSONDecodeError as jde:
raise RESTAPIException(res.status_code,
f'valr-python: unknown API error. HTTP ({res.status_code}): {jde.msg}')
def _do_delete(self, method: str, path: str, data: Optional[Dict] = None, params: Optional[Dict] = None,
is_authenticated: bool = False, subaccount_id: str = '') -> Optional[Union[List, Dict]]:
"""Executes API request and returns the response.
Includes HTTP 429 handling by honouring VALR's 429 Retry-After header cool-down.
"""
headers = {}
if data:
data = json.dumps(data, cls=DecimalEncoder) # serialize decimals as str
headers["Content-Type"] = "application/json"
params_str = parse.urlencode(params, safe=":") if params else None
if is_authenticated:
# todo - fix data processing in valr headers
headers.update(_get_valr_headers(api_key=self.api_key, api_secret=self.api_secret, method=method,
path=f'{path}?{params_str}' if params_str else path, data=data,
subaccount_id=subaccount_id))
url = self._base_url + '/' + path.lstrip('/')
args = dict(timeout=self._timeout, data=data, headers=headers)
if params_str:
args['params'] = params_str
return self._session.request(method, url, **args)