Skip to content

Commit e353993

Browse files
authored
Merge pull request #219 from Davi0kProgramsThings/feature/improvements
Merge branch `Davi0kProgramsThings:feature/improvements` into branch `bitfinexcom:v3-beta`.
2 parents 65eba2a + ad5f323 commit e353993

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+482
-1048
lines changed

bfxapi/client.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from typing import List, Optional
1+
from typing import List, Literal, Optional
22

33
from .rest import BfxRestInterface
4-
from .websocket import BfxWebsocketClient
4+
from .websocket import BfxWebSocketClient
55
from .urls import REST_HOST, WSS_HOST
66

77
class Client:
@@ -13,8 +13,9 @@ def __init__(
1313
*,
1414
rest_host: str = REST_HOST,
1515
wss_host: str = WSS_HOST,
16+
wss_timeout: Optional[float] = 60 * 15,
1617
log_filename: Optional[str] = None,
17-
log_level: str = "INFO"
18+
log_level: Literal["ERROR", "WARNING", "INFO", "DEBUG"] = "INFO"
1819
):
1920
credentials = None
2021

@@ -26,9 +27,10 @@ def __init__(
2627
credentials=credentials
2728
)
2829

29-
self.wss = BfxWebsocketClient(
30+
self.wss = BfxWebSocketClient(
3031
host=wss_host,
3132
credentials=credentials,
33+
wss_timeout=wss_timeout,
3234
log_filename=log_filename,
3335
log_level=log_level
3436
)

bfxapi/exceptions.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
__all__ = [
22
"BfxBaseException",
3-
4-
"LabelerSerializerException",
53
]
64

75
class BfxBaseException(Exception):
86
"""
97
Base class for every custom exception in bfxapi/rest/exceptions.py and bfxapi/websocket/exceptions.py.
108
"""
11-
12-
class LabelerSerializerException(BfxBaseException):
13-
"""
14-
This exception indicates an error thrown by the _Serializer class in bfxapi/labeler.py.
15-
"""
16-

bfxapi/rest/endpoints/rest_authenticated_endpoints.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
from decimal import Decimal
33
from datetime import datetime
44

5-
from .. types import Notification, \
5+
from ..middleware import Middleware
6+
7+
from ..enums import Sort, OrderType, FundingOfferType
8+
9+
from ...types import JSON, Notification, \
610
UserInfo, LoginHistory, BalanceAvailable, \
711
Order, Position, Trade, \
812
FundingTrade, OrderTrade, Ledger, \
@@ -14,13 +18,9 @@
1418
PositionIncrease, PositionHistory, PositionSnapshot, \
1519
PositionAudit, DerivativePositionCollateral, DerivativePositionCollateralLimits
1620

17-
from .. import serializers
18-
19-
from .. serializers import _Notification
20-
from .. enums import Sort, OrderType, FundingOfferType
21-
from .. middleware import Middleware
21+
from ...types import serializers
2222

23-
from ...utils.json_encoder import JSON
23+
from ...types.serializers import _Notification
2424

2525
class RestAuthenticatedEndpoints(Middleware):
2626
def get_user_info(self) -> UserInfo:

bfxapi/rest/endpoints/rest_merchant_endpoints.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,41 @@
1-
from typing import TypedDict, Dict, List, Union, Literal, Optional, Any
1+
import re
2+
3+
from typing import Callable, TypeVar, cast, \
4+
TypedDict, Dict, List, Union, Literal, Optional, Any
25

36
from decimal import Decimal
47

5-
from .. types import \
8+
from ..middleware import Middleware
9+
10+
from ..enums import MerchantSettingsKey
11+
12+
from ...types import \
613
InvoiceSubmission, InvoicePage, InvoiceStats, \
714
CurrencyConversion, MerchantDeposit, MerchantUnlinkedDeposit
815

9-
from .. enums import MerchantSettingsKey
16+
#region Defining methods to convert dictionary keys to snake_case and camelCase.
17+
18+
T = TypeVar("T")
19+
20+
_to_snake_case: Callable[[str], str] = lambda string: re.sub(r"(?<!^)(?=[A-Z])", "_", string).lower()
21+
22+
_to_camel_case: Callable[[str], str] = lambda string: \
23+
(components := string.split("_"))[0] + str().join(c.title() for c in components[1:])
24+
25+
def _scheme(data: T, adapter: Callable[[str], str]) -> T:
26+
if isinstance(data, list):
27+
return cast(T, [ _scheme(sub_data, adapter) for sub_data in data ])
28+
if isinstance(data, dict):
29+
return cast(T, { adapter(key): _scheme(value, adapter) for key, value in data.items() })
30+
return data
31+
32+
def _to_snake_case_keys(dictionary: T) -> T:
33+
return _scheme(dictionary, _to_snake_case)
1034

11-
from .. middleware import Middleware
35+
def _to_camel_case_keys(dictionary: T) -> T:
36+
return _scheme(dictionary, _to_camel_case)
1237

13-
from ...utils.camel_and_snake_case_helpers import to_snake_case_keys, to_camel_case_keys
38+
#endregion
1439

1540
_CustomerInfo = TypedDict("_CustomerInfo", {
1641
"nationality": str, "resid_country": str, "resid_city": str,
@@ -30,13 +55,13 @@ def submit_invoice(self,
3055
duration: Optional[int] = None,
3156
webhook: Optional[str] = None,
3257
redirect_url: Optional[str] = None) -> InvoiceSubmission:
33-
body = to_camel_case_keys({
58+
body = _to_camel_case_keys({
3459
"amount": amount, "currency": currency, "order_id": order_id,
3560
"customer_info": customer_info, "pay_currencies": pay_currencies, "duration": duration,
3661
"webhook": webhook, "redirect_url": redirect_url
3762
})
3863

39-
data = to_snake_case_keys(self._post("auth/w/ext/pay/invoice/create", body=body))
64+
data = _to_snake_case_keys(self._post("auth/w/ext/pay/invoice/create", body=body))
4065

4166
return InvoiceSubmission.parse(data)
4267

@@ -53,7 +78,7 @@ def get_invoices(self,
5378

5479
response = self._post("auth/r/ext/pay/invoices", body=body)
5580

56-
return [ InvoiceSubmission.parse(sub_data) for sub_data in to_snake_case_keys(response) ]
81+
return [ InvoiceSubmission.parse(sub_data) for sub_data in _to_snake_case_keys(response) ]
5782

5883
def get_invoices_paginated(self,
5984
page: int = 1,
@@ -66,13 +91,13 @@ def get_invoices_paginated(self,
6691
crypto: Optional[List[str]] = None,
6792
id: Optional[str] = None,
6893
order_id: Optional[str] = None) -> InvoicePage:
69-
body = to_camel_case_keys({
94+
body = _to_camel_case_keys({
7095
"page": page, "page_size": page_size, "sort": sort,
7196
"sort_field": sort_field, "status": status, "fiat": fiat,
7297
"crypto": crypto, "id": id, "order_id": order_id
7398
})
7499

75-
data = to_snake_case_keys(self._post("auth/r/ext/pay/invoices/paginated", body=body))
100+
data = _to_snake_case_keys(self._post("auth/r/ext/pay/invoices/paginated", body=body))
76101

77102
return InvoicePage.parse(data)
78103

@@ -94,15 +119,15 @@ def complete_invoice(self,
94119
*,
95120
deposit_id: Optional[int] = None,
96121
ledger_id: Optional[int] = None) -> InvoiceSubmission:
97-
return InvoiceSubmission.parse(to_snake_case_keys(self._post("auth/w/ext/pay/invoice/complete", body={
122+
return InvoiceSubmission.parse(_to_snake_case_keys(self._post("auth/w/ext/pay/invoice/complete", body={
98123
"id": id, "payCcy": pay_currency, "depositId": deposit_id,
99124
"ledgerId": ledger_id
100125
})))
101126

102127
def expire_invoice(self, id: str) -> InvoiceSubmission:
103128
body = { "id": id }
104129
response = self._post("auth/w/ext/pay/invoice/expire", body=body)
105-
return InvoiceSubmission.parse(to_snake_case_keys(response))
130+
return InvoiceSubmission.parse(_to_snake_case_keys(response))
106131

107132
def get_currency_conversion_list(self) -> List[CurrencyConversion]:
108133
return [
@@ -148,7 +173,7 @@ def get_deposits(self,
148173
unlinked: Optional[bool] = None) -> List[MerchantDeposit]:
149174
body = { "from": start, "to": end, "ccy": ccy, "unlinked": unlinked }
150175
response = self._post("auth/r/ext/pay/deposits", body=body)
151-
return [ MerchantDeposit(**sub_data) for sub_data in to_snake_case_keys(response) ]
176+
return [ MerchantDeposit(**sub_data) for sub_data in _to_snake_case_keys(response) ]
152177

153178
def get_unlinked_deposits(self,
154179
ccy: str,
@@ -157,4 +182,4 @@ def get_unlinked_deposits(self,
157182
end: Optional[int] = None) -> List[MerchantUnlinkedDeposit]:
158183
body = { "ccy": ccy, "start": start, "end": end }
159184
response = self._post("/auth/r/ext/pay/deposits/unlinked", body=body)
160-
return [ MerchantUnlinkedDeposit(**sub_data) for sub_data in to_snake_case_keys(response) ]
185+
return [ MerchantUnlinkedDeposit(**sub_data) for sub_data in _to_snake_case_keys(response) ]

bfxapi/rest/endpoints/rest_public_endpoints.py

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
from typing import List, Union, Literal, Optional, Any, cast
1+
from typing import List, Dict, Union, Literal, Optional, Any, cast
22

33
from decimal import Decimal
44

5-
from .. types import \
5+
from ..middleware import Middleware
6+
7+
from ..enums import Config, Sort
8+
9+
from ...types import \
610
PlatformStatus, TradingPairTicker, FundingCurrencyTicker, \
711
TickersHistory, TradingPairTrade, FundingCurrencyTrade, \
812
TradingPairBook, FundingCurrencyBook, TradingPairRawBook, \
@@ -11,9 +15,7 @@
1115
FundingStatistic, PulseProfile, PulseMessage, \
1216
TradingMarketAveragePrice, FundingMarketAveragePrice, FxRate
1317

14-
from .. import serializers
15-
from .. enums import Config, Sort
16-
from .. middleware import Middleware
18+
from ...types import serializers
1719

1820
class RestPublicEndpoints(Middleware):
1921
def conf(self, config: Config) -> Any:
@@ -22,37 +24,46 @@ def conf(self, config: Config) -> Any:
2224
def get_platform_status(self) -> PlatformStatus:
2325
return serializers.PlatformStatus.parse(*self._get("platform/status"))
2426

25-
def get_tickers(self, symbols: List[str]) -> List[Union[TradingPairTicker, FundingCurrencyTicker]]:
27+
def get_tickers(self, symbols: List[str]) -> Dict[str, Union[TradingPairTicker, FundingCurrencyTicker]]:
2628
data = self._get("tickers", params={ "symbols": ",".join(symbols) })
2729

2830
parsers = { "t": serializers.TradingPairTicker.parse, "f": serializers.FundingCurrencyTicker.parse }
2931

30-
return [ cast(Union[TradingPairTicker, FundingCurrencyTicker], \
31-
parsers[sub_data[0][0]](*sub_data)) for sub_data in data ]
32+
return {
33+
symbol: cast(Union[TradingPairTicker, FundingCurrencyTicker],
34+
parsers[symbol[0]](*sub_data)) for sub_data in data
35+
if (symbol := sub_data.pop(0))
36+
}
3237

33-
def get_t_tickers(self, pairs: Union[List[str], Literal["ALL"]]) -> List[TradingPairTicker]:
34-
if isinstance(pairs, str) and pairs == "ALL":
35-
return [ cast(TradingPairTicker, sub_data) for sub_data in self.get_tickers([ "ALL" ]) \
36-
if cast(str, sub_data.symbol).startswith("t") ]
38+
def get_t_tickers(self, symbols: Union[List[str], Literal["ALL"]]) -> Dict[str, TradingPairTicker]:
39+
if isinstance(symbols, str) and symbols == "ALL":
40+
return {
41+
symbol: cast(TradingPairTicker, sub_data)
42+
for symbol, sub_data in self.get_tickers([ "ALL" ]).items()
43+
if symbol.startswith("t")
44+
}
3745

38-
data = self.get_tickers(list(pairs))
46+
data = self.get_tickers(list(symbols))
3947

40-
return cast(List[TradingPairTicker], data)
48+
return cast(Dict[str, TradingPairTicker], data)
4149

42-
def get_f_tickers(self, currencies: Union[List[str], Literal["ALL"]]) -> List[FundingCurrencyTicker]:
43-
if isinstance(currencies, str) and currencies == "ALL":
44-
return [ cast(FundingCurrencyTicker, sub_data) for sub_data in self.get_tickers([ "ALL" ]) \
45-
if cast(str, sub_data.symbol).startswith("f") ]
50+
def get_f_tickers(self, symbols: Union[List[str], Literal["ALL"]]) -> Dict[str, FundingCurrencyTicker]:
51+
if isinstance(symbols, str) and symbols == "ALL":
52+
return {
53+
symbol: cast(FundingCurrencyTicker, sub_data)
54+
for symbol, sub_data in self.get_tickers([ "ALL" ]).items()
55+
if symbol.startswith("f")
56+
}
4657

47-
data = self.get_tickers(list(currencies))
58+
data = self.get_tickers(list(symbols))
4859

49-
return cast(List[FundingCurrencyTicker], data)
60+
return cast(Dict[str, FundingCurrencyTicker], data)
5061

51-
def get_t_ticker(self, pair: str) -> TradingPairTicker:
52-
return serializers.TradingPairTicker.parse(*([pair] + self._get(f"ticker/{pair}")))
62+
def get_t_ticker(self, symbol: str) -> TradingPairTicker:
63+
return serializers.TradingPairTicker.parse(*self._get(f"ticker/{symbol}"))
5364

54-
def get_f_ticker(self, currency: str) -> FundingCurrencyTicker:
55-
return serializers.FundingCurrencyTicker.parse(*([currency] + self._get(f"ticker/{currency}")))
65+
def get_f_ticker(self, symbol: str) -> FundingCurrencyTicker:
66+
return serializers.FundingCurrencyTicker.parse(*self._get(f"ticker/{symbol}"))
5667

5768
def get_tickers_history(self,
5869
symbols: List[str],
@@ -164,26 +175,29 @@ def get_candles_last(self,
164175
data = self._get(f"candles/trade:{tf}:{symbol}/last", params=params)
165176
return serializers.Candle.parse(*data)
166177

167-
def get_derivatives_status(self, keys: Union[List[str], Literal["ALL"]]) -> List[DerivativesStatus]:
178+
def get_derivatives_status(self, keys: Union[List[str], Literal["ALL"]]) -> Dict[str, DerivativesStatus]:
168179
if keys == "ALL":
169180
params = { "keys": "ALL" }
170181
else: params = { "keys": ",".join(keys) }
171182

172183
data = self._get("status/deriv", params=params)
173184

174-
return [ serializers.DerivativesStatus.parse(*sub_data) for sub_data in data ]
185+
return {
186+
key: serializers.DerivativesStatus.parse(*sub_data)
187+
for sub_data in data
188+
if (key := sub_data.pop(0))
189+
}
175190

176191
def get_derivatives_status_history(self,
177-
type: str,
178-
symbol: str,
192+
key: str,
179193
*,
180194
sort: Optional[Sort] = None,
181195
start: Optional[str] = None,
182196
end: Optional[str] = None,
183197
limit: Optional[int] = None) -> List[DerivativesStatus]:
184198
params = { "sort": sort, "start": start, "end": end, "limit": limit }
185-
data = self._get(f"status/{type}/{symbol}/hist", params=params)
186-
return [ serializers.DerivativesStatus.parse(*([symbol] + sub_data)) for sub_data in data ]
199+
data = self._get(f"status/deriv/{key}/hist", params=params)
200+
return [ serializers.DerivativesStatus.parse(*sub_data) for sub_data in data ]
187201

188202
def get_liquidations(self,
189203
*,

bfxapi/rest/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .. exceptions import BfxBaseException
1+
from ..exceptions import BfxBaseException
22

33
__all__ = [
44
"BfxRestException",

bfxapi/tests/__init__.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import unittest
22

3-
from .test_rest_serializers import TestRestSerializers
4-
from .test_websocket_serializers import TestWebsocketSerializers
5-
from .test_labeler import TestLabeler
6-
from .test_notification import TestNotification
3+
from .test_types_labeler import TestTypesLabeler
4+
from .test_types_notification import TestTypesNotification
5+
from .test_types_serializers import TestTypesSerializers
76

87
def suite():
98
return unittest.TestSuite([
10-
unittest.makeSuite(TestRestSerializers),
11-
unittest.makeSuite(TestWebsocketSerializers),
12-
unittest.makeSuite(TestLabeler),
13-
unittest.makeSuite(TestNotification),
9+
unittest.makeSuite(TestTypesLabeler),
10+
unittest.makeSuite(TestTypesNotification),
11+
unittest.makeSuite(TestTypesSerializers),
1412
])
1513

1614
if __name__ == "__main__":
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
from typing import Optional
44

55
from dataclasses import dataclass
6-
from ..exceptions import LabelerSerializerException
7-
from ..labeler import _Type, generate_labeler_serializer, generate_recursive_serializer
86

9-
class TestLabeler(unittest.TestCase):
7+
from ..types.labeler import _Type, generate_labeler_serializer, generate_recursive_serializer
8+
9+
class TestTypesLabeler(unittest.TestCase):
1010
def test_generate_labeler_serializer(self):
1111
@dataclass
1212
class Test(_Type):
@@ -24,8 +24,8 @@ class Test(_Type):
2424
self.assertListEqual(serializer.get_labels(), [ "A", "B", "C" ],
2525
msg="_Serializer::get_labels() should return the right list of labels.")
2626

27-
with self.assertRaises(LabelerSerializerException,
28-
msg="_Serializer should raise LabelerSerializerException if given " \
27+
with self.assertRaises(AssertionError,
28+
msg="_Serializer should raise an AssertionError if given " \
2929
"fewer arguments than the serializer labels."):
3030
serializer.parse(5, 65.0, "X")
3131

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import unittest
22

33
from dataclasses import dataclass
4-
from ..labeler import generate_labeler_serializer
5-
from ..notification import _Type, _Notification, Notification
4+
from ..types.labeler import generate_labeler_serializer
5+
from ..types.notification import _Type, _Notification, Notification
66

7-
class TestNotification(unittest.TestCase):
8-
def test_notification(self):
7+
class TestTypesNotification(unittest.TestCase):
8+
def test_types_notification(self):
99
@dataclass
1010
class Test(_Type):
1111
A: int

0 commit comments

Comments
 (0)