1- """Async client (pilot) — ``httpx``-based, mirrors a slice of the sync surface .
1+ """Async client — ``httpx``-based mirror of the sync DataMaxi+ client .
22
33Requires the ``async`` extra::
44
1414 ticker = await client.cex.ticker.get(exchange="binance", market="spot",
1515 symbol="BTC-USDT")
1616
17- This is a deliberately small pilot (candle + ticker). It reuses the sync
18- client's endpoint resolution and error handling (``datamaxi._dispatch``) and
19- the shared DataFrame / ResponseMeta helpers, so the two clients can't drift on
20- request building or error semantics.
17+ Mirrors the full sync surface (``cex.*``, ``funding_rate``, ``forex``,
18+ ``premium``, ``liquidation``, ``open_interest``, ``margin_borrow``,
19+ ``index_price``, plus standalone ``AsyncTelegram`` / ``AsyncNaver``). Reuses
20+ the sync client's endpoint resolution and error handling (``datamaxi._dispatch``)
21+ and the shared DataFrame / ResponseMeta helpers, so the two clients can't drift
22+ on request building or error semantics.
2123"""
2224
23- from __future__ import annotations
24-
25- import asyncio
26- import os
27- from typing import Any , Union , TYPE_CHECKING
28-
29- from datamaxi .__version__ import __version__
30- from datamaxi .api import ResponseMeta
31- from datamaxi ._dispatch import resolve_endpoint , raise_for_error , extract_limit_usage
32- from datamaxi .lib .constants import (
33- BASE_URL ,
34- SPOT ,
35- FUTURES ,
36- USD ,
37- INTERVAL_1D ,
38- Market ,
39- Interval ,
25+ from typing import Any
26+
27+ from datamaxi .lib .constants import BASE_URL
28+ from datamaxi .aio ._core import AsyncAPI , AsyncResource
29+ from datamaxi .aio .cex import (
30+ AsyncCex ,
31+ AsyncCexCandle ,
32+ AsyncCexTicker ,
33+ AsyncCexFee ,
34+ AsyncCexWalletStatus ,
35+ AsyncCexAnnouncement ,
36+ AsyncCexToken ,
37+ AsyncCexSymbol ,
4038)
41- from datamaxi .lib .utils import check_required_parameters
42- from datamaxi .resources .responses import CandleResponse , TickerResponse
43-
44- if TYPE_CHECKING :
45- import pandas as pd
46-
47-
48- def _import_httpx ():
49- try :
50- import httpx
51- except ImportError as exc : # pragma: no cover - exercised via extra
52- raise ImportError (
53- "The async client requires httpx. Install it with: "
54- "pip install 'datamaxi[async]'"
55- ) from exc
56- return httpx
57-
58-
59- class AsyncAPI :
60- """Async transport built on ``httpx.AsyncClient``.
61-
62- Mirrors the sync ``API``: shared endpoint resolution, bounded retry of
63- transient gateway 5xx, the same ``ClientError`` / ``ServerError`` contract,
64- and ``last_response`` metadata.
65- """
66-
67- def __init__ (
68- self ,
69- api_key = None ,
70- base_url = None ,
71- timeout = 10 ,
72- max_retries = 3 ,
73- retry_backoff = 0.5 ,
74- retry_statuses = (502 , 503 , 504 ),
75- transport = None ,
76- ):
77- httpx = _import_httpx ()
78- self .api_key = api_key or os .environ .get ("DATAMAXI_API_KEY" )
79- self .base_url = base_url
80- self .timeout = timeout
81- self .max_retries = max_retries
82- self .retry_backoff = retry_backoff
83- self .retry_statuses = tuple (retry_statuses )
84- self .last_response = None
85- self ._client = httpx .AsyncClient (
86- base_url = base_url or "" ,
87- timeout = timeout ,
88- transport = transport ,
89- headers = {
90- "Content-Type" : "application/json;charset=utf-8" ,
91- "User-Agent" : "datamaxi/" + __version__ ,
92- "X-DTMX-APIKEY" : str (self .api_key ),
93- },
94- )
95-
96- async def request_endpoint (self , op_id , ** params ):
97- method , url_path , query_params = resolve_endpoint (op_id , ** params )
98- return await self .send_request (method , url_path , payload = query_params )
99-
100- async def send_request (self , method , url_path , payload = None ):
101- # str()-encode scalars so bools match the sync client's urlencode
102- # output (e.g. include_source -> "True", not httpx's "true").
103- params = {k : str (v ) for k , v in (payload or {}).items () if v is not None }
104- for attempt in range (self .max_retries + 1 ):
105- response = await self ._client .request (method , url_path , params = params )
106- if (
107- response .status_code in self .retry_statuses
108- and attempt < self .max_retries
109- ):
110- await asyncio .sleep (self .retry_backoff * (attempt + 1 ))
111- continue
112- break
113-
114- raise_for_error (response .status_code , response .text , response .headers )
115-
116- try :
117- data = response .json ()
118- except ValueError :
119- data = response .text
120-
121- self .last_response = ResponseMeta (
122- status_code = response .status_code ,
123- headers = response .headers ,
124- limit_usage = extract_limit_usage (response .headers ),
125- data = data ,
126- )
127- return data
128-
129- async def aclose (self ):
130- await self ._client .aclose ()
131-
132- async def __aenter__ (self ):
133- return self
134-
135- async def __aexit__ (self , * exc ):
136- await self .aclose ()
137-
138-
139- class AsyncResource :
140- """Base for async resources — composes a shared ``AsyncAPI``."""
141-
142- def __init__ (self , api : "AsyncAPI" ):
143- self ._api = api
144-
145- async def request_endpoint (self , op_id , ** params ):
146- return await self ._api .request_endpoint (op_id , ** params )
147-
148- @property
149- def last_response (self ):
150- return self ._api .last_response
151-
152-
153- class AsyncCexCandle (AsyncResource ):
154- async def __call__ (
155- self ,
156- exchange : str ,
157- market : Market ,
158- symbol : str ,
159- currency : str = USD ,
160- interval : Interval = INTERVAL_1D ,
161- from_unix : str = None ,
162- to_unix : str = None ,
163- pandas : bool = True ,
164- ) -> Union [pd .DataFrame , CandleResponse ]:
165- """Fetch candle data (async). See ``datamaxi.Datamaxi.cex.candle``."""
166- check_required_parameters (
167- [
168- [exchange , "exchange" ],
169- [symbol , "symbol" ],
170- [interval , "interval" ],
171- [market , "market" ],
172- [currency , "currency" ],
173- ]
174- )
175- if market not in [SPOT , FUTURES ]:
176- raise ValueError ("market must be either spot or futures" )
177-
178- res = await self .request_endpoint (
179- "cex_candle" ,
180- exchange = exchange ,
181- market = market ,
182- symbol = symbol ,
183- interval = interval ,
184- currency = currency ,
185- ** {"from" : from_unix , "to" : to_unix },
186- )
187- if res ["data" ] is None or len (res ["data" ]) == 0 :
188- raise ValueError ("no data found" )
189-
190- if pandas :
191- from datamaxi .resources .utils import convert_data_to_data_frame
192-
193- return convert_data_to_data_frame (res ["data" ])
194- return res
195-
196-
197- class AsyncCexTicker (AsyncResource ):
198- async def get (
199- self ,
200- exchange : str ,
201- symbol : str ,
202- market : Market ,
203- currency : str = None ,
204- conversion_base : str = None ,
205- include_source : bool = False ,
206- pandas : bool = True ,
207- ) -> Union [pd .DataFrame , TickerResponse ]:
208- """Fetch ticker data (async). See ``datamaxi.Datamaxi.cex.ticker``."""
209- check_required_parameters (
210- [
211- [exchange , "exchange" ],
212- [symbol , "symbol" ],
213- [market , "market" ],
214- ]
215- )
216- if market not in [SPOT , FUTURES ]:
217- raise ValueError ("market must be either spot or futures" )
218-
219- res = await self .request_endpoint (
220- "ticker" ,
221- exchange = exchange ,
222- symbol = symbol ,
223- market = market ,
224- currency = currency ,
225- conversion_base = conversion_base ,
226- include_source = include_source ,
227- )
228-
229- if pandas :
230- import pandas as pd
231-
232- df = pd .DataFrame ([res ["data" ]])
233- df = df .set_index ("d" )
234- return df
235- return res
236-
237-
238- class AsyncCex (AsyncResource ):
239- def __init__ (self , api : "AsyncAPI" ):
240- super ().__init__ (api )
241- self .candle = AsyncCexCandle (api )
242- self .ticker = AsyncCexTicker (api )
39+ from datamaxi .aio .funding_rate import AsyncFundingRate
40+ from datamaxi .aio .forex import AsyncForex
41+ from datamaxi .aio .premium import AsyncPremium
42+ from datamaxi .aio .liquidation import AsyncLiquidation
43+ from datamaxi .aio .open_interest import AsyncOpenInterest
44+ from datamaxi .aio .margin_borrow import AsyncMarginBorrow
45+ from datamaxi .aio .index_price import AsyncIndexPrice
46+ from datamaxi .aio .telegram import AsyncTelegram
47+ from datamaxi .aio .naver import AsyncNaver
24348
24449
24550class AsyncDatamaxi :
246- """Async entrypoint (pilot). Exposes ``cex.candle`` and ``cex.ticker` `.
51+ """Async entrypoint — full mirror of the sync :class:`datamaxi.Datamaxi `.
24752
24853 Use as an async context manager so the underlying ``httpx`` client is
24954 closed, or call :meth:`aclose` explicitly.
@@ -252,8 +57,17 @@ class AsyncDatamaxi:
25257 def __init__ (self , api_key = None , ** kwargs : Any ):
25358 if "base_url" not in kwargs :
25459 kwargs ["base_url" ] = BASE_URL
255- self ._api = AsyncAPI (api_key , ** kwargs )
256- self .cex = AsyncCex (self ._api )
60+ api = AsyncAPI (api_key , ** kwargs )
61+ self ._api = api
62+
63+ self .cex = AsyncCex (api )
64+ self .funding_rate = AsyncFundingRate (api )
65+ self .forex = AsyncForex (api )
66+ self .premium = AsyncPremium (api )
67+ self .liquidation = AsyncLiquidation (api )
68+ self .open_interest = AsyncOpenInterest (api )
69+ self .margin_borrow = AsyncMarginBorrow (api )
70+ self .index_price = AsyncIndexPrice (api )
25771
25872 async def aclose (self ):
25973 await self ._api .aclose ()
@@ -272,9 +86,23 @@ def __repr__(self):
27286
27387__all__ = [
27488 "AsyncDatamaxi" ,
89+ "AsyncTelegram" ,
90+ "AsyncNaver" ,
27591 "AsyncAPI" ,
27692 "AsyncResource" ,
27793 "AsyncCex" ,
27894 "AsyncCexCandle" ,
27995 "AsyncCexTicker" ,
96+ "AsyncCexFee" ,
97+ "AsyncCexWalletStatus" ,
98+ "AsyncCexAnnouncement" ,
99+ "AsyncCexToken" ,
100+ "AsyncCexSymbol" ,
101+ "AsyncFundingRate" ,
102+ "AsyncForex" ,
103+ "AsyncPremium" ,
104+ "AsyncLiquidation" ,
105+ "AsyncOpenInterest" ,
106+ "AsyncMarginBorrow" ,
107+ "AsyncIndexPrice" ,
280108]
0 commit comments