From d3f402fbc70863df79b6c478a8d1881bba8c4e3d Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Mon, 26 Feb 2024 19:26:33 +0100 Subject: [PATCH 01/14] Add isort, black and flake8 to dev-requirements.txt (+ remove support to pylint). --- .pylintrc | 25 ------------------------- dev-requirements.txt | Bin 216 -> 274 bytes 2 files changed, 25 deletions(-) delete mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index c616eb1..0000000 --- a/.pylintrc +++ /dev/null @@ -1,25 +0,0 @@ -[MAIN] -py-version=3.8.0 - -[MESSAGES CONTROL] -disable= - missing-docstring, - multiple-imports, - too-few-public-methods, - too-many-instance-attributes - -[VARIABLES] -allowed-redefined-builtins=all,dir,format,id,len,type - -[FORMAT] -max-line-length=120 -expected-line-ending-format=LF - -[BASIC] -good-names=f,t,id,ip,on,pl,tf,to,A,B,C,D,E,F - -[TYPECHECK] -generated-members=websockets - -[STRING] -check-quote-consistency=yes diff --git a/dev-requirements.txt b/dev-requirements.txt index 0cd0c125ab44449379d02fdf2c09ca6c6aa0f460..53d49eb09199b12077385d0c1b882d6b1c75a88b 100644 GIT binary patch delta 97 zcmcb?IEiV(j;KtAVupN%B8CzMTOc%L&|@%UFb0xF47?0n3`q<*42cZM4B22=BL)+& bj2?plST+qTo6V5QU;&ml2g(|NF-R`}T)+-$ delta 39 rcmbQlbc1oi4ut}SN`@SUOolv$5(Zl!G-A+WFk~ Date: Mon, 26 Feb 2024 19:36:10 +0100 Subject: [PATCH 02/14] Apply isort to all python files (bfxapi/**/*.py). --- bfxapi/__init__.py | 7 +- bfxapi/_client.py | 9 +- bfxapi/_utils/json_decoder.py | 5 +- bfxapi/_utils/json_encoder.py | 6 +- bfxapi/_utils/logging.py | 9 +- bfxapi/rest/__init__.py | 8 +- bfxapi/rest/endpoints/__init__.py | 3 +- bfxapi/rest/endpoints/bfx_rest_interface.py | 3 +- bfxapi/rest/endpoints/rest_auth_endpoints.py | 55 ++++++++---- .../rest/endpoints/rest_merchant_endpoints.py | 22 ++--- .../rest/endpoints/rest_public_endpoints.py | 39 ++++++--- bfxapi/rest/exceptions.py | 1 + bfxapi/rest/middleware/middleware.py | 17 ++-- bfxapi/types/__init__.py | 84 +++++++++++++------ bfxapi/types/dataclasses.py | 6 +- bfxapi/types/labeler.py | 3 +- bfxapi/types/notification.py | 5 +- bfxapi/types/serializers.py | 9 +- .../websocket/_client/bfx_websocket_bucket.py | 12 ++- .../websocket/_client/bfx_websocket_client.py | 46 ++++------ .../websocket/_client/bfx_websocket_inputs.py | 6 +- bfxapi/websocket/_connection.py | 27 ++---- .../_event_emitter/bfx_event_emitter.py | 9 +- bfxapi/websocket/_handlers/__init__.py | 3 +- .../_handlers/auth_events_handler.py | 7 +- .../_handlers/public_channels_handler.py | 15 ++-- bfxapi/websocket/exceptions.py | 1 + bfxapi/websocket/subscriptions.py | 3 +- pyproject.toml | 2 + 29 files changed, 225 insertions(+), 197 deletions(-) create mode 100644 pyproject.toml diff --git a/bfxapi/__init__.py b/bfxapi/__init__.py index 9138036..950842e 100644 --- a/bfxapi/__init__.py +++ b/bfxapi/__init__.py @@ -1,6 +1 @@ -from ._client import \ - Client, \ - REST_HOST, \ - WSS_HOST, \ - PUB_REST_HOST, \ - PUB_WSS_HOST +from ._client import PUB_REST_HOST, PUB_WSS_HOST, REST_HOST, WSS_HOST, Client diff --git a/bfxapi/_client.py b/bfxapi/_client.py index 2a7d8f0..66a4032 100644 --- a/bfxapi/_client.py +++ b/bfxapi/_client.py @@ -1,15 +1,12 @@ -from typing import \ - TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, List, Optional from bfxapi._utils.logging import ColorLogger - +from bfxapi.exceptions import IncompleteCredentialError from bfxapi.rest import BfxRestInterface from bfxapi.websocket import BfxWebSocketClient -from bfxapi.exceptions import IncompleteCredentialError if TYPE_CHECKING: - from bfxapi.websocket._client.bfx_websocket_client import \ - _Credentials + from bfxapi.websocket._client.bfx_websocket_client import _Credentials REST_HOST = "https://api.bitfinex.com/v2" WSS_HOST = "wss://api.bitfinex.com/ws/2" diff --git a/bfxapi/_utils/json_decoder.py b/bfxapi/_utils/json_decoder.py index 968258d..7c361bf 100644 --- a/bfxapi/_utils/json_decoder.py +++ b/bfxapi/_utils/json_decoder.py @@ -1,6 +1,7 @@ -from typing import Dict, Any +import json +import re +from typing import Any, Dict -import re, json def _to_snake_case(string: str) -> str: return re.sub(r"(? Date: Mon, 26 Feb 2024 19:43:14 +0100 Subject: [PATCH 03/14] Apply black to all python files (bfxapi/**/*.py). --- bfxapi/_client.py | 41 +- bfxapi/_utils/json_decoder.py | 4 +- bfxapi/_utils/json_encoder.py | 18 +- bfxapi/_utils/logging.py | 32 +- bfxapi/exceptions.py | 2 + bfxapi/rest/endpoints/bfx_rest_interface.py | 6 +- bfxapi/rest/endpoints/rest_auth_endpoints.py | 896 ++++++++++-------- .../rest/endpoints/rest_merchant_endpoints.py | 268 +++--- .../rest/endpoints/rest_public_endpoints.py | 472 +++++---- bfxapi/rest/exceptions.py | 2 + bfxapi/rest/middleware/middleware.py | 83 +- bfxapi/types/dataclasses.py | 84 +- bfxapi/types/labeler.py | 67 +- bfxapi/types/notification.py | 28 +- bfxapi/types/serializers.py | 396 ++++---- .../websocket/_client/bfx_websocket_bucket.py | 79 +- .../websocket/_client/bfx_websocket_client.py | 241 ++--- .../websocket/_client/bfx_websocket_inputs.py | 186 ++-- bfxapi/websocket/_connection.py | 30 +- .../_event_emitter/bfx_event_emitter.py | 101 +- .../_handlers/auth_events_handler.py | 46 +- .../_handlers/public_channels_handler.py | 191 ++-- bfxapi/websocket/exceptions.py | 7 + bfxapi/websocket/subscriptions.py | 5 + examples/rest/auth/claim_position.py | 5 +- examples/rest/auth/get_wallets.py | 5 +- .../set_derivative_position_collateral.py | 5 +- examples/rest/auth/submit_funding_offer.py | 5 +- examples/rest/auth/submit_order.py | 5 +- examples/rest/auth/toggle_keep_funding.py | 5 +- examples/rest/merchant/settings.py | 5 +- examples/rest/merchant/submit_invoice.py | 5 +- pyproject.toml | 5 + 33 files changed, 1919 insertions(+), 1411 deletions(-) diff --git a/bfxapi/_client.py b/bfxapi/_client.py index 66a4032..21f2f86 100644 --- a/bfxapi/_client.py +++ b/bfxapi/_client.py @@ -14,29 +14,35 @@ PUB_REST_HOST = "https://api-pub.bitfinex.com/v2" PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2" + class Client: def __init__( - self, - api_key: Optional[str] = None, - api_secret: Optional[str] = None, - *, - rest_host: str = REST_HOST, - wss_host: str = WSS_HOST, - filters: Optional[List[str]] = None, - timeout: Optional[int] = 60 * 15, - log_filename: Optional[str] = None + self, + api_key: Optional[str] = None, + api_secret: Optional[str] = None, + *, + rest_host: str = REST_HOST, + wss_host: str = WSS_HOST, + filters: Optional[List[str]] = None, + timeout: Optional[int] = 60 * 15, + log_filename: Optional[str] = None, ) -> None: credentials: Optional["_Credentials"] = None if api_key and api_secret: - credentials = \ - { "api_key": api_key, "api_secret": api_secret, "filters": filters } + credentials = { + "api_key": api_key, + "api_secret": api_secret, + "filters": filters, + } elif api_key: - raise IncompleteCredentialError( \ - "You must provide both an API-KEY and an API-SECRET (missing API-KEY).") + raise IncompleteCredentialError( + "You must provide both an API-KEY and an API-SECRET (missing API-KEY)." + ) elif api_secret: - raise IncompleteCredentialError( \ - "You must provide both an API-KEY and an API-SECRET (missing API-SECRET).") + raise IncompleteCredentialError( + "You must provide both an API-KEY and an API-SECRET (missing API-SECRET)." + ) self.rest = BfxRestInterface(rest_host, api_key, api_secret) @@ -45,5 +51,6 @@ def __init__( if log_filename: logger.register(filename=log_filename) - self.wss = BfxWebSocketClient(wss_host, \ - credentials=credentials, timeout=timeout, logger=logger) + self.wss = BfxWebSocketClient( + wss_host, credentials=credentials, timeout=timeout, logger=logger + ) diff --git a/bfxapi/_utils/json_decoder.py b/bfxapi/_utils/json_decoder.py index 7c361bf..21164a9 100644 --- a/bfxapi/_utils/json_decoder.py +++ b/bfxapi/_utils/json_decoder.py @@ -6,8 +6,10 @@ def _to_snake_case(string: str) -> str: return re.sub(r"(? Any: - return { _to_snake_case(key): value for key, value in data.items() } + return {_to_snake_case(key): value for key, value in data.items()} + class JSONDecoder(json.JSONDecoder): def __init__(self, *args: Any, **kwargs: Any) -> None: diff --git a/bfxapi/_utils/json_encoder.py b/bfxapi/_utils/json_encoder.py index a4f1498..0d0d9e3 100644 --- a/bfxapi/_utils/json_encoder.py +++ b/bfxapi/_utils/json_encoder.py @@ -2,15 +2,16 @@ from decimal import Decimal from typing import Any, Dict, List, Union -_ExtJSON = Union[Dict[str, "_ExtJSON"], List["_ExtJSON"], \ - bool, int, float, str, Decimal, None] +_ExtJSON = Union[ + Dict[str, "_ExtJSON"], List["_ExtJSON"], bool, int, float, str, Decimal, None +] + +_StrictJSON = Union[Dict[str, "_StrictJSON"], List["_StrictJSON"], int, str, None] -_StrictJSON = Union[Dict[str, "_StrictJSON"], List["_StrictJSON"], \ - int, str, None] def _clear(dictionary: Dict[str, Any]) -> Dict[str, Any]: - return { key: value for key, value in dictionary.items() \ - if value is not None } + return {key: value for key, value in dictionary.items() if value is not None} + def _adapter(data: _ExtJSON) -> _StrictJSON: if isinstance(data, bool): @@ -21,12 +22,13 @@ def _adapter(data: _ExtJSON) -> _StrictJSON: return format(data, "f") if isinstance(data, list): - return [ _adapter(sub_data) for sub_data in data ] + return [_adapter(sub_data) for sub_data in data] if isinstance(data, dict): - return _clear({ key: _adapter(value) for key, value in data.items() }) + return _clear({key: _adapter(value) for key, value in data.items()}) return data + class JSONEncoder(json.JSONEncoder): def encode(self, o: _ExtJSON) -> str: return super().encode(_adapter(o)) diff --git a/bfxapi/_utils/logging.py b/bfxapi/_utils/logging.py index 31832bf..593b99c 100644 --- a/bfxapi/_utils/logging.py +++ b/bfxapi/_utils/logging.py @@ -1,30 +1,38 @@ import sys from copy import copy -#pylint: disable-next=wildcard-import,unused-wildcard-import +# pylint: disable-next=wildcard-import,unused-wildcard-import from logging import * from typing import TYPE_CHECKING, Literal, Optional if TYPE_CHECKING: _Level = Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] -_BLACK, _RED, _GREEN, _YELLOW, \ -_BLUE, _MAGENTA, _CYAN, _WHITE = \ - [ f"\033[0;{90 + i}m" for i in range(8) ] - -_BOLD_BLACK, _BOLD_RED, _BOLD_GREEN, _BOLD_YELLOW, \ -_BOLD_BLUE, _BOLD_MAGENTA, _BOLD_CYAN, _BOLD_WHITE = \ - [ f"\033[1;{90 + i}m" for i in range(8) ] +_BLACK, _RED, _GREEN, _YELLOW, _BLUE, _MAGENTA, _CYAN, _WHITE = [ + f"\033[0;{90 + i}m" for i in range(8) +] + +( + _BOLD_BLACK, + _BOLD_RED, + _BOLD_GREEN, + _BOLD_YELLOW, + _BOLD_BLUE, + _BOLD_MAGENTA, + _BOLD_CYAN, + _BOLD_WHITE, +) = [f"\033[1;{90 + i}m" for i in range(8)] _NC = "\033[0m" + class _ColorFormatter(Formatter): __LEVELS = { "INFO": _BLUE, "WARNING": _YELLOW, "ERROR": _RED, "CRITICAL": _BOLD_RED, - "DEBUG": _BOLD_WHITE + "DEBUG": _BOLD_WHITE, } def format(self, record: LogRecord) -> str: @@ -34,7 +42,7 @@ def format(self, record: LogRecord) -> str: return super().format(_record) - #pylint: disable-next=invalid-name + # pylint: disable-next=invalid-name def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str: return _GREEN + super().formatTime(record, datefmt) + _NC @@ -42,12 +50,14 @@ def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str: def __format_level(level: str) -> str: return _ColorFormatter.__LEVELS[level] + level + _NC + _FORMAT = "%(asctime)s %(name)s %(levelname)s %(message)s" _DATE_FORMAT = "%d-%m-%Y %H:%M:%S" + class ColorLogger(Logger): - __FORMATTER = Formatter(_FORMAT,_DATE_FORMAT) + __FORMATTER = Formatter(_FORMAT, _DATE_FORMAT) def __init__(self, name: str, level: "_Level" = "NOTSET") -> None: super().__init__(name, level) diff --git a/bfxapi/exceptions.py b/bfxapi/exceptions.py index 663752a..80d56d5 100644 --- a/bfxapi/exceptions.py +++ b/bfxapi/exceptions.py @@ -3,8 +3,10 @@ class BfxBaseException(Exception): Base class for every custom exception thrown by bitfinex-api-py. """ + class IncompleteCredentialError(BfxBaseException): pass + class InvalidCredentialError(BfxBaseException): pass diff --git a/bfxapi/rest/endpoints/bfx_rest_interface.py b/bfxapi/rest/endpoints/bfx_rest_interface.py index e5bd018..dff3a43 100644 --- a/bfxapi/rest/endpoints/bfx_rest_interface.py +++ b/bfxapi/rest/endpoints/bfx_rest_interface.py @@ -6,7 +6,9 @@ class BfxRestInterface: VERSION = 2 - def __init__(self, host, api_key = None, api_secret = None): + 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) + self.merchant = RestMerchantEndpoints( + host=host, api_key=api_key, api_secret=api_secret + ) diff --git a/bfxapi/rest/endpoints/rest_auth_endpoints.py b/bfxapi/rest/endpoints/rest_auth_endpoints.py index db41263..8afd13f 100644 --- a/bfxapi/rest/endpoints/rest_auth_endpoints.py +++ b/bfxapi/rest/endpoints/rest_auth_endpoints.py @@ -39,452 +39,590 @@ from ..middleware import Middleware -#pylint: disable-next=too-many-public-methods +# pylint: disable-next=too-many-public-methods class RestAuthEndpoints(Middleware): def get_user_info(self) -> UserInfo: - return serializers.UserInfo \ - .parse(*self._post("auth/r/info/user")) + return serializers.UserInfo.parse(*self._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") ] - - def get_balance_available_for_orders_or_offers(self, - symbol: str, - type: str, - *, - dir: Optional[int] = None, - rate: Optional[str] = None, - lev: Optional[str] = None) -> BalanceAvailable: - body = { - "symbol": symbol, "type": type, "dir": dir, - "rate": rate, "lev": lev - } - - return serializers.BalanceAvailable \ - .parse(*self._post("auth/calc/order/avail", body=body)) + return [ + serializers.LoginHistory.parse(*sub_data) + for sub_data in self._post("auth/r/logins/hist") + ] + + def get_balance_available_for_orders_or_offers( + self, + symbol: str, + type: str, + *, + dir: Optional[int] = None, + rate: Optional[str] = None, + lev: Optional[str] = None, + ) -> BalanceAvailable: + body = {"symbol": symbol, "type": type, "dir": dir, "rate": rate, "lev": lev} + + return serializers.BalanceAvailable.parse( + *self._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") ] - - def get_orders(self, - *, - symbol: Optional[str] = None, - ids: Optional[List[str]] = None) -> List[Order]: + return [ + serializers.Wallet.parse(*sub_data) + for sub_data in self._post("auth/r/wallets") + ] + + def get_orders( + self, *, symbol: Optional[str] = None, ids: Optional[List[str]] = None + ) -> List[Order]: if symbol is None: endpoint = "auth/r/orders" - else: endpoint = f"auth/r/orders/{symbol}" - - return [ serializers.Order.parse(*sub_data) \ - for sub_data in self._post(endpoint, body={ "id": ids }) ] - - def submit_order(self, - type: str, - symbol: str, - amount: Union[str, float, Decimal], - price: Union[str, float, Decimal], - *, - lev: Optional[int] = None, - price_trailing: Optional[Union[str, float, Decimal]] = None, - price_aux_limit: Optional[Union[str, float, Decimal]] = None, - price_oco_stop: Optional[Union[str, float, Decimal]] = None, - gid: Optional[int] = None, - cid: Optional[int] = None, - flags: Optional[int] = None, - tif: Optional[str] = None) -> Notification[Order]: + else: + endpoint = f"auth/r/orders/{symbol}" + + return [ + serializers.Order.parse(*sub_data) + for sub_data in self._post(endpoint, body={"id": ids}) + ] + + def submit_order( + self, + type: str, + symbol: str, + amount: Union[str, float, Decimal], + price: Union[str, float, Decimal], + *, + lev: Optional[int] = None, + price_trailing: Optional[Union[str, float, Decimal]] = None, + price_aux_limit: Optional[Union[str, float, Decimal]] = None, + price_oco_stop: Optional[Union[str, float, Decimal]] = None, + gid: Optional[int] = None, + cid: Optional[int] = None, + flags: Optional[int] = None, + tif: Optional[str] = None, + ) -> Notification[Order]: body = { - "type": type, "symbol": symbol, "amount": amount, - "price": price, "lev": lev, "price_trailing": price_trailing, - "price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop, "gid": gid, - "cid": cid, "flags": flags, "tif": tif + "type": type, + "symbol": symbol, + "amount": amount, + "price": price, + "lev": lev, + "price_trailing": price_trailing, + "price_aux_limit": price_aux_limit, + "price_oco_stop": price_oco_stop, + "gid": gid, + "cid": cid, + "flags": flags, + "tif": tif, } - return _Notification[Order](serializers.Order) \ - .parse(*self._post("auth/w/order/submit", body=body)) - - def update_order(self, - id: int, - *, - amount: Optional[Union[str, float, Decimal]] = None, - price: Optional[Union[str, float, Decimal]] = None, - cid: Optional[int] = None, - cid_date: Optional[str] = None, - gid: Optional[int] = None, - flags: Optional[int] = None, - lev: Optional[int] = None, - delta: Optional[Union[str, float, Decimal]] = None, - price_aux_limit: Optional[Union[str, float, Decimal]] = None, - price_trailing: Optional[Union[str, float, Decimal]] = None, - tif: Optional[str] = None) -> Notification[Order]: + return _Notification[Order](serializers.Order).parse( + *self._post("auth/w/order/submit", body=body) + ) + + def update_order( + self, + id: int, + *, + amount: Optional[Union[str, float, Decimal]] = None, + price: Optional[Union[str, float, Decimal]] = None, + cid: Optional[int] = None, + cid_date: Optional[str] = None, + gid: Optional[int] = None, + flags: Optional[int] = None, + lev: Optional[int] = None, + delta: Optional[Union[str, float, Decimal]] = None, + price_aux_limit: Optional[Union[str, float, Decimal]] = None, + price_trailing: Optional[Union[str, float, Decimal]] = None, + tif: Optional[str] = None, + ) -> Notification[Order]: body = { - "id": id, "amount": amount, "price": price, - "cid": cid, "cid_date": cid_date, "gid": gid, - "flags": flags, "lev": lev, "delta": delta, - "price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif + "id": id, + "amount": amount, + "price": price, + "cid": cid, + "cid_date": cid_date, + "gid": gid, + "flags": flags, + "lev": lev, + "delta": delta, + "price_aux_limit": price_aux_limit, + "price_trailing": price_trailing, + "tif": tif, } - return _Notification[Order](serializers.Order) \ - .parse(*self._post("auth/w/order/update", body=body)) - - def cancel_order(self, - *, - id: Optional[int] = None, - cid: Optional[int] = None, - cid_date: Optional[str] = None) -> Notification[Order]: - return _Notification[Order](serializers.Order) \ - .parse(*self._post("auth/w/order/cancel", \ - body={ "id": id, "cid": cid, "cid_date": cid_date })) - - def cancel_order_multi(self, - *, - id: Optional[List[int]] = None, - cid: Optional[List[Tuple[int, str]]] = None, - gid: Optional[List[int]] = None, - all: Optional[bool] = None) -> Notification[List[Order]]: - 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)) - - def get_orders_history(self, - *, - symbol: Optional[str] = None, - ids: Optional[List[int]] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[Order]: + return _Notification[Order](serializers.Order).parse( + *self._post("auth/w/order/update", body=body) + ) + + def cancel_order( + self, + *, + id: Optional[int] = None, + cid: Optional[int] = None, + cid_date: Optional[str] = None, + ) -> Notification[Order]: + return _Notification[Order](serializers.Order).parse( + *self._post( + "auth/w/order/cancel", body={"id": id, "cid": cid, "cid_date": cid_date} + ) + ) + + def cancel_order_multi( + self, + *, + id: Optional[List[int]] = None, + cid: Optional[List[Tuple[int, str]]] = None, + gid: Optional[List[int]] = None, + all: Optional[bool] = None, + ) -> Notification[List[Order]]: + 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) + ) + + def get_orders_history( + self, + *, + symbol: Optional[str] = None, + ids: Optional[List[int]] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[Order]: if symbol is None: endpoint = "auth/r/orders/hist" - else: endpoint = f"auth/r/orders/{symbol}/hist" - - body = { - "id": ids, "start": start, "end": end, - "limit": limit - } - - return [ serializers.Order.parse(*sub_data) \ - for sub_data in self._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") ] - - def get_trades_history(self, - *, - symbol: Optional[str] = None, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[Trade]: + else: + endpoint = f"auth/r/orders/{symbol}/hist" + + body = {"id": ids, "start": start, "end": end, "limit": limit} + + return [ + serializers.Order.parse(*sub_data) + for sub_data in self._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") + ] + + def get_trades_history( + self, + *, + symbol: Optional[str] = None, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[Trade]: if symbol is None: endpoint = "auth/r/trades/hist" - else: endpoint = f"auth/r/trades/{symbol}/hist" - - body = { - "sort": sort, "start": start, "end": end, - "limit": limit - } - - return [ serializers.Trade.parse(*sub_data) \ - for sub_data in self._post(endpoint, body=body) ] - - def get_ledgers(self, - currency: str, - *, - category: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[Ledger]: - body = { - "category": category, "start": start, "end": end, - "limit": limit - } - - return [ serializers.Ledger.parse(*sub_data) \ - for sub_data in self._post(f"auth/r/ledgers/{currency}/hist", body=body) ] + else: + endpoint = f"auth/r/trades/{symbol}/hist" + + body = {"sort": sort, "start": start, "end": end, "limit": limit} + + return [ + serializers.Trade.parse(*sub_data) + for sub_data in self._post(endpoint, body=body) + ] + + def get_ledgers( + self, + currency: str, + *, + category: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[Ledger]: + body = {"category": category, "start": start, "end": end, "limit": limit} + + return [ + serializers.Ledger.parse(*sub_data) + for sub_data in self._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])) + return serializers.BaseMarginInfo.parse( + *(self._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}")) + return serializers.SymbolMarginInfo.parse( + *self._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") ] + return [ + serializers.SymbolMarginInfo.parse(*sub_data) + for sub_data in self._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") ] - - 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 })) - - def increase_position(self, - symbol: str, - amount: Union[str, float, Decimal]) -> Notification[PositionIncrease]: - return _Notification[PositionIncrease](serializers.PositionIncrease) \ - .parse(*self._post("auth/w/position/increase", \ - body={ "symbol": symbol, "amount": amount })) - - def get_increase_position_info(self, - symbol: str, - amount: Union[str, float, Decimal]) -> PositionIncreaseInfo: - return serializers.PositionIncreaseInfo \ - .parse(*self._post("auth/r/position/increase/info", \ - body={ "symbol": symbol, "amount": amount })) - - def get_positions_history(self, - *, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[PositionHistory]: - return [ serializers.PositionHistory.parse(*sub_data) \ - for sub_data in self._post("auth/r/positions/hist", \ - body={ "start": start, "end": end, "limit": limit }) ] - - def get_positions_snapshot(self, - *, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[PositionSnapshot]: - return [ serializers.PositionSnapshot.parse(*sub_data) \ - for sub_data in self._post("auth/r/positions/snap", \ - body={ "start": start, "end": end, "limit": limit }) ] - - def get_positions_audit(self, - *, - ids: Optional[List[int]] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[PositionAudit]: - body = { - "ids": ids, "start": start, "end": end, - "limit": limit - } - - return [ serializers.PositionAudit.parse(*sub_data) \ - for sub_data in self._post("auth/r/positions/audit", body=body) ] - - def set_derivative_position_collateral(self, - symbol: str, - collateral: Union[str, float, Decimal]) -> DerivativePositionCollateral: - return serializers.DerivativePositionCollateral \ - .parse(*(self._post("auth/w/deriv/collateral/set", \ - body={ "symbol": symbol, "collateral": collateral })[0])) - - def get_derivative_position_collateral_limits(self, symbol: str) -> DerivativePositionCollateralLimits: - return serializers.DerivativePositionCollateralLimits \ - .parse(*self._post("auth/calc/deriv/collateral/limit", body={ "symbol": symbol })) + return [ + serializers.Position.parse(*sub_data) + for sub_data in self._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}) + ) + + def increase_position( + self, symbol: str, amount: Union[str, float, Decimal] + ) -> Notification[PositionIncrease]: + return _Notification[PositionIncrease](serializers.PositionIncrease).parse( + *self._post( + "auth/w/position/increase", body={"symbol": symbol, "amount": amount} + ) + ) + + def get_increase_position_info( + self, symbol: str, amount: Union[str, float, Decimal] + ) -> PositionIncreaseInfo: + return serializers.PositionIncreaseInfo.parse( + *self._post( + "auth/r/position/increase/info", + body={"symbol": symbol, "amount": amount}, + ) + ) + + def get_positions_history( + self, + *, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[PositionHistory]: + return [ + serializers.PositionHistory.parse(*sub_data) + for sub_data in self._post( + "auth/r/positions/hist", + body={"start": start, "end": end, "limit": limit}, + ) + ] + + def get_positions_snapshot( + self, + *, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[PositionSnapshot]: + return [ + serializers.PositionSnapshot.parse(*sub_data) + for sub_data in self._post( + "auth/r/positions/snap", + body={"start": start, "end": end, "limit": limit}, + ) + ] + + def get_positions_audit( + self, + *, + ids: Optional[List[int]] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[PositionAudit]: + body = {"ids": ids, "start": start, "end": end, "limit": limit} + + return [ + serializers.PositionAudit.parse(*sub_data) + for sub_data in self._post("auth/r/positions/audit", body=body) + ] + + def set_derivative_position_collateral( + self, symbol: str, collateral: Union[str, float, Decimal] + ) -> DerivativePositionCollateral: + return serializers.DerivativePositionCollateral.parse( + *( + self._post( + "auth/w/deriv/collateral/set", + body={"symbol": symbol, "collateral": collateral}, + )[0] + ) + ) + + def get_derivative_position_collateral_limits( + self, symbol: str + ) -> DerivativePositionCollateralLimits: + return serializers.DerivativePositionCollateralLimits.parse( + *self._post("auth/calc/deriv/collateral/limit", body={"symbol": symbol}) + ) def get_funding_offers(self, *, symbol: Optional[str] = None) -> List[FundingOffer]: if symbol is None: endpoint = "auth/r/funding/offers" - else: endpoint = f"auth/r/funding/offers/{symbol}" - - return [ serializers.FundingOffer.parse(*sub_data) \ - for sub_data in self._post(endpoint) ] - - #pylint: disable-next=too-many-arguments - def submit_funding_offer(self, - type: str, - symbol: str, - amount: Union[str, float, Decimal], - rate: Union[str, float, Decimal], - period: int, - *, - flags: Optional[int] = None) -> Notification[FundingOffer]: + else: + endpoint = f"auth/r/funding/offers/{symbol}" + + return [ + serializers.FundingOffer.parse(*sub_data) + for sub_data in self._post(endpoint) + ] + + # pylint: disable-next=too-many-arguments + def submit_funding_offer( + self, + type: str, + symbol: str, + amount: Union[str, float, Decimal], + rate: Union[str, float, Decimal], + period: int, + *, + flags: Optional[int] = None, + ) -> Notification[FundingOffer]: body = { - "type": type, "symbol": symbol, "amount": amount, - "rate": rate, "period": period, "flags": flags + "type": type, + "symbol": symbol, + "amount": amount, + "rate": rate, + "period": period, + "flags": flags, } - return _Notification[FundingOffer](serializers.FundingOffer) \ - .parse(*self._post("auth/w/funding/offer/submit", body=body)) + return _Notification[FundingOffer](serializers.FundingOffer).parse( + *self._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 })) + return _Notification[FundingOffer](serializers.FundingOffer).parse( + *self._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 })) + return _Notification[Literal[None]](None).parse( + *self._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 })) - - def toggle_auto_renew(self, - status: bool, - currency: str, - *, - amount: Optional[str] = None, - rate: Optional[int] = None, - period: Optional[int] = None) -> Notification[FundingAutoRenew]: + return _Notification[Literal[None]](None).parse( + *self._post("auth/w/funding/close", body={"id": id}) + ) + + def toggle_auto_renew( + self, + status: bool, + currency: str, + *, + amount: Optional[str] = None, + rate: Optional[int] = None, + period: Optional[int] = None, + ) -> Notification[FundingAutoRenew]: body = { - "status": status, "currency": currency, "amount": amount, - "rate": rate, "period": period + "status": status, + "currency": currency, + "amount": amount, + "rate": rate, + "period": period, } - return _Notification[FundingAutoRenew](serializers.FundingAutoRenew) \ - .parse(*self._post("auth/w/funding/auto", body=body)) - - def toggle_keep_funding(self, - type: Literal["credit", "loan"], - *, - ids: Optional[List[int]] = None, - changes: Optional[Dict[int, Literal[1, 2]]] = None) -> Notification[Literal[None]]: - return _Notification[Literal[None]](None) \ - .parse(*self._post("auth/w/funding/keep", \ - body={ "type": type, "id": ids, "changes": changes })) - - def get_funding_offers_history(self, - *, - symbol: Optional[str] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[FundingOffer]: + return _Notification[FundingAutoRenew](serializers.FundingAutoRenew).parse( + *self._post("auth/w/funding/auto", body=body) + ) + + def toggle_keep_funding( + self, + type: Literal["credit", "loan"], + *, + ids: Optional[List[int]] = None, + changes: Optional[Dict[int, Literal[1, 2]]] = None, + ) -> Notification[Literal[None]]: + return _Notification[Literal[None]](None).parse( + *self._post( + "auth/w/funding/keep", + body={"type": type, "id": ids, "changes": changes}, + ) + ) + + def get_funding_offers_history( + self, + *, + symbol: Optional[str] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[FundingOffer]: if symbol is None: endpoint = "auth/r/funding/offers/hist" - else: endpoint = f"auth/r/funding/offers/{symbol}/hist" + else: + endpoint = f"auth/r/funding/offers/{symbol}/hist" - return [ serializers.FundingOffer.parse(*sub_data) \ - for sub_data in self._post(endpoint, \ - body={ "start": start, "end": end, "limit": limit }) ] + return [ + serializers.FundingOffer.parse(*sub_data) + for sub_data in self._post( + endpoint, body={"start": start, "end": end, "limit": limit} + ) + ] def get_funding_loans(self, *, symbol: Optional[str] = None) -> List[FundingLoan]: if symbol is None: endpoint = "auth/r/funding/loans" - else: endpoint = f"auth/r/funding/loans/{symbol}" - - return [ serializers.FundingLoan.parse(*sub_data) \ - for sub_data in self._post(endpoint) ] - - def get_funding_loans_history(self, - *, - symbol: Optional[str] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[FundingLoan]: + else: + endpoint = f"auth/r/funding/loans/{symbol}" + + return [ + serializers.FundingLoan.parse(*sub_data) + for sub_data in self._post(endpoint) + ] + + def get_funding_loans_history( + self, + *, + symbol: Optional[str] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[FundingLoan]: if symbol is None: endpoint = "auth/r/funding/loans/hist" - else: endpoint = f"auth/r/funding/loans/{symbol}/hist" - - return [ serializers.FundingLoan.parse(*sub_data) \ - for sub_data in self._post(endpoint, \ - body={ "start": start, "end": end, "limit": limit }) ] - - def get_funding_credits(self, *, symbol: Optional[str] = None) -> List[FundingCredit]: + else: + endpoint = f"auth/r/funding/loans/{symbol}/hist" + + return [ + serializers.FundingLoan.parse(*sub_data) + for sub_data in self._post( + endpoint, body={"start": start, "end": end, "limit": limit} + ) + ] + + def get_funding_credits( + self, *, symbol: Optional[str] = None + ) -> List[FundingCredit]: if symbol is None: endpoint = "auth/r/funding/credits" - else: endpoint = f"auth/r/funding/credits/{symbol}" - - return [ serializers.FundingCredit.parse(*sub_data) \ - for sub_data in self._post(endpoint) ] - - def get_funding_credits_history(self, - *, - symbol: Optional[str] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[FundingCredit]: + else: + endpoint = f"auth/r/funding/credits/{symbol}" + + return [ + serializers.FundingCredit.parse(*sub_data) + for sub_data in self._post(endpoint) + ] + + def get_funding_credits_history( + self, + *, + symbol: Optional[str] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[FundingCredit]: if symbol is None: endpoint = "auth/r/funding/credits/hist" - else: endpoint = f"auth/r/funding/credits/{symbol}/hist" - - return [ serializers.FundingCredit.parse(*sub_data) \ - for sub_data in self._post(endpoint, \ - body={ "start": start, "end": end, "limit": limit }) ] - - def get_funding_trades_history(self, - *, - symbol: Optional[str] = None, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[FundingTrade]: + else: + endpoint = f"auth/r/funding/credits/{symbol}/hist" + + return [ + serializers.FundingCredit.parse(*sub_data) + for sub_data in self._post( + endpoint, body={"start": start, "end": end, "limit": limit} + ) + ] + + def get_funding_trades_history( + self, + *, + symbol: Optional[str] = None, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[FundingTrade]: if symbol is None: endpoint = "auth/r/funding/trades/hist" - else: endpoint = f"auth/r/funding/trades/{symbol}/hist" + else: + endpoint = f"auth/r/funding/trades/{symbol}/hist" - body = { - "sort": sort, "start": start, "end": end, - "limit": limit } + body = {"sort": sort, "start": start, "end": end, "limit": limit} - return [ serializers.FundingTrade.parse(*sub_data) \ - for sub_data in self._post(endpoint, body=body) ] + return [ + serializers.FundingTrade.parse(*sub_data) + for sub_data in self._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])) - - #pylint: disable-next=too-many-arguments - def transfer_between_wallets(self, - from_wallet: str, - to_wallet: str, - currency: str, - currency_to: str, - amount: Union[str, float, Decimal]) -> Notification[Transfer]: + return serializers.FundingInfo.parse( + *(self._post(f"auth/r/info/funding/{key}")[2]) + ) + + # pylint: disable-next=too-many-arguments + def transfer_between_wallets( + self, + from_wallet: str, + to_wallet: str, + currency: str, + currency_to: str, + amount: Union[str, float, Decimal], + ) -> Notification[Transfer]: body = { - "from": from_wallet, "to": to_wallet, "currency": currency, - "currency_to": currency_to, "amount": amount + "from": from_wallet, + "to": to_wallet, + "currency": currency, + "currency_to": currency_to, + "amount": amount, } - return _Notification[Transfer](serializers.Transfer) \ - .parse(*self._post("auth/w/transfer", body=body)) + return _Notification[Transfer](serializers.Transfer).parse( + *self._post("auth/w/transfer", body=body) + ) - def submit_wallet_withdrawal(self, - wallet: str, - method: str, - address: str, - amount: Union[str, float, Decimal]) -> Notification[Withdrawal]: + def submit_wallet_withdrawal( + self, wallet: str, method: str, address: str, amount: Union[str, float, Decimal] + ) -> Notification[Withdrawal]: body = { - "wallet": wallet, "method": method, "address": address, - "amount": amount + "wallet": wallet, + "method": method, + "address": address, + "amount": amount, } - return _Notification[Withdrawal](serializers.Withdrawal) \ - .parse(*self._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("auth/w/deposit/address", \ - body={ "wallet": wallet, "method": method, "op_renew": op_renew })) - - def generate_deposit_invoice(self, - wallet: str, - currency: str, - amount: Union[str, float, Decimal]) -> LightningNetworkInvoice: - return serializers.LightningNetworkInvoice \ - .parse(*self._post("auth/w/deposit/invoice", \ - body={ "wallet": wallet, "currency": currency, "amount": amount })) - - def get_movements(self, - *, - currency: Optional[str] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[Movement]: + return _Notification[Withdrawal](serializers.Withdrawal).parse( + *self._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( + "auth/w/deposit/address", + body={"wallet": wallet, "method": method, "op_renew": op_renew}, + ) + ) + + def generate_deposit_invoice( + self, wallet: str, currency: str, amount: Union[str, float, Decimal] + ) -> LightningNetworkInvoice: + return serializers.LightningNetworkInvoice.parse( + *self._post( + "auth/w/deposit/invoice", + body={"wallet": wallet, "currency": currency, "amount": amount}, + ) + ) + + def get_movements( + self, + *, + currency: Optional[str] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[Movement]: if currency is None: endpoint = "auth/r/movements/hist" - else: endpoint = f"auth/r/movements/{currency}/hist" - - return [ serializers.Movement.parse(*sub_data) \ - for sub_data in self._post(endpoint, \ - body={ "start": start, "end": end, "limit": limit }) ] + else: + endpoint = f"auth/r/movements/{currency}/hist" + + return [ + serializers.Movement.parse(*sub_data) + for sub_data in self._post( + endpoint, body={"start": start, "end": end, "limit": limit} + ) + ] diff --git a/bfxapi/rest/endpoints/rest_merchant_endpoints.py b/bfxapi/rest/endpoints/rest_merchant_endpoints.py index 5d60743..5752011 100644 --- a/bfxapi/rest/endpoints/rest_merchant_endpoints.py +++ b/bfxapi/rest/endpoints/rest_merchant_endpoints.py @@ -11,99 +11,131 @@ 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 -}) +_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): - #pylint: disable-next=too-many-arguments - def submit_invoice(self, - amount: Union[str, float, Decimal], - currency: str, - order_id: str, - customer_info: _CustomerInfo, - pay_currencies: List[str], - *, - duration: Optional[int] = None, - webhook: Optional[str] = None, - redirect_url: Optional[str] = None) -> InvoiceSubmission: + # pylint: disable-next=too-many-arguments + def submit_invoice( + self, + amount: Union[str, float, Decimal], + currency: str, + order_id: str, + customer_info: _CustomerInfo, + pay_currencies: List[str], + *, + duration: Optional[int] = None, + webhook: Optional[str] = None, + redirect_url: Optional[str] = None, + ) -> InvoiceSubmission: body = { - "amount": amount, "currency": currency, "orderId": order_id, - "customerInfo": customer_info, "payCurrencies": pay_currencies, "duration": duration, - "webhook": webhook, "redirectUrl": redirect_url + "amount": amount, + "currency": currency, + "orderId": order_id, + "customerInfo": customer_info, + "payCurrencies": pay_currencies, + "duration": duration, + "webhook": webhook, + "redirectUrl": redirect_url, } data = self._post("auth/w/ext/pay/invoice/create", body=body) return InvoiceSubmission.parse(data) - def get_invoices(self, - *, - id: Optional[str] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[InvoiceSubmission]: - body = { - "id": id, "start": start, "end": end, - "limit": limit - } + def get_invoices( + self, + *, + id: Optional[str] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[InvoiceSubmission]: + body = {"id": id, "start": start, "end": end, "limit": limit} data = self._post("auth/r/ext/pay/invoices", body=body) - return [ InvoiceSubmission.parse(sub_data) for sub_data in data ] - - def get_invoices_paginated(self, - page: int = 1, - page_size: int = 10, - sort: Literal["asc", "desc"] = "asc", - sort_field: Literal["t", "amount", "status"] = "t", - *, - status: Optional[List[Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"]]] = None, - fiat: Optional[List[str]] = None, - crypto: Optional[List[str]] = None, - id: Optional[str] = None, - order_id: Optional[str] = None) -> InvoicePage: + return [InvoiceSubmission.parse(sub_data) for sub_data in data] + + def get_invoices_paginated( + self, + page: int = 1, + page_size: int = 10, + sort: Literal["asc", "desc"] = "asc", + sort_field: Literal["t", "amount", "status"] = "t", + *, + status: Optional[ + List[Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"]] + ] = None, + fiat: Optional[List[str]] = None, + crypto: Optional[List[str]] = None, + id: Optional[str] = None, + order_id: Optional[str] = None, + ) -> InvoicePage: body = { - "page": page, "pageSize": page_size, "sort": sort, - "sortField": sort_field, "status": status, "fiat": fiat, - "crypto": crypto, "id": id, "orderId": order_id + "page": page, + "pageSize": page_size, + "sort": sort, + "sortField": sort_field, + "status": status, + "fiat": fiat, + "crypto": crypto, + "id": id, + "orderId": order_id, } data = self._post("auth/r/ext/pay/invoices/paginated", body=body) return InvoicePage.parse(data) - def get_invoice_count_stats(self, - status: Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"], - format: str) -> List[InvoiceStats]: - return [ InvoiceStats(**sub_data) for sub_data in \ - self._post("auth/r/ext/pay/invoice/stats/count", \ - body={ "status": status, "format": format }) ] - - def get_invoice_earning_stats(self, - currency: str, - format: str) -> List[InvoiceStats]: - return [ InvoiceStats(**sub_data) for sub_data in \ - self._post("auth/r/ext/pay/invoice/stats/earning", \ - body={ "currency": currency, "format": format }) ] - - def complete_invoice(self, - id: str, - pay_currency: str, - *, - deposit_id: Optional[int] = None, - ledger_id: Optional[int] = None) -> InvoiceSubmission: + def get_invoice_count_stats( + self, status: Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"], format: str + ) -> List[InvoiceStats]: + return [ + InvoiceStats(**sub_data) + for sub_data in self._post( + "auth/r/ext/pay/invoice/stats/count", + body={"status": status, "format": format}, + ) + ] + + def get_invoice_earning_stats( + self, currency: str, format: str + ) -> List[InvoiceStats]: + return [ + InvoiceStats(**sub_data) + for sub_data in self._post( + "auth/r/ext/pay/invoice/stats/earning", + body={"currency": currency, "format": format}, + ) + ] + + def complete_invoice( + self, + id: str, + pay_currency: str, + *, + deposit_id: Optional[int] = None, + ledger_id: Optional[int] = None, + ) -> InvoiceSubmission: body = { - "id": id, "payCcy": pay_currency, "depositId": deposit_id, - "ledgerId": ledger_id + "id": id, + "payCcy": pay_currency, + "depositId": deposit_id, + "ledgerId": ledger_id, } data = self._post("auth/w/ext/pay/invoice/complete", body=body) @@ -111,65 +143,65 @@ def complete_invoice(self, return InvoiceSubmission.parse(data) def expire_invoice(self, id: str) -> InvoiceSubmission: - body = { "id": id } + body = {"id": id} data = self._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") ] - - def add_currency_conversion(self, - base_ccy: str, - convert_ccy: str) -> bool: - return bool(self._post("auth/w/ext/pay/settings/convert/create", \ - body={ "baseCcy": base_ccy, "convertCcy": convert_ccy })) - - def remove_currency_conversion(self, - base_ccy: str, - convert_ccy: str) -> bool: - return bool(self._post("auth/w/ext/pay/settings/convert/remove", \ - body={ "baseCcy": base_ccy, "convertCcy": convert_ccy })) - - 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 })) + return [ + CurrencyConversion(**sub_data) + for sub_data in self._post("auth/r/ext/pay/settings/convert/list") + ] + + def add_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool: + return bool( + self._post( + "auth/w/ext/pay/settings/convert/create", + body={"baseCcy": base_ccy, "convertCcy": convert_ccy}, + ) + ) + + def remove_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool: + return bool( + self._post( + "auth/w/ext/pay/settings/convert/remove", + body={"baseCcy": base_ccy, "convertCcy": convert_ccy}, + ) + ) + + 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}) + ) def get_merchant_settings(self, key: str) -> Any: - return self._post("auth/r/ext/pay/settings/get", body={ "key": key }) + return self._post("auth/r/ext/pay/settings/get", body={"key": key}) - #pylint: disable-next=dangerous-default-value + # pylint: disable-next=dangerous-default-value def list_merchant_settings(self, keys: List[str] = []) -> Dict[str, Any]: - return self._post("auth/r/ext/pay/settings/list", body={ "keys": keys }) - - def get_deposits(self, - start: int, - to: int, - *, - ccy: Optional[str] = None, - unlinked: Optional[bool] = None) -> List[MerchantDeposit]: - body = { - "from": start, "to": to, "ccy": ccy, - "unlinked": unlinked - } + return self._post("auth/r/ext/pay/settings/list", body={"keys": keys}) + + def get_deposits( + self, + start: int, + to: int, + *, + ccy: Optional[str] = None, + unlinked: Optional[bool] = None, + ) -> List[MerchantDeposit]: + body = {"from": start, "to": to, "ccy": ccy, "unlinked": unlinked} data = self._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] - def get_unlinked_deposits(self, - ccy: str, - *, - start: Optional[int] = None, - end: Optional[int] = None) -> List[MerchantUnlinkedDeposit]: - body = { - "ccy": ccy, "start": start, "end": end - } + def get_unlinked_deposits( + self, ccy: str, *, start: Optional[int] = None, end: Optional[int] = None + ) -> List[MerchantUnlinkedDeposit]: + body = {"ccy": ccy, "start": start, "end": end} data = self._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] diff --git a/bfxapi/rest/endpoints/rest_public_endpoints.py b/bfxapi/rest/endpoints/rest_public_endpoints.py index f3001e7..c8b86b0 100644 --- a/bfxapi/rest/endpoints/rest_public_endpoints.py +++ b/bfxapi/rest/endpoints/rest_public_endpoints.py @@ -28,7 +28,7 @@ from ..middleware import Middleware -#pylint: disable-next=too-many-public-methods +# pylint: disable-next=too-many-public-methods class RestPublicEndpoints(Middleware): def conf(self, config: str) -> Any: return self._get(f"conf/{config}")[0] @@ -36,35 +36,47 @@ def conf(self, config: str) -> Any: def get_platform_status(self) -> PlatformStatus: return serializers.PlatformStatus.parse(*self._get("platform/status")) - def get_tickers(self, symbols: List[str]) -> Dict[str, Union[TradingPairTicker, FundingCurrencyTicker]]: - data = self._get("tickers", params={ "symbols": ",".join(symbols) }) + def get_tickers( + self, symbols: List[str] + ) -> Dict[str, Union[TradingPairTicker, FundingCurrencyTicker]]: + data = self._get("tickers", params={"symbols": ",".join(symbols)}) - parsers = { "t": serializers.TradingPairTicker.parse, "f": serializers.FundingCurrencyTicker.parse } + parsers = { + "t": serializers.TradingPairTicker.parse, + "f": serializers.FundingCurrencyTicker.parse, + } return { - symbol: cast(Union[TradingPairTicker, FundingCurrencyTicker], - parsers[symbol[0]](*sub_data)) for sub_data in data - if (symbol := sub_data.pop(0)) + symbol: cast( + Union[TradingPairTicker, FundingCurrencyTicker], + parsers[symbol[0]](*sub_data), + ) + for sub_data in data + if (symbol := sub_data.pop(0)) } - def get_t_tickers(self, symbols: Union[List[str], Literal["ALL"]]) -> Dict[str, TradingPairTicker]: + def get_t_tickers( + self, symbols: Union[List[str], Literal["ALL"]] + ) -> Dict[str, TradingPairTicker]: if isinstance(symbols, str) and symbols == "ALL": return { symbol: cast(TradingPairTicker, sub_data) - for symbol, sub_data in self.get_tickers([ "ALL" ]).items() - if symbol.startswith("t") + for symbol, sub_data in self.get_tickers(["ALL"]).items() + if symbol.startswith("t") } data = self.get_tickers(list(symbols)) return cast(Dict[str, TradingPairTicker], data) - def get_f_tickers(self, symbols: Union[List[str], Literal["ALL"]]) -> Dict[str, FundingCurrencyTicker]: + def get_f_tickers( + self, symbols: Union[List[str], Literal["ALL"]] + ) -> Dict[str, FundingCurrencyTicker]: if isinstance(symbols, str) and symbols == "ALL": return { symbol: cast(FundingCurrencyTicker, sub_data) - for symbol, sub_data in self.get_tickers([ "ALL" ]).items() - if symbol.startswith("f") + for symbol, sub_data in self.get_tickers(["ALL"]).items() + if symbol.startswith("f") } data = self.get_tickers(list(symbols)) @@ -77,230 +89,292 @@ def get_t_ticker(self, symbol: str) -> TradingPairTicker: def get_f_ticker(self, symbol: str) -> FundingCurrencyTicker: return serializers.FundingCurrencyTicker.parse(*self._get(f"ticker/{symbol}")) - def get_tickers_history(self, - symbols: List[str], - *, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[TickersHistory]: - return [ serializers.TickersHistory.parse(*sub_data) for sub_data in self._get("tickers/hist", params={ - "symbols": ",".join(symbols), - "start": start, "end": end, - "limit": limit - }) ] - - def get_t_trades(self, - pair: str, - *, - limit: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - sort: Optional[int] = None) -> List[TradingPairTrade]: - params = { "limit": limit, "start": start, "end": end, "sort": sort } + def get_tickers_history( + self, + symbols: List[str], + *, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[TickersHistory]: + return [ + serializers.TickersHistory.parse(*sub_data) + for sub_data in self._get( + "tickers/hist", + params={ + "symbols": ",".join(symbols), + "start": start, + "end": end, + "limit": limit, + }, + ) + ] + + def get_t_trades( + self, + pair: str, + *, + limit: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + sort: Optional[int] = None, + ) -> List[TradingPairTrade]: + params = {"limit": limit, "start": start, "end": end, "sort": sort} data = self._get(f"trades/{pair}/hist", params=params) - return [ serializers.TradingPairTrade.parse(*sub_data) for sub_data in data ] - - def get_f_trades(self, - currency: str, - *, - limit: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - sort: Optional[int] = None) -> List[FundingCurrencyTrade]: - params = { "limit": limit, "start": start, "end": end, "sort": sort } + return [serializers.TradingPairTrade.parse(*sub_data) for sub_data in data] + + def get_f_trades( + self, + currency: str, + *, + limit: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + sort: Optional[int] = None, + ) -> List[FundingCurrencyTrade]: + params = {"limit": limit, "start": start, "end": end, "sort": sort} data = self._get(f"trades/{currency}/hist", params=params) - return [ serializers.FundingCurrencyTrade.parse(*sub_data) for sub_data in data ] - - def get_t_book(self, - pair: str, - precision: Literal["P0", "P1", "P2", "P3", "P4"], - *, - len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairBook]: - return [ serializers.TradingPairBook.parse(*sub_data) \ - for sub_data in self._get(f"book/{pair}/{precision}", params={ "len": len }) ] - - def get_f_book(self, - currency: str, - precision: Literal["P0", "P1", "P2", "P3", "P4"], - *, - len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyBook]: - return [ serializers.FundingCurrencyBook.parse(*sub_data) \ - for sub_data in self._get(f"book/{currency}/{precision}", params={ "len": len }) ] - - def get_t_raw_book(self, - pair: str, - *, - len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairRawBook]: - return [ serializers.TradingPairRawBook.parse(*sub_data) \ - for sub_data in self._get(f"book/{pair}/R0", params={ "len": len }) ] - - def get_f_raw_book(self, - currency: str, - *, - len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyRawBook]: - return [ serializers.FundingCurrencyRawBook.parse(*sub_data) \ - for sub_data in self._get(f"book/{currency}/R0", params={ "len": len }) ] - - def get_stats_hist(self, - resource: str, - *, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[Statistic]: - params = { "sort": sort, "start": start, "end": end, "limit": limit } + return [serializers.FundingCurrencyTrade.parse(*sub_data) for sub_data in data] + + def get_t_book( + self, + pair: str, + precision: Literal["P0", "P1", "P2", "P3", "P4"], + *, + len: Optional[Literal[1, 25, 100]] = None, + ) -> List[TradingPairBook]: + return [ + serializers.TradingPairBook.parse(*sub_data) + for sub_data in self._get(f"book/{pair}/{precision}", params={"len": len}) + ] + + def get_f_book( + self, + currency: str, + precision: Literal["P0", "P1", "P2", "P3", "P4"], + *, + len: Optional[Literal[1, 25, 100]] = None, + ) -> List[FundingCurrencyBook]: + return [ + serializers.FundingCurrencyBook.parse(*sub_data) + for sub_data in self._get( + f"book/{currency}/{precision}", params={"len": len} + ) + ] + + def get_t_raw_book( + self, pair: str, *, len: Optional[Literal[1, 25, 100]] = None + ) -> List[TradingPairRawBook]: + return [ + serializers.TradingPairRawBook.parse(*sub_data) + for sub_data in self._get(f"book/{pair}/R0", params={"len": len}) + ] + + def get_f_raw_book( + self, currency: str, *, len: Optional[Literal[1, 25, 100]] = None + ) -> List[FundingCurrencyRawBook]: + return [ + serializers.FundingCurrencyRawBook.parse(*sub_data) + for sub_data in self._get(f"book/{currency}/R0", params={"len": len}) + ] + + def get_stats_hist( + self, + resource: str, + *, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[Statistic]: + params = {"sort": sort, "start": start, "end": end, "limit": limit} data = self._get(f"stats1/{resource}/hist", params=params) - return [ serializers.Statistic.parse(*sub_data) for sub_data in data ] - - def get_stats_last(self, - resource: str, - *, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> Statistic: - params = { "sort": sort, "start": start, "end": end, "limit": limit } + return [serializers.Statistic.parse(*sub_data) for sub_data in data] + + def get_stats_last( + self, + resource: str, + *, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> Statistic: + params = {"sort": sort, "start": start, "end": end, "limit": limit} data = self._get(f"stats1/{resource}/last", params=params) return serializers.Statistic.parse(*data) - def get_candles_hist(self, - symbol: str, - tf: str = "1m", - *, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[Candle]: - params = { "sort": sort, "start": start, "end": end, "limit": limit } + def get_candles_hist( + self, + symbol: str, + tf: str = "1m", + *, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + 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) - return [ serializers.Candle.parse(*sub_data) for sub_data in data ] - - def get_candles_last(self, - symbol: str, - tf: str = "1m", - *, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> Candle: - params = { "sort": sort, "start": start, "end": end, "limit": limit } + return [serializers.Candle.parse(*sub_data) for sub_data in data] + + def get_candles_last( + self, + symbol: str, + tf: str = "1m", + *, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + 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) return serializers.Candle.parse(*data) - def get_derivatives_status(self, keys: Union[List[str], Literal["ALL"]]) -> Dict[str, DerivativesStatus]: + def get_derivatives_status( + self, keys: Union[List[str], Literal["ALL"]] + ) -> Dict[str, DerivativesStatus]: if keys == "ALL": - params = { "keys": "ALL" } - else: params = { "keys": ",".join(keys) } + params = {"keys": "ALL"} + else: + params = {"keys": ",".join(keys)} data = self._get("status/deriv", params=params) return { key: serializers.DerivativesStatus.parse(*sub_data) - for sub_data in data - if (key := sub_data.pop(0)) + for sub_data in data + if (key := sub_data.pop(0)) } - def get_derivatives_status_history(self, - key: str, - *, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[DerivativesStatus]: - params = { "sort": sort, "start": start, "end": end, "limit": limit } + def get_derivatives_status_history( + self, + key: str, + *, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + 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) - return [ serializers.DerivativesStatus.parse(*sub_data) for sub_data in data ] - - def get_liquidations(self, - *, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[Liquidation]: - params = { "sort": sort, "start": start, "end": end, "limit": limit } + return [serializers.DerivativesStatus.parse(*sub_data) for sub_data in data] + + def get_liquidations( + self, + *, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[Liquidation]: + params = {"sort": sort, "start": start, "end": end, "limit": limit} data = self._get("liquidations/hist", params=params) - return [ serializers.Liquidation.parse(*sub_data[0]) for sub_data in data ] - - def get_seed_candles(self, - symbol: str, - tf: str = "1m", - *, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[Candle]: + return [serializers.Liquidation.parse(*sub_data[0]) for sub_data in data] + + def get_seed_candles( + self, + symbol: str, + tf: str = "1m", + *, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + 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) - return [ serializers.Candle.parse(*sub_data) for sub_data in data ] - - def get_leaderboards_hist(self, - resource: str, - *, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[Leaderboard]: - params = { "sort": sort, "start": start, "end": end, "limit": limit } + return [serializers.Candle.parse(*sub_data) for sub_data in data] + + def get_leaderboards_hist( + self, + resource: str, + *, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[Leaderboard]: + params = {"sort": sort, "start": start, "end": end, "limit": limit} data = self._get(f"rankings/{resource}/hist", params=params) - return [ serializers.Leaderboard.parse(*sub_data) for sub_data in data ] - - def get_leaderboards_last(self, - resource: str, - *, - sort: Optional[int] = None, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> Leaderboard: - params = { "sort": sort, "start": start, "end": end, "limit": limit } + return [serializers.Leaderboard.parse(*sub_data) for sub_data in data] + + def get_leaderboards_last( + self, + resource: str, + *, + sort: Optional[int] = None, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> Leaderboard: + params = {"sort": sort, "start": start, "end": end, "limit": limit} data = self._get(f"rankings/{resource}/last", params=params) return serializers.Leaderboard.parse(*data) - def get_funding_stats(self, - symbol: str, - *, - start: Optional[str] = None, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[FundingStatistic]: - params = { "start": start, "end": end, "limit": limit } + def get_funding_stats( + self, + symbol: str, + *, + start: Optional[str] = None, + end: Optional[str] = None, + limit: Optional[int] = None, + ) -> List[FundingStatistic]: + params = {"start": start, "end": end, "limit": limit} data = self._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: return serializers.PulseProfile.parse(*self._get(f"pulse/profile/{nickname}")) - def get_pulse_message_history(self, - *, - end: Optional[str] = None, - limit: Optional[int] = None) -> List[PulseMessage]: + 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._get("pulse/hist", params={"end": end, "limit": limit}): sub_data[18] = sub_data[18][0] message = serializers.PulseMessage.parse(*sub_data) messages.append(message) return messages - def get_trading_market_average_price(self, - symbol: str, - amount: Union[str, float, Decimal], - *, - price_limit: Optional[Union[str, float, Decimal]] = None - ) -> TradingMarketAveragePrice: - return serializers.TradingMarketAveragePrice.parse(*self._post("calc/trade/avg", body={ - "symbol": symbol, "amount": amount, "price_limit": price_limit - })) - - def get_funding_market_average_price(self, - symbol: str, - amount: Union[str, float, Decimal], - period: int, - *, - rate_limit: Optional[Union[str, float, Decimal]] = None - ) -> FundingMarketAveragePrice: - return serializers.FundingMarketAveragePrice.parse(*self._post("calc/trade/avg", body={ - "symbol": symbol, "amount": amount, "period": period, "rate_limit": rate_limit - })) + def get_trading_market_average_price( + self, + symbol: str, + amount: Union[str, float, Decimal], + *, + price_limit: Optional[Union[str, float, Decimal]] = None, + ) -> TradingMarketAveragePrice: + return serializers.TradingMarketAveragePrice.parse( + *self._post( + "calc/trade/avg", + body={"symbol": symbol, "amount": amount, "price_limit": price_limit}, + ) + ) + + def get_funding_market_average_price( + self, + symbol: str, + amount: Union[str, float, Decimal], + period: int, + *, + rate_limit: Optional[Union[str, float, Decimal]] = None, + ) -> FundingMarketAveragePrice: + return serializers.FundingMarketAveragePrice.parse( + *self._post( + "calc/trade/avg", + body={ + "symbol": symbol, + "amount": amount, + "period": period, + "rate_limit": rate_limit, + }, + ) + ) def get_fx_rate(self, ccy1: str, ccy2: str) -> FxRate: - return serializers.FxRate.parse(*self._post("calc/fx", body={ "ccy1": ccy1, "ccy2": ccy2 })) + return serializers.FxRate.parse( + *self._post("calc/fx", body={"ccy1": ccy1, "ccy2": ccy2}) + ) diff --git a/bfxapi/rest/exceptions.py b/bfxapi/rest/exceptions.py index ec83eec..c19d92e 100644 --- a/bfxapi/rest/exceptions.py +++ b/bfxapi/rest/exceptions.py @@ -4,8 +4,10 @@ class NotFoundError(BfxBaseException): pass + class RequestParametersError(BfxBaseException): pass + class UnknownGenericError(BfxBaseException): pass diff --git a/bfxapi/rest/middleware/middleware.py b/bfxapi/rest/middleware/middleware.py index d89023e..7047127 100644 --- a/bfxapi/rest/middleware/middleware.py +++ b/bfxapi/rest/middleware/middleware.py @@ -16,45 +16,47 @@ 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): + 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 str to call __build_authentication_headers" + assert isinstance(self.api_key, str) and isinstance( + self.api_secret, str + ), "API_KEY and API_SECRET must be both str to call __build_authentication_headers" 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}" + else: + path = f"/api/v2/{endpoint}{nonce}{data}" signature = hmac.new( - self.api_secret.encode("utf8"), - path.encode("utf8"), - hashlib.sha384 + self.api_secret.encode("utf8"), path.encode("utf8"), hashlib.sha384 ).hexdigest() return { "bfx-nonce": nonce, "bfx-signature": signature, - "bfx-apikey": self.api_key + "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 + url=f"{self.host}/{endpoint}", params=params, timeout=Middleware.TIMEOUT ) if response.status_code == HTTPStatus.NOT_FOUND: @@ -64,30 +66,43 @@ def _get(self, endpoint: str, params: Optional["_Params"] = None) -> Any: 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]}>.") + 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: + 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" } + 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) } + 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 + timeout=Middleware.TIMEOUT, ) if response.status_code == HTTPStatus.NOT_FOUND: @@ -97,14 +112,24 @@ def _post(self, endpoint: str, params: Optional["_Params"] = None, 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]}>") + 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]}>.") + 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 diff --git a/bfxapi/types/dataclasses.py b/bfxapi/types/dataclasses.py index 2d32fa9..91337f2 100644 --- a/bfxapi/types/dataclasses.py +++ b/bfxapi/types/dataclasses.py @@ -3,12 +3,14 @@ from .labeler import _Type, compose, partial -#region Dataclass definitions for types of public use +# region Dataclass definitions for types of public use + @dataclass class PlatformStatus(_Type): status: int + @dataclass class TradingPairTicker(_Type): bid: float @@ -22,6 +24,7 @@ class TradingPairTicker(_Type): high: float low: float + @dataclass class FundingCurrencyTicker(_Type): frr: float @@ -39,6 +42,7 @@ class FundingCurrencyTicker(_Type): low: float frr_amount_available: float + @dataclass class TickersHistory(_Type): symbol: str @@ -46,6 +50,7 @@ class TickersHistory(_Type): ask: float mts: int + @dataclass class TradingPairTrade(_Type): id: int @@ -53,6 +58,7 @@ class TradingPairTrade(_Type): amount: float price: float + @dataclass class FundingCurrencyTrade(_Type): id: int @@ -61,12 +67,14 @@ class FundingCurrencyTrade(_Type): rate: float period: int + @dataclass class TradingPairBook(_Type): price: float count: int amount: float + @dataclass class FundingCurrencyBook(_Type): rate: float @@ -74,12 +82,14 @@ class FundingCurrencyBook(_Type): count: int amount: float + @dataclass class TradingPairRawBook(_Type): order_id: int price: float amount: float + @dataclass class FundingCurrencyRawBook(_Type): offer_id: int @@ -87,11 +97,13 @@ class FundingCurrencyRawBook(_Type): rate: float amount: float + @dataclass class Statistic(_Type): mts: int value: float + @dataclass class Candle(_Type): mts: int @@ -101,6 +113,7 @@ class Candle(_Type): low: int volume: float + @dataclass class DerivativesStatus(_Type): mts: int @@ -116,6 +129,7 @@ class DerivativesStatus(_Type): clamp_min: float clamp_max: float + @dataclass class Liquidation(_Type): pos_id: int @@ -127,6 +141,7 @@ class Liquidation(_Type): is_market_sold: int liquidation_price: float + @dataclass class Leaderboard(_Type): mts: int @@ -135,6 +150,7 @@ class Leaderboard(_Type): value: float twitter_handle: Optional[str] + @dataclass class FundingStatistic(_Type): mts: int @@ -144,6 +160,7 @@ class FundingStatistic(_Type): funding_amount_used: float funding_below_threshold: float + @dataclass class PulseProfile(_Type): puid: str @@ -156,6 +173,7 @@ class PulseProfile(_Type): following: int tipping_status: int + @dataclass class PulseMessage(_Type): pid: str @@ -173,23 +191,28 @@ class PulseMessage(_Type): profile: PulseProfile comments: int + @dataclass class TradingMarketAveragePrice(_Type): price_avg: float amount: float + @dataclass class FundingMarketAveragePrice(_Type): rate_avg: float amount: float + @dataclass class FxRate(_Type): current_rate: float -#endregion -#region Dataclass definitions for types of auth use +# endregion + +# region Dataclass definitions for types of auth use + @dataclass class UserInfo(_Type): @@ -222,6 +245,7 @@ class UserInfo(_Type): compl_countries_resid: List[str] is_merchant_enterprise: int + @dataclass class LoginHistory(_Type): id: int @@ -229,10 +253,12 @@ class LoginHistory(_Type): ip: str extra_info: Dict[str, Any] + @dataclass class BalanceAvailable(_Type): amount: float + @dataclass class Order(_Type): id: int @@ -258,6 +284,7 @@ class Order(_Type): routing: str meta: Dict[str, Any] + @dataclass class Position(_Type): symbol: str @@ -278,6 +305,7 @@ class Position(_Type): collateral_min: float meta: Dict[str, Any] + @dataclass class Trade(_Type): id: int @@ -288,11 +316,12 @@ class Trade(_Type): exec_price: float order_type: str order_price: float - maker:int + maker: int fee: float fee_currency: str cid: int + @dataclass() class FundingTrade(_Type): id: int @@ -303,6 +332,7 @@ class FundingTrade(_Type): rate: float period: int + @dataclass class OrderTrade(_Type): id: int @@ -311,11 +341,12 @@ class OrderTrade(_Type): order_id: int exec_amount: float exec_price: float - maker:int + maker: int fee: float fee_currency: str cid: int + @dataclass class Ledger(_Type): id: int @@ -325,6 +356,7 @@ class Ledger(_Type): balance: float description: str + @dataclass class FundingOffer(_Type): id: int @@ -342,6 +374,7 @@ class FundingOffer(_Type): hidden: int renew: int + @dataclass class FundingCredit(_Type): id: int @@ -363,6 +396,7 @@ class FundingCredit(_Type): no_close: int position_pair: str + @dataclass class FundingLoan(_Type): id: int @@ -383,6 +417,7 @@ class FundingLoan(_Type): renew: int no_close: int + @dataclass class FundingAutoRenew(_Type): currency: str @@ -390,6 +425,7 @@ class FundingAutoRenew(_Type): rate: float threshold: float + @dataclass() class FundingInfo(_Type): yield_loan: float @@ -397,6 +433,7 @@ class FundingInfo(_Type): duration_loan: float duration_lend: float + @dataclass class Wallet(_Type): wallet_type: str @@ -407,6 +444,7 @@ class Wallet(_Type): last_change: str trade_details: Dict[str, Any] + @dataclass class Transfer(_Type): mts: int @@ -416,6 +454,7 @@ class Transfer(_Type): currency_to: str amount: int + @dataclass class Withdrawal(_Type): withdrawal_id: int @@ -425,6 +464,7 @@ class Withdrawal(_Type): amount: float withdrawal_fee: float + @dataclass class DepositAddress(_Type): method: str @@ -432,12 +472,14 @@ class DepositAddress(_Type): address: str pool_address: str + @dataclass class LightningNetworkInvoice(_Type): invoice_hash: str invoice: str amount: str + @dataclass class Movement(_Type): id: str @@ -452,6 +494,7 @@ class Movement(_Type): transaction_id: str withdraw_transaction_note: str + @dataclass class SymbolMarginInfo(_Type): symbol: str @@ -460,6 +503,7 @@ class SymbolMarginInfo(_Type): buy: float sell: float + @dataclass class BaseMarginInfo(_Type): user_pl: float @@ -468,6 +512,7 @@ class BaseMarginInfo(_Type): margin_net: float margin_min: float + @dataclass class PositionClaim(_Type): symbol: str @@ -484,6 +529,7 @@ class PositionClaim(_Type): min_collateral: str meta: Dict[str, Any] + @dataclass class PositionIncreaseInfo(_Type): max_pos: int @@ -499,12 +545,14 @@ class PositionIncreaseInfo(_Type): funding_value_currency: str funding_required_currency: str + @dataclass class PositionIncrease(_Type): symbol: str amount: float base_price: float + @dataclass class PositionHistory(_Type): symbol: str @@ -517,6 +565,7 @@ class PositionHistory(_Type): mts_create: int mts_update: int + @dataclass class PositionSnapshot(_Type): symbol: str @@ -529,6 +578,7 @@ class PositionSnapshot(_Type): mts_create: int mts_update: int + @dataclass class PositionAudit(_Type): symbol: str @@ -545,18 +595,22 @@ class PositionAudit(_Type): collateral_min: float meta: Dict[str, Any] + @dataclass class DerivativePositionCollateral(_Type): status: int + @dataclass class DerivativePositionCollateralLimits(_Type): min_collateral: float max_collateral: float -#endregion -#region Dataclass definitions for types of merchant use +# endregion + +# region Dataclass definitions for types of merchant use + @compose(dataclass, partial) class InvoiceSubmission(_Type): @@ -580,7 +634,9 @@ class InvoiceSubmission(_Type): @classmethod def parse(cls, data: Dict[str, Any]) -> "InvoiceSubmission": if "customer_info" in data and data["customer_info"] is not None: - data["customer_info"] = InvoiceSubmission.CustomerInfo(**data["customer_info"]) + data["customer_info"] = InvoiceSubmission.CustomerInfo( + **data["customer_info"] + ) for index, invoice in enumerate(data["invoices"]): data["invoices"][index] = InvoiceSubmission.Invoice(**invoice) @@ -590,7 +646,9 @@ def parse(cls, data: Dict[str, Any]) -> "InvoiceSubmission": if "additional_payments" in data and data["additional_payments"] is not None: for index, additional_payment in enumerate(data["additional_payments"]): - data["additional_payments"][index] = InvoiceSubmission.Payment(**additional_payment) + data["additional_payments"][index] = InvoiceSubmission.Payment( + **additional_payment + ) return InvoiceSubmission(**data) @@ -631,6 +689,7 @@ class Payment: force_completed: bool amount_diff: str + @dataclass class InvoicePage(_Type): page: int @@ -648,17 +707,20 @@ def parse(cls, data: Dict[str, Any]) -> "InvoicePage": return InvoicePage(**data) + @dataclass class InvoiceStats(_Type): time: str count: float + @dataclass class CurrencyConversion(_Type): base_ccy: str convert_ccy: str created: int + @dataclass class MerchantDeposit(_Type): id: int @@ -672,6 +734,7 @@ class MerchantDeposit(_Type): method: str pay_method: str + @dataclass class MerchantUnlinkedDeposit(_Type): id: int @@ -687,4 +750,5 @@ class MerchantUnlinkedDeposit(_Type): status: str note: Optional[str] -#endregion + +# endregion diff --git a/bfxapi/types/labeler.py b/bfxapi/types/labeler.py index b32e535..008a07d 100644 --- a/bfxapi/types/labeler.py +++ b/bfxapi/types/labeler.py @@ -2,6 +2,7 @@ T = TypeVar("T", bound="_Type") + def compose(*decorators): def wrapper(function): for decorator in reversed(decorators): @@ -10,30 +11,37 @@ def wrapper(function): return wrapper + def partial(cls): def __init__(self, **kwargs): for annotation in self.__annotations__.keys(): if annotation not in kwargs: self.__setattr__(annotation, None) - else: self.__setattr__(annotation, kwargs[annotation]) + else: + self.__setattr__(annotation, kwargs[annotation]) kwargs.pop(annotation, None) if len(kwargs) != 0: - raise TypeError(f"{cls.__name__}.__init__() got an unexpected keyword argument '{list(kwargs.keys())[0]}'") + raise TypeError( + f"{cls.__name__}.__init__() got an unexpected keyword argument '{list(kwargs.keys())[0]}'" + ) cls.__init__ = __init__ return cls + class _Type: """ Base class for any dataclass serializable by the _Serializer generic class. """ + class _Serializer(Generic[T]): - def __init__(self, name: str, klass: Type[_Type], labels: List[str], - *, flat: bool = False): + def __init__( + self, name: str, klass: Type[_Type], labels: List[str], *, flat: bool = False + ): self.name, self.klass, self.__labels, self.__flat = name, klass, labels, flat def _serialize(self, *args: Any) -> Iterable[Tuple[str, Any]]: @@ -41,8 +49,10 @@ def _serialize(self, *args: Any) -> Iterable[Tuple[str, Any]]: args = tuple(_Serializer.__flatten(list(args))) if len(self.__labels) > len(args): - raise AssertionError(f"{self.name} -> and <*args> " \ - "arguments should contain the same amount of elements.") + raise AssertionError( + f"{self.name} -> and <*args> " + "arguments should contain the same amount of elements." + ) for index, label in enumerate(self.__labels): if label != "_PLACEHOLDER": @@ -52,7 +62,7 @@ def parse(self, *values: Any) -> T: return cast(T, self.klass(**dict(self._serialize(*values)))) def get_labels(self) -> List[str]: - return [ label for label in self.__labels if label != "_PLACEHOLDER" ] + return [label for label in self.__labels if label != "_PLACEHOLDER"] @classmethod def __flatten(cls, array: List[Any]) -> List[Any]: @@ -64,10 +74,17 @@ def __flatten(cls, array: List[Any]) -> List[Any]: return array[:1] + cls.__flatten(array[1:]) + class _RecursiveSerializer(_Serializer, Generic[T]): - def __init__(self, name: str, klass: Type[_Type], labels: List[str], - *, serializers: Dict[str, _Serializer[Any]], - flat: bool = False): + def __init__( + self, + name: str, + klass: Type[_Type], + labels: List[str], + *, + serializers: Dict[str, _Serializer[Any]], + flat: bool = False, + ): super().__init__(name, klass, labels, flat=flat) self.serializers = serializers @@ -81,15 +98,21 @@ def parse(self, *values: Any) -> T: return cast(T, self.klass(**serialization)) -def generate_labeler_serializer(name: str, klass: Type[T], labels: List[str], - *, flat: bool = False - ) -> _Serializer[T]: - return _Serializer[T](name, klass, labels, \ - flat=flat) - -def generate_recursive_serializer(name: str, klass: Type[T], labels: List[str], - *, serializers: Dict[str, _Serializer[Any]], - flat: bool = False - ) -> _RecursiveSerializer[T]: - return _RecursiveSerializer[T](name, klass, labels, \ - serializers=serializers, flat=flat) + +def generate_labeler_serializer( + name: str, klass: Type[T], labels: List[str], *, flat: bool = False +) -> _Serializer[T]: + return _Serializer[T](name, klass, labels, flat=flat) + + +def generate_recursive_serializer( + name: str, + klass: Type[T], + labels: List[str], + *, + serializers: Dict[str, _Serializer[Any]], + flat: bool = False, +) -> _RecursiveSerializer[T]: + return _RecursiveSerializer[T]( + name, klass, labels, serializers=serializers, flat=flat + ) diff --git a/bfxapi/types/notification.py b/bfxapi/types/notification.py index 2d318f8..2b7d375 100644 --- a/bfxapi/types/notification.py +++ b/bfxapi/types/notification.py @@ -5,6 +5,7 @@ T = TypeVar("T") + @dataclass class Notification(_Type, Generic[T]): mts: int @@ -15,16 +16,30 @@ class Notification(_Type, Generic[T]): status: str text: str -class _Notification(_Serializer, Generic[T]): - __LABELS = [ "mts", "type", "message_id", "_PLACEHOLDER", "data", "code", "status", "text" ] - def __init__(self, serializer: Optional[_Serializer] = None, is_iterable: bool = False): +class _Notification(_Serializer, Generic[T]): + __LABELS = [ + "mts", + "type", + "message_id", + "_PLACEHOLDER", + "data", + "code", + "status", + "text", + ] + + def __init__( + self, serializer: Optional[_Serializer] = None, is_iterable: bool = False + ): super().__init__("Notification", Notification, _Notification.__LABELS) self.serializer, self.is_iterable = serializer, is_iterable def parse(self, *values: Any) -> Notification[T]: - notification = cast(Notification[T], Notification(**dict(self._serialize(*values)))) + notification = cast( + Notification[T], Notification(**dict(self._serialize(*values))) + ) if isinstance(self.serializer, _Serializer): data = cast(List[Any], notification.data) @@ -34,6 +49,9 @@ def parse(self, *values: Any) -> Notification[T]: data = data[0] notification.data = self.serializer.parse(*data) - else: notification.data = cast(T, [ self.serializer.parse(*sub_data) for sub_data in data ]) + else: + notification.data = cast( + T, [self.serializer.parse(*sub_data) for sub_data in data] + ) return notification diff --git a/bfxapi/types/serializers.py b/bfxapi/types/serializers.py index 5117b13..2b83b7c 100644 --- a/bfxapi/types/serializers.py +++ b/bfxapi/types/serializers.py @@ -1,44 +1,73 @@ from . import dataclasses -#pylint: disable-next=unused-import +# pylint: disable-next=unused-import from .labeler import ( _Serializer, generate_labeler_serializer, generate_recursive_serializer, ) -#pylint: disable-next=unused-import +# pylint: disable-next=unused-import from .notification import _Notification __serializers__ = [ - "PlatformStatus", "TradingPairTicker", "FundingCurrencyTicker", - "TickersHistory", "TradingPairTrade", "FundingCurrencyTrade", - "TradingPairBook", "FundingCurrencyBook", "TradingPairRawBook", - "FundingCurrencyRawBook", "Statistic", "Candle", - "DerivativesStatus", "Liquidation", "Leaderboard", - "FundingStatistic", "PulseProfile", "PulseMessage", - "TradingMarketAveragePrice", "FundingMarketAveragePrice", "FxRate", - - "UserInfo", "LoginHistory", "BalanceAvailable", - "Order", "Position", "Trade", - "FundingTrade", "OrderTrade", "Ledger", - "FundingOffer", "FundingCredit", "FundingLoan", - "FundingAutoRenew", "FundingInfo", "Wallet", - "Transfer", "Withdrawal", "DepositAddress", - "LightningNetworkInvoice", "Movement", "SymbolMarginInfo", - "BaseMarginInfo", "PositionClaim", "PositionIncreaseInfo", - "PositionIncrease", "PositionHistory", "PositionSnapshot", - "PositionAudit", "DerivativePositionCollateral", "DerivativePositionCollateralLimits", + "PlatformStatus", + "TradingPairTicker", + "FundingCurrencyTicker", + "TickersHistory", + "TradingPairTrade", + "FundingCurrencyTrade", + "TradingPairBook", + "FundingCurrencyBook", + "TradingPairRawBook", + "FundingCurrencyRawBook", + "Statistic", + "Candle", + "DerivativesStatus", + "Liquidation", + "Leaderboard", + "FundingStatistic", + "PulseProfile", + "PulseMessage", + "TradingMarketAveragePrice", + "FundingMarketAveragePrice", + "FxRate", + "UserInfo", + "LoginHistory", + "BalanceAvailable", + "Order", + "Position", + "Trade", + "FundingTrade", + "OrderTrade", + "Ledger", + "FundingOffer", + "FundingCredit", + "FundingLoan", + "FundingAutoRenew", + "FundingInfo", + "Wallet", + "Transfer", + "Withdrawal", + "DepositAddress", + "LightningNetworkInvoice", + "Movement", + "SymbolMarginInfo", + "BaseMarginInfo", + "PositionClaim", + "PositionIncreaseInfo", + "PositionIncrease", + "PositionHistory", + "PositionSnapshot", + "PositionAudit", + "DerivativePositionCollateral", + "DerivativePositionCollateralLimits", ] -#region Serializer definitions for types of public use +# region Serializer definitions for types of public use PlatformStatus = generate_labeler_serializer( - name="PlatformStatus", - klass=dataclasses.PlatformStatus, - labels=[ - "status" - ] + name="PlatformStatus", klass=dataclasses.PlatformStatus, labels=["status"] ) TradingPairTicker = generate_labeler_serializer( @@ -54,8 +83,8 @@ "last_price", "volume", "high", - "low" - ] + "low", + ], ) FundingCurrencyTicker = generate_labeler_serializer( @@ -77,8 +106,8 @@ "low", "_PLACEHOLDER", "_PLACEHOLDER", - "frr_amount_available" - ] + "frr_amount_available", + ], ) TickersHistory = generate_labeler_serializer( @@ -97,95 +126,54 @@ "_PLACEHOLDER", "_PLACEHOLDER", "_PLACEHOLDER", - "mts" - ] + "mts", + ], ) TradingPairTrade = generate_labeler_serializer( name="TradingPairTrade", klass=dataclasses.TradingPairTrade, - labels=[ - "id", - "mts", - "amount", - "price" - ] + labels=["id", "mts", "amount", "price"], ) FundingCurrencyTrade = generate_labeler_serializer( name="FundingCurrencyTrade", klass=dataclasses.FundingCurrencyTrade, - labels=[ - "id", - "mts", - "amount", - "rate", - "period" - ] + labels=["id", "mts", "amount", "rate", "period"], ) TradingPairBook = generate_labeler_serializer( name="TradingPairBook", klass=dataclasses.TradingPairBook, - labels=[ - "price", - "count", - "amount" - ] + labels=["price", "count", "amount"], ) FundingCurrencyBook = generate_labeler_serializer( name="FundingCurrencyBook", klass=dataclasses.FundingCurrencyBook, - labels=[ - "rate", - "period", - "count", - "amount" - ] + labels=["rate", "period", "count", "amount"], ) TradingPairRawBook = generate_labeler_serializer( name="TradingPairRawBook", klass=dataclasses.TradingPairRawBook, - labels=[ - "order_id", - "price", - "amount" - ] + labels=["order_id", "price", "amount"], ) FundingCurrencyRawBook = generate_labeler_serializer( name="FundingCurrencyRawBook", klass=dataclasses.FundingCurrencyRawBook, - labels=[ - "offer_id", - "period", - "rate", - "amount" - ] + labels=["offer_id", "period", "rate", "amount"], ) Statistic = generate_labeler_serializer( - name="Statistic", - klass=dataclasses.Statistic, - labels=[ - "mts", - "value" - ] + name="Statistic", klass=dataclasses.Statistic, labels=["mts", "value"] ) Candle = generate_labeler_serializer( name="Candle", klass=dataclasses.Candle, - labels=[ - "mts", - "open", - "close", - "high", - "low", - "volume" - ] + labels=["mts", "open", "close", "high", "low", "volume"], ) DerivativesStatus = generate_labeler_serializer( @@ -193,7 +181,7 @@ klass=dataclasses.DerivativesStatus, labels=[ "mts", - "_PLACEHOLDER", + "_PLACEHOLDER", "deriv_price", "spot_price", "_PLACEHOLDER", @@ -214,8 +202,8 @@ "_PLACEHOLDER", "_PLACEHOLDER", "clamp_min", - "clamp_max" - ] + "clamp_max", + ], ) Liquidation = generate_labeler_serializer( @@ -233,8 +221,8 @@ "is_match", "is_market_sold", "_PLACEHOLDER", - "liquidation_price" - ] + "liquidation_price", + ], ) Leaderboard = generate_labeler_serializer( @@ -250,8 +238,8 @@ "value", "_PLACEHOLDER", "_PLACEHOLDER", - "twitter_handle" - ] + "twitter_handle", + ], ) FundingStatistic = generate_labeler_serializer( @@ -269,8 +257,8 @@ "funding_amount_used", "_PLACEHOLDER", "_PLACEHOLDER", - "funding_below_threshold" - ] + "funding_below_threshold", + ], ) PulseProfile = generate_labeler_serializer( @@ -293,14 +281,14 @@ "_PLACEHOLDER", "_PLACEHOLDER", "_PLACEHOLDER", - "tipping_status" - ] + "tipping_status", + ], ) PulseMessage = generate_recursive_serializer( name="PulseMessage", klass=dataclasses.PulseMessage, - serializers={ "profile": PulseProfile }, + serializers={"profile": PulseProfile}, labels=[ "pid", "mts", @@ -314,7 +302,7 @@ "is_pin", "is_public", "comments_disabled", - "tags", + "tags", "attachments", "meta", "likes", @@ -323,39 +311,29 @@ "profile", "comments", "_PLACEHOLDER", - "_PLACEHOLDER" - ] + "_PLACEHOLDER", + ], ) TradingMarketAveragePrice = generate_labeler_serializer( name="TradingMarketAveragePrice", klass=dataclasses.TradingMarketAveragePrice, - labels=[ - "price_avg", - "amount" - ] + labels=["price_avg", "amount"], ) FundingMarketAveragePrice = generate_labeler_serializer( name="FundingMarketAveragePrice", klass=dataclasses.FundingMarketAveragePrice, - labels=[ - "rate_avg", - "amount" - ] + labels=["rate_avg", "amount"], ) FxRate = generate_labeler_serializer( - name="FxRate", - klass=dataclasses.FxRate, - labels=[ - "current_rate" - ] + name="FxRate", klass=dataclasses.FxRate, labels=["current_rate"] ) -#endregion +# endregion -#region Serializer definitions for types of auth use +# region Serializer definitions for types of auth use UserInfo = generate_labeler_serializer( name="UserInfo", @@ -415,8 +393,8 @@ "_PLACEHOLDER", "_PLACEHOLDER", "_PLACEHOLDER", - "is_merchant_enterprise" - ] + "is_merchant_enterprise", + ], ) LoginHistory = generate_labeler_serializer( @@ -430,16 +408,12 @@ "ip", "_PLACEHOLDER", "_PLACEHOLDER", - "extra_info" - ] + "extra_info", + ], ) BalanceAvailable = generate_labeler_serializer( - name="BalanceAvailable", - klass=dataclasses.BalanceAvailable, - labels=[ - "amount" - ] + name="BalanceAvailable", klass=dataclasses.BalanceAvailable, labels=["amount"] ) Order = generate_labeler_serializer( @@ -450,10 +424,10 @@ "gid", "cid", "symbol", - "mts_create", - "mts_update", - "amount", - "amount_orig", + "mts_create", + "mts_update", + "amount", + "amount_orig", "order_type", "type_prev", "mts_tif", @@ -470,26 +444,26 @@ "_PLACEHOLDER", "_PLACEHOLDER", "notify", - "hidden", + "hidden", "placed_id", "_PLACEHOLDER", "_PLACEHOLDER", "routing", "_PLACEHOLDER", "_PLACEHOLDER", - "meta" - ] + "meta", + ], ) Position = generate_labeler_serializer( name="Position", klass=dataclasses.Position, labels=[ - "symbol", - "status", - "amount", - "base_price", - "margin_funding", + "symbol", + "status", + "amount", + "base_price", + "margin_funding", "margin_funding_type", "pl", "pl_perc", @@ -504,41 +478,33 @@ "_PLACEHOLDER", "collateral", "collateral_min", - "meta" - ] + "meta", + ], ) Trade = generate_labeler_serializer( name="Trade", klass=dataclasses.Trade, labels=[ - "id", - "symbol", - "mts_create", - "order_id", - "exec_amount", - "exec_price", - "order_type", - "order_price", - "maker", - "fee", + "id", + "symbol", + "mts_create", + "order_id", + "exec_amount", + "exec_price", + "order_type", + "order_price", + "maker", + "fee", "fee_currency", - "cid" - ] + "cid", + ], ) FundingTrade = generate_labeler_serializer( name="FundingTrade", klass=dataclasses.FundingTrade, - labels=[ - "id", - "currency", - "mts_create", - "offer_id", - "amount", - "rate", - "period" - ] + labels=["id", "currency", "mts_create", "offer_id", "amount", "rate", "period"], ) OrderTrade = generate_labeler_serializer( @@ -556,8 +522,8 @@ "maker", "fee", "fee_currency", - "cid" - ] + "cid", + ], ) Ledger = generate_labeler_serializer( @@ -572,8 +538,8 @@ "amount", "balance", "_PLACEHOLDER", - "description" - ] + "description", + ], ) FundingOffer = generate_labeler_serializer( @@ -600,8 +566,8 @@ "hidden", "_PLACEHOLDER", "renew", - "_PLACEHOLDER" - ] + "_PLACEHOLDER", + ], ) FundingCredit = generate_labeler_serializer( @@ -629,8 +595,8 @@ "renew", "_PLACEHOLDER", "no_close", - "position_pair" - ] + "position_pair", + ], ) FundingLoan = generate_labeler_serializer( @@ -657,44 +623,34 @@ "_PLACEHOLDER", "renew", "_PLACEHOLDER", - "no_close" - ] + "no_close", + ], ) FundingAutoRenew = generate_labeler_serializer( name="FundingAutoRenew", klass=dataclasses.FundingAutoRenew, - labels=[ - "currency", - "period", - "rate", - "threshold" - ] + labels=["currency", "period", "rate", "threshold"], ) FundingInfo = generate_labeler_serializer( name="FundingInfo", klass=dataclasses.FundingInfo, - labels=[ - "yield_loan", - "yield_lend", - "duration_loan", - "duration_lend" - ] + labels=["yield_loan", "yield_lend", "duration_loan", "duration_lend"], ) Wallet = generate_labeler_serializer( name="Wallet", klass=dataclasses.Wallet, labels=[ - "wallet_type", - "currency", - "balance", + "wallet_type", + "currency", + "balance", "unsettled_interest", "available_balance", "last_change", - "trade_details" - ] + "trade_details", + ], ) Transfer = generate_labeler_serializer( @@ -708,8 +664,8 @@ "currency", "currency_to", "_PLACEHOLDER", - "amount" - ] + "amount", + ], ) Withdrawal = generate_labeler_serializer( @@ -724,8 +680,8 @@ "amount", "_PLACEHOLDER", "_PLACEHOLDER", - "withdrawal_fee" - ] + "withdrawal_fee", + ], ) DepositAddress = generate_labeler_serializer( @@ -737,20 +693,14 @@ "currency_code", "_PLACEHOLDER", "address", - "pool_address" - ] + "pool_address", + ], ) LightningNetworkInvoice = generate_labeler_serializer( name="LightningNetworkInvoice", klass=dataclasses.LightningNetworkInvoice, - labels=[ - "invoice_hash", - "invoice", - "_PLACEHOLDER", - "_PLACEHOLDER", - "amount" - ] + labels=["invoice_hash", "invoice", "_PLACEHOLDER", "_PLACEHOLDER", "amount"], ) Movement = generate_labeler_serializer( @@ -778,8 +728,8 @@ "_PLACEHOLDER", "_PLACEHOLDER", "transaction_id", - "withdraw_transaction_note" - ] + "withdraw_transaction_note", + ], ) SymbolMarginInfo = generate_labeler_serializer( @@ -791,22 +741,15 @@ "tradable_balance", "gross_balance", "buy", - "sell" + "sell", ], - - flat=True + flat=True, ) BaseMarginInfo = generate_labeler_serializer( name="BaseMarginInfo", klass=dataclasses.BaseMarginInfo, - labels=[ - "user_pl", - "user_swaps", - "margin_balance", - "margin_net", - "margin_min" - ] + labels=["user_pl", "user_swaps", "margin_balance", "margin_net", "margin_min"], ) PositionClaim = generate_labeler_serializer( @@ -832,8 +775,8 @@ "_PLACEHOLDER", "collateral", "min_collateral", - "meta" - ] + "meta", + ], ) PositionIncreaseInfo = generate_labeler_serializer( @@ -857,21 +800,15 @@ "funding_value", "funding_required", "funding_value_currency", - "funding_required_currency" + "funding_required_currency", ], - - flat=True + flat=True, ) PositionIncrease = generate_labeler_serializer( name="PositionIncrease", klass=dataclasses.PositionIncrease, - labels=[ - "symbol", - "_PLACEHOLDER", - "amount", - "base_price" - ] + labels=["symbol", "_PLACEHOLDER", "amount", "base_price"], ) PositionHistory = generate_labeler_serializer( @@ -891,8 +828,8 @@ "_PLACEHOLDER", "position_id", "mts_create", - "mts_update" - ] + "mts_update", + ], ) PositionSnapshot = generate_labeler_serializer( @@ -912,8 +849,8 @@ "_PLACEHOLDER", "position_id", "mts_create", - "mts_update" - ] + "mts_update", + ], ) PositionAudit = generate_labeler_serializer( @@ -939,25 +876,20 @@ "_PLACEHOLDER", "collateral", "collateral_min", - "meta" - ] + "meta", + ], ) DerivativePositionCollateral = generate_labeler_serializer( name="DerivativePositionCollateral", klass=dataclasses.DerivativePositionCollateral, - labels=[ - "status" - ] + labels=["status"], ) DerivativePositionCollateralLimits = generate_labeler_serializer( name="DerivativePositionCollateralLimits", klass=dataclasses.DerivativePositionCollateralLimits, - labels=[ - "min_collateral", - "max_collateral" - ] + labels=["min_collateral", "max_collateral"], ) -#endregion +# endregion diff --git a/bfxapi/websocket/_client/bfx_websocket_bucket.py b/bfxapi/websocket/_client/bfx_websocket_bucket.py index 182a4e0..5980440 100644 --- a/bfxapi/websocket/_client/bfx_websocket_bucket.py +++ b/bfxapi/websocket/_client/bfx_websocket_bucket.py @@ -13,9 +13,10 @@ _CHECKSUM_FLAG_VALUE = 131_072 + def _strip(message: Dict[str, Any], keys: List[str]) -> Dict[str, Any]: - return { key: value for key, value in message.items() \ - if not key in keys } + return {key: value for key, value in message.items() if not key in keys} + class BfxWebSocketBucket(Connection): __MAXIMUM_SUBSCRIPTIONS_AMOUNT = 25 @@ -24,28 +25,26 @@ def __init__(self, host: str, event_emitter: EventEmitter) -> None: super().__init__(host) self.__event_emitter = event_emitter - self.__pendings: List[Dict[str, Any]] = [ ] - self.__subscriptions: Dict[int, Subscription] = { } + self.__pendings: List[Dict[str, Any]] = [] + self.__subscriptions: Dict[int, Subscription] = {} self.__condition = asyncio.locks.Condition() - self.__handler = PublicChannelsHandler( \ - event_emitter=self.__event_emitter) + self.__handler = PublicChannelsHandler(event_emitter=self.__event_emitter) @property def count(self) -> int: - return len(self.__pendings) + \ - len(self.__subscriptions) + return len(self.__pendings) + len(self.__subscriptions) @property def is_full(self) -> bool: - return self.count == \ - BfxWebSocketBucket.__MAXIMUM_SUBSCRIPTIONS_AMOUNT + return self.count == BfxWebSocketBucket.__MAXIMUM_SUBSCRIPTIONS_AMOUNT @property def ids(self) -> List[str]: - return [ pending["subId"] for pending in self.__pendings ] + \ - [ subscription["sub_id"] for subscription in self.__subscriptions.values() ] + return [pending["subId"] for pending in self.__pendings] + [ + subscription["sub_id"] for subscription in self.__subscriptions.values() + ] async def start(self) -> None: async with websockets.client.connect(self._host) as websocket: @@ -64,20 +63,25 @@ async def start(self) -> None: self.__on_subscribed(message) if isinstance(message, list): - if (chan_id := cast(int, message[0])) and \ - (subscription := self.__subscriptions.get(chan_id)) and \ - (message[1] != Connection._HEARTBEAT): + if ( + (chan_id := cast(int, message[0])) + and (subscription := self.__subscriptions.get(chan_id)) + and (message[1] != Connection._HEARTBEAT) + ): self.__handler.handle(subscription, message[1:]) def __on_subscribed(self, message: Dict[str, Any]) -> None: chan_id = cast(int, message["chan_id"]) - subscription = cast(Subscription, _strip(message, \ - keys=["chan_id", "event", "pair", "currency"])) + subscription = cast( + Subscription, _strip(message, keys=["chan_id", "event", "pair", "currency"]) + ) - self.__pendings = [ pending \ - for pending in self.__pendings \ - if pending["subId"] != message["sub_id"] ] + self.__pendings = [ + pending + for pending in self.__pendings + if pending["subId"] != message["sub_id"] + ] self.__subscriptions[chan_id] = subscription @@ -85,47 +89,43 @@ def __on_subscribed(self, message: Dict[str, Any]) -> None: async def __recover_state(self) -> None: for pending in self.__pendings: - await self._websocket.send(message = \ - json.dumps(pending)) + await self._websocket.send(message=json.dumps(pending)) for chan_id in list(self.__subscriptions.keys()): subscription = self.__subscriptions.pop(chan_id) await self.subscribe(**subscription) - await self.__set_config([ _CHECKSUM_FLAG_VALUE ]) + await self.__set_config([_CHECKSUM_FLAG_VALUE]) async def __set_config(self, flags: List[int]) -> None: - await self._websocket.send(json.dumps( \ - { "event": "conf", "flags": sum(flags) })) + await self._websocket.send(json.dumps({"event": "conf", "flags": sum(flags)})) @Connection._require_websocket_connection - async def subscribe(self, - channel: str, - sub_id: Optional[str] = None, - **kwargs: Any) -> None: - subscription: Dict[str, Any] = \ - { **kwargs, "event": "subscribe", "channel": channel } + async def subscribe( + self, channel: str, sub_id: Optional[str] = None, **kwargs: Any + ) -> None: + subscription: Dict[str, Any] = { + **kwargs, + "event": "subscribe", + "channel": channel, + } subscription["subId"] = sub_id or str(uuid.uuid4()) self.__pendings.append(subscription) - await self._websocket.send(message = \ - json.dumps(subscription)) + await self._websocket.send(message=json.dumps(subscription)) @Connection._require_websocket_connection async def unsubscribe(self, sub_id: str) -> None: for chan_id, subscription in list(self.__subscriptions.items()): if subscription["sub_id"] == sub_id: - unsubscription = { - "event": "unsubscribe", - "chanId": chan_id } + unsubscription = {"event": "unsubscribe", "chanId": chan_id} del self.__subscriptions[chan_id] - await self._websocket.send(message = \ - json.dumps(unsubscription)) + await self._websocket.send(message=json.dumps(unsubscription)) @Connection._require_websocket_connection async def resubscribe(self, sub_id: str) -> None: @@ -148,5 +148,4 @@ def has(self, sub_id: str) -> bool: async def wait(self) -> None: async with self.__condition: - await self.__condition \ - .wait_for(lambda: self.open) + await self.__condition.wait_for(lambda: self.open) diff --git a/bfxapi/websocket/_client/bfx_websocket_client.py b/bfxapi/websocket/_client/bfx_websocket_client.py index 7e1eefd..ab10a94 100644 --- a/bfxapi/websocket/_client/bfx_websocket_client.py +++ b/bfxapi/websocket/_client/bfx_websocket_client.py @@ -28,14 +28,17 @@ from .bfx_websocket_bucket import BfxWebSocketBucket from .bfx_websocket_inputs import BfxWebSocketInputs -_Credentials = TypedDict("_Credentials", \ - { "api_key": str, "api_secret": str, "filters": Optional[List[str]] }) +_Credentials = TypedDict( + "_Credentials", {"api_key": str, "api_secret": str, "filters": Optional[List[str]]} +) -_Reconnection = TypedDict("_Reconnection", - { "attempts": int, "reason": str, "timestamp": datetime }) +_Reconnection = TypedDict( + "_Reconnection", {"attempts": int, "reason": str, "timestamp": datetime} +) _DEFAULT_LOGGER = Logger("bfxapi.websocket._client", level=0) + class _Delay: __BACKOFF_MIN = 1.92 @@ -54,59 +57,61 @@ def next(self) -> float: return _backoff_delay def peek(self) -> float: - return (self.__backoff_delay == _Delay.__BACKOFF_MIN) \ - and self.__initial_delay or self.__backoff_delay + return ( + (self.__backoff_delay == _Delay.__BACKOFF_MIN) + and self.__initial_delay + or self.__backoff_delay + ) def reset(self) -> None: self.__backoff_delay = _Delay.__BACKOFF_MIN -#pylint: disable-next=too-many-instance-attributes + +# pylint: disable-next=too-many-instance-attributes class BfxWebSocketClient(Connection): - def __init__(self, - host: str, - *, - credentials: Optional[_Credentials] = None, - timeout: Optional[int] = 60 * 15, - logger: Logger = _DEFAULT_LOGGER) -> None: + def __init__( + self, + host: str, + *, + credentials: Optional[_Credentials] = None, + timeout: Optional[int] = 60 * 15, + logger: Logger = _DEFAULT_LOGGER, + ) -> None: super().__init__(host) - self.__credentials, self.__timeout, self.__logger = \ - credentials, \ - timeout, \ - logger + self.__credentials, self.__timeout, self.__logger = credentials, timeout, logger - self.__buckets: Dict[BfxWebSocketBucket, Optional[Task]] = { } + self.__buckets: Dict[BfxWebSocketBucket, Optional[Task]] = {} self.__reconnection: Optional[_Reconnection] = None self.__event_emitter = BfxEventEmitter(loop=None) - self.__handler = AuthEventsHandler( \ - event_emitter=self.__event_emitter) + self.__handler = AuthEventsHandler(event_emitter=self.__event_emitter) - self.__inputs = BfxWebSocketInputs( \ - handle_websocket_input=self.__handle_websocket_input) + self.__inputs = BfxWebSocketInputs( + handle_websocket_input=self.__handle_websocket_input + ) @self.__event_emitter.listens_to("error") def error(exception: Exception) -> None: header = f"{type(exception).__name__}: {str(exception)}" - stack_trace = traceback.format_exception( \ - type(exception), exception, exception.__traceback__) + stack_trace = traceback.format_exception( + type(exception), exception, exception.__traceback__ + ) - #pylint: disable-next=logging-not-lazy - self.__logger.critical(header + "\n" + \ - str().join(stack_trace)[:-1]) + # pylint: disable-next=logging-not-lazy + self.__logger.critical(header + "\n" + str().join(stack_trace)[:-1]) @property def inputs(self) -> BfxWebSocketInputs: return self.__inputs def run(self) -> None: - return asyncio.get_event_loop() \ - .run_until_complete(self.start()) + return asyncio.get_event_loop().run_until_complete(self.start()) - #pylint: disable-next=too-many-branches + # pylint: disable-next=too-many-branches async def start(self) -> None: _delay = _Delay(backoff_factor=1.618) @@ -119,19 +124,20 @@ def _on_timeout(): while True: if self.__reconnection: - _sleep = asyncio.create_task( \ - asyncio.sleep(int(_delay.next()))) + _sleep = asyncio.create_task(asyncio.sleep(int(_delay.next()))) try: await _sleep except asyncio.CancelledError: - raise ReconnectionTimeoutError("Connection has been offline for too long " \ - f"without being able to reconnect (timeout: {self.__timeout}s).") \ - from None + raise ReconnectionTimeoutError( + "Connection has been offline for too long " + f"without being able to reconnect (timeout: {self.__timeout}s)." + ) from None try: await self.__connect() except (ConnectionClosedError, InvalidStatusCode, gaierror) as error: + async def _cancel(task: Task) -> None: task.cancel() @@ -150,69 +156,87 @@ async def _cancel(task: Task) -> None: await _cancel(task) - if isinstance(error, ConnectionClosedError) and error.code in (1006, 1012): + if isinstance(error, ConnectionClosedError) and error.code in ( + 1006, + 1012, + ): if error.code == 1006: self.__logger.error("Connection lost: trying to reconnect...") if error.code == 1012: - self.__logger.warning("WSS server is restarting: all " \ - "clients need to reconnect (server sent 20051).") + self.__logger.warning( + "WSS server is restarting: all " + "clients need to reconnect (server sent 20051)." + ) if self.__timeout: - asyncio.get_event_loop().call_later( - self.__timeout, _on_timeout) + asyncio.get_event_loop().call_later(self.__timeout, _on_timeout) - self.__reconnection = \ - { "attempts": 1, "reason": error.reason, "timestamp": datetime.now() } + self.__reconnection = { + "attempts": 1, + "reason": error.reason, + "timestamp": datetime.now(), + } self._authentication = False _delay.reset() - elif ((isinstance(error, InvalidStatusCode) and error.status_code == 408) or \ - isinstance(error, gaierror)) and self.__reconnection: - #pylint: disable-next=logging-fstring-interpolation - self.__logger.warning("Reconnection attempt unsuccessful (no." \ - f"{self.__reconnection['attempts']}): next attempt in " \ - f"~{int(_delay.peek())}.0s.") - - #pylint: disable-next=logging-fstring-interpolation - self.__logger.info(f"The client has been offline for " \ - f"{datetime.now() - self.__reconnection['timestamp']}.") + elif ( + (isinstance(error, InvalidStatusCode) and error.status_code == 408) + or isinstance(error, gaierror) + ) and self.__reconnection: + # pylint: disable-next=logging-fstring-interpolation + self.__logger.warning( + "Reconnection attempt unsuccessful (no." + f"{self.__reconnection['attempts']}): next attempt in " + f"~{int(_delay.peek())}.0s." + ) + + # pylint: disable-next=logging-fstring-interpolation + self.__logger.info( + f"The client has been offline for " + f"{datetime.now() - self.__reconnection['timestamp']}." + ) self.__reconnection["attempts"] += 1 else: raise error if not self.__reconnection: - self.__event_emitter.emit("disconnected", - self._websocket.close_code, \ - self._websocket.close_reason) + self.__event_emitter.emit( + "disconnected", + self._websocket.close_code, + self._websocket.close_reason, + ) break async def __connect(self) -> None: async with websockets.client.connect(self._host) as websocket: if self.__reconnection: - #pylint: disable-next=logging-fstring-interpolation - self.__logger.warning("Reconnection attempt successful (no." \ - f"{self.__reconnection['attempts']}): recovering " \ - "connection state...") + # pylint: disable-next=logging-fstring-interpolation + self.__logger.warning( + "Reconnection attempt successful (no." + f"{self.__reconnection['attempts']}): recovering " + "connection state..." + ) self.__reconnection = None self._websocket = websocket for bucket in self.__buckets: - self.__buckets[bucket] = \ - asyncio.create_task(bucket.start()) + self.__buckets[bucket] = asyncio.create_task(bucket.start()) - if len(self.__buckets) == 0 or \ - (await asyncio.gather(*[bucket.wait() for bucket in self.__buckets])): + if len(self.__buckets) == 0 or ( + await asyncio.gather(*[bucket.wait() for bucket in self.__buckets]) + ): self.__event_emitter.emit("open") if self.__credentials: - authentication = Connection. \ - _get_authentication_message(**self.__credentials) + authentication = Connection._get_authentication_message( + **self.__credentials + ) await self._websocket.send(authentication) @@ -222,61 +246,64 @@ async def __connect(self) -> None: if isinstance(message, dict): if message["event"] == "info" and "version" in message: if message["version"] != 2: - raise VersionMismatchError("Mismatch between the client and the server version: " + \ - "please update bitfinex-api-py to the latest version to resolve this error " + \ - f"(client version: 2, server version: {message['version']}).") + raise VersionMismatchError( + "Mismatch between the client and the server version: " + + "please update bitfinex-api-py to the latest version to resolve this error " + + f"(client version: 2, server version: {message['version']})." + ) elif message["event"] == "info" and message["code"] == 20051: - rcvd = websockets.frames.Close( \ - 1012, "Stop/Restart WebSocket Server (please reconnect).") + rcvd = websockets.frames.Close( + 1012, "Stop/Restart WebSocket Server (please reconnect)." + ) raise ConnectionClosedError(rcvd=rcvd, sent=None) elif message["event"] == "auth": if message["status"] != "OK": - raise InvalidCredentialError("Can't authenticate " + \ - "with given API-KEY and API-SECRET.") + raise InvalidCredentialError( + "Can't authenticate " + + "with given API-KEY and API-SECRET." + ) self.__event_emitter.emit("authenticated", message) self._authentication = True - if isinstance(message, list) and \ - message[0] == 0 and message[1] != Connection._HEARTBEAT: + if ( + isinstance(message, list) + and message[0] == 0 + and message[1] != Connection._HEARTBEAT + ): self.__handler.handle(message[1], message[2]) async def __new_bucket(self) -> BfxWebSocketBucket: - bucket = BfxWebSocketBucket( \ - self._host, self.__event_emitter) + bucket = BfxWebSocketBucket(self._host, self.__event_emitter) - self.__buckets[bucket] = asyncio \ - .create_task(bucket.start()) + self.__buckets[bucket] = asyncio.create_task(bucket.start()) await bucket.wait() return bucket @Connection._require_websocket_connection - async def subscribe(self, - channel: str, - sub_id: Optional[str] = None, - **kwargs: Any) -> None: + async def subscribe( + self, channel: str, sub_id: Optional[str] = None, **kwargs: Any + ) -> None: if not channel in ["ticker", "trades", "book", "candles", "status"]: - raise UnknownChannelError("Available channels are: " + \ - "ticker, trades, book, candles and status.") + raise UnknownChannelError( + "Available channels are: " + "ticker, trades, book, candles and status." + ) for bucket in self.__buckets: if sub_id in bucket.ids: - raise SubIdError("sub_id must be " + \ - "unique for all subscriptions.") + raise SubIdError("sub_id must be " + "unique for all subscriptions.") for bucket in self.__buckets: if not bucket.is_full: - return await bucket.subscribe( \ - channel, sub_id, **kwargs) + return await bucket.subscribe(channel, sub_id, **kwargs) bucket = await self.__new_bucket() - return await bucket.subscribe( \ - channel, sub_id, **kwargs) + return await bucket.subscribe(channel, sub_id, **kwargs) @Connection._require_websocket_connection async def unsubscribe(self, sub_id: str) -> None: @@ -286,13 +313,13 @@ async def unsubscribe(self, sub_id: str) -> None: if bucket.count == 1: del self.__buckets[bucket] - return await bucket.close( \ - code=1001, reason="Going Away") + return await bucket.close(code=1001, reason="Going Away") return await bucket.unsubscribe(sub_id) - raise UnknownSubscriptionError("Unable to find " + \ - f"a subscription with sub_id <{sub_id}>.") + raise UnknownSubscriptionError( + "Unable to find " + f"a subscription with sub_id <{sub_id}>." + ) @Connection._require_websocket_connection async def resubscribe(self, sub_id: str) -> None: @@ -300,8 +327,9 @@ async def resubscribe(self, sub_id: str) -> None: if bucket.has(sub_id): return await bucket.resubscribe(sub_id) - raise UnknownSubscriptionError("Unable to find " + \ - f"a subscription with sub_id <{sub_id}>.") + raise UnknownSubscriptionError( + "Unable to find " + f"a subscription with sub_id <{sub_id}>." + ) @Connection._require_websocket_connection async def close(self, code: int = 1000, reason: str = str()) -> None: @@ -309,22 +337,21 @@ async def close(self, code: int = 1000, reason: str = str()) -> None: await bucket.close(code=code, reason=reason) if self._websocket.open: - await self._websocket.close( \ - code=code, reason=reason) + await self._websocket.close(code=code, reason=reason) @Connection._require_websocket_authentication - async def notify(self, - info: Any, - message_id: Optional[int] = None, - **kwargs: Any) -> None: + async def notify( + self, info: Any, message_id: Optional[int] = None, **kwargs: Any + ) -> None: await self._websocket.send( - json.dumps([ 0, "n", message_id, - { "type": "ucm-test", "info": info, **kwargs } ])) + json.dumps( + [0, "n", message_id, {"type": "ucm-test", "info": info, **kwargs}] + ) + ) @Connection._require_websocket_authentication async def __handle_websocket_input(self, event: str, data: Any) -> None: - await self._websocket.send(json.dumps( \ - [ 0, event, None, data], cls=JSONEncoder)) + await self._websocket.send(json.dumps([0, event, None, data], cls=JSONEncoder)) - def on(self, event, callback = None): + def on(self, event, callback=None): return self.__event_emitter.on(event, callback) diff --git a/bfxapi/websocket/_client/bfx_websocket_inputs.py b/bfxapi/websocket/_client/bfx_websocket_inputs.py index a724e53..14925ce 100644 --- a/bfxapi/websocket/_client/bfx_websocket_inputs.py +++ b/bfxapi/websocket/_client/bfx_websocket_inputs.py @@ -3,89 +3,127 @@ _Handler = Callable[[str, Any], Awaitable[None]] + class BfxWebSocketInputs: def __init__(self, handle_websocket_input: _Handler) -> None: self.__handle_websocket_input = handle_websocket_input - async def submit_order(self, - type: str, - symbol: str, - amount: Union[str, float, Decimal], - price: Union[str, float, Decimal], - *, - lev: Optional[int] = None, - price_trailing: Optional[Union[str, float, Decimal]] = None, - price_aux_limit: Optional[Union[str, float, Decimal]] = None, - price_oco_stop: Optional[Union[str, float, Decimal]] = None, - gid: Optional[int] = None, - cid: Optional[int] = None, - flags: Optional[int] = None, - tif: Optional[str] = None) -> None: - await self.__handle_websocket_input("on", { - "type": type, "symbol": symbol, "amount": amount, - "price": price, "lev": lev, "price_trailing": price_trailing, - "price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop, "gid": gid, - "cid": cid, "flags": flags, "tif": tif - }) + async def submit_order( + self, + type: str, + symbol: str, + amount: Union[str, float, Decimal], + price: Union[str, float, Decimal], + *, + lev: Optional[int] = None, + price_trailing: Optional[Union[str, float, Decimal]] = None, + price_aux_limit: Optional[Union[str, float, Decimal]] = None, + price_oco_stop: Optional[Union[str, float, Decimal]] = None, + gid: Optional[int] = None, + cid: Optional[int] = None, + flags: Optional[int] = None, + tif: Optional[str] = None, + ) -> None: + await self.__handle_websocket_input( + "on", + { + "type": type, + "symbol": symbol, + "amount": amount, + "price": price, + "lev": lev, + "price_trailing": price_trailing, + "price_aux_limit": price_aux_limit, + "price_oco_stop": price_oco_stop, + "gid": gid, + "cid": cid, + "flags": flags, + "tif": tif, + }, + ) - async def update_order(self, - id: int, - *, - amount: Optional[Union[str, float, Decimal]] = None, - price: Optional[Union[str, float, Decimal]] = None, - cid: Optional[int] = None, - cid_date: Optional[str] = None, - gid: Optional[int] = None, - flags: Optional[int] = None, - lev: Optional[int] = None, - delta: Optional[Union[str, float, Decimal]] = None, - price_aux_limit: Optional[Union[str, float, Decimal]] = None, - price_trailing: Optional[Union[str, float, Decimal]] = None, - tif: Optional[str] = None) -> None: - await self.__handle_websocket_input("ou", { - "id": id, "amount": amount, "price": price, - "cid": cid, "cid_date": cid_date, "gid": gid, - "flags": flags, "lev": lev, "delta": delta, - "price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif - }) + async def update_order( + self, + id: int, + *, + amount: Optional[Union[str, float, Decimal]] = None, + price: Optional[Union[str, float, Decimal]] = None, + cid: Optional[int] = None, + cid_date: Optional[str] = None, + gid: Optional[int] = None, + flags: Optional[int] = None, + lev: Optional[int] = None, + delta: Optional[Union[str, float, Decimal]] = None, + price_aux_limit: Optional[Union[str, float, Decimal]] = None, + price_trailing: Optional[Union[str, float, Decimal]] = None, + tif: Optional[str] = None, + ) -> None: + await self.__handle_websocket_input( + "ou", + { + "id": id, + "amount": amount, + "price": price, + "cid": cid, + "cid_date": cid_date, + "gid": gid, + "flags": flags, + "lev": lev, + "delta": delta, + "price_aux_limit": price_aux_limit, + "price_trailing": price_trailing, + "tif": tif, + }, + ) - async def cancel_order(self, - *, - id: Optional[int] = None, - cid: Optional[int] = None, - cid_date: Optional[str] = None) -> None: - await self.__handle_websocket_input("oc", { - "id": id, "cid": cid, "cid_date": cid_date - }) + async def cancel_order( + self, + *, + id: Optional[int] = None, + cid: Optional[int] = None, + cid_date: Optional[str] = None, + ) -> None: + await self.__handle_websocket_input( + "oc", {"id": id, "cid": cid, "cid_date": cid_date} + ) - async def cancel_order_multi(self, - *, - id: Optional[List[int]] = None, - cid: Optional[List[Tuple[int, str]]] = None, - gid: Optional[List[int]] = None, - all: Optional[bool] = None) -> None: - await self.__handle_websocket_input("oc_multi", { - "id": id, "cid": cid, "gid": gid, - "all": all - }) + async def cancel_order_multi( + self, + *, + id: Optional[List[int]] = None, + cid: Optional[List[Tuple[int, str]]] = None, + gid: Optional[List[int]] = None, + all: Optional[bool] = None, + ) -> None: + await self.__handle_websocket_input( + "oc_multi", {"id": id, "cid": cid, "gid": gid, "all": all} + ) - #pylint: disable-next=too-many-arguments - async def submit_funding_offer(self, - type: str, - symbol: str, - amount: Union[str, float, Decimal], - rate: Union[str, float, Decimal], - period: int, - *, - flags: Optional[int] = None) -> None: - await self.__handle_websocket_input("fon", { - "type": type, "symbol": symbol, "amount": amount, - "rate": rate, "period": period, "flags": flags - }) + # pylint: disable-next=too-many-arguments + async def submit_funding_offer( + self, + type: str, + symbol: str, + amount: Union[str, float, Decimal], + rate: Union[str, float, Decimal], + period: int, + *, + flags: Optional[int] = None, + ) -> None: + await self.__handle_websocket_input( + "fon", + { + "type": type, + "symbol": symbol, + "amount": amount, + "rate": rate, + "period": period, + "flags": flags, + }, + ) async def cancel_funding_offer(self, id: int) -> None: - await self.__handle_websocket_input("foc", { "id": id }) + await self.__handle_websocket_input("foc", {"id": id}) async def calc(self, *args: str) -> None: - await self.__handle_websocket_input("calc", - list(map(lambda arg: [arg], args))) + await self.__handle_websocket_input("calc", list(map(lambda arg: [arg], args))) diff --git a/bfxapi/websocket/_connection.py b/bfxapi/websocket/_connection.py index 286435a..73c7dd9 100644 --- a/bfxapi/websocket/_connection.py +++ b/bfxapi/websocket/_connection.py @@ -18,6 +18,7 @@ _P = ParamSpec("_P") + class Connection(ABC): _HEARTBEAT = "hb" @@ -30,8 +31,7 @@ def __init__(self, host: str) -> None: @property def open(self) -> bool: - return self.__protocol is not None and \ - self.__protocol.open + return self.__protocol is not None and self.__protocol.open @property def authentication(self) -> bool: @@ -46,12 +46,11 @@ def _websocket(self, protocol: WebSocketClientProtocol) -> None: self.__protocol = protocol @abstractmethod - async def start(self) -> None: - ... + async def start(self) -> None: ... @staticmethod def _require_websocket_connection( - function: Callable[Concatenate[_S, _P], Awaitable[_R]] + function: Callable[Concatenate[_S, _P], Awaitable[_R]], ) -> Callable[Concatenate[_S, _P], Awaitable[_R]]: @wraps(function) async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R: @@ -64,13 +63,15 @@ async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R: @staticmethod def _require_websocket_authentication( - function: Callable[Concatenate[_S, _P], Awaitable[_R]] + function: Callable[Concatenate[_S, _P], Awaitable[_R]], ) -> Callable[Concatenate[_S, _P], Awaitable[_R]]: @wraps(function) async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R: if not self.authentication: - raise ActionRequiresAuthentication("To perform this action you need to " \ - "authenticate using your API_KEY and API_SECRET.") + raise ActionRequiresAuthentication( + "To perform this action you need to " + "authenticate using your API_KEY and API_SECRET." + ) internal = Connection._require_websocket_connection(function) @@ -80,12 +81,13 @@ async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R: @staticmethod def _get_authentication_message( - api_key: str, - api_secret: str, - filters: Optional[List[str]] = None + api_key: str, api_secret: str, filters: Optional[List[str]] = None ) -> str: - message: Dict[str, Any] = \ - { "event": "auth", "filter": filters, "apiKey": api_key } + message: Dict[str, Any] = { + "event": "auth", + "filter": filters, + "apiKey": api_key, + } message["authNonce"] = round(datetime.now().timestamp() * 1_000_000) @@ -94,7 +96,7 @@ def _get_authentication_message( auth_sig = hmac.new( key=api_secret.encode("utf8"), msg=message["authPayload"].encode("utf8"), - digestmod=hashlib.sha384 + digestmod=hashlib.sha384, ) message["authSig"] = auth_sig.hexdigest() diff --git a/bfxapi/websocket/_event_emitter/bfx_event_emitter.py b/bfxapi/websocket/_event_emitter/bfx_event_emitter.py index 8b9a25b..89931ba 100644 --- a/bfxapi/websocket/_event_emitter/bfx_event_emitter.py +++ b/bfxapi/websocket/_event_emitter/bfx_event_emitter.py @@ -9,57 +9,86 @@ _Handler = TypeVar("_Handler", bound=Callable[..., None]) _ONCE_PER_CONNECTION = [ - "open", "authenticated", "order_snapshot", - "position_snapshot", "funding_offer_snapshot", "funding_credit_snapshot", - "funding_loan_snapshot", "wallet_snapshot" + "open", + "authenticated", + "order_snapshot", + "position_snapshot", + "funding_offer_snapshot", + "funding_credit_snapshot", + "funding_loan_snapshot", + "wallet_snapshot", ] _ONCE_PER_SUBSCRIPTION = [ - "subscribed", "t_trades_snapshot", "f_trades_snapshot", - "t_book_snapshot", "f_book_snapshot", "t_raw_book_snapshot", - "f_raw_book_snapshot", "candles_snapshot" + "subscribed", + "t_trades_snapshot", + "f_trades_snapshot", + "t_book_snapshot", + "f_book_snapshot", + "t_raw_book_snapshot", + "f_raw_book_snapshot", + "candles_snapshot", ] _COMMON = [ - "disconnected", "t_ticker_update", "f_ticker_update", - "t_trade_execution", "t_trade_execution_update", "f_trade_execution", - "f_trade_execution_update", "t_book_update", "f_book_update", - "t_raw_book_update", "f_raw_book_update", "candles_update", - "derivatives_status_update", "liquidation_feed_update", "checksum", - "order_new", "order_update", "order_cancel", - "position_new", "position_update", "position_close", - "funding_offer_new", "funding_offer_update", "funding_offer_cancel", - "funding_credit_new", "funding_credit_update", "funding_credit_close", - "funding_loan_new", "funding_loan_update", "funding_loan_close", - "trade_execution", "trade_execution_update", "wallet_update", - "notification", "on-req-notification", "ou-req-notification", - "oc-req-notification", "fon-req-notification", "foc-req-notification" + "disconnected", + "t_ticker_update", + "f_ticker_update", + "t_trade_execution", + "t_trade_execution_update", + "f_trade_execution", + "f_trade_execution_update", + "t_book_update", + "f_book_update", + "t_raw_book_update", + "f_raw_book_update", + "candles_update", + "derivatives_status_update", + "liquidation_feed_update", + "checksum", + "order_new", + "order_update", + "order_cancel", + "position_new", + "position_update", + "position_close", + "funding_offer_new", + "funding_offer_update", + "funding_offer_cancel", + "funding_credit_new", + "funding_credit_update", + "funding_credit_close", + "funding_loan_new", + "funding_loan_update", + "funding_loan_close", + "trade_execution", + "trade_execution_update", + "wallet_update", + "notification", + "on-req-notification", + "ou-req-notification", + "oc-req-notification", + "fon-req-notification", + "foc-req-notification", ] + class BfxEventEmitter(AsyncIOEventEmitter): - _EVENTS = _ONCE_PER_CONNECTION + \ - _ONCE_PER_SUBSCRIPTION + \ - _COMMON + _EVENTS = _ONCE_PER_CONNECTION + _ONCE_PER_SUBSCRIPTION + _COMMON def __init__(self, loop: Optional[AbstractEventLoop] = None) -> None: super().__init__(loop) - self._connection: List[str] = [ ] + self._connection: List[str] = [] - self._subscriptions: Dict[str, List[str]] = \ - defaultdict(lambda: [ ]) + self._subscriptions: Dict[str, List[str]] = defaultdict(lambda: []) - def emit( - self, - event: str, - *args: Any, - **kwargs: Any - ) -> bool: + def emit(self, event: str, *args: Any, **kwargs: Any) -> bool: if event in _ONCE_PER_CONNECTION: if event in self._connection: return self._has_listeners(event) - self._connection += [ event ] + self._connection += [event] if event in _ONCE_PER_SUBSCRIPTION: sub_id = args[0]["sub_id"] @@ -67,7 +96,7 @@ def emit( if event in self._subscriptions[sub_id]: return self._has_listeners(event) - self._subscriptions[sub_id] += [ event ] + self._subscriptions[sub_id] += [event] return super().emit(event, *args, **kwargs) @@ -75,8 +104,10 @@ def on( self, event: str, f: Optional[_Handler] = None ) -> Union[_Handler, Callable[[_Handler], _Handler]]: if event not in BfxEventEmitter._EVENTS: - raise UnknownEventError(f"Can't register to unknown event: <{event}> " + \ - "(to get a full list of available events see https://docs.bitfinex.com/).") + raise UnknownEventError( + f"Can't register to unknown event: <{event}> " + + "(to get a full list of available events see https://docs.bitfinex.com/)." + ) return super().on(event, f) diff --git a/bfxapi/websocket/_handlers/auth_events_handler.py b/bfxapi/websocket/_handlers/auth_events_handler.py index 6d61ddb..89d6ce8 100644 --- a/bfxapi/websocket/_handlers/auth_events_handler.py +++ b/bfxapi/websocket/_handlers/auth_events_handler.py @@ -9,14 +9,30 @@ class AuthEventsHandler: __ABBREVIATIONS = { - "os": "order_snapshot", "on": "order_new", "ou": "order_update", - "oc": "order_cancel", "ps": "position_snapshot", "pn": "position_new", - "pu": "position_update", "pc": "position_close", "te": "trade_execution", - "tu": "trade_execution_update", "fos": "funding_offer_snapshot", "fon": "funding_offer_new", - "fou": "funding_offer_update", "foc": "funding_offer_cancel", "fcs": "funding_credit_snapshot", - "fcn": "funding_credit_new", "fcu": "funding_credit_update", "fcc": "funding_credit_close", - "fls": "funding_loan_snapshot", "fln": "funding_loan_new", "flu": "funding_loan_update", - "flc": "funding_loan_close", "ws": "wallet_snapshot", "wu": "wallet_update" + "os": "order_snapshot", + "on": "order_new", + "ou": "order_update", + "oc": "order_cancel", + "ps": "position_snapshot", + "pn": "position_new", + "pu": "position_update", + "pc": "position_close", + "te": "trade_execution", + "tu": "trade_execution_update", + "fos": "funding_offer_snapshot", + "fon": "funding_offer_new", + "fou": "funding_offer_update", + "foc": "funding_offer_cancel", + "fcs": "funding_credit_snapshot", + "fcn": "funding_credit_new", + "fcu": "funding_credit_update", + "fcc": "funding_credit_close", + "fls": "funding_loan_snapshot", + "fln": "funding_loan_new", + "flu": "funding_loan_update", + "flc": "funding_loan_close", + "ws": "wallet_snapshot", + "wu": "wallet_update", } __SERIALIZERS: Dict[Tuple[str, ...], serializers._Serializer] = { @@ -26,7 +42,7 @@ class AuthEventsHandler: ("fos", "fon", "fou", "foc"): serializers.FundingOffer, ("fcs", "fcn", "fcu", "fcc"): serializers.FundingCredit, ("fls", "fln", "flu", "flc"): serializers.FundingLoan, - ("ws", "wu"): serializers.Wallet + ("ws", "wu"): serializers.Wallet, } def __init__(self, event_emitter: EventEmitter) -> None: @@ -41,7 +57,7 @@ def handle(self, abbrevation: str, stream: Any) -> None: event = AuthEventsHandler.__ABBREVIATIONS[abbrevation] if all(isinstance(sub_stream, list) for sub_stream in stream): - data = [ serializer.parse(*sub_stream) for sub_stream in stream ] + data = [serializer.parse(*sub_stream) for sub_stream in stream] else: data = serializer.parse(*stream) @@ -53,11 +69,13 @@ def __notification(self, stream: Any) -> None: serializer: _Notification = _Notification[None](serializer=None) if stream[1] in ("on-req", "ou-req", "oc-req"): - event, serializer = f"{stream[1]}-notification", \ - _Notification[Order](serializer=serializers.Order) + event, serializer = f"{stream[1]}-notification", _Notification[Order]( + serializer=serializers.Order + ) if stream[1] in ("fon-req", "foc-req"): - event, serializer = f"{stream[1]}-notification", \ - _Notification[FundingOffer](serializer=serializers.FundingOffer) + event, serializer = f"{stream[1]}-notification", _Notification[ + FundingOffer + ](serializer=serializers.FundingOffer) self.__event_emitter.emit(event, serializer.parse(*stream)) diff --git a/bfxapi/websocket/_handlers/public_channels_handler.py b/bfxapi/websocket/_handlers/public_channels_handler.py index 8101911..556c539 100644 --- a/bfxapi/websocket/_handlers/public_channels_handler.py +++ b/bfxapi/websocket/_handlers/public_channels_handler.py @@ -14,6 +14,7 @@ _CHECKSUM = "cs" + class PublicChannelsHandler: def __init__(self, event_emitter: EventEmitter) -> None: self.__event_emitter = event_emitter @@ -38,101 +39,167 @@ def handle(self, subscription: Subscription, stream: List[Any]) -> None: elif subscription["channel"] == "status": self.__status_channel_handler(cast(Status, subscription), stream) - #pylint: disable-next=inconsistent-return-statements + # pylint: disable-next=inconsistent-return-statements def __ticker_channel_handler(self, subscription: Ticker, stream: List[Any]): if subscription["symbol"].startswith("t"): - return self.__event_emitter.emit("t_ticker_update", subscription, \ - serializers.TradingPairTicker.parse(*stream[0])) + return self.__event_emitter.emit( + "t_ticker_update", + subscription, + serializers.TradingPairTicker.parse(*stream[0]), + ) if subscription["symbol"].startswith("f"): - return self.__event_emitter.emit("f_ticker_update", subscription, \ - serializers.FundingCurrencyTicker.parse(*stream[0])) + return self.__event_emitter.emit( + "f_ticker_update", + subscription, + serializers.FundingCurrencyTicker.parse(*stream[0]), + ) - #pylint: disable-next=inconsistent-return-statements + # pylint: disable-next=inconsistent-return-statements def __trades_channel_handler(self, subscription: Trades, stream: List[Any]): - if (event := stream[0]) and event in [ "te", "tu", "fte", "ftu" ]: - events = { "te": "t_trade_execution", "tu": "t_trade_execution_update", \ - "fte": "f_trade_execution", "ftu": "f_trade_execution_update" } + if (event := stream[0]) and event in ["te", "tu", "fte", "ftu"]: + events = { + "te": "t_trade_execution", + "tu": "t_trade_execution_update", + "fte": "f_trade_execution", + "ftu": "f_trade_execution_update", + } if subscription["symbol"].startswith("t"): - return self.__event_emitter.emit(events[event], subscription, \ - serializers.TradingPairTrade.parse(*stream[1])) + return self.__event_emitter.emit( + events[event], + subscription, + serializers.TradingPairTrade.parse(*stream[1]), + ) if subscription["symbol"].startswith("f"): - return self.__event_emitter.emit(events[event], subscription, \ - serializers.FundingCurrencyTrade.parse(*stream[1])) + return self.__event_emitter.emit( + events[event], + subscription, + serializers.FundingCurrencyTrade.parse(*stream[1]), + ) if subscription["symbol"].startswith("t"): - return self.__event_emitter.emit("t_trades_snapshot", subscription, \ - [ serializers.TradingPairTrade.parse(*sub_stream) \ - for sub_stream in stream[0] ]) + return self.__event_emitter.emit( + "t_trades_snapshot", + subscription, + [ + serializers.TradingPairTrade.parse(*sub_stream) + for sub_stream in stream[0] + ], + ) if subscription["symbol"].startswith("f"): - return self.__event_emitter.emit("f_trades_snapshot", subscription, \ - [ serializers.FundingCurrencyTrade.parse(*sub_stream) \ - for sub_stream in stream[0] ]) - - #pylint: disable-next=inconsistent-return-statements + return self.__event_emitter.emit( + "f_trades_snapshot", + subscription, + [ + serializers.FundingCurrencyTrade.parse(*sub_stream) + for sub_stream in stream[0] + ], + ) + + # pylint: disable-next=inconsistent-return-statements def __book_channel_handler(self, subscription: Book, stream: List[Any]): if subscription["symbol"].startswith("t"): if all(isinstance(sub_stream, list) for sub_stream in stream[0]): - return self.__event_emitter.emit("t_book_snapshot", subscription, \ - [ serializers.TradingPairBook.parse(*sub_stream) \ - for sub_stream in stream[0] ]) - - return self.__event_emitter.emit("t_book_update", subscription, \ - serializers.TradingPairBook.parse(*stream[0])) + return self.__event_emitter.emit( + "t_book_snapshot", + subscription, + [ + serializers.TradingPairBook.parse(*sub_stream) + for sub_stream in stream[0] + ], + ) + + return self.__event_emitter.emit( + "t_book_update", + subscription, + serializers.TradingPairBook.parse(*stream[0]), + ) if subscription["symbol"].startswith("f"): if all(isinstance(sub_stream, list) for sub_stream in stream[0]): - return self.__event_emitter.emit("f_book_snapshot", subscription, \ - [ serializers.FundingCurrencyBook.parse(*sub_stream) \ - for sub_stream in stream[0] ]) - - return self.__event_emitter.emit("f_book_update", subscription, \ - serializers.FundingCurrencyBook.parse(*stream[0])) - - #pylint: disable-next=inconsistent-return-statements + return self.__event_emitter.emit( + "f_book_snapshot", + subscription, + [ + serializers.FundingCurrencyBook.parse(*sub_stream) + for sub_stream in stream[0] + ], + ) + + return self.__event_emitter.emit( + "f_book_update", + subscription, + serializers.FundingCurrencyBook.parse(*stream[0]), + ) + + # pylint: disable-next=inconsistent-return-statements def __raw_book_channel_handler(self, subscription: Book, stream: List[Any]): if subscription["symbol"].startswith("t"): if all(isinstance(sub_stream, list) for sub_stream in stream[0]): - return self.__event_emitter.emit("t_raw_book_snapshot", subscription, \ - [ serializers.TradingPairRawBook.parse(*sub_stream) \ - for sub_stream in stream[0] ]) - - return self.__event_emitter.emit("t_raw_book_update", subscription, \ - serializers.TradingPairRawBook.parse(*stream[0])) + return self.__event_emitter.emit( + "t_raw_book_snapshot", + subscription, + [ + serializers.TradingPairRawBook.parse(*sub_stream) + for sub_stream in stream[0] + ], + ) + + return self.__event_emitter.emit( + "t_raw_book_update", + subscription, + serializers.TradingPairRawBook.parse(*stream[0]), + ) if subscription["symbol"].startswith("f"): if all(isinstance(sub_stream, list) for sub_stream in stream[0]): - return self.__event_emitter.emit("f_raw_book_snapshot", subscription, \ - [ serializers.FundingCurrencyRawBook.parse(*sub_stream) \ - for sub_stream in stream[0] ]) - - return self.__event_emitter.emit("f_raw_book_update", subscription, \ - serializers.FundingCurrencyRawBook.parse(*stream[0])) - - #pylint: disable-next=inconsistent-return-statements + return self.__event_emitter.emit( + "f_raw_book_snapshot", + subscription, + [ + serializers.FundingCurrencyRawBook.parse(*sub_stream) + for sub_stream in stream[0] + ], + ) + + return self.__event_emitter.emit( + "f_raw_book_update", + subscription, + serializers.FundingCurrencyRawBook.parse(*stream[0]), + ) + + # pylint: disable-next=inconsistent-return-statements def __candles_channel_handler(self, subscription: Candles, stream: List[Any]): if all(isinstance(sub_stream, list) for sub_stream in stream[0]): - return self.__event_emitter.emit("candles_snapshot", subscription, \ - [ serializers.Candle.parse(*sub_stream) \ - for sub_stream in stream[0] ]) + return self.__event_emitter.emit( + "candles_snapshot", + subscription, + [serializers.Candle.parse(*sub_stream) for sub_stream in stream[0]], + ) - return self.__event_emitter.emit("candles_update", subscription, \ - serializers.Candle.parse(*stream[0])) + return self.__event_emitter.emit( + "candles_update", subscription, serializers.Candle.parse(*stream[0]) + ) - #pylint: disable-next=inconsistent-return-statements + # pylint: disable-next=inconsistent-return-statements def __status_channel_handler(self, subscription: Status, stream: List[Any]): if subscription["key"].startswith("deriv:"): - return self.__event_emitter.emit("derivatives_status_update", subscription, \ - serializers.DerivativesStatus.parse(*stream[0])) + return self.__event_emitter.emit( + "derivatives_status_update", + subscription, + serializers.DerivativesStatus.parse(*stream[0]), + ) if subscription["key"].startswith("liq:"): - return self.__event_emitter.emit("liquidation_feed_update", subscription, \ - serializers.Liquidation.parse(*stream[0][0])) + return self.__event_emitter.emit( + "liquidation_feed_update", + subscription, + serializers.Liquidation.parse(*stream[0][0]), + ) - #pylint: disable-next=inconsistent-return-statements + # pylint: disable-next=inconsistent-return-statements def __checksum_handler(self, subscription: Book, value: int): - return self.__event_emitter.emit( \ - "checksum", subscription, value & 0xFFFFFFFF) + return self.__event_emitter.emit("checksum", subscription, value & 0xFFFFFFFF) diff --git a/bfxapi/websocket/exceptions.py b/bfxapi/websocket/exceptions.py index 8bfaa82..bd276cb 100644 --- a/bfxapi/websocket/exceptions.py +++ b/bfxapi/websocket/exceptions.py @@ -4,23 +4,30 @@ class ConnectionNotOpen(BfxBaseException): pass + class ActionRequiresAuthentication(BfxBaseException): pass + class ReconnectionTimeoutError(BfxBaseException): pass + class VersionMismatchError(BfxBaseException): pass + class SubIdError(BfxBaseException): pass + class UnknownChannelError(BfxBaseException): pass + class UnknownEventError(BfxBaseException): pass + class UnknownSubscriptionError(BfxBaseException): pass diff --git a/bfxapi/websocket/subscriptions.py b/bfxapi/websocket/subscriptions.py index 0ad79a4..9f4f61f 100644 --- a/bfxapi/websocket/subscriptions.py +++ b/bfxapi/websocket/subscriptions.py @@ -4,16 +4,19 @@ Channel = Literal["ticker", "trades", "book", "candles", "status"] + class Ticker(TypedDict): channel: Literal["ticker"] sub_id: str symbol: str + class Trades(TypedDict): channel: Literal["trades"] sub_id: str symbol: str + class Book(TypedDict): channel: Literal["book"] sub_id: str @@ -22,11 +25,13 @@ class Book(TypedDict): freq: Literal["F0", "F1"] len: Literal["1", "25", "100", "250"] + class Candles(TypedDict): channel: Literal["candles"] sub_id: str key: str + class Status(TypedDict): channel: Literal["status"] sub_id: str diff --git a/examples/rest/auth/claim_position.py b/examples/rest/auth/claim_position.py index d0da7f9..6902565 100644 --- a/examples/rest/auth/claim_position.py +++ b/examples/rest/auth/claim_position.py @@ -5,10 +5,7 @@ from bfxapi import Client from bfxapi.types import Notification, PositionClaim -bfx = Client( - api_key=os.getenv("BFX_API_KEY"), - api_secret=os.getenv("BFX_API_SECRET") -) +bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET")) # Claims all active positions for position in bfx.rest.auth.get_positions(): diff --git a/examples/rest/auth/get_wallets.py b/examples/rest/auth/get_wallets.py index 67696fc..c980d24 100644 --- a/examples/rest/auth/get_wallets.py +++ b/examples/rest/auth/get_wallets.py @@ -13,10 +13,7 @@ Withdrawal, ) -bfx = Client( - api_key=os.getenv("BFX_API_KEY"), - api_secret=os.getenv("BFX_API_SECRET") -) +bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET")) # Gets all user's available wallets wallets: List[Wallet] = bfx.rest.auth.get_wallets() diff --git a/examples/rest/auth/set_derivative_position_collateral.py b/examples/rest/auth/set_derivative_position_collateral.py index 37a8002..2445fa7 100644 --- a/examples/rest/auth/set_derivative_position_collateral.py +++ b/examples/rest/auth/set_derivative_position_collateral.py @@ -8,10 +8,7 @@ DerivativePositionCollateralLimits, ) -bfx = Client( - api_key=os.getenv("BFX_API_KEY"), - api_secret=os.getenv("BFX_API_SECRET") -) +bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET")) submit_order_notification = bfx.rest.auth.submit_order( type="LIMIT", symbol="tBTCF0:USTF0", amount="0.015", price="16700", lev=10 diff --git a/examples/rest/auth/submit_funding_offer.py b/examples/rest/auth/submit_funding_offer.py index d390915..ff6fd01 100644 --- a/examples/rest/auth/submit_funding_offer.py +++ b/examples/rest/auth/submit_funding_offer.py @@ -5,10 +5,7 @@ from bfxapi import Client from bfxapi.types import FundingOffer, Notification -bfx = Client( - api_key=os.getenv("BFX_API_KEY"), - api_secret=os.getenv("BFX_API_SECRET") -) +bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET")) # Submit a new funding offer notification: Notification[FundingOffer] = bfx.rest.auth.submit_funding_offer( diff --git a/examples/rest/auth/submit_order.py b/examples/rest/auth/submit_order.py index bf4f899..ac2c8e8 100644 --- a/examples/rest/auth/submit_order.py +++ b/examples/rest/auth/submit_order.py @@ -5,10 +5,7 @@ from bfxapi import Client from bfxapi.types import Notification, Order -bfx = Client( - api_key=os.getenv("BFX_API_KEY"), - api_secret=os.getenv("BFX_API_SECRET") -) +bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET")) # Submit a new order submit_order_notification: Notification[Order] = bfx.rest.auth.submit_order( diff --git a/examples/rest/auth/toggle_keep_funding.py b/examples/rest/auth/toggle_keep_funding.py index dbacdcf..b572c5d 100644 --- a/examples/rest/auth/toggle_keep_funding.py +++ b/examples/rest/auth/toggle_keep_funding.py @@ -6,10 +6,7 @@ from bfxapi import Client from bfxapi.types import FundingLoan, Notification -bfx = Client( - api_key=os.getenv("BFX_API_KEY"), - api_secret=os.getenv("BFX_API_SECRET") -) +bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET")) loans: List[FundingLoan] = bfx.rest.auth.get_funding_loans(symbol="fUSD") diff --git a/examples/rest/merchant/settings.py b/examples/rest/merchant/settings.py index e82008e..9591de6 100644 --- a/examples/rest/merchant/settings.py +++ b/examples/rest/merchant/settings.py @@ -4,10 +4,7 @@ from bfxapi import Client -bfx = Client( - api_key=os.getenv("BFX_API_KEY"), - api_secret=os.getenv("BFX_API_SECRET") -) +bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET")) if not bfx.rest.merchant.set_merchant_settings("bfx_pay_recommend_store", 1): print("Cannot set to <1>.") diff --git a/examples/rest/merchant/submit_invoice.py b/examples/rest/merchant/submit_invoice.py index c2962b0..a44aad4 100644 --- a/examples/rest/merchant/submit_invoice.py +++ b/examples/rest/merchant/submit_invoice.py @@ -5,10 +5,7 @@ from bfxapi import Client from bfxapi.types import InvoiceSubmission -bfx = Client( - api_key=os.getenv("BFX_API_KEY"), - api_secret=os.getenv("BFX_API_SECRET") -) +bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET")) customer_info = { "nationality": "DE", diff --git a/pyproject.toml b/pyproject.toml index 6abb166..405d894 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,7 @@ [tool.isort] profile = "black" + +[tool.black] +target-version = ["py38", "py39", "py310", "py311"] + +preview = true \ No newline at end of file From 6a700690d73d7dc5edaabe15b7632461f1544d06 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Mon, 26 Feb 2024 19:56:59 +0100 Subject: [PATCH 04/14] Configure flake8 tool with some custom options. --- dev-requirements.txt | Bin 274 -> 380 bytes pyproject.toml | 7 ++++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 53d49eb09199b12077385d0c1b882d6b1c75a88b..b0c0355be5d804c2fd7be167c3f83556ee67034d 100644 GIT binary patch delta 111 zcmbQl^oMD}wTYLMCVo&#N@K`jNMy)nNM*2K&}B$sC}l_olBqzkA_iL^G-5De&;ycs k3}y_x3|tJT>I)bu847?Z^BJ;$dXgDRzK`#wTZXnCQC8$Oy*z|0sun91?m6* diff --git a/pyproject.toml b/pyproject.toml index 405d894..db770cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,4 +4,9 @@ profile = "black" [tool.black] target-version = ["py38", "py39", "py310", "py311"] -preview = true \ No newline at end of file +preview = true + +[tool.flake8] +max-line-length = 88 +extend-select = "B950" +extend-ignore = ["E203", "E501", "E701"] From 2344d44aa07069240b43de2ba13f7df5a609902e Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Mon, 26 Feb 2024 20:04:09 +0100 Subject: [PATCH 05/14] Remove all old '# pylint:' comments from all python files. --- bfxapi/_utils/logging.py | 2 -- bfxapi/rest/endpoints/rest_auth_endpoints.py | 3 --- bfxapi/rest/endpoints/rest_merchant_endpoints.py | 2 -- bfxapi/rest/endpoints/rest_public_endpoints.py | 1 - bfxapi/types/serializers.py | 2 -- bfxapi/websocket/_client/bfx_websocket_client.py | 8 -------- bfxapi/websocket/_client/bfx_websocket_inputs.py | 1 - bfxapi/websocket/_connection.py | 1 - bfxapi/websocket/_handlers/public_channels_handler.py | 7 ------- setup.py | 1 - 10 files changed, 28 deletions(-) diff --git a/bfxapi/_utils/logging.py b/bfxapi/_utils/logging.py index 593b99c..e19a120 100644 --- a/bfxapi/_utils/logging.py +++ b/bfxapi/_utils/logging.py @@ -1,7 +1,6 @@ import sys from copy import copy -# pylint: disable-next=wildcard-import,unused-wildcard-import from logging import * from typing import TYPE_CHECKING, Literal, Optional @@ -42,7 +41,6 @@ def format(self, record: LogRecord) -> str: return super().format(_record) - # pylint: disable-next=invalid-name def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str: return _GREEN + super().formatTime(record, datefmt) + _NC diff --git a/bfxapi/rest/endpoints/rest_auth_endpoints.py b/bfxapi/rest/endpoints/rest_auth_endpoints.py index 8afd13f..d8bcd1c 100644 --- a/bfxapi/rest/endpoints/rest_auth_endpoints.py +++ b/bfxapi/rest/endpoints/rest_auth_endpoints.py @@ -39,7 +39,6 @@ from ..middleware import Middleware -# pylint: disable-next=too-many-public-methods class RestAuthEndpoints(Middleware): def get_user_info(self) -> UserInfo: return serializers.UserInfo.parse(*self._post("auth/r/info/user")) @@ -368,7 +367,6 @@ def get_funding_offers(self, *, symbol: Optional[str] = None) -> List[FundingOff for sub_data in self._post(endpoint) ] - # pylint: disable-next=too-many-arguments def submit_funding_offer( self, type: str, @@ -552,7 +550,6 @@ def get_funding_info(self, key: str) -> FundingInfo: *(self._post(f"auth/r/info/funding/{key}")[2]) ) - # pylint: disable-next=too-many-arguments def transfer_between_wallets( self, from_wallet: str, diff --git a/bfxapi/rest/endpoints/rest_merchant_endpoints.py b/bfxapi/rest/endpoints/rest_merchant_endpoints.py index 5752011..6f783d6 100644 --- a/bfxapi/rest/endpoints/rest_merchant_endpoints.py +++ b/bfxapi/rest/endpoints/rest_merchant_endpoints.py @@ -28,7 +28,6 @@ class RestMerchantEndpoints(Middleware): - # pylint: disable-next=too-many-arguments def submit_invoice( self, amount: Union[str, float, Decimal], @@ -179,7 +178,6 @@ def set_merchant_settings(self, key: str, val: Any) -> bool: def get_merchant_settings(self, key: str) -> Any: return self._post("auth/r/ext/pay/settings/get", body={"key": key}) - # pylint: disable-next=dangerous-default-value def list_merchant_settings(self, keys: List[str] = []) -> Dict[str, Any]: return self._post("auth/r/ext/pay/settings/list", body={"keys": keys}) diff --git a/bfxapi/rest/endpoints/rest_public_endpoints.py b/bfxapi/rest/endpoints/rest_public_endpoints.py index c8b86b0..b8f5300 100644 --- a/bfxapi/rest/endpoints/rest_public_endpoints.py +++ b/bfxapi/rest/endpoints/rest_public_endpoints.py @@ -28,7 +28,6 @@ from ..middleware import Middleware -# pylint: disable-next=too-many-public-methods class RestPublicEndpoints(Middleware): def conf(self, config: str) -> Any: return self._get(f"conf/{config}")[0] diff --git a/bfxapi/types/serializers.py b/bfxapi/types/serializers.py index 2b83b7c..d5d2dab 100644 --- a/bfxapi/types/serializers.py +++ b/bfxapi/types/serializers.py @@ -1,13 +1,11 @@ from . import dataclasses -# pylint: disable-next=unused-import from .labeler import ( _Serializer, generate_labeler_serializer, generate_recursive_serializer, ) -# pylint: disable-next=unused-import from .notification import _Notification __serializers__ = [ diff --git a/bfxapi/websocket/_client/bfx_websocket_client.py b/bfxapi/websocket/_client/bfx_websocket_client.py index ab10a94..a1a2cb2 100644 --- a/bfxapi/websocket/_client/bfx_websocket_client.py +++ b/bfxapi/websocket/_client/bfx_websocket_client.py @@ -67,7 +67,6 @@ def reset(self) -> None: self.__backoff_delay = _Delay.__BACKOFF_MIN -# pylint: disable-next=too-many-instance-attributes class BfxWebSocketClient(Connection): def __init__( self, @@ -101,7 +100,6 @@ def error(exception: Exception) -> None: type(exception), exception, exception.__traceback__ ) - # pylint: disable-next=logging-not-lazy self.__logger.critical(header + "\n" + str().join(stack_trace)[:-1]) @property @@ -111,7 +109,6 @@ def inputs(self) -> BfxWebSocketInputs: def run(self) -> None: return asyncio.get_event_loop().run_until_complete(self.start()) - # pylint: disable-next=too-many-branches async def start(self) -> None: _delay = _Delay(backoff_factor=1.618) @@ -149,7 +146,6 @@ async def _cancel(task: Task) -> None: except asyncio.CancelledError: pass - # pylint: disable-next=consider-using-dict-items for bucket in self.__buckets: if task := self.__buckets[bucket]: self.__buckets[bucket] = None @@ -185,14 +181,12 @@ async def _cancel(task: Task) -> None: (isinstance(error, InvalidStatusCode) and error.status_code == 408) or isinstance(error, gaierror) ) and self.__reconnection: - # pylint: disable-next=logging-fstring-interpolation self.__logger.warning( "Reconnection attempt unsuccessful (no." f"{self.__reconnection['attempts']}): next attempt in " f"~{int(_delay.peek())}.0s." ) - # pylint: disable-next=logging-fstring-interpolation self.__logger.info( f"The client has been offline for " f"{datetime.now() - self.__reconnection['timestamp']}." @@ -214,7 +208,6 @@ async def _cancel(task: Task) -> None: async def __connect(self) -> None: async with websockets.client.connect(self._host) as websocket: if self.__reconnection: - # pylint: disable-next=logging-fstring-interpolation self.__logger.warning( "Reconnection attempt successful (no." f"{self.__reconnection['attempts']}): recovering " @@ -307,7 +300,6 @@ async def subscribe( @Connection._require_websocket_connection async def unsubscribe(self, sub_id: str) -> None: - # pylint: disable-next=consider-using-dict-items for bucket in self.__buckets: if bucket.has(sub_id): if bucket.count == 1: diff --git a/bfxapi/websocket/_client/bfx_websocket_inputs.py b/bfxapi/websocket/_client/bfx_websocket_inputs.py index 14925ce..d618135 100644 --- a/bfxapi/websocket/_client/bfx_websocket_inputs.py +++ b/bfxapi/websocket/_client/bfx_websocket_inputs.py @@ -99,7 +99,6 @@ async def cancel_order_multi( "oc_multi", {"id": id, "cid": cid, "gid": gid, "all": all} ) - # pylint: disable-next=too-many-arguments async def submit_funding_offer( self, type: str, diff --git a/bfxapi/websocket/_connection.py b/bfxapi/websocket/_connection.py index 73c7dd9..10579d9 100644 --- a/bfxapi/websocket/_connection.py +++ b/bfxapi/websocket/_connection.py @@ -6,7 +6,6 @@ from functools import wraps from typing import Any, Awaitable, Callable, Dict, List, Optional, TypeVar, cast -# pylint: disable-next=wrong-import-order from typing_extensions import Concatenate, ParamSpec from websockets.client import WebSocketClientProtocol diff --git a/bfxapi/websocket/_handlers/public_channels_handler.py b/bfxapi/websocket/_handlers/public_channels_handler.py index 556c539..98da88c 100644 --- a/bfxapi/websocket/_handlers/public_channels_handler.py +++ b/bfxapi/websocket/_handlers/public_channels_handler.py @@ -39,7 +39,6 @@ def handle(self, subscription: Subscription, stream: List[Any]) -> None: elif subscription["channel"] == "status": self.__status_channel_handler(cast(Status, subscription), stream) - # pylint: disable-next=inconsistent-return-statements def __ticker_channel_handler(self, subscription: Ticker, stream: List[Any]): if subscription["symbol"].startswith("t"): return self.__event_emitter.emit( @@ -55,7 +54,6 @@ def __ticker_channel_handler(self, subscription: Ticker, stream: List[Any]): serializers.FundingCurrencyTicker.parse(*stream[0]), ) - # pylint: disable-next=inconsistent-return-statements def __trades_channel_handler(self, subscription: Trades, stream: List[Any]): if (event := stream[0]) and event in ["te", "tu", "fte", "ftu"]: events = { @@ -99,7 +97,6 @@ def __trades_channel_handler(self, subscription: Trades, stream: List[Any]): ], ) - # pylint: disable-next=inconsistent-return-statements def __book_channel_handler(self, subscription: Book, stream: List[Any]): if subscription["symbol"].startswith("t"): if all(isinstance(sub_stream, list) for sub_stream in stream[0]): @@ -135,7 +132,6 @@ def __book_channel_handler(self, subscription: Book, stream: List[Any]): serializers.FundingCurrencyBook.parse(*stream[0]), ) - # pylint: disable-next=inconsistent-return-statements def __raw_book_channel_handler(self, subscription: Book, stream: List[Any]): if subscription["symbol"].startswith("t"): if all(isinstance(sub_stream, list) for sub_stream in stream[0]): @@ -171,7 +167,6 @@ def __raw_book_channel_handler(self, subscription: Book, stream: List[Any]): serializers.FundingCurrencyRawBook.parse(*stream[0]), ) - # pylint: disable-next=inconsistent-return-statements def __candles_channel_handler(self, subscription: Candles, stream: List[Any]): if all(isinstance(sub_stream, list) for sub_stream in stream[0]): return self.__event_emitter.emit( @@ -184,7 +179,6 @@ def __candles_channel_handler(self, subscription: Candles, stream: List[Any]): "candles_update", subscription, serializers.Candle.parse(*stream[0]) ) - # pylint: disable-next=inconsistent-return-statements def __status_channel_handler(self, subscription: Status, stream: List[Any]): if subscription["key"].startswith("deriv:"): return self.__event_emitter.emit( @@ -200,6 +194,5 @@ def __status_channel_handler(self, subscription: Status, stream: List[Any]): serializers.Liquidation.parse(*stream[0][0]), ) - # pylint: disable-next=inconsistent-return-statements def __checksum_handler(self, subscription: Book, value: int): return self.__event_emitter.emit("checksum", subscription, value & 0xFFFFFFFF) diff --git a/setup.py b/setup.py index 4abcb5e..b66d7a2 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ _version = { } with open("bfxapi/_version.py", encoding="utf-8") as f: - #pylint: disable-next=exec-used exec(f.read(), _version) setup( From 5543b0b1d2e5e47ad97299666427f504e8747ba9 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 27 Feb 2024 17:24:47 +0100 Subject: [PATCH 06/14] Fix all flake8 errors in all python files (+ edit configuration files). --- .flake8 | 6 +++++ bfxapi/_utils/json_decoder.py | 2 +- bfxapi/_utils/logging.py | 3 +-- .../rest/endpoints/rest_merchant_endpoints.py | 6 +++-- bfxapi/types/labeler.py | 3 ++- bfxapi/types/serializers.py | 6 ++--- .../websocket/_client/bfx_websocket_bucket.py | 4 +-- .../websocket/_client/bfx_websocket_client.py | 24 ++++++++++-------- .../_event_emitter/bfx_event_emitter.py | 4 +-- dev-requirements.txt | Bin 380 -> 326 bytes pyproject.toml | 7 ----- 11 files changed, 33 insertions(+), 32 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..4483e68 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +max-line-length = 88 +extend-select = B950 +extend-ignore = E203,E501,E701 + +per-file-ignores = */__init__.py:F401 diff --git a/bfxapi/_utils/json_decoder.py b/bfxapi/_utils/json_decoder.py index 21164a9..3597de9 100644 --- a/bfxapi/_utils/json_decoder.py +++ b/bfxapi/_utils/json_decoder.py @@ -13,4 +13,4 @@ def _object_hook(data: Dict[str, Any]) -> Any: class JSONDecoder(json.JSONDecoder): def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(object_hook=_object_hook, *args, **kwargs) + super().__init__(*args, **kwargs, object_hook=_object_hook) diff --git a/bfxapi/_utils/logging.py b/bfxapi/_utils/logging.py index e19a120..9eca09c 100644 --- a/bfxapi/_utils/logging.py +++ b/bfxapi/_utils/logging.py @@ -1,7 +1,6 @@ import sys from copy import copy - -from logging import * +from logging import FileHandler, Formatter, Logger, LogRecord, StreamHandler from typing import TYPE_CHECKING, Literal, Optional if TYPE_CHECKING: diff --git a/bfxapi/rest/endpoints/rest_merchant_endpoints.py b/bfxapi/rest/endpoints/rest_merchant_endpoints.py index 6f783d6..3c4d9af 100644 --- a/bfxapi/rest/endpoints/rest_merchant_endpoints.py +++ b/bfxapi/rest/endpoints/rest_merchant_endpoints.py @@ -178,8 +178,10 @@ def set_merchant_settings(self, key: str, val: Any) -> bool: def get_merchant_settings(self, key: str) -> Any: return self._post("auth/r/ext/pay/settings/get", body={"key": key}) - def list_merchant_settings(self, keys: List[str] = []) -> Dict[str, Any]: - return self._post("auth/r/ext/pay/settings/list", body={"keys": keys}) + 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 []}) def get_deposits( self, diff --git a/bfxapi/types/labeler.py b/bfxapi/types/labeler.py index 008a07d..6c2e373 100644 --- a/bfxapi/types/labeler.py +++ b/bfxapi/types/labeler.py @@ -24,7 +24,8 @@ def __init__(self, **kwargs): 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 '{list(kwargs.keys())[0]}'" ) cls.__init__ = __init__ diff --git a/bfxapi/types/serializers.py b/bfxapi/types/serializers.py index d5d2dab..ae2b3b1 100644 --- a/bfxapi/types/serializers.py +++ b/bfxapi/types/serializers.py @@ -1,12 +1,10 @@ from . import dataclasses - -from .labeler import ( +from .labeler import ( # noqa: F401 _Serializer, generate_labeler_serializer, generate_recursive_serializer, ) - -from .notification import _Notification +from .notification import _Notification # noqa: F401 __serializers__ = [ "PlatformStatus", diff --git a/bfxapi/websocket/_client/bfx_websocket_bucket.py b/bfxapi/websocket/_client/bfx_websocket_bucket.py index 5980440..fa6262f 100644 --- a/bfxapi/websocket/_client/bfx_websocket_bucket.py +++ b/bfxapi/websocket/_client/bfx_websocket_bucket.py @@ -15,7 +15,7 @@ def _strip(message: Dict[str, Any], keys: List[str]) -> Dict[str, Any]: - return {key: value for key, value in message.items() if not key in keys} + return {key: value for key, value in message.items() if key not in keys} class BfxWebSocketBucket(Connection): @@ -136,7 +136,7 @@ async def resubscribe(self, sub_id: str) -> None: await self.subscribe(**subscription) @Connection._require_websocket_connection - async def close(self, code: int = 1000, reason: str = str()) -> None: + async def close(self, code: int = 1000, reason: str = "") -> None: await self._websocket.close(code, reason) def has(self, sub_id: str) -> bool: diff --git a/bfxapi/websocket/_client/bfx_websocket_client.py b/bfxapi/websocket/_client/bfx_websocket_client.py index a1a2cb2..ffae0ad 100644 --- a/bfxapi/websocket/_client/bfx_websocket_client.py +++ b/bfxapi/websocket/_client/bfx_websocket_client.py @@ -100,7 +100,7 @@ def error(exception: Exception) -> None: type(exception), exception, exception.__traceback__ ) - self.__logger.critical(header + "\n" + str().join(stack_trace)[:-1]) + self.__logger.critical(f"{header}\n" + str().join(stack_trace)[:-1]) @property def inputs(self) -> BfxWebSocketInputs: @@ -141,6 +141,8 @@ async def _cancel(task: Task) -> None: try: await task except (ConnectionClosedError, InvalidStatusCode, gaierror) as _e: + nonlocal error + if type(error) is not type(_e) or error.args != _e.args: raise _e except asyncio.CancelledError: @@ -241,8 +243,9 @@ async def __connect(self) -> None: if message["version"] != 2: raise VersionMismatchError( "Mismatch between the client and the server version: " - + "please update bitfinex-api-py to the latest version to resolve this error " - + f"(client version: 2, server version: {message['version']})." + "please update bitfinex-api-py to the latest version " + f"to resolve this error (client version: 2, server " + f"version: {message['version']})." ) elif message["event"] == "info" and message["code"] == 20051: rcvd = websockets.frames.Close( @@ -253,8 +256,7 @@ async def __connect(self) -> None: elif message["event"] == "auth": if message["status"] != "OK": raise InvalidCredentialError( - "Can't authenticate " - + "with given API-KEY and API-SECRET." + "Can't authenticate with given API-KEY and API-SECRET." ) self.__event_emitter.emit("authenticated", message) @@ -281,14 +283,14 @@ async def __new_bucket(self) -> BfxWebSocketBucket: async def subscribe( self, channel: str, sub_id: Optional[str] = None, **kwargs: Any ) -> None: - if not channel in ["ticker", "trades", "book", "candles", "status"]: + if channel not in ["ticker", "trades", "book", "candles", "status"]: raise UnknownChannelError( - "Available channels are: " + "ticker, trades, book, candles and status." + "Available channels are: ticker, trades, book, candles and status." ) for bucket in self.__buckets: if sub_id in bucket.ids: - raise SubIdError("sub_id must be " + "unique for all subscriptions.") + raise SubIdError("sub_id must be unique for all subscriptions.") for bucket in self.__buckets: if not bucket.is_full: @@ -310,7 +312,7 @@ async def unsubscribe(self, sub_id: str) -> None: return await bucket.unsubscribe(sub_id) raise UnknownSubscriptionError( - "Unable to find " + f"a subscription with sub_id <{sub_id}>." + f"Unable to find a subscription with sub_id <{sub_id}>." ) @Connection._require_websocket_connection @@ -320,11 +322,11 @@ async def resubscribe(self, sub_id: str) -> None: return await bucket.resubscribe(sub_id) raise UnknownSubscriptionError( - "Unable to find " + f"a subscription with sub_id <{sub_id}>." + f"Unable to find a subscription with sub_id <{sub_id}>." ) @Connection._require_websocket_connection - async def close(self, code: int = 1000, reason: str = str()) -> None: + async def close(self, code: int = 1000, reason: str = "") -> None: for bucket in self.__buckets: await bucket.close(code=code, reason=reason) diff --git a/bfxapi/websocket/_event_emitter/bfx_event_emitter.py b/bfxapi/websocket/_event_emitter/bfx_event_emitter.py index 89931ba..3638767 100644 --- a/bfxapi/websocket/_event_emitter/bfx_event_emitter.py +++ b/bfxapi/websocket/_event_emitter/bfx_event_emitter.py @@ -105,8 +105,8 @@ def on( ) -> Union[_Handler, Callable[[_Handler], _Handler]]: if event not in BfxEventEmitter._EVENTS: raise UnknownEventError( - f"Can't register to unknown event: <{event}> " - + "(to get a full list of available events see https://docs.bitfinex.com/)." + f"Can't register to unknown event: <{event}> (to get a full" + "list of available events see https://docs.bitfinex.com/)." ) return super().on(event, f) diff --git a/dev-requirements.txt b/dev-requirements.txt index b0c0355be5d804c2fd7be167c3f83556ee67034d..a902e7091035fcd1f62914bc8db4e97b1b6e4b85 100644 GIT binary patch delta 24 gcmeyvbc|`jwTZXnCQC8$Oy*z|n)pLtvKpfb0DYYZl>h($ delta 57 zcmX@c^oMD}wTYLMCVo(wEXHU6#C$3R43!K83`Gq23|S1R49N^747NaM$e_ny1jNP+ IybN3n01bQ%ZU6uP diff --git a/pyproject.toml b/pyproject.toml index db770cc..6163d4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,10 +3,3 @@ profile = "black" [tool.black] target-version = ["py38", "py39", "py310", "py311"] - -preview = true - -[tool.flake8] -max-line-length = 88 -extend-select = "B950" -extend-ignore = ["E203", "E501", "E701"] From e2257561d9fd7539343def1c7cd82ef7520f6e4d Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 27 Feb 2024 18:11:10 +0100 Subject: [PATCH 07/14] Split isort, black and flake8 configuration in .isort.cfg, .flake8 and pyproject.toml. --- .flake8 | 5 +++++ .isort.cfg | 2 ++ pyproject.toml | 3 --- setup.py | 19 ++++++++----------- 4 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 .isort.cfg diff --git a/.flake8 b/.flake8 index 4483e68..fab72cf 100644 --- a/.flake8 +++ b/.flake8 @@ -4,3 +4,8 @@ extend-select = B950 extend-ignore = E203,E501,E701 per-file-ignores = */__init__.py:F401 + +exclude = + __pycache__ + dist, + venv diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..39b73a7 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +profile = black diff --git a/pyproject.toml b/pyproject.toml index 6163d4e..1833e97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,2 @@ -[tool.isort] -profile = "black" - [tool.black] target-version = ["py38", "py39", "py310", "py311"] diff --git a/setup.py b/setup.py index b66d7a2..8628987 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,15 @@ from distutils.core import setup -_version = { } - -with open("bfxapi/_version.py", encoding="utf-8") as f: - exec(f.read(), _version) +from bfxapi._version import __version__ setup( name="bitfinex-api-py", - version=_version["__version__"], + version=__version__, description="Official Bitfinex Python API", - long_description="A Python reference implementation of the Bitfinex API for both REST and websocket interaction", + long_description=( + "A Python reference implementation of the Bitfinex API " + "for both REST and websocket interaction." + ), long_description_content_type="text/markdown", url="https://github.com/bitfinexcom/bitfinex-api-py", author="Bitfinex", @@ -17,12 +17,9 @@ license="Apache-2.0", classifiers=[ "Development Status :: 4 - Beta", - "Intended Audience :: Developers", "Topic :: Software Development :: Build Tools", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -51,5 +48,5 @@ "requests~=2.28.1", "urllib3~=1.26.14", ], - python_requires=">=3.8" -) \ No newline at end of file + python_requires=">=3.8", +) From 1a6f4eaa21658638202598781ee94edc0e614e24 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 27 Feb 2024 18:14:47 +0100 Subject: [PATCH 08/14] Fix flake8 configuration to respect Black's 10% rule. --- .flake8 | 2 +- bfxapi/_client.py | 4 ++-- bfxapi/rest/middleware/middleware.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.flake8 b/.flake8 index fab72cf..58d33b8 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,5 @@ [flake8] -max-line-length = 88 +max-line-length = 80 extend-select = B950 extend-ignore = E203,E501,E701 diff --git a/bfxapi/_client.py b/bfxapi/_client.py index 21f2f86..f8fc67c 100644 --- a/bfxapi/_client.py +++ b/bfxapi/_client.py @@ -37,11 +37,11 @@ def __init__( } elif api_key: raise IncompleteCredentialError( - "You must provide both an API-KEY and an API-SECRET (missing API-KEY)." + "You must provide both API-KEY and API-SECRET (missing API-KEY)." ) elif api_secret: raise IncompleteCredentialError( - "You must provide both an API-KEY and an API-SECRET (missing API-SECRET)." + "You must provide both API-KEY and API-SECRET (missing API-SECRET)." ) self.rest = BfxRestInterface(rest_host, api_key, api_secret) diff --git a/bfxapi/rest/middleware/middleware.py b/bfxapi/rest/middleware/middleware.py index 7047127..48a4da4 100644 --- a/bfxapi/rest/middleware/middleware.py +++ b/bfxapi/rest/middleware/middleware.py @@ -35,7 +35,7 @@ def __init__( 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 str to call __build_authentication_headers" + ), "API_KEY and API_SECRET must be both strings" nonce = str(round(time.time() * 1_000_000)) From e4c7acd2c729c7064bfda6fff46d1025ecdeb681 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 27 Feb 2024 19:16:54 +0100 Subject: [PATCH 09/14] Install and configure pre-commit (to work with isort, black and flake8). --- .flake8 | 6 +++--- .pre-commit-config.yaml | 17 +++++++++++++++++ dev-requirements.txt | Bin 326 -> 368 bytes 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.flake8 b/.flake8 index 58d33b8..c257c3b 100644 --- a/.flake8 +++ b/.flake8 @@ -3,9 +3,9 @@ max-line-length = 80 extend-select = B950 extend-ignore = E203,E501,E701 -per-file-ignores = */__init__.py:F401 - exclude = __pycache__ - dist, + dist venv + +per-file-ignores = */__init__.py:F401 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..9397acf --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +repos: + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.2.0 + hooks: + - id: black + - repo: https://github.com/PyCQA/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + + additional_dependencies: [ + flake8-bugbear + ] diff --git a/dev-requirements.txt b/dev-requirements.txt index a902e7091035fcd1f62914bc8db4e97b1b6e4b85..61486ea9fb6eb47c619a0a7a46be0b31a1275beb 100644 GIT binary patch delta 50 zcmX@c^nq!E8>1F40~bR9LlHwNgDyidLq0<;5N0xzFxUd2F@qk184w#WfK&kh5Mu~< delta 7 Ocmeysbc|_(8zTS>CIYem From 98037981fcd8ae9a532584a4b98485987fa1cc7e Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 27 Feb 2024 21:02:16 +0100 Subject: [PATCH 10/14] Edit .github/workflows/bitfinex-api-py-ci.yml to run pre-commit hooks. --- .github/workflows/bitfinex-api-py-ci.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/bitfinex-api-py-ci.yml b/.github/workflows/bitfinex-api-py-ci.yml index 8dc0020..b82e55f 100644 --- a/.github/workflows/bitfinex-api-py-ci.yml +++ b/.github/workflows/bitfinex-api-py-ci.yml @@ -8,9 +8,6 @@ on: branches: - master -permissions: - contents: read - jobs: build: runs-on: ubuntu-latest @@ -23,7 +20,7 @@ jobs: python-version: '3.8' - name: Install bitfinex-api-py's dependencies run: python -m pip install -r dev-requirements.txt - - name: Lint the project with pylint (and fail if score is lower than 10.00/10.00) - run: python -m pylint bfxapi - - name: Run mypy to check the correctness of type hinting (and fail if any error or warning is found) + - name: Run pre-commit hooks (see .pre-commit-config.yaml) + uses: pre-commit/action@v3.0.1 + - name: Run mypy to ensure correct type hinting run: python -m mypy bfxapi From 48df3fa6e93d2d51026be39170dbfe0b90c3fed5 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 27 Feb 2024 21:22:30 +0100 Subject: [PATCH 11/14] Downgrade pre-commit version to 3.5.0 (dev-requirements.txt). --- dev-requirements.txt | Bin 368 -> 368 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 61486ea9fb6eb47c619a0a7a46be0b31a1275beb..efac474934b0f5d5c4fa3e96d22c3aa56d11aaf6 100644 GIT binary patch delta 18 Zcmeys^nqzZ8Y7n}gC2tc11|#?0{}E+1F!%9 delta 18 Zcmeys^nqzZ8Y7n(gC2ts11|#?0{}F71G4}C From ababa734887849f5919d44a02ce8070e68882bea Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 27 Feb 2024 21:36:38 +0100 Subject: [PATCH 12/14] Edit .gitignore to exclude more files/folders. --- .flake8 | 4 +++- .gitignore | 18 +++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.flake8 b/.flake8 index c257c3b..f7aee5b 100644 --- a/.flake8 +++ b/.flake8 @@ -5,7 +5,9 @@ extend-ignore = E203,E501,E701 exclude = __pycache__ + build dist venv -per-file-ignores = */__init__.py:F401 +per-file-ignores = + */__init__.py:F401 diff --git a/.gitignore b/.gitignore index 1556a49..f0c1f9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,16 @@ +.venv +.DS_Store .vscode -*.pyc -*.log +.python-version +__pycache__ bitfinex_api_py.egg-info +bitfinex_api_py.dist-info +build/ +dist/ +pip-wheel-metadata/ +.eggs -__pycache__ +.idea -dist -venv -!.gitkeep -MANIFEST +venv/ From 4f0f5efe09d5e376a8df52dc89aa1bbd77b5736e Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 27 Feb 2024 22:06:47 +0100 Subject: [PATCH 13/14] Add new tasks to .github/PULL_REQUEST_TEMPLATE.md. --- .github/ISSUE_TEMPLATE.md | 6 +++--- .github/PULL_REQUEST_TEMPLATE.md | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index c8b9498..eff17f2 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -20,9 +20,9 @@ A possible solution could be... ## Steps to reproduce (for bugs) -1.   -2.   -3.   +1. +2. +3. ### Python version diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 05f83fa..518ec60 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -20,10 +20,11 @@ PR fixes the following issue: # Checklist: -- [ ] My code follows the style guidelines of this project; -- [ ] I have performed a self-review of my code; -- [ ] I have commented my code, particularly in hard-to-understand areas; -- [ ] I have made corresponding changes to the documentation; -- [ ] My changes generate no new warnings; -- [ ] Mypy returns no errors or warnings when run on the root package; -- [ ] Pylint returns a score of 10.00/10.00 when run on the root package; +- [ ] I've done a self-review of my code; +- [ ] I've made corresponding changes to the documentation; +- [ ] I've made sure my changes generate no warnings; +- [ ] mypy returns no errors when run on the root package; + +- [ ] I've run black to format my code; +- [ ] I've run isort to format my code's import statements; +- [ ] flake8 reports no errors when run on the entire code base; From f94096bf3297d018a0593556fcf6a821544ff165 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 27 Feb 2024 23:35:38 +0100 Subject: [PATCH 14/14] Edit README.md documentation to add chapter on pre-commit. --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- README.md | 43 ++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 518ec60..34289f1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -24,7 +24,7 @@ PR fixes the following issue: - [ ] I've made corresponding changes to the documentation; - [ ] I've made sure my changes generate no warnings; - [ ] mypy returns no errors when run on the root package; - + - [ ] I've run black to format my code; - [ ] I've run isort to format my code's import statements; - [ ] flake8 reports no errors when run on the entire code base; diff --git a/README.md b/README.md index a564c54..3f44473 100644 --- a/README.md +++ b/README.md @@ -312,7 +312,9 @@ Contributors must uphold the [Contributor Covenant code of conduct](https://gith 1. [Installation and setup](#installation-and-setup) * [Cloning the repository](#cloning-the-repository) * [Installing the dependencies](#installing-the-dependencies) + * [Set up the pre-commit hooks (optional)](#set-up-the-pre-commit-hooks-optional) 2. [Before opening a PR](#before-opening-a-pr) + * [Tip](#tip) 3. [License](#license) ## Installation and setup @@ -333,23 +335,48 @@ git clone --branch v3-beta --single-branch https://github.com/bitfinexcom/bitfin python3 -m pip install -r dev-requirements.txt ``` -Make sure to install `dev-requirements.txt` instead of `requirements.txt`. \ -`dev-requirements.txt` will install all dependencies in `requirements.txt` plus any development dependencies. \ -This will also install the versions in use of [`pylint`](https://github.com/pylint-dev/pylint) and [`mypy`](https://github.com/python/mypy), which you should both use before opening your PRs. +Make sure to install `dev-requirements.txt` (and not `requirements.txt`!). \ +`dev-requirements.txt` will install all dependencies in `requirements.txt` plus any development dependency. \ +dev-requirements includes [mypy](https://github.com/python/mypy), [black](https://github.com/psf/black), [isort](https://github.com/PyCQA/isort), [flake8](https://github.com/PyCQA/flake8), and [pre-commit](https://github.com/pre-commit/pre-commit) (more on these tools in later chapters). All done, your Python 3.8+ environment should now be able to run `bitfinex-api-py`'s source code. +### Set up the pre-commit hooks (optional) + +**Do not skip this paragraph if you intend to contribute to the project.** + +This repository includes a pre-commit configuration file that defines the following hooks: +1. [isort](https://github.com/PyCQA/isort) +2. [black](https://github.com/psf/black) +3. [flake8](https://github.com/PyCQA/flake8) + +To set up pre-commit use: +```console +python3 -m pre-commit install +``` + +These will ensure that isort, black and flake8 are run on each git commit. + +[Visit this page to learn more about git hooks and pre-commit.](https://pre-commit.com/#introduction) + +#### Manually triggering the pre-commit hooks + +You can also manually trigger the execution of all hooks with: +```console +python3 -m pre-commit run --all-files +``` + ## Before opening a PR -**We won't accept your PR or we will request changes if the following requirements aren't met.** +**We won't accept your PR or we'll request changes if the following requirements aren't met.** Wheter you're submitting a bug fix, a new feature or a documentation change, you should first discuss it in an issue. -All PRs must follow this [PULL_REQUEST_TEMPLATE](https://github.com/bitfinexcom/bitfinex-api-py/blob/v3-beta/.github/PULL_REQUEST_TEMPLATE.md) and include an exhaustive description. +You must be able to check off all tasks listed in [PULL_REQUEST_TEMPLATE](https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-py/master/.github/PULL_REQUEST_TEMPLATE.md) before opening a pull request. + +### Tip -Before opening a pull request, you should also make sure that: -- [ ] [`pylint`](https://github.com/pylint-dev/pylint) returns a score of 10.00/10.00 when run against your code. -- [ ] [`mypy`](https://github.com/python/mypy) doesn't throw any error code when run on the project (excluding notes). +Setting up the project's pre-commit hooks will help automate this process ([more](#set-up-the-pre-commit-hooks-optional)). ## License