Skip to content

Commit

Permalink
Merge branch Davi0kProgramsThings:fix/refactoring into branch `bitf…
Browse files Browse the repository at this point in the history
…inexcom: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;
  • Loading branch information
Davi0kProgramsThings authored Apr 3, 2024
1 parent 3136b9c commit bdd78a8
Show file tree
Hide file tree
Showing 15 changed files with 274 additions and 283 deletions.
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

0 comments on commit bdd78a8

Please sign in to comment.