From 92f6e691f54fc035d75256974cc8c6e389e9b336 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Mon, 21 Nov 2022 18:41:05 +0100 Subject: [PATCH 1/6] Add BfxWebsocketBucket class in bfxapi/websocket/BfxWebsocketClient.py. Implement running multiple websocket client concurrently using asyncio to allow more than 25 connections to public channels. Rewrite BfxWebsocketClient to handle only websocket authenticated channels. --- bfxapi/websocket/BfxWebsocketClient.py | 163 +++++++++++++------------ 1 file changed, 87 insertions(+), 76 deletions(-) diff --git a/bfxapi/websocket/BfxWebsocketClient.py b/bfxapi/websocket/BfxWebsocketClient.py index f2ed76a..5a92e83 100644 --- a/bfxapi/websocket/BfxWebsocketClient.py +++ b/bfxapi/websocket/BfxWebsocketClient.py @@ -17,16 +17,6 @@ def _require_websocket_connection(function): return wrapper -def _require_websocket_authentication(function): - @_require_websocket_connection - async def wrapper(self, *args, **kwargs): - if self.authentication == False: - raise WebsocketAuthenticationRequired("To perform this action you need to authenticate using your API_KEY and API_SECRET.") - - await function(self, *args, **kwargs) - - return wrapper - class BfxWebsocketClient(object): VERSION = 2 @@ -36,73 +26,42 @@ class BfxWebsocketClient(object): *AuthenticatedChannelsHandler.EVENTS ] - def __init__(self, host, API_KEY=None, API_SECRET=None): - self.host, self.chanIds, self.event_emitter = host, dict(), AsyncIOEventEmitter() + def __init__(self, host, buckets=5, API_KEY=None, API_SECRET=None): + self.host, self.websocket, self.event_emitter = host, None, AsyncIOEventEmitter() - self.websocket, self.API_KEY, self.API_SECRET = None, API_KEY, API_SECRET + self.API_KEY, self.API_SECRET, self.authentication = API_KEY, API_SECRET, False - self.authentication = False + self.handler = AuthenticatedChannelsHandler(event_emitter=self.event_emitter) - self.handlers = { - "public": PublicChannelsHandler(event_emitter=self.event_emitter), - "authenticated": AuthenticatedChannelsHandler(event_emitter=self.event_emitter) - } + self.buckets = [ _BfxWebsocketBucket(self.host, self.event_emitter, self.__bucket_open_signal) for _ in range(buckets) ] - async def connect(self): - async for websocket in websockets.connect(self.host): + async def start(self): + tasks = [ bucket._connect(index) for index, bucket in enumerate(self.buckets) ] + + if self.API_KEY != None and self.API_SECRET != None: + tasks.append(self.__connect(self.API_KEY, self.API_SECRET)) + + await asyncio.gather(*tasks) + + async def __connect(self, API_KEY, API_SECRET, filter=None): + async with websockets.connect(self.host) as websocket: self.websocket = websocket - try: - self.event_emitter.emit("open") + await self.__authenticate(API_KEY, API_SECRET, filter) - if self.API_KEY != None and self.API_SECRET != None: - await self.authenticate(self.API_KEY, self.API_SECRET) + async for message in websocket: + message = json.loads(message) - async for message in websocket: - message = json.loads(message) + if isinstance(message, dict) and message["event"] == "auth": + if message["status"] == "OK": + self.event_emitter.emit("authenticated", message); self.authentication = True + else: raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.") + elif isinstance(message, dict) and message["event"] == "error": + self.event_emitter.emit("wss-error", message["code"], message["msg"]) + elif isinstance(message, list) and (chanId := message[0]) == 0 and message[1] != HEARTBEAT: + self.handler.handle(message[1], message[2]) - if isinstance(message, dict) and message["event"] == "info" and "version" in message: - if BfxWebsocketClient.VERSION != message["version"]: - raise OutdatedClientVersion(f"Mismatch between the client version and the server version. Update the library to the latest version to continue (client version: {BfxWebsocketClient.VERSION}, server version: {message['version']}).") - elif isinstance(message, dict) and message["event"] == "subscribed": - self.chanIds[message["chanId"]] = message - - self.event_emitter.emit("subscribed", message) - elif isinstance(message, dict) and message["event"] == "unsubscribed": - if message["status"] == "OK": - del self.chanIds[message["chanId"]] - elif isinstance(message, dict) and message["event"] == "auth": - if message["status"] == "OK": - self.event_emitter.emit("authenticated", message) - - self.authentication = True - else: raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.") - elif isinstance(message, dict) and message["event"] == "error": - self.event_emitter.emit("wss-error", message["code"], message["msg"]) - elif isinstance(message, list) and message[1] != HEARTBEAT: - if ((chanId := message[0]) or True) and chanId == 0: - self.handlers["authenticated"].handle(message[1], message[2]) - else: self.handlers["public"].handle(self.chanIds[chanId], *message[1:]) - except websockets.ConnectionClosed: - continue - - @_require_websocket_connection - async def subscribe(self, channel, **kwargs): - await self.websocket.send(json.dumps({ - "event": "subscribe", - "channel": channel, - **kwargs - })) - - @_require_websocket_connection - async def unsubscribe(self, chanId): - await self.websocket.send(json.dumps({ - "event": "unsubscribe", - "chanId": chanId - })) - - @_require_websocket_connection - async def authenticate(self, API_KEY, API_SECRET, filter=None): + async def __authenticate(self, API_KEY, API_SECRET, filter=None): data = { "event": "auth", "filter": filter, "apiKey": API_KEY } data["authNonce"] = int(time.time()) * 1000 @@ -117,13 +76,26 @@ class BfxWebsocketClient(object): await self.websocket.send(json.dumps(data)) - @_require_websocket_authentication - async def notify(self, MESSAGE_ID, info): - await self.websocket.send(json.dumps([ 0, "n", MESSAGE_ID, { "type": "ucm-test", "info": info } ])) + async def subscribe(self, channel, **kwargs): + counters = [ bucket.count for bucket in self.buckets ] - async def clear(self): - for chanId in self.chanIds.keys(): - await self.unsubscribe(chanId) + index = counters.index(min(counters)) + + await self.buckets[index]._subscribe(channel, **kwargs) + + def __require_websocket_authentication(function): + @_require_websocket_connection + async def wrapper(self, *args, **kwargs): + if self.authentication == False: + raise WebsocketAuthenticationRequired("To perform this action you need to authenticate using your API_KEY and API_SECRET.") + + await function(self, *args, **kwargs) + + return wrapper + + def __bucket_open_signal(self, index): + if all(bucket.websocket != None and bucket.websocket.open == True for bucket in self.buckets): + self.event_emitter.emit("open") def on(self, event): if event not in BfxWebsocketClient.EVENTS: @@ -143,5 +115,44 @@ class BfxWebsocketClient(object): return handler - def run(self): - asyncio.run(self.connect()) \ No newline at end of file +class _BfxWebsocketBucket(object): + def __init__(self, host, event_emitter, __bucket_open_signal): + self.host, self.event_emitter, self.__bucket_open_signal = host, event_emitter, __bucket_open_signal + + self.websocket, self.chanIds, self.count = None, dict(), 0 + + self.handler = PublicChannelsHandler(event_emitter=self.event_emitter) + + async def _connect(self, index): + async with websockets.connect(self.host) as websocket: + self.websocket = websocket + + self.__bucket_open_signal(index) + + async for message in websocket: + message = json.loads(message) + + if isinstance(message, dict) and message["event"] == "info" and "version" in message: + if BfxWebsocketClient.VERSION != message["version"]: + raise OutdatedClientVersion(f"Mismatch between the client version and the server version. Update the library to the latest version to continue (client version: {BfxWebsocketClient.VERSION}, server version: {message['version']}).") + elif isinstance(message, dict) and message["event"] == "subscribed" and (chanId := message["chanId"]): + self.chanIds[chanId] = message + + self.event_emitter.emit("subscribed", message) + elif isinstance(message, dict) and message["event"] == "unsubscribed" and (chanId := message["chanId"]): + if message["status"] == "OK": + del self.chanIds[chanId] + elif isinstance(message, dict) and message["event"] == "error": + self.event_emitter.emit("wss-error", message["code"], message["msg"]) + elif isinstance(message, list) and (chanId := message[0]) and message[1] != HEARTBEAT: + self.handler.handle(self.chanIds[chanId], *message[1:]) + + @_require_websocket_connection + async def _subscribe(self, channel, **kwargs): + self.count += 1 + + await self.websocket.send(json.dumps({ + "event": "subscribe", + "channel": channel, + **kwargs + })) \ No newline at end of file From 2f561a4fba40153eb039ff082e1ae02bae9a6a97 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Mon, 21 Nov 2022 18:41:40 +0100 Subject: [PATCH 2/6] Fix small bug in bfxapi/websocket/errors.py (__all__). --- bfxapi/websocket/errors.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bfxapi/websocket/errors.py b/bfxapi/websocket/errors.py index 7565bc9..c631cca 100644 --- a/bfxapi/websocket/errors.py +++ b/bfxapi/websocket/errors.py @@ -1,7 +1,9 @@ __all__ = [ - "BfxWebsocketException", "ConnectionNotOpen", - "InvalidAuthenticationCredentials" + "WebsocketAuthenticationRequired", + "InvalidAuthenticationCredentials", + "EventNotSupported", + "OutdatedClientVersion" ] class BfxWebsocketException(Exception): From 721e82b86d89763bcdf1a561132f48982d24e691 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 22 Nov 2022 17:21:21 +0100 Subject: [PATCH 3/6] Add pendings in _BfxWebsocketBucket. Add new logic for selecting the bucket with less connections. Add ._unsubscribe coroutine. --- bfxapi/websocket/BfxWebsocketClient.py | 37 ++++++++++++++++++++------ bfxapi/websocket/__init__.py | 2 +- bfxapi/websocket/errors.py | 8 ++++++ 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/bfxapi/websocket/BfxWebsocketClient.py b/bfxapi/websocket/BfxWebsocketClient.py index 5a92e83..07cc8b4 100644 --- a/bfxapi/websocket/BfxWebsocketClient.py +++ b/bfxapi/websocket/BfxWebsocketClient.py @@ -1,10 +1,10 @@ -import json, asyncio, hmac, hashlib, time, websockets +import json, asyncio, hmac, hashlib, time, uuid, websockets from pyee.asyncio import AsyncIOEventEmitter from .handlers import Channels, PublicChannelsHandler, AuthenticatedChannelsHandler -from .errors import ConnectionNotOpen, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion +from .errors import ConnectionNotOpen, TooManySubscriptions, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion HEARTBEAT = "hb" @@ -77,12 +77,17 @@ class BfxWebsocketClient(object): await self.websocket.send(json.dumps(data)) async def subscribe(self, channel, **kwargs): - counters = [ bucket.count for bucket in self.buckets ] + counters = [ len(bucket.pendings) + len(bucket.chanIds) for bucket in self.buckets ] index = counters.index(min(counters)) await self.buckets[index]._subscribe(channel, **kwargs) + async def unsubscribe(self, chanId): + for bucket in self.buckets: + if chanId in bucket.chanIds.keys(): + await bucket._unsubscribe(chanId=chanId) + def __require_websocket_authentication(function): @_require_websocket_connection async def wrapper(self, *args, **kwargs): @@ -116,10 +121,12 @@ class BfxWebsocketClient(object): return handler class _BfxWebsocketBucket(object): + MAXIMUM_SUBSCRIPTIONS_AMOUNT = 25 + def __init__(self, host, event_emitter, __bucket_open_signal): self.host, self.event_emitter, self.__bucket_open_signal = host, event_emitter, __bucket_open_signal - self.websocket, self.chanIds, self.count = None, dict(), 0 + self.websocket, self.chanIds, self.pendings = None, dict(), list() self.handler = PublicChannelsHandler(event_emitter=self.event_emitter) @@ -136,8 +143,8 @@ class _BfxWebsocketBucket(object): if BfxWebsocketClient.VERSION != message["version"]: raise OutdatedClientVersion(f"Mismatch between the client version and the server version. Update the library to the latest version to continue (client version: {BfxWebsocketClient.VERSION}, server version: {message['version']}).") elif isinstance(message, dict) and message["event"] == "subscribed" and (chanId := message["chanId"]): + self.pendings = [ pending for pending in self.pendings if pending["subId"] != message["subId"] ] self.chanIds[chanId] = message - self.event_emitter.emit("subscribed", message) elif isinstance(message, dict) and message["event"] == "unsubscribed" and (chanId := message["chanId"]): if message["status"] == "OK": @@ -148,11 +155,25 @@ class _BfxWebsocketBucket(object): self.handler.handle(self.chanIds[chanId], *message[1:]) @_require_websocket_connection - async def _subscribe(self, channel, **kwargs): - self.count += 1 + async def _subscribe(self, channel, subId=None, **kwargs): + if len(self.chanIds) + len(self.pendings) == _BfxWebsocketBucket.MAXIMUM_SUBSCRIPTIONS_AMOUNT: + raise TooManySubscriptions("The client has reached the maximum number of subscriptions.") - await self.websocket.send(json.dumps({ + subscription = { "event": "subscribe", "channel": channel, + "subId": subId or str(uuid.uuid4()), + **kwargs + } + + self.pendings.append(subscription) + + await self.websocket.send(json.dumps(subscription)) + + @_require_websocket_connection + async def _unsubscribe(self, chanId): + await self.websocket.send(json.dumps({ + "event": "unsubscribe", + "chanId": chanId })) \ No newline at end of file diff --git a/bfxapi/websocket/__init__.py b/bfxapi/websocket/__init__.py index f36a1b0..dee1570 100644 --- a/bfxapi/websocket/__init__.py +++ b/bfxapi/websocket/__init__.py @@ -1,3 +1,3 @@ from .BfxWebsocketClient import BfxWebsocketClient from .handlers import Channels -from .errors import BfxWebsocketException, ConnectionNotOpen, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion \ No newline at end of file +from .errors import BfxWebsocketException, ConnectionNotOpen, TooManySubscriptions, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion \ No newline at end of file diff --git a/bfxapi/websocket/errors.py b/bfxapi/websocket/errors.py index c631cca..3a1f900 100644 --- a/bfxapi/websocket/errors.py +++ b/bfxapi/websocket/errors.py @@ -1,5 +1,6 @@ __all__ = [ "ConnectionNotOpen", + "TooManySubscriptions", "WebsocketAuthenticationRequired", "InvalidAuthenticationCredentials", "EventNotSupported", @@ -20,6 +21,13 @@ class ConnectionNotOpen(BfxWebsocketException): pass +class TooManySubscriptions(BfxWebsocketException): + """ + This error indicates an attempt to subscribe to a public channel after reaching the limit of simultaneous connections. + """ + + pass + class WebsocketAuthenticationRequired(BfxWebsocketException): """ This error indicates an attempt to access a protected resource without logging in first. From 999766a30711ae53e6294cd91a1f6428dba8d80f Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 22 Nov 2022 18:02:19 +0100 Subject: [PATCH 4/6] Add .close coroutine to BfxWebsocketClient and _BfxWebsocketBucket classes. --- bfxapi/websocket/BfxWebsocketClient.py | 75 +++++++++++++++----------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/bfxapi/websocket/BfxWebsocketClient.py b/bfxapi/websocket/BfxWebsocketClient.py index 07cc8b4..cf4eba6 100644 --- a/bfxapi/websocket/BfxWebsocketClient.py +++ b/bfxapi/websocket/BfxWebsocketClient.py @@ -44,22 +44,24 @@ class BfxWebsocketClient(object): await asyncio.gather(*tasks) async def __connect(self, API_KEY, API_SECRET, filter=None): - async with websockets.connect(self.host) as websocket: + async for websocket in websockets.connect(self.host): self.websocket = websocket - + await self.__authenticate(API_KEY, API_SECRET, filter) - async for message in websocket: - message = json.loads(message) + try: + async for message in websocket: + message = json.loads(message) - if isinstance(message, dict) and message["event"] == "auth": - if message["status"] == "OK": - self.event_emitter.emit("authenticated", message); self.authentication = True - else: raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.") - elif isinstance(message, dict) and message["event"] == "error": - self.event_emitter.emit("wss-error", message["code"], message["msg"]) - elif isinstance(message, list) and (chanId := message[0]) == 0 and message[1] != HEARTBEAT: - self.handler.handle(message[1], message[2]) + if isinstance(message, dict) and message["event"] == "auth": + if message["status"] == "OK": + self.event_emitter.emit("authenticated", message); self.authentication = True + else: raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.") + elif isinstance(message, dict) and message["event"] == "error": + self.event_emitter.emit("wss-error", message["code"], message["msg"]) + elif isinstance(message, list) and (chanId := message[0]) == 0 and message[1] != HEARTBEAT: + self.handler.handle(message[1], message[2]) + except websockets.ConnectionClosedError: continue async def __authenticate(self, API_KEY, API_SECRET, filter=None): data = { "event": "auth", "filter": filter, "apiKey": API_KEY } @@ -88,6 +90,13 @@ class BfxWebsocketClient(object): if chanId in bucket.chanIds.keys(): await bucket._unsubscribe(chanId=chanId) + async def close(self, code=1000, reason=str()): + if self.websocket != None and self.websocket.open == True: + await self.websocket.close(code=code, reason=reason) + + for bucket in self.buckets: + await bucket.close(code=code, reason=reason) + def __require_websocket_authentication(function): @_require_websocket_connection async def wrapper(self, *args, **kwargs): @@ -131,28 +140,30 @@ class _BfxWebsocketBucket(object): self.handler = PublicChannelsHandler(event_emitter=self.event_emitter) async def _connect(self, index): - async with websockets.connect(self.host) as websocket: + async for websocket in websockets.connect(self.host): self.websocket = websocket self.__bucket_open_signal(index) - async for message in websocket: - message = json.loads(message) + try: + async for message in websocket: + message = json.loads(message) - if isinstance(message, dict) and message["event"] == "info" and "version" in message: - if BfxWebsocketClient.VERSION != message["version"]: - raise OutdatedClientVersion(f"Mismatch between the client version and the server version. Update the library to the latest version to continue (client version: {BfxWebsocketClient.VERSION}, server version: {message['version']}).") - elif isinstance(message, dict) and message["event"] == "subscribed" and (chanId := message["chanId"]): - self.pendings = [ pending for pending in self.pendings if pending["subId"] != message["subId"] ] - self.chanIds[chanId] = message - self.event_emitter.emit("subscribed", message) - elif isinstance(message, dict) and message["event"] == "unsubscribed" and (chanId := message["chanId"]): - if message["status"] == "OK": - del self.chanIds[chanId] - elif isinstance(message, dict) and message["event"] == "error": - self.event_emitter.emit("wss-error", message["code"], message["msg"]) - elif isinstance(message, list) and (chanId := message[0]) and message[1] != HEARTBEAT: - self.handler.handle(self.chanIds[chanId], *message[1:]) + if isinstance(message, dict) and message["event"] == "info" and "version" in message: + if BfxWebsocketClient.VERSION != message["version"]: + raise OutdatedClientVersion(f"Mismatch between the client version and the server version. Update the library to the latest version to continue (client version: {BfxWebsocketClient.VERSION}, server version: {message['version']}).") + elif isinstance(message, dict) and message["event"] == "subscribed" and (chanId := message["chanId"]): + self.pendings = [ pending for pending in self.pendings if pending["subId"] != message["subId"] ] + self.chanIds[chanId] = message + self.event_emitter.emit("subscribed", message) + elif isinstance(message, dict) and message["event"] == "unsubscribed" and (chanId := message["chanId"]): + if message["status"] == "OK": + del self.chanIds[chanId] + elif isinstance(message, dict) and message["event"] == "error": + self.event_emitter.emit("wss-error", message["code"], message["msg"]) + elif isinstance(message, list) and (chanId := message[0]) and message[1] != HEARTBEAT: + self.handler.handle(self.chanIds[chanId], *message[1:]) + except websockets.ConnectionClosedError: continue @_require_websocket_connection async def _subscribe(self, channel, subId=None, **kwargs): @@ -176,4 +187,8 @@ class _BfxWebsocketBucket(object): await self.websocket.send(json.dumps({ "event": "unsubscribe", "chanId": chanId - })) \ No newline at end of file + })) + + @_require_websocket_connection + async def close(self, code=1000, reason=str()): + await self.websocket.close(code=code, reason=reason) \ No newline at end of file From a8dec5c6bcde45dced71542eb43085fa7063ab06 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 22 Nov 2022 18:11:05 +0100 Subject: [PATCH 5/6] Rename bfxapi/websocket/errors.py to exceptions.py. Add Errors enumeration inside BfxWebsocketClient.py and bfxapi/websocket/__init__.py. --- bfxapi/websocket/BfxWebsocketClient.py | 23 +++++++++++++++++-- bfxapi/websocket/__init__.py | 4 ++-- bfxapi/websocket/{errors.py => exceptions.py} | 0 bfxapi/websocket/handlers.py | 2 +- bfxapi/websocket/serializers.py | 2 +- 5 files changed, 25 insertions(+), 6 deletions(-) rename bfxapi/websocket/{errors.py => exceptions.py} (100%) diff --git a/bfxapi/websocket/BfxWebsocketClient.py b/bfxapi/websocket/BfxWebsocketClient.py index cf4eba6..e040530 100644 --- a/bfxapi/websocket/BfxWebsocketClient.py +++ b/bfxapi/websocket/BfxWebsocketClient.py @@ -1,10 +1,12 @@ import json, asyncio, hmac, hashlib, time, uuid, websockets +from enum import Enum + from pyee.asyncio import AsyncIOEventEmitter from .handlers import Channels, PublicChannelsHandler, AuthenticatedChannelsHandler -from .errors import ConnectionNotOpen, TooManySubscriptions, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion +from .exceptions import ConnectionNotOpen, TooManySubscriptions, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion HEARTBEAT = "hb" @@ -191,4 +193,21 @@ class _BfxWebsocketBucket(object): @_require_websocket_connection async def close(self, code=1000, reason=str()): - await self.websocket.close(code=code, reason=reason) \ No newline at end of file + await self.websocket.close(code=code, reason=reason) + +class Errors(int, Enum): + ERR_UNK = 10000 + ERR_GENERIC = 10001 + ERR_CONCURRENCY = 10008 + ERR_PARAMS = 10020 + ERR_CONF_FAIL = 10050 + ERR_AUTH_FAIL = 10100 + ERR_AUTH_PAYLOAD = 10111 + ERR_AUTH_SIG = 10112 + ERR_AUTH_HMAC = 10113 + ERR_AUTH_NONCE = 10114 + ERR_UNAUTH_FAIL = 10200 + ERR_SUB_FAIL = 10300 + ERR_SUB_MULTI = 10301 + ERR_UNSUB_FAIL = 10400 + ERR_READY = 11000 \ No newline at end of file diff --git a/bfxapi/websocket/__init__.py b/bfxapi/websocket/__init__.py index dee1570..4704773 100644 --- a/bfxapi/websocket/__init__.py +++ b/bfxapi/websocket/__init__.py @@ -1,3 +1,3 @@ -from .BfxWebsocketClient import BfxWebsocketClient +from .BfxWebsocketClient import BfxWebsocketClient, Errors from .handlers import Channels -from .errors import BfxWebsocketException, ConnectionNotOpen, TooManySubscriptions, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion \ No newline at end of file +from .exceptions import BfxWebsocketException, ConnectionNotOpen, TooManySubscriptions, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion \ No newline at end of file diff --git a/bfxapi/websocket/errors.py b/bfxapi/websocket/exceptions.py similarity index 100% rename from bfxapi/websocket/errors.py rename to bfxapi/websocket/exceptions.py diff --git a/bfxapi/websocket/handlers.py b/bfxapi/websocket/handlers.py index e6f7dab..dd85b76 100644 --- a/bfxapi/websocket/handlers.py +++ b/bfxapi/websocket/handlers.py @@ -2,7 +2,7 @@ from enum import Enum from . import serializers -from .errors import BfxWebsocketException +from .exceptions import BfxWebsocketException def _get_sub_dictionary(dictionary, keys): return { key: dictionary[key] for key in dictionary if key in keys } diff --git a/bfxapi/websocket/serializers.py b/bfxapi/websocket/serializers.py index 47cd770..b8796fd 100644 --- a/bfxapi/websocket/serializers.py +++ b/bfxapi/websocket/serializers.py @@ -1,4 +1,4 @@ -from .errors import BfxWebsocketException +from .exceptions import BfxWebsocketException class _Serializer(object): def __init__(self, name, labels): From f24ed520751044d661d8110386428e0cc8516ebb Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Tue, 22 Nov 2022 18:13:06 +0100 Subject: [PATCH 6/6] Add setup.py file in project root. --- setup.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9eff973 --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +from distutils.core import setup + +setup( + name="bitfinex-api-py", + version="3.0.0", + packages=[ "bfxapi", "bfxapi.websocket" ], + url="https://github.com/bitfinexcom/bitfinex-api-py", + license="OSI Approved :: Apache Software License", + author="Bitfinex", + author_email="support@bitfinex.com", + description="Official Bitfinex Python API", + keywords="bitfinex,api,trading", + install_requires=[ + "pyee~=9.0.4", + "typing_extensions~=4.4.0", + "websockets~=10.4", + ], + project_urls={ + "Bug Reports": "https://github.com/bitfinexcom/bitfinex-api-py/issues", + "Source": "https://github.com/bitfinexcom/bitfinex-api-py", + } +) \ No newline at end of file