mirror of
https://github.com/aljazceru/bitfinex-api-py.git
synced 2025-12-18 22:34:21 +01:00
105 lines
3.0 KiB
Python
105 lines
3.0 KiB
Python
import hashlib
|
|
import hmac
|
|
import json
|
|
from abc import ABC, abstractmethod
|
|
from datetime import datetime
|
|
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
|
|
|
|
from bfxapi.websocket.exceptions import ActionRequiresAuthentication, ConnectionNotOpen
|
|
|
|
_S = TypeVar("_S", bound="Connection")
|
|
|
|
_R = TypeVar("_R")
|
|
|
|
_P = ParamSpec("_P")
|
|
|
|
|
|
class Connection(ABC):
|
|
_HEARTBEAT = "hb"
|
|
|
|
def __init__(self, host: str) -> None:
|
|
self._host = host
|
|
|
|
self._authentication: bool = False
|
|
|
|
self.__protocol: Optional[WebSocketClientProtocol] = None
|
|
|
|
@property
|
|
def open(self) -> bool:
|
|
return self.__protocol is not None and self.__protocol.open
|
|
|
|
@property
|
|
def authentication(self) -> bool:
|
|
return self._authentication
|
|
|
|
@property
|
|
def _websocket(self) -> WebSocketClientProtocol:
|
|
return cast(WebSocketClientProtocol, self.__protocol)
|
|
|
|
@_websocket.setter
|
|
def _websocket(self, protocol: WebSocketClientProtocol) -> None:
|
|
self.__protocol = protocol
|
|
|
|
@abstractmethod
|
|
async def start(self) -> None: ...
|
|
|
|
@staticmethod
|
|
def _require_websocket_connection(
|
|
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 self.open:
|
|
return await function(self, *args, **kwargs)
|
|
|
|
raise ConnectionNotOpen("No open connection with the server.")
|
|
|
|
return wrapper
|
|
|
|
@staticmethod
|
|
def _require_websocket_authentication(
|
|
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."
|
|
)
|
|
|
|
internal = Connection._require_websocket_connection(function)
|
|
|
|
return await internal(self, *args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
@staticmethod
|
|
def _get_authentication_message(
|
|
api_key: str, api_secret: str, filters: Optional[List[str]] = None
|
|
) -> str:
|
|
message: Dict[str, Any] = {
|
|
"event": "auth",
|
|
"filter": filters,
|
|
"apiKey": api_key,
|
|
}
|
|
|
|
message["authNonce"] = round(datetime.now().timestamp() * 1_000_000)
|
|
|
|
message["authPayload"] = f"AUTH{message['authNonce']}"
|
|
|
|
auth_sig = hmac.new(
|
|
key=api_secret.encode("utf8"),
|
|
msg=message["authPayload"].encode("utf8"),
|
|
digestmod=hashlib.sha384,
|
|
)
|
|
|
|
message["authSig"] = auth_sig.hexdigest()
|
|
|
|
return json.dumps(message)
|