Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
59a0dca
Improve and rewrite bfxapi.websocket.subscriptions.
Davi0kProgramsThings May 19, 2023
57680ab
Rename bfxapi.websocket.handlers.authenticated_events_handler to auth…
Davi0kProgramsThings May 19, 2023
c8290f1
Upgrade to Mypy 1.3.0 (old: 0.991). Fix compatibility problems with M…
Davi0kProgramsThings May 19, 2023
dbc61ab
Add type hints and type checks in bfxapi.websocket.client.bfx_websock…
Davi0kProgramsThings May 25, 2023
bc0f83d
Improve JSONEncoder class in bfxapi.utils.json_encoder.
Davi0kProgramsThings May 26, 2023
7059846
Remove support for datetime type and improve typing in several files.
Davi0kProgramsThings May 26, 2023
708fdc8
Add new event liquidation_feed_update to PublicChannelsHandler (and i…
Davi0kProgramsThings Jun 12, 2023
f343fce
Fix comment on top of examples in both examples.rest.auth and example…
Davi0kProgramsThings Jun 12, 2023
d63c2c6
Rename RestAuthenticatedEndpoints to RestAuthEndpoints (and bfxapi.re…
Davi0kProgramsThings Jun 12, 2023
cc5f9f5
Remove type hinting for decorators _require_websocket_connection and …
Davi0kProgramsThings Jun 12, 2023
b12fedb
Replace use of asyncio.locks.Event with asyncio.locks.Condition in bf…
Davi0kProgramsThings Jun 17, 2023
d9733e8
Change visibility of decorators require_websocket_connection and requ…
Davi0kProgramsThings Jun 17, 2023
fc84389
Remove support for BfxWebSocketClient's instance variable events_for_…
Davi0kProgramsThings Jun 17, 2023
1d911a2
Rename bfxapi.websocket.client to _client and bfxapi.websocket.handle…
Davi0kProgramsThings Jun 17, 2023
080ec40
Add sub-package bfxapi.websocket._event_emitter (with bfx_event_emitt…
Davi0kProgramsThings Jun 18, 2023
8b196b8
Add type hinting support to bfxapi.websocket.client.bfx_websocket_buc…
Davi0kProgramsThings Jun 18, 2023
f1e678e
Add type hinting support to bfxapi.websocket.client.bfx_websocket_cli…
Davi0kProgramsThings Jun 19, 2023
9edbd7a
Rename bfxapi.utils to _utils (and update references).
Davi0kProgramsThings Jun 19, 2023
bae48b2
Improve wss_timeout implementation in BfxWebSocketClient.
Davi0kProgramsThings Jun 20, 2023
755ee76
Improve bfxapi._utils.logger (and update usage in Client).
Davi0kProgramsThings Jun 23, 2023
faffb7f
Add and implement new IncompleteCredentialError in bfxapi.client.
Davi0kProgramsThings Jun 23, 2023
4ba6b28
Rename bfxapi._utils.logger to bfxapi._utils.logging (and update refe…
Davi0kProgramsThings Jun 23, 2023
da2b411
Fix missing return statement in public_channels_handler.__raw_book_ch…
Davi0kProgramsThings Jul 25, 2023
3038027
Add fix to handle InvalidStatusCode exception (for 408 Request Timeout).
Davi0kProgramsThings Jul 25, 2023
d9267de
Add config to enable checksums in BfxWebSocketBucket.
Davi0kProgramsThings Jul 28, 2023
3c02232
Add event handler for checksum messages (PublicChannelsHandler).
Davi0kProgramsThings Jul 28, 2023
ce23a89
Block negative checksums for possible race condition (PublicChannelsH…
Davi0kProgramsThings Jul 28, 2023
26f25e5
Fix bug in method BfxWebSocketBucket::unsubscribe.
Davi0kProgramsThings Jul 28, 2023
f39b054
Add implementation for BfxWebSocketClient::resubscribe and BfxWebSock…
Davi0kProgramsThings Jul 28, 2023
3a06b22
Add order book checksum handling in /examples/websocket/public/order_…
Davi0kProgramsThings Jul 28, 2023
875d1d6
Add order book checksum handling in /examples/websocket/public/raw_or…
Davi0kProgramsThings Jul 28, 2023
f6c49f6
Remove block for negative checksums (and replace crcmod with native z…
Davi0kProgramsThings Jul 28, 2023
8a1632d
Write new implementation for class BfxEventEmitter (bfxapi.websocket.…
Davi0kProgramsThings Oct 1, 2023
ca4050a
Rename event <disconnection> to <disconnected> (to mantain compliance).
Davi0kProgramsThings Oct 1, 2023
82a3307
Fix bug in local class _Delay (bfx_websocket_client.py).
Davi0kProgramsThings Oct 1, 2023
22451f6
Remove inner class Connection.Authenticable (_connection.py).
Davi0kProgramsThings Oct 1, 2023
206ebe7
Remove circular import from file bfx_websocket_client.py.
Davi0kProgramsThings Oct 1, 2023
628c3a0
Rewrite implementation for abstract class Connection (_connection.py).
Davi0kProgramsThings Oct 2, 2023
5ae576e
Fix and rewrite all logic in class BfxWebSocketBucket.
Davi0kProgramsThings Oct 8, 2023
9872adf
Fix type hinting in module bfxapi._utils.json_encoder.
Davi0kProgramsThings Oct 8, 2023
de0ee54
Add new module bfxapi._utils.json_decoder.
Davi0kProgramsThings Oct 8, 2023
25881e7
Fix and rewrite some logic in class BfxWebSocketClient.
Davi0kProgramsThings Oct 9, 2023
378e89b
Fix small bug in module bfxapi.exceptions.
Davi0kProgramsThings Oct 9, 2023
122d692
Rewrite all logic regarding connection multiplexing.
Davi0kProgramsThings Oct 13, 2023
374b632
Add pause/resume logic in class BfxWebSocketClient.
Davi0kProgramsThings Oct 13, 2023
e5ec94b
Remove wss-event event from BfxWebSocketClient and BfxWebSocketBucket.
Davi0kProgramsThings Oct 13, 2023
133db74
Add automatic deletion for buckets that reach zero subscriptions (e.g…
Davi0kProgramsThings Oct 16, 2023
ddce83b
Apply some refactoring to sub-package bfxapi.websocket.
Davi0kProgramsThings Oct 16, 2023
ac50f8f
Fix and rewrite module bfx_websocket_inputs in bfxapi.websocket._client.
Davi0kProgramsThings Oct 25, 2023
8e915e4
Improve fidelity to pylint's standard rules.
Davi0kProgramsThings Oct 26, 2023
2734ff9
Drop modules bfxapi.enums, bfxapi.rest.enums and bfxapi.websocket.enums.
Davi0kProgramsThings Oct 26, 2023
c02d6d7
Fix bug in module bfxapi.websocket._event_emitter.
Davi0kProgramsThings Oct 26, 2023
b082891
Remove useless and redundant docstrings from custom exceptions.
Davi0kProgramsThings Oct 26, 2023
77494de
Remove old test suite in module bfxapi.tests.
Davi0kProgramsThings Oct 26, 2023
36c48c3
Apply small changes to .github/ISSUE_TEMPLATE.md and .github/PULL_REQ…
Davi0kProgramsThings Oct 26, 2023
1e7a4d5
Upgrade dependencies in requirements.txt and dev-requirements.txt.
Davi0kProgramsThings Oct 26, 2023
2bed2f6
Fix bug in cancel_order_multi (both rest and websocket).
Davi0kProgramsThings Oct 26, 2023
8c65ba5
Rename property 'renew' to 'op_renew' in get_deposit_address.
Davi0kProgramsThings Oct 26, 2023
9287723
Fix several bugs in sub-package bfxapi.rest.endpoints.
Davi0kProgramsThings Oct 26, 2023
1ec6c49
Rewrite all rest examples according to v3.0.0b3's changes.
Davi0kProgramsThings Oct 26, 2023
1accf92
Rewrite all websocket examples according to v3.0.0b3's changes.
Davi0kProgramsThings Oct 26, 2023
5e50aa6
Fix bug in BfxWebSocketClient::on's arguments (bfxapi/websocket/_clie…
Davi0kProgramsThings Oct 26, 2023
f3fe14b
Add 'checksum' event in sub-package bfxapi.websocket._event_emitter.
Davi0kProgramsThings Oct 26, 2023
afca5e3
Add support for Python 3.11 (edit setup.py).
Davi0kProgramsThings Oct 26, 2023
f63224c
Bump __version__ in file bfxapi/_version.py to v3.0.0b3.
Davi0kProgramsThings Oct 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,5 @@ A possible solution could be...

### Python version
<!-- Indicate your python version here -->
<!-- You can print it using `python3 --version`-->
<!-- You can print it using `python3 --version` -->
Python 3.10.6 x64

### Mypy version
<!-- Indicate your mypy version here -->
<!-- You can print it using `python3 -m mypy --version`-->
mypy 0.991 (compiled: yes)
3 changes: 0 additions & 3 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,5 @@ PR fixes the following issue:
- [ ] 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;
- [ ] I have added tests that prove my fix is effective or that my feature works;
- [ ] New and existing unit tests pass locally with my changes;
- [ ] 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 have updated the library version and updated the CHANGELOG;
2 changes: 0 additions & 2 deletions .github/workflows/bitfinex-api-py-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,3 @@ jobs:
run: python -m pylint bfxapi
- name: Run mypy to check the correctness of type hinting (and fail if any error or warning is found)
run: python -m mypy bfxapi
- name: Execute project's unit tests (unittest)
run: python -m unittest bfxapi.tests
16 changes: 4 additions & 12 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,20 @@ py-version=3.8.0

[MESSAGES CONTROL]
disable=
multiple-imports,
missing-docstring,
logging-not-lazy,
logging-fstring-interpolation,
multiple-imports,
too-few-public-methods,
too-many-public-methods,
too-many-instance-attributes,
dangerous-default-value,
inconsistent-return-statements,

[SIMILARITIES]
min-similarity-lines=6
too-many-instance-attributes

[VARIABLES]
allowed-redefined-builtins=type,dir,id,all,format,len
allowed-redefined-builtins=all,dir,format,id,len,type

[FORMAT]
max-line-length=120
expected-line-ending-format=LF

[BASIC]
good-names=id,on,pl,t,ip,tf,A,B,C,D,E,F
good-names=f,t,id,ip,on,pl,tf,to,A,B,C,D,E,F

[TYPECHECK]
generated-members=websockets
Expand Down
11 changes: 0 additions & 11 deletions .travis.yml

This file was deleted.

51 changes: 4 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ _Revoke your API-KEYs and API-SECRETs immediately if you think they might have b

### Advanced features
* [Using custom notifications](#using-custom-notifications)
* [Setting up connection multiplexing](#setting-up-connection-multiplexing)

### Examples
* [Creating a new order](#creating-a-new-order)
Expand Down Expand Up @@ -181,10 +180,10 @@ A custom [close code number](https://www.iana.org/assignments/websocket/websocke
await bfx.wss.close(code=1001, reason="Going Away")
```

After closing the connection, the client will emit the `disconnection` event:
After closing the connection, the client will emit the `disconnected` event:
```python
@bfx.wss.on("disconnection")
def on_disconnection(code: int, reason: str):
@bfx.wss.on("disconnected")
def on_disconnected(code: int, reason: str):
if code == 1000 or code == 1001:
print("Closing the connection without errors!")
```
Expand All @@ -201,7 +200,7 @@ On each successful subscription, the client will emit the `subscribed` event:
@bfx.wss.on("subscribed")
def on_subscribed(subscription: subscriptions.Subscription):
if subscription["channel"] == "ticker":
print(f"{subscription['symbol']}: {subscription['subId']}") # tBTCUSD: f2757df2-7e11-4244-9bb7-a53b7343bef8
print(f"{subscription['symbol']}: {subscription['sub_id']}") # tBTCUSD: f2757df2-7e11-4244-9bb7-a53b7343bef8
```

### Unsubscribing from a public channel
Expand Down Expand Up @@ -242,11 +241,6 @@ The same can be done without using decorators:
bfx.wss.on("candles_update", callback=on_candles_update)
```

You can pass any number of events to register for the same callback function:
```python
bfx.wss.on("t_ticker_update", "f_ticker_update", callback=on_ticker_update)
```

# Advanced features

## Using custom notifications
Expand All @@ -269,27 +263,6 @@ def on_notification(notification: Notification[Any]):
print(notification.data) # { "foo": 1 }
```

## Setting up connection multiplexing

`BfxWebSocketClient::run` and `BfxWebSocketClient::start` accept a `connections` argument:
```python
bfx.wss.run(connections=3)
```

`connections` indicates the number of connections to run concurrently (through connection multiplexing).

Each of these connections can handle up to 25 subscriptions to public channels. \
So, using `N` connections will allow the client to handle at most `N * 25` subscriptions. \
You should always use the minimum number of connections necessary to handle all the subscriptions that will be made.

For example, if you know that your application will subscribe to 75 public channels, 75 / 25 = 3 connections will be enough to handle all the subscriptions.

The default number of connections is 5; therefore, if the `connections` argument is not given, the client will be able to handle a maximum of 25 * 5 = 125 subscriptions.

Keep in mind that using a large number of connections could slow down the client performance.

The use of more than 20 connections is not recommended.

# Examples

## Creating a new order
Expand Down Expand Up @@ -340,7 +313,6 @@ Contributors must uphold the [Contributor Covenant code of conduct](https://gith
* [Cloning the repository](#cloning-the-repository)
* [Installing the dependencies](#installing-the-dependencies)
2. [Before opening a PR](#before-opening-a-pr)
* [Running the unit tests](#running-the-unit-tests)
3. [License](#license)

## Installation and setup
Expand Down Expand Up @@ -376,24 +348,9 @@ Wheter you're submitting a bug fix, a new feature or a documentation change, you
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.

Before opening a pull request, you should also make sure that:
- [ ] all unit tests pass (see [Running the unit tests](#running-the-unit-tests)).
- [ ] [`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).

### Running the unit tests

`bitfinex-api-py` comes with a set of unit tests (written using the [`unittest`](https://docs.python.org/3.8/library/unittest.html) unit testing framework). \
Contributors must ensure that each unit test passes before opening a pull request. \
You can run all project's unit tests by calling `unittest` on `bfxapi.tests`:
```console
python3 -m unittest -v bfxapi.tests
```

A single unit test can be run as follows:
```console
python3 -m unittest -v bfxapi.tests.test_notification
```

## License

```
Expand Down
12 changes: 6 additions & 6 deletions bfxapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .client import Client

from .urls import REST_HOST, PUB_REST_HOST, \
WSS_HOST, PUB_WSS_HOST

from .version import __version__
from ._client import \
Client, \
REST_HOST, \
WSS_HOST, \
PUB_REST_HOST, \
PUB_WSS_HOST
52 changes: 52 additions & 0 deletions bfxapi/_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from typing import \
TYPE_CHECKING, List, Optional

from bfxapi._utils.logging import ColorLogger

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

REST_HOST = "https://api.bitfinex.com/v2"
WSS_HOST = "wss://api.bitfinex.com/ws/2"

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
) -> None:
credentials: Optional["_Credentials"] = None

if api_key and api_secret:
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).")
elif 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)

logger = ColorLogger("bfxapi", level="INFO")

if log_filename:
logger.register(filename=log_filename)

self.wss = BfxWebSocketClient(wss_host, \
credentials=credentials, timeout=timeout, logger=logger)
File renamed without changes.
13 changes: 13 additions & 0 deletions bfxapi/_utils/json_decoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from typing import Dict, Any

import re, json

def _to_snake_case(string: str) -> str:
return re.sub(r"(?<!^)(?=[A-Z])", "_", string).lower()

def _object_hook(data: Dict[str, Any]) -> Any:
return { _to_snake_case(key): value for key, value in data.items() }

class JSONDecoder(json.JSONDecoder):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(object_hook=_object_hook, *args, **kwargs)
36 changes: 36 additions & 0 deletions bfxapi/_utils/json_encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from typing import \
Union, List, Dict, \
Any

import json

from decimal import Decimal

_ExtJSON = Union[Dict[str, "_ExtJSON"], List["_ExtJSON"], \
bool, int, float, str, Decimal, 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 }

def _adapter(data: _ExtJSON) -> _StrictJSON:
if isinstance(data, bool):
return int(data)
if isinstance(data, float):
return format(Decimal(repr(data)), "f")
if isinstance(data, Decimal):
return format(data, "f")

if isinstance(data, list):
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 data

class JSONEncoder(json.JSONEncoder):
def encode(self, o: _ExtJSON) -> str:
return super().encode(_adapter(o))
67 changes: 67 additions & 0 deletions bfxapi/_utils/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from typing import \
TYPE_CHECKING, Literal, Optional

#pylint: disable-next=wildcard-import,unused-wildcard-import
from logging import *

from copy import copy

import sys

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) ]

_NC = "\033[0m"

class _ColorFormatter(Formatter):
__LEVELS = {
"INFO": _BLUE,
"WARNING": _YELLOW,
"ERROR": _RED,
"CRITICAL": _BOLD_RED,
"DEBUG": _BOLD_WHITE
}

def format(self, record: LogRecord) -> str:
_record = copy(record)
_record.name = _MAGENTA + record.name + _NC
_record.levelname = _ColorFormatter.__format_level(record.levelname)

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

@staticmethod
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)

def __init__(self, name: str, level: "_Level" = "NOTSET") -> None:
super().__init__(name, level)

formatter = _ColorFormatter(_FORMAT, _DATE_FORMAT)

handler = StreamHandler(stream=sys.stderr)
handler.setFormatter(fmt=formatter)
self.addHandler(hdlr=handler)

def register(self, filename: str) -> None:
handler = FileHandler(filename=filename)
handler.setFormatter(fmt=ColorLogger.__FORMATTER)
self.addHandler(hdlr=handler)
1 change: 1 addition & 0 deletions bfxapi/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "3.0.0b3"
Loading