From ed12bf473f4cad24a77ec58949e99c48dc9dedad Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Wed, 25 Jan 2023 18:18:15 +0100 Subject: [PATCH] Rewrite and extend custom JSONEncoder in bfxapi/utils/encoder.py to automatically convert floats to strs. Change every Union[Decimal, str] type to Union[Decimal, float, str]. Fix type hinting bug in labeler.py. --- bfxapi/labeler.py | 4 ++-- bfxapi/rest/BfxRestInterface.py | 26 ++++++++++++------------- bfxapi/rest/types.py | 4 +--- bfxapi/utils/encoder.py | 26 +++++++++++++++++++++++-- bfxapi/websocket/_BfxWebsocketInputs.py | 16 +++++++-------- bfxapi/websocket/types.py | 4 +--- 6 files changed, 49 insertions(+), 31 deletions(-) diff --git a/bfxapi/labeler.py b/bfxapi/labeler.py index 52b0e2b..6201b82 100644 --- a/bfxapi/labeler.py +++ b/bfxapi/labeler.py @@ -29,7 +29,7 @@ class _Serializer(Generic[T]): return cast(T, self.klass(**dict(self._serialize(*values, skip=skip)))) class _RecursiveSerializer(_Serializer, Generic[T]): - def __init__(self, name: str, klass: Type[_Type], labels: List[str], serializers: Dict[str, Type[_Serializer]], IGNORE: List[str] = ["_PLACEHOLDER"]): + def __init__(self, name: str, klass: Type[_Type], labels: List[str], serializers: Dict[str, _Serializer[Any]], IGNORE: List[str] = ["_PLACEHOLDER"]): super().__init__(name, klass, labels, IGNORE) self.serializers = serializers @@ -46,5 +46,5 @@ class _RecursiveSerializer(_Serializer, Generic[T]): def generate_labeler_serializer(name: str, klass: Type[T], labels: List[str], IGNORE: List[str] = [ "_PLACEHOLDER" ]) -> _Serializer[T]: return _Serializer[T](name, klass, labels, IGNORE) -def generate_recursive_serializer(name: str, klass: Type[T], labels: List[str], serializers: Dict[str, Type[_Serializer]], IGNORE: List[str] = [ "_PLACEHOLDER" ]) -> _RecursiveSerializer[T]: +def generate_recursive_serializer(name: str, klass: Type[T], labels: List[str], serializers: Dict[str, _Serializer[Any]], IGNORE: List[str] = [ "_PLACEHOLDER" ]) -> _RecursiveSerializer[T]: return _RecursiveSerializer[T](name, klass, labels, serializers, IGNORE) \ No newline at end of file diff --git a/bfxapi/rest/BfxRestInterface.py b/bfxapi/rest/BfxRestInterface.py index 347833b..88a08f5 100644 --- a/bfxapi/rest/BfxRestInterface.py +++ b/bfxapi/rest/BfxRestInterface.py @@ -268,14 +268,14 @@ class _RestPublicEndpoints(_Requests): return messages - def get_trading_market_average_price(self, symbol: str, amount: Union[Decimal, str], price_limit: Optional[Union[Decimal, str]] = None) -> TradingMarketAveragePrice: + def get_trading_market_average_price(self, symbol: str, amount: Union[Decimal, float, str], price_limit: Optional[Union[Decimal, float, str]] = None) -> TradingMarketAveragePrice: data = { "symbol": symbol, "amount": amount, "price_limit": price_limit } return serializers.TradingMarketAveragePrice.parse(*self._POST("calc/trade/avg", data=data)) - def get_funding_market_average_price(self, symbol: str, amount: Union[Decimal, str], period: int, rate_limit: Optional[Union[Decimal, str]] = None) -> FundingMarketAveragePrice: + def get_funding_market_average_price(self, symbol: str, amount: Union[Decimal, float, str], period: int, rate_limit: Optional[Union[Decimal, float, str]] = None) -> FundingMarketAveragePrice: data = { "symbol": symbol, "amount": amount, "period": period, "rate_limit": rate_limit @@ -301,9 +301,9 @@ class _RestAuthenticatedEndpoints(_Requests): def get_positions(self) -> List[Position]: return [ serializers.Position.parse(*sub_data) for sub_data in self._POST("auth/r/positions") ] - def submit_order(self, type: OrderType, symbol: str, amount: Union[Decimal, str], - price: Optional[Union[Decimal, str]] = None, lev: Optional[int] = None, - price_trailing: Optional[Union[Decimal, str]] = None, price_aux_limit: Optional[Union[Decimal, str]] = None, price_oco_stop: Optional[Union[Decimal, str]] = None, + def submit_order(self, type: OrderType, symbol: str, amount: Union[Decimal, float, str], + price: Optional[Union[Decimal, float, str]] = None, lev: Optional[int] = None, + price_trailing: Optional[Union[Decimal, float, str]] = None, price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_oco_stop: Optional[Union[Decimal, float, str]] = None, gid: Optional[int] = None, cid: Optional[int] = None, flags: Optional[int] = 0, tif: Optional[Union[datetime, str]] = None, meta: Optional[JSON] = None) -> Notification[Order]: data = { @@ -316,10 +316,10 @@ class _RestAuthenticatedEndpoints(_Requests): return serializers._Notification[Order](serializer=serializers.Order).parse(*self._POST("auth/w/order/submit", data=data)) - def update_order(self, id: int, amount: Optional[Union[Decimal, str]] = None, price: Optional[Union[Decimal, str]] = None, + def update_order(self, id: int, amount: Optional[Union[Decimal, float, str]] = None, price: Optional[Union[Decimal, float, str]] = None, cid: Optional[int] = None, cid_date: Optional[str] = None, gid: Optional[int] = None, - flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, str]] = None, - price_aux_limit: Optional[Union[Decimal, str]] = None, price_trailing: Optional[Union[Decimal, str]] = None, tif: Optional[Union[datetime, str]] = None) -> Notification[Order]: + flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, float, str]] = None, + price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_trailing: Optional[Union[Decimal, float, str]] = None, tif: Optional[Union[datetime, str]] = None) -> Notification[Order]: data = { "id": id, "amount": amount, "price": price, "cid": cid, "cid_date": cid_date, "gid": gid, @@ -395,8 +395,8 @@ class _RestAuthenticatedEndpoints(_Requests): return [ serializers.FundingOffer.parse(*sub_data) for sub_data in self._POST(endpoint) ] - def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, str], - rate: Union[Decimal, str], period: int, + def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, float, str], + rate: Union[Decimal, float, str], period: int, flags: Optional[int] = 0) -> Notification[FundingOffer]: data = { "type": type, "symbol": symbol, "amount": amount, @@ -440,7 +440,7 @@ class _RestAuthenticatedEndpoints(_Requests): return [ serializers.FundingCredit.parse(*sub_data) for sub_data in self._POST(endpoint, data=data) ] - def submit_wallet_transfer(self, from_wallet: str, to_wallet: str, currency: str, currency_to: str, amount: Union[Decimal, str]) -> Notification[Transfer]: + def submit_wallet_transfer(self, from_wallet: str, to_wallet: str, currency: str, currency_to: str, amount: Union[Decimal, float, str]) -> Notification[Transfer]: data = { "from": from_wallet, "to": to_wallet, "currency": currency, "currency_to": currency_to, @@ -449,7 +449,7 @@ class _RestAuthenticatedEndpoints(_Requests): return serializers._Notification[Transfer](serializer=serializers.Transfer).parse(*self._POST("auth/w/transfer", data=data)) - def submit_wallet_withdraw(self, wallet: str, method: str, address: str, amount: Union[Decimal, str]) -> Notification[Withdrawal]: + def submit_wallet_withdraw(self, wallet: str, method: str, address: str, amount: Union[Decimal, float, str]) -> Notification[Withdrawal]: data = { "wallet": wallet, "method": method, "address": address, "amount": amount, @@ -466,7 +466,7 @@ class _RestAuthenticatedEndpoints(_Requests): return serializers._Notification[DepositAddress](serializer=serializers.DepositAddress).parse(*self._POST("auth/w/deposit/address", data=data)) - def get_deposit_invoice(self, wallet: str, currency: str, amount: Union[Decimal, str]) -> Invoice: + def get_deposit_invoice(self, wallet: str, currency: str, amount: Union[Decimal, float, str]) -> Invoice: data = { "wallet": wallet, "currency": currency, "amount": amount diff --git a/bfxapi/rest/types.py b/bfxapi/rest/types.py index 2387bea..f9dc96d 100644 --- a/bfxapi/rest/types.py +++ b/bfxapi/rest/types.py @@ -3,10 +3,8 @@ from typing import Type, Tuple, List, Dict, TypedDict, Union, Optional, Any from dataclasses import dataclass from .. labeler import _Type - from .. notification import Notification - -JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]] +from .. utils.encoder import JSON #region Type hinting for Rest Public Endpoints diff --git a/bfxapi/utils/encoder.py b/bfxapi/utils/encoder.py index 3649823..885ab91 100644 --- a/bfxapi/utils/encoder.py +++ b/bfxapi/utils/encoder.py @@ -2,8 +2,30 @@ import json from decimal import Decimal from datetime import datetime +from typing import Type, List, Dict, Union, Any + +JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]] + class JSONEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, Decimal) or isinstance(obj, datetime): + def encode(self, obj: JSON) -> str: + def _convert_float_to_str(data: JSON) -> JSON: + if isinstance(data, float): + return format(Decimal(repr(data)), "f") + elif isinstance(data, list): + return [ _convert_float_to_str(sub_data) for sub_data in data ] + elif isinstance(data, dict): + return { key: _convert_float_to_str(value) for key, value in data.items() } + else: return data + + data = _convert_float_to_str(obj) + + return json.JSONEncoder.encode(self, data) + + def default(self, obj: Any) -> Any: + if isinstance(obj, Decimal): + return format(obj, "f") + + if isinstance(obj, datetime): return str(obj) + return json.JSONEncoder.default(self, obj) \ No newline at end of file diff --git a/bfxapi/websocket/_BfxWebsocketInputs.py b/bfxapi/websocket/_BfxWebsocketInputs.py index 85e29b0..041405f 100644 --- a/bfxapi/websocket/_BfxWebsocketInputs.py +++ b/bfxapi/websocket/_BfxWebsocketInputs.py @@ -12,9 +12,9 @@ class _BfxWebsocketInputs(object): def __init__(self, __handle_websocket_input): self.__handle_websocket_input = __handle_websocket_input - async def submit_order(self, type: OrderType, symbol: str, amount: Union[Decimal, str], - price: Optional[Union[Decimal, str]] = None, lev: Optional[int] = None, - price_trailing: Optional[Union[Decimal, str]] = None, price_aux_limit: Optional[Union[Decimal, str]] = None, price_oco_stop: Optional[Union[Decimal, str]] = None, + async def submit_order(self, type: OrderType, symbol: str, amount: Union[Decimal, float, str], + price: Optional[Union[Decimal, float, str]] = None, lev: Optional[int] = None, + price_trailing: Optional[Union[Decimal, float, str]] = None, price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_oco_stop: Optional[Union[Decimal, float, str]] = None, gid: Optional[int] = None, cid: Optional[int] = None, flags: Optional[int] = 0, tif: Optional[Union[datetime, str]] = None, meta: Optional[JSON] = None): data = _strip({ @@ -27,10 +27,10 @@ class _BfxWebsocketInputs(object): await self.__handle_websocket_input("on", data) - async def update_order(self, id: int, amount: Optional[Union[Decimal, str]] = None, price: Optional[Union[Decimal, str]] = None, + async def update_order(self, id: int, amount: Optional[Union[Decimal, float, str]] = None, price: Optional[Union[Decimal, float, str]] = None, cid: Optional[int] = None, cid_date: Optional[str] = None, gid: Optional[int] = None, - flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, str]] = None, - price_aux_limit: Optional[Union[Decimal, str]] = None, price_trailing: Optional[Union[Decimal, str]] = None, tif: Optional[Union[datetime, str]] = None): + flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, float, str]] = None, + price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_trailing: Optional[Union[Decimal, float, str]] = None, tif: Optional[Union[datetime, str]] = None): data = _strip({ "id": id, "amount": amount, "price": price, "cid": cid, "cid_date": cid_date, "gid": gid, @@ -60,8 +60,8 @@ class _BfxWebsocketInputs(object): await self.__handle_websocket_input("oc_multi", data) - async def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, str], - rate: Union[Decimal, str], period: int, + async def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, float, str], + rate: Union[Decimal, float, str], period: int, flags: Optional[int] = 0): data = { "type": type, "symbol": symbol, "amount": amount, diff --git a/bfxapi/websocket/types.py b/bfxapi/websocket/types.py index 97ef37b..19ed3ef 100644 --- a/bfxapi/websocket/types.py +++ b/bfxapi/websocket/types.py @@ -3,10 +3,8 @@ from typing import Type, Tuple, List, Dict, TypedDict, Union, Optional, Any from dataclasses import dataclass from ..labeler import _Type - from ..notification import Notification - -JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]] +from .. utils.encoder import JSON #region Type hinting for Websocket Public Channels