agentref 1.0.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.
- agentref/__init__.py +24 -0
- agentref/_http.py +252 -0
- agentref/client.py +83 -0
- agentref/errors.py +62 -0
- agentref/resources/__init__.py +24 -0
- agentref/resources/affiliates.py +122 -0
- agentref/resources/billing.py +66 -0
- agentref/resources/conversions.py +124 -0
- agentref/resources/flags.py +132 -0
- agentref/resources/merchant.py +34 -0
- agentref/resources/payouts.py +150 -0
- agentref/resources/programs.py +320 -0
- agentref/types/__init__.py +39 -0
- agentref/types/models.py +210 -0
- agentref-1.0.0.dist-info/METADATA +101 -0
- agentref-1.0.0.dist-info/RECORD +17 -0
- agentref-1.0.0.dist-info/WHEEL +4 -0
agentref/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .client import AgentRef, AsyncAgentRef
|
|
2
|
+
from .errors import (
|
|
3
|
+
AgentRefError,
|
|
4
|
+
AuthError,
|
|
5
|
+
ConflictError,
|
|
6
|
+
ForbiddenError,
|
|
7
|
+
NotFoundError,
|
|
8
|
+
RateLimitError,
|
|
9
|
+
ServerError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AgentRef",
|
|
15
|
+
"AsyncAgentRef",
|
|
16
|
+
"AgentRefError",
|
|
17
|
+
"AuthError",
|
|
18
|
+
"ForbiddenError",
|
|
19
|
+
"ValidationError",
|
|
20
|
+
"NotFoundError",
|
|
21
|
+
"ConflictError",
|
|
22
|
+
"RateLimitError",
|
|
23
|
+
"ServerError",
|
|
24
|
+
]
|
agentref/_http.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import importlib.metadata
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from email.utils import parsedate_to_datetime
|
|
8
|
+
from typing import Any, Dict, Mapping, Optional, Set, cast
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from .errors import (
|
|
13
|
+
AgentRefError,
|
|
14
|
+
AuthError,
|
|
15
|
+
ConflictError,
|
|
16
|
+
ForbiddenError,
|
|
17
|
+
NotFoundError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
ServerError,
|
|
20
|
+
ValidationError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
_SAFE_METHODS: Set[str] = {"GET", "HEAD"}
|
|
24
|
+
_DEFAULT_BASE_URL = "https://www.agentref.dev/api/v1"
|
|
25
|
+
_DEFAULT_TIMEOUT = 30.0
|
|
26
|
+
_DEFAULT_MAX_RETRIES = 2
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _sdk_version() -> str:
|
|
30
|
+
try:
|
|
31
|
+
return importlib.metadata.version("agentref")
|
|
32
|
+
except importlib.metadata.PackageNotFoundError:
|
|
33
|
+
return "unknown"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _parse_retry_after_seconds(value: Optional[str]) -> int:
|
|
37
|
+
if not value:
|
|
38
|
+
return 60
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
parsed = float(value)
|
|
42
|
+
if parsed >= 0:
|
|
43
|
+
return int(parsed) if parsed.is_integer() else int(parsed) + 1
|
|
44
|
+
except ValueError:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
parsed_date = parsedate_to_datetime(value)
|
|
49
|
+
delta = parsed_date.timestamp() - time.time()
|
|
50
|
+
if delta <= 0:
|
|
51
|
+
return 0
|
|
52
|
+
return int(delta) if float(delta).is_integer() else int(delta) + 1
|
|
53
|
+
except (TypeError, ValueError, OverflowError):
|
|
54
|
+
return 60
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _can_retry(method: str, idempotency_key: Optional[str]) -> bool:
|
|
58
|
+
upper = method.upper()
|
|
59
|
+
return upper in _SAFE_METHODS or (upper == "POST" and idempotency_key is not None)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _json_object(response: httpx.Response) -> Dict[str, Any]:
|
|
63
|
+
try:
|
|
64
|
+
payload = response.json()
|
|
65
|
+
except ValueError:
|
|
66
|
+
return {}
|
|
67
|
+
|
|
68
|
+
if isinstance(payload, dict):
|
|
69
|
+
return cast(Dict[str, Any], payload)
|
|
70
|
+
|
|
71
|
+
return {}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _BaseHttpClient:
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
*,
|
|
78
|
+
api_key: Optional[str] = None,
|
|
79
|
+
base_url: str = _DEFAULT_BASE_URL,
|
|
80
|
+
timeout: float = _DEFAULT_TIMEOUT,
|
|
81
|
+
max_retries: int = _DEFAULT_MAX_RETRIES,
|
|
82
|
+
) -> None:
|
|
83
|
+
resolved_key = api_key or os.environ.get("AGENTREF_API_KEY")
|
|
84
|
+
if not resolved_key:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
"[AgentRef] API key is required. Pass it as api_key or set AGENTREF_API_KEY environment variable."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
self._api_key = resolved_key
|
|
90
|
+
self._base_url = base_url.rstrip("/")
|
|
91
|
+
self._timeout = timeout
|
|
92
|
+
self._max_retries = max_retries
|
|
93
|
+
self._user_agent = f"agentref-python/{_sdk_version()}"
|
|
94
|
+
|
|
95
|
+
def _headers(self, method: str, idempotency_key: Optional[str]) -> Dict[str, str]:
|
|
96
|
+
headers: Dict[str, str] = {
|
|
97
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
"User-Agent": self._user_agent,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if method.upper() == "POST" and idempotency_key is not None:
|
|
103
|
+
headers["Idempotency-Key"] = idempotency_key
|
|
104
|
+
|
|
105
|
+
return headers
|
|
106
|
+
|
|
107
|
+
def _parse_error(self, response: httpx.Response) -> AgentRefError:
|
|
108
|
+
payload = _json_object(response)
|
|
109
|
+
raw_error = payload.get("error")
|
|
110
|
+
raw_meta = payload.get("meta")
|
|
111
|
+
|
|
112
|
+
error = raw_error if isinstance(raw_error, dict) else {}
|
|
113
|
+
meta = raw_meta if isinstance(raw_meta, dict) else {}
|
|
114
|
+
|
|
115
|
+
code = cast(str, error.get("code", "UNKNOWN_ERROR"))
|
|
116
|
+
message = cast(str, error.get("message", response.reason_phrase))
|
|
117
|
+
request_id = cast(str, meta.get("requestId", ""))
|
|
118
|
+
details = error.get("details")
|
|
119
|
+
|
|
120
|
+
if response.status_code == 400:
|
|
121
|
+
return ValidationError(message, code, request_id, details)
|
|
122
|
+
if response.status_code == 401:
|
|
123
|
+
return AuthError(message, code, request_id)
|
|
124
|
+
if response.status_code == 403:
|
|
125
|
+
return ForbiddenError(message, code, request_id)
|
|
126
|
+
if response.status_code == 404:
|
|
127
|
+
return NotFoundError(message, code, request_id)
|
|
128
|
+
if response.status_code == 409:
|
|
129
|
+
return ConflictError(message, code, request_id)
|
|
130
|
+
if response.status_code == 429:
|
|
131
|
+
retry_after = _parse_retry_after_seconds(response.headers.get("Retry-After"))
|
|
132
|
+
return RateLimitError(message, code, request_id, retry_after)
|
|
133
|
+
|
|
134
|
+
return ServerError(message, code, response.status_code, request_id)
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def _is_retryable(status: int) -> bool:
|
|
138
|
+
return status == 429 or status >= 500
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def _backoff_seconds(attempt: int) -> float:
|
|
142
|
+
return float(0.5 * (2 ** attempt))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class SyncHttpClient(_BaseHttpClient):
|
|
146
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
147
|
+
super().__init__(**kwargs)
|
|
148
|
+
self._client = httpx.Client(base_url=self._base_url, timeout=self._timeout)
|
|
149
|
+
|
|
150
|
+
def close(self) -> None:
|
|
151
|
+
self._client.close()
|
|
152
|
+
|
|
153
|
+
def request(
|
|
154
|
+
self,
|
|
155
|
+
method: str,
|
|
156
|
+
path: str,
|
|
157
|
+
*,
|
|
158
|
+
params: Optional[Mapping[str, Any]] = None,
|
|
159
|
+
json: Optional[Dict[str, Any]] = None,
|
|
160
|
+
idempotency_key: Optional[str] = None,
|
|
161
|
+
) -> Dict[str, Any]:
|
|
162
|
+
can_retry = _can_retry(method, idempotency_key)
|
|
163
|
+
attempts = self._max_retries + 1 if can_retry else 1
|
|
164
|
+
|
|
165
|
+
for attempt in range(attempts):
|
|
166
|
+
try:
|
|
167
|
+
response = self._client.request(
|
|
168
|
+
method,
|
|
169
|
+
path,
|
|
170
|
+
params={k: v for k, v in (params or {}).items() if v is not None},
|
|
171
|
+
json=json,
|
|
172
|
+
headers=self._headers(method, idempotency_key),
|
|
173
|
+
)
|
|
174
|
+
except httpx.HTTPError:
|
|
175
|
+
if can_retry and attempt < attempts - 1:
|
|
176
|
+
time.sleep(self._backoff_seconds(attempt))
|
|
177
|
+
continue
|
|
178
|
+
raise
|
|
179
|
+
|
|
180
|
+
if response.is_error:
|
|
181
|
+
parsed = self._parse_error(response)
|
|
182
|
+
if can_retry and self._is_retryable(response.status_code) and attempt < attempts - 1:
|
|
183
|
+
delay = (
|
|
184
|
+
float(_parse_retry_after_seconds(response.headers.get("Retry-After")))
|
|
185
|
+
if response.status_code == 429
|
|
186
|
+
else self._backoff_seconds(attempt)
|
|
187
|
+
)
|
|
188
|
+
time.sleep(delay)
|
|
189
|
+
continue
|
|
190
|
+
raise parsed
|
|
191
|
+
|
|
192
|
+
return _json_object(response)
|
|
193
|
+
|
|
194
|
+
raise ServerError("Request failed after retries", "REQUEST_RETRY_EXHAUSTED", 500, "")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class AsyncHttpClient(_BaseHttpClient):
|
|
198
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
199
|
+
super().__init__(**kwargs)
|
|
200
|
+
self._client = httpx.AsyncClient(base_url=self._base_url, timeout=self._timeout)
|
|
201
|
+
|
|
202
|
+
async def __aenter__(self) -> "AsyncHttpClient":
|
|
203
|
+
return self
|
|
204
|
+
|
|
205
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
206
|
+
await self._client.aclose()
|
|
207
|
+
|
|
208
|
+
async def aclose(self) -> None:
|
|
209
|
+
await self._client.aclose()
|
|
210
|
+
|
|
211
|
+
async def request(
|
|
212
|
+
self,
|
|
213
|
+
method: str,
|
|
214
|
+
path: str,
|
|
215
|
+
*,
|
|
216
|
+
params: Optional[Mapping[str, Any]] = None,
|
|
217
|
+
json: Optional[Dict[str, Any]] = None,
|
|
218
|
+
idempotency_key: Optional[str] = None,
|
|
219
|
+
) -> Dict[str, Any]:
|
|
220
|
+
can_retry = _can_retry(method, idempotency_key)
|
|
221
|
+
attempts = self._max_retries + 1 if can_retry else 1
|
|
222
|
+
|
|
223
|
+
for attempt in range(attempts):
|
|
224
|
+
try:
|
|
225
|
+
response = await self._client.request(
|
|
226
|
+
method,
|
|
227
|
+
path,
|
|
228
|
+
params={k: v for k, v in (params or {}).items() if v is not None},
|
|
229
|
+
json=json,
|
|
230
|
+
headers=self._headers(method, idempotency_key),
|
|
231
|
+
)
|
|
232
|
+
except httpx.HTTPError:
|
|
233
|
+
if can_retry and attempt < attempts - 1:
|
|
234
|
+
await asyncio.sleep(self._backoff_seconds(attempt))
|
|
235
|
+
continue
|
|
236
|
+
raise
|
|
237
|
+
|
|
238
|
+
if response.is_error:
|
|
239
|
+
parsed = self._parse_error(response)
|
|
240
|
+
if can_retry and self._is_retryable(response.status_code) and attempt < attempts - 1:
|
|
241
|
+
delay = (
|
|
242
|
+
float(_parse_retry_after_seconds(response.headers.get("Retry-After")))
|
|
243
|
+
if response.status_code == 429
|
|
244
|
+
else self._backoff_seconds(attempt)
|
|
245
|
+
)
|
|
246
|
+
await asyncio.sleep(delay)
|
|
247
|
+
continue
|
|
248
|
+
raise parsed
|
|
249
|
+
|
|
250
|
+
return _json_object(response)
|
|
251
|
+
|
|
252
|
+
raise ServerError("Request failed after retries", "REQUEST_RETRY_EXHAUSTED", 500, "")
|
agentref/client.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from ._http import AsyncHttpClient, SyncHttpClient
|
|
6
|
+
from .resources import (
|
|
7
|
+
AffiliatesResource,
|
|
8
|
+
AsyncAffiliatesResource,
|
|
9
|
+
AsyncBillingResource,
|
|
10
|
+
AsyncConversionsResource,
|
|
11
|
+
AsyncFlagsResource,
|
|
12
|
+
AsyncMerchantResource,
|
|
13
|
+
AsyncPayoutsResource,
|
|
14
|
+
AsyncProgramsResource,
|
|
15
|
+
BillingResource,
|
|
16
|
+
ConversionsResource,
|
|
17
|
+
FlagsResource,
|
|
18
|
+
MerchantResource,
|
|
19
|
+
PayoutsResource,
|
|
20
|
+
ProgramsResource,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AgentRef:
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
api_key: Optional[str] = None,
|
|
28
|
+
*,
|
|
29
|
+
base_url: str = "https://www.agentref.dev/api/v1",
|
|
30
|
+
timeout: float = 30.0,
|
|
31
|
+
max_retries: int = 2,
|
|
32
|
+
) -> None:
|
|
33
|
+
self._http = SyncHttpClient(
|
|
34
|
+
api_key=api_key,
|
|
35
|
+
base_url=base_url,
|
|
36
|
+
timeout=timeout,
|
|
37
|
+
max_retries=max_retries,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
self.programs = ProgramsResource(self._http)
|
|
41
|
+
self.affiliates = AffiliatesResource(self._http)
|
|
42
|
+
self.conversions = ConversionsResource(self._http)
|
|
43
|
+
self.payouts = PayoutsResource(self._http)
|
|
44
|
+
self.flags = FlagsResource(self._http)
|
|
45
|
+
self.billing = BillingResource(self._http)
|
|
46
|
+
self.merchant = MerchantResource(self._http)
|
|
47
|
+
|
|
48
|
+
def close(self) -> None:
|
|
49
|
+
self._http.close()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AsyncAgentRef:
|
|
53
|
+
"""Async variant — use as async context manager for connection pooling."""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
api_key: Optional[str] = None,
|
|
58
|
+
*,
|
|
59
|
+
base_url: str = "https://www.agentref.dev/api/v1",
|
|
60
|
+
timeout: float = 30.0,
|
|
61
|
+
max_retries: int = 2,
|
|
62
|
+
) -> None:
|
|
63
|
+
self._http = AsyncHttpClient(
|
|
64
|
+
api_key=api_key,
|
|
65
|
+
base_url=base_url,
|
|
66
|
+
timeout=timeout,
|
|
67
|
+
max_retries=max_retries,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self.programs = AsyncProgramsResource(self._http)
|
|
71
|
+
self.affiliates = AsyncAffiliatesResource(self._http)
|
|
72
|
+
self.conversions = AsyncConversionsResource(self._http)
|
|
73
|
+
self.payouts = AsyncPayoutsResource(self._http)
|
|
74
|
+
self.flags = AsyncFlagsResource(self._http)
|
|
75
|
+
self.billing = AsyncBillingResource(self._http)
|
|
76
|
+
self.merchant = AsyncMerchantResource(self._http)
|
|
77
|
+
|
|
78
|
+
async def __aenter__(self) -> "AsyncAgentRef":
|
|
79
|
+
await self._http.__aenter__()
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
83
|
+
await self._http.__aexit__(*args)
|
agentref/errors.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AgentRefError(Exception):
|
|
5
|
+
code: str
|
|
6
|
+
status: int
|
|
7
|
+
request_id: str
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, code: str, status: int, request_id: str) -> None:
|
|
10
|
+
super().__init__(message)
|
|
11
|
+
self.code = code
|
|
12
|
+
self.status = status
|
|
13
|
+
self.request_id = request_id
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AuthError(AgentRefError):
|
|
17
|
+
def __init__(self, message: str, code: str, request_id: str) -> None:
|
|
18
|
+
super().__init__(message, code, 401, request_id)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ForbiddenError(AgentRefError):
|
|
22
|
+
"""403 — authenticated but not authorized: wrong scope, ownerType, or trust level."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, message: str, code: str, request_id: str) -> None:
|
|
25
|
+
super().__init__(message, code, 403, request_id)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ValidationError(AgentRefError):
|
|
29
|
+
details: Optional[object]
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
message: str,
|
|
34
|
+
code: str,
|
|
35
|
+
request_id: str,
|
|
36
|
+
details: Optional[object] = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__(message, code, 400, request_id)
|
|
39
|
+
self.details = details
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class NotFoundError(AgentRefError):
|
|
43
|
+
def __init__(self, message: str, code: str, request_id: str) -> None:
|
|
44
|
+
super().__init__(message, code, 404, request_id)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ConflictError(AgentRefError):
|
|
48
|
+
def __init__(self, message: str, code: str, request_id: str) -> None:
|
|
49
|
+
super().__init__(message, code, 409, request_id)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RateLimitError(AgentRefError):
|
|
53
|
+
retry_after: int
|
|
54
|
+
|
|
55
|
+
def __init__(self, message: str, code: str, request_id: str, retry_after: int) -> None:
|
|
56
|
+
super().__init__(message, code, 429, request_id)
|
|
57
|
+
self.retry_after = retry_after
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ServerError(AgentRefError):
|
|
61
|
+
def __init__(self, message: str, code: str, status: int, request_id: str) -> None:
|
|
62
|
+
super().__init__(message, code, status, request_id)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .affiliates import AffiliatesResource, AsyncAffiliatesResource
|
|
2
|
+
from .billing import AsyncBillingResource, BillingResource
|
|
3
|
+
from .conversions import AsyncConversionsResource, ConversionsResource
|
|
4
|
+
from .flags import AsyncFlagsResource, FlagsResource
|
|
5
|
+
from .merchant import AsyncMerchantResource, MerchantResource
|
|
6
|
+
from .payouts import AsyncPayoutsResource, PayoutsResource
|
|
7
|
+
from .programs import AsyncProgramsResource, ProgramsResource
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"ProgramsResource",
|
|
11
|
+
"AffiliatesResource",
|
|
12
|
+
"ConversionsResource",
|
|
13
|
+
"PayoutsResource",
|
|
14
|
+
"FlagsResource",
|
|
15
|
+
"BillingResource",
|
|
16
|
+
"MerchantResource",
|
|
17
|
+
"AsyncProgramsResource",
|
|
18
|
+
"AsyncAffiliatesResource",
|
|
19
|
+
"AsyncConversionsResource",
|
|
20
|
+
"AsyncPayoutsResource",
|
|
21
|
+
"AsyncFlagsResource",
|
|
22
|
+
"AsyncBillingResource",
|
|
23
|
+
"AsyncMerchantResource",
|
|
24
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from .._http import AsyncHttpClient, SyncHttpClient
|
|
6
|
+
from ..types.models import Affiliate, PaginatedResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AffiliatesResource:
|
|
10
|
+
def __init__(self, http: SyncHttpClient) -> None:
|
|
11
|
+
self._http = http
|
|
12
|
+
|
|
13
|
+
def list(
|
|
14
|
+
self,
|
|
15
|
+
*,
|
|
16
|
+
program_id: Optional[str] = None,
|
|
17
|
+
include_blocked: Optional[bool] = None,
|
|
18
|
+
cursor: Optional[str] = None,
|
|
19
|
+
limit: Optional[int] = None,
|
|
20
|
+
page: Optional[int] = None,
|
|
21
|
+
page_size: Optional[int] = None,
|
|
22
|
+
offset: Optional[int] = None,
|
|
23
|
+
) -> PaginatedResponse[Affiliate]:
|
|
24
|
+
envelope = self._http.request(
|
|
25
|
+
"GET",
|
|
26
|
+
"/affiliates",
|
|
27
|
+
params={
|
|
28
|
+
"programId": program_id,
|
|
29
|
+
"includeBlocked": include_blocked,
|
|
30
|
+
"cursor": cursor,
|
|
31
|
+
"limit": limit,
|
|
32
|
+
"page": page,
|
|
33
|
+
"pageSize": page_size,
|
|
34
|
+
"offset": offset,
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
return PaginatedResponse[Affiliate].model_validate(envelope)
|
|
38
|
+
|
|
39
|
+
def get(self, id: str) -> Affiliate:
|
|
40
|
+
envelope = self._http.request("GET", f"/affiliates/{id}")
|
|
41
|
+
return Affiliate.model_validate(envelope["data"])
|
|
42
|
+
|
|
43
|
+
def approve(self, id: str, *, idempotency_key: Optional[str] = None) -> Affiliate:
|
|
44
|
+
envelope = self._http.request("POST", f"/affiliates/{id}/approve", idempotency_key=idempotency_key)
|
|
45
|
+
return Affiliate.model_validate(envelope["data"])
|
|
46
|
+
|
|
47
|
+
def block(
|
|
48
|
+
self,
|
|
49
|
+
id: str,
|
|
50
|
+
*,
|
|
51
|
+
reason: Optional[str] = None,
|
|
52
|
+
idempotency_key: Optional[str] = None,
|
|
53
|
+
) -> Affiliate:
|
|
54
|
+
envelope = self._http.request(
|
|
55
|
+
"POST",
|
|
56
|
+
f"/affiliates/{id}/block",
|
|
57
|
+
json={"reason": reason} if reason is not None else None,
|
|
58
|
+
idempotency_key=idempotency_key,
|
|
59
|
+
)
|
|
60
|
+
return Affiliate.model_validate(envelope["data"])
|
|
61
|
+
|
|
62
|
+
def unblock(self, id: str, *, idempotency_key: Optional[str] = None) -> Affiliate:
|
|
63
|
+
envelope = self._http.request("POST", f"/affiliates/{id}/unblock", idempotency_key=idempotency_key)
|
|
64
|
+
return Affiliate.model_validate(envelope["data"])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class AsyncAffiliatesResource:
|
|
68
|
+
def __init__(self, http: AsyncHttpClient) -> None:
|
|
69
|
+
self._http = http
|
|
70
|
+
|
|
71
|
+
async def list(
|
|
72
|
+
self,
|
|
73
|
+
*,
|
|
74
|
+
program_id: Optional[str] = None,
|
|
75
|
+
include_blocked: Optional[bool] = None,
|
|
76
|
+
cursor: Optional[str] = None,
|
|
77
|
+
limit: Optional[int] = None,
|
|
78
|
+
page: Optional[int] = None,
|
|
79
|
+
page_size: Optional[int] = None,
|
|
80
|
+
offset: Optional[int] = None,
|
|
81
|
+
) -> PaginatedResponse[Affiliate]:
|
|
82
|
+
envelope = await self._http.request(
|
|
83
|
+
"GET",
|
|
84
|
+
"/affiliates",
|
|
85
|
+
params={
|
|
86
|
+
"programId": program_id,
|
|
87
|
+
"includeBlocked": include_blocked,
|
|
88
|
+
"cursor": cursor,
|
|
89
|
+
"limit": limit,
|
|
90
|
+
"page": page,
|
|
91
|
+
"pageSize": page_size,
|
|
92
|
+
"offset": offset,
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
return PaginatedResponse[Affiliate].model_validate(envelope)
|
|
96
|
+
|
|
97
|
+
async def get(self, id: str) -> Affiliate:
|
|
98
|
+
envelope = await self._http.request("GET", f"/affiliates/{id}")
|
|
99
|
+
return Affiliate.model_validate(envelope["data"])
|
|
100
|
+
|
|
101
|
+
async def approve(self, id: str, *, idempotency_key: Optional[str] = None) -> Affiliate:
|
|
102
|
+
envelope = await self._http.request("POST", f"/affiliates/{id}/approve", idempotency_key=idempotency_key)
|
|
103
|
+
return Affiliate.model_validate(envelope["data"])
|
|
104
|
+
|
|
105
|
+
async def block(
|
|
106
|
+
self,
|
|
107
|
+
id: str,
|
|
108
|
+
*,
|
|
109
|
+
reason: Optional[str] = None,
|
|
110
|
+
idempotency_key: Optional[str] = None,
|
|
111
|
+
) -> Affiliate:
|
|
112
|
+
envelope = await self._http.request(
|
|
113
|
+
"POST",
|
|
114
|
+
f"/affiliates/{id}/block",
|
|
115
|
+
json={"reason": reason} if reason is not None else None,
|
|
116
|
+
idempotency_key=idempotency_key,
|
|
117
|
+
)
|
|
118
|
+
return Affiliate.model_validate(envelope["data"])
|
|
119
|
+
|
|
120
|
+
async def unblock(self, id: str, *, idempotency_key: Optional[str] = None) -> Affiliate:
|
|
121
|
+
envelope = await self._http.request("POST", f"/affiliates/{id}/unblock", idempotency_key=idempotency_key)
|
|
122
|
+
return Affiliate.model_validate(envelope["data"])
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Literal, Optional
|
|
4
|
+
|
|
5
|
+
from .._http import AsyncHttpClient, SyncHttpClient
|
|
6
|
+
from ..types.models import BillingStatus, BillingTier
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BillingResource:
|
|
10
|
+
def __init__(self, http: SyncHttpClient) -> None:
|
|
11
|
+
self._http = http
|
|
12
|
+
|
|
13
|
+
def current(self) -> BillingStatus:
|
|
14
|
+
envelope = self._http.request("GET", "/billing")
|
|
15
|
+
return BillingStatus.model_validate(envelope["data"])
|
|
16
|
+
|
|
17
|
+
def tiers(self) -> List[BillingTier]:
|
|
18
|
+
envelope = self._http.request("GET", "/billing/tiers")
|
|
19
|
+
raw = envelope.get("data", [])
|
|
20
|
+
if not isinstance(raw, list):
|
|
21
|
+
return []
|
|
22
|
+
return [BillingTier.model_validate(item) for item in raw]
|
|
23
|
+
|
|
24
|
+
def subscribe(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
tier: Literal["starter", "growth", "pro", "scale"],
|
|
28
|
+
idempotency_key: Optional[str] = None,
|
|
29
|
+
) -> BillingStatus:
|
|
30
|
+
envelope = self._http.request(
|
|
31
|
+
"POST",
|
|
32
|
+
"/billing/subscribe",
|
|
33
|
+
json={"tier": tier},
|
|
34
|
+
idempotency_key=idempotency_key,
|
|
35
|
+
)
|
|
36
|
+
return BillingStatus.model_validate(envelope["data"])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AsyncBillingResource:
|
|
40
|
+
def __init__(self, http: AsyncHttpClient) -> None:
|
|
41
|
+
self._http = http
|
|
42
|
+
|
|
43
|
+
async def current(self) -> BillingStatus:
|
|
44
|
+
envelope = await self._http.request("GET", "/billing")
|
|
45
|
+
return BillingStatus.model_validate(envelope["data"])
|
|
46
|
+
|
|
47
|
+
async def tiers(self) -> List[BillingTier]:
|
|
48
|
+
envelope = await self._http.request("GET", "/billing/tiers")
|
|
49
|
+
raw = envelope.get("data", [])
|
|
50
|
+
if not isinstance(raw, list):
|
|
51
|
+
return []
|
|
52
|
+
return [BillingTier.model_validate(item) for item in raw]
|
|
53
|
+
|
|
54
|
+
async def subscribe(
|
|
55
|
+
self,
|
|
56
|
+
*,
|
|
57
|
+
tier: Literal["starter", "growth", "pro", "scale"],
|
|
58
|
+
idempotency_key: Optional[str] = None,
|
|
59
|
+
) -> BillingStatus:
|
|
60
|
+
envelope = await self._http.request(
|
|
61
|
+
"POST",
|
|
62
|
+
"/billing/subscribe",
|
|
63
|
+
json={"tier": tier},
|
|
64
|
+
idempotency_key=idempotency_key,
|
|
65
|
+
)
|
|
66
|
+
return BillingStatus.model_validate(envelope["data"])
|