huntapi 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.
- huntapi/__init__.py +61 -0
- huntapi/_endpoints.py +50 -0
- huntapi/_operations.py +380 -0
- huntapi/_response.py +82 -0
- huntapi/_serialization.py +109 -0
- huntapi/_version.py +1 -0
- huntapi/client.py +531 -0
- huntapi/errors.py +85 -0
- huntapi/models.py +81 -0
- huntapi/py.typed +1 -0
- huntapi-0.1.0.dist-info/METADATA +201 -0
- huntapi-0.1.0.dist-info/RECORD +14 -0
- huntapi-0.1.0.dist-info/WHEEL +4 -0
- huntapi-0.1.0.dist-info/licenses/LICENSE +21 -0
huntapi/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Official Python SDK for the HuntAPI API."""
|
|
2
|
+
|
|
3
|
+
from ._endpoints import ENDPOINTS
|
|
4
|
+
from ._version import __version__
|
|
5
|
+
from .client import (
|
|
6
|
+
DEFAULT_BASE_URL,
|
|
7
|
+
DEFAULT_CONCURRENCY,
|
|
8
|
+
DEFAULT_RETRY_DELAY,
|
|
9
|
+
DEFAULT_TIMEOUT,
|
|
10
|
+
MAX_RETRIES,
|
|
11
|
+
RETRY_BACKOFF_FACTOR,
|
|
12
|
+
AsyncHuntApiClient,
|
|
13
|
+
HuntApiClient,
|
|
14
|
+
)
|
|
15
|
+
from .errors import (
|
|
16
|
+
HuntApiAPIError,
|
|
17
|
+
HuntApiConfigurationError,
|
|
18
|
+
HuntApiError,
|
|
19
|
+
HuntApiJobTimeoutError,
|
|
20
|
+
HuntApiResponseDecodingError,
|
|
21
|
+
HuntApiSerializationError,
|
|
22
|
+
HuntApiTransportError,
|
|
23
|
+
)
|
|
24
|
+
from .models import (
|
|
25
|
+
APIResponse,
|
|
26
|
+
BatchResult,
|
|
27
|
+
BinaryResponse,
|
|
28
|
+
EndpointDefinition,
|
|
29
|
+
HTTPMethod,
|
|
30
|
+
JsonResponse,
|
|
31
|
+
RawResponse,
|
|
32
|
+
RequestOptions,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"APIResponse",
|
|
37
|
+
"AsyncHuntApiClient",
|
|
38
|
+
"BatchResult",
|
|
39
|
+
"BinaryResponse",
|
|
40
|
+
"DEFAULT_BASE_URL",
|
|
41
|
+
"DEFAULT_CONCURRENCY",
|
|
42
|
+
"DEFAULT_RETRY_DELAY",
|
|
43
|
+
"DEFAULT_TIMEOUT",
|
|
44
|
+
"MAX_RETRIES",
|
|
45
|
+
"RETRY_BACKOFF_FACTOR",
|
|
46
|
+
"ENDPOINTS",
|
|
47
|
+
"EndpointDefinition",
|
|
48
|
+
"HTTPMethod",
|
|
49
|
+
"JsonResponse",
|
|
50
|
+
"RawResponse",
|
|
51
|
+
"RequestOptions",
|
|
52
|
+
"HuntApiAPIError",
|
|
53
|
+
"HuntApiClient",
|
|
54
|
+
"HuntApiConfigurationError",
|
|
55
|
+
"HuntApiError",
|
|
56
|
+
"HuntApiJobTimeoutError",
|
|
57
|
+
"HuntApiResponseDecodingError",
|
|
58
|
+
"HuntApiSerializationError",
|
|
59
|
+
"HuntApiTransportError",
|
|
60
|
+
"__version__",
|
|
61
|
+
]
|
huntapi/_endpoints.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Generated from the HuntAPI OpenAPI document. Do not edit by hand."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .models import EndpointDefinition
|
|
6
|
+
|
|
7
|
+
ENDPOINTS: dict[str, EndpointDefinition] = {
|
|
8
|
+
"audio_download": EndpointDefinition(
|
|
9
|
+
path="/v1/audio/download",
|
|
10
|
+
methods=(
|
|
11
|
+
"GET",
|
|
12
|
+
"POST",
|
|
13
|
+
),
|
|
14
|
+
response="json",
|
|
15
|
+
required=("query",),
|
|
16
|
+
),
|
|
17
|
+
"jobs": EndpointDefinition(
|
|
18
|
+
path="/v1/jobs/{id}",
|
|
19
|
+
methods=("GET",),
|
|
20
|
+
response="json",
|
|
21
|
+
required=("id",),
|
|
22
|
+
),
|
|
23
|
+
"media_metadata": EndpointDefinition(
|
|
24
|
+
path="/v1/media/metadata",
|
|
25
|
+
methods=("GET",),
|
|
26
|
+
response="json",
|
|
27
|
+
required=("query",),
|
|
28
|
+
),
|
|
29
|
+
"stats": EndpointDefinition(
|
|
30
|
+
path="/v1/stats",
|
|
31
|
+
methods=("GET",),
|
|
32
|
+
response="json",
|
|
33
|
+
required=(),
|
|
34
|
+
),
|
|
35
|
+
"usage": EndpointDefinition(
|
|
36
|
+
path="/v1/usage",
|
|
37
|
+
methods=("GET",),
|
|
38
|
+
response="json",
|
|
39
|
+
required=(),
|
|
40
|
+
),
|
|
41
|
+
"video_download": EndpointDefinition(
|
|
42
|
+
path="/v1/video/download",
|
|
43
|
+
methods=(
|
|
44
|
+
"GET",
|
|
45
|
+
"POST",
|
|
46
|
+
),
|
|
47
|
+
response="json",
|
|
48
|
+
required=("query",),
|
|
49
|
+
),
|
|
50
|
+
}
|
huntapi/_operations.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""Generated from the HuntAPI OpenAPI document. Do not edit by hand."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, cast
|
|
6
|
+
|
|
7
|
+
from .models import APIResponse, JsonResponse, RequestOptions
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _SyncOperations:
|
|
11
|
+
"""Generated HuntAPI endpoint methods."""
|
|
12
|
+
|
|
13
|
+
__slots__ = ()
|
|
14
|
+
|
|
15
|
+
def _call(
|
|
16
|
+
self, name: str, params: dict[str, Any], options: RequestOptions | None
|
|
17
|
+
) -> APIResponse:
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
|
|
20
|
+
def audio_download(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
query: str,
|
|
24
|
+
download_type: str | None = None,
|
|
25
|
+
max_duration: int | float | None = None,
|
|
26
|
+
audio_quality: str | None = None,
|
|
27
|
+
audio_format: str | None = None,
|
|
28
|
+
webhook_url: str | None = None,
|
|
29
|
+
webhook_authorization: str | None = None,
|
|
30
|
+
fields: str | None = None,
|
|
31
|
+
is_async: bool | None = None,
|
|
32
|
+
soft_fail: bool | None = None,
|
|
33
|
+
request_options: RequestOptions | None = None,
|
|
34
|
+
) -> JsonResponse:
|
|
35
|
+
"""Audio Download. Download audio from a URL via HuntAPI and receive a job id. Credit cost: 1 credit per call.
|
|
36
|
+
|
|
37
|
+
``GET / POST /v1/audio/download``
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
query: URL to download audio from.
|
|
41
|
+
download_type: Desired download type. Supported values: audio (default).
|
|
42
|
+
max_duration: Maximum duration of the downloaded content in seconds. If the audio is longer than this value, only the content up to this duration will be downloaded.
|
|
43
|
+
audio_quality: Desired audio quality.
|
|
44
|
+
audio_format: Desired output format for the audio.
|
|
45
|
+
webhook_url: URL of the webhook where results will be sent. When specified, the system will attempt to send the response to this URL up to 3 times with a 5-second delay between each attempt. The results will be s…
|
|
46
|
+
webhook_authorization: Authorization header for the webhook. This parameter allows you to add authentication to your webhook endpoint.
|
|
47
|
+
fields: Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
|
|
48
|
+
is_async: Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
|
|
49
|
+
soft_fail: Return HTTP 200 even when an error occurs; the error is then in the response body.
|
|
50
|
+
"""
|
|
51
|
+
return cast(
|
|
52
|
+
JsonResponse,
|
|
53
|
+
self._call(
|
|
54
|
+
"audio_download",
|
|
55
|
+
{
|
|
56
|
+
"query": query,
|
|
57
|
+
"download_type": download_type,
|
|
58
|
+
"max_duration": max_duration,
|
|
59
|
+
"audio_quality": audio_quality,
|
|
60
|
+
"audio_format": audio_format,
|
|
61
|
+
"webhook_url": webhook_url,
|
|
62
|
+
"webhook_authorization": webhook_authorization,
|
|
63
|
+
"fields": fields,
|
|
64
|
+
"is_async": is_async,
|
|
65
|
+
"soft_fail": soft_fail,
|
|
66
|
+
},
|
|
67
|
+
request_options,
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def jobs(
|
|
72
|
+
self,
|
|
73
|
+
*,
|
|
74
|
+
id: str,
|
|
75
|
+
request_options: RequestOptions | None = None,
|
|
76
|
+
) -> JsonResponse:
|
|
77
|
+
"""Jobs. Poll status, progress (0–100), and result of an async HuntAPI job.
|
|
78
|
+
|
|
79
|
+
``GET /v1/jobs/{id}``
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
id: Job UUID returned as job_id by async endpoints such as /v1/video/download and /v1/audio/download.
|
|
83
|
+
"""
|
|
84
|
+
return cast(JsonResponse, self._call("jobs", {"id": id}, request_options))
|
|
85
|
+
|
|
86
|
+
def media_metadata(
|
|
87
|
+
self,
|
|
88
|
+
*,
|
|
89
|
+
query: str,
|
|
90
|
+
fields: str | None = None,
|
|
91
|
+
is_async: bool | None = None,
|
|
92
|
+
soft_fail: bool | None = None,
|
|
93
|
+
request_options: RequestOptions | None = None,
|
|
94
|
+
) -> JsonResponse:
|
|
95
|
+
"""Media Metadata. Extract metadata from a media URL without downloading the file. Credit cost: 1 credit per call.
|
|
96
|
+
|
|
97
|
+
``GET /v1/media/metadata``
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
query: Media URL to inspect (YouTube, Instagram, TikTok, Vimeo, X, Facebook, Twitch, Reddit, etc.).
|
|
101
|
+
fields: Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
|
|
102
|
+
is_async: Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
|
|
103
|
+
soft_fail: Return HTTP 200 even when an error occurs; the error is then in the response body.
|
|
104
|
+
"""
|
|
105
|
+
return cast(
|
|
106
|
+
JsonResponse,
|
|
107
|
+
self._call(
|
|
108
|
+
"media_metadata",
|
|
109
|
+
{"query": query, "fields": fields, "is_async": is_async, "soft_fail": soft_fail},
|
|
110
|
+
request_options,
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def stats(
|
|
115
|
+
self,
|
|
116
|
+
*,
|
|
117
|
+
engine: str | None = None,
|
|
118
|
+
path: str | None = None,
|
|
119
|
+
request_options: RequestOptions | None = None,
|
|
120
|
+
) -> JsonResponse:
|
|
121
|
+
"""Stats. Infrastructure health for an engine or endpoint over the last hour. Not scoped to your account or tenant. Enabled per account: HTTP 403 means contact support to activate it.
|
|
122
|
+
|
|
123
|
+
``GET /v1/stats``
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
engine: API family to check, for example video. Provide at least engine or path.
|
|
127
|
+
path: Specific endpoint to check, for example /v1/video/download. If you also pass engine, use an endpoint from that API.
|
|
128
|
+
"""
|
|
129
|
+
return cast(
|
|
130
|
+
JsonResponse, self._call("stats", {"engine": engine, "path": path}, request_options)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def usage(
|
|
134
|
+
self,
|
|
135
|
+
*,
|
|
136
|
+
request_options: RequestOptions | None = None,
|
|
137
|
+
) -> JsonResponse:
|
|
138
|
+
"""Usage. Retrieve real-time usage statistics and credit balance for your Huntapi account.
|
|
139
|
+
|
|
140
|
+
``GET /v1/usage``
|
|
141
|
+
"""
|
|
142
|
+
return cast(JsonResponse, self._call("usage", {}, request_options))
|
|
143
|
+
|
|
144
|
+
def video_download(
|
|
145
|
+
self,
|
|
146
|
+
*,
|
|
147
|
+
query: str,
|
|
148
|
+
download_type: str | None = None,
|
|
149
|
+
max_duration: int | float | None = None,
|
|
150
|
+
video_quality: str | None = None,
|
|
151
|
+
video_format: str | None = None,
|
|
152
|
+
webhook_url: str | None = None,
|
|
153
|
+
webhook_authorization: str | None = None,
|
|
154
|
+
fields: str | None = None,
|
|
155
|
+
is_async: bool | None = None,
|
|
156
|
+
soft_fail: bool | None = None,
|
|
157
|
+
request_options: RequestOptions | None = None,
|
|
158
|
+
) -> JsonResponse:
|
|
159
|
+
"""Video Download. Credit cost: 1 credit per call.
|
|
160
|
+
|
|
161
|
+
``GET / POST /v1/video/download``
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
query: Video URL to download.
|
|
165
|
+
download_type: Desired download type. You have three options:
|
|
166
|
+
max_duration: Maximum duration of the downloaded content in seconds. If the video/audio is longer than this value, only the content up to this duration will be downloaded.
|
|
167
|
+
video_quality: Desired video quality. Available options:
|
|
168
|
+
video_format: Desired output format for the video. Available options:
|
|
169
|
+
webhook_url: URL of the webhook where results will be sent. When specified, the system will attempt to send the response to this URL up to 3 times with a 5-second delay between each attempt. The results will be s…
|
|
170
|
+
webhook_authorization: Authorization header for the webhook. This parameter allows you to add authentication to your webhook endpoint. This value will be included in the Authorization header when making the webhook request…
|
|
171
|
+
fields: Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
|
|
172
|
+
is_async: Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
|
|
173
|
+
soft_fail: Return HTTP 200 even when an error occurs; the error is then in the response body.
|
|
174
|
+
"""
|
|
175
|
+
return cast(
|
|
176
|
+
JsonResponse,
|
|
177
|
+
self._call(
|
|
178
|
+
"video_download",
|
|
179
|
+
{
|
|
180
|
+
"query": query,
|
|
181
|
+
"download_type": download_type,
|
|
182
|
+
"max_duration": max_duration,
|
|
183
|
+
"video_quality": video_quality,
|
|
184
|
+
"video_format": video_format,
|
|
185
|
+
"webhook_url": webhook_url,
|
|
186
|
+
"webhook_authorization": webhook_authorization,
|
|
187
|
+
"fields": fields,
|
|
188
|
+
"is_async": is_async,
|
|
189
|
+
"soft_fail": soft_fail,
|
|
190
|
+
},
|
|
191
|
+
request_options,
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class _AsyncOperations:
|
|
197
|
+
"""Generated HuntAPI endpoint methods."""
|
|
198
|
+
|
|
199
|
+
__slots__ = ()
|
|
200
|
+
|
|
201
|
+
async def _call(
|
|
202
|
+
self, name: str, params: dict[str, Any], options: RequestOptions | None
|
|
203
|
+
) -> APIResponse:
|
|
204
|
+
raise NotImplementedError
|
|
205
|
+
|
|
206
|
+
async def audio_download(
|
|
207
|
+
self,
|
|
208
|
+
*,
|
|
209
|
+
query: str,
|
|
210
|
+
download_type: str | None = None,
|
|
211
|
+
max_duration: int | float | None = None,
|
|
212
|
+
audio_quality: str | None = None,
|
|
213
|
+
audio_format: str | None = None,
|
|
214
|
+
webhook_url: str | None = None,
|
|
215
|
+
webhook_authorization: str | None = None,
|
|
216
|
+
fields: str | None = None,
|
|
217
|
+
is_async: bool | None = None,
|
|
218
|
+
soft_fail: bool | None = None,
|
|
219
|
+
request_options: RequestOptions | None = None,
|
|
220
|
+
) -> JsonResponse:
|
|
221
|
+
"""Audio Download. Download audio from a URL via HuntAPI and receive a job id. Credit cost: 1 credit per call.
|
|
222
|
+
|
|
223
|
+
``GET / POST /v1/audio/download``
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
query: URL to download audio from.
|
|
227
|
+
download_type: Desired download type. Supported values: audio (default).
|
|
228
|
+
max_duration: Maximum duration of the downloaded content in seconds. If the audio is longer than this value, only the content up to this duration will be downloaded.
|
|
229
|
+
audio_quality: Desired audio quality.
|
|
230
|
+
audio_format: Desired output format for the audio.
|
|
231
|
+
webhook_url: URL of the webhook where results will be sent. When specified, the system will attempt to send the response to this URL up to 3 times with a 5-second delay between each attempt. The results will be s…
|
|
232
|
+
webhook_authorization: Authorization header for the webhook. This parameter allows you to add authentication to your webhook endpoint.
|
|
233
|
+
fields: Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
|
|
234
|
+
is_async: Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
|
|
235
|
+
soft_fail: Return HTTP 200 even when an error occurs; the error is then in the response body.
|
|
236
|
+
"""
|
|
237
|
+
return cast(
|
|
238
|
+
JsonResponse,
|
|
239
|
+
await self._call(
|
|
240
|
+
"audio_download",
|
|
241
|
+
{
|
|
242
|
+
"query": query,
|
|
243
|
+
"download_type": download_type,
|
|
244
|
+
"max_duration": max_duration,
|
|
245
|
+
"audio_quality": audio_quality,
|
|
246
|
+
"audio_format": audio_format,
|
|
247
|
+
"webhook_url": webhook_url,
|
|
248
|
+
"webhook_authorization": webhook_authorization,
|
|
249
|
+
"fields": fields,
|
|
250
|
+
"is_async": is_async,
|
|
251
|
+
"soft_fail": soft_fail,
|
|
252
|
+
},
|
|
253
|
+
request_options,
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
async def jobs(
|
|
258
|
+
self,
|
|
259
|
+
*,
|
|
260
|
+
id: str,
|
|
261
|
+
request_options: RequestOptions | None = None,
|
|
262
|
+
) -> JsonResponse:
|
|
263
|
+
"""Jobs. Poll status, progress (0–100), and result of an async HuntAPI job.
|
|
264
|
+
|
|
265
|
+
``GET /v1/jobs/{id}``
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
id: Job UUID returned as job_id by async endpoints such as /v1/video/download and /v1/audio/download.
|
|
269
|
+
"""
|
|
270
|
+
return cast(JsonResponse, await self._call("jobs", {"id": id}, request_options))
|
|
271
|
+
|
|
272
|
+
async def media_metadata(
|
|
273
|
+
self,
|
|
274
|
+
*,
|
|
275
|
+
query: str,
|
|
276
|
+
fields: str | None = None,
|
|
277
|
+
is_async: bool | None = None,
|
|
278
|
+
soft_fail: bool | None = None,
|
|
279
|
+
request_options: RequestOptions | None = None,
|
|
280
|
+
) -> JsonResponse:
|
|
281
|
+
"""Media Metadata. Extract metadata from a media URL without downloading the file. Credit cost: 1 credit per call.
|
|
282
|
+
|
|
283
|
+
``GET /v1/media/metadata``
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
query: Media URL to inspect (YouTube, Instagram, TikTok, Vimeo, X, Facebook, Twitch, Reddit, etc.).
|
|
287
|
+
fields: Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
|
|
288
|
+
is_async: Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
|
|
289
|
+
soft_fail: Return HTTP 200 even when an error occurs; the error is then in the response body.
|
|
290
|
+
"""
|
|
291
|
+
return cast(
|
|
292
|
+
JsonResponse,
|
|
293
|
+
await self._call(
|
|
294
|
+
"media_metadata",
|
|
295
|
+
{"query": query, "fields": fields, "is_async": is_async, "soft_fail": soft_fail},
|
|
296
|
+
request_options,
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
async def stats(
|
|
301
|
+
self,
|
|
302
|
+
*,
|
|
303
|
+
engine: str | None = None,
|
|
304
|
+
path: str | None = None,
|
|
305
|
+
request_options: RequestOptions | None = None,
|
|
306
|
+
) -> JsonResponse:
|
|
307
|
+
"""Stats. Infrastructure health for an engine or endpoint over the last hour. Not scoped to your account or tenant. Enabled per account: HTTP 403 means contact support to activate it.
|
|
308
|
+
|
|
309
|
+
``GET /v1/stats``
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
engine: API family to check, for example video. Provide at least engine or path.
|
|
313
|
+
path: Specific endpoint to check, for example /v1/video/download. If you also pass engine, use an endpoint from that API.
|
|
314
|
+
"""
|
|
315
|
+
return cast(
|
|
316
|
+
JsonResponse,
|
|
317
|
+
await self._call("stats", {"engine": engine, "path": path}, request_options),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
async def usage(
|
|
321
|
+
self,
|
|
322
|
+
*,
|
|
323
|
+
request_options: RequestOptions | None = None,
|
|
324
|
+
) -> JsonResponse:
|
|
325
|
+
"""Usage. Retrieve real-time usage statistics and credit balance for your Huntapi account.
|
|
326
|
+
|
|
327
|
+
``GET /v1/usage``
|
|
328
|
+
"""
|
|
329
|
+
return cast(JsonResponse, await self._call("usage", {}, request_options))
|
|
330
|
+
|
|
331
|
+
async def video_download(
|
|
332
|
+
self,
|
|
333
|
+
*,
|
|
334
|
+
query: str,
|
|
335
|
+
download_type: str | None = None,
|
|
336
|
+
max_duration: int | float | None = None,
|
|
337
|
+
video_quality: str | None = None,
|
|
338
|
+
video_format: str | None = None,
|
|
339
|
+
webhook_url: str | None = None,
|
|
340
|
+
webhook_authorization: str | None = None,
|
|
341
|
+
fields: str | None = None,
|
|
342
|
+
is_async: bool | None = None,
|
|
343
|
+
soft_fail: bool | None = None,
|
|
344
|
+
request_options: RequestOptions | None = None,
|
|
345
|
+
) -> JsonResponse:
|
|
346
|
+
"""Video Download. Credit cost: 1 credit per call.
|
|
347
|
+
|
|
348
|
+
``GET / POST /v1/video/download``
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
query: Video URL to download.
|
|
352
|
+
download_type: Desired download type. You have three options:
|
|
353
|
+
max_duration: Maximum duration of the downloaded content in seconds. If the video/audio is longer than this value, only the content up to this duration will be downloaded.
|
|
354
|
+
video_quality: Desired video quality. Available options:
|
|
355
|
+
video_format: Desired output format for the video. Available options:
|
|
356
|
+
webhook_url: URL of the webhook where results will be sent. When specified, the system will attempt to send the response to this URL up to 3 times with a 5-second delay between each attempt. The results will be s…
|
|
357
|
+
webhook_authorization: Authorization header for the webhook. This parameter allows you to add authentication to your webhook endpoint. This value will be included in the Authorization header when making the webhook request…
|
|
358
|
+
fields: Comma-separated list of fields to keep in the response, with dot notation for nested fields (e.g. name,location.city). Does not change the credit cost.
|
|
359
|
+
is_async: Process the request asynchronously: the response is a job_id to poll on /v1/jobs/{id} (see waitForJob).
|
|
360
|
+
soft_fail: Return HTTP 200 even when an error occurs; the error is then in the response body.
|
|
361
|
+
"""
|
|
362
|
+
return cast(
|
|
363
|
+
JsonResponse,
|
|
364
|
+
await self._call(
|
|
365
|
+
"video_download",
|
|
366
|
+
{
|
|
367
|
+
"query": query,
|
|
368
|
+
"download_type": download_type,
|
|
369
|
+
"max_duration": max_duration,
|
|
370
|
+
"video_quality": video_quality,
|
|
371
|
+
"video_format": video_format,
|
|
372
|
+
"webhook_url": webhook_url,
|
|
373
|
+
"webhook_authorization": webhook_authorization,
|
|
374
|
+
"fields": fields,
|
|
375
|
+
"is_async": is_async,
|
|
376
|
+
"soft_fail": soft_fail,
|
|
377
|
+
},
|
|
378
|
+
request_options,
|
|
379
|
+
),
|
|
380
|
+
)
|
huntapi/_response.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Buffered HTTP response preservation and decoding."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from .errors import HuntApiAPIError, HuntApiResponseDecodingError
|
|
12
|
+
from .models import APIResponse, BinaryResponse, JsonResponse, RawResponse
|
|
13
|
+
|
|
14
|
+
_ERROR_MESSAGE_KEYS = ("error", "message", "error_message", "detail")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_raw_response(response: httpx.Response, body: bytes) -> RawResponse:
|
|
18
|
+
grouped: dict[str, list[str]] = {}
|
|
19
|
+
for name, value in response.headers.multi_items():
|
|
20
|
+
grouped.setdefault(name.lower(), []).append(value)
|
|
21
|
+
return RawResponse(
|
|
22
|
+
status=response.status_code,
|
|
23
|
+
status_text=response.reason_phrase,
|
|
24
|
+
headers=MappingProxyType({name: tuple(values) for name, values in grouped.items()}),
|
|
25
|
+
content_type=response.headers.get("content-type"),
|
|
26
|
+
body=body,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_api_error(raw_response: RawResponse) -> HuntApiAPIError:
|
|
31
|
+
parsed, value = _try_parse_json(raw_response.body)
|
|
32
|
+
message = None
|
|
33
|
+
if parsed and isinstance(value, dict):
|
|
34
|
+
for key in _ERROR_MESSAGE_KEYS:
|
|
35
|
+
candidate = value.get(key)
|
|
36
|
+
if isinstance(candidate, str) and candidate:
|
|
37
|
+
message = candidate
|
|
38
|
+
break
|
|
39
|
+
return HuntApiAPIError(
|
|
40
|
+
raw_response=raw_response,
|
|
41
|
+
response_body=value if parsed else None,
|
|
42
|
+
error_message=message,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def decode_success_response(
|
|
47
|
+
raw_response: RawResponse, expected: Literal["json", "any"]
|
|
48
|
+
) -> APIResponse:
|
|
49
|
+
if not _is_json_media_type(raw_response.content_type):
|
|
50
|
+
if expected == "json":
|
|
51
|
+
raise HuntApiResponseDecodingError(
|
|
52
|
+
"HuntAPI returned a successful non-JSON response for a JSON operation.",
|
|
53
|
+
raw_response,
|
|
54
|
+
TypeError(f"Unexpected content type: {raw_response.content_type}."),
|
|
55
|
+
)
|
|
56
|
+
return BinaryResponse(
|
|
57
|
+
bytes=raw_response.body,
|
|
58
|
+
content_type=raw_response.content_type,
|
|
59
|
+
raw_response=raw_response,
|
|
60
|
+
)
|
|
61
|
+
parsed, value = _try_parse_json(raw_response.body)
|
|
62
|
+
if not parsed:
|
|
63
|
+
raise HuntApiResponseDecodingError(
|
|
64
|
+
"HuntAPI returned a successful JSON response that could not be decoded.",
|
|
65
|
+
raw_response,
|
|
66
|
+
value if isinstance(value, Exception) else None,
|
|
67
|
+
)
|
|
68
|
+
return JsonResponse(data=value, raw_response=raw_response)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _is_json_media_type(content_type: str | None) -> bool:
|
|
72
|
+
if content_type is None:
|
|
73
|
+
return False
|
|
74
|
+
media_type = content_type.split(";", 1)[0].strip().lower()
|
|
75
|
+
return media_type == "application/json" or media_type.endswith("+json")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _try_parse_json(body: bytes) -> tuple[bool, Any]:
|
|
79
|
+
try:
|
|
80
|
+
return True, json.loads(body)
|
|
81
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as cause:
|
|
82
|
+
return False, cause
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Request serialization: path filling, query pairs and JSON bodies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
import re
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any
|
|
11
|
+
from urllib.parse import quote, urlencode
|
|
12
|
+
|
|
13
|
+
from .errors import HuntApiSerializationError
|
|
14
|
+
from .models import HTTPMethod
|
|
15
|
+
|
|
16
|
+
_PATH_PARAM = re.compile(r"\{([^}]+)\}")
|
|
17
|
+
# The API reads the global parameters from the query string, whatever the method.
|
|
18
|
+
QUERY_STRING_PARAMS = ("fields", "is_async", "soft_fail")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class SerializedRequest:
|
|
23
|
+
path: str
|
|
24
|
+
query: list[tuple[str, str]]
|
|
25
|
+
body: dict[str, Any] | None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def serialize_request(
|
|
29
|
+
method: HTTPMethod, path_template: str, params: Mapping[str, Any] | None
|
|
30
|
+
) -> SerializedRequest:
|
|
31
|
+
"""Fill ``{name}`` path segments, then send the rest as query pairs (GET) or a JSON body (POST).
|
|
32
|
+
|
|
33
|
+
On POST, the global parameters (``fields``, ``is_async``, ``soft_fail``) still travel in the query string.
|
|
34
|
+
|
|
35
|
+
``None`` values and empty sequences are omitted; ``False`` and ``0`` are sent.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
if params is not None and not isinstance(params, Mapping):
|
|
39
|
+
raise HuntApiSerializationError("Request parameters must be a mapping.")
|
|
40
|
+
remaining = {name: value for name, value in (params or {}).items() if value is not None}
|
|
41
|
+
|
|
42
|
+
def fill(match: re.Match[str]) -> str:
|
|
43
|
+
name = match.group(1)
|
|
44
|
+
if name not in remaining:
|
|
45
|
+
raise HuntApiSerializationError(f'The "{name}" path parameter is required.', name)
|
|
46
|
+
return quote(to_wire_string(name, remaining.pop(name)), safe="")
|
|
47
|
+
|
|
48
|
+
path = _PATH_PARAM.sub(fill, path_template)
|
|
49
|
+
|
|
50
|
+
if method == "GET":
|
|
51
|
+
query: list[tuple[str, str]] = []
|
|
52
|
+
for name, value in remaining.items():
|
|
53
|
+
if isinstance(value, (list, tuple)):
|
|
54
|
+
query.extend(
|
|
55
|
+
(name, to_wire_string(name, item)) for item in value if item is not None
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
query.append((name, to_wire_string(name, value)))
|
|
59
|
+
return SerializedRequest(path=path, query=query, body=None)
|
|
60
|
+
|
|
61
|
+
query = [
|
|
62
|
+
(name, to_wire_string(name, remaining.pop(name)))
|
|
63
|
+
for name in QUERY_STRING_PARAMS
|
|
64
|
+
if name in remaining
|
|
65
|
+
]
|
|
66
|
+
for name, value in remaining.items():
|
|
67
|
+
if isinstance(value, float) and not math.isfinite(value):
|
|
68
|
+
raise HuntApiSerializationError(
|
|
69
|
+
f'The "{name}" parameter must be a finite number.', name
|
|
70
|
+
)
|
|
71
|
+
return SerializedRequest(path=path, query=query, body=remaining)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def to_wire_string(name: str, value: Any) -> str:
|
|
75
|
+
if isinstance(value, bool):
|
|
76
|
+
return "true" if value else "false"
|
|
77
|
+
if isinstance(value, str):
|
|
78
|
+
return value
|
|
79
|
+
if isinstance(value, int):
|
|
80
|
+
return str(value)
|
|
81
|
+
if isinstance(value, float):
|
|
82
|
+
if not math.isfinite(value):
|
|
83
|
+
raise HuntApiSerializationError(
|
|
84
|
+
f'The "{name}" parameter must be a finite number.', name
|
|
85
|
+
)
|
|
86
|
+
return str(int(value)) if value.is_integer() else repr(value)
|
|
87
|
+
if isinstance(value, (Mapping, list, tuple)):
|
|
88
|
+
try:
|
|
89
|
+
return json.dumps(value, separators=(",", ":"), ensure_ascii=False)
|
|
90
|
+
except (TypeError, ValueError) as cause:
|
|
91
|
+
raise HuntApiSerializationError(
|
|
92
|
+
f'The "{name}" parameter could not be serialized.', name
|
|
93
|
+
) from cause
|
|
94
|
+
raise HuntApiSerializationError(
|
|
95
|
+
f'The "{name}" parameter has an unsupported type ({type(value).__name__}).', name
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def encode_query(pairs: list[tuple[str, str]]) -> str:
|
|
100
|
+
return urlencode(pairs, quote_via=quote)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def encode_json_body(body: Mapping[str, Any]) -> bytes:
|
|
104
|
+
try:
|
|
105
|
+
return json.dumps(body, ensure_ascii=False, allow_nan=False).encode("utf-8")
|
|
106
|
+
except (TypeError, ValueError) as cause:
|
|
107
|
+
raise HuntApiSerializationError(
|
|
108
|
+
"The request body could not be serialized as JSON."
|
|
109
|
+
) from cause
|
huntapi/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|