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:
Davide Casale
2024-04-03 22:34:23 +02:00
committed by GitHub
parent 3136b9cfe4
commit bdd78a817d
15 changed files with 274 additions and 283 deletions

View File

@@ -1,6 +1 @@
from .endpoints import ( from ._bfx_rest_interface import BfxRestInterface
BfxRestInterface,
RestAuthEndpoints,
RestMerchantEndpoints,
RestPublicEndpoints,
)

View File

@@ -1,14 +1,20 @@
from .rest_auth_endpoints import RestAuthEndpoints from typing import Optional
from .rest_merchant_endpoints import RestMerchantEndpoints
from .rest_public_endpoints import RestPublicEndpoints from bfxapi.rest._interfaces import (
RestAuthEndpoints,
RestMerchantEndpoints,
class BfxRestInterface: RestPublicEndpoints,
VERSION = 2 )
def __init__(self, host, api_key=None, api_secret=None):
self.public = RestPublicEndpoints(host=host) class BfxRestInterface:
self.auth = RestAuthEndpoints(host=host, api_key=api_key, api_secret=api_secret) def __init__(
self.merchant = RestMerchantEndpoints( self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
host=host, api_key=api_key, api_secret=api_secret ):
) 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)

View File

@@ -0,0 +1 @@
from .interface import Interface

View 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)

View 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,
}

View File

@@ -1,4 +1,3 @@
from .bfx_rest_interface import BfxRestInterface from .rest_auth_endpoints import RestAuthEndpoints
from .rest_auth_endpoints import RestAuthEndpoints from .rest_merchant_endpoints import RestMerchantEndpoints
from .rest_merchant_endpoints import RestMerchantEndpoints from .rest_public_endpoints import RestPublicEndpoints
from .rest_public_endpoints import RestPublicEndpoints

View File

