sdkrouter 0.1.1__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.
- sdkrouter/__init__.py +110 -0
- sdkrouter/_api/__init__.py +28 -0
- sdkrouter/_api/client.py +204 -0
- sdkrouter/_api/generated/__init__.py +21 -0
- sdkrouter/_api/generated/cdn/__init__.py +209 -0
- sdkrouter/_api/generated/cdn/cdn__api__cdn/__init__.py +7 -0
- sdkrouter/_api/generated/cdn/cdn__api__cdn/client.py +133 -0
- sdkrouter/_api/generated/cdn/cdn__api__cdn/models.py +163 -0
- sdkrouter/_api/generated/cdn/cdn__api__cdn/sync_client.py +132 -0
- sdkrouter/_api/generated/cdn/client.py +75 -0
- sdkrouter/_api/generated/cdn/logger.py +256 -0
- sdkrouter/_api/generated/cdn/pyproject.toml +55 -0
- sdkrouter/_api/generated/cdn/retry.py +272 -0
- sdkrouter/_api/generated/cdn/sync_client.py +58 -0
- sdkrouter/_api/generated/cleaner/__init__.py +212 -0
- sdkrouter/_api/generated/cleaner/cleaner__api__cleaner/__init__.py +7 -0
- sdkrouter/_api/generated/cleaner/cleaner__api__cleaner/client.py +83 -0
- sdkrouter/_api/generated/cleaner/cleaner__api__cleaner/models.py +117 -0
- sdkrouter/_api/generated/cleaner/cleaner__api__cleaner/sync_client.py +82 -0
- sdkrouter/_api/generated/cleaner/client.py +75 -0
- sdkrouter/_api/generated/cleaner/enums.py +55 -0
- sdkrouter/_api/generated/cleaner/logger.py +256 -0
- sdkrouter/_api/generated/cleaner/pyproject.toml +55 -0
- sdkrouter/_api/generated/cleaner/retry.py +272 -0
- sdkrouter/_api/generated/cleaner/sync_client.py +58 -0
- sdkrouter/_api/generated/keys/__init__.py +212 -0
- sdkrouter/_api/generated/keys/client.py +75 -0
- sdkrouter/_api/generated/keys/enums.py +64 -0
- sdkrouter/_api/generated/keys/keys__api__keys/__init__.py +7 -0
- sdkrouter/_api/generated/keys/keys__api__keys/client.py +150 -0
- sdkrouter/_api/generated/keys/keys__api__keys/models.py +152 -0
- sdkrouter/_api/generated/keys/keys__api__keys/sync_client.py +149 -0
- sdkrouter/_api/generated/keys/logger.py +256 -0
- sdkrouter/_api/generated/keys/pyproject.toml +55 -0
- sdkrouter/_api/generated/keys/retry.py +272 -0
- sdkrouter/_api/generated/keys/sync_client.py +58 -0
- sdkrouter/_api/generated/models/__init__.py +209 -0
- sdkrouter/_api/generated/models/client.py +75 -0
- sdkrouter/_api/generated/models/logger.py +256 -0
- sdkrouter/_api/generated/models/models__api__llm_models/__init__.py +7 -0
- sdkrouter/_api/generated/models/models__api__llm_models/client.py +99 -0
- sdkrouter/_api/generated/models/models__api__llm_models/models.py +206 -0
- sdkrouter/_api/generated/models/models__api__llm_models/sync_client.py +99 -0
- sdkrouter/_api/generated/models/pyproject.toml +55 -0
- sdkrouter/_api/generated/models/retry.py +272 -0
- sdkrouter/_api/generated/models/sync_client.py +58 -0
- sdkrouter/_api/generated/shortlinks/__init__.py +209 -0
- sdkrouter/_api/generated/shortlinks/client.py +75 -0
- sdkrouter/_api/generated/shortlinks/logger.py +256 -0
- sdkrouter/_api/generated/shortlinks/pyproject.toml +55 -0
- sdkrouter/_api/generated/shortlinks/retry.py +272 -0
- sdkrouter/_api/generated/shortlinks/shortlinks__api__shortlinks/__init__.py +7 -0
- sdkrouter/_api/generated/shortlinks/shortlinks__api__shortlinks/client.py +137 -0
- sdkrouter/_api/generated/shortlinks/shortlinks__api__shortlinks/models.py +153 -0
- sdkrouter/_api/generated/shortlinks/shortlinks__api__shortlinks/sync_client.py +136 -0
- sdkrouter/_api/generated/shortlinks/sync_client.py +58 -0
- sdkrouter/_api/generated/vision/__init__.py +212 -0
- sdkrouter/_api/generated/vision/client.py +75 -0
- sdkrouter/_api/generated/vision/enums.py +40 -0
- sdkrouter/_api/generated/vision/logger.py +256 -0
- sdkrouter/_api/generated/vision/pyproject.toml +55 -0
- sdkrouter/_api/generated/vision/retry.py +272 -0
- sdkrouter/_api/generated/vision/sync_client.py +58 -0
- sdkrouter/_api/generated/vision/vision__api__vision/__init__.py +7 -0
- sdkrouter/_api/generated/vision/vision__api__vision/client.py +65 -0
- sdkrouter/_api/generated/vision/vision__api__vision/models.py +138 -0
- sdkrouter/_api/generated/vision/vision__api__vision/sync_client.py +65 -0
- sdkrouter/_client.py +432 -0
- sdkrouter/_config.py +74 -0
- sdkrouter/_constants.py +21 -0
- sdkrouter/_internal/__init__.py +1 -0
- sdkrouter/_types/__init__.py +30 -0
- sdkrouter/_types/cdn.py +27 -0
- sdkrouter/_types/models.py +26 -0
- sdkrouter/_types/ocr.py +24 -0
- sdkrouter/_types/parsed.py +101 -0
- sdkrouter/_types/shortlinks.py +27 -0
- sdkrouter/_types/vision.py +29 -0
- sdkrouter/_version.py +3 -0
- sdkrouter/helpers/__init__.py +13 -0
- sdkrouter/helpers/formatting.py +15 -0
- sdkrouter/helpers/html.py +100 -0
- sdkrouter/helpers/json_cleaner.py +53 -0
- sdkrouter/tools/__init__.py +129 -0
- sdkrouter/tools/cdn.py +285 -0
- sdkrouter/tools/cleaner.py +186 -0
- sdkrouter/tools/keys.py +215 -0
- sdkrouter/tools/models.py +196 -0
- sdkrouter/tools/shortlinks.py +165 -0
- sdkrouter/tools/vision.py +173 -0
- sdkrouter/utils/__init__.py +27 -0
- sdkrouter/utils/parsing.py +109 -0
- sdkrouter/utils/tokens.py +375 -0
- sdkrouter-0.1.1.dist-info/METADATA +411 -0
- sdkrouter-0.1.1.dist-info/RECORD +96 -0
- sdkrouter-0.1.1.dist-info/WHEEL +4 -0
sdkrouter/tools/cdn.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""CDN file storage tool using generated API client."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, BinaryIO, Union
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from .._config import SDKConfig
|
|
9
|
+
from .._api.client import (
|
|
10
|
+
BaseResource,
|
|
11
|
+
AsyncBaseResource,
|
|
12
|
+
SyncCdnCdnAPI,
|
|
13
|
+
CdnCdnAPI,
|
|
14
|
+
)
|
|
15
|
+
from .._api.generated.cdn.cdn__api__cdn.models import (
|
|
16
|
+
CDNFileList,
|
|
17
|
+
CDNFileDetail,
|
|
18
|
+
CDNFileUpload,
|
|
19
|
+
CDNFileUploadRequest,
|
|
20
|
+
PaginatedCDNFileListList,
|
|
21
|
+
CDNStats,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CDNUploadResponse:
|
|
26
|
+
"""Response from CDN upload - can be immediate result or async job."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, data: dict, is_async: bool = False):
|
|
29
|
+
self._data = data
|
|
30
|
+
self.is_async = is_async
|
|
31
|
+
|
|
32
|
+
if is_async:
|
|
33
|
+
self.status = data.get("status")
|
|
34
|
+
self.job_id = data.get("job_id")
|
|
35
|
+
self.message = data.get("message")
|
|
36
|
+
self.uuid = None
|
|
37
|
+
self.filename = None
|
|
38
|
+
self.url = None
|
|
39
|
+
else:
|
|
40
|
+
self.uuid = data.get("uuid")
|
|
41
|
+
self.filename = data.get("filename")
|
|
42
|
+
self.url = data.get("url")
|
|
43
|
+
self.short_url = data.get("short_url")
|
|
44
|
+
self.content_type = data.get("content_type")
|
|
45
|
+
self.size_bytes = data.get("size_bytes")
|
|
46
|
+
self.status = "completed"
|
|
47
|
+
self.job_id = None
|
|
48
|
+
self.message = None
|
|
49
|
+
|
|
50
|
+
def __repr__(self):
|
|
51
|
+
if self.is_async:
|
|
52
|
+
return f"CDNUploadResponse(status={self.status}, job_id={self.job_id})"
|
|
53
|
+
return f"CDNUploadResponse(uuid={self.uuid}, filename={self.filename})"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CDNResource(BaseResource):
|
|
57
|
+
"""CDN file storage tool (sync).
|
|
58
|
+
|
|
59
|
+
Uses generated SyncCdnCdnAPI client.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, config: SDKConfig):
|
|
63
|
+
super().__init__(config)
|
|
64
|
+
self._api = SyncCdnCdnAPI(self._http_client)
|
|
65
|
+
|
|
66
|
+
def upload(
|
|
67
|
+
self,
|
|
68
|
+
file: Path | BinaryIO | bytes | None = None,
|
|
69
|
+
*,
|
|
70
|
+
url: Optional[str] = None,
|
|
71
|
+
filename: Optional[str] = None,
|
|
72
|
+
ttl: Optional[str] = None,
|
|
73
|
+
is_public: bool = True,
|
|
74
|
+
metadata: Optional[dict] = None,
|
|
75
|
+
) -> Union[CDNFileDetail, CDNUploadResponse]:
|
|
76
|
+
"""
|
|
77
|
+
Upload a file to CDN.
|
|
78
|
+
|
|
79
|
+
Supports two modes:
|
|
80
|
+
- file: Direct upload from Path, bytes, or file-like object
|
|
81
|
+
- url: Server downloads from URL (may be processed async)
|
|
82
|
+
|
|
83
|
+
Note: File upload requires multipart/form-data, so we use httpx directly.
|
|
84
|
+
"""
|
|
85
|
+
if not file and not url:
|
|
86
|
+
raise ValueError("Either 'file' or 'url' must be provided")
|
|
87
|
+
if file and url:
|
|
88
|
+
raise ValueError("Provide either 'file' or 'url', not both")
|
|
89
|
+
|
|
90
|
+
data = {"is_public": str(is_public).lower()}
|
|
91
|
+
if ttl:
|
|
92
|
+
data["ttl"] = ttl
|
|
93
|
+
if metadata:
|
|
94
|
+
import json
|
|
95
|
+
data["metadata"] = json.dumps(metadata)
|
|
96
|
+
|
|
97
|
+
if url:
|
|
98
|
+
# URL mode - use httpx directly since response varies (async job vs file)
|
|
99
|
+
json_data = {"url": url, "is_public": is_public}
|
|
100
|
+
if filename is not None:
|
|
101
|
+
json_data["filename"] = filename
|
|
102
|
+
if ttl is not None:
|
|
103
|
+
json_data["ttl"] = ttl
|
|
104
|
+
if metadata is not None:
|
|
105
|
+
json_data["metadata"] = metadata
|
|
106
|
+
|
|
107
|
+
response = self._http_client.post("/api/cdn/", json=json_data)
|
|
108
|
+
response.raise_for_status()
|
|
109
|
+
response_data = response.json()
|
|
110
|
+
|
|
111
|
+
# Check if async processing
|
|
112
|
+
if response.status_code == 202 or "job_id" in response_data or response_data.get("status") == "processing":
|
|
113
|
+
return CDNUploadResponse(response_data, is_async=True)
|
|
114
|
+
return CDNFileDetail.model_validate(response_data)
|
|
115
|
+
else:
|
|
116
|
+
# File upload mode - use httpx directly (multipart)
|
|
117
|
+
if isinstance(file, Path):
|
|
118
|
+
filename = filename or file.name
|
|
119
|
+
with open(file, "rb") as f:
|
|
120
|
+
content = f.read()
|
|
121
|
+
elif isinstance(file, bytes):
|
|
122
|
+
content = file
|
|
123
|
+
if not filename:
|
|
124
|
+
raise ValueError("filename required when uploading bytes")
|
|
125
|
+
else:
|
|
126
|
+
# BinaryIO
|
|
127
|
+
content = file.read()
|
|
128
|
+
filename = filename or getattr(file, "name", "file")
|
|
129
|
+
|
|
130
|
+
files = {"file": (filename, content)}
|
|
131
|
+
response = self._http_client.post("/api/cdn/", files=files, data=data)
|
|
132
|
+
|
|
133
|
+
response.raise_for_status()
|
|
134
|
+
response_data = response.json()
|
|
135
|
+
|
|
136
|
+
if response.status_code == 202 or "job_id" in response_data:
|
|
137
|
+
return CDNUploadResponse(response_data, is_async=True)
|
|
138
|
+
|
|
139
|
+
return CDNFileDetail.model_validate(response_data)
|
|
140
|
+
|
|
141
|
+
def get(self, uuid: str) -> CDNFileDetail:
|
|
142
|
+
"""Get file details by UUID."""
|
|
143
|
+
return self._api.retrieve(uuid)
|
|
144
|
+
|
|
145
|
+
def list(
|
|
146
|
+
self,
|
|
147
|
+
*,
|
|
148
|
+
page: int = 1,
|
|
149
|
+
page_size: int = 20,
|
|
150
|
+
) -> PaginatedCDNFileListList:
|
|
151
|
+
"""List files."""
|
|
152
|
+
return self._api.list(page=page, page_size=page_size)
|
|
153
|
+
|
|
154
|
+
def delete(self, uuid: str) -> bool:
|
|
155
|
+
"""Delete a file by UUID."""
|
|
156
|
+
try:
|
|
157
|
+
self._api.destroy(uuid)
|
|
158
|
+
return True
|
|
159
|
+
except httpx.HTTPStatusError:
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
def stats(self) -> CDNStats:
|
|
163
|
+
"""Get storage statistics."""
|
|
164
|
+
return self._api.stats_retrieve()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class AsyncCDNResource(AsyncBaseResource):
|
|
168
|
+
"""CDN file storage tool (async).
|
|
169
|
+
|
|
170
|
+
Uses generated CdnCdnAPI client.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def __init__(self, config: SDKConfig):
|
|
174
|
+
super().__init__(config)
|
|
175
|
+
self._api = CdnCdnAPI(self._http_client)
|
|
176
|
+
|
|
177
|
+
async def upload(
|
|
178
|
+
self,
|
|
179
|
+
file: Path | BinaryIO | bytes | None = None,
|
|
180
|
+
*,
|
|
181
|
+
url: Optional[str] = None,
|
|
182
|
+
filename: Optional[str] = None,
|
|
183
|
+
ttl: Optional[str] = None,
|
|
184
|
+
is_public: bool = True,
|
|
185
|
+
metadata: Optional[dict] = None,
|
|
186
|
+
) -> Union[CDNFileDetail, CDNUploadResponse]:
|
|
187
|
+
"""
|
|
188
|
+
Upload a file to CDN.
|
|
189
|
+
|
|
190
|
+
Note: File upload requires multipart/form-data, so we use httpx directly.
|
|
191
|
+
"""
|
|
192
|
+
if not file and not url:
|
|
193
|
+
raise ValueError("Either 'file' or 'url' must be provided")
|
|
194
|
+
if file and url:
|
|
195
|
+
raise ValueError("Provide either 'file' or 'url', not both")
|
|
196
|
+
|
|
197
|
+
data = {"is_public": str(is_public).lower()}
|
|
198
|
+
if ttl:
|
|
199
|
+
data["ttl"] = ttl
|
|
200
|
+
if metadata:
|
|
201
|
+
import json
|
|
202
|
+
data["metadata"] = json.dumps(metadata)
|
|
203
|
+
|
|
204
|
+
if url:
|
|
205
|
+
# URL mode - use httpx directly since response varies (async job vs file)
|
|
206
|
+
json_data = {"url": url, "is_public": is_public}
|
|
207
|
+
if filename is not None:
|
|
208
|
+
json_data["filename"] = filename
|
|
209
|
+
if ttl is not None:
|
|
210
|
+
json_data["ttl"] = ttl
|
|
211
|
+
if metadata is not None:
|
|
212
|
+
json_data["metadata"] = metadata
|
|
213
|
+
|
|
214
|
+
response = await self._http_client.post("/api/cdn/", json=json_data)
|
|
215
|
+
response.raise_for_status()
|
|
216
|
+
response_data = response.json()
|
|
217
|
+
|
|
218
|
+
# Check if async processing
|
|
219
|
+
if response.status_code == 202 or "job_id" in response_data or response_data.get("status") == "processing":
|
|
220
|
+
return CDNUploadResponse(response_data, is_async=True)
|
|
221
|
+
return CDNFileDetail.model_validate(response_data)
|
|
222
|
+
else:
|
|
223
|
+
# File upload mode - use httpx directly (multipart)
|
|
224
|
+
if isinstance(file, Path):
|
|
225
|
+
filename = filename or file.name
|
|
226
|
+
with open(file, "rb") as f:
|
|
227
|
+
content = f.read()
|
|
228
|
+
elif isinstance(file, bytes):
|
|
229
|
+
content = file
|
|
230
|
+
if not filename:
|
|
231
|
+
raise ValueError("filename required when uploading bytes")
|
|
232
|
+
else:
|
|
233
|
+
# BinaryIO
|
|
234
|
+
content = file.read()
|
|
235
|
+
filename = filename or getattr(file, "name", "file")
|
|
236
|
+
|
|
237
|
+
files = {"file": (filename, content)}
|
|
238
|
+
response = await self._http_client.post("/api/cdn/", files=files, data=data)
|
|
239
|
+
|
|
240
|
+
response.raise_for_status()
|
|
241
|
+
response_data = response.json()
|
|
242
|
+
|
|
243
|
+
if response.status_code == 202 or "job_id" in response_data:
|
|
244
|
+
return CDNUploadResponse(response_data, is_async=True)
|
|
245
|
+
|
|
246
|
+
return CDNFileDetail.model_validate(response_data)
|
|
247
|
+
|
|
248
|
+
async def get(self, uuid: str) -> CDNFileDetail:
|
|
249
|
+
"""Get file details by UUID."""
|
|
250
|
+
return await self._api.retrieve(uuid)
|
|
251
|
+
|
|
252
|
+
async def list(
|
|
253
|
+
self,
|
|
254
|
+
*,
|
|
255
|
+
page: int = 1,
|
|
256
|
+
page_size: int = 20,
|
|
257
|
+
) -> PaginatedCDNFileListList:
|
|
258
|
+
"""List files."""
|
|
259
|
+
return await self._api.list(page=page, page_size=page_size)
|
|
260
|
+
|
|
261
|
+
async def delete(self, uuid: str) -> bool:
|
|
262
|
+
"""Delete a file by UUID."""
|
|
263
|
+
try:
|
|
264
|
+
await self._api.destroy(uuid)
|
|
265
|
+
return True
|
|
266
|
+
except httpx.HTTPStatusError:
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
async def stats(self) -> CDNStats:
|
|
270
|
+
"""Get storage statistics."""
|
|
271
|
+
return await self._api.stats_retrieve()
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
__all__ = [
|
|
275
|
+
"CDNResource",
|
|
276
|
+
"AsyncCDNResource",
|
|
277
|
+
"CDNUploadResponse",
|
|
278
|
+
"CDNStats",
|
|
279
|
+
# Models
|
|
280
|
+
"CDNFileList",
|
|
281
|
+
"CDNFileDetail",
|
|
282
|
+
"CDNFileUpload",
|
|
283
|
+
"CDNFileUploadRequest",
|
|
284
|
+
"PaginatedCDNFileListList",
|
|
285
|
+
]
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""HTML Cleaner tool using generated API client."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from .._config import SDKConfig
|
|
7
|
+
from .._api.client import (
|
|
8
|
+
BaseResource,
|
|
9
|
+
AsyncBaseResource,
|
|
10
|
+
SyncCleanerCleanerAPI,
|
|
11
|
+
CleanerCleanerAPI,
|
|
12
|
+
)
|
|
13
|
+
from .._api.generated.cleaner.cleaner__api__cleaner.models import (
|
|
14
|
+
CleanRequestRequest,
|
|
15
|
+
CleanResponse,
|
|
16
|
+
CleaningRequestDetail,
|
|
17
|
+
CleaningStats,
|
|
18
|
+
)
|
|
19
|
+
from .._api.generated.cleaner.enums import (
|
|
20
|
+
CleanRequestRequestOutputFormat,
|
|
21
|
+
CleaningRequestDetailStatus,
|
|
22
|
+
CleaningRequestListStatus,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CleanerResource(BaseResource):
|
|
27
|
+
"""HTML Cleaner tool (sync).
|
|
28
|
+
|
|
29
|
+
Uses generated SyncCleanerCleanerAPI client.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, config: SDKConfig):
|
|
33
|
+
super().__init__(config)
|
|
34
|
+
self._api = SyncCleanerCleanerAPI(self._http_client)
|
|
35
|
+
|
|
36
|
+
def clean(
|
|
37
|
+
self,
|
|
38
|
+
html: bytes | str,
|
|
39
|
+
*,
|
|
40
|
+
filename: str = "input.html",
|
|
41
|
+
output_format: CleanRequestRequestOutputFormat | str = CleanRequestRequestOutputFormat.HTML,
|
|
42
|
+
max_tokens: int = 10000,
|
|
43
|
+
remove_scripts: bool = True,
|
|
44
|
+
remove_styles: bool = True,
|
|
45
|
+
remove_comments: bool = True,
|
|
46
|
+
remove_hidden: bool = True,
|
|
47
|
+
filter_classes: bool = True,
|
|
48
|
+
class_threshold: float = 0.3,
|
|
49
|
+
try_hydration: bool = True,
|
|
50
|
+
preserve_selectors: Optional[list[str]] = None,
|
|
51
|
+
) -> CleanResponse:
|
|
52
|
+
"""Clean HTML content via API."""
|
|
53
|
+
if isinstance(html, str):
|
|
54
|
+
content = html.encode("utf-8")
|
|
55
|
+
else:
|
|
56
|
+
content = html
|
|
57
|
+
|
|
58
|
+
# Handle string or enum for output_format
|
|
59
|
+
format_value = output_format.value if hasattr(output_format, "value") else output_format
|
|
60
|
+
|
|
61
|
+
files = {"file": (filename, content, "text/html")}
|
|
62
|
+
form_data = {
|
|
63
|
+
"output_format": format_value,
|
|
64
|
+
"max_tokens": str(max_tokens),
|
|
65
|
+
"remove_scripts": str(remove_scripts).lower(),
|
|
66
|
+
"remove_styles": str(remove_styles).lower(),
|
|
67
|
+
"remove_comments": str(remove_comments).lower(),
|
|
68
|
+
"remove_hidden": str(remove_hidden).lower(),
|
|
69
|
+
"filter_classes": str(filter_classes).lower(),
|
|
70
|
+
"class_threshold": str(class_threshold),
|
|
71
|
+
"try_hydration": str(try_hydration).lower(),
|
|
72
|
+
}
|
|
73
|
+
if preserve_selectors:
|
|
74
|
+
form_data["preserve_selectors"] = ",".join(preserve_selectors)
|
|
75
|
+
|
|
76
|
+
response = self._http_client.post("/api/cleaner/clean/", files=files, data=form_data)
|
|
77
|
+
response.raise_for_status()
|
|
78
|
+
return CleanResponse.model_validate(response.json())
|
|
79
|
+
|
|
80
|
+
def clean_file(self, file_path: str | Path, **kwargs) -> CleanResponse:
|
|
81
|
+
"""Clean HTML file."""
|
|
82
|
+
path = Path(file_path)
|
|
83
|
+
content = path.read_bytes()
|
|
84
|
+
filename = kwargs.pop("filename", path.name)
|
|
85
|
+
return self.clean(content, filename=filename, **kwargs)
|
|
86
|
+
|
|
87
|
+
def get(self, uuid: str) -> CleaningRequestDetail:
|
|
88
|
+
"""Get cleaning request details."""
|
|
89
|
+
return self._api.retrieve(uuid)
|
|
90
|
+
|
|
91
|
+
def list(self):
|
|
92
|
+
"""List cleaning requests."""
|
|
93
|
+
return self._api.list()
|
|
94
|
+
|
|
95
|
+
def stats(self) -> CleaningStats:
|
|
96
|
+
"""Get cleaning statistics."""
|
|
97
|
+
return self._api.stats_retrieve()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AsyncCleanerResource(AsyncBaseResource):
|
|
101
|
+
"""HTML Cleaner tool (async).
|
|
102
|
+
|
|
103
|
+
Uses generated CleanerCleanerAPI client.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(self, config: SDKConfig):
|
|
107
|
+
super().__init__(config)
|
|
108
|
+
self._api = CleanerCleanerAPI(self._http_client)
|
|
109
|
+
|
|
110
|
+
async def clean(
|
|
111
|
+
self,
|
|
112
|
+
html: bytes | str,
|
|
113
|
+
*,
|
|
114
|
+
filename: str = "input.html",
|
|
115
|
+
output_format: CleanRequestRequestOutputFormat | str = CleanRequestRequestOutputFormat.HTML,
|
|
116
|
+
max_tokens: int = 10000,
|
|
117
|
+
remove_scripts: bool = True,
|
|
118
|
+
remove_styles: bool = True,
|
|
119
|
+
remove_comments: bool = True,
|
|
120
|
+
remove_hidden: bool = True,
|
|
121
|
+
filter_classes: bool = True,
|
|
122
|
+
class_threshold: float = 0.3,
|
|
123
|
+
try_hydration: bool = True,
|
|
124
|
+
preserve_selectors: Optional[list[str]] = None,
|
|
125
|
+
) -> CleanResponse:
|
|
126
|
+
"""Clean HTML content via API."""
|
|
127
|
+
if isinstance(html, str):
|
|
128
|
+
content = html.encode("utf-8")
|
|
129
|
+
else:
|
|
130
|
+
content = html
|
|
131
|
+
|
|
132
|
+
# Handle string or enum for output_format
|
|
133
|
+
format_value = output_format.value if hasattr(output_format, "value") else output_format
|
|
134
|
+
|
|
135
|
+
files = {"file": (filename, content, "text/html")}
|
|
136
|
+
form_data = {
|
|
137
|
+
"output_format": format_value,
|
|
138
|
+
"max_tokens": str(max_tokens),
|
|
139
|
+
"remove_scripts": str(remove_scripts).lower(),
|
|
140
|
+
"remove_styles": str(remove_styles).lower(),
|
|
141
|
+
"remove_comments": str(remove_comments).lower(),
|
|
142
|
+
"remove_hidden": str(remove_hidden).lower(),
|
|
143
|
+
"filter_classes": str(filter_classes).lower(),
|
|
144
|
+
"class_threshold": str(class_threshold),
|
|
145
|
+
"try_hydration": str(try_hydration).lower(),
|
|
146
|
+
}
|
|
147
|
+
if preserve_selectors:
|
|
148
|
+
form_data["preserve_selectors"] = ",".join(preserve_selectors)
|
|
149
|
+
|
|
150
|
+
response = await self._http_client.post("/api/cleaner/clean/", files=files, data=form_data)
|
|
151
|
+
response.raise_for_status()
|
|
152
|
+
return CleanResponse.model_validate(response.json())
|
|
153
|
+
|
|
154
|
+
async def clean_file(self, file_path: str | Path, **kwargs) -> CleanResponse:
|
|
155
|
+
"""Clean HTML file."""
|
|
156
|
+
path = Path(file_path)
|
|
157
|
+
content = path.read_bytes()
|
|
158
|
+
filename = kwargs.pop("filename", path.name)
|
|
159
|
+
return await self.clean(content, filename=filename, **kwargs)
|
|
160
|
+
|
|
161
|
+
async def get(self, uuid: str) -> CleaningRequestDetail:
|
|
162
|
+
"""Get cleaning request details."""
|
|
163
|
+
return await self._api.retrieve(uuid)
|
|
164
|
+
|
|
165
|
+
async def list(self):
|
|
166
|
+
"""List cleaning requests."""
|
|
167
|
+
return await self._api.list()
|
|
168
|
+
|
|
169
|
+
async def stats(self) -> CleaningStats:
|
|
170
|
+
"""Get cleaning statistics."""
|
|
171
|
+
return await self._api.stats_retrieve()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
__all__ = [
|
|
175
|
+
"CleanerResource",
|
|
176
|
+
"AsyncCleanerResource",
|
|
177
|
+
# Models
|
|
178
|
+
"CleanRequestRequest",
|
|
179
|
+
"CleanResponse",
|
|
180
|
+
"CleaningRequestDetail",
|
|
181
|
+
"CleaningStats",
|
|
182
|
+
# Enums
|
|
183
|
+
"CleanRequestRequestOutputFormat",
|
|
184
|
+
"CleaningRequestDetailStatus",
|
|
185
|
+
"CleaningRequestListStatus",
|
|
186
|
+
]
|
sdkrouter/tools/keys.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""API Keys management tool using generated API client."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from .._config import SDKConfig
|
|
8
|
+
from .._api.client import (
|
|
9
|
+
BaseResource,
|
|
10
|
+
AsyncBaseResource,
|
|
11
|
+
SyncKeysKeysAPI,
|
|
12
|
+
KeysKeysAPI,
|
|
13
|
+
)
|
|
14
|
+
from .._api.generated.keys.keys__api__keys.models import (
|
|
15
|
+
APIKeyList,
|
|
16
|
+
APIKeyDetail,
|
|
17
|
+
APIKeyCreate,
|
|
18
|
+
APIKeyCreateRequest,
|
|
19
|
+
PaginatedAPIKeyListList,
|
|
20
|
+
)
|
|
21
|
+
from .._api.generated.keys.enums import (
|
|
22
|
+
APIKeyCreatePermission,
|
|
23
|
+
APIKeyCreateRequestPermission,
|
|
24
|
+
APIKeyDetailPermission,
|
|
25
|
+
APIKeyListPermission,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class KeysResource(BaseResource):
|
|
30
|
+
"""API Keys management tool (sync).
|
|
31
|
+
|
|
32
|
+
Uses generated SyncKeysKeysAPI client.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
```python
|
|
36
|
+
from sdkrouter import SDKRouter
|
|
37
|
+
|
|
38
|
+
client = SDKRouter(api_key="your-api-key")
|
|
39
|
+
|
|
40
|
+
# Create a new key
|
|
41
|
+
new_key = client.keys.create(name="Production Key")
|
|
42
|
+
print(f"New key: {new_key.plain_key}") # Save this!
|
|
43
|
+
|
|
44
|
+
# List all keys
|
|
45
|
+
keys = client.keys.list()
|
|
46
|
+
for key in keys.results:
|
|
47
|
+
print(f"{key.name}: {key.key_prefix}...")
|
|
48
|
+
|
|
49
|
+
# Rotate a key
|
|
50
|
+
rotated = client.keys.rotate(key_id)
|
|
51
|
+
print(f"New rotated key: {rotated.plain_key}")
|
|
52
|
+
```
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, config: SDKConfig):
|
|
56
|
+
super().__init__(config)
|
|
57
|
+
self._api = SyncKeysKeysAPI(self._http_client)
|
|
58
|
+
|
|
59
|
+
def create(
|
|
60
|
+
self,
|
|
61
|
+
name: str,
|
|
62
|
+
*,
|
|
63
|
+
permission: APIKeyCreateRequestPermission = APIKeyCreateRequestPermission.WRITE,
|
|
64
|
+
rate_limit_rpm: Optional[int] = None,
|
|
65
|
+
rate_limit_rpd: Optional[int] = None,
|
|
66
|
+
quota_monthly_usd: Optional[Decimal] = None,
|
|
67
|
+
expires_at: Optional[datetime] = None,
|
|
68
|
+
metadata: Optional[dict] = None,
|
|
69
|
+
is_test: bool = False,
|
|
70
|
+
) -> APIKeyCreate:
|
|
71
|
+
"""
|
|
72
|
+
Create a new API key.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
name: Human-readable name for the key
|
|
76
|
+
permission: Permission level (read, write, admin)
|
|
77
|
+
rate_limit_rpm: Max requests per minute (None = unlimited)
|
|
78
|
+
rate_limit_rpd: Max requests per day (None = unlimited)
|
|
79
|
+
quota_monthly_usd: Monthly spending limit in USD
|
|
80
|
+
expires_at: When the key expires
|
|
81
|
+
metadata: Additional metadata
|
|
82
|
+
is_test: Create a test key (sk_test_ prefix)
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
APIKeyCreate with key info including `plain_key` (only shown once!)
|
|
86
|
+
"""
|
|
87
|
+
request = APIKeyCreateRequest(
|
|
88
|
+
name=name,
|
|
89
|
+
permission=permission,
|
|
90
|
+
rate_limit_rpm=rate_limit_rpm,
|
|
91
|
+
rate_limit_rpd=rate_limit_rpd,
|
|
92
|
+
quota_monthly_usd=str(quota_monthly_usd) if quota_monthly_usd else None,
|
|
93
|
+
expires_at=expires_at.isoformat() if expires_at else None,
|
|
94
|
+
metadata=metadata,
|
|
95
|
+
is_test=is_test,
|
|
96
|
+
)
|
|
97
|
+
return self._api.create(request)
|
|
98
|
+
|
|
99
|
+
def get(self, key_id: str) -> APIKeyDetail:
|
|
100
|
+
"""Get API key details by ID."""
|
|
101
|
+
return self._api.retrieve(key_id)
|
|
102
|
+
|
|
103
|
+
def list(
|
|
104
|
+
self,
|
|
105
|
+
*,
|
|
106
|
+
page: int = 1,
|
|
107
|
+
page_size: int = 20,
|
|
108
|
+
) -> PaginatedAPIKeyListList:
|
|
109
|
+
"""List your API keys."""
|
|
110
|
+
return self._api.list(page=page, page_size=page_size)
|
|
111
|
+
|
|
112
|
+
def delete(self, key_id: str) -> bool:
|
|
113
|
+
"""Deactivate an API key (soft delete)."""
|
|
114
|
+
try:
|
|
115
|
+
self._api.destroy(key_id)
|
|
116
|
+
return True
|
|
117
|
+
except httpx.HTTPStatusError:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
def rotate(self, key_id: str) -> APIKeyList:
|
|
121
|
+
"""
|
|
122
|
+
Rotate an API key (generate new key).
|
|
123
|
+
|
|
124
|
+
The old key will stop working immediately.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
APIKeyList with `plain_key` (the new key - only shown once!)
|
|
128
|
+
"""
|
|
129
|
+
return self._api.rotate_create(key_id)
|
|
130
|
+
|
|
131
|
+
def reactivate(self, key_id: str) -> APIKeyList:
|
|
132
|
+
"""Reactivate a deactivated API key."""
|
|
133
|
+
return self._api.reactivate_create(key_id)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class AsyncKeysResource(AsyncBaseResource):
|
|
137
|
+
"""API Keys management tool (async).
|
|
138
|
+
|
|
139
|
+
Uses generated KeysKeysAPI client.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(self, config: SDKConfig):
|
|
143
|
+
super().__init__(config)
|
|
144
|
+
self._api = KeysKeysAPI(self._http_client)
|
|
145
|
+
|
|
146
|
+
async def create(
|
|
147
|
+
self,
|
|
148
|
+
name: str,
|
|
149
|
+
*,
|
|
150
|
+
permission: APIKeyCreateRequestPermission = APIKeyCreateRequestPermission.WRITE,
|
|
151
|
+
rate_limit_rpm: Optional[int] = None,
|
|
152
|
+
rate_limit_rpd: Optional[int] = None,
|
|
153
|
+
quota_monthly_usd: Optional[Decimal] = None,
|
|
154
|
+
expires_at: Optional[datetime] = None,
|
|
155
|
+
metadata: Optional[dict] = None,
|
|
156
|
+
is_test: bool = False,
|
|
157
|
+
) -> APIKeyCreate:
|
|
158
|
+
"""Create a new API key."""
|
|
159
|
+
request = APIKeyCreateRequest(
|
|
160
|
+
name=name,
|
|
161
|
+
permission=permission,
|
|
162
|
+
rate_limit_rpm=rate_limit_rpm,
|
|
163
|
+
rate_limit_rpd=rate_limit_rpd,
|
|
164
|
+
quota_monthly_usd=str(quota_monthly_usd) if quota_monthly_usd else None,
|
|
165
|
+
expires_at=expires_at.isoformat() if expires_at else None,
|
|
166
|
+
metadata=metadata,
|
|
167
|
+
is_test=is_test,
|
|
168
|
+
)
|
|
169
|
+
return await self._api.create(request)
|
|
170
|
+
|
|
171
|
+
async def get(self, key_id: str) -> APIKeyDetail:
|
|
172
|
+
"""Get API key details by ID."""
|
|
173
|
+
return await self._api.retrieve(key_id)
|
|
174
|
+
|
|
175
|
+
async def list(
|
|
176
|
+
self,
|
|
177
|
+
*,
|
|
178
|
+
page: int = 1,
|
|
179
|
+
page_size: int = 20,
|
|
180
|
+
) -> PaginatedAPIKeyListList:
|
|
181
|
+
"""List your API keys."""
|
|
182
|
+
return await self._api.list(page=page, page_size=page_size)
|
|
183
|
+
|
|
184
|
+
async def delete(self, key_id: str) -> bool:
|
|
185
|
+
"""Deactivate an API key (soft delete)."""
|
|
186
|
+
try:
|
|
187
|
+
await self._api.destroy(key_id)
|
|
188
|
+
return True
|
|
189
|
+
except httpx.HTTPStatusError:
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
async def rotate(self, key_id: str) -> APIKeyList:
|
|
193
|
+
"""Rotate an API key (generate new key)."""
|
|
194
|
+
return await self._api.rotate_create(key_id)
|
|
195
|
+
|
|
196
|
+
async def reactivate(self, key_id: str) -> APIKeyList:
|
|
197
|
+
"""Reactivate a deactivated API key."""
|
|
198
|
+
return await self._api.reactivate_create(key_id)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
__all__ = [
|
|
202
|
+
"KeysResource",
|
|
203
|
+
"AsyncKeysResource",
|
|
204
|
+
# Models
|
|
205
|
+
"APIKeyList",
|
|
206
|
+
"APIKeyDetail",
|
|
207
|
+
"APIKeyCreate",
|
|
208
|
+
"APIKeyCreateRequest",
|
|
209
|
+
"PaginatedAPIKeyListList",
|
|
210
|
+
# Enums
|
|
211
|
+
"APIKeyCreatePermission",
|
|
212
|
+
"APIKeyCreateRequestPermission",
|
|
213
|
+
"APIKeyDetailPermission",
|
|
214
|
+
"APIKeyListPermission",
|
|
215
|
+
]
|