Skip to content

Commit bdd78a8

Browse files
Merge branch Davi0kProgramsThings:fix/refactoring into branch bitfinexcom:master. (#238)
# Description <!--- Describe your changes in detail --> PR includes some global refactoring in preparation for the v3.0.0 stable release. ## Motivation and Context <!--- Why is this change required? What problem does it solve? --> - ## Related Issue <!--- If suggesting a new feature or change, please discuss it in an issue first --> <!--- If fixing a bug, there should be an issue describing it with steps to reproduce --> <!--- Please link to the issue here: --> PR fixes the following issue: - ## Type of change <!-- Select the most suitable choice and remove the others from the checklist --> - [X] Bug fix (non-breaking change which fixes an issue); # Checklist: - [X] I've done a self-review of my code; - [X] I've made corresponding changes to the documentation; - [X] I've made sure my changes generate no warnings; - [X] mypy returns no errors when run on the root package; <!-- If you use pre-commit hooks you can always check off the following tasks --> - [X] I've run black to format my code; - [X] I've run isort to format my code's import statements; - [X] flake8 reports no errors when run on the entire code base;
1 parent 3136b9c commit bdd78a8

15 files changed

+274
-283
lines changed

bfxapi/rest/__init__.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
from .endpoints import (
2-
BfxRestInterface,
3-
RestAuthEndpoints,
4-
RestMerchantEndpoints,
5-
RestPublicEndpoints,
6-
)
1+
from ._bfx_rest_interface import BfxRestInterface
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
from .rest_auth_endpoints import RestAuthEndpoints
2-
from .rest_merchant_endpoints import RestMerchantEndpoints
3-
from .rest_public_endpoints import RestPublicEndpoints
4-
5-
6-
class BfxRestInterface:
7-
VERSION = 2
8-
9-
def __init__(self, host, api_key=None, api_secret=None):
10-
self.public = RestPublicEndpoints(host=host)
11-
self.auth = RestAuthEndpoints(host=host, api_key=api_key, api_secret=api_secret)
12-
self.merchant = RestMerchantEndpoints(
13-
host=host, api_key=api_key, api_secret=api_secret
14-
)
1+
from typing import Optional
2+
3+
from bfxapi.rest._interfaces import (
4+
RestAuthEndpoints,
5+
RestMerchantEndpoints,
6+
RestPublicEndpoints,
7+
)
8+
9+
10+
class BfxRestInterface:
11+
def __init__(
12+
self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
13+
):
14+
self.auth = RestAuthEndpoints(host=host, api_key=api_key, api_secret=api_secret)
15+
16+
self.merchant = RestMerchantEndpoints(
17+
host=host, api_key=api_key, api_secret=api_secret
18+
)
19+
20+
self.public = RestPublicEndpoints(host=host)

bfxapi/rest/_interface/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .interface import Interface

bfxapi/rest/_interface/interface.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from typing import Optional
2+
3+
from .middleware import Middleware
4+
5+
6+
class Interface:
7+
def __init__(
8+
self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
9+
):
10+
self._m = Middleware(host, api_key, api_secret)

bfxapi/rest/_interface/middleware.py

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import hashlib
2+
import hmac
3+
import json
4+
from datetime import datetime
5+
from enum import IntEnum
6+
from typing import TYPE_CHECKING, Any, List, Optional
7+
8+
import requests
9+
10+
from bfxapi._utils.json_decoder import JSONDecoder
11+
from bfxapi._utils.json_encoder import JSONEncoder
12+
from bfxapi.exceptions import InvalidCredentialError
13+
from bfxapi.rest.exceptions import RequestParametersError, UnknownGenericError
14+
15+
if TYPE_CHECKING:
16+
from requests.sessions import _Params
17+
18+
19+
class _Error(IntEnum):
20+
ERR_UNK = 10000
21+
ERR_GENERIC = 10001
22+
ERR_PARAMS = 10020
23+
ERR_AUTH_FAIL = 10100
24+
25+
26+
class Middleware:
27+
__TIMEOUT = 30
28+
29+
def __init__(
30+
self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
31+
):
32+
self.__host = host
33+
34+
self.__api_key = api_key
35+
36+
self.__api_secret = api_secret
37+
38+
def get(self, endpoint: str, params: Optional["_Params"] = None) -> Any:
39+
headers = {"Accept": "application/json"}
40+
41+
if self.__api_key and self.__api_secret:
42+
headers = {**headers, **self.__get_authentication_headers(endpoint)}
43+
44+
request = requests.get(
45+
url=f"{self.__host}/{endpoint}",
46+
params=params,
47+
headers=headers,
48+
timeout=Middleware.__TIMEOUT,
49+
)
50+
51+
data = request.json(cls=JSONDecoder)
52+
53+
if isinstance(data, list) and len(data) > 0 and data[0] == "error":
54+
self.__handle_error(data)
55+
56+
return data
57+
58+
def post(
59+
self,
60+
endpoint: str,
61+
body: Optional[Any] = None,
62+
params: Optional["_Params"] = None,
63+
) -> Any:
64+
_body = body and json.dumps(body, cls=JSONEncoder) or None
65+
66+
headers = {"Accept": "application/json", "Content-Type": "application/json"}
67+
68+
if self.__api_key and self.__api_secret:
69+
headers = {
70+
**headers,
71+
**self.__get_authentication_headers(endpoint, _body),
72+
}
73+
74+
request = requests.post(
75+
url=f"{self.__host}/{endpoint}",
76+
data=_body,
77+
params=params,
78+
headers=headers,
79+
timeout=Middleware.__TIMEOUT,
80+
)
81+
82+
data = request.json(cls=JSONDecoder)
83+
84+
if isinstance(data, list) and len(data) > 0 and data[0] == "error":
85+
self.__handle_error(data)
86+
87+
return data
88+
89+
def __handle_error(self, error: List[Any]) -> None:
90+
if error[1] == _Error.ERR_PARAMS:
91+
raise RequestParametersError(
92+
"The request was rejected with the following parameter"
93+
f"error: <{error[2]}>"
94+
)
95+
96+
if error[1] == _Error.ERR_AUTH_FAIL:
97+
raise InvalidCredentialError(
98+
"Cannot authenticate with given API-KEY and API-SECRET."
99+
)
100+
101+
if not error[1] or error[1] == _Error.ERR_UNK or error[1] == _Error.ERR_GENERIC:
102+
raise UnknownGenericError(
103+
"The server replied to the request with a generic error with "
104+
f"the following message: <{error[2]}>."
105+
)
106+
107+
def __get_authentication_headers(self, endpoint: str, data: Optional[str] = None):
108+
assert (
109+
self.__api_key and self.__api_secret
110+
), "API-KEY and API-SECRET must be strings."
111+
112+
nonce = str(round(datetime.now().timestamp() * 1_000_000))
113+
114+
if not data:
115+
message = f"/api/v2/{endpoint}{nonce}"
116+
else:
117+
message = f"/api/v2/{endpoint}{nonce}{data}"
118+
119+
signature = hmac.new(
120+
key=self.__api_secret.encode("utf8"),
121+
msg=message.encode("utf8"),
122+
digestmod=hashlib.sha384,
123+
)
124+
125+
return {
126+
"bfx-nonce": nonce,
127+
"bfx-signature": signature.hexdigest(),
128+
"bfx-apikey": self.__api_key,
129+
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from .bfx_rest_interface import BfxRestInterface
2-
from .rest_auth_endpoints import RestAuthEndpoints
3-
from .rest_merchant_endpoints import RestMerchantEndpoints
4-
from .rest_public_endpoints import RestPublicEndpoints
1+
from .rest_auth_endpoints import RestAuthEndpoints
2+
from .rest_merchant_endpoints import RestMerchantEndpoints
3+
from .rest_public_endpoints import RestPublicEndpoints

0 commit comments

Comments
 (0)