Skip to content

Commit

Permalink
Merge pull request #65 from MtkN1/develop
Browse files Browse the repository at this point in the history
✨v0.4.1リリース

✅Binanceのイベント仕様に従ってDataStoreを更新する #58
✅Bybit DataStoreのKline(ローソク足)のキーが足りていない #62
✅DataStoreにおいてWebSocketエラーメッセージのWarningを表示する #63

✅Python 3.7以外のテストが通っていない #51
✅Linter, Formatter を導入したい #59
  • Loading branch information
MtkN1 committed Jun 21, 2021
2 parents 908809c + 2670472 commit 6244c5f
Show file tree
Hide file tree
Showing 20 changed files with 1,136 additions and 539 deletions.
2 changes: 2 additions & 0 deletions .flake8
@@ -0,0 +1,2 @@
[flake8]
max-line-length = 88
4 changes: 3 additions & 1 deletion .github/workflows/pytest.yml
Expand Up @@ -24,4 +24,6 @@ jobs:
- name: Install dependency libraries
run: poetry install
- name: Run Tests
run: poetry run pytest
run: poetry run pytest
- name: Run Linter
run: poetry run flake8
227 changes: 226 additions & 1 deletion poetry.lock

Large diffs are not rendered by default.

109 changes: 69 additions & 40 deletions pybotters/__init__.py
@@ -1,5 +1,5 @@
import asyncio
from typing import Any, Dict, List, Mapping, Optional, Union
from typing import Any, Dict, List, Mapping, Optional, Tuple, Union

import aiohttp
from aiohttp import hdrs
Expand All @@ -12,6 +12,21 @@
from .models.binance import BinanceDataStore
from .typedefs import WsJsonHandler, WsStrHandler

__all__: Tuple[str, ...] = (
'Client',
'request',
'get',
'post',
'put',
'delete',
'BTCMEXDataStore',
'BybitDataStore',
'FTXDataStore',
'BinanceDataStore',
'print',
'print_handler',
)


def print_handler(msg: Any, ws: aiohttp.ClientWebSocketResponse):
print(msg)
Expand All @@ -29,13 +44,15 @@ async def _request(
method: str,
url: str,
*,
params: Optional[Mapping[str, str]]=None,
data: Any=None,
apis: Union[Dict[str, List[str]], str]={},
params: Optional[Mapping[str, str]] = None,
data: Any = None,
apis: Union[Dict[str, List[str]], str] = {},
**kwargs: Any,
) -> SyncClientResponse:
async with Client(apis=apis, response_class=SyncClientResponse) as client:
async with client.request(method, url, params=params, data=data, **kwargs) as resp:
async with client.request(
method, url, params=params, data=data, **kwargs
) as resp:
await resp.read()
return resp

Expand All @@ -44,67 +61,77 @@ def request(
method: str,
url: str,
*,
params: Optional[Mapping[str, str]]=None,
data: Any=None,
apis: Union[Dict[str, List[str]], str]={},
params: Optional[Mapping[str, str]] = None,
data: Any = None,
apis: Union[Dict[str, List[str]], str] = {},
**kwargs: Any,
) -> SyncClientResponse:
loop = asyncio.get_event_loop()
return loop.run_until_complete(_request(method, url, params=params, data=data, apis=apis, **kwargs))
return loop.run_until_complete(
_request(method, url, params=params, data=data, apis=apis, **kwargs)
)


def get(
url: str,
*,
params: Optional[Mapping[str, str]]=None,
apis: Union[Dict[str, List[str]], str]={},
params: Optional[Mapping[str, str]] = None,
apis: Union[Dict[str, List[str]], str] = {},
**kwargs: Any,
) -> SyncClientResponse:
loop = asyncio.get_event_loop()
return loop.run_until_complete(_request(hdrs.METH_GET, url, params=params, apis=apis, **kwargs))
return loop.run_until_complete(
_request(hdrs.METH_GET, url, params=params, apis=apis, **kwargs)
)


def post(
url: str,
*,
data: Any=None,
apis: Union[Dict[str, List[str]], str]={},
data: Any = None,
apis: Union[Dict[str, List[str]], str] = {},
**kwargs: Any,
) -> SyncClientResponse:
loop = asyncio.get_event_loop()
return loop.run_until_complete(_request(hdrs.METH_POST, url, data=data, apis=apis, **kwargs))
return loop.run_until_complete(
_request(hdrs.METH_POST, url, data=data, apis=apis, **kwargs)
)


