avee 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- avee/__init__.py +33 -0
- avee/_client.py +214 -0
- avee/_decode.py +79 -0
- avee/_operations.py +1384 -0
- avee/_request.py +232 -0
- avee/_transport.py +219 -0
- avee/_types.py +30 -0
- avee/astra/__init__.py +65 -0
- avee/astra/_transport.py +152 -0
- avee/astra/client.py +426 -0
- avee/astra/errors.py +60 -0
- avee/astra/ids.py +37 -0
- avee/astra/models.py +354 -0
- avee/astra/price.py +47 -0
- avee/astra/py.typed +0 -0
- avee/astra/stream.py +378 -0
- avee/errors.py +87 -0
- avee/models.py +3277 -0
- avee/py.typed +0 -0
- avee/x402.py +167 -0
- avee-0.1.0.dist-info/METADATA +239 -0
- avee-0.1.0.dist-info/RECORD +24 -0
- avee-0.1.0.dist-info/WHEEL +4 -0
- avee-0.1.0.dist-info/licenses/LICENSE +21 -0
avee/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from . import models
|
|
2
|
+
from ._client import DEFAULT_BASE_URL, PREVIEW_BASE_URL, VERSION, AsyncAveeClient, AveeClient
|
|
3
|
+
from ._operations import OPERATIONS
|
|
4
|
+
from ._request import RequestOptions, apaginate, paginate
|
|
5
|
+
from ._types import PaymentReceipt, RateLimit, ResponseInfo
|
|
6
|
+
from .errors import AveeAPIError, AveeConnectionError, AveeError, AveePaymentError, AveeTimeoutError, AveeValidationError
|
|
7
|
+
from .x402 import Payer, PaymentContext, PaymentSignature
|
|
8
|
+
|
|
9
|
+
__version__ = VERSION
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DEFAULT_BASE_URL",
|
|
13
|
+
"OPERATIONS",
|
|
14
|
+
"PREVIEW_BASE_URL",
|
|
15
|
+
"AsyncAveeClient",
|
|
16
|
+
"AveeAPIError",
|
|
17
|
+
"AveeClient",
|
|
18
|
+
"AveeConnectionError",
|
|
19
|
+
"AveeError",
|
|
20
|
+
"AveePaymentError",
|
|
21
|
+
"AveeTimeoutError",
|
|
22
|
+
"AveeValidationError",
|
|
23
|
+
"Payer",
|
|
24
|
+
"PaymentContext",
|
|
25
|
+
"PaymentReceipt",
|
|
26
|
+
"PaymentSignature",
|
|
27
|
+
"RateLimit",
|
|
28
|
+
"RequestOptions",
|
|
29
|
+
"ResponseInfo",
|
|
30
|
+
"apaginate",
|
|
31
|
+
"models",
|
|
32
|
+
"paginate",
|
|
33
|
+
]
|
avee/_client.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import inspect
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Callable, Mapping
|
|
8
|
+
from types import TracebackType
|
|
9
|
+
from typing import Any, TypeVar
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from . import _transport as tr
|
|
14
|
+
from ._operations import AsyncOperations, SyncOperations
|
|
15
|
+
from ._request import Call, RequestOptions
|
|
16
|
+
from ._types import ResponseInfo
|
|
17
|
+
from .errors import AveeError, AveePaymentError, AveeValidationError
|
|
18
|
+
from .x402 import Payer, decode_required, finish, prepare
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
DEFAULT_BASE_URL = "https://api.preview.avee.tech/api/v1"
|
|
23
|
+
PREVIEW_BASE_URL = "https://api.preview.avee.tech/api/v1"
|
|
24
|
+
VERSION = "0.1.0"
|
|
25
|
+
_USER_AGENT = f"avee-python/{VERSION}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _Base:
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
base_url: str,
|
|
32
|
+
api_key: str | None,
|
|
33
|
+
timeout: float,
|
|
34
|
+
max_retries: int,
|
|
35
|
+
max_retry_delay: float,
|
|
36
|
+
max_response_bytes: int,
|
|
37
|
+
headers: Mapping[str, str] | None,
|
|
38
|
+
payer: Payer | None,
|
|
39
|
+
) -> None:
|
|
40
|
+
self._config = tr.validate_config(base_url, api_key, timeout, max_retries, max_retry_delay, max_response_bytes, headers, payer)
|
|
41
|
+
self.last_response: ResponseInfo | None = None
|
|
42
|
+
"""Status, request id, rate-limit headers and x402 receipt of the most recent response."""
|
|
43
|
+
|
|
44
|
+
def _prepare(self, call: Call) -> tuple[str, bytes | None]:
|
|
45
|
+
body = None if call.body is None else json.dumps(call.body, separators=(",", ":")).encode()
|
|
46
|
+
return self._config.base_url + call.path(), body
|
|
47
|
+
|
|
48
|
+
def _challenge(self, call: Call, headers: httpx.Headers, data: bytes) -> dict[str, Any]:
|
|
49
|
+
raw = decode_required(headers.get("payment-required"), data)
|
|
50
|
+
if raw is None:
|
|
51
|
+
raise AveePaymentError("the 402 challenge is unreadable", call.operation)
|
|
52
|
+
return raw
|
|
53
|
+
|
|
54
|
+
def _record(self, call: Call, resp: httpx.Response, data: bytes | None, url: str) -> bytes:
|
|
55
|
+
self.last_response = tr.response_info(call.operation, resp.status_code, resp.headers)
|
|
56
|
+
if data is None:
|
|
57
|
+
raise AveeValidationError(f"{call.operation}: response from {url} exceeds {self._config.max_response_bytes} bytes")
|
|
58
|
+
return data
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class AveeClient(_Base, SyncOperations):
|
|
62
|
+
"""Synchronous client for the avee DEX data API; every /api/v1 operation is a method."""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
67
|
+
*,
|
|
68
|
+
api_key: str | None = None,
|
|
69
|
+
timeout: float = 30.0,
|
|
70
|
+
max_retries: int = 2,
|
|
71
|
+
max_retry_delay: float = 30.0,
|
|
72
|
+
max_response_bytes: int = 16 * 1024 * 1024,
|
|
73
|
+
headers: Mapping[str, str] | None = None,
|
|
74
|
+
payer: Payer | None = None,
|
|
75
|
+
http_client: httpx.Client | None = None,
|
|
76
|
+
) -> None:
|
|
77
|
+
super().__init__(base_url, api_key, timeout, max_retries, max_retry_delay, max_response_bytes, headers, payer)
|
|
78
|
+
self._owns = http_client is None
|
|
79
|
+
self._http = http_client if http_client is not None else httpx.Client(follow_redirects=False)
|
|
80
|
+
|
|
81
|
+
def close(self) -> None:
|
|
82
|
+
if self._owns:
|
|
83
|
+
self._http.close()
|
|
84
|
+
|
|
85
|
+
def __enter__(self) -> AveeClient:
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def __exit__(self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None:
|
|
89
|
+
self.close()
|
|
90
|
+
|
|
91
|
+
def _attempt(self, call: Call, url: str, body: bytes | None, signature: str | None) -> tuple[int, httpx.Headers, bytes]:
|
|
92
|
+
headers = tr.request_headers(self._config, body is not None, signature, _USER_AGENT)
|
|
93
|
+
deadline = time.monotonic() + self._config.timeout
|
|
94
|
+
try:
|
|
95
|
+
with self._http.stream(call.method, url, params=tuple(call.query), content=body, headers=headers, timeout=self._config.timeout, follow_redirects=False) as resp:
|
|
96
|
+
data = tr.read_sync(resp.iter_bytes(), self._config.max_response_bytes, deadline)
|
|
97
|
+
return resp.status_code, resp.headers, self._record(call, resp, data, url)
|
|
98
|
+
except httpx.HTTPError as err:
|
|
99
|
+
raise tr.transport_error(err, call.method, url, self._config.timeout) from err
|
|
100
|
+
|
|
101
|
+
def _sign(self, payer: Payer, call: Call, url: str, raw: dict[str, Any]) -> str:
|
|
102
|
+
context, choice = prepare(payer, call.operation, call.method, url, raw)
|
|
103
|
+
try:
|
|
104
|
+
signature = payer.sign(context)
|
|
105
|
+
except Exception as err:
|
|
106
|
+
raise AveePaymentError(f"the payer did not sign: {err}", call.operation, raw) from err
|
|
107
|
+
if inspect.isawaitable(signature):
|
|
108
|
+
if inspect.iscoroutine(signature):
|
|
109
|
+
signature.close()
|
|
110
|
+
raise AveePaymentError("an async payer needs AsyncAveeClient", call.operation, raw)
|
|
111
|
+
return finish(signature, context, choice, raw)
|
|
112
|
+
|
|
113
|
+
def _send(self, call: Call, decode: Callable[[Any], T], options: RequestOptions | None) -> T:
|
|
114
|
+
url, body = self._prepare(call)
|
|
115
|
+
signature: str | None = None
|
|
116
|
+
attempt = 0
|
|
117
|
+
while True:
|
|
118
|
+
try:
|
|
119
|
+
status, headers, data = self._attempt(call, url, body, signature)
|
|
120
|
+
except AveeError as err:
|
|
121
|
+
delay = tr.retry_delay(err, call.method, attempt, signature is not None, self._config)
|
|
122
|
+
if delay is None:
|
|
123
|
+
raise
|
|
124
|
+
time.sleep(delay)
|
|
125
|
+
attempt += 1
|
|
126
|
+
continue
|
|
127
|
+
if 200 <= status < 300:
|
|
128
|
+
return decode(tr.decode_json(data, call.operation, url))
|
|
129
|
+
if status == 402 and self._config.payer is not None and signature is None:
|
|
130
|
+
signature = self._sign(self._config.payer, call, url, self._challenge(call, headers, data))
|
|
131
|
+
continue
|
|
132
|
+
api_err = tr.api_error(call.operation, call.method, url, status, headers, data, signature is not None)
|
|
133
|
+
delay = tr.retry_delay(api_err, call.method, attempt, signature is not None, self._config)
|
|
134
|
+
if delay is None:
|
|
135
|
+
raise api_err
|
|
136
|
+
time.sleep(delay)
|
|
137
|
+
attempt += 1
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class AsyncAveeClient(_Base, AsyncOperations):
|
|
141
|
+
"""Asyncio client for the avee DEX data API; the same methods as coroutines."""
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
146
|
+
*,
|
|
147
|
+
api_key: str | None = None,
|
|
148
|
+
timeout: float = 30.0,
|
|
149
|
+
max_retries: int = 2,
|
|
150
|
+
max_retry_delay: float = 30.0,
|
|
151
|
+
max_response_bytes: int = 16 * 1024 * 1024,
|
|
152
|
+
headers: Mapping[str, str] | None = None,
|
|
153
|
+
payer: Payer | None = None,
|
|
154
|
+
http_client: httpx.AsyncClient | None = None,
|
|
155
|
+
) -> None:
|
|
156
|
+
super().__init__(base_url, api_key, timeout, max_retries, max_retry_delay, max_response_bytes, headers, payer)
|
|
157
|
+
self._owns = http_client is None
|
|
158
|
+
self._http = http_client if http_client is not None else httpx.AsyncClient(follow_redirects=False)
|
|
159
|
+
|
|
160
|
+
async def aclose(self) -> None:
|
|
161
|
+
if self._owns:
|
|
162
|
+
await self._http.aclose()
|
|
163
|
+
|
|
164
|
+
async def __aenter__(self) -> AsyncAveeClient:
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
async def __aexit__(self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None:
|
|
168
|
+
await self.aclose()
|
|
169
|
+
|
|
170
|
+
async def _attempt(self, call: Call, url: str, body: bytes | None, signature: str | None) -> tuple[int, httpx.Headers, bytes]:
|
|
171
|
+
headers = tr.request_headers(self._config, body is not None, signature, _USER_AGENT)
|
|
172
|
+
deadline = time.monotonic() + self._config.timeout
|
|
173
|
+
try:
|
|
174
|
+
async with self._http.stream(call.method, url, params=tuple(call.query), content=body, headers=headers, timeout=self._config.timeout, follow_redirects=False) as resp:
|
|
175
|
+
data = await tr.read_async(resp.aiter_bytes(), self._config.max_response_bytes, deadline)
|
|
176
|
+
return resp.status_code, resp.headers, self._record(call, resp, data, url)
|
|
177
|
+
except httpx.HTTPError as err:
|
|
178
|
+
raise tr.transport_error(err, call.method, url, self._config.timeout) from err
|
|
179
|
+
|
|
180
|
+
async def _sign(self, payer: Payer, call: Call, url: str, raw: dict[str, Any]) -> str:
|
|
181
|
+
context, choice = prepare(payer, call.operation, call.method, url, raw)
|
|
182
|
+
try:
|
|
183
|
+
signature = payer.sign(context)
|
|
184
|
+
if inspect.isawaitable(signature):
|
|
185
|
+
signature = await signature
|
|
186
|
+
except Exception as err:
|
|
187
|
+
raise AveePaymentError(f"the payer did not sign: {err}", call.operation, raw) from err
|
|
188
|
+
return finish(signature, context, choice, raw)
|
|
189
|
+
|
|
190
|
+
async def _send(self, call: Call, decode: Callable[[Any], T], options: RequestOptions | None) -> T:
|
|
191
|
+
url, body = self._prepare(call)
|
|
192
|
+
signature: str | None = None
|
|
193
|
+
attempt = 0
|
|
194
|
+
while True:
|
|
195
|
+
try:
|
|
196
|
+
status, headers, data = await self._attempt(call, url, body, signature)
|
|
197
|
+
except AveeError as err:
|
|
198
|
+
delay = tr.retry_delay(err, call.method, attempt, signature is not None, self._config)
|
|
199
|
+
if delay is None:
|
|
200
|
+
raise
|
|
201
|
+
await asyncio.sleep(delay)
|
|
202
|
+
attempt += 1
|
|
203
|
+
continue
|
|
204
|
+
if 200 <= status < 300:
|
|
205
|
+
return decode(tr.decode_json(data, call.operation, url))
|
|
206
|
+
if status == 402 and self._config.payer is not None and signature is None:
|
|
207
|
+
signature = await self._sign(self._config.payer, call, url, self._challenge(call, headers, data))
|
|
208
|
+
continue
|
|
209
|
+
api_err = tr.api_error(call.operation, call.method, url, status, headers, data, signature is not None)
|
|
210
|
+
delay = tr.retry_delay(api_err, call.method, attempt, signature is not None, self._config)
|
|
211
|
+
if delay is None:
|
|
212
|
+
raise api_err
|
|
213
|
+
await asyncio.sleep(delay)
|
|
214
|
+
attempt += 1
|
avee/_decode.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Mapping
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
from .errors import AveeValidationError
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
Decoder = Callable[[Any, str], T]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _obj(value: Any, where: str) -> Mapping[str, Any]:
|
|
13
|
+
if not isinstance(value, dict):
|
|
14
|
+
raise AveeValidationError(f"{where} must be an object, got {type(value).__name__}")
|
|
15
|
+
return value
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _req(o: Mapping[str, Any], key: str, fn: Decoder[T], where: str) -> T:
|
|
19
|
+
value = o.get(key)
|
|
20
|
+
if value is None:
|
|
21
|
+
raise AveeValidationError(f"{where} is missing")
|
|
22
|
+
return fn(value, where)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _opt(o: Mapping[str, Any], key: str, fn: Decoder[T], where: str) -> T | None:
|
|
26
|
+
value = o.get(key)
|
|
27
|
+
return None if value is None else fn(value, where)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _str(value: Any, where: str) -> str:
|
|
31
|
+
if not isinstance(value, str):
|
|
32
|
+
raise AveeValidationError(f"{where} must be a string, got {type(value).__name__}")
|
|
33
|
+
return value
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _int(value: Any, where: str) -> int:
|
|
37
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)) or (isinstance(value, float) and not value.is_integer()):
|
|
38
|
+
raise AveeValidationError(f"{where} must be an integer, got {value!r}")
|
|
39
|
+
return int(value)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _float(value: Any, where: str) -> float:
|
|
43
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
44
|
+
raise AveeValidationError(f"{where} must be a number, got {type(value).__name__}")
|
|
45
|
+
return float(value)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _bool(value: Any, where: str) -> bool:
|
|
49
|
+
if not isinstance(value, bool):
|
|
50
|
+
raise AveeValidationError(f"{where} must be a boolean, got {type(value).__name__}")
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _any(value: Any, where: str) -> Any:
|
|
55
|
+
return value
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _list_of(fn: Decoder[T]) -> Decoder[list[T]]:
|
|
59
|
+
def decode(value: Any, where: str) -> list[T]:
|
|
60
|
+
if not isinstance(value, list):
|
|
61
|
+
raise AveeValidationError(f"{where} must be an array, got {type(value).__name__}")
|
|
62
|
+
return [fn(v, f"{where}[{i}]") for i, v in enumerate(value)]
|
|
63
|
+
|
|
64
|
+
return decode
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _map_of(fn: Decoder[T]) -> Decoder[dict[str, T]]:
|
|
68
|
+
def decode(value: Any, where: str) -> dict[str, T]:
|
|
69
|
+
return {k: fn(v, f"{where}.{k}") for k, v in _obj(value, where).items()}
|
|
70
|
+
|
|
71
|
+
return decode
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _one_of(value: Any, where: str, branches: tuple[tuple[Decoder[Any], tuple[str, ...]], ...]) -> Any:
|
|
75
|
+
o = _obj(value, where)
|
|
76
|
+
for fn, required in branches:
|
|
77
|
+
if all(k in o for k in required):
|
|
78
|
+
return fn(value, where)
|
|
79
|
+
return dict(o)
|