mirror of
https://github.com/aljazceru/bitfinex-api-py.git
synced 2025-12-18 22:34:21 +01:00
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;
This commit is contained in:
@@ -1,6 +1 @@
|
||||
from .endpoints import (
|
||||
BfxRestInterface,
|
||||
RestAuthEndpoints,
|
||||
RestMerchantEndpoints,
|
||||
RestPublicEndpoints,
|
||||
)
|
||||
from ._bfx_rest_interface import BfxRestInterface
|
||||
|
||||
@@ -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
bfxapi/rest/_interface/__init__.py
Normal file
1
bfxapi/rest/_interface/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .interface import Interface
|
||||
10
bfxapi/rest/_interface/interface.py
Normal file
10
bfxapi/rest/_interface/interface.py
Normal file
@@ -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
bfxapi/rest/_interface/middleware.py
Normal file
129
bfxapi/rest/_interface/middleware.py
Normal file
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,7 +1,8 @@
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, Literal, Optional, Tuple, Union
|
||||
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
||||
|
||||
from ...types import (
|
||||
from bfxapi.rest._interface import Interface
|
||||
from bfxapi.types import (
|
||||
BalanceAvailable,
|
||||
BaseMarginInfo,
|
||||
DepositAddress,
|
||||
@@ -35,18 +36,17 @@ from ...types import (
|
||||
Withdrawal,
|
||||
serializers,
|
||||
)
|
||||
from ...types.serializers import _Notification
|
||||
from ..middleware import Middleware
|
||||
from bfxapi.types.serializers import _Notification
|
||||
|
||||
|
||||
class RestAuthEndpoints(Middleware):
|
||||
class RestAuthEndpoints(Interface):
|
||||
def get_user_info(self) -> UserInfo:
|
||||
return serializers.UserInfo.parse(*self._post("auth/r/info/user"))
|
||||
return serializers.UserInfo.parse(*self._m.post("auth/r/info/user"))
|
||||
|
||||
def get_login_history(self) -> List[LoginHistory]:
|
||||
return [
|
||||
serializers.LoginHistory.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/logins/hist")
|
||||
for sub_data in self._m.post("auth/r/logins/hist")
|
||||
]
|
||||
|
||||
def get_balance_available_for_orders_or_offers(
|
||||
@@ -61,13 +61,13 @@ class RestAuthEndpoints(Middleware):
|
||||
body = {"symbol": symbol, "type": type, "dir": dir, "rate": rate, "lev": lev}
|
||||
|
||||
return serializers.BalanceAvailable.parse(
|
||||
*self._post("auth/calc/order/avail", body=body)
|
||||
*self._m.post("auth/calc/order/avail", body=body)
|
||||
)
|
||||
|
||||
def get_wallets(self) -> List[Wallet]:
|
||||
return [
|
||||
serializers.Wallet.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/wallets")
|
||||
for sub_data in self._m.post("auth/r/wallets")
|
||||
]
|
||||
|
||||
def get_orders(
|
||||
@@ -80,7 +80,7 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.Order.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint, body={"id": ids})
|
||||
for sub_data in self._m.post(endpoint, body={"id": ids})
|
||||
]
|
||||
|
||||
def submit_order(
|
||||
@@ -98,6 +98,7 @@ class RestAuthEndpoints(Middleware):
|
||||
cid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
tif: Optional[str] = None,
|
||||
meta: Optional[Dict[str, Any]] = None,
|
||||
) -> Notification[Order]:
|
||||
body = {
|
||||
"type": type,
|
||||
@@ -112,10 +113,11 @@ class RestAuthEndpoints(Middleware):
|
||||
"cid": cid,
|
||||
"flags": flags,
|
||||
"tif": tif,
|
||||
"meta": meta,
|
||||
}
|
||||
|
||||
return _Notification[Order](serializers.Order).parse(
|
||||
*self._post("auth/w/order/submit", body=body)
|
||||
*self._m.post("auth/w/order/submit", body=body)
|
||||
)
|
||||
|
||||
def update_order(
|
||||
@@ -150,7 +152,7 @@ class RestAuthEndpoints(Middleware):
|
||||
}
|
||||
|
||||
return _Notification[Order](serializers.Order).parse(
|
||||
*self._post("auth/w/order/update", body=body)
|
||||
*self._m.post("auth/w/order/update", body=body)
|
||||
)
|
||||
|
||||
def cancel_order(
|
||||
@@ -161,7 +163,7 @@ class RestAuthEndpoints(Middleware):
|
||||
cid_date: Optional[str] = None,
|
||||
) -> Notification[Order]:
|
||||
return _Notification[Order](serializers.Order).parse(
|
||||
*self._post(
|
||||
*self._m.post(
|
||||
"auth/w/order/cancel", body={"id": id, "cid": cid, "cid_date": cid_date}
|
||||
)
|
||||
)
|
||||
@@ -177,7 +179,7 @@ class RestAuthEndpoints(Middleware):
|
||||
body = {"id": id, "cid": cid, "gid": gid, "all": all}
|
||||
|
||||
return _Notification[List[Order]](serializers.Order, is_iterable=True).parse(
|
||||
*self._post("auth/w/order/cancel/multi", body=body)
|
||||
*self._m.post("auth/w/order/cancel/multi", body=body)
|
||||
)
|
||||
|
||||
def get_orders_history(
|
||||
@@ -198,13 +200,13 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.Order.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint, body=body)
|
||||
for sub_data in self._m.post(endpoint, body=body)
|
||||
]
|
||||
|
||||
def get_order_trades(self, symbol: str, id: int) -> List[OrderTrade]:
|
||||
return [
|
||||
serializers.OrderTrade.parse(*sub_data)
|
||||
for sub_data in self._post(f"auth/r/order/{symbol}:{id}/trades")
|
||||
for sub_data in self._m.post(f"auth/r/order/{symbol}:{id}/trades")
|
||||
]
|
||||
|
||||
def get_trades_history(
|
||||
@@ -225,7 +227,7 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.Trade.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint, body=body)
|
||||
for sub_data in self._m.post(endpoint, body=body)
|
||||
]
|
||||
|
||||
def get_ledgers(
|
||||
@@ -241,43 +243,43 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.Ledger.parse(*sub_data)
|
||||
for sub_data in self._post(f"auth/r/ledgers/{currency}/hist", body=body)
|
||||
for sub_data in self._m.post(f"auth/r/ledgers/{currency}/hist", body=body)
|
||||
]
|
||||
|
||||
def get_base_margin_info(self) -> BaseMarginInfo:
|
||||
return serializers.BaseMarginInfo.parse(
|
||||
*(self._post("auth/r/info/margin/base")[1])
|
||||
*(self._m.post("auth/r/info/margin/base")[1])
|
||||
)
|
||||
|
||||
def get_symbol_margin_info(self, symbol: str) -> SymbolMarginInfo:
|
||||
return serializers.SymbolMarginInfo.parse(
|
||||
*self._post(f"auth/r/info/margin/{symbol}")
|
||||
*self._m.post(f"auth/r/info/margin/{symbol}")
|
||||
)
|
||||
|
||||
def get_all_symbols_margin_info(self) -> List[SymbolMarginInfo]:
|
||||
return [
|
||||
serializers.SymbolMarginInfo.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/info/margin/sym_all")
|
||||
for sub_data in self._m.post("auth/r/info/margin/sym_all")
|
||||
]
|
||||
|
||||
def get_positions(self) -> List[Position]:
|
||||
return [
|
||||
serializers.Position.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/positions")
|
||||
for sub_data in self._m.post("auth/r/positions")
|
||||
]
|
||||
|
||||
def claim_position(
|
||||
self, id: int, *, amount: Optional[Union[str, float, Decimal]] = None
|
||||
) -> Notification[PositionClaim]:
|
||||
return _Notification[PositionClaim](serializers.PositionClaim).parse(
|
||||
*self._post("auth/w/position/claim", body={"id": id, "amount": amount})
|
||||
*self._m.post("auth/w/position/claim", body={"id": id, "amount": amount})
|
||||
)
|
||||
|
||||
def increase_position(
|
||||
self, symbol: str, amount: Union[str, float, Decimal]
|
||||
) -> Notification[PositionIncrease]:
|
||||
return _Notification[PositionIncrease](serializers.PositionIncrease).parse(
|
||||
*self._post(
|
||||
*self._m.post(
|
||||
"auth/w/position/increase", body={"symbol": symbol, "amount": amount}
|
||||
)
|
||||
)
|
||||
@@ -286,7 +288,7 @@ class RestAuthEndpoints(Middleware):
|
||||
self, symbol: str, amount: Union[str, float, Decimal]
|
||||
) -> PositionIncreaseInfo:
|
||||
return serializers.PositionIncreaseInfo.parse(
|
||||
*self._post(
|
||||
*self._m.post(
|
||||
"auth/r/position/increase/info",
|
||||
body={"symbol": symbol, "amount": amount},
|
||||
)
|
||||
@@ -301,7 +303,7 @@ class RestAuthEndpoints(Middleware):
|
||||
) -> List[PositionHistory]:
|
||||
return [
|
||||
serializers.PositionHistory.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
for sub_data in self._m.post(
|
||||
"auth/r/positions/hist",
|
||||
body={"start": start, "end": end, "limit": limit},
|
||||
)
|
||||
@@ -316,7 +318,7 @@ class RestAuthEndpoints(Middleware):
|
||||
) -> List[PositionSnapshot]:
|
||||
return [
|
||||
serializers.PositionSnapshot.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
for sub_data in self._m.post(
|
||||
"auth/r/positions/snap",
|
||||
body={"start": start, "end": end, "limit": limit},
|
||||
)
|
||||
@@ -334,7 +336,7 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.PositionAudit.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/positions/audit", body=body)
|
||||
for sub_data in self._m.post("auth/r/positions/audit", body=body)
|
||||
]
|
||||
|
||||
def set_derivative_position_collateral(
|
||||
@@ -342,7 +344,7 @@ class RestAuthEndpoints(Middleware):
|
||||
) -> DerivativePositionCollateral:
|
||||
return serializers.DerivativePositionCollateral.parse(
|
||||
*(
|
||||
self._post(
|
||||
self._m.post(
|
||||
"auth/w/deriv/collateral/set",
|
||||
body={"symbol": symbol, "collateral": collateral},
|
||||
)[0]
|
||||
@@ -353,7 +355,7 @@ class RestAuthEndpoints(Middleware):
|
||||
self, symbol: str
|
||||
) -> DerivativePositionCollateralLimits:
|
||||
return serializers.DerivativePositionCollateralLimits.parse(
|
||||
*self._post("auth/calc/deriv/collateral/limit", body={"symbol": symbol})
|
||||
*self._m.post("auth/calc/deriv/collateral/limit", body={"symbol": symbol})
|
||||
)
|
||||
|
||||
def get_funding_offers(self, *, symbol: Optional[str] = None) -> List[FundingOffer]:
|
||||
@@ -364,7 +366,7 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.FundingOffer.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint)
|
||||
for sub_data in self._m.post(endpoint)
|
||||
]
|
||||
|
||||
def submit_funding_offer(
|
||||
@@ -387,22 +389,24 @@ class RestAuthEndpoints(Middleware):
|
||||
}
|
||||
|
||||
return _Notification[FundingOffer](serializers.FundingOffer).parse(
|
||||
*self._post("auth/w/funding/offer/submit", body=body)
|
||||
*self._m.post("auth/w/funding/offer/submit", body=body)
|
||||
)
|
||||
|
||||
def cancel_funding_offer(self, id: int) -> Notification[FundingOffer]:
|
||||
return _Notification[FundingOffer](serializers.FundingOffer).parse(
|
||||
*self._post("auth/w/funding/offer/cancel", body={"id": id})
|
||||
*self._m.post("auth/w/funding/offer/cancel", body={"id": id})
|
||||
)
|
||||
|
||||
def cancel_all_funding_offers(self, currency: str) -> Notification[Literal[None]]:
|
||||
return _Notification[Literal[None]](None).parse(
|
||||
*self._post("auth/w/funding/offer/cancel/all", body={"currency": currency})
|
||||
*self._m.post(
|
||||
"auth/w/funding/offer/cancel/all", body={"currency": currency}
|
||||
)
|
||||
)
|
||||
|
||||
def submit_funding_close(self, id: int) -> Notification[Literal[None]]:
|
||||
return _Notification[Literal[None]](None).parse(
|
||||
*self._post("auth/w/funding/close", body={"id": id})
|
||||
*self._m.post("auth/w/funding/close", body={"id": id})
|
||||
)
|
||||
|
||||
def toggle_auto_renew(
|
||||
@@ -423,7 +427,7 @@ class RestAuthEndpoints(Middleware):
|
||||
}
|
||||
|
||||
return _Notification[FundingAutoRenew](serializers.FundingAutoRenew).parse(
|
||||
*self._post("auth/w/funding/auto", body=body)
|
||||
*self._m.post("auth/w/funding/auto", body=body)
|
||||
)
|
||||
|
||||
def toggle_keep_funding(
|
||||
@@ -434,7 +438,7 @@ class RestAuthEndpoints(Middleware):
|
||||
changes: Optional[Dict[int, Literal[1, 2]]] = None,
|
||||
) -> Notification[Literal[None]]:
|
||||
return _Notification[Literal[None]](None).parse(
|
||||
*self._post(
|
||||
*self._m.post(
|
||||
"auth/w/funding/keep",
|
||||
body={"type": type, "id": ids, "changes": changes},
|
||||
)
|
||||
@@ -455,7 +459,7 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.FundingOffer.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
for sub_data in self._m.post(
|
||||
endpoint, body={"start": start, "end": end, "limit": limit}
|
||||
)
|
||||
]
|
||||
@@ -468,7 +472,7 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.FundingLoan.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint)
|
||||
for sub_data in self._m.post(endpoint)
|
||||
]
|
||||
|
||||
def get_funding_loans_history(
|
||||
@@ -486,7 +490,7 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.FundingLoan.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
for sub_data in self._m.post(
|
||||
endpoint, body={"start": start, "end": end, "limit": limit}
|
||||
)
|
||||
]
|
||||
@@ -501,7 +505,7 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.FundingCredit.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint)
|
||||
for sub_data in self._m.post(endpoint)
|
||||
]
|
||||
|
||||
def get_funding_credits_history(
|
||||
@@ -519,7 +523,7 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.FundingCredit.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
for sub_data in self._m.post(
|
||||
endpoint, body={"start": start, "end": end, "limit": limit}
|
||||
)
|
||||
]
|
||||
@@ -542,12 +546,12 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.FundingTrade.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint, body=body)
|
||||
for sub_data in self._m.post(endpoint, body=body)
|
||||
]
|
||||
|
||||
def get_funding_info(self, key: str) -> FundingInfo:
|
||||
return serializers.FundingInfo.parse(
|
||||
*(self._post(f"auth/r/info/funding/{key}")[2])
|
||||
*(self._m.post(f"auth/r/info/funding/{key}")[2])
|
||||
)
|
||||
|
||||
def transfer_between_wallets(
|
||||
@@ -567,7 +571,7 @@ class RestAuthEndpoints(Middleware):
|
||||
}
|
||||
|
||||
return _Notification[Transfer](serializers.Transfer).parse(
|
||||
*self._post("auth/w/transfer", body=body)
|
||||
*self._m.post("auth/w/transfer", body=body)
|
||||
)
|
||||
|
||||
def submit_wallet_withdrawal(
|
||||
@@ -581,14 +585,14 @@ class RestAuthEndpoints(Middleware):
|
||||
}
|
||||
|
||||
return _Notification[Withdrawal](serializers.Withdrawal).parse(
|
||||
*self._post("auth/w/withdraw", body=body)
|
||||
*self._m.post("auth/w/withdraw", body=body)
|
||||
)
|
||||
|
||||
def get_deposit_address(
|
||||
self, wallet: str, method: str, op_renew: bool = False
|
||||
) -> Notification[DepositAddress]:
|
||||
return _Notification[DepositAddress](serializers.DepositAddress).parse(
|
||||
*self._post(
|
||||
*self._m.post(
|
||||
"auth/w/deposit/address",
|
||||
body={"wallet": wallet, "method": method, "op_renew": op_renew},
|
||||
)
|
||||
@@ -598,7 +602,7 @@ class RestAuthEndpoints(Middleware):
|
||||
self, wallet: str, currency: str, amount: Union[str, float, Decimal]
|
||||
) -> LightningNetworkInvoice:
|
||||
return serializers.LightningNetworkInvoice.parse(
|
||||
*self._post(
|
||||
*self._m.post(
|
||||
"auth/w/deposit/invoice",
|
||||
body={"wallet": wallet, "currency": currency, "amount": amount},
|
||||
)
|
||||
@@ -619,7 +623,7 @@ class RestAuthEndpoints(Middleware):
|
||||
|
||||
return [
|
||||
serializers.Movement.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
for sub_data in self._m.post(
|
||||
endpoint, body={"start": start, "end": end, "limit": limit}
|
||||
)
|
||||
]
|
||||
@@ -1,7 +1,7 @@
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, List, Literal, Optional, TypedDict, Union
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
|
||||
from bfxapi.rest.middleware import Middleware
|
||||
from bfxapi.rest._interface import Interface
|
||||
from bfxapi.types import (
|
||||
CurrencyConversion,
|
||||
InvoicePage,
|
||||
@@ -11,29 +11,14 @@ from bfxapi.types import (
|
||||
MerchantUnlinkedDeposit,
|
||||
)
|
||||
|
||||
_CustomerInfo = TypedDict(
|
||||
"_CustomerInfo",
|
||||
{
|
||||
"nationality": str,
|
||||
"resid_country": str,
|
||||
"resid_city": str,
|
||||
"resid_zip_code": str,
|
||||
"resid_street": str,
|
||||
"resid_building_no": str,
|
||||
"full_name": str,
|
||||
"email": str,
|
||||
"tos_accepted": bool,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class RestMerchantEndpoints(Middleware):
|
||||
class RestMerchantEndpoints(Interface):
|
||||
def submit_invoice(
|
||||
self,
|
||||
amount: Union[str, float, Decimal],
|
||||
currency: str,
|
||||
order_id: str,
|
||||
customer_info: _CustomerInfo,
|
||||
customer_info: Dict[str, Any],
|
||||
pay_currencies: List[str],
|
||||
*,
|
||||
duration: Optional[int] = None,
|
||||
@@ -51,7 +36,7 @@ class RestMerchantEndpoints(Middleware):
|
||||
"redirectUrl": redirect_url,
|
||||
}
|
||||
|
||||
data = self._post("auth/w/ext/pay/invoice/create", body=body)
|
||||
data = self._m.post("auth/w/ext/pay/invoice/create", body=body)
|
||||
|
||||
return InvoiceSubmission.parse(data)
|
||||
|
||||
@@ -65,7 +50,7 @@ class RestMerchantEndpoints(Middleware):
|
||||
) -> List[InvoiceSubmission]:
|
||||
body = {"id": id, "start": start, "end": end, "limit": limit}
|
||||
|
||||
data = self._post("auth/r/ext/pay/invoices", body=body)
|
||||
data = self._m.post("auth/r/ext/pay/invoices", body=body)
|
||||
|
||||
return [InvoiceSubmission.parse(sub_data) for sub_data in data]
|
||||
|
||||
@@ -96,7 +81,7 @@ class RestMerchantEndpoints(Middleware):
|
||||
"orderId": order_id,
|
||||
}
|
||||
|
||||
data = self._post("auth/r/ext/pay/invoices/paginated", body=body)
|
||||
data = self._m.post("auth/r/ext/pay/invoices/paginated", body=body)
|
||||
|
||||
return InvoicePage.parse(data)
|
||||
|
||||
@@ -105,7 +90,7 @@ class RestMerchantEndpoints(Middleware):
|
||||
) -> List[InvoiceStats]:
|
||||
return [
|
||||
InvoiceStats(**sub_data)
|
||||
for sub_data in self._post(
|
||||
for sub_data in self._m.post(
|
||||
"auth/r/ext/pay/invoice/stats/count",
|
||||
body={"status": status, "format": format},
|
||||
)
|
||||
@@ -116,7 +101,7 @@ class RestMerchantEndpoints(Middleware):
|
||||
) -> List[InvoiceStats]:
|
||||
return [
|
||||
InvoiceStats(**sub_data)
|
||||
for sub_data in self._post(
|
||||
for sub_data in self._m.post(
|
||||
"auth/r/ext/pay/invoice/stats/earning",
|
||||
body={"currency": currency, "format": format},
|
||||
)
|
||||
@@ -137,26 +122,26 @@ class RestMerchantEndpoints(Middleware):
|
||||
"ledgerId": ledger_id,
|
||||
}
|
||||
|
||||
data = self._post("auth/w/ext/pay/invoice/complete", body=body)
|
||||
data = self._m.post("auth/w/ext/pay/invoice/complete", body=body)
|
||||
|
||||
return InvoiceSubmission.parse(data)
|
||||
|
||||
def expire_invoice(self, id: str) -> InvoiceSubmission:
|
||||
body = {"id": id}
|
||||
|
||||
data = self._post("auth/w/ext/pay/invoice/expire", body=body)
|
||||
data = self._m.post("auth/w/ext/pay/invoice/expire", body=body)
|
||||
|
||||
return InvoiceSubmission.parse(data)
|
||||
|
||||
def get_currency_conversion_list(self) -> List[CurrencyConversion]:
|
||||
return [
|
||||
CurrencyConversion(**sub_data)
|
||||
for sub_data in self._post("auth/r/ext/pay/settings/convert/list")
|
||||
for sub_data in self._m.post("auth/r/ext/pay/settings/convert/list")
|
||||
]
|
||||
|
||||
def add_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
|
||||
return bool(
|
||||
self._post(
|
||||
self._m.post(
|
||||
"auth/w/ext/pay/settings/convert/create",
|
||||
body={"baseCcy": base_ccy, "convertCcy": convert_ccy},
|
||||
)
|
||||
@@ -164,7 +149,7 @@ class RestMerchantEndpoints(Middleware):
|
||||
|
||||
def remove_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
|
||||
return bool(
|
||||
self._post(
|
||||
self._m.post(
|
||||
"auth/w/ext/pay/settings/convert/remove",
|
||||
body={"baseCcy": base_ccy, "convertCcy": convert_ccy},
|
||||
)
|
||||
@@ -172,16 +157,16 @@ class RestMerchantEndpoints(Middleware):
|
||||
|
||||
def set_merchant_settings(self, key: str, val: Any) -> bool:
|
||||
return bool(
|
||||
self._post("auth/w/ext/pay/settings/set", body={"key": key, "val": val})
|
||||
self._m.post("auth/w/ext/pay/settings/set", body={"key": key, "val": val})
|
||||
)
|
||||
|
||||
def get_merchant_settings(self, key: str) -> Any:
|
||||
return self._post("auth/r/ext/pay/settings/get", body={"key": key})
|
||||
return self._m.post("auth/r/ext/pay/settings/get", body={"key": key})
|
||||
|
||||
def list_merchant_settings(
|
||||
self, keys: Optional[List[str]] = None
|
||||
) -> Dict[str, Any]:
|
||||
return self._post("auth/r/ext/pay/settings/list", body={"keys": keys or []})
|
||||
return self._m.post("auth/r/ext/pay/settings/list", body={"keys": keys or []})
|
||||
|
||||
def get_deposits(
|
||||
self,
|
||||
@@ -193,7 +178,7 @@ class RestMerchantEndpoints(Middleware):
|
||||
) -> List[MerchantDeposit]:
|
||||
body = {"from": start, "to": to, "ccy": ccy, "unlinked": unlinked}
|
||||
|
||||
data = self._post("auth/r/ext/pay/deposits", body=body)
|
||||
data = self._m.post("auth/r/ext/pay/deposits", body=body)
|
||||
|
||||
return [MerchantDeposit(**sub_data) for sub_data in data]
|
||||
|
||||
@@ -202,6 +187,6 @@ class RestMerchantEndpoints(Middleware):
|
||||
) -> List[MerchantUnlinkedDeposit]:
|
||||
body = {"ccy": ccy, "start": start, "end": end}
|
||||
|
||||
data = self._post("/auth/r/ext/pay/deposits/unlinked", body=body)
|
||||
data = self._m.post("/auth/r/ext/pay/deposits/unlinked", body=body)
|
||||
|
||||
return [MerchantUnlinkedDeposit(**sub_data) for sub_data in data]
|
||||
@@ -1,7 +1,8 @@
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, List, Literal, Optional, Union, cast
|
||||
|
||||
from ...types import (
|
||||
from bfxapi.rest._interface import Interface
|
||||
from bfxapi.types import (
|
||||
Candle,
|
||||
DerivativesStatus,
|
||||
FundingCurrencyBook,
|
||||
@@ -25,20 +26,19 @@ from ...types import (
|
||||
TradingPairTrade,
|
||||
serializers,
|
||||
)
|
||||
from ..middleware import Middleware
|
||||
|
||||
|
||||
class RestPublicEndpoints(Middleware):
|
||||
class RestPublicEndpoints(Interface):
|
||||
def conf(self, config: str) -> Any:
|
||||
return self._get(f"conf/{config}")[0]
|
||||
return self._m.get(f"conf/{config}")[0]
|
||||
|
||||
def get_platform_status(self) -> PlatformStatus:
|
||||
return serializers.PlatformStatus.parse(*self._get("platform/status"))
|
||||
return serializers.PlatformStatus.parse(*self._m.get("platform/status"))
|
||||
|
||||
def get_tickers(
|
||||
self, symbols: List[str]
|
||||
) -> Dict[str, Union[TradingPairTicker, FundingCurrencyTicker]]:
|
||||
data = self._get("tickers", params={"symbols": ",".join(symbols)})
|
||||
data = self._m.get("tickers", params={"symbols": ",".join(symbols)})
|
||||
|
||||
parsers = {
|
||||
"t": serializers.TradingPairTicker.parse,
|
||||
@@ -83,10 +83,10 @@ class RestPublicEndpoints(Middleware):
|
||||
return cast(Dict[str, FundingCurrencyTicker], data)
|
||||
|
||||
def get_t_ticker(self, symbol: str) -> TradingPairTicker:
|
||||
return serializers.TradingPairTicker.parse(*self._get(f"ticker/{symbol}"))
|
||||
return serializers.TradingPairTicker.parse(*self._m.get(f"ticker/{symbol}"))
|
||||
|
||||
def get_f_ticker(self, symbol: str) -> FundingCurrencyTicker:
|
||||
return serializers.FundingCurrencyTicker.parse(*self._get(f"ticker/{symbol}"))
|
||||
return serializers.FundingCurrencyTicker.parse(*self._m.get(f"ticker/{symbol}"))
|
||||
|
||||
def get_tickers_history(
|
||||
self,
|
||||
@@ -98,7 +98,7 @@ class RestPublicEndpoints(Middleware):
|
||||
) -> List[TickersHistory]:
|
||||
return [
|
||||
serializers.TickersHistory.parse(*sub_data)
|
||||
for sub_data in self._get(
|
||||
for sub_data in self._m.get(
|
||||
"tickers/hist",
|
||||
params={
|
||||
"symbols": ",".join(symbols),
|
||||
@@ -119,7 +119,7 @@ class RestPublicEndpoints(Middleware):
|
||||
sort: Optional[int] = None,
|
||||
) -> List[TradingPairTrade]:
|
||||
params = {"limit": limit, "start": start, "end": end, "sort": sort}
|
||||
data = self._get(f"trades/{pair}/hist", params=params)
|
||||
data = self._m.get(f"trades/{pair}/hist", params=params)
|
||||
return [serializers.TradingPairTrade.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_f_trades(
|
||||
@@ -132,7 +132,7 @@ class RestPublicEndpoints(Middleware):
|
||||
sort: Optional[int] = None,
|
||||
) -> List[FundingCurrencyTrade]:
|
||||
params = {"limit": limit, "start": start, "end": end, "sort": sort}
|
||||
data = self._get(f"trades/{currency}/hist", params=params)
|
||||
data = self._m.get(f"trades/{currency}/hist", params=params)
|
||||
return [serializers.FundingCurrencyTrade.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_t_book(
|
||||
@@ -144,7 +144,7 @@ class RestPublicEndpoints(Middleware):
|
||||
) -> List[TradingPairBook]:
|
||||
return [
|
||||
serializers.TradingPairBook.parse(*sub_data)
|
||||
for sub_data in self._get(f"book/{pair}/{precision}", params={"len": len})
|
||||
for sub_data in self._m.get(f"book/{pair}/{precision}", params={"len": len})
|
||||
]
|
||||
|
||||
def get_f_book(
|
||||
@@ -156,7 +156,7 @@ class RestPublicEndpoints(Middleware):
|
||||
) -> List[FundingCurrencyBook]:
|
||||
return [
|
||||
serializers.FundingCurrencyBook.parse(*sub_data)
|
||||
for sub_data in self._get(
|
||||
for sub_data in self._m.get(
|
||||
f"book/{currency}/{precision}", params={"len": len}
|
||||
)
|
||||
]
|
||||
@@ -166,7 +166,7 @@ class RestPublicEndpoints(Middleware):
|
||||
) -> List[TradingPairRawBook]:
|
||||
return [
|
||||
serializers.TradingPairRawBook.parse(*sub_data)
|
||||
for sub_data in self._get(f"book/{pair}/R0", params={"len": len})
|
||||
for sub_data in self._m.get(f"book/{pair}/R0", params={"len": len})
|
||||
]
|
||||
|
||||
def get_f_raw_book(
|
||||
@@ -174,7 +174,7 @@ class RestPublicEndpoints(Middleware):
|
||||
) -> List[FundingCurrencyRawBook]:
|
||||
return [
|
||||
serializers.FundingCurrencyRawBook.parse(*sub_data)
|
||||
for sub_data in self._get(f"book/{currency}/R0", params={"len": len})
|
||||
for sub_data in self._m.get(f"book/{currency}/R0", params={"len": len})
|
||||
]
|
||||
|
||||
def get_stats_hist(
|
||||
@@ -187,7 +187,7 @@ class RestPublicEndpoints(Middleware):
|
||||
limit: Optional[int] = None,
|
||||
) -> List[Statistic]:
|
||||
params = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
data = self._get(f"stats1/{resource}/hist", params=params)
|
||||
data = self._m.get(f"stats1/{resource}/hist", params=params)
|
||||
return [serializers.Statistic.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_stats_last(
|
||||
@@ -200,7 +200,7 @@ class RestPublicEndpoints(Middleware):
|
||||
limit: Optional[int] = None,
|
||||
) -> Statistic:
|
||||
params = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
data = self._get(f"stats1/{resource}/last", params=params)
|
||||
data = self._m.get(f"stats1/{resource}/last", params=params)
|
||||
return serializers.Statistic.parse(*data)
|
||||
|
||||
def get_candles_hist(
|
||||
@@ -214,7 +214,7 @@ class RestPublicEndpoints(Middleware):
|
||||
limit: Optional[int] = None,
|
||||
) -> List[Candle]:
|
||||
params = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
data = self._get(f"candles/trade:{tf}:{symbol}/hist", params=params)
|
||||
data = self._m.get(f"candles/trade:{tf}:{symbol}/hist", params=params)
|
||||
return [serializers.Candle.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_candles_last(
|
||||
@@ -228,7 +228,7 @@ class RestPublicEndpoints(Middleware):
|
||||
limit: Optional[int] = None,
|
||||
) -> Candle:
|
||||
params = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
data = self._get(f"candles/trade:{tf}:{symbol}/last", params=params)
|
||||
data = self._m.get(f"candles/trade:{tf}:{symbol}/last", params=params)
|
||||
return serializers.Candle.parse(*data)
|
||||
|
||||
def get_derivatives_status(
|
||||
@@ -239,7 +239,7 @@ class RestPublicEndpoints(Middleware):
|
||||
else:
|
||||
params = {"keys": ",".join(keys)}
|
||||
|
||||
data = self._get("status/deriv", params=params)
|
||||
data = self._m.get("status/deriv", params=params)
|
||||
|
||||
return {
|
||||
key: serializers.DerivativesStatus.parse(*sub_data)
|
||||
@@ -257,7 +257,7 @@ class RestPublicEndpoints(Middleware):
|
||||
limit: Optional[int] = None,
|
||||
) -> List[DerivativesStatus]:
|
||||
params = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
data = self._get(f"status/deriv/{key}/hist", params=params)
|
||||
data = self._m.get(f"status/deriv/{key}/hist", params=params)
|
||||
return [serializers.DerivativesStatus.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_liquidations(
|
||||
@@ -269,7 +269,7 @@ class RestPublicEndpoints(Middleware):
|
||||
limit: Optional[int] = None,
|
||||
) -> List[Liquidation]:
|
||||
params = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
data = self._get("liquidations/hist", params=params)
|
||||
data = self._m.get("liquidations/hist", params=params)
|
||||
return [serializers.Liquidation.parse(*sub_data[0]) for sub_data in data]
|
||||
|
||||
def get_seed_candles(
|
||||
@@ -283,7 +283,7 @@ class RestPublicEndpoints(Middleware):
|
||||
limit: Optional[int] = None,
|
||||
) -> List[Candle]:
|
||||
params = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
data = self._get(f"candles/trade:{tf}:{symbol}/hist", params=params)
|
||||
data = self._m.get(f"candles/trade:{tf}:{symbol}/hist", params=params)
|
||||
return [serializers.Candle.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_leaderboards_hist(
|
||||
@@ -296,7 +296,7 @@ class RestPublicEndpoints(Middleware):
|
||||
limit: Optional[int] = None,
|
||||
) -> List[Leaderboard]:
|
||||
params = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
data = self._get(f"rankings/{resource}/hist", params=params)
|
||||
data = self._m.get(f"rankings/{resource}/hist", params=params)
|
||||
return [serializers.Leaderboard.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_leaderboards_last(
|
||||
@@ -309,7 +309,7 @@ class RestPublicEndpoints(Middleware):
|
||||
limit: Optional[int] = None,
|
||||
) -> Leaderboard:
|
||||
params = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
data = self._get(f"rankings/{resource}/last", params=params)
|
||||
data = self._m.get(f"rankings/{resource}/last", params=params)
|
||||
return serializers.Leaderboard.parse(*data)
|
||||
|
||||
def get_funding_stats(
|
||||
@@ -321,18 +321,18 @@ class RestPublicEndpoints(Middleware):
|
||||
limit: Optional[int] = None,
|
||||
) -> List[FundingStatistic]:
|
||||
params = {"start": start, "end": end, "limit": limit}
|
||||
data = self._get(f"funding/stats/{symbol}/hist", params=params)
|
||||
data = self._m.get(f"funding/stats/{symbol}/hist", params=params)
|
||||
return [serializers.FundingStatistic.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_pulse_profile_details(self, nickname: str) -> PulseProfile:
|
||||
return serializers.PulseProfile.parse(*self._get(f"pulse/profile/{nickname}"))
|
||||
return serializers.PulseProfile.parse(*self._m.get(f"pulse/profile/{nickname}"))
|
||||
|
||||
def get_pulse_message_history(
|
||||
self, *, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[PulseMessage]:
|
||||
messages = []
|
||||
|
||||
for sub_data in self._get("pulse/hist", params={"end": end, "limit": limit}):
|
||||
for sub_data in self._m.get("pulse/hist", params={"end": end, "limit": limit}):
|
||||
sub_data[18] = sub_data[18][0]
|
||||
message = serializers.PulseMessage.parse(*sub_data)
|
||||
messages.append(message)
|
||||
@@ -347,7 +347,7 @@ class RestPublicEndpoints(Middleware):
|
||||
price_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
) -> TradingMarketAveragePrice:
|
||||
return serializers.TradingMarketAveragePrice.parse(
|
||||
*self._post(
|
||||
*self._m.post(
|
||||
"calc/trade/avg",
|
||||
body={"symbol": symbol, "amount": amount, "price_limit": price_limit},
|
||||
)
|
||||
@@ -362,7 +362,7 @@ class RestPublicEndpoints(Middleware):
|
||||
rate_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
) -> FundingMarketAveragePrice:
|
||||
return serializers.FundingMarketAveragePrice.parse(
|
||||
*self._post(
|
||||
*self._m.post(
|
||||
"calc/trade/avg",
|
||||
body={
|
||||
"symbol": symbol,
|
||||
@@ -375,5 +375,5 @@ class RestPublicEndpoints(Middleware):
|
||||
|
||||
def get_fx_rate(self, ccy1: str, ccy2: str) -> FxRate:
|
||||
return serializers.FxRate.parse(
|
||||
*self._post("calc/fx", body={"ccy1": ccy1, "ccy2": ccy2})
|
||||
*self._m.post("calc/fx", body={"ccy1": ccy1, "ccy2": ccy2})
|
||||
)
|
||||
@@ -1,10 +1,6 @@
|
||||
from bfxapi.exceptions import BfxBaseException
|
||||
|
||||
|
||||
class NotFoundError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class RequestParametersError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from .middleware import Middleware
|
||||
@@ -1,135 +0,0 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import time
|
||||
from enum import IntEnum
|
||||
from http import HTTPStatus
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
import requests
|
||||
|
||||
from ..._utils.json_decoder import JSONDecoder
|
||||
from ..._utils.json_encoder import JSONEncoder
|
||||
from ...exceptions import InvalidCredentialError
|
||||
from ..exceptions import NotFoundError, 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, self.api_key, self.api_secret = host, api_key, api_secret
|
||||
|
||||
def __build_authentication_headers(self, endpoint: str, data: Optional[str] = None):
|
||||
assert isinstance(self.api_key, str) and isinstance(
|
||||
self.api_secret, str
|
||||
), "API_KEY and API_SECRET must be both strings"
|
||||
|
||||
nonce = str(round(time.time() * 1_000_000))
|
||||
|
||||
if data is None:
|
||||
path = f"/api/v2/{endpoint}{nonce}"
|
||||
else:
|
||||
path = f"/api/v2/{endpoint}{nonce}{data}"
|
||||
|
||||
signature = hmac.new(
|
||||
self.api_secret.encode("utf8"), path.encode("utf8"), hashlib.sha384
|
||||
).hexdigest()
|
||||
|
||||
return {
|
||||
"bfx-nonce": nonce,
|
||||
"bfx-signature": signature,
|
||||
"bfx-apikey": self.api_key,
|
||||
}
|
||||
|
||||
def _get(self, endpoint: str, params: Optional["_Params"] = None) -> Any:
|
||||
response = requests.get(
|
||||
url=f"{self.host}/{endpoint}", params=params, timeout=Middleware.TIMEOUT
|
||||
)
|
||||
|
||||
if response.status_code == HTTPStatus.NOT_FOUND:
|
||||
raise NotFoundError(f"No resources found at endpoint <{endpoint}>.")
|
||||
|
||||
data = response.json(cls=JSONDecoder)
|
||||
|
||||
if len(data) and data[0] == "error":
|
||||
if data[1] == _Error.ERR_PARAMS:
|
||||
raise RequestParametersError(
|
||||
"The request was rejected with the "
|
||||
f"following parameter error: <{data[2]}>"
|
||||
)
|
||||
|
||||
if (
|
||||
data[1] is None
|
||||
or data[1] == _Error.ERR_UNK
|
||||
or data[1] == _Error.ERR_GENERIC
|
||||
):
|
||||
raise UnknownGenericError(
|
||||
"The server replied to the request with "
|
||||
f"a generic error with message: <{data[2]}>."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
def _post(
|
||||
self,
|
||||
endpoint: str,
|
||||
params: Optional["_Params"] = None,
|
||||
body: Optional[Any] = None,
|
||||
_ignore_authentication_headers: bool = False,
|
||||
) -> Any:
|
||||
data = body and json.dumps(body, cls=JSONEncoder) or None
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
if self.api_key and self.api_secret and not _ignore_authentication_headers:
|
||||
headers = {**headers, **self.__build_authentication_headers(endpoint, data)}
|
||||
|
||||
response = requests.post(
|
||||
url=f"{self.host}/{endpoint}",
|
||||
params=params,
|
||||
data=data,
|
||||
headers=headers,
|
||||
timeout=Middleware.TIMEOUT,
|
||||
)
|
||||
|
||||
if response.status_code == HTTPStatus.NOT_FOUND:
|
||||
raise NotFoundError(f"No resources found at endpoint <{endpoint}>.")
|
||||
|
||||
data = response.json(cls=JSONDecoder)
|
||||
|
||||
if isinstance(data, list) and len(data) and data[0] == "error":
|
||||
if data[1] == _Error.ERR_PARAMS:
|
||||
raise RequestParametersError(
|
||||
"The request was rejected with the "
|
||||
f"following parameter error: <{data[2]}>"
|
||||
)
|
||||
|
||||
if data[1] == _Error.ERR_AUTH_FAIL:
|
||||
raise InvalidCredentialError(
|
||||
"Cannot authenticate with given API-KEY and API-SECRET."
|
||||
)
|
||||
|
||||
if (
|
||||
data[1] is None
|
||||
or data[1] == _Error.ERR_UNK
|
||||
or data[1] == _Error.ERR_GENERIC
|
||||
):
|
||||
raise UnknownGenericError(
|
||||
"The server replied to the request with "
|
||||
f"a generic error with message: <{data[2]}>."
|
||||
)
|
||||
|
||||
return data
|
||||
@@ -24,8 +24,8 @@ def partial(cls):
|
||||
|
||||
if len(kwargs) != 0:
|
||||
raise TypeError(
|
||||
f"{cls.__name__}.__init__() got an unexpected "
|
||||
"keyword argument '{list(kwargs.keys())[0]}'"
|
||||
f"{cls.__name__}.__init__() got an unexpected keyword argument "
|
||||
f"'{list(kwargs.keys())[0]}'"
|
||||
)
|
||||
|
||||
cls.__init__ = __init__
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from decimal import Decimal
|
||||
from typing import Any, Awaitable, Callable, List, Optional, Tuple, Union
|
||||
from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Union
|
||||
|
||||
_Handler = Callable[[str, Any], Awaitable[None]]
|
||||
|
||||
@@ -23,6 +23,7 @@ class BfxWebSocketInputs:
|
||||
cid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
tif: Optional[str] = None,
|
||||
meta: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"on",
|
||||
@@ -39,6 +40,7 @@ class BfxWebSocketInputs:
|
||||
"cid": cid,
|
||||
"flags": flags,
|
||||
"tif": tif,
|
||||
"meta": meta,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user