def put(
url: str,
*,
data: Any=None,
apis: Union[Dict[str, List[str]], str]={},
data: Any = None,
apis: Union[Dict[str, List[str]], str] = {},
**kwargs: Any,
) -> SyncClientResponse:
loop = asyncio.get_event_loop()
return loop.run_until_complete(_request(hdrs.METH_PUT, url, data=data, apis=apis, **kwargs))
return loop.run_until_complete(
_request(hdrs.METH_PUT, url, data=data, apis=apis, **kwargs)
)


def delete(
url: str,
*,
data: Any=None,
apis: Union[Dict[str, List[str]], str]={},
data: Any = None,
apis: Union[Dict[str, List[str]], str] = {},
**kwargs: Any,
) -> SyncClientResponse:
loop = asyncio.get_event_loop()
return loop.run_until_complete(_request(hdrs.METH_DELETE, url, data=data, apis=apis, **kwargs))
return loop.run_until_complete(
_request(hdrs.METH_DELETE, url, data=data, apis=apis, **kwargs)
)


async def _ws_connect(
url: str,
*,
send_str: Optional[Union[str, List[str]]]=None,
send_json: Any=None,
hdlr_str: Optional[WsStrHandler]=None,
hdlr_json: Optional[WsJsonHandler]=None,
apis: Union[Dict[str, List[str]], str]={},
send_str: Optional[Union[str, List[str]]] = None,
send_json: Any = None,
hdlr_str: Optional[WsStrHandler] = None,
hdlr_json: Optional[WsJsonHandler] = None,
apis: Union[Dict[str, List[str]], str] = {},
**kwargs: Any,
) -> None:
async with Client(apis=apis) as client:
Expand All @@ -122,23 +149,25 @@ async def _ws_connect(
def ws_connect(
url: str,
*,
send_str: Optional[Union[str, List[str]]]=None,
send_json: Any=None,
hdlr_str: Optional[WsStrHandler]=None,
hdlr_json: Optional[WsJsonHandler]=None,
apis: Union[Dict[str, List[str]], str]={},
send_str: Optional[Union[str, List[str]]] = None,
send_json: Any = None,
hdlr_str: Optional[WsStrHandler] = None,
hdlr_json: Optional[WsJsonHandler] = None,
apis: Union[Dict[str, List[str]], str] = {},
**kwargs: Any,
) -> None:
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(_ws_connect(
url,
send_str=send_str,
send_json=send_json,
hdlr_str=hdlr_str,
hdlr_json=hdlr_json,
apis=apis,
**kwargs,
))
loop.run_until_complete(
_ws_connect(
url,
send_str=send_str,
send_json=send_json,
hdlr_str=hdlr_str,
hdlr_json=hdlr_json,
apis=apis,
**kwargs,
)
)
except KeyboardInterrupt:
pass
54 changes: 38 additions & 16 deletions pybotters/auth.py
Expand Up @@ -31,15 +31,22 @@ def bybit(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]:
if url.scheme == 'https':
query.extend({'api_key': key, 'timestamp': expires})
query_string = '&'.join(f'{k}={v}' for k, v in sorted(query.items()))
sign = hmac.new(secret, query_string.encode(), hashlib.sha256).hexdigest()
sign = hmac.new(
secret, query_string.encode(), hashlib.sha256
).hexdigest()
query.extend({'sign': sign})
else:
expires = str(int((time.time() + 1.0) * 1000))
path = f'{method}/realtime{expires}'
signature = hmac.new(secret, path.encode(), hashlib.sha256).hexdigest()
query.extend({'api_key': key, 'expires': expires, 'signature': signature})
query.extend(
{'api_key': key, 'expires': expires, 'signature': signature}
)
url = url.with_query(query)
args = (method, url, )
args = (
method,
url,
)
else:
data.update({'api_key': key, 'timestamp': expires})
body = FormData(sorted(data.items()))()
Expand Down Expand Up @@ -67,7 +74,9 @@ def btcmex(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]:
message = f'{method}{path}{expires}'.encode() + body._value
signature = hmac.new(secret, message, hashlib.sha256).hexdigest()
kwargs.update({'data': body})
headers.update({'api-expires': expires, 'api-key': key, 'api-signature': signature})
headers.update(
{'api-expires': expires, 'api-key': key, 'api-signature': signature}
)

return args

Expand All @@ -88,10 +97,15 @@ def binance(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]:
query = MultiDict(url.query)
query.extend({'timestamp': expires})
query_string = '&'.join(f'{k}={v}' for k, v in query.items())
signature = hmac.new(secret, query_string.encode(), hashlib.sha256).hexdigest()
signature = hmac.new(
secret, query_string.encode(), hashlib.sha256
).hexdigest()
query.extend({'signature': signature})
url = url.with_query(query)
args = (method, url, )
args = (
method,
url,
)
else:
data.update({'timestamp': expires})
body = FormData(data)()
Expand Down Expand Up @@ -120,7 +134,9 @@ def bitflyer(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]:
text = f'{timestamp}{method}{path}'.encode() + body._value
signature = hmac.new(secret, text, hashlib.sha256).hexdigest()
kwargs.update({'data': body})
headers.update({'ACCESS-KEY': key, 'ACCESS-TIMESTAMP': timestamp, 'ACCESS-SIGN': signature})
headers.update(
{'ACCESS-KEY': key, 'ACCESS-TIMESTAMP': timestamp, 'ACCESS-SIGN': signature}
)

return args

Expand All @@ -141,13 +157,14 @@ def gmocoin(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]:
text = f'{timestamp}{method}{path}'.encode() + body._value
signature = hmac.new(secret, text, hashlib.sha256).hexdigest()
kwargs.update({'data': body})
headers.update({'API-KEY': key, 'API-TIMESTAMP': timestamp, 'API-SIGN': signature})
headers.update(
{'API-KEY': key, 'API-TIMESTAMP': timestamp, 'API-SIGN': signature}
)

return args

@staticmethod
def liquid(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 {}
headers: CIMultiDict = kwargs['headers']
Expand All @@ -157,7 +174,11 @@ def liquid(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]:
secret: bytes = session.__dict__['_apis'][Hosts.items[url.host].name][1]

json_payload = json.dumps(
{'path': url.raw_path_qs, 'nonce': str(int(time.time() * 1000)), 'token_id': key},
{
'path': url.raw_path_qs,
'nonce': str(int(time.time() * 1000)),
'token_id': key,
},
separators=(',', ':'),
).encode()
json_header = json.dumps(
Expand All @@ -170,9 +191,7 @@ def liquid(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]:
]
signing_input = b'.'.join(segments)
signature = hmac.new(secret, signing_input, hashlib.sha256).digest()
segments.append(
base64.urlsafe_b64encode(signature).replace(b'=', b'')
)
segments.append(base64.urlsafe_b64encode(signature).replace(b'=', b''))
encoded_string = b'.'.join(segments).decode()
body = JsonPayload(data) if data else FormData(data)()
kwargs.update({'data': body})
Expand Down Expand Up @@ -200,7 +219,9 @@ def bitbank(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]:
text = nonce.encode() + body._value
signature = hmac.new(secret, text, hashlib.sha256).hexdigest()
kwargs.update({'data': body})
headers.update({'ACCESS-KEY': key, 'ACCESS-NONCE': nonce, 'ACCESS-SIGNATURE': signature})
headers.update(
{'ACCESS-KEY': key, 'ACCESS-NONCE': nonce, 'ACCESS-SIGNATURE': signature}
)

return args

Expand Down Expand Up @@ -242,7 +263,9 @@ def bitmex(args: Tuple[str, URL], kwargs: Dict[str, Any]) -> Tuple[str, URL]:
message = f'{method}{path}{expires}'.encode() + body._value
signature = hmac.new(secret, message, hashlib.sha256).hexdigest()
kwargs.update({'data': body})
headers.update({'api-expires': expires, 'api-key': key, 'api-signature': signature})
headers.update(
{'api-expires': expires, 'api-key': key, 'api-signature': signature}
)

return args

Expand Down Expand Up @@ -275,7 +298,6 @@ class Hosts:
'vstream.binance.com': Item('binance', Auth.binance),
'testnet.binancefuture.com': Item('binance_testnet', Auth.binance),
'stream.binancefuture.com': Item('binance_testnet', Auth.binance),
'testnet.binancefuture.com': Item('binance_testnet', Auth.binance),
'dstream.binancefuture.com': Item('binance_testnet', Auth.binance),
'testnet.binanceops.com': Item('binance_testnet', Auth.binance),
'testnetws.binanceops.com': Item('binance_testnet', Auth.binance),
Expand Down

0 comments on commit 6244c5f

Please sign in to comment.