diff --git a/bfxapi/labeler.py b/bfxapi/labeler.py index d2fbdb5..52b0e2b 100644 --- a/bfxapi/labeler.py +++ b/bfxapi/labeler.py @@ -1,6 +1,6 @@ from .exceptions import LabelerSerializerException -from typing import Type, Generic, TypeVar, Iterable, Optional, List, Tuple, Any, cast +from typing import Type, Generic, TypeVar, Iterable, Optional, Dict, List, Tuple, Any, cast T = TypeVar("T", bound="_Type") @@ -28,5 +28,23 @@ class _Serializer(Generic[T]): def parse(self, *values: Any, skip: Optional[List[str]] = None) -> T: return cast(T, self.klass(**dict(self._serialize(*values, skip=skip)))) +class _RecursiveSerializer(_Serializer, Generic[T]): + def __init__(self, name: str, klass: Type[_Type], labels: List[str], serializers: Dict[str, Type[_Serializer]], IGNORE: List[str] = ["_PLACEHOLDER"]): + super().__init__(name, klass, labels, IGNORE) + + self.serializers = serializers + + def parse(self, *values: Any, skip: Optional[List[str]] = None) -> T: + serialization = dict(self._serialize(*values, skip=skip)) + + for key in serialization: + if key in self.serializers.keys(): + serialization[key] = self.serializers[key].parse(*serialization[key], skip=skip) + + return cast(T, self.klass(**serialization)) + def generate_labeler_serializer(name: str, klass: Type[T], labels: List[str], IGNORE: List[str] = [ "_PLACEHOLDER" ]) -> _Serializer[T]: - return _Serializer[T](name, klass, labels, IGNORE) \ No newline at end of file + return _Serializer[T](name, klass, labels, IGNORE) + +def generate_recursive_serializer(name: str, klass: Type[T], labels: List[str], serializers: Dict[str, Type[_Serializer]], IGNORE: List[str] = [ "_PLACEHOLDER" ]) -> _RecursiveSerializer[T]: + return _RecursiveSerializer[T](name, klass, labels, serializers, IGNORE) \ No newline at end of file diff --git a/bfxapi/rest/BfxRestInterface.py b/bfxapi/rest/BfxRestInterface.py index 741a3ec..bca18c1 100644 --- a/bfxapi/rest/BfxRestInterface.py +++ b/bfxapi/rest/BfxRestInterface.py @@ -58,7 +58,7 @@ class _Requests(object): raise RequestParametersError(f"The request was rejected with the following parameter error: <{data[2]}>") if data[1] == None or data[1] == Error.ERR_UNK or data[1] == Error.ERR_GENERIC: - raise UnknownGenericError("The server replied to the request with a generic error with message: <{data[2]}>.") + raise UnknownGenericError(f"The server replied to the request with a generic error with message: <{data[2]}>.") return data @@ -255,6 +255,19 @@ class _RestPublicEndpoints(_Requests): def conf(self, config: Config) -> Any: return self._GET(f"conf/{config}")[0] + def get_pulse_profile(self, nickname: str) -> PulseProfile: + return serializers.PulseProfile.parse(*self._GET(f"pulse/profile/{nickname}")) + + def get_pulse_history(self, end: Optional[str] = None, limit: Optional[int] = None) -> List[PulseMessage]: + messages = list() + + for subdata in self._GET("pulse/hist", params={ "end": end, "limit": limit }): + subdata[18] = subdata[18][0] + message = serializers.PulseMessage.parse(*subdata) + messages.append(message) + + return messages + class _RestAuthenticatedEndpoints(_Requests): def get_wallets(self) -> List[Wallet]: return [ serializers.Wallet.parse(*subdata) for subdata in self._POST("auth/r/wallets") ] diff --git a/bfxapi/rest/serializers.py b/bfxapi/rest/serializers.py index 7cea3db..5209d6f 100644 --- a/bfxapi/rest/serializers.py +++ b/bfxapi/rest/serializers.py @@ -1,6 +1,6 @@ from . import types -from .. labeler import generate_labeler_serializer +from .. labeler import generate_labeler_serializer, generate_recursive_serializer from .. notification import _Notification @@ -185,6 +185,51 @@ FundingStatistic = generate_labeler_serializer("FundingStatistic", klass=types.F "FUNDING_BELOW_THRESHOLD" ]) +PulseProfile = generate_labeler_serializer("PulseProfile", klass=types.PulseProfile, labels=[ + "PUID", + "MTS", + "_PLACEHOLDER", + "NICKNAME", + "_PLACEHOLDER", + "PICTURE", + "TEXT", + "_PLACEHOLDER", + "_PLACEHOLDER", + "TWITTER_HANDLE", + "_PLACEHOLDER", + "FOLLOWERS", + "FOLLOWING", + "_PLACEHOLDER", + "_PLACEHOLDER", + "_PLACEHOLDER", + "TIPPING_STATUS" +]) + +PulseMessage = generate_recursive_serializer("PulseMessage", klass=types.PulseMessage, serializers={ "PROFILE": PulseProfile }, labels=[ + "PID", + "MTS", + "_PLACEHOLDER", + "PUID", + "_PLACEHOLDER", + "TITLE", + "CONTENT", + "_PLACEHOLDER", + "_PLACEHOLDER", + "IS_PIN", + "IS_PUBLIC", + "COMMENTS_DISABLED", + "TAGS", + "ATTACHMENTS", + "META", + "LIKES", + "_PLACEHOLDER", + "_PLACEHOLDER", + "PROFILE", + "COMMENTS", + "_PLACEHOLDER", + "_PLACEHOLDER" +]) + #endregion #region Serializers definition for Rest Authenticated Endpoints diff --git a/bfxapi/rest/types.py b/bfxapi/rest/types.py index 9cd14b9..7193d5c 100644 --- a/bfxapi/rest/types.py +++ b/bfxapi/rest/types.py @@ -152,6 +152,35 @@ class FundingStatistic(_Type): FUNDING_AMOUNT_USED: float FUNDING_BELOW_THRESHOLD: float +@dataclass +class PulseProfile(_Type): + PUID: str + MTS: int + NICKNAME: str + PICTURE: str + TEXT: str + TWITTER_HANDLE: str + FOLLOWERS: int + FOLLOWING: int + TIPPING_STATUS: int + +@dataclass +class PulseMessage(_Type): + PID: str + MTS: int + PUID: str + TITLE: str + CONTENT: str + IS_PIN: int + IS_PUBLIC: int + COMMENTS_DISABLED: int + TAGS: List[str] + ATTACHMENTS: List[str] + META: List[JSON] + LIKES: int + PROFILE: PulseProfile + COMMENTS: int + #endregion #region Type hinting for Rest Authenticated Endpoints diff --git a/examples/rest/get_pulse_data.py b/examples/rest/get_pulse_data.py new file mode 100644 index 0000000..fc5c15f --- /dev/null +++ b/examples/rest/get_pulse_data.py @@ -0,0 +1,22 @@ +# python -c "from examples.rest.get_pulse_data import *" + +import time + +from bfxapi.client import Client, Constants + +bfx = Client( + REST_HOST=Constants.REST_HOST +) + +now = int(round(time.time() * 1000)) + +messages = bfx.rest.public.get_pulse_history(end=now, limit=100) + +for message in messages: + print(f"Message: {message}") + print(message.CONTENT) + print(message.PROFILE.PICTURE) + +profile = bfx.rest.public.get_pulse_profile("News") +print(f"Profile: {profile}") +print(f"Profile picture: {profile.PICTURE}") \ No newline at end of file