diff --git a/poetry.lock b/poetry.lock index 1d7ae76..71d0f6d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aiohttp" @@ -385,6 +385,23 @@ files = [ [package.dependencies] identify = "*" +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + [[package]] name = "dill" version = "0.3.7" @@ -1776,6 +1793,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1947,6 +1965,33 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruff" +version = "0.7.0" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.7.0-py3-none-linux_armv6l.whl", hash = "sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628"}, + {file = "ruff-0.7.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737"}, + {file = "ruff-0.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11"}, + {file = "ruff-0.7.0-py3-none-win32.whl", hash = "sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec"}, + {file = "ruff-0.7.0-py3-none-win_amd64.whl", hash = "sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2"}, + {file = "ruff-0.7.0-py3-none-win_arm64.whl", hash = "sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e"}, + {file = "ruff-0.7.0.tar.gz", hash = "sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b"}, +] + [[package]] name = "setuptools" version = "69.0.3" @@ -2349,4 +2394,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "fa37e344ad87bc39410b203bc3ad364b4ec8131d8401407e97df42a30f836b9a" +content-hash = "f77cba89abaadf700ad5a7e508001a741eb1d5464257333fce46ea5ce3f0ecb1" diff --git a/pyproject.toml b/pyproject.toml index f74a995..9e9115c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "python-bitpin" -version = "0.0.11" +version = "1.0.0" description = "Bitpin Exchange Python SDK" authors = ["AMiWR "] license = "MIT" @@ -14,6 +14,7 @@ python = ">=3.9,<4.0" requests = "^2.31.0" aiohttp = "^3.8.5" pysocks = "^1.7.1" +deprecated = "^1.2.14" [tool.poetry.group.dev.dependencies] @@ -25,6 +26,7 @@ dead = "^1.5.2" pytype = "^2023.8.22" pydocstyle = "^6.3.0" vulture = "^2.9.1" +ruff = "^0.7.0" [tool.poetry.group.docs.dependencies] diff --git a/src/bitpin/__init__.py b/src/bitpin/__init__.py index 49f9dce..751457f 100644 --- a/src/bitpin/__init__.py +++ b/src/bitpin/__init__.py @@ -1,12 +1,11 @@ """# Bitpin Python Library.""" +import deprecated + from .clients.async_client import AsyncClient from .clients.client import Client -__all__ = [ - "AsyncClient", - "Client", -] +__all__ = ["AsyncClient", "Client", "deprecated"] # Meta diff --git a/src/bitpin/clients/__pycache__/__init__.cpython-312.pyc b/src/bitpin/clients/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..950d0c3 Binary files /dev/null and b/src/bitpin/clients/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/bitpin/clients/__pycache__/async_client.cpython-312.pyc b/src/bitpin/clients/__pycache__/async_client.cpython-312.pyc new file mode 100644 index 0000000..7ee2744 Binary files /dev/null and b/src/bitpin/clients/__pycache__/async_client.cpython-312.pyc differ diff --git a/src/bitpin/clients/__pycache__/client.cpython-312.pyc b/src/bitpin/clients/__pycache__/client.cpython-312.pyc new file mode 100644 index 0000000..e071ccb Binary files /dev/null and b/src/bitpin/clients/__pycache__/client.cpython-312.pyc differ diff --git a/src/bitpin/clients/__pycache__/core.cpython-312.pyc b/src/bitpin/clients/__pycache__/core.cpython-312.pyc new file mode 100644 index 0000000..dce3673 Binary files /dev/null and b/src/bitpin/clients/__pycache__/core.cpython-312.pyc differ diff --git a/src/bitpin/clients/async_client.py b/src/bitpin/clients/async_client.py index 1a9512b..f7fe300 100644 --- a/src/bitpin/clients/async_client.py +++ b/src/bitpin/clients/async_client.py @@ -3,16 +3,18 @@ # pylint: disable=invalid-overridden-method import asyncio +from warnings import warn + import aiohttp -from .core import CoreClient -from .. import types as t from .. import enums +from .. import response_types as t +from .._utils import get_loop from ..exceptions import ( APIException, RequestException, ) -from .._utils import get_loop +from .core import CoreClient class AsyncClient(CoreClient): @@ -22,15 +24,17 @@ class AsyncClient(CoreClient): Methods: login: Login and set (refresh_token/access_token) refresh_access_token: Refresh token. - get_user_info: Get user info. get_currencies_info: Get currencies info. get_markets_info: Get markets info. + get_tickets_info: Get tickets info. get_wallets: Get wallets. get_orderbook: Get orderbook. get_recent_trades: Get recent trades. get_user_orders: Get user orders. create_order: Create order. cancel_order: Cancel order. + create_order_bulk: Create Bulk Order. + cancel_order_bulk: Cancel Bulk Order. get_user_trades: Get user trades. close_connection: Close connection. @@ -313,7 +317,8 @@ async def _handle_response(response: aiohttp.ClientResponse) -> t.DictStrAny: # return {"status": "success", "id": response.request_info.url.parts[-2]} return await response.json() # type: ignore[no-any-return] except ValueError as exc: - raise RequestException(f"Invalid Response: {await response.text()}") from exc + msg = f"Invalid Response: {await response.text()}" + raise RequestException(msg) from exc async def _background_relogin_task(self) -> None: # type: ignore[override] """Background relogin task.""" @@ -342,10 +347,10 @@ async def _handle_login(self) -> None: # type: ignore[override] await self.login() if self._background_relogin: - self.loop.create_task(self._background_relogin_task()) # noqa + self.loop.create_task(self._background_relogin_task()) if self._background_refresh_token: - self.loop.create_task(self._background_refresh_token_task()) # noqa + self.loop.create_task(self._background_refresh_token_task()) async def login(self, **kwargs) -> t.LoginResponse: # type: ignore[no-untyped-def, override] """ @@ -358,7 +363,7 @@ async def login(self, **kwargs) -> t.LoginResponse: # type: ignore[no-untyped-d Response (LoginResponse): Response. References: - [API Docs](https://docs.bitpin.ir/#02c24a5326) + [API Docs](https://docs.bitpin.ir/v1/docs/authentication/intro) """ kwargs["json"] = {"api_key": self.api_key, "secret_key": self.api_secret} @@ -383,7 +388,7 @@ async def refresh_access_token( # type: ignore[no-untyped-def, override] Response (RefreshTokenResponse): Response. References: - [API Docs](https://docs.bitpin.ir/#9b81094f74) + [API Docs](https://docs.bitpin.ir/v1/docs/authentication/refresh_token) """ kwargs["json"] = {"refresh": refresh_token or self.refresh_token} @@ -393,196 +398,242 @@ async def refresh_access_token( # type: ignore[no-untyped-def, override] return _ - async def get_user_info(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def, override] + # Deprecated Methods + async def get_user_info(self, **kwargs) -> None: # type: ignore[no-untyped-def, override] + """ + Get user info (DEPRECATED). """ - Get user info. - Args: - **kwargs: Kwargs. + warn( + "get_user_info is deprecated! if deprecated method's usage is still in need import client from bitpin.deprecated instead!", + DeprecationWarning, + 2, + ) + + # Working Methods + async def get_currencies_info( # type: ignore[no-untyped-def, override] + self, + ) -> t.CurrenciesInfo: + """ + Get currencies info. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#5b3c85d79e) + [API Docs](https://docs.bitpin.ir/v1/docs/market-data/currencies) + + Notes: + Rate limit: 10000/day or 200/minute if you are authenticated. """ - return await self._get(self.USER_INFO_URL, signed=True, **kwargs) + return await self._get(self.CURRENCIES_LIST_URL) - async def get_currencies_info( # type: ignore[no-untyped-def, override] - self, page: int = 1, **kwargs - ) -> t.DictStrAny: + async def get_markets_info(self) -> t.MarketsInfo: # type: ignore[no-untyped-def, override] """ - Get currencies info. - - Args: - page (int): Page. - **kwargs: Kwargs. + Get markets info. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#7e59da3d0d) + [API Docs](https://docs.bitpin.ir/v1/docs/market-data/markets) Notes: Rate limit: 10000/day or 200/minute if you are authenticated. """ - return await self._get(self.CURRENCIES_LIST_URL.format(page), **kwargs) + return await self._get(self.MARKETS_LIST_URL) - async def get_markets_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def, override] + async def get_tickers_info(self) -> t.DictStrAny: """ - Get markets info. - - Args: - page (int): Page. - **kwargs: Kwargs. + Get tickets info. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#334792bb2b) + [API Docs](https://docs.bitpin.ir/v1/docs/market-data/tickers) Notes: - Rate limit: 10000/day or 200/minute if you are authenticated. + Rate limit: 80/minute . """ - return await self._get(self.MARKETS_LIST_URL.format(page), **kwargs) + return await self._get(self.TICKERS_LIST_URL) - async def get_wallets(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def, override] + async def get_wallets( # type: ignore[no-untyped-def, override] + self, + assets: t.OptionalStr = None, + service: t.OptionalStr = None, + offset: t.OptionalInt = None, + limit: t.OptionalInt = None, + **kwargs, + ) -> t.WalletInfo: """ Get wallets. Args: - **kwargs: Kwargs. + assets: asset name [BTC, IRT, USDT, ...] + service: name of service + offset: asset balance offset, i.e. assets below 10000 + limit: maximum received assets info Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#9b93495188) + [API Docs](https://docs.bitpin.ir/v1/docs/wallets) Notes: Rate limit: 10000/day. """ + kwargs["params"] = {k: str(v) for k, v in locals().items() if v is not None and k not in {"self", "kwargs"}} return await self._get(self.WALLETS_URL, signed=True, **kwargs) async def get_orderbook( # type: ignore[no-untyped-def, override] self, - market_id: int, - type: t.OrderTypes, - **kwargs, # pylint: disable=redefined-builtin + symbol: str, ) -> t.OrderbookResponse: """ Get orderbook. Args: - market_id (int): Market ID. - type (OrderTypes): Type. - **kwargs: Kwargs. + symbol (str): i.e. BTC_IRT, ETH_USDT Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#ec7180fc0e) + [API Docs](https://docs.bitpin.ir/v1/docs/market-data/orderbook) + + Notes: + Rate Limit: 60 Requests/minute """ + return await self._get( # type: ignore[return-value] - self.ORDERBOOK_URL.format(market_id, str(type)), version=self.PUBLIC_API_VERSION_2, **kwargs + self.ORDERBOOK_URL.format(symbol, str(type)), + version=self.PUBLIC_API_VERSION_1, ) - async def get_recent_trades( # type: ignore[no-untyped-def, override] - self, market_id: int, **kwargs - ) -> t.TradeResponse: + async def get_recent_trades(self, symbol: str) -> t.RecentTradesInfo: # type: ignore[no-untyped-def, override] """ Get recent trades. Args: - market_id (int): Market ID. - **kwargs: Kwargs. + symbol (str): i.e. BTC_IRT. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#1dd63530b5) + [API Docs](https://docs.bitpin.ir/v1/docs/market-data/matches) + + Notes: + Rate Limit: 60 requests/minute """ - return await self._get(self.RECENT_TRADES_URL.format(market_id), **kwargs) # type: ignore[return-value] + return await self._get(self.RECENT_TRADES_URL.format(symbol)) # type: ignore[return-value] async def get_user_orders( # type: ignore[no-untyped-def, override] self, - market_id: t.OptionalInt = None, - type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin - state: t.OptionalStr = None, - mode: t.OptionalStr = None, + symbol: t.OptionalStr = None, + side: t.OptionalOrderTypesList = None, # pylint: disable=redefined-builtin + state: t.OptionalOrderStateList = None, + type: t.OptionalOrderModesList = None, identifier: t.OptionalStr = None, - page: int = 1, + start: t.OptionalDate = None, + end: t.OptionalDate = None, + ids_in: t.OptionalStrList = None, + identifiers_in: t.OptionalStrList = None, + offset: t.OptionalInt = None, + limit: t.OptionalInt = None, **kwargs, ) -> t.OpenOrdersResponse: """ Get user orders. Args: - market_id (int): Market ID. - type (OrderTypes): Type. - state (str): State. - mode (str): Mode. - identifier (str): Identifier. - page (int): Page. + symbol (Optional[str]): symbol (e.g., BTC_IRT, ETH_USDT). Defaults to None. + side (Optional[List[str]]): The type of order, either 'buy' or 'sell'. Defaults to None. + state (Optional[List[str]]): The state of the order, can be 'initial', 'active', or 'closed'. Defaults to None. + type (Optional[List[str]]): The type of the order, can be 'limit', 'market', 'stop_limit', or 'oco'. Defaults to None. + identifier (Optional[str]): A unique identifier for the order, useful for tracking or preventing duplicate entries. Defaults to None. + start (Optional[date]): Show orders created after this date. Defaults to None. + end (Optional[date]): Show orders created before this date. Defaults to None. + ids_in (Optional[List[str]]): A list of order IDs to filter results. Defaults to None. + identifiers_in (Optional[List[str]]): A list of specific order identifiers to filter results. Defaults to None. + offset (Optional[int]): Show orders with IDs less than this value. Defaults to None. + limit (Optional[int]): The maximum number of orders to retrieve (maximum: 100). Defaults to None. **kwargs: Kwargs. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#8a7c2a2af5) + [API Docs](https://docs.bitpin.ir/v1/docs/order/get_order_list) + + Notes: + Rate Limit: 80 Requests/minute """ - kwargs["params"] = {k: str(v) for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} + kwargs["params"] = kwargs.get("params", {}) + kwargs["params"].update( + {k: str(v) for k, v in locals().items() if v is not None and k not in {"self", "kwargs"}} + ) return await self._get(self.ORDERS_URL, signed=True, **kwargs) # type: ignore[return-value] async def create_order( # type: ignore[no-untyped-def, override] self, - market: int, - amount1: float, - price: float, - mode: t.OrderModes, - type: t.OrderTypes, # pylint: disable=redefined-builtin + symbol: str, + type: t.OrderModes, + side: t.OrderTypes, # pylint: disable=redefined-builtin + base_amount: float, + quote_amount: t.OptionalFloat = None, + price: t.OptionalFloat = None, + stop_price: t.OptionalFloat = None, + oco_target_price: t.OptionalFloat = None, identifier: t.OptionalStr = None, - price_limit: t.OptionalFloat = None, - price_stop: t.OptionalFloat = None, - price_limit_oco: t.OptionalFloat = None, - amount2: t.OptionalFloat = None, **kwargs, ) -> t.CreateOrderResponse: """ Create order. Args: - market (int): Market. - amount1 (float): Amount1. - price (float): Price. - mode (OrderModes): Mode. - type (OrderTypes): Type. - identifier (str): Identifier. - price_limit (float): Price limit. - price_stop (float): Price stop. - price_limit_oco (float): Price limit oco. - amount2 (float): Amount2. + symbol (str): i.e. [USDT_IRT] + type: t.OrderModes + side: t.OrderTypes + price: float + base_amount: float + quote_amount: t.OptionalFloat = None + stop_price: t.OptionalFloat = None + oco_target_price: t.OptionalFloat = None + identifier: t.OptionalStr = None **kwargs: Kwargs. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#34b353d77b) - """ + [API Docs](https://docs.bitpin.ir/v1/docs/order/place_order) - kwargs["json"] = {k: v for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} + Notes: + Rate Limit: 5400 Requests/hour + """ + + kwargs["json"] = { + "symbol": symbol, + "type": type, + "side": side, + "price": price, + "base_amount": base_amount, + "quote_amount": quote_amount, + "stop_price": stop_price, + "oco_target_price": oco_target_price, + "identifier": identifier, + } + + kwargs["json"] = {k: v for k, v in kwargs["json"].items() if v is not None} return await self._post(self.ORDERS_URL, signed=True, **kwargs) # type: ignore[return-value] async def cancel_order( # type: ignore[no-untyped-def, override] @@ -599,36 +650,119 @@ async def cancel_order( # type: ignore[no-untyped-def, override] Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#3fe8d57657) + [API Docs](https://docs.bitpin.ir/v1/docs/order/cancel) + + Notes: + Rate Limit: 5400 Requests/hour + """ + + try: + await self._delete(self.ORDERS_URL + f"{order_id}/", signed=True, **kwargs) # type: ignore[return-value] + except APIException as e: + raise e from e + else: + return {"status": "success", "id": order_id} + + async def create_order_bulk(self, orders: t.BulkOrderList, **kwargs): + """ + Create multiple orders in bulk. + + Args: + orders (BulkOrderList): A list of order objects to be created in bulk. + Each order object (dict) should contain: + - symbol (str): The market symbol for the order (e.g., USDT_IRT). + - base_amount (float): The amount of the base asset to be ordered. + - price (float): The price at which the order is placed (for limit orders). + - side (str): The side of the order, either 'buy' or 'sell'. + - type (str): The type of the order, such as 'limit', 'market', etc. + **kwargs: Additional parameters to be passed in the request. + + Returns: + Response (dict): Response. + + limitations: + - a maximum of 10 orders can be placed at a time + - all orders must be in the same market + - if one wrong order is in the list of order objects (dict), the entire request will raise an exception + + References: + [API Docs](https://docs.bitpin.ir/v1/docs/order/Bulk%20Orders/Place_Bulk_Orders) + + Notes: + Rate Limit: 1800 Requests/hour + """ + + max_orders = 10 + if len(orders) > max_orders: + msg = "A maximum of 10 orders can be placed at a time! not creating order!" + raise ValueError(msg) + + market = orders[0]["symbol"] if orders else None + if any(order["symbol"] != market for order in orders): + msg = "All orders must be in the same market! not creating order!" + raise ValueError(msg) + + kwargs["json"] = {k: str(v) for k, v in locals().items() if v is not None and k not in {"self", "kwargs"}} + return await self._post(self.BULK_ORDER_URL, signed=True, **kwargs) # type: ignore[return-value] + + async def cancel_order_bulk( + self, + ids: t.OptionalStrList = None, + identifiers: t.OptionalStrList = None, + **kwargs, + ) -> t.CancelBulkOrderResponse: + """ + Cancel multiple orders in bulk using either order IDs or specific identifiers. + + Args: + ids (Optional[List[str]]): A list of order IDs to cancel. Defaults to None. + identifiers (Optional[List[str]]): A list of specific order identifiers to cancel. Defaults to None. + **kwargs: Additional parameters. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/v1/docs/order/Bulk%20Orders/Cancel_Bulk_Orders) """ - return await self._delete(self.ORDERS_URL + f"{order_id}/", signed=True, **kwargs) # type: ignore[return-value] + kwargs["json"] = {k: str(v) for k, v in locals().items() if v is not None and k not in {"self", "kwargs"}} + return await self._delete(self.BULK_ORDER_URL, signed=True, **kwargs) # type: ignore[return-value] async def get_user_trades( # type: ignore[no-untyped-def, override] self, - market_id: t.OptionalInt = None, - type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin - page: int = 1, + symbol: t.OptionalStr = None, + side: t.OptionalOrderTypesList = None, + offset: t.OptionalInt = None, + limit: t.OptionalInt = None, **kwargs, - ) -> t.DictStrAny: + ) -> t.TradeResponse: """ - Get user trades. + Retrieve user filled (executed) orders. Args: - market_id (int): Market ID. - type (OrderTypes): Type. - page (int): Page. - **kwargs: Kwargs. + symbol (Optional[str]): symbol (e.g., BTC_IRT, ETH_USDT). Defaults to None. + side (Optional[str]): The side of the trade, either 'buy' or 'sell'. Defaults to None. + offset (Optional[int]): Fetch trades where the order ID is less than this value. Useful for pagination. Defaults to None. + limit (Optional[int]): Maximum number of trades to retrieve, with an upper limit of 100. Defaults to None. + **kwargs: Additional parameters. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#3fe8d57657) + [API Docs](https://docs.bitpin.ir/v1/docs/order/get_fills_list) + + Notes: + Rate Limit: 80 Requests/minute """ - kwargs["params"] = {k: str(v) for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} - return await self._get(self.USER_TRADES_URL, signed=True, **kwargs) + kwargs["params"] = kwargs.get("params", {}) + kwargs["params"].update( + {k: str(v) for k, v in locals().items() if v is not None and k not in {"self", "kwargs"}} + ) + + return await self._get(self.ORDERS_URL, signed=True, **kwargs) # type: ignore[return-value] async def close_connection(self) -> None: # type: ignore[override] """Close connection.""" diff --git a/src/bitpin/clients/client.py b/src/bitpin/clients/client.py index 560c794..8fa4a33 100644 --- a/src/bitpin/clients/client.py +++ b/src/bitpin/clients/client.py @@ -2,15 +2,17 @@ import time from threading import Thread +from warnings import warn + import requests -from .core import CoreClient -from .. import types as t from .. import enums +from .. import response_types as t from ..exceptions import ( APIException, RequestException, ) +from .core import CoreClient class Client(CoreClient): @@ -243,12 +245,16 @@ def _handle_response(response: requests.Response) -> t.DictStrAny: # type: igno if not str(response.status_code).startswith("2"): if response.request.method.lower() == enums.RequestMethod.DELETE: # type: ignore[union-attr] - return {"status": "success", "id": response.request.path_url.split("/")[-2]} + return { + "status": "success", + "id": response.request.path_url.split("/")[-2], + } raise APIException(response, response.status_code, response.text) try: return response.json() # type: ignore[no-any-return] except ValueError as exc: - raise RequestException(f"Invalid Response: {response.text}") from exc + msg = f"Invalid Response: {response.text}" + raise RequestException(msg) from exc def _handle_login(self) -> None: """Handle login.""" @@ -282,7 +288,7 @@ def _background_refresh_token_task(self) -> None: except Exception: # pylint: disable=broad-except continue - def login(self, **kwargs) -> t.LoginResponse: # type: ignore[no-untyped-def] + def login(self, **kwargs) -> t.LoginResponse: # type: ignore[no-untyped-def, override] """ Login and set (refresh_token/access_token). @@ -293,7 +299,7 @@ def login(self, **kwargs) -> t.LoginResponse: # type: ignore[no-untyped-def] Response (LoginResponse): Response. References: - [API Docs](https://docs.bitpin.ir/#02c24a5326) + [API Docs](https://docs.bitpin.ir/v1/docs/authentication/intro) """ kwargs["json"] = {"api_key": self.api_key, "secret_key": self.api_secret} @@ -304,7 +310,7 @@ def login(self, **kwargs) -> t.LoginResponse: # type: ignore[no-untyped-def] return _ - def refresh_access_token( # type: ignore[no-untyped-def] + def refresh_access_token( # type: ignore[no-untyped-def, override] self, refresh_token: t.OptionalStr = None, **kwargs ) -> t.RefreshTokenResponse: """ @@ -318,7 +324,7 @@ def refresh_access_token( # type: ignore[no-untyped-def] Response (RefreshTokenResponse): Response. References: - [API Docs](https://docs.bitpin.ir/#9b81094f74) + [API Docs](https://docs.bitpin.ir/v1/docs/authentication/refresh_token) """ kwargs["json"] = {"refresh": refresh_token or self.refresh_token} @@ -328,194 +334,245 @@ def refresh_access_token( # type: ignore[no-untyped-def] return _ - def get_user_info(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + # Deprecated Methods + def get_user_info(self, **kwargs) -> None: # type: ignore[no-untyped-def, override] + """ + Get user info (DEPRECATED). """ - Get user info. - Args: - **kwargs: Kwargs. + warn( + "get_user_info is deprecated! if deprecated method's usage is still in need import client from bitpin.deprecated instead!", + DeprecationWarning, + 2, + ) + + # Working Methods + def get_currencies_info( # type: ignore[no-untyped-def, override] + self, + ) -> t.CurrenciesInfo: + """ + Get currencies info. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#5b3c85d79e) + [API Docs](https://docs.bitpin.ir/v1/docs/market-data/currencies) + + Notes: + Rate limit: 10000/day or 200/minute if you are authenticated. """ - return self._get(self.USER_INFO_URL, signed=True, **kwargs) + return self._get(self.CURRENCIES_LIST_URL) - def get_currencies_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def get_markets_info(self) -> t.MarketsInfo: # type: ignore[no-untyped-def, override] """ - Get currencies info. - - Args: - page (int): Page. - **kwargs: Kwargs. + Get markets info. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#7e59da3d0d) + [API Docs](https://docs.bitpin.ir/v1/docs/market-data/markets) Notes: Rate limit: 10000/day or 200/minute if you are authenticated. """ - return self._get(self.CURRENCIES_LIST_URL.format(page), **kwargs) + return self._get(self.MARKETS_LIST_URL) - def get_markets_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def get_tickers_info(self) -> t.DictStrAny: """ - Get markets info. - - Args: - page (int): Page. - **kwargs: Kwargs. + Get tickets info. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#334792bb2b) + [API Docs](https://docs.bitpin.ir/v1/docs/market-data/tickers) Notes: - Rate limit: 10000/day or 200/minute if you are authenticated. + Rate limit: 80/minute . """ - return self._get(self.MARKETS_LIST_URL.format(page), **kwargs) + return self._get(self.TICKERS_LIST_URL) - def get_wallets(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def get_wallets( # type: ignore[no-untyped-def, override] + self, + assets: t.OptionalStr = None, + service: t.OptionalStr = None, + offset: t.OptionalInt = None, + limit: t.OptionalInt = None, + **kwargs, + ) -> t.WalletInfo: """ Get wallets. Args: - **kwargs: Kwargs. + assets: asset name [BTC, IRT, USDT, ...] + service: name of service + offset: asset balance offset, i.e. assets below 10000 + limit: maximum received assets info Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#9b93495188) + [API Docs](https://docs.bitpin.ir/v1/docs/wallets) Notes: Rate limit: 10000/day. """ + kwargs["params"] = {k: str(v) for k, v in locals().items() if v is not None and k not in {"self", "kwargs"}} return self._get(self.WALLETS_URL, signed=True, **kwargs) - def get_orderbook( # type: ignore[no-untyped-def] + def get_orderbook( # type: ignore[no-untyped-def, override] self, - market_id: int, - type: t.OrderTypes, - **kwargs, # pylint: disable=redefined-builtin + symbol: str, ) -> t.OrderbookResponse: """ Get orderbook. Args: - market_id (int): Market ID. - type (OrderTypes): Type. - **kwargs: Kwargs. + symbol (str): i.e. BTC_IRT, ETH_USDT Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#ec7180fc0e) + [API Docs](https://docs.bitpin.ir/v1/docs/market-data/orderbook) + + Notes: + Rate Limit: 60 Requests/minute """ - return self._get(self.ORDERBOOK_URL.format(market_id, str(type)), **kwargs) # type: ignore[return-value] + return self._get( # type: ignore[return-value] + self.ORDERBOOK_URL.format(symbol, str(type)), + version=self.PUBLIC_API_VERSION_1, + ) - def get_recent_trades(self, market_id: int, **kwargs) -> t.TradeResponse: # type: ignore[no-untyped-def] + def get_recent_trades(self, symbol: str) -> t.RecentTradesInfo: # type: ignore[no-untyped-def, override] """ Get recent trades. Args: - market_id (int): Market ID. - **kwargs: Kwargs. + symbol (str): i.e. BTC_IRT. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#1dd63530b5) + [API Docs](https://docs.bitpin.ir/v1/docs/market-data/matches) + + Notes: + Rate Limit: 60 requests/minute """ - return self._get(self.RECENT_TRADES_URL.format(market_id), **kwargs) # type: ignore[return-value] + return self._get(self.RECENT_TRADES_URL.format(symbol)) # type: ignore[return-value] - def get_user_orders( # type: ignore[no-untyped-def] + def get_user_orders( # type: ignore[no-untyped-def, override] self, - market_id: t.OptionalInt = None, - type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin - state: t.OptionalStr = None, - mode: t.OptionalStr = None, + symbol: t.OptionalStr = None, + side: t.OptionalOrderTypesList = None, # pylint: disable=redefined-builtin + state: t.OptionalOrderStateList = None, + type: t.OptionalOrderModesList = None, identifier: t.OptionalStr = None, - page: int = 1, + start: t.OptionalDate = None, + end: t.OptionalDate = None, + ids_in: t.OptionalStrList = None, + identifiers_in: t.OptionalStrList = None, + offset: t.OptionalInt = None, + limit: t.OptionalInt = None, **kwargs, ) -> t.OpenOrdersResponse: """ - Get user Orders. + Get user orders. Args: - market_id (int): Market ID. - type (OrderTypes): Type. - state (str): State. - mode (str): Mode. - identifier (str): Identifier. - page (int): Page. + symbol (Optional[str]): symbol (e.g., BTC_IRT, ETH_USDT). Defaults to None. + side (Optional[List[str]]): The type of order, either 'buy' or 'sell'. Defaults to None. + state (Optional[List[str]]): The state of the order, can be 'initial', 'active', or 'closed'. Defaults to None. + type (Optional[List[str]]): The type of the order, can be 'limit', 'market', 'stop_limit', or 'oco'. Defaults to None. + identifier (Optional[str]): A unique identifier for the order, useful for tracking or preventing duplicate entries. Defaults to None. + start (Optional[date]): Show orders created after this date. Defaults to None. + end (Optional[date]): Show orders created before this date. Defaults to None. + ids_in (Optional[List[str]]): A list of order IDs to filter results. Defaults to None. + identifiers_in (Optional[List[str]]): A list of specific order identifiers to filter results. Defaults to None. + offset (Optional[int]): Show orders with IDs less than this value. Defaults to None. + limit (Optional[int]): The maximum number of orders to retrieve (maximum: 100). Defaults to None. **kwargs: Kwargs. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#8a7c2a2af5) + [API Docs](https://docs.bitpin.ir/v1/docs/order/get_order_list) + + Notes: + Rate Limit: 80 Requests/minute """ - kwargs["params"] = {k: str(v) for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} + kwargs["params"] = kwargs.get("params", {}) + kwargs["params"].update( + {k: str(v) for k, v in locals().items() if v is not None and k not in {"self", "kwargs"}} + ) return self._get(self.ORDERS_URL, signed=True, **kwargs) # type: ignore[return-value] - def create_order( # type: ignore[no-untyped-def] + def create_order( # type: ignore[no-untyped-def, override] self, - market: int, - amount1: float, - price: float, - mode: t.OrderModes, - type: t.OrderTypes, # pylint: disable=redefined-builtin + symbol: str, + type: t.OrderModes, + side: t.OrderTypes, # pylint: disable=redefined-builtin + base_amount: float, + quote_amount: t.OptionalFloat = None, + price: t.OptionalFloat = None, + stop_price: t.OptionalFloat = None, + oco_target_price: t.OptionalFloat = None, identifier: t.OptionalStr = None, - price_limit: t.OptionalFloat = None, - price_stop: t.OptionalFloat = None, - price_limit_oco: t.OptionalFloat = None, - amount2: t.OptionalFloat = None, **kwargs, ) -> t.CreateOrderResponse: """ Create order. Args: - market (int): Market. - amount1 (float): Amount1. - price (float): Price. - mode (OrderModes): Mode. - type (OrderTypes): Type. - identifier (str): Identifier. - price_limit (float): Price limit. - price_stop (float): Price stop. - price_limit_oco (float): Price limit oco. - amount2 (float): Amount2. + symbol (str): i.e. [USDT_IRT] + type: t.OrderModes + side: t.OrderTypes + price: float + base_amount: float + quote_amount: t.OptionalFloat = None + stop_price: t.OptionalFloat = None + oco_target_price: t.OptionalFloat = None + identifier: t.OptionalStr = None **kwargs: Kwargs. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#34b353d77b) - """ + [API Docs](https://docs.bitpin.ir/v1/docs/order/place_order) - kwargs["json"] = {k: str(v) for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} + Notes: + Rate Limit: 5400 Requests/hour + """ + + kwargs["json"] = { + "symbol": symbol, + "type": type, + "side": side, + "price": price, + "base_amount": base_amount, + "quote_amount": quote_amount, + "stop_price": stop_price, + "oco_target_price": oco_target_price, + "identifier": identifier, + } + + kwargs["json"] = {k: v for k, v in kwargs["json"].items() if v is not None} return self._post(self.ORDERS_URL, signed=True, **kwargs) # type: ignore[return-value] - def cancel_order(self, order_id: str, **kwargs) -> t.CancelOrderResponse: # type: ignore[no-untyped-def] + def cancel_order(self, order_id: str, **kwargs) -> t.CancelOrderResponse: # type: ignore[no-untyped-def, override] """ Cancel order. @@ -527,38 +584,130 @@ def cancel_order(self, order_id: str, **kwargs) -> t.CancelOrderResponse: # typ Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#3fe8d57657) + [API Docs](https://docs.bitpin.ir/v1/docs/order/cancel) + + Notes: + Rate Limit: 5400 Requests/hour + """ + + try: + self._delete(self.ORDERS_URL + f"{order_id}/", signed=True, **kwargs) # type: ignore[return-value] + except APIException as e: + raise e from e + else: + return {"status": "success", "id": order_id} + + def create_order_bulk(self, orders: t.BulkOrderList, **kwargs): """ + Create multiple orders in bulk. + + Args: + orders (BulkOrderList): A list of order objects to be created in bulk. + Each order object (dict) should contain: + - symbol (str): The market symbol for the order (e.g., USDT_IRT). + - base_amount (float): The amount of the base asset to be ordered. + - price (float): The price at which the order is placed (for limit orders). + - side (str): The side of the order, either 'buy' or 'sell'. + - type (str): The type of the order, such as 'limit', 'market', etc. + **kwargs: Additional parameters to be passed in the request. + + Returns: + Response (dict): Response. + + limitations: + - a maximum of 10 orders can be placed at a time + - all orders must be in the same market + - if one wrong order is in the list of order objects (dict), the entire request will raise an exception + + References: + [API Docs](https://docs.bitpin.ir/v1/docs/order/Bulk%20Orders/Place_Bulk_Orders) + + Notes: + Rate Limit: 1800 Requests/hour + """ + + max_orders = 10 + if len(orders) > max_orders: + msg = "A maximum of 10 orders can be placed at a time! not creating order!" + raise ValueError(msg) - return self._delete(self.ORDERS_URL + f"{order_id}/", signed=True, **kwargs) # type: ignore[return-value] + market = orders[0]["symbol"] if orders else None + if any(order["symbol"] != market for order in orders): + msg = "All orders must be in the same market! not creating order!" + raise ValueError(msg) - def get_user_trades( # type: ignore[no-untyped-def] + kwargs["json"] = {k: str(v) for k, v in locals().items() if v is not None and k not in {"self", "kwargs"}} + return self._post(self.BULK_ORDER_URL, signed=True, **kwargs) # type: ignore[return-value] + + def cancel_order_bulk( self, - market_id: t.OptionalInt = None, - type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin - page: int = 1, + ids: t.OptionalStrList = None, + identifiers: t.OptionalStrList = None, **kwargs, - ) -> t.DictStrAny: + ) -> t.CancelBulkOrderResponse: """ - Get user trades. + Cancel multiple orders in bulk using either order IDs or specific identifiers. Args: - market_id (int): Market ID. - type (OrderTypes): Type. - page (int): Page. - **kwargs: Kwargs. + ids (Optional[List[str]]): A list of order IDs to cancel. Defaults to None. + identifiers (Optional[List[str]]): A list of specific order identifiers to cancel. Defaults to None. + **kwargs: Additional parameters. Returns: Response (dict): Response. References: - [API Docs](https://docs.bitpin.ir/#3fe8d57657) + [API Docs](https://docs.bitpin.ir/v1/docs/order/Bulk%20Orders/Cancel_Bulk_Orders) + """ + + kwargs["json"] = {k: str(v) for k, v in locals().items() if v is not None and k not in {"self", "kwargs"}} + return self._delete(self.BULK_ORDER_URL, signed=True, **kwargs) # type: ignore[return-value] + + def get_user_trades( # type: ignore[no-untyped-def, override] + self, + symbol: t.OptionalStr = None, + side: t.OptionalOrderTypesList = None, + offset: t.OptionalInt = None, + limit: t.OptionalInt = None, + **kwargs, + ) -> t.TradeResponse: """ + Retrieve user filled (executed) orders. + + Args: + symbol (Optional[str]): symbol (e.g., BTC_IRT, ETH_USDT). Defaults to None. + side (Optional[str]): The side of the trade, either 'buy' or 'sell'. Defaults to None. + offset (Optional[int]): Fetch trades where the order ID is less than this value. Useful for pagination. Defaults to None. + limit (Optional[int]): Maximum number of trades to retrieve, with an upper limit of 100. Defaults to None. + **kwargs: Additional parameters. - kwargs["params"] = {k: str(v) for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} - return self._get(self.USER_TRADES_URL, signed=True, **kwargs) + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/v1/docs/order/get_fills_list) + + Notes: + Rate Limit: 80 Requests/minute + """ + + kwargs["params"] = kwargs.get("params", {}) + kwargs["params"].update( + { + k: str(v) + for k, v in locals().items() + if v is not None + and k + not in { + "self", + "kwargs", + } + } + ) + + return self._get(self.ORDERS_URL, signed=True, **kwargs) # type: ignore[return-value] - def close_connection(self) -> None: + def close_connection(self) -> None: # type: ignore[override] """Close connection.""" - self.session.close() + self.session.close() # type: ignore[misc] diff --git a/src/bitpin/clients/core.py b/src/bitpin/clients/core.py index 0e3b558..7d6593e 100644 --- a/src/bitpin/clients/core.py +++ b/src/bitpin/clients/core.py @@ -5,29 +5,32 @@ ABC, abstractmethod, ) -from .. import types as t + +from .. import response_types as t class CoreClient(ABC): # pylint: disable=too-many-instance-attributes """Core Client.""" - API_URL = "https://api.bitpin.ir" + API_URL = "https://api.bitpin.ir/api" PUBLIC_API_VERSION_1 = "v1" PUBLIC_API_VERSION_2 = "v2" REQUEST_TIMEOUT: float = 10 - LOGIN_URL = "usr/api/login/" + LOGIN_URL = "usr/authenticate/" REFRESH_TOKEN_URL = "usr/refresh_token/" - USER_INFO_URL = "usr/info/" - CURRENCIES_LIST_URL = "mkt/currencies/?page={}" - MARKETS_LIST_URL = "mkt/markets/?page={}" + CURRENCIES_LIST_URL = "mkt/currencies/" + MARKETS_LIST_URL = "mkt/markets/" + TICKERS_LIST_URL = "mkt/tickers/" WALLETS_URL = "wlt/wallets/" - ORDERBOOK_URL = "mth/actives/{}/?type={}" + ORDERBOOK_URL = "mth/orderbook/{}/" RECENT_TRADES_URL = "mth/matches/{}/" ORDERS_URL = "odr/orders/" + FILLED_ORDERS_URL = "odr/fills/" USER_TRADES_URL = "odr/matches/?type={}" + BULK_ORDER_URL = "odr/orders/bulk/" def __init__( # type: ignore[no-untyped-def] self, @@ -86,13 +89,15 @@ def __init__( # type: ignore[no-untyped-def] self._requests_params = requests_params self.session = self._init_session() - def _get_request_kwargs(self, method: t.RequestMethods, signed: bool, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def _get_request_kwargs( + self, method: t.RequestMethods, signed: bool, **kwargs + ) -> t.DictStrAny: # type: ignore[no-untyped-def] kwargs["timeout"] = self.REQUEST_TIMEOUT if self._requests_params: kwargs.update(self._requests_params) - data = kwargs.get("data", None) + data = kwargs.get("data") if data and isinstance(data, dict): kwargs["data"] = data @@ -134,7 +139,13 @@ def _init_session(self) -> t.HttpSession: raise NotImplementedError @abstractmethod - def _get(self, path: str, signed: bool = False, version: str = PUBLIC_API_VERSION_1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def _get( + self, + path: str, + signed: bool = False, + version: str = PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: # type: ignore[no-untyped-def] """ Make a GET request. @@ -151,7 +162,13 @@ def _get(self, path: str, signed: bool = False, version: str = PUBLIC_API_VERSIO raise NotImplementedError @abstractmethod - def _post(self, path: str, signed: bool = False, version: str = PUBLIC_API_VERSION_1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def _post( + self, + path: str, + signed: bool = False, + version: str = PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: # type: ignore[no-untyped-def] """ Make a POST request. @@ -168,7 +185,13 @@ def _post(self, path: str, signed: bool = False, version: str = PUBLIC_API_VERSI raise NotImplementedError @abstractmethod - def _delete(self, path: str, signed: bool = False, version: str = PUBLIC_API_VERSION_1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def _delete( + self, + path: str, + signed: bool = False, + version: str = PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: # type: ignore[no-untyped-def] """ Make a DELETE request. @@ -210,7 +233,9 @@ def _request_api( # type: ignore[no-untyped-def] raise NotImplementedError @abstractmethod - def _request(self, method: t.RequestMethods, uri: str, signed: bool, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def _request( + self, method: t.RequestMethods, uri: str, signed: bool, **kwargs + ) -> t.DictStrAny: # type: ignore[no-untyped-def] """ Request. @@ -271,7 +296,9 @@ def login(self, **kwargs) -> t.LoginResponse: # type: ignore[no-untyped-def] raise NotImplementedError @abstractmethod - def refresh_access_token(self, refresh_token: t.OptionalStr = None, **kwargs) -> t.RefreshTokenResponse: # type: ignore[no-untyped-def] + def refresh_access_token( + self, refresh_token: t.OptionalStr = None, **kwargs + ) -> t.RefreshTokenResponse: # type: ignore[no-untyped-def] """ Refresh token. @@ -285,24 +312,21 @@ def refresh_access_token(self, refresh_token: t.OptionalStr = None, **kwargs) -> raise NotImplementedError @abstractmethod - def get_user_info(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def get_user_info(self, **kwargs) -> None: # type: ignore[no-untyped-def] """ - Get user info. + Get user info. (Deprecated) Returns: - dict: Response. + None. """ raise NotImplementedError @abstractmethod - def get_currencies_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def get_currencies_info(self) -> t.CurrenciesInfo: # type: ignore[no-untyped-def] """ Get currencies info. - Args: - page (int): Page. - Returns: dict: Response. """ @@ -310,12 +334,20 @@ def get_currencies_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: raise NotImplementedError @abstractmethod - def get_markets_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def get_markets_info(self) -> t.MarketsInfo: # type: ignore[no-untyped-def] """ Get markets info. - Args: - page (int): Page. + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def get_tickers_info(self) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Get tickers info. Returns: dict: Response. @@ -324,41 +356,58 @@ def get_markets_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ig raise NotImplementedError @abstractmethod - def get_wallets(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + def get_wallets( + self, + assets: t.OptionalStr, + service: t.OptionalStr, + offset: t.OptionalFloat, + limit: t.OptionalInt, + ) -> t.DictStrAny: # type: ignore[no-untyped-def] """ Get wallets. + Args: + assets: asset name [BTC, IRT, USDT, ...] + service: name of service + offset: asset balance offset, i.e. assets below 10000 + limit: maximum received assets info + Returns: - dict: Response. + Response (dict): Response. + + Notes: + Rate limit: 10000/day. """ raise NotImplementedError @abstractmethod - def get_orderbook(self, market_id: int, type: t.OrderTypes, **kwargs) -> t.OrderbookResponse: # type: ignore[no-untyped-def] # pylint: disable=redefined-builtin + def get_orderbook( + self, + symbol: str, + ) -> t.OrderbookResponse: # type: ignore[no-untyped-def] # pylint: disable=redefined-builtin """ Get orderbook. Args: - market_id (int): Market ID. - type (str): Type. + symbol (str): i.e. BTC_IRT, ETH_USDT Returns: - dict: Response. + Response (dict): Response. """ raise NotImplementedError @abstractmethod - def get_recent_trades(self, market_id: int, **kwargs) -> t.TradeResponse: # type: ignore[no-untyped-def] + def get_recent_trades(self, symbol: str) -> t.RecentTradesInfo: """ Get recent trades. Args: - market_id (int): Market ID. + symbol (str): i.e. BTC_IRT. Returns: - dict: Response. + Response (dict): Response. """ raise NotImplementedError @@ -366,27 +415,38 @@ def get_recent_trades(self, market_id: int, **kwargs) -> t.TradeResponse: # typ @abstractmethod def get_user_orders( # type: ignore[no-untyped-def] self, - market_id: t.OptionalInt = None, - type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin - state: t.OptionalStr = None, - mode: t.OptionalStr = None, + symbol: t.OptionalStr = None, + side: t.OptionalOrderTypesList = None, # pylint: disable=redefined-builtin + state: t.OptionalOrderStateList = None, + type: t.OptionalOrderModesList = None, identifier: t.OptionalStr = None, - page: int = 1, + start: t.OptionalDate = None, + end: t.OptionalDate = None, + ids_in: t.OptionalStrList = None, + identifiers_in: t.OptionalStrList = None, + offset: t.OptionalInt = None, + limit: t.OptionalInt = None, **kwargs, ) -> t.OpenOrdersResponse: """ Get user orders. Args: - market_id (int): Market ID. - type (str): Type. - state (str): State. - mode (str): Mode. - identifier (str): Identifier. - page (int): Page. + symbol (Optional[str]): symbol (e.g., BTC_IRT, ETH_USDT). Defaults to None. + side (Optional[List[str]]): The type of order, either 'buy' or 'sell'. Defaults to None. + state (Optional[List[str]]): The state of the order, can be 'initial', 'active', or 'closed'. Defaults to None. + type (Optional[List[str]]): The type of the order, can be 'limit', 'market', 'stop_limit', or 'oco'. Defaults to None. + identifier (Optional[str]): A unique identifier for the order, useful for tracking or preventing duplicate entries. Defaults to None. + start (Optional[date]): Show orders created after this date. Defaults to None. + end (Optional[date]): Show orders created before this date. Defaults to None. + ids_in (Optional[List[str]]): A list of order IDs to filter results. Defaults to None. + identifiers_in (Optional[List[str]]): A list of specific order identifiers to filter results. Defaults to None. + offset (Optional[int]): Show orders with IDs less than this value. Defaults to None. + limit (Optional[int]): The maximum number of orders to retrieve (maximum: 100). Defaults to None. + **kwargs: Kwargs. Returns: - dict: Response. + Response (dict): Response. """ raise NotImplementedError @@ -394,35 +454,76 @@ def get_user_orders( # type: ignore[no-untyped-def] @abstractmethod def create_order( # type: ignore[no-untyped-def] self, - market: int, - amount1: float, - price: float, - mode: t.OrderModes, - type: t.OrderTypes, # pylint: disable=redefined-builtin + symbol: str, + type: t.OrderModes, + side: t.OrderTypes, # pylint: disable=redefined-builtin + base_amount: float, + quote_amount: t.OptionalFloat = None, + price: t.OptionalFloat = None, + stop_price: t.OptionalFloat = None, + oco_target_price: t.OptionalFloat = None, identifier: t.OptionalStr = None, - price_limit: t.OptionalFloat = None, - price_stop: t.OptionalFloat = None, - price_limit_oco: t.OptionalFloat = None, - amount2: t.OptionalFloat = None, **kwargs, ) -> t.CreateOrderResponse: """ Create order. Args: - market (int): Market. - amount1 (float): Amount1. - price (float): Price. - mode (str): Mode. - type (str): Type. - identifier (str): Identifier. - price_limit (float): Price limit. - price_stop (float): Price stop. - price_limit_oco (float): Price limit oco. - amount2 (float): Amount2. + symbol (str): i.e. [USDT_IRT] + type: t.OrderModes + side: t.OrderTypes + price: float + base_amount: float + quote_amount: t.OptionalFloat = None + stop_price: t.OptionalFloat = None + oco_target_price: t.OptionalFloat = None + identifier: t.OptionalStr = None + **kwargs: Kwargs. Returns: - dict: Response. + Response (dict): Response. + """ + + raise NotImplementedError + + @abstractmethod + async def create_order_bulk(self, orders: t.BulkOrderList, **kwargs): + """ + Create multiple orders in bulk. + + Args: + orders (BulkOrderList): A list of order objects to be created in bulk. + Each order object (dict) should contain: + - symbol (str): The market symbol for the order (e.g., USDT_IRT). + - base_amount (float): The amount of the base asset to be ordered. + - price (float): The price at which the order is placed (for limit orders). + - side (str): The side of the order, either 'buy' or 'sell'. + - type (str): The type of the order, such as 'limit', 'market', etc. + **kwargs: Additional parameters to be passed in the request. + + Returns: + Response (dict): Response. + """ + + raise NotImplementedError + + @abstractmethod + def cancel_order_bulk( + self, + ids: t.OptionalStrList = None, + identifiers: t.OptionalStrList = None, + **kwargs, + ) -> t.CancelBulkOrderResponse: + """ + Cancel multiple orders in bulk using either order IDs or specific identifiers. + + Args: + ids (Optional[List[str]]): A list of order IDs to cancel. Defaults to None. + identifiers (Optional[List[str]]): A list of specific order identifiers to cancel. Defaults to None. + **kwargs: Additional parameters. + + Returns: + t.CancelBulkOrderResponse """ raise NotImplementedError @@ -444,21 +545,24 @@ def cancel_order(self, order_id: str, **kwargs) -> t.CancelOrderResponse: # typ @abstractmethod def get_user_trades( # type: ignore[no-untyped-def] self, - market_id: t.OptionalInt = None, - type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin - page: int = 1, + symbol: t.OptionalStr = None, + side: t.OptionalOrderTypesList = None, + offset: t.OptionalInt = None, + limit: t.OptionalInt = None, **kwargs, - ) -> t.DictStrAny: + ) -> t.TradeResponse: """ - Get user trades. + Retrieve user filled (executed) orders. Args: - market_id (int): Market ID. - type (str): Type. - page (int): Page. + symbol (Optional[str]): symbol (e.g., BTC_IRT, ETH_USDT). Defaults to None. + side (Optional[str]): The side of the trade, either 'buy' or 'sell'. Defaults to None. + offset (Optional[int]): Fetch trades where the order ID is less than this value. Useful for pagination. Defaults to None. + limit (Optional[int]): Maximum number of trades to retrieve, with an upper limit of 100. Defaults to None. + **kwargs: Additional parameters. Returns: - dict: Response. + Response (dict): Response. """ raise NotImplementedError diff --git a/src/bitpin/deprecated/__init__.py b/src/bitpin/deprecated/__init__.py new file mode 100644 index 0000000..10baab5 --- /dev/null +++ b/src/bitpin/deprecated/__init__.py @@ -0,0 +1,9 @@ +"""# Deprecated Bitpin Python Library.""" + +from .clients.async_client import AsyncClient +from .clients.client import Client + +__all__ = [ + "AsyncClient", + "Client", +] diff --git a/src/bitpin/deprecated/__pycache__/__init__.cpython-312.pyc b/src/bitpin/deprecated/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..663d94e Binary files /dev/null and b/src/bitpin/deprecated/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/bitpin/deprecated/__pycache__/deprecated_enums.cpython-312.pyc b/src/bitpin/deprecated/__pycache__/deprecated_enums.cpython-312.pyc new file mode 100644 index 0000000..38799d3 Binary files /dev/null and b/src/bitpin/deprecated/__pycache__/deprecated_enums.cpython-312.pyc differ diff --git a/src/bitpin/deprecated/__pycache__/deprecated_types.cpython-312.pyc b/src/bitpin/deprecated/__pycache__/deprecated_types.cpython-312.pyc new file mode 100644 index 0000000..a7510d3 Binary files /dev/null and b/src/bitpin/deprecated/__pycache__/deprecated_types.cpython-312.pyc differ diff --git a/src/bitpin/deprecated/clients/__init__.py b/src/bitpin/deprecated/clients/__init__.py new file mode 100644 index 0000000..9e36ecb --- /dev/null +++ b/src/bitpin/deprecated/clients/__init__.py @@ -0,0 +1,17 @@ +""" +# Deprecated Bitpin Client. + +Client for Bitpin API. + +[Client](client) Submodule contains the synchronous client. +[AsyncClient](async_client) Submodule contains the asynchronous client. +[Core](core) Submodule contains the core client. +""" + +from .async_client import AsyncClient +from .client import Client + +__all__ = [ + "AsyncClient", + "Client", +] diff --git a/src/bitpin/deprecated/clients/__pycache__/__init__.cpython-312.pyc b/src/bitpin/deprecated/clients/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..b22297c Binary files /dev/null and b/src/bitpin/deprecated/clients/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/bitpin/deprecated/clients/__pycache__/async_client.cpython-312.pyc b/src/bitpin/deprecated/clients/__pycache__/async_client.cpython-312.pyc new file mode 100644 index 0000000..bd62829 Binary files /dev/null and b/src/bitpin/deprecated/clients/__pycache__/async_client.cpython-312.pyc differ diff --git a/src/bitpin/deprecated/clients/__pycache__/client.cpython-312.pyc b/src/bitpin/deprecated/clients/__pycache__/client.cpython-312.pyc new file mode 100644 index 0000000..f1f3a5a Binary files /dev/null and b/src/bitpin/deprecated/clients/__pycache__/client.cpython-312.pyc differ diff --git a/src/bitpin/deprecated/clients/__pycache__/core.cpython-312.pyc b/src/bitpin/deprecated/clients/__pycache__/core.cpython-312.pyc new file mode 100644 index 0000000..c337605 Binary files /dev/null and b/src/bitpin/deprecated/clients/__pycache__/core.cpython-312.pyc differ diff --git a/src/bitpin/deprecated/clients/async_client.py b/src/bitpin/deprecated/clients/async_client.py new file mode 100644 index 0000000..f602e2a --- /dev/null +++ b/src/bitpin/deprecated/clients/async_client.py @@ -0,0 +1,647 @@ +"""# Deprecated Bitpin Async Client.""" + +# pylint: disable=invalid-overridden-method + +import asyncio + +import aiohttp +from deprecated import deprecated + +from src.bitpin._utils import get_loop +from src.bitpin.deprecated import deprecated_enums +from src.bitpin.deprecated import deprecated_types as t +from src.bitpin.exceptions import ( + APIException, + RequestException, +) + +from .core import CoreClient + + +@deprecated( + version="1.0.0", + reason="API end point has been updated and will be deprecated soon!" + "please import desired client from bitpin!" + "if you still need to use these methods import from bitpin.deprecated", +) +class AsyncClient(CoreClient): + """ + Async Client. + + Methods: + login: Login and set (refresh_token/access_token) + refresh_access_token: Refresh token. + get_user_info: Get user info. + get_currencies_info: Get currencies info. + get_markets_info: Get markets info. + get_wallets: Get wallets. + get_orderbook: Get orderbook. + get_recent_trades: Get recent trades. + get_user_orders: Get user orders. + create_order: Create order. + cancel_order: Cancel order. + get_user_trades: Get user trades. + close_connection: Close connection. + + Attributes: + session (aiohttp.ClientSession): Session. + loop (asyncio.AbstractEventLoop): Event Loop + api_key (str): API key. + api_secret (str): API secret. + refresh_token (str): Refresh token. + access_token (str): Access token. + """ + + def __init__( # type: ignore[no-untyped-def] + self, + api_key: t.OptionalStr = None, + api_secret: t.OptionalStr = None, + access_token: t.OptionalStr = None, + refresh_token: t.OptionalStr = None, + requests_params: t.OptionalDictStrAny = None, + session_params: t.OptionalDictStrAny = None, + loop: t.OptionalEventLoop = None, + background_relogin: bool = False, + background_relogin_interval: int = 60 * 60 * 24 * 6, + background_refresh_token: bool = False, + background_refresh_token_interval: int = 60 * 13, + ): + """ + Constructor. + + Args: + api_key (str): API key. + api_secret (str): API secret. + access_token (str): Access token. + refresh_token (str): Refresh token. + requests_params (dict): Requests params. + session_params (dict): Session params. + loop (asyncio.AbstractEventLoop): Event loop. + background_relogin (bool): Background refresh. + background_relogin_interval (int): Background refresh interval. + background_refresh_token (bool): Background refresh token. + background_refresh_token_interval (int): Background refresh token interval. + + Notes: + If `api_key` and `api_secret` are not provided, they will be read from the environment variables + `BITPIN_API_KEY` and `BITPIN_API_SECRET` respectively. + + If `access_token` and `refresh_token` are not provided, they will be read from the environment variables + `BITPIN_ACCESS_TOKEN` and `BITPIN_REFRESH_TOKEN` respectively. + + If `requests_params` are provided, they will be used as default for every request. + + If `requests_params` are provided in `kwargs`, they will override existing `requests_params`. + + If `background_relogin` is enabled, access token will be refreshed in background every + `background_relogin_interval` seconds. + + If `background_refresh_token` is enabled, refresh token will be refreshed in background every + `background_refresh_token_interval` seconds. + """ + + self.loop = loop or get_loop() + self._session_params = session_params or {} + + super().__init__( + api_key, + api_secret, + access_token, + refresh_token, + requests_params, + background_relogin, + background_relogin_interval, + background_refresh_token, + background_refresh_token_interval, + ) + + @classmethod + async def create( # type: ignore[no-untyped-def] + cls, + api_key: t.OptionalStr = None, + api_secret: t.OptionalStr = None, + access_token: t.OptionalStr = None, + refresh_token: t.OptionalStr = None, + requests_params: t.OptionalDictStrAny = None, + session_params: t.OptionalDictStrAny = None, + loop: t.OptionalEventLoop = None, + background_relogin: bool = False, + background_relogin_interval: int = 60 * 60 * 24 * 6, + background_refresh_token: bool = False, + background_refresh_token_interval: int = 60 * 13, + ) -> "AsyncClient": + """ + Create AsyncClient. + + Args: + api_key (str): API key. + api_secret (str): API secret. + access_token (str): Access token. + refresh_token (str): Refresh token. + requests_params (dict): Requests params. + session_params (dict): Session params. + loop (asyncio.AbstractEventLoop): Event loop. + background_relogin (bool): Background refresh. + background_relogin_interval (int): Background refresh interval. + background_refresh_token (bool): Background refresh token. + background_refresh_token_interval (int): Background refresh token interval. + + Returns: + AsyncClient: AsyncClient. + """ + + self = cls( + api_key, + api_secret, + access_token, + refresh_token, + requests_params, + session_params, + loop, + background_relogin, + background_relogin_interval, + background_refresh_token, + background_refresh_token_interval, + ) + + await self._handle_login() + return self + + def _init_session(self) -> aiohttp.ClientSession: + """ + Initialize session. + + Returns: + session (aiohttp.ClientSession): Session. + + """ + + session = aiohttp.ClientSession( + loop=self.loop, + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + }, + **self._session_params, + ) + return session + + async def _get( # type: ignore[no-untyped-def, override] + self, + path: str, + signed: bool = False, + version: str = CoreClient.PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: + """ + Make a GET request. + + Args: + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + return await self._request_api(deprecated_enums.RequestMethod.GET, path, signed, version, **kwargs) + + async def _post( # type: ignore[no-untyped-def, override] + self, + path: str, + signed: bool = False, + version: str = CoreClient.PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: + """ + Make a POST request. + + Args: + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + return await self._request_api(deprecated_enums.RequestMethod.POST, path, signed, version, **kwargs) + + async def _delete( # type: ignore[no-untyped-def, override] + self, + path: str, + signed: bool = False, + version: str = CoreClient.PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: + """ + Make a DELETE request. + + Args: + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + return await self._request_api(deprecated_enums.RequestMethod.DELETE, path, signed, version, **kwargs) + + async def _request_api( # type: ignore[no-untyped-def, override] + self, + method: t.RequestMethods, + path: str, + signed: bool = False, + version: str = CoreClient.PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: + """ + Request API. + + Args: + method (RequestMethod): Method. + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + uri = self._create_api_uri(path, version) + return await self._request(method, uri, signed, **kwargs) + + async def _request( # type: ignore[no-untyped-def, override] + self, method: t.RequestMethods, uri: str, signed: bool, **kwargs + ) -> t.DictStrAny: + """ + Request. + + Args: + method (RequestMethod): Method. + uri (str): URI. + signed (bool): Signed. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + kwargs = self._get_request_kwargs(method, signed, **kwargs) + + async with getattr(self.session, method)(uri, **kwargs) as response: + self.response = response # pylint: disable=attribute-defined-outside-init + return await self._handle_response(response) + + @staticmethod + async def _handle_response(response: aiohttp.ClientResponse) -> t.DictStrAny: # type: ignore[override] + """ + Handle response. + + Args: + response (aiohttp.ClientResponse): Response. + + Returns: + dict: Response. + + Raises: + APIException: API Exception. + RequestException: Request Exception. + """ + + if not str(response.status).startswith("2"): + raise APIException(response, response.status, await response.text()) + try: + if response.method.lower() == deprecated_enums.RequestMethod.DELETE: + return {"status": "success", "id": response.request_info.url.parts[-2]} + return await response.json() # type: ignore[no-any-return] + except ValueError as exc: + raise RequestException(f"Invalid Response: {await response.text()}") from exc + + async def _background_relogin_task(self) -> None: # type: ignore[override] + """Background relogin task.""" + + while True: + try: + await self.login() + await asyncio.sleep(self._background_relogin_interval) + except Exception: # pylint: disable=broad-except + continue + + async def _background_refresh_token_task(self) -> None: # type: ignore[override] + """Background refresh token task.""" + + while True: + try: + await self.refresh_access_token() + await asyncio.sleep(self._background_refresh_token_interval) + except Exception: # pylint: disable=broad-except + continue + + async def _handle_login(self) -> None: # type: ignore[override] + """Handle login.""" + + if self.api_key and self.api_secret: + await self.login() + + if self._background_relogin: + self.loop.create_task(self._background_relogin_task()) + + if self._background_refresh_token: + self.loop.create_task(self._background_refresh_token_task()) + + async def login(self, **kwargs) -> t.LoginResponse: # type: ignore[no-untyped-def, override] + """ + Login and set (refresh_token/access_token). + + Args: + **kwargs: Kwargs. + + Returns: + Response (LoginResponse): Response. + + References: + [API Docs](https://docs.bitpin.ir/#02c24a5326) + """ + + kwargs["json"] = {"api_key": self.api_key, "secret_key": self.api_secret} + _: t.LoginResponse = await self._post(self.LOGIN_URL, **kwargs) # type: ignore[assignment] + + self.refresh_token = _["refresh"] + self.access_token = _["access"] + + return _ + + async def refresh_access_token( # type: ignore[no-untyped-def, override] + self, refresh_token: t.OptionalStr = None, **kwargs + ) -> t.RefreshTokenResponse: + """ + Refresh token. + + Args: + refresh_token (str): Refresh token. + **kwargs: Kwargs. + + Returns: + Response (RefreshTokenResponse): Response. + + References: + [API Docs](https://docs.bitpin.ir/#9b81094f74) + """ + + kwargs["json"] = {"refresh": refresh_token or self.refresh_token} + _: t.RefreshTokenResponse = await self._post(self.REFRESH_TOKEN_URL, **kwargs) # type: ignore[assignment] + + self.access_token = _["access"] + + return _ + + async def get_user_info(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def, override] + """ + Get user info. + + Args: + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#5b3c85d79e) + """ + + return await self._get(self.USER_INFO_URL, signed=True, **kwargs) + + async def get_currencies_info( # type: ignore[no-untyped-def, override] + self, page: int = 1, **kwargs + ) -> t.DictStrAny: + """ + Get currencies info. + + Args: + page (int): Page. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#7e59da3d0d) + + Notes: + Rate limit: 10000/day or 200/minute if you are authenticated. + """ + + return await self._get(self.CURRENCIES_LIST_URL.format(page), **kwargs) + + async def get_markets_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def, override] + """ + Get markets info. + + Args: + page (int): Page. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#334792bb2b) + + Notes: + Rate limit: 10000/day or 200/minute if you are authenticated. + """ + + return await self._get(self.MARKETS_LIST_URL.format(page), **kwargs) + + async def get_wallets(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def, override] + """ + Get wallets. + + Args: + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#9b93495188) + + Notes: + Rate limit: 10000/day. + """ + + return await self._get(self.WALLETS_URL, signed=True, **kwargs) + + async def get_orderbook( # type: ignore[no-untyped-def, override] + self, + market_id: int, + type: t.OrderTypes, + **kwargs, # pylint: disable=redefined-builtin + ) -> t.OrderbookResponse: + """ + Get orderbook. + + Args: + market_id (int): Market ID. + type (OrderTypes): Type. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#ec7180fc0e) + """ + return await self._get( # type: ignore[return-value] + self.ORDERBOOK_URL.format(market_id, str(type)), + version=self.PUBLIC_API_VERSION_2, + **kwargs, + ) + + async def get_recent_trades( # type: ignore[no-untyped-def, override] + self, market_id: int, **kwargs + ) -> t.TradeResponse: + """ + Get recent trades. + + Args: + market_id (int): Market ID. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#1dd63530b5) + """ + + return await self._get(self.RECENT_TRADES_URL.format(market_id), **kwargs) # type: ignore[return-value] + + async def get_user_orders( # type: ignore[no-untyped-def, override] + self, + market_id: t.OptionalInt = None, + type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin + state: t.OptionalStr = None, + mode: t.OptionalStr = None, + identifier: t.OptionalStr = None, + page: int = 1, + **kwargs, + ) -> t.OpenOrdersResponse: + """ + Get user orders. + + Args: + market_id (int): Market ID. + type (OrderTypes): Type. + state (str): State. + mode (str): Mode. + identifier (str): Identifier. + page (int): Page. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#8a7c2a2af5) + """ + + kwargs["params"] = {k: str(v) for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} + return await self._get(self.ORDERS_URL, signed=True, **kwargs) # type: ignore[return-value] + + async def create_order( # type: ignore[no-untyped-def, override] + self, + market: int, + amount1: float, + price: float, + mode: t.OrderModes, + type: t.OrderTypes, # pylint: disable=redefined-builtin + identifier: t.OptionalStr = None, + price_limit: t.OptionalFloat = None, + price_stop: t.OptionalFloat = None, + price_limit_oco: t.OptionalFloat = None, + amount2: t.OptionalFloat = None, + **kwargs, + ) -> t.CreateOrderResponse: + """ + Create order. + + Args: + market (int): Market. + amount1 (float): Amount1. + price (float): Price. + mode (OrderModes): Mode. + type (OrderTypes): Type. + identifier (str): Identifier. + price_limit (float): Price limit. + price_stop (float): Price stop. + price_limit_oco (float): Price limit oco. + amount2 (float): Amount2. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#34b353d77b) + """ + + kwargs["json"] = {k: v for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} + return await self._post(self.ORDERS_URL, signed=True, **kwargs) # type: ignore[return-value] + + async def cancel_order( # type: ignore[no-untyped-def, override] + self, order_id: str, **kwargs + ) -> t.CancelOrderResponse: + """ + Cancel order. + + Args: + order_id (str): Order ID. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#3fe8d57657) + """ + + return await self._delete(self.ORDERS_URL + f"{order_id}/", signed=True, **kwargs) # type: ignore[return-value] + + async def get_user_trades( # type: ignore[no-untyped-def, override] + self, + market_id: t.OptionalInt = None, + type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin + page: int = 1, + **kwargs, + ) -> t.DictStrAny: + """ + Get user trades. + + Args: + market_id (int): Market ID. + type (OrderTypes): Type. + page (int): Page. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#3fe8d57657) + """ + + kwargs["params"] = {k: str(v) for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} + return await self._get(self.USER_TRADES_URL, signed=True, **kwargs) + + async def close_connection(self) -> None: # type: ignore[override] + """Close connection.""" + + await self.session.close() # type: ignore[misc] diff --git a/src/bitpin/deprecated/clients/client.py b/src/bitpin/deprecated/clients/client.py new file mode 100644 index 0000000..5fa58a5 --- /dev/null +++ b/src/bitpin/deprecated/clients/client.py @@ -0,0 +1,576 @@ +"""# Deprecated Bitpin Client.""" + +import time +from threading import Thread + +import requests +from deprecated import deprecated + +from src.bitpin.deprecated import deprecated_enums +from src.bitpin.deprecated import deprecated_types as t +from src.bitpin.exceptions import ( + APIException, + RequestException, +) + +from .core import CoreClient + + +@deprecated( + version="1.0.0", + reason="API end point has been updated and will be deprecated soon!" + "please import desired client from bitpin!" + "if you still need to use these methods import from bitpin.deprecated", +) +class Client(CoreClient): + """ + Client. + + Methods: + login: Login and set (refresh_token/access_token) + refresh_access_token: Refresh token. + get_user_info: Get user info. + get_currencies_info: Get currencies info. + get_markets_info: Get markets info. + get_wallets: Get wallets. + get_orderbook: Get orderbook. + get_recent_trades: Get recent trades. + get_user_orders: Get use orders. + create_order: Create order. + cancel_order: Cancel order. + get_user_trades: Get user trades. + close_connection: Close connection. + + Attributes: + session (aiohttp.ClientSession): Session. + api_key (str): API key. + api_secret (str): API secret. + refresh_token (str): Refresh token. + access_token (str): Access token. + """ + + def __init__( # type: ignore[no-untyped-def] + self, + api_key: t.OptionalStr = None, + api_secret: t.OptionalStr = None, + access_token: t.OptionalStr = None, + refresh_token: t.OptionalStr = None, + requests_params: t.OptionalDictStrAny = None, + background_relogin: bool = False, + background_relogin_interval: int = 60 * 60 * 24 * 6, + background_refresh_token: bool = False, + background_refresh_token_interval: int = 60 * 13, + ): + """ + Constructor. + + Args: + api_key (str): API key. + api_secret (str): API secret. + access_token (str): Access token. + refresh_token (str): Refresh token. + requests_params (dict): Requests params. + background_relogin (bool): Background refresh. + background_relogin_interval (int): Background refresh interval. + background_refresh_token (bool): Background refresh token. + background_refresh_token_interval (int): Background refresh token interval. + + Notes: + If `api_key` and `api_secret` are not provided, they will be read from the environment variables + `BITPIN_API_KEY` and `BITPIN_API_SECRET` respectively. + + If `access_token` and `refresh_token` are not provided, they will be read from the environment variables + `BITPIN_ACCESS_TOKEN` and `BITPIN_REFRESH_TOKEN` respectively. + + If `requests_params` are provided, they will be used as default for every request. + + If `requests_params` are provided in `kwargs`, they will override existing `requests_params`. + + If `background_relogin` is enabled, access token will be refreshed in background every + `background_relogin_interval` seconds. + + If `background_refresh_token` is enabled, refresh token will be refreshed in background every + `background_refresh_token_interval` seconds. + """ + + super().__init__( + api_key, + api_secret, + access_token, + refresh_token, + requests_params, + background_relogin, + background_relogin_interval, + background_refresh_token, + background_refresh_token_interval, + ) + + self._handle_login() + + def _init_session(self) -> requests.Session: + """ + Initialize session. + + Returns: + session (aiohttp.ClientSession): Session. + + """ + + session = requests.Session() + session.headers["Content-Type"] = "application/json" + session.headers["Accept"] = "application/json" + return session + + def _get( # type: ignore[no-untyped-def] + self, + path: str, + signed: bool = False, + version: str = CoreClient.PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: + """ + Make a GET request. + + Args: + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + return self._request_api(deprecated_enums.RequestMethod.GET, path, signed, version, **kwargs) + + def _post( # type: ignore[no-untyped-def] + self, + path: str, + signed: bool = False, + version: str = CoreClient.PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: + """ + Make a POST request. + + Args: + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + return self._request_api(deprecated_enums.RequestMethod.POST, path, signed, version, **kwargs) + + def _delete( # type: ignore[no-untyped-def] + self, + path: str, + signed: bool = False, + version: str = CoreClient.PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: + """ + Make a DELETE request. + + Args: + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + return self._request_api(deprecated_enums.RequestMethod.DELETE, path, signed, version, **kwargs) + + def _request_api( # type: ignore[no-untyped-def] + self, + method: t.RequestMethods, + path: str, + signed: bool = False, + version: str = CoreClient.PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: + """ + Request API. + + Args: + method (RequestMethod): Method. + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + uri = self._create_api_uri(path, version) + return self._request(method, uri, signed, **kwargs) + + def _request( # type: ignore[no-untyped-def] + self, method: t.RequestMethods, uri: str, signed: bool, **kwargs + ) -> t.DictStrAny: + """ + Request. + + Args: + method (RequestMethod): Method. + uri (str): URI. + signed (bool): Signed. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + kwargs = self._get_request_kwargs(method, signed, **kwargs) + + with getattr(self.session, method)(uri, **kwargs) as response: + self.response = response # pylint: disable=attribute-defined-outside-init + return self._handle_response(response) + + @staticmethod + def _handle_response(response: requests.Response) -> t.DictStrAny: # type: ignore[override] + """ + Handle response. + + Args: + response (aiohttp.ClientResponse): Response. + + Returns: + dict: Response. + + Raises: + APIException: API Exception. + RequestException: Request Exception. + """ + + if not str(response.status_code).startswith("2"): + if response.request.method.lower() == deprecated_enums.RequestMethod.DELETE: # type: ignore[union-attr] + return { + "status": "success", + "id": response.request.path_url.split("/")[-2], + } + raise APIException(response, response.status_code, response.text) + try: + return response.json() # type: ignore[no-any-return] + except ValueError as exc: + raise RequestException(f"Invalid Response: {response.text}") from exc + + def _handle_login(self) -> None: + """Handle login.""" + + if self.api_key and self.api_secret: + self.login() + + if self._background_relogin: + Thread(target=self._background_relogin_task, daemon=True).start() + + if self._background_refresh_token: + Thread(target=self._background_refresh_token_task, daemon=True).start() + + def _background_relogin_task(self) -> None: + """Background relogin task.""" + + while True: + try: + self.login() + time.sleep(self._background_relogin_interval) + except Exception: # pylint: disable=broad-except + continue + + def _background_refresh_token_task(self) -> None: + """Background refresh token task.""" + + while True: + try: + self.refresh_access_token() + time.sleep(self._background_refresh_token_interval) + except Exception: # pylint: disable=broad-except + continue + + def login(self, **kwargs) -> t.LoginResponse: # type: ignore[no-untyped-def] + """ + Login and set (refresh_token/access_token). + + Args: + **kwargs: Kwargs. + + Returns: + Response (LoginResponse): Response. + + References: + [API Docs](https://docs.bitpin.ir/#02c24a5326) + """ + + kwargs["json"] = {"api_key": self.api_key, "secret_key": self.api_secret} + _: t.LoginResponse = self._post(self.LOGIN_URL, **kwargs) # type: ignore[assignment] + + self.refresh_token = _["refresh"] + self.access_token = _["access"] + + return _ + + def refresh_access_token( # type: ignore[no-untyped-def] + self, refresh_token: t.OptionalStr = None, **kwargs + ) -> t.RefreshTokenResponse: + """ + Refresh token. + + Args: + refresh_token (str): Refresh token. + **kwargs: Kwargs. + + Returns: + Response (RefreshTokenResponse): Response. + + References: + [API Docs](https://docs.bitpin.ir/#9b81094f74) + """ + + kwargs["json"] = {"refresh": refresh_token or self.refresh_token} + _: t.RefreshTokenResponse = self._post(self.REFRESH_TOKEN_URL, **kwargs) # type: ignore[assignment] + + self.access_token = _["access"] + + return _ + + def get_user_info(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Get user info. + + Args: + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#5b3c85d79e) + """ + + return self._get(self.USER_INFO_URL, signed=True, **kwargs) + + def get_currencies_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Get currencies info. + + Args: + page (int): Page. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#7e59da3d0d) + + Notes: + Rate limit: 10000/day or 200/minute if you are authenticated. + """ + + return self._get(self.CURRENCIES_LIST_URL.format(page), **kwargs) + + def get_markets_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Get markets info. + + Args: + page (int): Page. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#334792bb2b) + + Notes: + Rate limit: 10000/day or 200/minute if you are authenticated. + """ + + return self._get(self.MARKETS_LIST_URL.format(page), **kwargs) + + def get_wallets(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Get wallets. + + Args: + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#9b93495188) + + Notes: + Rate limit: 10000/day. + """ + + return self._get(self.WALLETS_URL, signed=True, **kwargs) + + def get_orderbook( # type: ignore[no-untyped-def] + self, + market_id: int, + type: t.OrderTypes, + **kwargs, # pylint: disable=redefined-builtin + ) -> t.OrderbookResponse: + """ + Get orderbook. + + Args: + market_id (int): Market ID. + type (OrderTypes): Type. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#ec7180fc0e) + """ + + return self._get(self.ORDERBOOK_URL.format(market_id, str(type)), **kwargs) # type: ignore[return-value] + + def get_recent_trades(self, market_id: int, **kwargs) -> t.TradeResponse: # type: ignore[no-untyped-def] + """ + Get recent trades. + + Args: + market_id (int): Market ID. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#1dd63530b5) + """ + + return self._get(self.RECENT_TRADES_URL.format(market_id), **kwargs) # type: ignore[return-value] + + def get_user_orders( # type: ignore[no-untyped-def] + self, + market_id: t.OptionalInt = None, + type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin + state: t.OptionalStr = None, + mode: t.OptionalStr = None, + identifier: t.OptionalStr = None, + page: int = 1, + **kwargs, + ) -> t.OpenOrdersResponse: + """ + Get user Orders. + + Args: + market_id (int): Market ID. + type (OrderTypes): Type. + state (str): State. + mode (str): Mode. + identifier (str): Identifier. + page (int): Page. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#8a7c2a2af5) + """ + + kwargs["params"] = {k: str(v) for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} + return self._get(self.ORDERS_URL, signed=True, **kwargs) # type: ignore[return-value] + + def create_order( # type: ignore[no-untyped-def] + self, + market: int, + amount1: float, + price: float, + mode: t.OrderModes, + type: t.OrderTypes, # pylint: disable=redefined-builtin + identifier: t.OptionalStr = None, + price_limit: t.OptionalFloat = None, + price_stop: t.OptionalFloat = None, + price_limit_oco: t.OptionalFloat = None, + amount2: t.OptionalFloat = None, + **kwargs, + ) -> t.CreateOrderResponse: + """ + Create order. + + Args: + market (int): Market. + amount1 (float): Amount1. + price (float): Price. + mode (OrderModes): Mode. + type (OrderTypes): Type. + identifier (str): Identifier. + price_limit (float): Price limit. + price_stop (float): Price stop. + price_limit_oco (float): Price limit oco. + amount2 (float): Amount2. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#34b353d77b) + """ + + kwargs["json"] = {k: str(v) for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} + return self._post(self.ORDERS_URL, signed=True, **kwargs) # type: ignore[return-value] + + def cancel_order(self, order_id: str, **kwargs) -> t.CancelOrderResponse: # type: ignore[no-untyped-def] + """ + Cancel order. + + Args: + order_id (str): Order ID. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#3fe8d57657) + """ + + return self._delete(self.ORDERS_URL + f"{order_id}/", signed=True, **kwargs) # type: ignore[return-value] + + def get_user_trades( # type: ignore[no-untyped-def] + self, + market_id: t.OptionalInt = None, + type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin + page: int = 1, + **kwargs, + ) -> t.DictStrAny: + """ + Get user trades. + + Args: + market_id (int): Market ID. + type (OrderTypes): Type. + page (int): Page. + **kwargs: Kwargs. + + Returns: + Response (dict): Response. + + References: + [API Docs](https://docs.bitpin.ir/#3fe8d57657) + """ + + kwargs["params"] = {k: str(v) for k, v in locals().items() if v is not None and k not in ("self", "kwargs")} + return self._get(self.USER_TRADES_URL, signed=True, **kwargs) + + def close_connection(self) -> None: + """Close connection.""" + + self.session.close() diff --git a/src/bitpin/deprecated/clients/core.py b/src/bitpin/deprecated/clients/core.py new file mode 100644 index 0000000..a1390ca --- /dev/null +++ b/src/bitpin/deprecated/clients/core.py @@ -0,0 +1,497 @@ +"""# Core Client.""" + +import os +from abc import ( + ABC, + abstractmethod, +) + +from src.bitpin.deprecated import deprecated_types as t + + +class CoreClient(ABC): # pylint: disable=too-many-instance-attributes + """Core Client.""" + + API_URL = "https://api.bitpin.ir" + + PUBLIC_API_VERSION_1 = "v1" + PUBLIC_API_VERSION_2 = "v2" + + REQUEST_TIMEOUT: float = 10 + + LOGIN_URL = "usr/api/login/" + REFRESH_TOKEN_URL = "usr/refresh_token/" + USER_INFO_URL = "usr/info/" + CURRENCIES_LIST_URL = "mkt/currencies/?page={}" + MARKETS_LIST_URL = "mkt/markets/?page={}" + WALLETS_URL = "wlt/wallets/" + ORDERBOOK_URL = "mth/actives/{}/?type={}" + RECENT_TRADES_URL = "mth/matches/{}/" + ORDERS_URL = "odr/orders/" + USER_TRADES_URL = "odr/matches/?type={}" + + def __init__( # type: ignore[no-untyped-def] + self, + api_key: t.OptionalStr = None, + api_secret: t.OptionalStr = None, + access_token: t.OptionalStr = None, + refresh_token: t.OptionalStr = None, + requests_params: t.OptionalDictStrAny = None, + background_relogin: bool = False, + background_relogin_interval: int = 60 * 60 * 24 * 6, + background_refresh_token: bool = False, + background_refresh_token_interval: int = 60 * 13, + ): + """ + Constructor. + + Args: + api_key (str): API key. + api_secret (str): API secret. + access_token (str): Access token. + refresh_token (str): Refresh token. + requests_params (dict): Requests params. + background_relogin (bool): Background refresh. + background_relogin_interval (int): Background refresh interval. + background_refresh_token (bool): Background refresh token. + background_refresh_token_interval (int): Background refresh token interval. + + Notes: + If `api_key` and `api_secret` are not provided, they will be read from the environment variables + `BITPIN_API_KEY` and `BITPIN_API_SECRET` respectively. + + If `access_token` and `refresh_token` are not provided, they will be read from the environment variables + `BITPIN_ACCESS_TOKEN` and `BITPIN_REFRESH_TOKEN` respectively. + + If `requests_params` are provided, they will be used as default for every request. + + If `requests_params` are provided in method's `kwargs`, they will override existing `requests_params`. + + If `background_relogin` is enabled, access token will be refreshed in background every + `background_relogin_interval` seconds. + + If `background_refresh_token` is enabled, refresh token will be refreshed in background every + `background_refresh_token_interval` seconds. + """ + + self.api_key = api_key or os.environ.get("BITPIN_API_KEY") + self.api_secret = api_secret or os.environ.get("BITPIN_API_SECRET") + self.access_token: t.OptionalStr = access_token or os.environ.get("BITPIN_ACCESS_TOKEN") + self.refresh_token: t.OptionalStr = refresh_token or os.environ.get("BITPIN_REFRESH_TOKEN") + + self._background_relogin = background_relogin + self._background_relogin_interval = background_relogin_interval + self._background_refresh_token = background_refresh_token + self._background_refresh_token_interval = background_refresh_token_interval + + self._requests_params = requests_params + self.session = self._init_session() + + def _get_request_kwargs( + self, method: t.RequestMethods, signed: bool, **kwargs + ) -> t.DictStrAny: # type: ignore[no-untyped-def] + kwargs["timeout"] = self.REQUEST_TIMEOUT + + if self._requests_params: + kwargs.update(self._requests_params) + + data = kwargs.get("data") + if data and isinstance(data, dict): + kwargs["data"] = data + + if "requests_params" in kwargs["data"]: + kwargs.update(kwargs["data"]["requests_params"]) + del kwargs["data"]["requests_params"] + + if signed is True: + headers: t.DictStrAny = kwargs.get("headers", {}) + headers.update({"Authorization": f"Bearer {self.access_token}"}) + kwargs["headers"] = headers + + if data and method == "get": + kwargs["params"] = "&".join(f"{data[0]}={data[1]}" for data in kwargs["data"]) + del kwargs["data"] + + return kwargs + + @staticmethod + def _pick(response: t.DictStrAny, key: str, value: t.t.Any, result_key: str = "results") -> t.DictStrAny: + for _ in response.get(result_key, []): + if _[key] == value: + response[result_key] = _ + return response + raise ValueError(f"{key} {value} not found in {response}") + + def _create_api_uri(self, path: str, version: str = PUBLIC_API_VERSION_1) -> str: + return self.API_URL + "/" + str(version) + "/" + path + + @abstractmethod + def _init_session(self) -> t.HttpSession: + """ + Initialize session. + + Returns: + session (t.Union[requests.Session, aiohttp.ClientSession]): Session. + """ + + raise NotImplementedError + + @abstractmethod + def _get( + self, + path: str, + signed: bool = False, + version: str = PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Make a GET request. + + Args: + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def _post( + self, + path: str, + signed: bool = False, + version: str = PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Make a POST request. + + Args: + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def _delete( + self, + path: str, + signed: bool = False, + version: str = PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Make a DELETE request. + + Args: + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def _request_api( # type: ignore[no-untyped-def] + self, + method: t.RequestMethods, + path: str, + signed: bool = False, + version: str = PUBLIC_API_VERSION_1, + **kwargs, + ) -> t.DictStrAny: + """ + Request API. + + Args: + method (str): Method (GET, POST, PUT, DELETE). + path (str): Path. + signed (bool): Signed. + version (str): Version. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def _request( + self, method: t.RequestMethods, uri: str, signed: bool, **kwargs + ) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Request. + + Args: + method (str): Method (GET, POST, PUT, DELETE). + uri (str): URI. + signed (bool): Signed. + **kwargs: Kwargs. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @staticmethod + @abstractmethod + def _handle_response(response: t.HttpResponses) -> t.DictStrAny: + """ + Handle response. + + Args: + response (t.Union[requests.Response, aiohttp.ClientResponse]): Response. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def _handle_login(self) -> None: + """Handle login.""" + + raise NotImplementedError + + @abstractmethod + def _background_relogin_task(self) -> None: + """Background relogin task.""" + + raise NotImplementedError + + @abstractmethod + def _background_refresh_token_task(self) -> None: + """Background refresh token task.""" + + raise NotImplementedError + + @abstractmethod + def login(self, **kwargs) -> t.LoginResponse: # type: ignore[no-untyped-def] + """ + Login. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def refresh_access_token( + self, refresh_token: t.OptionalStr = None, **kwargs + ) -> t.RefreshTokenResponse: # type: ignore[no-untyped-def] + """ + Refresh token. + + Args: + refresh_token (str): Refresh token. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def get_user_info(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Get user info. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def get_currencies_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Get currencies info. + + Args: + page (int): Page. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def get_markets_info(self, page: int = 1, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Get markets info. + + Args: + page (int): Page. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def get_wallets(self, **kwargs) -> t.DictStrAny: # type: ignore[no-untyped-def] + """ + Get wallets. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def get_orderbook( + self, market_id: int, type: t.OrderTypes, **kwargs + ) -> t.OrderbookResponse: # type: ignore[no-untyped-def] # pylint: disable=redefined-builtin + """ + Get orderbook. + + Args: + market_id (int): Market ID. + type (str): Type. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def get_recent_trades(self, market_id: int, **kwargs) -> t.TradeResponse: # type: ignore[no-untyped-def] + """ + Get recent trades. + + Args: + market_id (int): Market ID. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def get_user_orders( # type: ignore[no-untyped-def] + self, + market_id: t.OptionalInt = None, + type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin + state: t.OptionalStr = None, + mode: t.OptionalStr = None, + identifier: t.OptionalStr = None, + page: int = 1, + **kwargs, + ) -> t.OpenOrdersResponse: + """ + Get user orders. + + Args: + market_id (int): Market ID. + type (str): Type. + state (str): State. + mode (str): Mode. + identifier (str): Identifier. + page (int): Page. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def create_order( # type: ignore[no-untyped-def] + self, + market: int, + amount1: float, + price: float, + mode: t.OrderModes, + type: t.OrderTypes, # pylint: disable=redefined-builtin + identifier: t.OptionalStr = None, + price_limit: t.OptionalFloat = None, + price_stop: t.OptionalFloat = None, + price_limit_oco: t.OptionalFloat = None, + amount2: t.OptionalFloat = None, + **kwargs, + ) -> t.CreateOrderResponse: + """ + Create order. + + Args: + market (int): Market. + amount1 (float): Amount1. + price (float): Price. + mode (str): Mode. + type (str): Type. + identifier (str): Identifier. + price_limit (float): Price limit. + price_stop (float): Price stop. + price_limit_oco (float): Price limit oco. + amount2 (float): Amount2. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def cancel_order(self, order_id: str, **kwargs) -> t.CancelOrderResponse: # type: ignore[no-untyped-def] + """ + Cancel order. + + Args: + order_id (str): Order ID. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def get_user_trades( # type: ignore[no-untyped-def] + self, + market_id: t.OptionalInt = None, + type: t.OptionalOrderTypes = None, # pylint: disable=redefined-builtin + page: int = 1, + **kwargs, + ) -> t.DictStrAny: + """ + Get user trades. + + Args: + market_id (int): Market ID. + type (str): Type. + page (int): Page. + + Returns: + dict: Response. + """ + + raise NotImplementedError + + @abstractmethod + def close_connection(self) -> None: + """Close connection.""" + + raise NotImplementedError diff --git a/src/bitpin/deprecated/deprecated_enums.py b/src/bitpin/deprecated/deprecated_enums.py new file mode 100644 index 0000000..56525bd --- /dev/null +++ b/src/bitpin/deprecated/deprecated_enums.py @@ -0,0 +1,198 @@ +""" +# Enums. + +Enum classes for the BitPin API. +""" + +from enum import ( + Enum as _Enum, +) +from enum import ( + EnumMeta as _EnumMeta, +) +from typing import Any + + +class EnumMeta(_EnumMeta): + """Enum Meta.""" + + def __call__(cls, value: Any, *args: Any, **kwargs: Any) -> "Enum": # type: ignore[override] + """Call.""" + if isinstance(value, str): # pragma: no cover + value = value.upper() + + return super().__call__(value, *args, **kwargs) # type: ignore[no-any-return] + + def __contains__(cls, value: Any) -> bool: + """Contains.""" + + if isinstance(value, str): + return value.upper() in cls._value2member_map_ # type: ignore[attr-defined] + + return super().__contains__(value) # type: ignore[call-arg] + + +class Enum(_Enum, metaclass=EnumMeta): + """Enum.""" + + def __repr__(self) -> str: + """ + Representation. + + Returns: + str: Representation. + """ + + return f"{self.__class__.__name__}.{self.name}" + + def __str__(self) -> str: + """ + String representation. + + Returns: + str: String representation. + """ + + return str(self.value) + + def __eq__(self, other: Any) -> bool: # pragma: no cover + """ + Equal. + + Args: + other (Any): Other. + + Returns: + bool: True if equal, else False. + """ + + if isinstance(other, Enum): + return self.value == other.value # type: ignore[no-any-return] + return self.value == other # type: ignore[no-any-return] + + def __hash__(self) -> int: # pragma: no cover + """ + Hash. + + Returns: + int: Hash. + """ + + return hash(self.value) + + @classmethod + def _missing_(cls, value: object) -> "Enum": + """ + Missing. + + Args: + value (object): Value. + + Returns: + Enum: Enum. + """ + + for member in cls: + if isinstance(value, str) and member.value == value.upper(): # pragma: no cover + return member + + return super()._missing_(value) # type: ignore[attribute-error, no-any-return] + + @classmethod + def get_by_value(cls, value: Any) -> "Enum": + """ + Get Enum by value. + + Args: + value (Any): Value. + + Returns: + Enum: Enum. + """ + + for enum in cls: + if enum.value.lower() == value.lower(): + return enum + raise ValueError(f"Invalid value: {value}") + + @classmethod + def get_by_name(cls, name: Any) -> "Enum": + """ + Get Enum by name. + + Args: + name (Any): Name. + + Returns: + Enum: Enum. + """ + + for enum in cls: + if enum.name.lower() == name.lower(): + return enum + raise ValueError(f"Invalid name: {name}") + + @classmethod + def get_by_name_or_value(cls, name_or_value: Any) -> "Enum": + """ + Get Enum by name or value. + + Args: + name_or_value (Any): Name or value. + + Returns: + Enum: Enum. + """ + + try: + return cls.get_by_name(name_or_value) + except ValueError: + return cls.get_by_value(name_or_value) + + @classmethod + def get_all_values(cls) -> list[Any]: + """Get all values.""" + return [enum.value for enum in cls] + + @classmethod + def get_all_names(cls) -> list[str]: + """Get all names.""" + return [enum.name for enum in cls] + + @classmethod + def to_django_choices(cls) -> list[tuple]: + """Get all names.""" + return [(enum.name, enum.name) for enum in cls] + + +class OrderType(str, Enum): + """Order Type (BUY/SELL).""" + + BUY = "buy" + SELL = "sell" + + +class OrderMode(str, Enum): + """Order Mode (LIMIT/MARKET/OCO/STOP_LIMIT).""" + + LIMIT = "limit" + MARKET = "market" + OCO = "oco" + STOP_LIMIT = "stop_limit" + + +class OrderState(str, Enum): + """Order State (INITIAL/ACTIVE/CLOSED).""" + + INITIAL = "initial" + ACTIVE = "active" + CLOSED = "closed" + + +class RequestMethod(str, Enum): + """Request Methods (GET/POST/PUT/DELETE).""" + + GET = "get" + POST = "post" + PUT = "put" + DELETE = "delete" diff --git a/src/bitpin/deprecated/deprecated_types.py b/src/bitpin/deprecated/deprecated_types.py new file mode 100644 index 0000000..f7ccd94 --- /dev/null +++ b/src/bitpin/deprecated/deprecated_types.py @@ -0,0 +1,204 @@ +""" +# Deprecated Types. + +Types for Python Bitpin. + +## Description +This file contains all the types used in the project. +""" + +import asyncio +import typing as t + +import aiohttp +import requests + +from . import deprecated_enums + +# General Types: +OptionalStr = t.Optional[str] +OptionalInt = t.Optional[int] +OptionalFloat = t.Optional[float] + +DictStrAny = dict[str, t.Any] +OptionalDictStrAny = t.Optional[DictStrAny] + +EventLoop = asyncio.AbstractEventLoop +OptionalEventLoop = t.Optional[EventLoop] + +# Client Types: +OrderTypeBuy = t.Literal["buy"] +OrderTypeSell = t.Literal["sell"] +OrderTypes = t.Union[OrderTypeBuy, OrderTypeSell, deprecated_enums.OrderType] +OptionalOrderTypes = t.Optional[OrderTypes] + +OrderModeLimit = t.Literal["limit"] +OrderModeMarket = t.Literal["market"] +OrderModeOCO = t.Literal["oco"] +OrderModeStopLimit = t.Literal["stop_limit"] +OrderModes = t.Union[ + OrderModeLimit, + OrderModeMarket, + OrderModeOCO, + OrderModeStopLimit, + deprecated_enums.OrderMode, +] +OptionalOrderModes = t.Optional[OrderModes] + +# HTTP Types: +HttpSession = t.Union[requests.Session, aiohttp.ClientSession] +HttpResponses = t.Union[requests.Response, aiohttp.ClientResponse] + +# Request Types: +RequestMethodGet = t.Literal["get"] +RequestMethodPost = t.Literal["post"] +RequestMethodPut = t.Literal["put"] +RequestMethodDelete = t.Literal["delete"] +RequestMethods = t.Union[ + RequestMethodGet, + RequestMethodPost, + RequestMethodPut, + RequestMethodDelete, + deprecated_enums.RequestMethod, +] + + +# Response Types: +class ResultListResponse(t.TypedDict): + count: int | None + next: str | None + previous: str | None + results: list[DictStrAny] + + +class InnerOrderbookResponse(t.TypedDict): + amount: str + price: str + remain: str + value: str + + +class OrderbookResponse(t.TypedDict): + orders: list[InnerOrderbookResponse] + volume: str + + +class InnerTradeResponse(t.TypedDict): + time: float + price: str + value: str + match_amount: str + type: str + match_id: str + + +TradeResponse = list[InnerTradeResponse] + + +class LoginResponse(t.TypedDict): + refresh: str + access: str + + +class RefreshTokenResponse(t.TypedDict): + access: str + + +class CurrencyInfo(t.TypedDict): + id: int + title: str + title_fa: str + code: str + tradable: bool + for_test: bool + image: str + decimal: int + decimal_amount: int + decimal_irt: int + color: str + high_risk: bool + show_high_risk: bool + withdraw_commission: str + tags: list[DictStrAny] + + +class WalletInfo(t.TypedDict): + id: int + currency: CurrencyInfo + balance: str + frozen: str + total: str + value: str + value_frozen: str + value_total: str + usdt_value: str + usdt_value_frozen: str + usdt_value_total: str + address: str + inviter_commission: str + service: str + daily_withdraw: str + + +class MarketInfo(t.TypedDict): + id: int + currency1: CurrencyInfo + currency2: CurrencyInfo + code: str + title: str + title_fa: str + commissions: dict[str, float] + + +class CreateOrderResponse(t.TypedDict): + id: int + market: MarketInfo + amount1: str + amount2: str + price: str + price_limit: str + price_stop: OptionalStr + price_limit_oco: OptionalStr + type: str + active_limit: str + identifier: OptionalStr + mode: str + expected_gain: str + expected_resource: str + commission_percent: float + user_share_percent: float + expected_commission: str + expected_user_gain: str + expected_user_price: str + gain_currency: CurrencyInfo + resource_currency: CurrencyInfo + fulfilled: float + exchanged1: str + exchanged2: str + gain: str + resource: str + remain_amount: str + average_price: str + average_user_price: str + commission: str + user_commission: str + user_gain: str + created_at: str + activated_at: str + state: str + req_to_cancel: bool + info: dict[str, t.Any] + closed_at: OptionalStr + external_address: str + + +class OpenOrdersResponse(t.TypedDict): + count: int + next: OptionalStr + previous: OptionalStr + results: list[CreateOrderResponse] + + +class CancelOrderResponse(t.TypedDict): + status: str + id: str diff --git a/src/bitpin/enums.py b/src/bitpin/enums.py index cc23231..c775cdb 100644 --- a/src/bitpin/enums.py +++ b/src/bitpin/enums.py @@ -1,14 +1,16 @@ """ # Enums. -Enum classes for the BitPin API. +Enum classes for the Bitpin API. """ -from typing import Any, List from enum import ( Enum as _Enum, +) +from enum import ( EnumMeta as _EnumMeta, ) +from typing import Any class EnumMeta(_EnumMeta): @@ -19,15 +21,15 @@ def __call__(cls, value: Any, *args: Any, **kwargs: Any) -> "Enum": # type: ign if isinstance(value, str): # pragma: no cover value = value.upper() - return super().__call__(value, *args, **kwargs) # type: ignore[no-any-return] # noqa + return super().__call__(value, *args, **kwargs) # type: ignore[no-any-return] def __contains__(cls, value: Any) -> bool: """Contains.""" if isinstance(value, str): - return value.upper() in cls._value2member_map_ # type: ignore[attr-defined] # noqa + return value.upper() in cls._value2member_map_ # type: ignore[attr-defined] - return super().__contains__(value) # type: ignore[call-arg] # noqa + return super().__contains__(value) # type: ignore[call-arg] class Enum(_Enum, metaclass=EnumMeta): @@ -65,8 +67,8 @@ def __eq__(self, other: Any) -> bool: # pragma: no cover """ if isinstance(other, Enum): - return self.value == other.value # type: ignore[no-any-return] # noqa - return self.value == other # type: ignore[no-any-return] # noqa + return self.value == other.value # type: ignore[no-any-return] + return self.value == other # type: ignore[no-any-return] def __hash__(self) -> int: # pragma: no cover """ @@ -94,7 +96,7 @@ def _missing_(cls, value: object) -> "Enum": if isinstance(value, str) and member.value == value.upper(): # pragma: no cover return member - return super()._missing_(value) # type: ignore[attribute-error, no-any-return] # noqa + return super()._missing_(value) # type: ignore[attribute-error, no-any-return] @classmethod def get_by_value(cls, value: Any) -> "Enum": @@ -148,17 +150,17 @@ def get_by_name_or_value(cls, name_or_value: Any) -> "Enum": return cls.get_by_value(name_or_value) @classmethod - def get_all_values(cls) -> List[Any]: + def get_all_values(cls) -> list[Any]: """Get all values.""" return [enum.value for enum in cls] @classmethod - def get_all_names(cls) -> List[str]: + def get_all_names(cls) -> list[str]: """Get all names.""" return [enum.name for enum in cls] @classmethod - def to_django_choices(cls) -> List[tuple]: + def to_django_choices(cls) -> list[tuple]: """Get all names.""" return [(enum.name, enum.name) for enum in cls] @@ -194,3 +196,10 @@ class RequestMethod(str, Enum): POST = "post" PUT = "put" DELETE = "delete" + + +class OrderBookQuoteAsset(str, Enum): + """Order Book Quote Assets""" + + USDT = "USDT" + IRT = "IRT" diff --git a/src/bitpin/exceptions.py b/src/bitpin/exceptions.py index b54d71f..47addd7 100644 --- a/src/bitpin/exceptions.py +++ b/src/bitpin/exceptions.py @@ -9,7 +9,7 @@ import json -from . import types as t +from . import response_types as t class APIException(Exception): diff --git a/src/bitpin/response_types.py b/src/bitpin/response_types.py new file mode 100644 index 0000000..0f7495b --- /dev/null +++ b/src/bitpin/response_types.py @@ -0,0 +1,245 @@ +""" +# Types. + +Types for Python Bitpin. + +## Description +This file contains all the types used in the project. +""" + +import asyncio +import typing as t +from datetime import datetime + +import aiohttp +import requests + +from . import enums + +# General Types: +OptionalStr = t.Optional[str] +OptionalInt = t.Optional[int] +OptionalFloat = t.Optional[float] +OptionalStrList = t.Optional[list[str]] +OptionalDate = t.Optional[datetime] + +DictStrAny = dict[str, t.Any] +OptionalDictStrAny = t.Optional[DictStrAny] + +EventLoop = asyncio.AbstractEventLoop +OptionalEventLoop = t.Optional[EventLoop] + +# Client Types: +OrderTypeBuy = t.Literal["buy"] +OrderTypeSell = t.Literal["sell"] +OrderTypes = t.Union[OrderTypeBuy, OrderTypeSell, enums.OrderType] + +OptionalOrderTypes = t.Optional[OrderTypes] +OptionalOrderTypesList = t.Optional[list[OrderTypes]] + +OrderStateInitial = t.Literal["initial"] +OrderStateActive = t.Literal["active"] +OrderStateClosed = t.Literal["closed"] +OrderState = t.Union[OrderStateInitial, OrderStateActive, OrderStateClosed, enums.OrderState] +OptionalOrderState = t.Optional[OrderState] +OptionalOrderStateList = t.Optional[list[OrderState]] + +QuoteAssetIRT = t.Literal["IRT"] +QuoteAssetUSDT = t.Literal["USDT"] +OrderbookQuoteAsset = t.Union[QuoteAssetIRT, QuoteAssetUSDT, enums.OrderBookQuoteAsset] +OptionalQuoteAsset = t.Optional[OrderbookQuoteAsset] + +OrderModeLimit = t.Literal["limit"] +OrderModeMarket = t.Literal["market"] +OrderModeOCO = t.Literal["oco"] +OrderModeStopLimit = t.Literal["stop_limit"] +OrderModes = t.Union[OrderModeLimit, OrderModeMarket, OrderModeOCO, OrderModeStopLimit, enums.OrderMode] +OptionalOrderModes = t.Optional[OrderModes] +OptionalOrderModesList = t.Optional[list[OrderModes]] + +# HTTP Types: +HttpSession = t.Union[requests.Session, aiohttp.ClientSession] +HttpResponses = t.Union[requests.Response, aiohttp.ClientResponse] + +# Request Types: +RequestMethodGet = t.Literal["get"] +RequestMethodPost = t.Literal["post"] +RequestMethodPut = t.Literal["put"] +RequestMethodDelete = t.Literal["delete"] +RequestMethods = t.Union[ + RequestMethodGet, + RequestMethodPost, + RequestMethodPut, + RequestMethodDelete, + enums.RequestMethod, +] + + +# Request Types: +class OrderDict(t.TypedDict): + symbol: str + base_amount: float + price: float + side: OrderTypes + type: OrderModes + + +BulkOrderList = list[OrderDict] + +# Response Types: +CurrenciesInfo = list[ + t.TypedDict( + "CurrenciesInfo", + {"currency": str, "name": str, "tradable": bool, "precision": str}, + ) +] + +MarketsInfo = list[ + t.TypedDict( + "MarketsInfo", + { + "symbol": str, + "name": str, + "base": str, + "quote": str, + "tradable": bool, + "price_precision": str, + "base_amount_precision": str, + "quote_amount_precision": str, + }, + ) +] + +TickersInfo = list[ + t.TypedDict( + "TickersInfo", + { + "symbol": str, + "price": str, + "daily_change_price": float, + "low": str, + "high": str, + "timestamp": float, + }, + ) +] + +RecentTradesInfo = list[ + t.TypedDict( + "RecentTradesInfo", + { + "id": str, + "price": str, + "base_amount": str, + "quote_amount": str, + "side": OrderTypes, + }, + ) +] + + +class InnerOrderbookResponse(t.TypedDict): + price: str + quantity: str + + +class OrderbookResponse(t.TypedDict): + asks: list[InnerOrderbookResponse] + bids: list[InnerOrderbookResponse] + + +class LoginResponse(t.TypedDict): + refresh: str + access: str + + +class RefreshTokenResponse(t.TypedDict): + access: str + + +WalletInfo = list[ + t.TypedDict( + "WalletInfo", + {"id": int, "asset": str, "balance": str, "frozen": str, "service": str}, + ) +] + + +class CreateOrderResponse(t.TypedDict): + id: int + symbol: str + type: OrderModes + side: OrderTypes + price: str + stop_price: OptionalStr + oco_target_price: OptionalStr + base_amount: str + quote_amount: str + identifier: OptionalStr + state: str + closed_at: OptionalStr + created_at: str + dealed_base_amount: str + dealed_quote_amount: str + req_to_cancel: bool + commission: str + + +OpenOrdersResponse = list[ + t.TypedDict( + "OpenOrdersResponse", + { + "id": int, + "symbol": str, + "type": OrderModes, + "side": OrderTypes, + "base_amount": str, + "quote_amount": str, + "price": str, + "stop_price": OptionalStr, + "oco_target_price": OptionalStr, + "identifier": OptionalStr, + "state": str, + "created_at": str, + "closed_at": OptionalStr, + "dealed_base_amount": str, + "dealed_quote_amount": str, + "req_to_cancel": bool, + "commission": str, + }, + ) +] + +TradeResponse = list[ + t.TypedDict( + "TradeResponse", + { + "id": int, + "symbol": str, + "base_amount": str, + "quote_amount": str, + "price": str, + "created_at": str, + "commission": str, + "side": OrderTypes, + "order_id": int, + "identifier": OptionalStr, + }, + ) +] + + +class CancelOrderResponse(t.TypedDict): + status: str + id: str + + +CancelBulkOrderResponse = list[ + t.TypedDict( + "CancelBulkOrderResponse", + { + "canceled_orders": list[OptionalStr], + "not_canceled_orders": list[OptionalStr], + }, + ) +] diff --git a/src/bitpin/types.py b/src/bitpin/types.py deleted file mode 100644 index a30e141..0000000 --- a/src/bitpin/types.py +++ /dev/null @@ -1,226 +0,0 @@ -""" -# Types. - -Types for Python Bitpin. - -## Description -This file contains all the types used in the project. -""" - -import typing as t -import asyncio -import requests -import aiohttp - -from . import enums - -# General Types: -OptionalStr = t.Optional[str] -OptionalInt = t.Optional[int] -OptionalFloat = t.Optional[float] - -DictStrAny = t.Dict[str, t.Any] -OptionalDictStrAny = t.Optional[DictStrAny] - -EventLoop = asyncio.AbstractEventLoop -OptionalEventLoop = t.Optional[EventLoop] - -# Client Types: -OrderTypeBuy = t.Literal["buy"] -OrderTypeSell = t.Literal["sell"] -OrderTypes = t.Union[OrderTypeBuy, OrderTypeSell, enums.OrderType] -OptionalOrderTypes = t.Optional[OrderTypes] - -OrderModeLimit = t.Literal["limit"] -OrderModeMarket = t.Literal["market"] -OrderModeOCO = t.Literal["oco"] -OrderModeStopLimit = t.Literal["stop_limit"] -OrderModes = t.Union[OrderModeLimit, OrderModeMarket, OrderModeOCO, OrderModeStopLimit, enums.OrderMode] -OptionalOrderModes = t.Optional[OrderModes] - -# HTTP Types: -HttpSession = t.Union[requests.Session, aiohttp.ClientSession] -HttpResponses = t.Union[requests.Response, aiohttp.ClientResponse] - -# Request Types: -RequestMethodGet = t.Literal["get"] -RequestMethodPost = t.Literal["post"] -RequestMethodPut = t.Literal["put"] -RequestMethodDelete = t.Literal["delete"] -RequestMethods = t.Union[ - RequestMethodGet, - RequestMethodPost, - RequestMethodPut, - RequestMethodDelete, - enums.RequestMethod, -] - -# Response Types: -ResultListResponse = t.TypedDict( - "ResultListResponse", - { - "count": t.Optional[int], - "next": t.Optional[str], - "previous": t.Optional[str], - "results": t.List[DictStrAny], - }, -) - -InnerOrderbookResponse = t.TypedDict( - "InnerOrderbookResponse", - { - "amount": str, - "price": str, - "remain": str, - "value": str, - }, -) - -OrderbookResponse = t.TypedDict("OrderbookResponse", {"orders": t.List[InnerOrderbookResponse], "volume": str}) - -InnerTradeResponse = t.TypedDict( - "InnerTradeResponse", - { - "time": float, - "price": str, - "value": str, - "match_amount": str, - "type": str, - "match_id": str, - }, -) - -TradeResponse = t.List[InnerTradeResponse] - -LoginResponse = t.TypedDict( - "LoginResponse", - { - "refresh": str, - "access": str, - }, -) - -RefreshTokenResponse = t.TypedDict( - "RefreshTokenResponse", - { - "access": str, - }, -) - -CurrencyInfo = t.TypedDict( - "CurrencyInfo", - { - "id": int, - "title": str, - "title_fa": str, - "code": str, - "tradable": bool, - "for_test": bool, - "image": str, - "decimal": int, - "decimal_amount": int, - "decimal_irt": int, - "color": str, - "high_risk": bool, - "show_high_risk": bool, - "withdraw_commission": str, - "tags": t.List[DictStrAny], - }, -) - -WalletInfo = t.TypedDict( - "WalletInfo", - { - "id": int, - "currency": CurrencyInfo, - "balance": str, - "frozen": str, - "total": str, - "value": str, - "value_frozen": str, - "value_total": str, - "usdt_value": str, - "usdt_value_frozen": str, - "usdt_value_total": str, - "address": str, - "inviter_commission": str, - "service": str, - "daily_withdraw": str, - }, -) - -MarketInfo = t.TypedDict( - "MarketInfo", - { - "id": int, - "currency1": CurrencyInfo, - "currency2": CurrencyInfo, - "code": str, - "title": str, - "title_fa": str, - "commissions": t.Dict[str, float], - }, -) - -CreateOrderResponse = t.TypedDict( - "CreateOrderResponse", - { - "id": int, - "market": MarketInfo, - "amount1": str, - "amount2": str, - "price": str, - "price_limit": str, - "price_stop": OptionalStr, - "price_limit_oco": OptionalStr, - "type": str, - "active_limit": str, - "identifier": OptionalStr, - "mode": str, - "expected_gain": str, - "expected_resource": str, - "commission_percent": float, - "user_share_percent": float, - "expected_commission": str, - "expected_user_gain": str, - "expected_user_price": str, - "gain_currency": CurrencyInfo, - "resource_currency": CurrencyInfo, - "fulfilled": float, - "exchanged1": str, - "exchanged2": str, - "gain": str, - "resource": str, - "remain_amount": str, - "average_price": str, - "average_user_price": str, - "commission": str, - "user_commission": str, - "user_gain": str, - "created_at": str, - "activated_at": str, - "state": str, - "req_to_cancel": bool, - "info": t.Dict[str, t.Any], - "closed_at": OptionalStr, - "external_address": str, - }, -) - -OpenOrdersResponse = t.TypedDict( - "OpenOrdersResponse", - { - "count": int, - "next": OptionalStr, - "previous": OptionalStr, - "results": t.List[CreateOrderResponse], - }, -) - -CancelOrderResponse = t.TypedDict( - "CancelOrderResponse", - { - "status": str, - "id": str, - }, -) diff --git a/version b/version index 9cb76a8..60453e6 100644 --- a/version +++ b/version @@ -1 +1 @@ -v0.0.11 \ No newline at end of file +v1.0.0 \ No newline at end of file