p2bit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- p2bit-0.1.0/PKG-INFO +9 -0
- p2bit-0.1.0/pyproject.toml +20 -0
- p2bit-0.1.0/setup.cfg +4 -0
- p2bit-0.1.0/src/p2bit/__init__.py +19 -0
- p2bit-0.1.0/src/p2bit/_api.py +25 -0
- p2bit-0.1.0/src/p2bit/_auth.py +23 -0
- p2bit-0.1.0/src/p2bit/_base.py +34 -0
- p2bit-0.1.0/src/p2bit/_request.py +158 -0
- p2bit-0.1.0/src/p2bit/_types.py +21 -0
- p2bit-0.1.0/src/p2bit/async_client.py +104 -0
- p2bit-0.1.0/src/p2bit/client.py +96 -0
- p2bit-0.1.0/src/p2bit/exceptions.py +11 -0
- p2bit-0.1.0/src/p2bit.egg-info/PKG-INFO +9 -0
- p2bit-0.1.0/src/p2bit.egg-info/SOURCES.txt +15 -0
- p2bit-0.1.0/src/p2bit.egg-info/dependency_links.txt +1 -0
- p2bit-0.1.0/src/p2bit.egg-info/requires.txt +3 -0
- p2bit-0.1.0/src/p2bit.egg-info/top_level.txt +1 -0
p2bit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: p2bit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sync/async lib for the Bybit API
|
|
5
|
+
Author-email: herbalsommml <herbalsomml@gmail.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Requires-Dist: pycryptodome>=3.23.0
|
|
9
|
+
Requires-Dist: requests>=2.34.2
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "p2bit"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Sync/async lib for the Bybit API"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "herbalsommml", email = "herbalsomml@gmail.com" }
|
|
12
|
+
]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"httpx>=0.28.1",
|
|
15
|
+
"pycryptodome>=3.23.0",
|
|
16
|
+
"requests>=2.34.2",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["src"]
|
p2bit-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .async_client import AsyncBybitClient
|
|
2
|
+
from .client import SyncBybitClient
|
|
3
|
+
from .exceptions import FailedRequestError
|
|
4
|
+
from ._types import AccountType, Domain, Endpoint, Endpoints, HttpMethod, Tld
|
|
5
|
+
|
|
6
|
+
VERSION = "0.0.1"
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"AsyncBybitClient",
|
|
10
|
+
"SyncBybitClient",
|
|
11
|
+
"FailedRequestError",
|
|
12
|
+
"AccountType",
|
|
13
|
+
"Domain",
|
|
14
|
+
"Endpoint",
|
|
15
|
+
"Endpoints",
|
|
16
|
+
"HttpMethod",
|
|
17
|
+
"Tld",
|
|
18
|
+
"VERSION",
|
|
19
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from _types import AccountType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def build_balance_params(
|
|
7
|
+
*,
|
|
8
|
+
member_id: str | None = None,
|
|
9
|
+
account_type: AccountType = "FUND",
|
|
10
|
+
coins: list[str] | None = None,
|
|
11
|
+
with_bonus: bool = False,
|
|
12
|
+
) -> dict[str, Any]:
|
|
13
|
+
if account_type == "UNIFIED" and not member_id:
|
|
14
|
+
raise ValueError("member_id is required for unified account type")
|
|
15
|
+
|
|
16
|
+
params: dict[str, Any] = {"accountType": account_type}
|
|
17
|
+
|
|
18
|
+
if member_id:
|
|
19
|
+
params["memberId"] = member_id
|
|
20
|
+
if coins:
|
|
21
|
+
params["coin"] = ",".join(coins)
|
|
22
|
+
if with_bonus:
|
|
23
|
+
params["withBonus"] = 1
|
|
24
|
+
|
|
25
|
+
return params
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import hmac
|
|
4
|
+
|
|
5
|
+
from Crypto.Hash import SHA256
|
|
6
|
+
from Crypto.PublicKey import RSA
|
|
7
|
+
from Crypto.Signature import PKCS1_v1_5
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def sign(
|
|
11
|
+
secret: str,
|
|
12
|
+
message: str | bytes,
|
|
13
|
+
*,
|
|
14
|
+
use_rsa: bool = False,
|
|
15
|
+
) -> str:
|
|
16
|
+
data = message if isinstance(message, bytes) else message.encode("utf-8")
|
|
17
|
+
|
|
18
|
+
if use_rsa:
|
|
19
|
+
digest = SHA256.new(data)
|
|
20
|
+
signature = PKCS1_v1_5.new(RSA.import_key(secret)).sign(digest)
|
|
21
|
+
return base64.b64encode(signature).decode()
|
|
22
|
+
|
|
23
|
+
return hmac.new(secret.encode("utf-8"), data, hashlib.sha256).hexdigest()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from _types import Domain, Tld
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def resolve_base_url(testnet: bool, domain: Domain, tld: Tld) -> str:
|
|
5
|
+
if testnet:
|
|
6
|
+
return "https://api-testnet.bybit.com"
|
|
7
|
+
return f"https://api.{domain}.{tld}"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseBybitClient:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
api_key: str,
|
|
14
|
+
api_secret: str,
|
|
15
|
+
*,
|
|
16
|
+
testnet: bool = False,
|
|
17
|
+
recv_window: int = 5000,
|
|
18
|
+
rsa: bool = False,
|
|
19
|
+
domain: Domain = "bybit",
|
|
20
|
+
tld: Tld = "com",
|
|
21
|
+
timeout: float = 10,
|
|
22
|
+
) -> None:
|
|
23
|
+
if not api_key or not api_secret:
|
|
24
|
+
raise ValueError("api_key and api_secret are required")
|
|
25
|
+
|
|
26
|
+
self._api_key = api_key
|
|
27
|
+
self._api_secret = api_secret
|
|
28
|
+
self._testnet = testnet
|
|
29
|
+
self._recv_window = recv_window
|
|
30
|
+
self._rsa = rsa
|
|
31
|
+
self._domain = domain
|
|
32
|
+
self._tld = tld
|
|
33
|
+
self._timeout = timeout
|
|
34
|
+
self._base_url = resolve_base_url(testnet, domain, tld)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from json import JSONDecodeError
|
|
6
|
+
from typing import Any, Protocol
|
|
7
|
+
|
|
8
|
+
from _auth import sign
|
|
9
|
+
from _types import HttpMethod
|
|
10
|
+
from exceptions import FailedRequestError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class JsonResponse(Protocol):
|
|
14
|
+
status_code: int
|
|
15
|
+
headers: Any
|
|
16
|
+
text: str
|
|
17
|
+
|
|
18
|
+
def json(self) -> Any: ...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class PreparedRequest:
|
|
23
|
+
method: HttpMethod
|
|
24
|
+
path: str
|
|
25
|
+
headers: dict[str, str]
|
|
26
|
+
payload: str
|
|
27
|
+
endpoint: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def sanitize_params(params: dict[str, Any]) -> None:
|
|
31
|
+
for key, value in params.items():
|
|
32
|
+
if isinstance(value, float) and value.is_integer():
|
|
33
|
+
params[key] = int(value)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def build_payload(method: HttpMethod, params: dict[str, Any]) -> str:
|
|
37
|
+
if method == "GET":
|
|
38
|
+
return "&".join(f"{key}={value}" for key, value in params.items())
|
|
39
|
+
return json.dumps(params)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_signature(
|
|
43
|
+
api_key: str,
|
|
44
|
+
api_secret: str,
|
|
45
|
+
recv_window: int,
|
|
46
|
+
payload: str,
|
|
47
|
+
timestamp: int,
|
|
48
|
+
*,
|
|
49
|
+
use_rsa: bool = False,
|
|
50
|
+
) -> str:
|
|
51
|
+
sign_string = f"{timestamp}{api_key}{recv_window}{payload}"
|
|
52
|
+
return sign(api_secret, sign_string, use_rsa=use_rsa)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_headers(
|
|
56
|
+
api_key: str,
|
|
57
|
+
signature: str,
|
|
58
|
+
timestamp: int,
|
|
59
|
+
recv_window: int,
|
|
60
|
+
) -> dict[str, str]:
|
|
61
|
+
return {
|
|
62
|
+
"X-BAPI-API-KEY": api_key,
|
|
63
|
+
"X-BAPI-SIGN": signature,
|
|
64
|
+
"X-BAPI-SIGN-TYPE": "2",
|
|
65
|
+
"X-BAPI-TIMESTAMP": str(timestamp),
|
|
66
|
+
"X-BAPI-RECV-WINDOW": str(recv_window),
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_path(endpoint: str, method: HttpMethod, payload: str) -> str:
|
|
72
|
+
path = endpoint.lstrip("/")
|
|
73
|
+
if method == "GET" and payload:
|
|
74
|
+
return f"{path}?{payload}"
|
|
75
|
+
return path
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def build_full_url(base_url: str, path: str) -> str:
|
|
79
|
+
return f"{base_url}/{path}"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def prepare_request(
|
|
83
|
+
api_key: str,
|
|
84
|
+
api_secret: str,
|
|
85
|
+
recv_window: int,
|
|
86
|
+
endpoint: str,
|
|
87
|
+
method: HttpMethod,
|
|
88
|
+
params: dict[str, Any] | None,
|
|
89
|
+
*,
|
|
90
|
+
use_rsa: bool = False,
|
|
91
|
+
) -> PreparedRequest:
|
|
92
|
+
params = dict(params or {})
|
|
93
|
+
sanitize_params(params)
|
|
94
|
+
|
|
95
|
+
timestamp = int(time.time() * 1000)
|
|
96
|
+
payload = build_payload(method, params)
|
|
97
|
+
signature = build_signature(
|
|
98
|
+
api_key, api_secret, recv_window, payload, timestamp, use_rsa=use_rsa
|
|
99
|
+
)
|
|
100
|
+
headers = build_headers(api_key, signature, timestamp, recv_window)
|
|
101
|
+
|
|
102
|
+
return PreparedRequest(
|
|
103
|
+
method=method,
|
|
104
|
+
path=build_path(endpoint, method, payload),
|
|
105
|
+
headers=headers,
|
|
106
|
+
payload=payload,
|
|
107
|
+
endpoint=endpoint,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _failed_request(
|
|
112
|
+
response: JsonResponse,
|
|
113
|
+
endpoint: str,
|
|
114
|
+
payload: str,
|
|
115
|
+
message: str,
|
|
116
|
+
status_code: int | str,
|
|
117
|
+
) -> FailedRequestError:
|
|
118
|
+
return FailedRequestError(
|
|
119
|
+
request=f"{endpoint}: {payload}",
|
|
120
|
+
message=message,
|
|
121
|
+
status_code=status_code,
|
|
122
|
+
time=datetime.now(timezone.utc).strftime("%H:%M:%S"),
|
|
123
|
+
resp_headers=response.headers,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def process_response(response: JsonResponse, endpoint: str, payload: str) -> Any:
|
|
128
|
+
if response.status_code != 200:
|
|
129
|
+
if response.status_code == 403:
|
|
130
|
+
message = (
|
|
131
|
+
"Access denied error. Possible causes: 1) your IP is located "
|
|
132
|
+
"in the US or Mainland China, 2) IP banned due to ratelimit violation"
|
|
133
|
+
)
|
|
134
|
+
elif response.status_code == 401:
|
|
135
|
+
message = (
|
|
136
|
+
"Unauthorized. Possible causes: 1) incorrect API key and/or secret, "
|
|
137
|
+
"2) incorrect environment: Mainnet vs Testnet"
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
message = f"HTTP status code is: {response.status_code}, expected: 200"
|
|
141
|
+
raise _failed_request(response, endpoint, payload, message, response.status_code)
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
body = response.json()
|
|
145
|
+
except JSONDecodeError:
|
|
146
|
+
raise _failed_request(
|
|
147
|
+
response, endpoint, payload, "Could not decode JSON.", response.status_code
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
ret_code = "retCode" if "retCode" in body else "ret_code"
|
|
151
|
+
ret_msg = "retMsg" if "retMsg" in body else "ret_msg"
|
|
152
|
+
|
|
153
|
+
if body[ret_code]:
|
|
154
|
+
raise _failed_request(
|
|
155
|
+
response, endpoint, payload, body[ret_msg], body[ret_code]
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return body
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
HttpMethod = Literal["GET", "POST"]
|
|
5
|
+
Domain = Literal["bybit", "bytick", "byhkbit"]
|
|
6
|
+
Tld = Literal["com", "nl", "tr", "kz"]
|
|
7
|
+
AccountType = Literal["UNIFIED", "FUND"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class Endpoint:
|
|
12
|
+
path: str
|
|
13
|
+
method: HttpMethod
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Endpoints:
|
|
17
|
+
ACCOUNT_INFO = Endpoint("/v5/p2p/user/personal/info", "POST")
|
|
18
|
+
CURRENT_BALANCE = Endpoint(
|
|
19
|
+
"/v5/asset/transfer/query-account-coins-balance", "GET"
|
|
20
|
+
)
|
|
21
|
+
RELEASE_ORDER = Endpoint("/v5/p2p/order/finish", "POST")
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from _api import build_balance_params
|
|
6
|
+
from _base import BaseBybitClient
|
|
7
|
+
from _request import prepare_request, process_response
|
|
8
|
+
from _types import AccountType, Domain, Endpoints, HttpMethod, Tld
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncBybitClient(BaseBybitClient):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
api_key: str,
|
|
15
|
+
api_secret: str,
|
|
16
|
+
*,
|
|
17
|
+
testnet: bool = False,
|
|
18
|
+
recv_window: int = 5000,
|
|
19
|
+
rsa: bool = False,
|
|
20
|
+
domain: Domain = "bybit",
|
|
21
|
+
tld: Tld = "com",
|
|
22
|
+
timeout: float = 10,
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__(
|
|
25
|
+
api_key,
|
|
26
|
+
api_secret,
|
|
27
|
+
testnet=testnet,
|
|
28
|
+
recv_window=recv_window,
|
|
29
|
+
rsa=rsa,
|
|
30
|
+
domain=domain,
|
|
31
|
+
tld=tld,
|
|
32
|
+
timeout=timeout,
|
|
33
|
+
)
|
|
34
|
+
self._http = httpx.AsyncClient(
|
|
35
|
+
base_url=self._base_url,
|
|
36
|
+
timeout=timeout,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
async def __aenter__(self) -> "AsyncBybitClient":
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
43
|
+
await self.close()
|
|
44
|
+
|
|
45
|
+
async def close(self) -> None:
|
|
46
|
+
await self._http.aclose()
|
|
47
|
+
|
|
48
|
+
async def request(
|
|
49
|
+
self,
|
|
50
|
+
endpoint: str,
|
|
51
|
+
method: HttpMethod,
|
|
52
|
+
params: dict[str, Any] | None = None,
|
|
53
|
+
) -> Any:
|
|
54
|
+
prepared = prepare_request(
|
|
55
|
+
self._api_key,
|
|
56
|
+
self._api_secret,
|
|
57
|
+
self._recv_window,
|
|
58
|
+
endpoint,
|
|
59
|
+
method,
|
|
60
|
+
params,
|
|
61
|
+
use_rsa=self._rsa,
|
|
62
|
+
)
|
|
63
|
+
request = self._http.build_request(
|
|
64
|
+
method=prepared.method,
|
|
65
|
+
url=prepared.path,
|
|
66
|
+
headers=prepared.headers,
|
|
67
|
+
content=prepared.payload if prepared.method == "POST" else None,
|
|
68
|
+
)
|
|
69
|
+
response = await self._http.send(request)
|
|
70
|
+
return process_response(response, prepared.endpoint, prepared.payload)
|
|
71
|
+
|
|
72
|
+
async def get_account_info(self) -> dict[str, Any]:
|
|
73
|
+
response = await self.request(
|
|
74
|
+
Endpoints.ACCOUNT_INFO.path,
|
|
75
|
+
Endpoints.ACCOUNT_INFO.method,
|
|
76
|
+
)
|
|
77
|
+
return response.get("result")
|
|
78
|
+
|
|
79
|
+
async def get_current_balance(
|
|
80
|
+
self,
|
|
81
|
+
member_id: str | None = None,
|
|
82
|
+
account_type: AccountType = "FUND",
|
|
83
|
+
coins: list[str] | None = None,
|
|
84
|
+
with_bonues: bool = False,
|
|
85
|
+
) -> dict[str, Any]:
|
|
86
|
+
response = await self.request(
|
|
87
|
+
Endpoints.CURRENT_BALANCE.path,
|
|
88
|
+
Endpoints.CURRENT_BALANCE.method,
|
|
89
|
+
params=build_balance_params(
|
|
90
|
+
member_id=member_id,
|
|
91
|
+
account_type=account_type,
|
|
92
|
+
coins=coins,
|
|
93
|
+
with_bonus=with_bonues,
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
return response.get("result")
|
|
97
|
+
|
|
98
|
+
async def release_order(self, order_id: str | int) -> dict[str, Any]:
|
|
99
|
+
response = await self.request(
|
|
100
|
+
Endpoints.RELEASE_ORDER.path,
|
|
101
|
+
Endpoints.RELEASE_ORDER.method,
|
|
102
|
+
params={"orderId": str(order_id)},
|
|
103
|
+
)
|
|
104
|
+
return response.get("result")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from _api import build_balance_params
|
|
6
|
+
from _base import BaseBybitClient
|
|
7
|
+
from _request import build_full_url, prepare_request, process_response
|
|
8
|
+
from _types import AccountType, Domain, Endpoints, HttpMethod, Tld
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SyncBybitClient(BaseBybitClient):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
api_key: str,
|
|
15
|
+
api_secret: str,
|
|
16
|
+
*,
|
|
17
|
+
testnet: bool = False,
|
|
18
|
+
recv_window: int = 5000,
|
|
19
|
+
rsa: bool = False,
|
|
20
|
+
domain: Domain = "bybit",
|
|
21
|
+
tld: Tld = "com",
|
|
22
|
+
timeout: float = 10,
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__(
|
|
25
|
+
api_key,
|
|
26
|
+
api_secret,
|
|
27
|
+
testnet=testnet,
|
|
28
|
+
recv_window=recv_window,
|
|
29
|
+
rsa=rsa,
|
|
30
|
+
domain=domain,
|
|
31
|
+
tld=tld,
|
|
32
|
+
timeout=timeout,
|
|
33
|
+
)
|
|
34
|
+
self._http = requests.Session()
|
|
35
|
+
|
|
36
|
+
def request(
|
|
37
|
+
self,
|
|
38
|
+
endpoint: str,
|
|
39
|
+
method: HttpMethod,
|
|
40
|
+
params: dict[str, Any] | None = None,
|
|
41
|
+
) -> Any:
|
|
42
|
+
prepared = prepare_request(
|
|
43
|
+
self._api_key,
|
|
44
|
+
self._api_secret,
|
|
45
|
+
self._recv_window,
|
|
46
|
+
endpoint,
|
|
47
|
+
method,
|
|
48
|
+
params,
|
|
49
|
+
use_rsa=self._rsa,
|
|
50
|
+
)
|
|
51
|
+
url = build_full_url(self._base_url, prepared.path)
|
|
52
|
+
request = requests.Request(
|
|
53
|
+
method=prepared.method,
|
|
54
|
+
url=url,
|
|
55
|
+
headers=prepared.headers,
|
|
56
|
+
data=prepared.payload if prepared.method == "POST" else None,
|
|
57
|
+
)
|
|
58
|
+
response = self._http.send(
|
|
59
|
+
self._http.prepare_request(request),
|
|
60
|
+
timeout=self._timeout,
|
|
61
|
+
)
|
|
62
|
+
return process_response(response, prepared.endpoint, prepared.payload)
|
|
63
|
+
|
|
64
|
+
def get_account_info(self) -> dict[str, Any]:
|
|
65
|
+
response = self.request(
|
|
66
|
+
Endpoints.ACCOUNT_INFO.path,
|
|
67
|
+
Endpoints.ACCOUNT_INFO.method,
|
|
68
|
+
)
|
|
69
|
+
return response.get("result")
|
|
70
|
+
|
|
71
|
+
def get_current_balance(
|
|
72
|
+
self,
|
|
73
|
+
member_id: str | None = None,
|
|
74
|
+
account_type: AccountType = "FUND",
|
|
75
|
+
coins: list[str] | None = None,
|
|
76
|
+
with_bonues: bool = False,
|
|
77
|
+
) -> dict[str, Any]:
|
|
78
|
+
response = self.request(
|
|
79
|
+
Endpoints.CURRENT_BALANCE.path,
|
|
80
|
+
Endpoints.CURRENT_BALANCE.method,
|
|
81
|
+
params=build_balance_params(
|
|
82
|
+
member_id=member_id,
|
|
83
|
+
account_type=account_type,
|
|
84
|
+
coins=coins,
|
|
85
|
+
with_bonus=with_bonues,
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
return response.get("result")
|
|
89
|
+
|
|
90
|
+
def release_order(self, order_id: str | int) -> dict[str, Any]:
|
|
91
|
+
response = self.request(
|
|
92
|
+
Endpoints.RELEASE_ORDER.path,
|
|
93
|
+
Endpoints.RELEASE_ORDER.method,
|
|
94
|
+
params={"orderId": str(order_id)},
|
|
95
|
+
)
|
|
96
|
+
return response.get("result")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class FailedRequestError(Exception):
|
|
2
|
+
def __init__(self, request, message, status_code, time, resp_headers):
|
|
3
|
+
self.request = request
|
|
4
|
+
self.message = message
|
|
5
|
+
self.status_code = status_code
|
|
6
|
+
self.time = time
|
|
7
|
+
self.resp_headers = resp_headers
|
|
8
|
+
super().__init__(
|
|
9
|
+
f"{message.capitalize()} (ErrCode: {status_code}) (ErrTime: {time})"
|
|
10
|
+
f".\nRequest → {request}."
|
|
11
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: p2bit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sync/async lib for the Bybit API
|
|
5
|
+
Author-email: herbalsommml <herbalsomml@gmail.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Requires-Dist: pycryptodome>=3.23.0
|
|
9
|
+
Requires-Dist: requests>=2.34.2
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/p2bit/__init__.py
|
|
3
|
+
src/p2bit/_api.py
|
|
4
|
+
src/p2bit/_auth.py
|
|
5
|
+
src/p2bit/_base.py
|
|
6
|
+
src/p2bit/_request.py
|
|
7
|
+
src/p2bit/_types.py
|
|
8
|
+
src/p2bit/async_client.py
|
|
9
|
+
src/p2bit/client.py
|
|
10
|
+
src/p2bit/exceptions.py
|
|
11
|
+
src/p2bit.egg-info/PKG-INFO
|
|
12
|
+
src/p2bit.egg-info/SOURCES.txt
|
|
13
|
+
src/p2bit.egg-info/dependency_links.txt
|
|
14
|
+
src/p2bit.egg-info/requires.txt
|
|
15
|
+
src/p2bit.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
p2bit
|