@@ -1,7 +1,8 @@
from decimal import Decimal 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, BalanceAvailable,
BaseMarginInfo, BaseMarginInfo,
DepositAddress, DepositAddress,
@@ -35,18 +36,17 @@ from ...types import (
Withdrawal, Withdrawal,
serializers, serializers,
) )
from ...types.serializers import _Notification from bfxapi.types.serializers import _Notification
from ..middleware import Middleware
class RestAuthEndpoints(Middleware): class RestAuthEndpoints(Interface):
def get_user_info(self) -> UserInfo: 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]: def get_login_history(self) -> List[LoginHistory]:
return [ return [
serializers.LoginHistory.parse(*sub_data) 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( 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} body = {"symbol": symbol, "type": type, "dir": dir, "rate": rate, "lev": lev}
return serializers.BalanceAvailable.parse( 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]: def get_wallets(self) -> List[Wallet]:
return [ return [
serializers.Wallet.parse(*sub_data) 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( def get_orders(
@@ -80,7 +80,7 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.Order.parse(*sub_data) 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( def submit_order(
@@ -98,6 +98,7 @@ class RestAuthEndpoints(Middleware):
cid: Optional[int] = None, cid: Optional[int] = None,
flags: Optional[int] = None, flags: Optional[int] = None,
tif: Optional[str] = None, tif: Optional[str] = None,
meta: Optional[Dict[str, Any]] = None,
) -> Notification[Order]: ) -> Notification[Order]:
body = { body = {
"type": type, "type": type,
@@ -112,10 +113,11 @@ class RestAuthEndpoints(Middleware):
"cid": cid, "cid": cid,
"flags": flags, "flags": flags,
"tif": tif, "tif": tif,
"meta": meta,
} }
return _Notification[Order](serializers.Order).parse( 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( def update_order(
@@ -150,7 +152,7 @@ class RestAuthEndpoints(Middleware):
} }
return _Notification[Order](serializers.Order).parse( 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( def cancel_order(
@@ -161,7 +163,7 @@ class RestAuthEndpoints(Middleware):
cid_date: Optional[str] = None, cid_date: Optional[str] = None,
) -> Notification[Order]: ) -> Notification[Order]:
return _Notification[Order](serializers.Order).parse( return _Notification[Order](serializers.Order).parse(
*self._post( *self._m.post(
"auth/w/order/cancel", body={"id": id, "cid": cid, "cid_date": cid_date} "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} body = {"id": id, "cid": cid, "gid": gid, "all": all}
return _Notification[List[Order]](serializers.Order, is_iterable=True).parse( 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( def get_orders_history(
@@ -198,13 +200,13 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.Order.parse(*sub_data) 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]: def get_order_trades(self, symbol: str, id: int) -> List[OrderTrade]:
return [ return [
serializers.OrderTrade.parse(*sub_data) 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( def get_trades_history(
@@ -225,7 +227,7 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.Trade.parse(*sub_data) 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( def get_ledgers(
@@ -241,43 +243,43 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.Ledger.parse(*sub_data) 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: def get_base_margin_info(self) -> BaseMarginInfo:
return serializers.BaseMarginInfo.parse( 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: def get_symbol_margin_info(self, symbol: str) -> SymbolMarginInfo:
return serializers.SymbolMarginInfo.parse( 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]: def get_all_symbols_margin_info(self) -> List[SymbolMarginInfo]:
return [ return [
serializers.SymbolMarginInfo.parse(*sub_data) 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]: def get_positions(self) -> List[Position]:
return [ return [
serializers.Position.parse(*sub_data) 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( def claim_position(
self, id: int, *, amount: Optional[Union[str, float, Decimal]] = None self, id: int, *, amount: Optional[Union[str, float, Decimal]] = None
) -> Notification[PositionClaim]: ) -> Notification[PositionClaim]:
return _Notification[PositionClaim](serializers.PositionClaim).parse( 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( def increase_position(
self, symbol: str, amount: Union[str, float, Decimal] self, symbol: str, amount: Union[str, float, Decimal]
) -> Notification[PositionIncrease]: ) -> Notification[PositionIncrease]:
return _Notification[PositionIncrease](serializers.PositionIncrease).parse( return _Notification[PositionIncrease](serializers.PositionIncrease).parse(
*self._post( *self._m.post(
"auth/w/position/increase", body={"symbol": symbol, "amount": amount} "auth/w/position/increase", body={"symbol": symbol, "amount": amount}
) )
) )
@@ -286,7 +288,7 @@ class RestAuthEndpoints(Middleware):
self, symbol: str, amount: Union[str, float, Decimal] self, symbol: str, amount: Union[str, float, Decimal]
) -> PositionIncreaseInfo: ) -> PositionIncreaseInfo:
return serializers.PositionIncreaseInfo.parse( return serializers.PositionIncreaseInfo.parse(
*self._post( *self._m.post(
"auth/r/position/increase/info", "auth/r/position/increase/info",
body={"symbol": symbol, "amount": amount}, body={"symbol": symbol, "amount": amount},
) )
@@ -301,7 +303,7 @@ class RestAuthEndpoints(Middleware):
) -> List[PositionHistory]: ) -> List[PositionHistory]:
return [ return [
serializers.PositionHistory.parse(*sub_data) serializers.PositionHistory.parse(*sub_data)
for sub_data in self._post( for sub_data in self._m.post(
"auth/r/positions/hist", "auth/r/positions/hist",
body={"start": start, "end": end, "limit": limit}, body={"start": start, "end": end, "limit": limit},
) )
@@ -316,7 +318,7 @@ class RestAuthEndpoints(Middleware):
) -> List[PositionSnapshot]: ) -> List[PositionSnapshot]:
return [ return [
serializers.PositionSnapshot.parse(*sub_data) serializers.PositionSnapshot.parse(*sub_data)
for sub_data in self._post( for sub_data in self._m.post(
"auth/r/positions/snap", "auth/r/positions/snap",
body={"start": start, "end": end, "limit": limit}, body={"start": start, "end": end, "limit": limit},
) )
@@ -334,7 +336,7 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.PositionAudit.parse(*sub_data) 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( def set_derivative_position_collateral(
@@ -342,7 +344,7 @@ class RestAuthEndpoints(Middleware):
) -> DerivativePositionCollateral: ) -> DerivativePositionCollateral:
return serializers.DerivativePositionCollateral.parse( return serializers.DerivativePositionCollateral.parse(
*( *(
self._post( self._m.post(
"auth/w/deriv/collateral/set", "auth/w/deriv/collateral/set",
body={"symbol": symbol, "collateral": collateral}, body={"symbol": symbol, "collateral": collateral},
)[0] )[0]
@@ -353,7 +355,7 @@ class RestAuthEndpoints(Middleware):
self, symbol: str self, symbol: str
) -> DerivativePositionCollateralLimits: ) -> DerivativePositionCollateralLimits:
return serializers.DerivativePositionCollateralLimits.parse( 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]: def get_funding_offers(self, *, symbol: Optional[str] = None) -> List[FundingOffer]:
@@ -364,7 +366,7 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.FundingOffer.parse(*sub_data) serializers.FundingOffer.parse(*sub_data)
for sub_data in self._post(endpoint) for sub_data in self._m.post(endpoint)
] ]
def submit_funding_offer( def submit_funding_offer(
@@ -387,22 +389,24 @@ class RestAuthEndpoints(Middleware):
} }
return _Notification[FundingOffer](serializers.FundingOffer).parse( 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]: def cancel_funding_offer(self, id: int) -> Notification[FundingOffer]:
return _Notification[FundingOffer](serializers.FundingOffer).parse( 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]]: def cancel_all_funding_offers(self, currency: str) -> Notification[Literal[None]]:
return _Notification[Literal[None]](None).parse( 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]]: def submit_funding_close(self, id: int) -> Notification[Literal[None]]:
return _Notification[Literal[None]](None).parse( 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( def toggle_auto_renew(
@@ -423,7 +427,7 @@ class RestAuthEndpoints(Middleware):
} }
return _Notification[FundingAutoRenew](serializers.FundingAutoRenew).parse( 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( def toggle_keep_funding(
@@ -434,7 +438,7 @@ class RestAuthEndpoints(Middleware):
changes: Optional[Dict[int, Literal[1, 2]]] = None, changes: Optional[Dict[int, Literal[1, 2]]] = None,
) -> Notification[Literal[None]]: ) -> Notification[Literal[None]]:
return _Notification[Literal[None]](None).parse( return _Notification[Literal[None]](None).parse(
*self._post( *self._m.post(
"auth/w/funding/keep", "auth/w/funding/keep",
body={"type": type, "id": ids, "changes": changes}, body={"type": type, "id": ids, "changes": changes},
) )
@@ -455,7 +459,7 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.FundingOffer.parse(*sub_data) 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} endpoint, body={"start": start, "end": end, "limit": limit}
) )
] ]
@@ -468,7 +472,7 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.FundingLoan.parse(*sub_data) 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( def get_funding_loans_history(
@@ -486,7 +490,7 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.FundingLoan.parse(*sub_data) 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} endpoint, body={"start": start, "end": end, "limit": limit}
) )
] ]
@@ -501,7 +505,7 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.FundingCredit.parse(*sub_data) 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( def get_funding_credits_history(
@@ -519,7 +523,7 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.FundingCredit.parse(*sub_data) 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} endpoint, body={"start": start, "end": end, "limit": limit}
) )
] ]
@@ -542,12 +546,12 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.FundingTrade.parse(*sub_data) 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: def get_funding_info(self, key: str) -> FundingInfo:
return serializers.FundingInfo.parse( 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( def transfer_between_wallets(
@@ -567,7 +571,7 @@ class RestAuthEndpoints(Middleware):
} }
return _Notification[Transfer](serializers.Transfer).parse( 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( def submit_wallet_withdrawal(
@@ -581,14 +585,14 @@ class RestAuthEndpoints(Middleware):
} }
return _Notification[Withdrawal](serializers.Withdrawal).parse( 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( def get_deposit_address(
self, wallet: str, method: str, op_renew: bool = False self, wallet: str, method: str, op_renew: bool = False
) -> Notification[DepositAddress]: ) -> Notification[DepositAddress]:
return _Notification[DepositAddress](serializers.DepositAddress).parse( return _Notification[DepositAddress](serializers.DepositAddress).parse(
*self._post( *self._m.post(
"auth/w/deposit/address", "auth/w/deposit/address",
body={"wallet": wallet, "method": method, "op_renew": op_renew}, 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] self, wallet: str, currency: str, amount: Union[str, float, Decimal]
) -> LightningNetworkInvoice: ) -> LightningNetworkInvoice:
return serializers.LightningNetworkInvoice.parse( return serializers.LightningNetworkInvoice.parse(
*self._post( *self._m.post(
"auth/w/deposit/invoice", "auth/w/deposit/invoice",
body={"wallet": wallet, "currency": currency, "amount": amount}, body={"wallet": wallet, "currency": currency, "amount": amount},
) )
@@ -619,7 +623,7 @@ class RestAuthEndpoints(Middleware):
return [ return [
serializers.Movement.parse(*sub_data) 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} endpoint, body={"start": start, "end": end, "limit": limit}
) )
] ]

View File

@@ -1,7 +1,7 @@
from decimal import Decimal 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 ( from bfxapi.types import (
CurrencyConversion, CurrencyConversion,
InvoicePage, InvoicePage,
@@ -11,29 +11,14 @@ from bfxapi.types import (
MerchantUnlinkedDeposit, 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(Interface):
class RestMerchantEndpoints(Middleware):
def submit_invoice( def submit_invoice(
self, self,
amount: Union[str, float, Decimal], amount: Union[str, float, Decimal],
currency: str, currency: str,
order_id: str, order_id: str,
customer_info: _CustomerInfo, customer_info: Dict[str, Any],
pay_currencies: List[str], pay_currencies: List[str],
*, *,
duration: Optional[int] = None, duration: Optional[int] = None,
@@ -51,7 +36,7 @@ class RestMerchantEndpoints(Middleware):
"redirectUrl": redirect_url, "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) return InvoiceSubmission.parse(data)
@@ -65,7 +50,7 @@ class RestMerchantEndpoints(Middleware):
) -> List[InvoiceSubmission]: ) -> List[InvoiceSubmission]:
body = {"id": id, "start": start, "end": end, "limit": limit} 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] return [InvoiceSubmission.parse(sub_data) for sub_data in data]
@@ -96,7 +81,7 @@ class RestMerchantEndpoints(Middleware):
"orderId": order_id, "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) return InvoicePage.parse(data)
@@ -105,7 +90,7 @@ class RestMerchantEndpoints(Middleware):
) -> List[InvoiceStats]: ) -> List[InvoiceStats]:
return [ return [
InvoiceStats(**sub_data) InvoiceStats(**sub_data)
for sub_data in self._post( for sub_data in self._m.post(
"auth/r/ext/pay/invoice/stats/count", "auth/r/ext/pay/invoice/stats/count",
body={"status": status, "format": format}, body={"status": status, "format": format},
) )
@@ -116,7 +101,7 @@ class RestMerchantEndpoints(Middleware):
) -> List[InvoiceStats]: ) -> List[InvoiceStats]:
return [ return [
InvoiceStats(**sub_data) InvoiceStats(**sub_data)
for sub_data in self._post( for sub_data in self._m.post(
"auth/r/ext/pay/invoice/stats/earning", "auth/r/ext/pay/invoice/stats/earning",
body={"currency": currency, "format": format}, body={"currency": currency, "format": format},
) )
@@ -137,26 +122,26 @@ class RestMerchantEndpoints(Middleware):
"ledgerId": ledger_id, "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) return InvoiceSubmission.parse(data)
def expire_invoice(self, id: str) -> InvoiceSubmission: def expire_invoice(self, id: str) -> InvoiceSubmission:
body = {"id": id} 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) return InvoiceSubmission.parse(data)
def get_currency_conversion_list(self) -> List[CurrencyConversion]: def get_currency_conversion_list(self) -> List[CurrencyConversion]:
return [ return [
CurrencyConversion(**sub_data) 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: def add_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
return bool( return bool(
self._post( self._m.post(
"auth/w/ext/pay/settings/convert/create", "auth/w/ext/pay/settings/convert/create",
body={"baseCcy": base_ccy, "convertCcy": convert_ccy}, 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: def remove_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
return bool( return bool(
self._post( self._m.post(
"auth/w/ext/pay/settings/convert/remove", "auth/w/ext/pay/settings/convert/remove",
body={"baseCcy": base_ccy, "convertCcy": convert_ccy}, body={"baseCcy": base_ccy, "convertCcy": convert_ccy},
) )
@@ -172,16 +157,16 @@ class RestMerchantEndpoints(Middleware):
def set_merchant_settings(self, key: str, val: Any) -> bool: def set_merchant_settings(self, key: str, val: Any) -> bool:
return 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: 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( def list_merchant_settings(
self, keys: Optional[List[str]] = None self, keys: Optional[List[str]] = None
) -> Dict[str, Any]: ) -> 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( def get_deposits(
self, self,
@@ -193,7 +178,7 @@ class RestMerchantEndpoints(Middleware):
) -> List[MerchantDeposit]: ) -> List[MerchantDeposit]:
body = {"from": start, "to": to, "ccy": ccy, "unlinked": unlinked} 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] return [MerchantDeposit(**sub_data) for sub_data in data]
@@ -202,6 +187,6 @@ class RestMerchantEndpoints(Middleware):
) -> List[MerchantUnlinkedDeposit]: ) -> List[MerchantUnlinkedDeposit]:
body = {"ccy": ccy, "start": start, "end": end} 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] return [MerchantUnlinkedDeposit(**sub_data) for sub_data in data]

View File

@@ -1,7 +1,8 @@
from decimal import Decimal from decimal import Decimal
from typing import Any, Dict, List, Literal, Optional, Union, cast from typing import Any, Dict, List, Literal, Optional, Union, cast
from ...types import ( from bfxapi.rest._interface import Interface
from bfxapi.types import (
Candle, Candle,
DerivativesStatus, DerivativesStatus,
FundingCurrencyBook, FundingCurrencyBook,
@@ -25,20 +26,19 @@ from ...types import (
TradingPairTrade, TradingPairTrade,
serializers, serializers,
) )
from ..middleware import Middleware
class RestPublicEndpoints(Middleware): class RestPublicEndpoints(Interface):
def conf(self, config: str) -> Any: 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: 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( def get_tickers(
self, symbols: List[str] self, symbols: List[str]
) -> Dict[str, Union[TradingPairTicker, FundingCurrencyTicker]]: ) -> Dict[str, Union[TradingPairTicker, FundingCurrencyTicker]]:
data = self._get("tickers", params={"symbols": ",".join(symbols)}) data = self._m.get("tickers", params={"symbols": ",".join(symbols)})
parsers = { parsers = {
"t": serializers.TradingPairTicker.parse, "t": serializers.TradingPairTicker.parse,
@@ -83,10 +83,10 @@ class RestPublicEndpoints(Middleware):
return cast(Dict[str, FundingCurrencyTicker], data) return cast(Dict[str, FundingCurrencyTicker], data)
def get_t_ticker(self, symbol: str) -> TradingPairTicker: 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: 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( def get_tickers_history(
self, self,
@@ -98,7 +98,7 @@ class RestPublicEndpoints(Middleware):
) -> List[TickersHistory]: ) -> List[TickersHistory]:
return [ return [
serializers.TickersHistory.parse(*sub_data) serializers.TickersHistory.parse(*sub_data)
for sub_data in self._get( for sub_data in self._m.get(
"tickers/hist", "tickers/hist",
params={ params={
"symbols": ",".join(symbols), "symbols": ",".join(symbols),
@@ -119,7 +119,7 @@ class RestPublicEndpoints(Middleware):
sort: Optional[int] = None, sort: Optional[int] = None,
) -> List[TradingPairTrade]: ) -> List[TradingPairTrade]:
params = {"limit": limit, "start": start, "end": end, "sort": sort} 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] return [serializers.TradingPairTrade.parse(*sub_data) for sub_data in data]
def get_f_trades( def get_f_trades(
@@ -132,7 +132,7 @@ class RestPublicEndpoints(Middleware):
sort: Optional[int] = None, sort: Optional[int] = None,
) -> List[FundingCurrencyTrade]: ) -> List[FundingCurrencyTrade]:
params = {"limit": limit, "start": start, "end": end, "sort": sort} 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] return [serializers.FundingCurrencyTrade.parse(*sub_data) for sub_data in data]
def get_t_book( def get_t_book(
@@ -144,7 +144,7 @@ class RestPublicEndpoints(Middleware):
) -> List[TradingPairBook]: ) -> List[TradingPairBook]:
return [ return [
serializers.TradingPairBook.parse(*sub_data) 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( def get_f_book(
@@ -156,7 +156,7 @@ class RestPublicEndpoints(Middleware):
) -> List[FundingCurrencyBook]: ) -> List[FundingCurrencyBook]:
return [ return [
serializers.FundingCurrencyBook.parse(*sub_data) 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} f"book/{currency}/{precision}", params={"len": len}
) )
] ]
@@ -166,7 +166,7 @@ class RestPublicEndpoints(Middleware):
) -> List[TradingPairRawBook]: ) -> List[TradingPairRawBook]:
return [ return [
serializers.TradingPairRawBook.parse(*sub_data) 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( def get_f_raw_book(
@@ -174,7 +174,7 @@ class RestPublicEndpoints(Middleware):
) -> List[FundingCurrencyRawBook]: ) -> List[FundingCurrencyRawBook]:
return [ return [
serializers.FundingCurrencyRawBook.parse(*sub_data) 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( def get_stats_hist(
@@ -187,7 +187,7 @@ class RestPublicEndpoints(Middleware):
limit: Optional[int] = None, limit: Optional[int] = None,
) -> List[Statistic]: ) -> List[Statistic]:
params = {"sort": sort, "start": start, "end": end, "limit": limit} 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] return [serializers.Statistic.parse(*sub_data) for sub_data in data]
def get_stats_last( def get_stats_last(
@@ -200,7 +200,7 @@ class RestPublicEndpoints(Middleware):
limit: Optional[int] = None, limit: Optional[int] = None,
) -> Statistic: ) -> Statistic:
params = {"sort": sort, "start": start, "end": end, "limit": limit} 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) return serializers.Statistic.parse(*data)
def get_candles_hist( def get_candles_hist(
@@ -214,7 +214,7 @@ class RestPublicEndpoints(Middleware):
limit: Optional[int] = None, limit: Optional[int] = None,
) -> List[Candle]: ) -> List[Candle]:
params = {"sort": sort, "start": start, "end": end, "limit": limit} 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] return [serializers.Candle.parse(*sub_data) for sub_data in data]
def get_candles_last( def get_candles_last(
@@ -228,7 +228,7 @@ class RestPublicEndpoints(Middleware):
limit: Optional[int] = None, limit: Optional[int] = None,
) -> Candle: ) -> Candle:
params = {"sort": sort, "start": start, "end": end, "limit": limit} 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) return serializers.Candle.parse(*data)
def get_derivatives_status( def get_derivatives_status(
@@ -239,7 +239,7 @@ class RestPublicEndpoints(Middleware):
else: else:
params = {"keys": ",".join(keys)} params = {"keys": ",".join(keys)}
data = self._get("status/deriv", params=params) data = self._m.get("status/deriv", params=params)
return { return {
key: serializers.DerivativesStatus.parse(*sub_data) key: serializers.DerivativesStatus.parse(*sub_data)
@@ -257,7 +257,7 @@ class RestPublicEndpoints(Middleware):
limit: Optional[int] = None, limit: Optional[int] = None,
) -> List[DerivativesStatus]: ) -> List[DerivativesStatus]:
params = {"sort": sort, "start": start, "end": end, "limit": limit} 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] return [serializers.DerivativesStatus.parse(*sub_data) for sub_data in data]
def get_liquidations( def get_liquidations(
@@ -269,7 +269,7 @@ class RestPublicEndpoints(Middleware):
limit: Optional[int] = None, limit: Optional[int] = None,
) -> List[Liquidation]: ) -> List[Liquidation]:
params = {"sort": sort, "start": start, "end": end, "limit": limit} 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] return [serializers.Liquidation.parse(*sub_data[0]) for sub_data in data]
def get_seed_candles( def get_seed_candles(
@@ -283,7 +283,7 @@ class RestPublicEndpoints(Middleware):
limit: Optional[int] = None, limit: Optional[int] = None,
) -> List[Candle]: ) -> List[Candle]:
params = {"sort": sort, "start": start, "end": end, "limit": limit} 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] return [serializers.Candle.parse(*sub_data) for sub_data in data]
def get_leaderboards_hist( def get_leaderboards_hist(
@@ -296,7 +296,7 @@ class RestPublicEndpoints(Middleware):
limit: Optional[int] = None, limit: Optional[int] = None,
) -> List[Leaderboard]: ) -> List[Leaderboard]:
params = {"sort": sort, "start": start, "end": end, "limit": limit} 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] return [serializers.Leaderboard.parse(*sub_data) for sub_data in data]
def get_leaderboards_last( def get_leaderboards_last(
@@ -309,7 +309,7 @@ class RestPublicEndpoints(Middleware):
limit: Optional[int] = None, limit: Optional[int] = None,
) -> Leaderboard: ) -> Leaderboard:
params = {"sort": sort, "start": start, "end": end, "limit": limit} 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) return serializers.Leaderboard.parse(*data)
def get_funding_stats( def get_funding_stats(
@@ -321,18 +321,18 @@ class RestPublicEndpoints(Middleware):
limit: Optional[int] = None, limit: Optional[int] = None,
) -> List[FundingStatistic]: ) -> List[FundingStatistic]:
params = {"start": start, "end": end, "limit": limit} 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] return [serializers.FundingStatistic.parse(*sub_data) for sub_data in data]
def get_pulse_profile_details(self, nickname: str) -> PulseProfile: 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( def get_pulse_message_history(
self, *, end: Optional[str] = None, limit: Optional[int] = None self, *, end: Optional[str] = None, limit: Optional[int] = None
) -> List[PulseMessage]: ) -> List[PulseMessage]:
messages = [] 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] sub_data[18] = sub_data[18][0]
message = serializers.PulseMessage.parse(*sub_data) message = serializers.PulseMessage.parse(*sub_data)
messages.append(message) messages.append(message)
@@ -347,7 +347,7 @@ class RestPublicEndpoints(Middleware):
price_limit: Optional[Union[str, float, Decimal]] = None, price_limit: Optional[Union[str, float, Decimal]] = None,
) -> TradingMarketAveragePrice: ) -> TradingMarketAveragePrice:
return serializers.TradingMarketAveragePrice.parse( return serializers.TradingMarketAveragePrice.parse(
*self._post( *self._m.post(
"calc/trade/avg", "calc/trade/avg",
body={"symbol": symbol, "amount": amount, "price_limit": price_limit}, body={"symbol": symbol, "amount": amount, "price_limit": price_limit},
) )
@@ -362,7 +362,7 @@ class RestPublicEndpoints(Middleware):
rate_limit: Optional[Union[str, float, Decimal]] = None, rate_limit: Optional[Union[str, float, Decimal]] = None,
) -> FundingMarketAveragePrice: ) -> FundingMarketAveragePrice:
return serializers.FundingMarketAveragePrice.parse( return serializers.FundingMarketAveragePrice.parse(
*self._post( *self._m.post(
"calc/trade/avg", "calc/trade/avg",
body={ body={
"symbol": symbol, "symbol": symbol,
@@ -375,5 +375,5 @@ class RestPublicEndpoints(Middleware):
def get_fx_rate(self, ccy1: str, ccy2: str) -> FxRate: def get_fx_rate(self, ccy1: str, ccy2: str) -> FxRate:
return serializers.FxRate.parse( return serializers.FxRate.parse(
*self._post("calc/fx", body={"ccy1": ccy1, "ccy2": ccy2}) *self._m.post("calc/fx", body={"ccy1": ccy1, "ccy2": ccy2})
) )

View File

@@ -1,10 +1,6 @@
from bfxapi.exceptions import BfxBaseException from bfxapi.exceptions import BfxBaseException
class NotFoundError(BfxBaseException):
pass
class RequestParametersError(BfxBaseException): class RequestParametersError(BfxBaseException):
pass pass

View File

@@ -1 +0,0 @@
from .middleware import Middleware

View File

@@ -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

View File

@@ -24,8 +24,8 @@ def partial(cls):
if len(kwargs) != 0: if len(kwargs) != 0:
raise TypeError( raise TypeError(
f"{cls.__name__}.__init__() got an unexpected " f"{cls.__name__}.__init__() got an unexpected keyword argument "
"keyword argument '{list(kwargs.keys())[0]}'" f"'{list(kwargs.keys())[0]}'"
) )
cls.__init__ = __init__ cls.__init__ = __init__

View File

@@ -1,5 +1,5 @@
from decimal import Decimal 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]] _Handler = Callable[[str, Any], Awaitable[None]]
@@ -23,6 +23,7 @@ class BfxWebSocketInputs:
cid: Optional[int] = None, cid: Optional[int] = None,
flags: Optional[int] = None, flags: Optional[int] = None,
tif: Optional[str] = None, tif: Optional[str] = None,
meta: Optional[Dict[str, Any]] = None,
) -> None: ) -> None:
await self.__handle_websocket_input( await self.__handle_websocket_input(
"on", "on",
@@ -39,6 +40,7 @@ class BfxWebSocketInputs:
"cid": cid, "cid": cid,
"flags": flags, "flags": flags,
"tif": tif, "tif": tif,
"meta": meta,
}, },
) )

View File

@@ -39,8 +39,8 @@ setup(
"bfxapi.websocket._handlers", "bfxapi.websocket._handlers",
"bfxapi.websocket._event_emitter", "bfxapi.websocket._event_emitter",
"bfxapi.rest", "bfxapi.rest",
"bfxapi.rest.endpoints", "bfxapi.rest._interface",
"bfxapi.rest.middleware", "bfxapi.rest._interfaces",
], ],
install_requires=[ install_requires=[
"pyee~=9.0.4", "pyee~=9.0.4",