Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge branch Davi0kProgramsThings:fix/refactoring into branch bitfinexcom:master. #238

Merged
merged 3 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions bfxapi/rest/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
from .endpoints import (
BfxRestInterface,
RestAuthEndpoints,
RestMerchantEndpoints,
RestPublicEndpoints,
)
from ._bfx_rest_interface import BfxRestInterface
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
from .rest_auth_endpoints import RestAuthEndpoints
from .rest_merchant_endpoints import RestMerchantEndpoints
from .rest_public_endpoints import RestPublicEndpoints


class BfxRestInterface:
VERSION = 2

def __init__(self, host, api_key=None, api_secret=None):
self.public = RestPublicEndpoints(host=host)
self.auth = RestAuthEndpoints(host=host, api_key=api_key, api_secret=api_secret)
self.merchant = RestMerchantEndpoints(
host=host, api_key=api_key, api_secret=api_secret
)
from typing import Optional

from bfxapi.rest._interfaces import (
RestAuthEndpoints,
RestMerchantEndpoints,
RestPublicEndpoints,
)


class BfxRestInterface:
def __init__(
self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
):
self.auth = RestAuthEndpoints(host=host, api_key=api_key, api_secret=api_secret)

self.merchant = RestMerchantEndpoints(
host=host, api_key=api_key, api_secret=api_secret
)

self.public = RestPublicEndpoints(host=host)
1 change: 1 addition & 0 deletions bfxapi/rest/_interface/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .interface import Interface
10 changes: 10 additions & 0 deletions bfxapi/rest/_interface/interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import Optional

from .middleware import Middleware


class Interface:
def __init__(
self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
):
self._m = Middleware(host, api_key, api_secret)
129 changes: 129 additions & 0 deletions bfxapi/rest/_interface/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import hashlib
import hmac
import json
from datetime import datetime
from enum import IntEnum
from typing import TYPE_CHECKING, Any, List, Optional

import requests

from bfxapi._utils.json_decoder import JSONDecoder
from bfxapi._utils.json_encoder import JSONEncoder
from bfxapi.exceptions import InvalidCredentialError
from bfxapi.rest.exceptions import RequestParametersError, UnknownGenericError

if TYPE_CHECKING:
from requests.sessions import _Params


class _Error(IntEnum):
ERR_UNK = 10000
ERR_GENERIC = 10001
ERR_PARAMS = 10020
ERR_AUTH_FAIL = 10100


class Middleware:
__TIMEOUT = 30

def __init__(
self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
):
self.__host = host

self.__api_key = api_key

self.__api_secret = api_secret

def get(self, endpoint: str, params: Optional["_Params"] = None) -> Any:
headers = {"Accept": "application/json"}

if self.__api_key and self.__api_secret:
headers = {**headers, **self.__get_authentication_headers(endpoint)}

request = requests.get(
url=f"{self.__host}/{endpoint}",
params=params,
headers=headers,
timeout=Middleware.__TIMEOUT,
)

data = request.json(cls=JSONDecoder)

if isinstance(data, list) and len(data) > 0 and data[0] == "error":
self.__handle_error(data)

return data

def post(
self,
endpoint: str,
body: Optional[Any] = None,
params: Optional["_Params"] = None,
) -> Any:
_body = body and json.dumps(body, cls=JSONEncoder) or None

headers = {"Accept": "application/json", "Content-Type": "application/json"}

if self.__api_key and self.__api_secret:
headers = {
**headers,
**self.__get_authentication_headers(endpoint, _body),
}

request = requests.post(
url=f"{self.__host}/{endpoint}",
data=_body,
params=params,
headers=headers,
timeout=Middleware.__TIMEOUT,
)

data = request.json(cls=JSONDecoder)

if isinstance(data, list) and len(data) > 0 and data[0] == "error":
self.__handle_error(data)

return data

def __handle_error(self, error: List[Any]) -> None:
if error[1] == _Error.ERR_PARAMS:
raise RequestParametersError(
"The request was rejected with the following parameter"
f"error: <{error[2]}>"
)

if error[1] == _Error.ERR_AUTH_FAIL:
raise InvalidCredentialError(
"Cannot authenticate with given API-KEY and API-SECRET."
)

if not error[1] or error[1] == _Error.ERR_UNK or error[1] == _Error.ERR_GENERIC:
raise UnknownGenericError(
"The server replied to the request with a generic error with "
f"the following message: <{error[2]}>."
)

def __get_authentication_headers(self, endpoint: str, data: Optional[str] = None):
assert (
self.__api_key and self.__api_secret
), "API-KEY and API-SECRET must be strings."

nonce = str(round(datetime.now().timestamp() * 1_000_000))

if not data:
message = f"/api/v2/{endpoint}{nonce}"
else:
message = f"/api/v2/{endpoint}{nonce}{data}"

signature = hmac.new(
key=self.__api_secret.encode("utf8"),
msg=message.encode("utf8"),
digestmod=hashlib.sha384,
)

return {
"bfx-nonce": nonce,
"bfx-signature": signature.hexdigest(),
"bfx-apikey": self.__api_key,
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .bfx_rest_interface import BfxRestInterface
from .rest_auth_endpoints import RestAuthEndpoints
from .rest_merchant_endpoints import RestMerchantEndpoints
from .rest_public_endpoints import RestPublicEndpoints
from .rest_auth_endpoints import RestAuthEndpoints
from .rest_merchant_endpoints import RestMerchantEndpoints
from .rest_public_endpoints import RestPublicEndpoints
Loading
Loading