From 1754d4c19e51b262391b3044a6a368c97615b42c Mon Sep 17 00:00:00 2001 From: MtkN1 <51289448+MtkN1@users.noreply.github.com> Date: Tue, 28 Sep 2021 20:28:37 +0900 Subject: [PATCH 1/4] Update to v0.7.1 --- pybotters/__init__.py | 2 + pybotters/models/bitflyer.py | 280 ++++++++++++++++++ pybotters/models/experimental/__init__.py | 7 + .../bybit.py} | 12 +- pybotters/store.py | 25 ++ pybotters/ws.py | 6 +- 6 files changed, 328 insertions(+), 4 deletions(-) create mode 100644 pybotters/models/bitflyer.py create mode 100644 pybotters/models/experimental/__init__.py rename pybotters/models/{experimental.py => experimental/bybit.py} (98%) diff --git a/pybotters/__init__.py b/pybotters/__init__.py index 6f8fc9e..1677609 100644 --- a/pybotters/__init__.py +++ b/pybotters/__init__.py @@ -9,6 +9,7 @@ from .models import experimental from .models.binance import BinanceDataStore from .models.bitbank import bitbankDataStore +from .models.bitflyer import bitFlyerDataStore from .models.bitmex import BitMEXDataStore from .models.bybit import BybitDataStore from .models.ftx import FTXDataStore @@ -26,6 +27,7 @@ 'FTXDataStore', 'BinanceDataStore', 'bitbankDataStore', + 'bitFlyerDataStore', 'BitMEXDataStore', 'GMOCoinDataStore', 'experimental', diff --git a/pybotters/models/bitflyer.py b/pybotters/models/bitflyer.py new file mode 100644 index 0000000..47843f2 --- /dev/null +++ b/pybotters/models/bitflyer.py @@ -0,0 +1,280 @@ +import asyncio +import logging +import operator +from decimal import Decimal +from typing import Awaitable, Dict, List + +import aiohttp + +from ..store import DataStore, DataStoreManager +from ..typedefs import Item +from ..ws import ClientWebSocketResponse + +logger = logging.getLogger(__name__) + + +class bitFlyerDataStore(DataStoreManager): + def _init(self) -> None: + self.create('board', datastore_class=Board) + self.create('ticker', datastore_class=Ticker) + self.create('executions', datastore_class=Executions) + self.create('childorderevents', datastore_class=ChildOrderEvents) + self.create('childorders', datastore_class=ChildOrders) + self.create('parentorderevents', datastore_class=ParentOrderEvents) + self.create('parentorders', datastore_class=ParentOrders) + self.create('positions', datastore_class=Positions) + self._snapshots = set() + + async def initialize(self, *aws: Awaitable[aiohttp.ClientResponse]) -> None: + for f in asyncio.as_completed(aws): + resp = await f + data = await resp.json() + if resp.url.path == '/v1/me/getchildorders': + self.childorders._onresponse(data) + elif resp.url.path == '/v1/me/getparentorders': + self.parentorders._onresponse(data) + elif resp.url.path == '/v1/me/getpositions': + self.positions._onresponse(data) + + def _onmessage(self, msg: Item, ws: ClientWebSocketResponse) -> None: + if 'error' in msg: + logger.warning(msg) + if 'params' in msg: + channel: str = msg['params']['channel'] + message = msg['params']['message'] + if channel.startswith('lightning_board_'): + if channel.startswith('lightning_board_snapshot_'): + asyncio.create_task( + ws.send_json( + { + 'method': 'unsubscribe', + 'params': {'channel': channel}, + } + ) + ) + product_code = channel.replace('lightning_board_snapshot_', '') + self.board._delete(self.board.find({'product_code': product_code})) + self._snapshots.add(product_code) + else: + product_code = channel.replace('lightning_board_', '') + if product_code in self._snapshots: + self.board._onmessage(product_code, message) + elif channel.startswith('lightning_ticker_'): + self.ticker._onmessage(message) + elif channel.startswith('lightning_executions_'): + self.executions._onmessage(message) + elif channel == 'child_order_events': + self.childorderevents._onmessage(message) + self.childorders._onmessage(message) + self.positions._onmessage(message) + elif channel == 'parent_order_events': + self.parentorderevents._onmessage(message) + self.parentorders._onmessage(message) + + @property + def board(self) -> 'Board': + return self.get('board', Board) + + @property + def ticker(self) -> 'Ticker': + return self.get('ticker', Ticker) + + @property + def executions(self) -> 'Executions': + return self.get('executions', Executions) + + @property + def childorderevents(self) -> 'ChildOrderEvents': + return self.get('childorderevents', ChildOrderEvents) + + @property + def childorders(self) -> 'ChildOrders': + return self.get('childorders', ChildOrders) + + @property + def parentorderevents(self) -> 'ParentOrderEvents': + return self.get('parentorderevents', ParentOrderEvents) + + @property + def parentorders(self) -> 'ParentOrders': + return self.get('parentorders', ParentOrders) + + @property + def positions(self) -> 'Positions': + return self.get('positions', Positions) + + +class Board(DataStore): + _KEYS = ['product_code', 'side', 'price'] + + def _init(self) -> None: + self.mid_price: Dict[str, float] = {} + + def sorted(self, query: Item = {}) -> Dict[str, List[Item]]: + result = {'SELL': [], 'BUY': []} + for item in self: + if all(k in item and query[k] == item[k] for k in query): + result[item['side']].append(item) + result['SELL'].sort(key=lambda x: x['price']) + result['BUY'].sort(key=lambda x: x['price'], reverse=True) + return result + + def _onmessage(self, product_code: str, message: Item) -> None: + self.mid_price[product_code] = message['mid_price'] + for key, side in (('bids', 'BUY'), ('asks', 'SELL')): + for item in message[key]: + if item['size']: + self._insert([{'product_code': product_code, 'side': side, **item}]) + else: + self._delete([{'product_code': product_code, 'side': side, **item}]) + board = self.sorted({'product_code': product_code}) + targets = [] + for side, ope in (('BUY', operator.le), ('SELL', operator.gt)): + for item in board[side]: + if ope(item['price'], message['mid_price']): + break + else: + targets.append(item) + self._delete(targets) + + +class Ticker(DataStore): + _KEYS = ['product_code'] + + def _onmessage(self, message: Item) -> None: + self._update([message]) + + +class Executions(DataStore): + _MAXLEN = 99999 + + def _onmessage(self, message: List[Item]) -> None: + self._insert(message) + + +class ChildOrderEvents(DataStore): + def _onmessage(self, message: List[Item]) -> None: + self._insert(message) + + +class ParentOrderEvents(DataStore): + def _onmessage(self, message: List[Item]) -> None: + self._insert(message) + + +class ChildOrders(DataStore): + _KEYS = ['child_order_acceptance_id'] + + def _onresponse(self, data: List[Item]) -> None: + if data: + self._delete(self.find({'product_code': data[0]['product_code']})) + for item in data: + if item['child_order_state'] == 'ACTIVE': + self._insert([item]) + + def _onmessage(self, message: List[Item]) -> None: + for item in message: + if item['event_type'] == 'ORDER': + self._insert([item]) + elif item['event_type'] in ('CANCEL', 'EXPIRE'): + self._delete([item]) + elif item['event_type'] == 'EXECUTION': + if item['outstanding_size']: + childorder = self.get(item) + if childorder: + if isinstance(childorder['size'], int) and isinstance( + item['size'], int + ): + childorder['size'] -= item['size'] + else: + childorder['size'] = float( + Decimal(str(childorder['size'])) + - Decimal(str(item['size'])) + ) + else: + self._delete([item]) + + +class ParentOrders(DataStore): + _KEYS = ['parent_order_acceptance_id'] + + def _onresponse(self, data: List[Item]) -> None: + if data: + self._delete(self.find({'product_code': data[0]['product_code']})) + for item in data: + if item['parent_order_state'] == 'ACTIVE': + self._insert([item]) + + def _onmessage(self, message: List[Item]) -> None: + for item in message: + if item['event_type'] == 'ORDER': + self._insert([item]) + elif item['event_type'] in ('CANCEL', 'EXPIRE'): + self._delete([item]) + elif item['event_type'] == 'COMPLETE': + parentorder = self.get(item) + if parentorder: + if parentorder['parent_order_type'] in ('IFD', 'IFDOCO'): + if item['parameter_index'] >= 2: + self._delete([item]) + else: + self._delete([item]) + + +class Positions(DataStore): + _COMMON_KEYS = [ + 'product_code', + 'side', + 'price', + 'size', + 'commission', + 'sfd', + ] + + def _common_keys(self, item: Item) -> Item: + return {key: item[key] for key in self._COMMON_KEYS} + + def _onresponse(self, data: List[Item]) -> None: + if data: + self._delete(self.find({'product_code': data[0]['product_code']})) + for item in data: + self._insert([self._common_keys(item)]) + + def _onmessage(self, message: List[Item]) -> None: + for item in message: + if item['event_type'] == 'EXECUTION': + positions = self._find_with_uuid({'product_code': item['product_code']}) + if positions: + if positions[next(iter(positions))]['side'] == item['side']: + self._insert([self._common_keys(item)]) + else: + for uid, pos in positions.items(): + if pos['size'] > item['size']: + if isinstance(pos['size'], int) and isinstance( + item['size'], int + ): + pos['size'] -= item['size'] + else: + pos['size'] = float( + Decimal(str(pos['size'])) + - Decimal(str(item['size'])) + ) + break + else: + if isinstance(pos['size'], int) and isinstance( + item['size'], int + ): + item['size'] -= pos['size'] + else: + item['size'] = float( + Decimal(str(item['size'])) + - Decimal(str(pos['size'])) + ) + self._remove([uid]) + if not pos['size']: + break + else: + try: + self._insert([self._common_keys(item)]) + except KeyError: + pass diff --git a/pybotters/models/experimental/__init__.py b/pybotters/models/experimental/__init__.py new file mode 100644 index 0000000..659875c --- /dev/null +++ b/pybotters/models/experimental/__init__.py @@ -0,0 +1,7 @@ +from typing import Tuple +from .bybit import BybitInverseDataStore, BybitUSDTDataStore + +__all__: Tuple[str, ...] = ( + 'BybitInverseDataStore', + 'BybitUSDTDataStore', +) diff --git a/pybotters/models/experimental.py b/pybotters/models/experimental/bybit.py similarity index 98% rename from pybotters/models/experimental.py rename to pybotters/models/experimental/bybit.py index 1261edb..00c2f3a 100644 --- a/pybotters/models/experimental.py +++ b/pybotters/models/experimental/bybit.py @@ -4,9 +4,9 @@ import aiohttp -from ..store import DataStore, DataStoreManager -from ..typedefs import Item -from ..ws import ClientWebSocketResponse +from ...store import DataStore, DataStoreManager +from ...typedefs import Item +from ...ws import ClientWebSocketResponse logger = logging.getLogger(__name__) @@ -531,6 +531,12 @@ class PositionUSDT(PositionInverse): ], } + def one(self, symbol: str) -> Dict[str, Optional[Item]]: + return { + "Sell": self.get({"symbol": symbol, "side": "Sell"}), + "Buy": self.get({"symbol": symbol, "side": "Buy"}), + } + def both(self, symbol: str) -> Dict[str, Optional[Item]]: return { "Sell": self.get({"symbol": symbol, "side": "Sell"}), diff --git a/pybotters/store.py b/pybotters/store.py index 96164c0..5fde3f2 100644 --- a/pybotters/store.py +++ b/pybotters/store.py @@ -103,6 +103,21 @@ def _delete(self, data: List[Item]) -> None: # !TODO! This behaviour might be undesirable. self._set(data) + def _remove(self, uuids: List[uuid.UUID]) -> None: + if self._keys: + for _id in uuids: + if _id in self._data: + item = self._data[_id] + keyhash = self._hash({k: item[k] for k in self._keys}) + del self._data[_id] + del self._index[keyhash] + else: + for _id in uuids: + if _id in self._data: + del self._data[_id] + # !TODO! This behaviour might be undesirable. + self._set([]) + def _clear(self) -> None: self._data.clear() self._index.clear() @@ -160,6 +175,16 @@ def find(self, query: Item = {}) -> List[Item]: else: return list(self) + def _find_with_uuid(self, query: Item = {}) -> Dict[uuid.UUID, Item]: + if query: + return { + _id: item + for _id, item in self._data.items() + if all(k in item and query[k] == item[k] for k in query) + } + else: + return self._data + def _find_and_delete(self, query: Item = {}) -> List[Item]: if query: ret = [ diff --git a/pybotters/ws.py b/pybotters/ws.py index fab6eca..e00fe29 100644 --- a/pybotters/ws.py +++ b/pybotters/ws.py @@ -80,7 +80,11 @@ async def ws_run_forever( logger.error(repr(e)) elif msg.type == aiohttp.WSMsgType.ERROR: break - except (aiohttp.WSServerHandshakeError, aiohttp.ClientOSError) as e: + except ( + aiohttp.WSServerHandshakeError, + aiohttp.ClientOSError, + ConnectionResetError, + ) as e: logger.warning(repr(e)) await cooldown From 0356ae51a7561380c1567469838d5220ce1cef18 Mon Sep 17 00:00:00 2001 From: azriel1rf Date: Fri, 29 Oct 2021 00:50:27 +0900 Subject: [PATCH 2/4] remove empty lists/dicts from the default values. type Optional appropriately --- pybotters/__init__.py | 20 ++++++++++------- pybotters/client.py | 14 +++++++++--- pybotters/models/binance.py | 4 +++- pybotters/models/bitbank.py | 6 ++++-- pybotters/models/bybit.py | 4 +++- pybotters/models/experimental/bybit.py | 4 +++- pybotters/models/ftx.py | 6 ++++-- pybotters/models/gmocoin.py | 17 ++++++--------- pybotters/store.py | 30 +++++++++++++++++++------- tests/test_client.py | 7 +++--- 10 files changed, 72 insertions(+), 40 deletions(-) diff --git a/pybotters/__init__.py b/pybotters/__init__.py index 1677609..a79c9a4 100644 --- a/pybotters/__init__.py +++ b/pybotters/__init__.py @@ -54,9 +54,11 @@ async def _request( *, params: Optional[Mapping[str, str]] = None, data: Any = None, - apis: Union[Dict[str, List[str]], str] = {}, + apis: Optional[Union[Dict[str, List[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: + if apis is None: + apis = {} async with Client(apis=apis, response_class=SyncClientResponse) as client: async with client.request( method, url, params=params, data=data, **kwargs @@ -71,7 +73,7 @@ def request( *, params: Optional[Mapping[str, str]] = None, data: Any = None, - apis: Union[Dict[str, List[str]], str] = {}, + apis: Optional[Union[Dict[str, List[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: loop = asyncio.get_event_loop() @@ -84,7 +86,7 @@ def get( url: str, *, params: Optional[Mapping[str, str]] = None, - apis: Union[Dict[str, List[str]], str] = {}, + apis: Optional[Union[Dict[str, List[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: loop = asyncio.get_event_loop() @@ -97,7 +99,7 @@ def post( url: str, *, data: Any = None, - apis: Union[Dict[str, List[str]], str] = {}, + apis: Optional[Union[Dict[str, List[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: loop = asyncio.get_event_loop() @@ -110,7 +112,7 @@ def put( url: str, *, data: Any = None, - apis: Union[Dict[str, List[str]], str] = {}, + apis: Optional[Union[Dict[str, List[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: loop = asyncio.get_event_loop() @@ -123,7 +125,7 @@ def delete( url: str, *, data: Any = None, - apis: Union[Dict[str, List[str]], str] = {}, + apis: Optional[Union[Dict[str, List[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: loop = asyncio.get_event_loop() @@ -139,9 +141,11 @@ async def _ws_connect( send_json: Any = None, hdlr_str: Optional[WsStrHandler] = None, hdlr_json: Optional[WsJsonHandler] = None, - apis: Union[Dict[str, List[str]], str] = {}, + apis: Optional[Union[Dict[str, List[str]], str]] = None, **kwargs: Any, ) -> None: + if apis is None: + apis = {} async with Client(apis=apis) as client: wstask = await client.ws_connect( url, @@ -161,7 +165,7 @@ def ws_connect( send_json: Any = None, hdlr_str: Optional[WsStrHandler] = None, hdlr_json: Optional[WsJsonHandler] = None, - apis: Union[Dict[str, List[str]], str] = {}, + apis: Optional[Union[Dict[str, List[str]], str]] = None, **kwargs: Any, ) -> None: loop = asyncio.get_event_loop() diff --git a/pybotters/client.py b/pybotters/client.py index f9da3df..56300e6 100644 --- a/pybotters/client.py +++ b/pybotters/client.py @@ -22,7 +22,7 @@ class Client: def __init__( self, - apis: Union[Dict[str, List[str]], str] = {}, + apis: Optional[Union[Dict[str, List[str]], str]] = None, base_url: str = '', **kwargs: Any, ) -> None: @@ -141,7 +141,11 @@ async def ws_connect( return task @staticmethod - def _load_apis(apis: Union[Dict[str, List[str]], str]) -> Dict[str, List[str]]: + def _load_apis( + apis: Optional[Union[Dict[str, List[str]], str]] + ) -> Dict[str, List[str]]: + if apis is None: + apis = {} if isinstance(apis, dict): if apis: return apis @@ -165,7 +169,11 @@ def _load_apis(apis: Union[Dict[str, List[str]], str]) -> Dict[str, List[str]]: return {} @staticmethod - def _encode_apis(apis: Dict[str, List[str]]) -> Dict[str, Tuple[str, bytes]]: + def _encode_apis( + apis: Optional[Dict[str, List[str]]] + ) -> Dict[str, Tuple[str, bytes]]: + if apis is None: + apis = {} encoded = {} for name in apis: if len(apis[name]) == 2: diff --git a/pybotters/models/binance.py b/pybotters/models/binance.py index 17887b4..b0a39e7 100644 --- a/pybotters/models/binance.py +++ b/pybotters/models/binance.py @@ -183,7 +183,9 @@ def _init(self) -> None: self.initialized = False self._buff = deque(maxlen=200) - def sorted(self, query: Item = {}) -> Dict[str, List[float]]: + def sorted(self, query: Optional[Item] = None) -> Dict[str, List[float]]: + if query is None: + query = {} result = {self._MAPSIDE[k]: [] for k in self._MAPSIDE} for item in self: if all(k in item and query[k] == item[k] for k in query): diff --git a/pybotters/models/bitbank.py b/pybotters/models/bitbank.py index e899bea..1812bf4 100644 --- a/pybotters/models/bitbank.py +++ b/pybotters/models/bitbank.py @@ -1,5 +1,5 @@ import json -from typing import Dict, List +from typing import Dict, List, Optional from ..store import DataStore, DataStoreManager from ..typedefs import Item @@ -51,7 +51,9 @@ class Depth(DataStore): _KEYS = ['pair', 'side', 'price'] _BDSIDE = {'sell': 'asks', 'buy': 'bids'} - def sorted(self, query: Item = {}) -> Dict[str, List[float]]: + def sorted(self, query: Optional[Item] = None) -> Dict[str, List[float]]: + if query is None: + query = {} result = {'asks': [], 'bids': []} for item in self: if all(k in item and query[k] == item[k] for k in query): diff --git a/pybotters/models/bybit.py b/pybotters/models/bybit.py index 0a0db2d..5f27d03 100644 --- a/pybotters/models/bybit.py +++ b/pybotters/models/bybit.py @@ -153,7 +153,9 @@ def wallet(self) -> 'Wallet': class OrderBook(DataStore): _KEYS = ['symbol', 'id', 'side'] - def sorted(self, query: Item = {}) -> Dict[str, List[Item]]: + def sorted(self, query: Optional[Item] = None) -> Dict[str, List[Item]]: + if query is None: + query = {} result = {'Sell': [], 'Buy': []} for item in self: if all(k in item and query[k] == item[k] for k in query): diff --git a/pybotters/models/experimental/bybit.py b/pybotters/models/experimental/bybit.py index 00c2f3a..538a1a6 100644 --- a/pybotters/models/experimental/bybit.py +++ b/pybotters/models/experimental/bybit.py @@ -274,7 +274,9 @@ class OrderBookInverse(CastDataStore): ], } - def sorted(self, query: Item = {}) -> Dict[str, List[Item]]: + def sorted(self, query: Optional[Item] = None) -> Dict[str, List[Item]]: + if query is None: + query = {} result = {"Sell": [], "Buy": []} for item in self: if all(k in item and query[k] == item[k] for k in query): diff --git a/pybotters/models/ftx.py b/pybotters/models/ftx.py index 7bf5903..969c82f 100644 --- a/pybotters/models/ftx.py +++ b/pybotters/models/ftx.py @@ -1,6 +1,6 @@ import asyncio import logging -from typing import Any, Awaitable, Dict, List +from typing import Any, Awaitable, Dict, List, Optional import aiohttp @@ -118,7 +118,9 @@ class OrderBook(DataStore): _KEYS = ['market', 'side', 'price'] _BDSIDE = {'sell': 'asks', 'buy': 'bids'} - def sorted(self, query: Item = {}) -> Dict[str, List[float]]: + def sorted(self, query: Optional[Item] = None) -> Dict[str, List[float]]: + if query is None: + query = {} result = {'asks': [], 'bids': []} for item in self: if all(k in item and query[k] == item[k] for k in query): diff --git a/pybotters/models/gmocoin.py b/pybotters/models/gmocoin.py index b196409..c595e8d 100644 --- a/pybotters/models/gmocoin.py +++ b/pybotters/models/gmocoin.py @@ -3,14 +3,7 @@ from datetime import datetime, timezone from decimal import Decimal from enum import Enum, auto -from typing import ( - Any, - Awaitable, - Dict, - List, - Optional, - cast, -) +from typing import Any, Awaitable, Dict, List, Optional, cast import aiohttp from pybotters.store import DataStore, DataStoreManager @@ -294,7 +287,9 @@ def _onmessage(self, mes: Ticker) -> None: class OrderBookStore(DataStore): _KEYS = ["symbol", "side", "price"] - def sorted(self, query: Item = {}) -> Dict[OrderSide, List[OrderLevel]]: + def sorted(self, query: Optional[Item] = None) -> Dict[OrderSide, List[OrderLevel]]: + if query is None: + query = {} result: Dict[OrderSide, List[OrderLevel]] = { OrderSide.BUY: [], OrderSide.SELL: [], @@ -348,7 +343,9 @@ def _onexecution(self, mes: Execution) -> None: class ExecutionStore(DataStore): _KEYS = ["execution_id"] - def sorted(self, query: Item = {}) -> List[Execution]: + def sorted(self, query: Optional[Item] = None) -> List[Execution]: + if query is None: + query = {} result = [] for item in self: if all(k in item and query[k] == item[k] for k in query): diff --git a/pybotters/store.py b/pybotters/store.py index 5fde3f2..737743b 100644 --- a/pybotters/store.py +++ b/pybotters/store.py @@ -2,7 +2,6 @@ import uuid from typing import ( Any, - cast, Dict, Hashable, Iterator, @@ -11,6 +10,7 @@ Tuple, Type, TypeVar, + cast, ) from .typedefs import Item @@ -21,11 +21,15 @@ class DataStore: _KEYS = [] _MAXLEN = 9999 - def __init__(self, keys: List[str] = [], data: List[Item] = []) -> None: + def __init__( + self, keys: Optional[List[str]] = None, data: Optional[List[Item]] = None + ) -> None: self._data: Dict[uuid.UUID, Item] = {} self._index: Dict[int, uuid.UUID] = {} self._keys: Tuple[str, ...] = tuple(keys if keys else self._KEYS) self._events: Dict[asyncio.Event, List[Item]] = {} + if data is None: + data = [] self._insert(data) if hasattr(self, '_init'): getattr(self, '_init')() @@ -165,7 +169,7 @@ def _pop(self, item: Item) -> Optional[Item]: del self._index[keyhash] return ret - def find(self, query: Item = {}) -> List[Item]: + def find(self, query: Optional[Item] = None) -> List[Item]: if query: return [ item @@ -175,7 +179,9 @@ def find(self, query: Item = {}) -> List[Item]: else: return list(self) - def _find_with_uuid(self, query: Item = {}) -> Dict[uuid.UUID, Item]: + def _find_with_uuid(self, query: Optional[Item] = None) -> dict[uuid.UUID, Item]: + if query is None: + query = {} if query: return { _id: item @@ -185,7 +191,9 @@ def _find_with_uuid(self, query: Item = {}) -> Dict[uuid.UUID, Item]: else: return self._data - def _find_and_delete(self, query: Item = {}) -> List[Item]: + def _find_and_delete(self, query: Optional[Item] = None) -> list[Item]: + if query is None: + query = {} if query: ret = [ item @@ -199,7 +207,9 @@ def _find_and_delete(self, query: Item = {}) -> List[Item]: self._clear() return ret - def _set(self, data: List[Item] = None) -> None: + def _set(self, data: Optional[List[Item]] = None) -> None: + if data is None: + data = [] for event in self._events: event.set() self._events[event].extend(data) @@ -234,10 +244,14 @@ def create( self, name: str, *, - keys: List[str] = [], - data: List[Item] = [], + keys: Optional[List[str]] = None, + data: Optional[List[Item]] = None, datastore_class: Type[DataStore] = DataStore, ) -> None: + if keys is None: + keys = [] + if data is None: + data = [] self._stores[name] = datastore_class(keys, data) def get(self, name: str, type: Type[TDataStore]) -> TDataStore: diff --git a/tests/test_client.py b/tests/test_client.py index f758f6f..f308ed4 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -3,11 +3,10 @@ from unittest.mock import mock_open import aiohttp +import pybotters import pytest import pytest_mock -import pybotters - async def test_client(): apis = { @@ -50,7 +49,7 @@ async def test_client_open(mocker: pytest_mock.MockerFixture): async def test_client_warn(mocker: pytest_mock.MockerFixture): apis = {'name1', 'key1', 'secret1'} base_url = 'http://example.com' - async with pybotters.Client(apis=apis, base_url=base_url) as client: + async with pybotters.Client(apis=apis, base_url=base_url) as client: # type: ignore assert isinstance(client._session, aiohttp.ClientSession) assert not client._session.closed assert client._base_url == base_url @@ -149,7 +148,7 @@ async def test_client_ws_connect_json(mocker: pytest_mock.MockerFixture): ret = await client.ws_connect( 'ws://test.org', send_json={'foo': 'bar'}, - hdlr_json=lambda msg, ws: ..., + hdlr_json=lambda msg, ws: None, ) assert coro.called assert task.called From b744469b49a9dfe8938eeef9e0095e9abba273a3 Mon Sep 17 00:00:00 2001 From: azriel1rf Date: Fri, 29 Oct 2021 00:59:42 +0900 Subject: [PATCH 3/4] * type hinting respecting PEP 585. * replace Dict with Mapping --- pybotters/__init__.py | 22 ++++++------ pybotters/auth.py | 42 +++++++++++------------ pybotters/client.py | 18 +++++----- pybotters/models/binance.py | 14 ++++---- pybotters/models/bitbank.py | 8 ++--- pybotters/models/bitflyer.py | 26 +++++++------- pybotters/models/bybit.py | 42 +++++++++++------------ pybotters/models/experimental/bybit.py | 46 ++++++++++++------------- pybotters/models/ftx.py | 12 +++---- pybotters/models/gmocoin.py | 42 +++++++++++------------ pybotters/store.py | 47 ++++++++++---------------- pybotters/typedefs.py | 4 +-- pybotters/ws.py | 4 +-- 13 files changed, 159 insertions(+), 168 deletions(-) diff --git a/pybotters/__init__.py b/pybotters/__init__.py index a79c9a4..83c3d39 100644 --- a/pybotters/__init__.py +++ b/pybotters/__init__.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, Dict, List, Mapping, Optional, Tuple, Union +from typing import Any, Mapping, Optional, Tuple, Union import aiohttp from aiohttp import hdrs @@ -54,7 +54,7 @@ async def _request( *, params: Optional[Mapping[str, str]] = None, data: Any = None, - apis: Optional[Union[Dict[str, List[str]], str]] = None, + apis: Optional[Union[dict[str, list[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: if apis is None: @@ -73,7 +73,7 @@ def request( *, params: Optional[Mapping[str, str]] = None, data: Any = None, - apis: Optional[Union[Dict[str, List[str]], str]] = None, + apis: Optional[Union[dict[str, list[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: loop = asyncio.get_event_loop() @@ -86,7 +86,7 @@ def get( url: str, *, params: Optional[Mapping[str, str]] = None, - apis: Optional[Union[Dict[str, List[str]], str]] = None, + apis: Optional[Union[dict[str, list[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: loop = asyncio.get_event_loop() @@ -99,7 +99,7 @@ def post( url: str, *, data: Any = None, - apis: Optional[Union[Dict[str, List[str]], str]] = None, + apis: Optional[Union[dict[str, list[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: loop = asyncio.get_event_loop() @@ -112,7 +112,7 @@ def put( url: str, *, data: Any = None, - apis: Optional[Union[Dict[str, List[str]], str]] = None, + apis: Optional[Union[dict[str, list[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: loop = asyncio.get_event_loop() @@ -125,7 +125,7 @@ def delete( url: str, *, data: Any = None, - apis: Optional[Union[Dict[str, List[str]], str]] = None, + apis: Optional[Union[dict[str, list[str]], str]] = None, **kwargs: Any, ) -> SyncClientResponse: loop = asyncio.get_event_loop() @@ -137,11 +137,11 @@ def delete( async def _ws_connect( url: str, *, - send_str: Optional[Union[str, List[str]]] = None, + send_str: Optional[Union[str, list[str]]] = None, send_json: Any = None, hdlr_str: Optional[WsStrHandler] = None, hdlr_json: Optional[WsJsonHandler] = None, - apis: Optional[Union[Dict[str, List[str]], str]] = None, + apis: Optional[Union[dict[str, list[str]], str]] = None, **kwargs: Any, ) -> None: if apis is None: @@ -161,11 +161,11 @@ async def _ws_connect( def ws_connect( url: str, *, - send_str: Optional[Union[str, List[str]]] = None, + send_str: Optional[Union[str, list[str]]] = None, send_json: Any = None, hdlr_str: Optional[WsStrHandler] = None, hdlr_json: Optional[WsJsonHandler] = None, - apis: Optional[Union[Dict[str, List[str]], str]] = None, + apis: Optional[Union[dict[str, list[str]], str]] = None, **kwargs: Any, ) -> None: loop = asyncio.get_event_loop() diff --git a/pybotters/auth.py b/pybotters/auth.py index f288ec1..1a085d2 100644 --- a/pybotters/auth.py +++ b/pybotters/auth.py @@ -4,7 +4,7 @@ import json import time from dataclasses import dataclass -from typing import Any, Dict, Tuple +from typing import Any import aiohttp from aiohttp.formdata import FormData @@ -16,10 +16,10 @@ class Auth: @staticmethod - def bybit(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: + def bybit(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]: method: str = args[0] url: URL = args[1] - data: Dict[str, Any] = kwargs['data'] or {} + data: dict[str, Any] = kwargs['data'] or {} session: aiohttp.ClientSession = kwargs['session'] key: str = session.__dict__['_apis'][Hosts.items[url.host].name][0] @@ -58,10 +58,10 @@ def bybit(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: return args @staticmethod - def binance(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: + def binance(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]: method: str = args[0] url: URL = args[1] - data: Dict[str, Any] = kwargs['data'] or {} + data: dict[str, Any] = kwargs['data'] or {} headers: CIMultiDict = kwargs['headers'] session: aiohttp.ClientSession = kwargs['session'] @@ -95,10 +95,10 @@ def binance(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: return args @staticmethod - def bitflyer(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: + def bitflyer(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]: method: str = args[0] url: URL = args[1] - data: Dict[str, Any] = kwargs['data'] or {} + data: dict[str, Any] = kwargs['data'] or {} headers: CIMultiDict = kwargs['headers'] session: aiohttp.ClientSession = kwargs['session'] @@ -118,10 +118,10 @@ def bitflyer(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: return args @staticmethod - def gmocoin(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: + def gmocoin(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]: method: str = args[0] url: URL = args[1] - data: Dict[str, Any] = kwargs['data'] or {} + data: dict[str, Any] = kwargs['data'] or {} headers: CIMultiDict = kwargs['headers'] session: aiohttp.ClientSession = kwargs['session'] @@ -145,9 +145,9 @@ def gmocoin(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: return args @staticmethod - def liquid(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: + def liquid(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]: url: URL = args[1] - data: Dict[str, Any] = kwargs['data'] or {} + data: dict[str, Any] = kwargs['data'] or {} headers: CIMultiDict = kwargs['headers'] session: aiohttp.ClientSession = kwargs['session'] @@ -181,10 +181,10 @@ def liquid(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: return args @staticmethod - def bitbank(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: + def bitbank(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]: method: str = args[0] url: URL = args[1] - data: Dict[str, Any] = kwargs['data'] or {} + data: dict[str, Any] = kwargs['data'] or {} headers: CIMultiDict = kwargs['headers'] session: aiohttp.ClientSession = kwargs['session'] @@ -207,10 +207,10 @@ def bitbank(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: return args @staticmethod - def ftx(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: + def ftx(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]: method: str = args[0] url: URL = args[1] - data: Dict[str, Any] = kwargs['data'] or {} + data: dict[str, Any] = kwargs['data'] or {} headers: CIMultiDict = kwargs['headers'] session: aiohttp.ClientSession = kwargs['session'] @@ -228,10 +228,10 @@ def ftx(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: return args @staticmethod - def bitmex(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: + def bitmex(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]: method: str = args[0] url: URL = args[1] - data: Dict[str, Any] = kwargs['data'] or {} + data: dict[str, Any] = kwargs['data'] or {} headers: CIMultiDict = kwargs['headers'] session: aiohttp.ClientSession = kwargs['session'] @@ -251,9 +251,9 @@ def bitmex(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: return args @staticmethod - def phemex(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: + def phemex(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]: url: URL = args[1] - data: Dict[str, Any] = kwargs['data'] or {} + data: dict[str, Any] = kwargs['data'] or {} headers: CIMultiDict = kwargs['headers'] session: aiohttp.ClientSession = kwargs['session'] @@ -278,9 +278,9 @@ def phemex(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: return args @staticmethod - def coincheck(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]: + def coincheck(args: tuple[str, URL], kwargs: dict[str, Any]) -> tuple[str, URL]: url: URL = args[1] - data: Dict[str, Any] = kwargs['data'] or {} + data: dict[str, Any] = kwargs['data'] or {} headers: CIMultiDict = kwargs['headers'] session: aiohttp.ClientSession = kwargs['session'] diff --git a/pybotters/client.py b/pybotters/client.py index 56300e6..10428ab 100644 --- a/pybotters/client.py +++ b/pybotters/client.py @@ -2,7 +2,7 @@ import json import logging import os -from typing import Any, Dict, List, Mapping, Optional, Tuple, Union +from typing import Any, Mapping, Optional, Union import aiohttp from aiohttp import hdrs @@ -22,7 +22,7 @@ class Client: def __init__( self, - apis: Optional[Union[Dict[str, List[str]], str]] = None, + apis: Optional[Union[dict[str, list[str]], str]] = None, base_url: str = '', **kwargs: Any, ) -> None: @@ -49,8 +49,8 @@ def _request( method: str, url: str, *, - params: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, + params: Optional[Mapping[str, Any]] = None, + data: Optional[dict[str, Any]] = None, auth: Optional[Auth] = Auth, **kwargs: Any, ) -> _RequestContextManager: @@ -118,7 +118,7 @@ async def ws_connect( self, url: str, *, - send_str: Optional[Union[str, List[str]]] = None, + send_str: Optional[Union[str, list[str]]] = None, send_json: Any = None, hdlr_str: Optional[WsStrHandler] = None, hdlr_json: Optional[WsJsonHandler] = None, @@ -142,8 +142,8 @@ async def ws_connect( @staticmethod def _load_apis( - apis: Optional[Union[Dict[str, List[str]], str]] - ) -> Dict[str, List[str]]: + apis: Optional[Union[dict[str, list[str]], str]] + ) -> dict[str, list[str]]: if apis is None: apis = {} if isinstance(apis, dict): @@ -170,8 +170,8 @@ def _load_apis( @staticmethod def _encode_apis( - apis: Optional[Dict[str, List[str]]] - ) -> Dict[str, Tuple[str, bytes]]: + apis: Optional[dict[str, list[str]]] + ) -> dict[str, tuple[str, bytes]]: if apis is None: apis = {} encoded = {} diff --git a/pybotters/models/binance.py b/pybotters/models/binance.py index b0a39e7..43eade6 100644 --- a/pybotters/models/binance.py +++ b/pybotters/models/binance.py @@ -1,6 +1,6 @@ import asyncio from collections import deque -from typing import Any, Awaitable, Dict, List, Optional, Union +from typing import Any, Awaitable, Optional, Union import aiohttp @@ -132,7 +132,7 @@ def _onmessage(self, item: Item) -> None: class MarkPrice(DataStore): _KEYS = ['s'] - def _onmessage(self, data: Union[Item, List[Item]]) -> None: + def _onmessage(self, data: Union[Item, list[Item]]) -> None: if isinstance(data, list): self._update(data) else: @@ -156,7 +156,7 @@ def _onmessage(self, item: Item) -> None: class Ticker(DataStore): _KEYS = ['s'] - def _onmessage(self, data: Union[Item, List[Item]]) -> None: + def _onmessage(self, data: Union[Item, list[Item]]) -> None: if isinstance(data, list): self._update(data) else: @@ -183,7 +183,7 @@ def _init(self) -> None: self.initialized = False self._buff = deque(maxlen=200) - def sorted(self, query: Optional[Item] = None) -> Dict[str, List[float]]: + def sorted(self, query: Optional[Item] = None) -> dict[str, list[float]]: if query is None: query = {} result = {self._MAPSIDE[k]: [] for k in self._MAPSIDE} @@ -222,7 +222,7 @@ class Balance(DataStore): def _onmessage(self, item: Item) -> None: self._update(item['a']['B']) - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: for item in data: self._update( [ @@ -241,7 +241,7 @@ class Position(DataStore): def _onmessage(self, item: Item) -> None: self._update(item['a']['P']) - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: for item in data: self._update( [ @@ -266,7 +266,7 @@ def _onmessage(self, item: Item) -> None: else: self._delete([item['o']]) - def _onresponse(self, symbol: Optional[str], data: List[Item]) -> None: + def _onresponse(self, symbol: Optional[str], data: list[Item]) -> None: if symbol is not None: self._delete(self.find({'symbol': symbol})) else: diff --git a/pybotters/models/bitbank.py b/pybotters/models/bitbank.py index 1812bf4..04f1b54 100644 --- a/pybotters/models/bitbank.py +++ b/pybotters/models/bitbank.py @@ -1,5 +1,5 @@ import json -from typing import Dict, List, Optional +from typing import Optional from ..store import DataStore, DataStoreManager from ..typedefs import Item @@ -40,7 +40,7 @@ def ticker(self) -> 'Ticker': class Transactions(DataStore): _MAXLEN = 99999 - def _onmessage(self, room_name: str, data: List[Item]) -> None: + def _onmessage(self, room_name: str, data: list[Item]) -> None: data = data['transactions'] for item in data: pair = room_name.replace('transactions_', '') @@ -51,7 +51,7 @@ class Depth(DataStore): _KEYS = ['pair', 'side', 'price'] _BDSIDE = {'sell': 'asks', 'buy': 'bids'} - def sorted(self, query: Optional[Item] = None) -> Dict[str, List[float]]: + def sorted(self, query: Optional[Item] = None) -> dict[str, list[float]]: if query is None: query = {} result = {'asks': [], 'bids': []} @@ -62,7 +62,7 @@ def sorted(self, query: Optional[Item] = None) -> Dict[str, List[float]]: result['bids'].sort(key=lambda x: x[0], reverse=True) return result - def _onmessage(self, room_name: str, data: List[Item]) -> None: + def _onmessage(self, room_name: str, data: list[Item]) -> None: if 'whole' in room_name: pair = room_name.replace('depth_whole_', '') result = self.find({'pair': pair}) diff --git a/pybotters/models/bitflyer.py b/pybotters/models/bitflyer.py index 47843f2..ee89d5c 100644 --- a/pybotters/models/bitflyer.py +++ b/pybotters/models/bitflyer.py @@ -2,7 +2,7 @@ import logging import operator from decimal import Decimal -from typing import Awaitable, Dict, List +from typing import Awaitable import aiohttp @@ -108,9 +108,11 @@ class Board(DataStore): _KEYS = ['product_code', 'side', 'price'] def _init(self) -> None: - self.mid_price: Dict[str, float] = {} + self.mid_price: dict[str, float] = {} - def sorted(self, query: Item = {}) -> Dict[str, List[Item]]: + def sorted(self, query: Item = None) -> dict[str, list[Item]]: + if query is None: + query = {} result = {'SELL': [], 'BUY': []} for item in self: if all(k in item and query[k] == item[k] for k in query): @@ -148,31 +150,31 @@ def _onmessage(self, message: Item) -> None: class Executions(DataStore): _MAXLEN = 99999 - def _onmessage(self, message: List[Item]) -> None: + def _onmessage(self, message: list[Item]) -> None: self._insert(message) class ChildOrderEvents(DataStore): - def _onmessage(self, message: List[Item]) -> None: + def _onmessage(self, message: list[Item]) -> None: self._insert(message) class ParentOrderEvents(DataStore): - def _onmessage(self, message: List[Item]) -> None: + def _onmessage(self, message: list[Item]) -> None: self._insert(message) class ChildOrders(DataStore): _KEYS = ['child_order_acceptance_id'] - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: if data: self._delete(self.find({'product_code': data[0]['product_code']})) for item in data: if item['child_order_state'] == 'ACTIVE': self._insert([item]) - def _onmessage(self, message: List[Item]) -> None: + def _onmessage(self, message: list[Item]) -> None: for item in message: if item['event_type'] == 'ORDER': self._insert([item]) @@ -198,14 +200,14 @@ def _onmessage(self, message: List[Item]) -> None: class ParentOrders(DataStore): _KEYS = ['parent_order_acceptance_id'] - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: if data: self._delete(self.find({'product_code': data[0]['product_code']})) for item in data: if item['parent_order_state'] == 'ACTIVE': self._insert([item]) - def _onmessage(self, message: List[Item]) -> None: + def _onmessage(self, message: list[Item]) -> None: for item in message: if item['event_type'] == 'ORDER': self._insert([item]) @@ -234,13 +236,13 @@ class Positions(DataStore): def _common_keys(self, item: Item) -> Item: return {key: item[key] for key in self._COMMON_KEYS} - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: if data: self._delete(self.find({'product_code': data[0]['product_code']})) for item in data: self._insert([self._common_keys(item)]) - def _onmessage(self, message: List[Item]) -> None: + def _onmessage(self, message: list[Item]) -> None: for item in message: if item['event_type'] == 'EXECUTION': positions = self._find_with_uuid({'product_code': item['product_code']}) diff --git a/pybotters/models/bybit.py b/pybotters/models/bybit.py index 5f27d03..532bfa8 100644 --- a/pybotters/models/bybit.py +++ b/pybotters/models/bybit.py @@ -1,6 +1,6 @@ import asyncio import logging -from typing import Any, Awaitable, Dict, List, Optional, Union +from typing import Any, Awaitable, Optional, Union import aiohttp @@ -153,7 +153,7 @@ def wallet(self) -> 'Wallet': class OrderBook(DataStore): _KEYS = ['symbol', 'id', 'side'] - def sorted(self, query: Optional[Item] = None) -> Dict[str, List[Item]]: + def sorted(self, query: Optional[Item] = None) -> dict[str, list[Item]]: if query is None: query = {} result = {'Sell': [], 'Buy': []} @@ -164,7 +164,7 @@ def sorted(self, query: Optional[Item] = None) -> Dict[str, List[Item]]: result['Buy'].sort(key=lambda x: x['id'], reverse=True) return result - def _onmessage(self, topic: str, type_: str, data: Union[List[Item], Item]) -> None: + def _onmessage(self, topic: str, type_: str, data: Union[list[Item], Item]) -> None: if type_ == 'snapshot': symbol = topic.split('.')[-1] # ex: 'orderBookL2_25.BTCUSD' result = self.find({'symbol': symbol}) @@ -182,14 +182,14 @@ class Trade(DataStore): _KEYS = ['trade_id'] _MAXLEN = 99999 - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: self._insert(data) class Insurance(DataStore): _KEYS = ['currency'] - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: self._update(data) @@ -209,14 +209,14 @@ def _onmessage(self, topic: str, type_: str, data: Item) -> None: class Kline(DataStore): _KEYS = ['symbol', 'period', 'start'] - def _onmessage(self, topic: str, data: List[Item]) -> None: + def _onmessage(self, topic: str, data: list[Item]) -> None: topic_split = topic.split('.') # ex:'klineV2.1.BTCUSD' for item in data: item['symbol'] = topic_split[-1] item['period'] = topic_split[-2] self._update(data) - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: for item in data: item["start"] = item.pop("open_time") item["period"] = item.pop("interval") @@ -244,13 +244,13 @@ class PositionInverse(DataStore): def getone(self, symbol: str) -> Optional[Item]: return self.get({'symbol': symbol, 'position_idx': 0}) - def getboth(self, symbol: str) -> Dict[str, Optional[Item]]: + def getboth(self, symbol: str) -> dict[str, Optional[Item]]: return { 'Sell': self.get({'symbol': symbol, 'position_idx': 2}), 'Buy': self.get({'symbol': symbol, 'position_idx': 1}), } - def _onresponse(self, data: Union[Item, List[Item]]) -> None: + def _onresponse(self, data: Union[Item, list[Item]]) -> None: if isinstance(data, dict): self._update([data]) elif isinstance(data, list): @@ -260,47 +260,47 @@ def _onresponse(self, data: Union[Item, List[Item]]) -> None: else: self._update(data) - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: self._update(data) class PositionUSDT(DataStore): _KEYS = ['symbol', 'side'] - def getboth(self, symbol: str) -> Dict[str, Optional[Item]]: + def getboth(self, symbol: str) -> dict[str, Optional[Item]]: return { 'Sell': self.get({'symbol': symbol, 'side': 'Sell'}), 'Buy': self.get({'symbol': symbol, 'side': 'Buy'}), } - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: if len(data): if 'data' in data[0]: self._update([item['data'] for item in data]) else: self._update(data) - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: self._update(data) class Execution(DataStore): _KEYS = ['exec_id'] - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: self._update(data) class Order(DataStore): _KEYS = ['order_id'] - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: if isinstance(data, list): self._update(data) elif isinstance(data, dict): self._update([data]) - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: for item in data: if item['order_status'] in ('Created', 'New', 'PartiallyFilled'): self._update([item]) @@ -311,13 +311,13 @@ def _onmessage(self, data: List[Item]) -> None: class StopOrder(DataStore): _KEYS = ['stop_order_id'] - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: if isinstance(data, list): self._update(data) elif isinstance(data, dict): self._update([data]) - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: for item in data: if 'order_id' in item: item['stop_order_id'] = item.pop('order_id') @@ -332,7 +332,7 @@ def _onmessage(self, data: List[Item]) -> None: class Wallet(DataStore): _KEYS = ['coin'] - def _onresponse(self, data: Dict[str, Item]) -> None: + def _onresponse(self, data: dict[str, Item]) -> None: for coin, item in data.items(): self._update( [ @@ -344,7 +344,7 @@ def _onresponse(self, data: Dict[str, Item]) -> None: ] ) - def _onposition(self, data: List[Item]) -> None: + def _onposition(self, data: list[Item]) -> None: for item in data: symbol: str = item['symbol'] if symbol.endswith('USD'): @@ -361,7 +361,7 @@ def _onposition(self, data: List[Item]) -> None: ] ) - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: for item in data: self._update( [ diff --git a/pybotters/models/experimental/bybit.py b/pybotters/models/experimental/bybit.py index 538a1a6..cfd51e1 100644 --- a/pybotters/models/experimental/bybit.py +++ b/pybotters/models/experimental/bybit.py @@ -1,6 +1,6 @@ import asyncio import logging -from typing import Awaitable, Dict, List, Optional, Union +from typing import Awaitable, Optional, Union import aiohttp @@ -242,7 +242,7 @@ def wallet(self) -> "Wallet": class CastDataStore(DataStore): _CAST_TYPES = {} - def _cast(self, data: List[Item]) -> None: + def _cast(self, data: list[Item]) -> None: for item in data: for x in self._CAST_TYPES: for k in self._CAST_TYPES[x]: @@ -253,15 +253,15 @@ def _cast(self, data: List[Item]) -> None: except TypeError: pass - def _insert(self, data: List[Item]) -> None: + def _insert(self, data: list[Item]) -> None: self._cast(data) super()._insert(data) - def _update(self, data: List[Item]) -> None: + def _update(self, data: list[Item]) -> None: self._cast(data) super()._update(data) - def _delete(self, data: List[Item]) -> None: + def _delete(self, data: list[Item]) -> None: self._cast(data) super()._delete(data) @@ -274,7 +274,7 @@ class OrderBookInverse(CastDataStore): ], } - def sorted(self, query: Optional[Item] = None) -> Dict[str, List[Item]]: + def sorted(self, query: Optional[Item] = None) -> dict[str, list[Item]]: if query is None: query = {} result = {"Sell": [], "Buy": []} @@ -285,7 +285,7 @@ def sorted(self, query: Optional[Item] = None) -> Dict[str, List[Item]]: result["Buy"].sort(key=lambda x: x["id"], reverse=True) return result - def _onmessage(self, topic: str, type_: str, data: Union[List[Item], Item]) -> None: + def _onmessage(self, topic: str, type_: str, data: Union[list[Item], Item]) -> None: if type_ == "snapshot": symbol = topic.split(".")[-1] # ex: "orderBookL2_25.BTCUSD", "orderBook_200.100ms.BTCUSD" @@ -308,7 +308,7 @@ class OrderBookUSDT(OrderBookInverse): ], } - def _onmessage(self, topic: str, type_: str, data: Union[List[Item], Item]) -> None: + def _onmessage(self, topic: str, type_: str, data: Union[list[Item], Item]) -> None: if type_ == "snapshot": symbol = topic.split(".")[-1] # ex: "orderBookL2_25.BTCUSDT", "orderBook_200.100ms.BTCUSDT" @@ -325,7 +325,7 @@ class TradeInverse(CastDataStore): _KEYS = ['trade_id'] _MAXLEN = 99999 - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: self._insert(data) @@ -343,7 +343,7 @@ class TradeUSDT(TradeInverse): class Insurance(CastDataStore): _KEYS = ["currency"] - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: self._update(data) @@ -414,14 +414,14 @@ class InstrumentUSDT(InstrumentInverse): class KlineInverse(CastDataStore): _KEYS = ["start", "symbol", "interval"] - def _onmessage(self, topic: str, data: List[Item]) -> None: + def _onmessage(self, topic: str, data: list[Item]) -> None: topic_split = topic.split(".") # ex:"klineV2.1.BTCUSD" for item in data: item["symbol"] = topic_split[-1] item["interval"] = topic_split[-2] self._update(data) - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: for item in data: item["start"] = item.pop("open_time") self._update(data) @@ -490,13 +490,13 @@ class PositionInverse(CastDataStore): def one(self, symbol: str) -> Optional[Item]: return self.get({"symbol": symbol, "position_idx": 0}) - def both(self, symbol: str) -> Dict[str, Optional[Item]]: + def both(self, symbol: str) -> dict[str, Optional[Item]]: return { "Sell": self.get({"symbol": symbol, "position_idx": 2}), "Buy": self.get({"symbol": symbol, "position_idx": 1}), } - def _onresponse(self, data: Union[Item, List[Item]]) -> None: + def _onresponse(self, data: Union[Item, list[Item]]) -> None: if isinstance(data, dict): self._update([data]) # ex: {"symbol": "BTCUSD", ...} elif isinstance(data, list): @@ -516,7 +516,7 @@ def _onresponse(self, data: Union[Item, List[Item]]) -> None: self._update([item]) # ex: [{"symbol": "BTCUSDT", ...}, ...] - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: self._update(data) @@ -533,13 +533,13 @@ class PositionUSDT(PositionInverse): ], } - def one(self, symbol: str) -> Dict[str, Optional[Item]]: + def one(self, symbol: str) -> dict[str, Optional[Item]]: return { "Sell": self.get({"symbol": symbol, "side": "Sell"}), "Buy": self.get({"symbol": symbol, "side": "Buy"}), } - def both(self, symbol: str) -> Dict[str, Optional[Item]]: + def both(self, symbol: str) -> dict[str, Optional[Item]]: return { "Sell": self.get({"symbol": symbol, "side": "Sell"}), "Buy": self.get({"symbol": symbol, "side": "Buy"}), @@ -555,7 +555,7 @@ class ExecutionInverse(CastDataStore): ], } - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: self._update(data) @@ -577,13 +577,13 @@ class OrderInverse(CastDataStore): ], } - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: if isinstance(data, list): self._update(data) elif isinstance(data, dict): self._update([data]) - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: for item in data: if item["order_status"] in ("Created", "New", "PartiallyFilled"): self._update([item]) @@ -604,13 +604,13 @@ class StopOrderInverse(CastDataStore): ], } - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: if isinstance(data, list): self._update(data) elif isinstance(data, dict): self._update([data]) - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: for item in data: if item["order_status"] in ("Active", "Untriggered"): self._update([item]) @@ -628,6 +628,6 @@ class StopOrderUSDT(StopOrderInverse): class Wallet(CastDataStore): - def _onmessage(self, data: List[Item]) -> None: + def _onmessage(self, data: list[Item]) -> None: self._clear() self._update(data) diff --git a/pybotters/models/ftx.py b/pybotters/models/ftx.py index 969c82f..2270b21 100644 --- a/pybotters/models/ftx.py +++ b/pybotters/models/ftx.py @@ -1,6 +1,6 @@ import asyncio import logging -from typing import Any, Awaitable, Dict, List, Optional +from typing import Any, Awaitable, Optional import aiohttp @@ -109,7 +109,7 @@ def _onmessage(self, item: Item) -> None: class Trades(DataStore): _MAXLEN = 99999 - def _onmessage(self, market: str, data: List[Item]) -> None: + def _onmessage(self, market: str, data: list[Item]) -> None: for item in data: self._insert([{'market': market, **item}]) @@ -118,7 +118,7 @@ class OrderBook(DataStore): _KEYS = ['market', 'side', 'price'] _BDSIDE = {'sell': 'asks', 'buy': 'bids'} - def sorted(self, query: Optional[Item] = None) -> Dict[str, List[float]]: + def sorted(self, query: Optional[Item] = None) -> dict[str, list[float]]: if query is None: query = {} result = {'asks': [], 'bids': []} @@ -129,7 +129,7 @@ def sorted(self, query: Optional[Item] = None) -> Dict[str, List[float]]: result['bids'].sort(key=lambda x: x[0], reverse=True) return result - def _onmessage(self, market: str, data: List[Item]) -> None: + def _onmessage(self, market: str, data: list[Item]) -> None: if data['action'] == 'partial': result = self.find({'market': market}) self._delete(result) @@ -158,7 +158,7 @@ def _onmessage(self, item: Item) -> None: class Orders(DataStore): _KEYS = ['id'] - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: if data: results = self.find({'market': data[0]['market']}) self._delete(results) @@ -177,7 +177,7 @@ class Positions(DataStore): def _init(self) -> None: self._fetch = False - def _onresponse(self, data: List[Item]) -> None: + def _onresponse(self, data: list[Item]) -> None: self._update(data) async def _onfills(self, session: aiohttp.ClientSession) -> None: diff --git a/pybotters/models/gmocoin.py b/pybotters/models/gmocoin.py index c595e8d..97dff27 100644 --- a/pybotters/models/gmocoin.py +++ b/pybotters/models/gmocoin.py @@ -3,7 +3,7 @@ from datetime import datetime, timezone from decimal import Decimal from enum import Enum, auto -from typing import Any, Awaitable, Dict, List, Optional, cast +from typing import Any, Awaitable, Optional, cast import aiohttp from pybotters.store import DataStore, DataStoreManager @@ -204,8 +204,8 @@ class OrderLevel(TypedDict): class OrderBook(TypedDict): - asks: List[OrderLevel] - bids: List[OrderLevel] + asks: list[OrderLevel] + bids: list[OrderLevel] symbol: Symbol timestamp: datetime @@ -287,10 +287,10 @@ def _onmessage(self, mes: Ticker) -> None: class OrderBookStore(DataStore): _KEYS = ["symbol", "side", "price"] - def sorted(self, query: Optional[Item] = None) -> Dict[OrderSide, List[OrderLevel]]: + def sorted(self, query: Optional[Item] = None) -> dict[OrderSide, list[OrderLevel]]: if query is None: query = {} - result: Dict[OrderSide, List[OrderLevel]] = { + result: dict[OrderSide, list[OrderLevel]] = { OrderSide.BUY: [], OrderSide.SELL: [], } @@ -305,7 +305,7 @@ def _onmessage(self, mes: OrderBook) -> None: data = mes["asks"] + mes["bids"] result = self.find({"symbol": mes["symbol"]}) self._delete(result) - self._insert(cast(List[Item], data)) + self._insert(cast(list[Item], data)) class TradeStore(DataStore): @@ -316,8 +316,8 @@ def _onmessage(self, mes: Trade) -> None: class OrderStore(DataStore): _KEYS = ["order_id"] - def _onresponse(self, data: List[Order]) -> None: - self._insert(cast(List[Item], data)) + def _onresponse(self, data: list[Order]) -> None: + self._insert(cast(list[Item], data)) def _onmessage(self, mes: Order) -> None: if mes["order_status"] in (OrderStatus.WAITING, OrderStatus.ORDERED): @@ -343,7 +343,7 @@ def _onexecution(self, mes: Execution) -> None: class ExecutionStore(DataStore): _KEYS = ["execution_id"] - def sorted(self, query: Optional[Item] = None) -> List[Execution]: + def sorted(self, query: Optional[Item] = None) -> list[Execution]: if query is None: query = {} result = [] @@ -353,8 +353,8 @@ def sorted(self, query: Optional[Item] = None) -> List[Execution]: result.sort(key=lambda x: x["execution_id"], reverse=True) return result - def _onresponse(self, data: List[Execution]) -> None: - self._insert(cast(List[Item], data)) + def _onresponse(self, data: list[Execution]) -> None: + self._insert(cast(list[Item], data)) def _onmessage(self, mes: Execution) -> None: self._insert([cast(Item, mes)]) @@ -363,8 +363,8 @@ def _onmessage(self, mes: Execution) -> None: class PositionStore(DataStore): _KEYS = ["position_id"] - def _onresponse(self, data: List[Position]) -> None: - self._update(cast(List[Item], data)) + def _onresponse(self, data: list[Position]) -> None: + self._update(cast(list[Item], data)) def _onmessage(self, mes: Position, type: MessageType) -> None: if type == MessageType.OPR: @@ -378,8 +378,8 @@ def _onmessage(self, mes: Position, type: MessageType) -> None: class PositionSummaryStore(DataStore): _KEYS = ["symbol", "side"] - def _onresponse(self, data: List[PositionSummary]) -> None: - self._update(cast(List[Item], data)) + def _onresponse(self, data: list[PositionSummary]) -> None: + self._update(cast(list[Item], data)) def _onmessage(self, mes: PositionSummary) -> None: self._update([cast(Item, mes)]) @@ -387,7 +387,7 @@ def _onmessage(self, mes: PositionSummary) -> None: class MessageHelper: @staticmethod - def to_tickers(data: List[Item]) -> List["Ticker"]: + def to_tickers(data: list[Item]) -> list["Ticker"]: return [MessageHelper.to_ticker(x) for x in data] @staticmethod @@ -429,7 +429,7 @@ def to_orderbook(data: Item) -> "OrderBook": ) @staticmethod - def to_trades(data: List[Item]) -> List["Trade"]: + def to_trades(data: list[Item]) -> list["Trade"]: return [MessageHelper.to_trade(x) for x in data] @staticmethod @@ -443,7 +443,7 @@ def to_trade(data: Item) -> "Trade": ) @staticmethod - def to_executions(data: List[Item]) -> List["Execution"]: + def to_executions(data: list[Item]) -> list["Execution"]: return [MessageHelper.to_execution(x) for x in data] @staticmethod @@ -478,7 +478,7 @@ def to_execution(data: Item) -> "Execution": ) @staticmethod - def to_orders(data: List[Item]) -> List["Order"]: + def to_orders(data: list[Item]) -> list["Order"]: return [MessageHelper.to_order(x) for x in data] @staticmethod @@ -504,7 +504,7 @@ def to_order(data: Item) -> "Order": ) @staticmethod - def to_positions(data: List[Item]) -> List["Position"]: + def to_positions(data: list[Item]) -> list["Position"]: return [MessageHelper.to_position(x) for x in data] @staticmethod @@ -523,7 +523,7 @@ def to_position(data: Item) -> "Position": ) @staticmethod - def to_position_summaries(data: List[Item]) -> List["PositionSummary"]: + def to_position_summaries(data: list[Item]) -> list["PositionSummary"]: return [MessageHelper.to_position_summary(x) for x in data] @staticmethod diff --git a/pybotters/store.py b/pybotters/store.py index 737743b..5ab806c 100644 --- a/pybotters/store.py +++ b/pybotters/store.py @@ -1,17 +1,6 @@ import asyncio import uuid -from typing import ( - Any, - Dict, - Hashable, - Iterator, - List, - Optional, - Tuple, - Type, - TypeVar, - cast, -) +from typing import Any, Hashable, Iterator, Optional, Type, TypeVar, cast from .typedefs import Item from .ws import ClientWebSocketResponse @@ -22,12 +11,12 @@ class DataStore: _MAXLEN = 9999 def __init__( - self, keys: Optional[List[str]] = None, data: Optional[List[Item]] = None + self, keys: Optional[list[str]] = None, data: Optional[list[Item]] = None ) -> None: - self._data: Dict[uuid.UUID, Item] = {} - self._index: Dict[int, uuid.UUID] = {} - self._keys: Tuple[str, ...] = tuple(keys if keys else self._KEYS) - self._events: Dict[asyncio.Event, List[Item]] = {} + self._data: dict[uuid.UUID, Item] = {} + self._index: dict[int, uuid.UUID] = {} + self._keys: tuple[str, ...] = tuple(keys if keys else self._KEYS) + self._events: dict[asyncio.Event, list[Item]] = {} if data is None: data = [] self._insert(data) @@ -41,10 +30,10 @@ def __iter__(self) -> Iterator[Item]: return iter(self._data.values()) @staticmethod - def _hash(item: Dict[str, Hashable]) -> int: + def _hash(item: dict[str, Hashable]) -> int: return hash(tuple(item.items())) - def _insert(self, data: List[Item]) -> None: + def _insert(self, data: list[Item]) -> None: if self._keys: for item in data: try: @@ -68,7 +57,7 @@ def _insert(self, data: List[Item]) -> None: # !TODO! This behaviour might be undesirable. self._set(data) - def _update(self, data: List[Item]) -> None: + def _update(self, data: list[Item]) -> None: if self._keys: for item in data: try: @@ -92,7 +81,7 @@ def _update(self, data: List[Item]) -> None: # !TODO! This behaviour might be undesirable. self._set(data) - def _delete(self, data: List[Item]) -> None: + def _delete(self, data: list[Item]) -> None: if self._keys: for item in data: try: @@ -107,7 +96,7 @@ def _delete(self, data: List[Item]) -> None: # !TODO! This behaviour might be undesirable. self._set(data) - def _remove(self, uuids: List[uuid.UUID]) -> None: + def _remove(self, uuids: list[uuid.UUID]) -> None: if self._keys: for _id in uuids: if _id in self._data: @@ -169,7 +158,7 @@ def _pop(self, item: Item) -> Optional[Item]: del self._index[keyhash] return ret - def find(self, query: Optional[Item] = None) -> List[Item]: + def find(self, query: Optional[Item] = None) -> list[Item]: if query: return [ item @@ -207,14 +196,14 @@ def _find_and_delete(self, query: Optional[Item] = None) -> list[Item]: self._clear() return ret - def _set(self, data: Optional[List[Item]] = None) -> None: + def _set(self, data: Optional[list[Item]] = None) -> None: if data is None: data = [] for event in self._events: event.set() self._events[event].extend(data) - async def wait(self) -> List[Item]: + async def wait(self) -> list[Item]: event = asyncio.Event() ret = [] self._events[event] = ret @@ -228,8 +217,8 @@ async def wait(self) -> List[Item]: class DataStoreManager: def __init__(self) -> None: - self._stores: Dict[str, DataStore] = {} - self._events: List[asyncio.Event] = [] + self._stores: dict[str, DataStore] = {} + self._events: list[asyncio.Event] = [] self._iscorofunc = asyncio.iscoroutinefunction(self._onmessage) if hasattr(self, '_init'): getattr(self, '_init')() @@ -244,8 +233,8 @@ def create( self, name: str, *, - keys: Optional[List[str]] = None, - data: Optional[List[Item]] = None, + keys: Optional[list[str]] = None, + data: Optional[list[Item]] = None, datastore_class: Type[DataStore] = DataStore, ) -> None: if keys is None: diff --git a/pybotters/typedefs.py b/pybotters/typedefs.py index 3063b92..5d2ca0b 100644 --- a/pybotters/typedefs.py +++ b/pybotters/typedefs.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Coroutine, Dict, Optional +from typing import Any, Callable, Coroutine, Optional from aiohttp.client_ws import ClientWebSocketResponse @@ -8,4 +8,4 @@ WsJsonHandler = Callable[ [Any, ClientWebSocketResponse], Optional[Coroutine[Any, Any, None]] ] -Item = Dict[str, Any] +Item = dict[str, Any] diff --git a/pybotters/ws.py b/pybotters/ws.py index e00fe29..5a73db2 100644 --- a/pybotters/ws.py +++ b/pybotters/ws.py @@ -7,7 +7,7 @@ import time from dataclasses import dataclass from secrets import token_hex -from typing import Any, List, Optional, Union +from typing import Any, Optional, Union import aiohttp from aiohttp.http_websocket import json @@ -25,7 +25,7 @@ async def ws_run_forever( session: aiohttp.ClientSession, event: asyncio.Event, *, - send_str: Optional[Union[str, List[str]]] = None, + send_str: Optional[Union[str, list[str]]] = None, send_json: Any = None, hdlr_str=None, hdlr_json=None, From 3d83eaafa61509ecd5e1adff7bb4d5d676b427cf Mon Sep 17 00:00:00 2001 From: azriel1rf Date: Fri, 29 Oct 2021 11:41:46 +0900 Subject: [PATCH 4/4] fix annotation compatibility --- pybotters/__init__.py | 2 ++ pybotters/auth.py | 2 ++ pybotters/client.py | 2 ++ pybotters/models/binance.py | 2 ++ pybotters/models/bitbank.py | 2 ++ pybotters/models/bitflyer.py | 2 ++ pybotters/models/bybit.py | 2 ++ pybotters/models/experimental/bybit.py | 2 ++ pybotters/models/ftx.py | 2 ++ pybotters/models/gmocoin.py | 2 ++ pybotters/store.py | 2 ++ pybotters/typedefs.py | 4 ++-- pybotters/ws.py | 2 ++ 13 files changed, 26 insertions(+), 2 deletions(-) diff --git a/pybotters/__init__.py b/pybotters/__init__.py index 83c3d39..4c73d4e 100644 --- a/pybotters/__init__.py +++ b/pybotters/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio from typing import Any, Mapping, Optional, Tuple, Union diff --git a/pybotters/auth.py b/pybotters/auth.py index 1a085d2..4246ec5 100644 --- a/pybotters/auth.py +++ b/pybotters/auth.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import base64 import hashlib import hmac diff --git a/pybotters/client.py b/pybotters/client.py index 10428ab..8584ca8 100644 --- a/pybotters/client.py +++ b/pybotters/client.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import json import logging diff --git a/pybotters/models/binance.py b/pybotters/models/binance.py index 43eade6..abf3eec 100644 --- a/pybotters/models/binance.py +++ b/pybotters/models/binance.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio from collections import deque from typing import Any, Awaitable, Optional, Union diff --git a/pybotters/models/bitbank.py b/pybotters/models/bitbank.py index 04f1b54..7926ab9 100644 --- a/pybotters/models/bitbank.py +++ b/pybotters/models/bitbank.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from typing import Optional diff --git a/pybotters/models/bitflyer.py b/pybotters/models/bitflyer.py index ee89d5c..b71827c 100644 --- a/pybotters/models/bitflyer.py +++ b/pybotters/models/bitflyer.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import logging import operator diff --git a/pybotters/models/bybit.py b/pybotters/models/bybit.py index 532bfa8..f0c5737 100644 --- a/pybotters/models/bybit.py +++ b/pybotters/models/bybit.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import logging from typing import Any, Awaitable, Optional, Union diff --git a/pybotters/models/experimental/bybit.py b/pybotters/models/experimental/bybit.py index cfd51e1..211e776 100644 --- a/pybotters/models/experimental/bybit.py +++ b/pybotters/models/experimental/bybit.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import logging from typing import Awaitable, Optional, Union diff --git a/pybotters/models/ftx.py b/pybotters/models/ftx.py index 2270b21..eb58ce1 100644 --- a/pybotters/models/ftx.py +++ b/pybotters/models/ftx.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import logging from typing import Any, Awaitable, Optional diff --git a/pybotters/models/gmocoin.py b/pybotters/models/gmocoin.py index 97dff27..4c1d0d6 100644 --- a/pybotters/models/gmocoin.py +++ b/pybotters/models/gmocoin.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import logging from datetime import datetime, timezone diff --git a/pybotters/store.py b/pybotters/store.py index 5ab806c..3333545 100644 --- a/pybotters/store.py +++ b/pybotters/store.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import uuid from typing import Any, Hashable, Iterator, Optional, Type, TypeVar, cast diff --git a/pybotters/typedefs.py b/pybotters/typedefs.py index 5d2ca0b..3063b92 100644 --- a/pybotters/typedefs.py +++ b/pybotters/typedefs.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Coroutine, Optional +from typing import Any, Callable, Coroutine, Dict, Optional from aiohttp.client_ws import ClientWebSocketResponse @@ -8,4 +8,4 @@ WsJsonHandler = Callable[ [Any, ClientWebSocketResponse], Optional[Coroutine[Any, Any, None]] ] -Item = dict[str, Any] +Item = Dict[str, Any] diff --git a/pybotters/ws.py b/pybotters/ws.py index 5a73db2..11e0e38 100644 --- a/pybotters/ws.py +++ b/pybotters/ws.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import base64 import datetime