never-primp 1.2.2__cp38-abi3-macosx_11_0_arm64.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.
- never_primp/__init__.py +653 -0
- never_primp/never_primp.abi3.so +0 -0
- never_primp/never_primp.pyi +591 -0
- never_primp/py.typed +0 -0
- never_primp-1.2.2.dist-info/METADATA +896 -0
- never_primp-1.2.2.dist-info/RECORD +8 -0
- never_primp-1.2.2.dist-info/WHEEL +4 -0
- never_primp-1.2.2.dist-info/licenses/LICENSE +21 -0
never_primp/__init__.py
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import sys
|
|
5
|
+
from functools import partial
|
|
6
|
+
from typing import TYPE_CHECKING, TypedDict, Iterator
|
|
7
|
+
from collections.abc import MutableMapping
|
|
8
|
+
|
|
9
|
+
if sys.version_info <= (3, 11):
|
|
10
|
+
from typing_extensions import Unpack
|
|
11
|
+
else:
|
|
12
|
+
from typing import Unpack
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from .never_primp import RClient
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from .never_primp import IMPERSONATE, IMPERSONATE_OS, ClientRequestParams, HttpMethod, RequestParams, Response
|
|
19
|
+
else:
|
|
20
|
+
|
|
21
|
+
class _Unpack:
|
|
22
|
+
@staticmethod
|
|
23
|
+
def __getitem__(*args, **kwargs):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
Unpack = _Unpack()
|
|
27
|
+
RequestParams = ClientRequestParams = TypedDict
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class HeadersJar(MutableMapping):
|
|
31
|
+
"""
|
|
32
|
+
A dict-like container for managing HTTP headers, compatible with requests.Session.headers API.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
client = Client()
|
|
36
|
+
|
|
37
|
+
# Set headers
|
|
38
|
+
client.headers['User-Agent'] = 'CustomAgent/1.0'
|
|
39
|
+
client.headers['Accept'] = 'application/json'
|
|
40
|
+
|
|
41
|
+
# Get headers
|
|
42
|
+
value = client.headers['User-Agent']
|
|
43
|
+
value = client.headers.get('Accept', 'default')
|
|
44
|
+
|
|
45
|
+
# Update multiple headers
|
|
46
|
+
client.headers.update({'X-Custom': 'value1', 'X-Another': 'value2'})
|
|
47
|
+
|
|
48
|
+
# Delete headers
|
|
49
|
+
del client.headers['User-Agent']
|
|
50
|
+
|
|
51
|
+
# Clear all headers
|
|
52
|
+
client.headers.clear()
|
|
53
|
+
|
|
54
|
+
# Check existence
|
|
55
|
+
if 'User-Agent' in client.headers:
|
|
56
|
+
print("User-Agent exists")
|
|
57
|
+
|
|
58
|
+
# Convert to dict
|
|
59
|
+
all_headers = dict(client.headers)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, client: RClient):
|
|
63
|
+
"""Initialize HeadersJar with a reference to the client."""
|
|
64
|
+
self._client = client
|
|
65
|
+
|
|
66
|
+
def __getitem__(self, name: str) -> str:
|
|
67
|
+
"""Get a header value by name."""
|
|
68
|
+
value = self._client.get_header(name)
|
|
69
|
+
if value is None:
|
|
70
|
+
raise KeyError(name)
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
def __setitem__(self, name: str, value: str) -> None:
|
|
74
|
+
"""Set a header by name."""
|
|
75
|
+
self._client.set_header(name, value)
|
|
76
|
+
|
|
77
|
+
def __delitem__(self, name: str) -> None:
|
|
78
|
+
"""Delete a header by name."""
|
|
79
|
+
# Check if header exists first
|
|
80
|
+
if self._client.get_header(name) is None:
|
|
81
|
+
raise KeyError(name)
|
|
82
|
+
self._client.delete_header(name)
|
|
83
|
+
|
|
84
|
+
def __iter__(self) -> Iterator[str]:
|
|
85
|
+
"""Iterate over header names."""
|
|
86
|
+
return iter(self._client.get_headers().keys())
|
|
87
|
+
|
|
88
|
+
def __len__(self) -> int:
|
|
89
|
+
"""Return the number of headers."""
|
|
90
|
+
return len(self._client.get_headers())
|
|
91
|
+
|
|
92
|
+
def __contains__(self, name: object) -> bool:
|
|
93
|
+
"""Check if a header exists."""
|
|
94
|
+
if not isinstance(name, str):
|
|
95
|
+
return False
|
|
96
|
+
return self._client.get_header(name) is not None
|
|
97
|
+
|
|
98
|
+
def __repr__(self) -> str:
|
|
99
|
+
"""Return string representation of headers."""
|
|
100
|
+
headers = self._client.get_headers()
|
|
101
|
+
return f"HeadersJar({headers})"
|
|
102
|
+
|
|
103
|
+
def get(self, name: str, default: str | None = None) -> str | None:
|
|
104
|
+
"""Get a header value with a default fallback."""
|
|
105
|
+
value = self._client.get_header(name)
|
|
106
|
+
return value if value is not None else default
|
|
107
|
+
|
|
108
|
+
def update(self, headers: dict[str, str]) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Update multiple headers at once.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
headers: Dictionary of header names to values
|
|
114
|
+
"""
|
|
115
|
+
self._client.headers_update(headers)
|
|
116
|
+
|
|
117
|
+
def clear(self) -> None:
|
|
118
|
+
"""Remove all headers."""
|
|
119
|
+
self._client.clear_headers()
|
|
120
|
+
|
|
121
|
+
def set(self, name: str, value: str) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Set a single header.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
name: Header name
|
|
127
|
+
value: Header value
|
|
128
|
+
"""
|
|
129
|
+
self._client.set_header(name, value)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class CookieJar(MutableMapping):
|
|
133
|
+
"""
|
|
134
|
+
A dict-like container for managing HTTP cookies, compatible with requests.Session.cookies API.
|
|
135
|
+
|
|
136
|
+
Examples:
|
|
137
|
+
client = Client()
|
|
138
|
+
|
|
139
|
+
# Set cookies
|
|
140
|
+
client.cookies['session_id'] = 'abc123'
|
|
141
|
+
client.cookies['user_token'] = 'xyz789'
|
|
142
|
+
|
|
143
|
+
# Get cookies
|
|
144
|
+
value = client.cookies['session_id']
|
|
145
|
+
value = client.cookies.get('user_token', 'default')
|
|
146
|
+
|
|
147
|
+
# Update multiple cookies
|
|
148
|
+
client.cookies.update({'key1': 'value1', 'key2': 'value2'})
|
|
149
|
+
|
|
150
|
+
# Delete cookies
|
|
151
|
+
del client.cookies['session_id']
|
|
152
|
+
|
|
153
|
+
# Clear all cookies
|
|
154
|
+
client.cookies.clear()
|
|
155
|
+
|
|
156
|
+
# Check existence
|
|
157
|
+
if 'session_id' in client.cookies:
|
|
158
|
+
print("Session exists")
|
|
159
|
+
|
|
160
|
+
# Convert to dict
|
|
161
|
+
all_cookies = dict(client.cookies)
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(self, client: RClient):
|
|
165
|
+
"""Initialize CookieJar with a reference to the client."""
|
|
166
|
+
self._client = client
|
|
167
|
+
|
|
168
|
+
def __getitem__(self, name: str) -> str:
|
|
169
|
+
"""Get a cookie value by name."""
|
|
170
|
+
value = self._client.get_cookie(name)
|
|
171
|
+
if value is None:
|
|
172
|
+
raise KeyError(name)
|
|
173
|
+
return value
|
|
174
|
+
|
|
175
|
+
def __setitem__(self, name: str, value: str) -> None:
|
|
176
|
+
"""Set a cookie by name."""
|
|
177
|
+
self._client.set_cookie(name, value)
|
|
178
|
+
|
|
179
|
+
def __delitem__(self, name: str) -> None:
|
|
180
|
+
"""Delete a cookie by name."""
|
|
181
|
+
# Check if cookie exists first
|
|
182
|
+
if self._client.get_cookie(name) is None:
|
|
183
|
+
raise KeyError(name)
|
|
184
|
+
self._client.delete_cookie(name)
|
|
185
|
+
|
|
186
|
+
def __iter__(self) -> Iterator[str]:
|
|
187
|
+
"""Iterate over cookie names."""
|
|
188
|
+
return iter(self._client.get_all_cookies().keys())
|
|
189
|
+
|
|
190
|
+
def __len__(self) -> int:
|
|
191
|
+
"""Return the number of cookies."""
|
|
192
|
+
return len(self._client.get_all_cookies())
|
|
193
|
+
|
|
194
|
+
def __contains__(self, name: object) -> bool:
|
|
195
|
+
"""Check if a cookie exists."""
|
|
196
|
+
if not isinstance(name, str):
|
|
197
|
+
return False
|
|
198
|
+
return self._client.get_cookie(name) is not None
|
|
199
|
+
|
|
200
|
+
def __repr__(self) -> str:
|
|
201
|
+
"""Return string representation of cookies."""
|
|
202
|
+
cookies = self._client.get_all_cookies()
|
|
203
|
+
return f"CookieJar({cookies})"
|
|
204
|
+
|
|
205
|
+
def get(self, name: str, default: str | None = None) -> str | None:
|
|
206
|
+
"""Get a cookie value with a default fallback."""
|
|
207
|
+
value = self._client.get_cookie(name)
|
|
208
|
+
return value if value is not None else default
|
|
209
|
+
|
|
210
|
+
def update(self, cookies: dict[str, str], domain: str | None = None, path: str | None = None) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Update multiple cookies at once.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
cookies: Dictionary of cookie names to values
|
|
216
|
+
domain: Optional domain for the cookies (e.g., ".example.com")
|
|
217
|
+
path: Optional path for the cookies (e.g., "/")
|
|
218
|
+
"""
|
|
219
|
+
self._client.update_cookies(cookies, domain=domain, path=path)
|
|
220
|
+
|
|
221
|
+
def clear(self) -> None:
|
|
222
|
+
"""Remove all cookies."""
|
|
223
|
+
self._client.clear_cookies()
|
|
224
|
+
|
|
225
|
+
def set(self, name: str, value: str, domain: str | None = None, path: str | None = None) -> None:
|
|
226
|
+
"""
|
|
227
|
+
Set a single cookie with optional domain and path.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
name: Cookie name
|
|
231
|
+
value: Cookie value
|
|
232
|
+
domain: Optional domain (e.g., ".example.com")
|
|
233
|
+
path: Optional path (e.g., "/")
|
|
234
|
+
"""
|
|
235
|
+
self._client.set_cookie(name, value, domain=domain, path=path)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class Client(RClient):
|
|
239
|
+
"""Initializes an HTTP client that can impersonate web browsers."""
|
|
240
|
+
|
|
241
|
+
def __init__(
|
|
242
|
+
self,
|
|
243
|
+
auth: tuple[str, str | None] | None = None,
|
|
244
|
+
auth_bearer: str | None = None,
|
|
245
|
+
params: dict[str, str] | None = None,
|
|
246
|
+
headers: dict[str, str] | None = None,
|
|
247
|
+
cookies: dict[str, str] | None = None,
|
|
248
|
+
cookie_store: bool | None = True,
|
|
249
|
+
split_cookies: bool | None = False,
|
|
250
|
+
referer: bool | None = True,
|
|
251
|
+
proxy: str | None = None,
|
|
252
|
+
timeout: float | None = 30,
|
|
253
|
+
impersonate: IMPERSONATE | None = None,
|
|
254
|
+
impersonate_os: IMPERSONATE_OS | None = None,
|
|
255
|
+
follow_redirects: bool | None = True,
|
|
256
|
+
max_redirects: int | None = 20,
|
|
257
|
+
verify: bool | None = True,
|
|
258
|
+
ca_cert_file: str | None = None,
|
|
259
|
+
https_only: bool | None = False,
|
|
260
|
+
http1_only: bool | None = False,
|
|
261
|
+
http2_only: bool | None = False,
|
|
262
|
+
pool_idle_timeout: float | None = None,
|
|
263
|
+
pool_max_idle_per_host: int | None = None,
|
|
264
|
+
tcp_nodelay: bool | None = None,
|
|
265
|
+
tcp_keepalive: float | None = None,
|
|
266
|
+
):
|
|
267
|
+
"""
|
|
268
|
+
Args:
|
|
269
|
+
auth: a tuple containing the username and an optional password for basic authentication. Default is None.
|
|
270
|
+
auth_bearer: a string representing the bearer token for bearer token authentication. Default is None.
|
|
271
|
+
params: a map of query parameters to append to the URL. Default is None.
|
|
272
|
+
headers: an optional ordered map of HTTP headers with strict order preservation.
|
|
273
|
+
Headers will be sent in the exact order specified, with automatic positioning of:
|
|
274
|
+
- Host (first position)
|
|
275
|
+
- Content-Length (second position for POST/PUT/PATCH)
|
|
276
|
+
- Content-Type (third position if auto-calculated)
|
|
277
|
+
- cookie (second-to-last position)
|
|
278
|
+
- priority (last position)
|
|
279
|
+
Example: {"User-Agent": "...", "Accept": "...", "Accept-Language": "..."}
|
|
280
|
+
Note: Python 3.7+ dict maintains insertion order by default.
|
|
281
|
+
cookies: initial cookies to set for the client. These cookies will be included in all requests.
|
|
282
|
+
Can be updated later using client.cookies.update(). Default is None.
|
|
283
|
+
cookie_store: enable a persistent cookie store. Received cookies will be preserved and included
|
|
284
|
+
in additional requests. Default is True.
|
|
285
|
+
split_cookies: split cookies into multiple `cookie` headers (HTTP/2 style) instead of a single
|
|
286
|
+
`Cookie` header. Useful for mimicking browser behavior in HTTP/2. Default is False.
|
|
287
|
+
When True: cookie: a=1 \n cookie: b=2 \n cookie: c=3
|
|
288
|
+
When False: Cookie: a=1; b=2; c=3
|
|
289
|
+
referer: automatic setting of the `Referer` header. Default is True.
|
|
290
|
+
proxy: proxy URL for HTTP requests, example: "socks5://127.0.0.1:9150". Default is None.
|
|
291
|
+
timeout: timeout for HTTP requests in seconds. Default is 30.
|
|
292
|
+
impersonate: impersonate browser. Supported browsers:
|
|
293
|
+
"chrome_100", "chrome_101", "chrome_104", "chrome_105", "chrome_106",
|
|
294
|
+
"chrome_107", "chrome_108", "chrome_109", "chrome_114", "chrome_116",
|
|
295
|
+
"chrome_117", "chrome_118", "chrome_119", "chrome_120", "chrome_123",
|
|
296
|
+
"chrome_124", "chrome_126", "chrome_127", "chrome_128", "chrome_129",
|
|
297
|
+
"chrome_130", "chrome_131", "chrome_133"
|
|
298
|
+
"safari_15.3", "safari_15.5", "safari_15.6.1", "safari_16",
|
|
299
|
+
"safari_16.5", "safari_17.0", "safari_17.2.1", "safari_17.4.1",
|
|
300
|
+
"safari_17.5", "safari_18", "safari_18.2",
|
|
301
|
+
"safari_ios_16.5", "safari_ios_17.2", "safari_ios_17.4.1", "safari_ios_18.1.1",
|
|
302
|
+
"safari_ipad_18",
|
|
303
|
+
"okhttp_3.9", "okhttp_3.11", "okhttp_3.13", "okhttp_3.14", "okhttp_4.9",
|
|
304
|
+
"okhttp_4.10", "okhttp_5",
|
|
305
|
+
"edge_101", "edge_122", "edge_127", "edge_131",
|
|
306
|
+
"firefox_109", "firefox_117", "firefox_128", "firefox_133", "firefox_135".
|
|
307
|
+
Default is None.
|
|
308
|
+
impersonate_os: impersonate OS. Supported OS:
|
|
309
|
+
"android", "ios", "linux", "macos", "windows". Default is None.
|
|
310
|
+
follow_redirects: a boolean to enable or disable following redirects. Default is True.
|
|
311
|
+
max_redirects: the maximum number of redirects if `follow_redirects` is True. Default is 20.
|
|
312
|
+
verify: an optional boolean indicating whether to verify SSL certificates. Default is True.
|
|
313
|
+
ca_cert_file: path to CA certificate store. Default is None.
|
|
314
|
+
https_only: restrict the Client to be used with HTTPS only requests. Default is False.
|
|
315
|
+
http1_only: if true - use only HTTP/1.1. Default is False.
|
|
316
|
+
http2_only: if true - use only HTTP/2. Default is False.
|
|
317
|
+
Note: http1_only and http2_only are mutually exclusive. If both are true, http1_only takes precedence.
|
|
318
|
+
"""
|
|
319
|
+
super().__init__()
|
|
320
|
+
self._cookies_jar: CookieJar | None = None
|
|
321
|
+
self._headers_jar: HeadersJar | None = None
|
|
322
|
+
|
|
323
|
+
# Set initial cookies if provided
|
|
324
|
+
if cookies:
|
|
325
|
+
self.update_cookies(cookies)
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def headers(self) -> HeadersJar:
|
|
329
|
+
"""
|
|
330
|
+
Access the headers for dict-like header management.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
HeadersJar: A dict-like container for managing headers.
|
|
334
|
+
|
|
335
|
+
Examples:
|
|
336
|
+
# Set headers
|
|
337
|
+
client.headers['User-Agent'] = 'CustomAgent/1.0'
|
|
338
|
+
|
|
339
|
+
# Get headers
|
|
340
|
+
value = client.headers['User-Agent']
|
|
341
|
+
|
|
342
|
+
# Update headers
|
|
343
|
+
client.headers.update({'Accept': 'application/json'})
|
|
344
|
+
|
|
345
|
+
# Delete headers
|
|
346
|
+
del client.headers['User-Agent']
|
|
347
|
+
|
|
348
|
+
# Clear all
|
|
349
|
+
client.headers.clear()
|
|
350
|
+
"""
|
|
351
|
+
if self._headers_jar is None:
|
|
352
|
+
self._headers_jar = HeadersJar(self)
|
|
353
|
+
return self._headers_jar
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def cookies(self) -> CookieJar:
|
|
357
|
+
"""
|
|
358
|
+
Access the cookie jar for dict-like cookie management.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
CookieJar: A dict-like container for managing cookies.
|
|
362
|
+
|
|
363
|
+
Examples:
|
|
364
|
+
# Set cookies
|
|
365
|
+
client.cookies['session_id'] = 'abc123'
|
|
366
|
+
|
|
367
|
+
# Get cookies
|
|
368
|
+
value = client.cookies['session_id']
|
|
369
|
+
|
|
370
|
+
# Update cookies
|
|
371
|
+
client.cookies.update({'key': 'value'})
|
|
372
|
+
|
|
373
|
+
# Delete cookies
|
|
374
|
+
del client.cookies['session_id']
|
|
375
|
+
|
|
376
|
+
# Clear all
|
|
377
|
+
client.cookies.clear()
|
|
378
|
+
"""
|
|
379
|
+
if self._cookies_jar is None:
|
|
380
|
+
self._cookies_jar = CookieJar(self)
|
|
381
|
+
return self._cookies_jar
|
|
382
|
+
|
|
383
|
+
def __enter__(self) -> Client:
|
|
384
|
+
return self
|
|
385
|
+
|
|
386
|
+
def __exit__(self, *args):
|
|
387
|
+
del self
|
|
388
|
+
|
|
389
|
+
def request(self, method: HttpMethod, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
390
|
+
"""
|
|
391
|
+
Send an HTTP request with support for requests-toolbelt MultipartEncoder.
|
|
392
|
+
|
|
393
|
+
Supports both native primp format and requests-toolbelt MultipartEncoder:
|
|
394
|
+
- Native: request(url, data={...}, files={...})
|
|
395
|
+
- Toolbelt: request(url, data=MultipartEncoder(...))
|
|
396
|
+
"""
|
|
397
|
+
# Check if data is a MultipartEncoder from requests-toolbelt
|
|
398
|
+
data = kwargs.get('data')
|
|
399
|
+
if data is not None and hasattr(data, 'fields') and hasattr(data, 'content_type'):
|
|
400
|
+
# This looks like a MultipartEncoder
|
|
401
|
+
# Extract fields and convert to primp format
|
|
402
|
+
converted_data = {}
|
|
403
|
+
converted_files = {}
|
|
404
|
+
|
|
405
|
+
try:
|
|
406
|
+
# MultipartEncoder.fields is a dict-like object
|
|
407
|
+
for field_name, field_value in data.fields.items():
|
|
408
|
+
if isinstance(field_value, tuple):
|
|
409
|
+
# File field: (filename, file_obj, content_type)
|
|
410
|
+
if len(field_value) >= 2:
|
|
411
|
+
filename = field_value[0]
|
|
412
|
+
file_obj = field_value[1]
|
|
413
|
+
|
|
414
|
+
# Read the file content
|
|
415
|
+
if hasattr(file_obj, 'read'):
|
|
416
|
+
file_content = file_obj.read()
|
|
417
|
+
# Reset file pointer if possible
|
|
418
|
+
if hasattr(file_obj, 'seek'):
|
|
419
|
+
try:
|
|
420
|
+
file_obj.seek(0)
|
|
421
|
+
except:
|
|
422
|
+
pass
|
|
423
|
+
else:
|
|
424
|
+
file_content = file_obj
|
|
425
|
+
|
|
426
|
+
# Add mime type if provided
|
|
427
|
+
if len(field_value) >= 3:
|
|
428
|
+
mime_type = field_value[2]
|
|
429
|
+
converted_files[field_name] = (filename, file_content, mime_type)
|
|
430
|
+
else:
|
|
431
|
+
converted_files[field_name] = (filename, file_content)
|
|
432
|
+
else:
|
|
433
|
+
# Regular field
|
|
434
|
+
if isinstance(field_value, bytes):
|
|
435
|
+
converted_data[field_name] = field_value.decode('utf-8')
|
|
436
|
+
else:
|
|
437
|
+
converted_data[field_name] = str(field_value)
|
|
438
|
+
|
|
439
|
+
# Replace data and files in kwargs
|
|
440
|
+
if converted_data:
|
|
441
|
+
kwargs['data'] = converted_data
|
|
442
|
+
else:
|
|
443
|
+
kwargs.pop('data', None)
|
|
444
|
+
|
|
445
|
+
if converted_files:
|
|
446
|
+
kwargs['files'] = converted_files
|
|
447
|
+
|
|
448
|
+
except Exception as e:
|
|
449
|
+
# If conversion fails, fall back to treating it as raw content
|
|
450
|
+
# Read the encoder as bytes and send as content
|
|
451
|
+
if hasattr(data, 'read'):
|
|
452
|
+
kwargs['content'] = data.read()
|
|
453
|
+
kwargs.pop('data', None)
|
|
454
|
+
|
|
455
|
+
# Get content type from encoder
|
|
456
|
+
if hasattr(data, 'content_type'):
|
|
457
|
+
headers = kwargs.get('headers', {})
|
|
458
|
+
if not isinstance(headers, dict):
|
|
459
|
+
headers = dict(headers)
|
|
460
|
+
headers['Content-Type'] = data.content_type
|
|
461
|
+
kwargs['headers'] = headers
|
|
462
|
+
|
|
463
|
+
return super().request(method=method, url=url, **kwargs)
|
|
464
|
+
|
|
465
|
+
def get(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
466
|
+
return self.request(method="GET", url=url, **kwargs)
|
|
467
|
+
|
|
468
|
+
def head(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
469
|
+
return self.request(method="HEAD", url=url, **kwargs)
|
|
470
|
+
|
|
471
|
+
def options(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
472
|
+
return self.request(method="OPTIONS", url=url, **kwargs)
|
|
473
|
+
|
|
474
|
+
def delete(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
475
|
+
return self.request(method="DELETE", url=url, **kwargs)
|
|
476
|
+
|
|
477
|
+
def post(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
478
|
+
return self.request(method="POST", url=url, **kwargs)
|
|
479
|
+
|
|
480
|
+
def put(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
481
|
+
return self.request(method="PUT", url=url, **kwargs)
|
|
482
|
+
|
|
483
|
+
def patch(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
|
|
484
|
+
return self.request(method="PATCH", url=url, **kwargs)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
class AsyncClient(Client):
|
|
488
|
+
def __init__(self,
|
|
489
|
+
auth: tuple[str, str | None] | None = None,
|
|
490
|
+
auth_bearer: str | None = None,
|
|
491
|
+
params: dict[str, str] | None = None,
|
|
492
|
+
headers: dict[str, str] | None = None,
|
|
493
|
+
cookies: dict[str, str] | None = None,
|
|
494
|
+
cookie_store: bool | None = True,
|
|
495
|
+
split_cookies: bool | None = False,
|
|
496
|
+
referer: bool | None = True,
|
|
497
|
+
proxy: str | None = None,
|
|
498
|
+
timeout: float | None = None,
|
|
499
|
+
impersonate: IMPERSONATE | None = None,
|
|
500
|
+
impersonate_os: IMPERSONATE_OS | None = None,
|
|
501
|
+
follow_redirects: bool | None = True,
|
|
502
|
+
max_redirects: int | None = 20,
|
|
503
|
+
verify: bool | None = True,
|
|
504
|
+
ca_cert_file: str | None = None,
|
|
505
|
+
https_only: bool | None = False,
|
|
506
|
+
http1_only: bool | None = False,
|
|
507
|
+
http2_only: bool | None = False,
|
|
508
|
+
# Performance optimization parameters
|
|
509
|
+
pool_idle_timeout: float | None = None,
|
|
510
|
+
pool_max_idle_per_host: int | None = None,
|
|
511
|
+
tcp_nodelay: bool | None = None,
|
|
512
|
+
tcp_keepalive: float | None = None,
|
|
513
|
+
):
|
|
514
|
+
super().__init__(
|
|
515
|
+
auth=auth,
|
|
516
|
+
auth_bearer=auth_bearer,
|
|
517
|
+
params=params,
|
|
518
|
+
headers=headers,
|
|
519
|
+
cookies=cookies,
|
|
520
|
+
cookie_store=cookie_store,
|
|
521
|
+
split_cookies=split_cookies,
|
|
522
|
+
referer=referer,
|
|
523
|
+
proxy=proxy,
|
|
524
|
+
timeout=timeout,
|
|
525
|
+
impersonate=impersonate,
|
|
526
|
+
impersonate_os=impersonate_os,
|
|
527
|
+
follow_redirects=follow_redirects,
|
|
528
|
+
max_redirects=max_redirects,
|
|
529
|
+
verify=verify,
|
|
530
|
+
ca_cert_file=ca_cert_file,
|
|
531
|
+
https_only=https_only,
|
|
532
|
+
http1_only=http1_only,
|
|
533
|
+
http2_only=http2_only,
|
|
534
|
+
pool_idle_timeout=pool_idle_timeout,
|
|
535
|
+
pool_max_idle_per_host=pool_max_idle_per_host,
|
|
536
|
+
tcp_nodelay=tcp_nodelay,
|
|
537
|
+
tcp_keepalive=tcp_keepalive,
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
async def __aenter__(self) -> AsyncClient:
|
|
541
|
+
return self
|
|
542
|
+
|
|
543
|
+
async def __aexit__(self, *args):
|
|
544
|
+
del self
|
|
545
|
+
|
|
546
|
+
async def _run_sync_asyncio(self, fn, *args, **kwargs):
|
|
547
|
+
loop = asyncio.get_running_loop()
|
|
548
|
+
return await loop.run_in_executor(None, partial(fn, *args, **kwargs))
|
|
549
|
+
|
|
550
|
+
async def request(self, method: HttpMethod, url: str, **kwargs: Unpack[RequestParams]): # type: ignore
|
|
551
|
+
return await self._run_sync_asyncio(super().request, method=method, url=url, **kwargs)
|
|
552
|
+
|
|
553
|
+
async def get(self, url: str, **kwargs: Unpack[RequestParams]): # type: ignore
|
|
554
|
+
return await self.request(method="GET", url=url, **kwargs)
|
|
555
|
+
|
|
556
|
+
async def head(self, url: str, **kwargs: Unpack[RequestParams]): # type: ignore
|
|
557
|
+
return await self.request(method="HEAD", url=url, **kwargs)
|
|
558
|
+
|
|
559
|
+
async def options(self, url: str, **kwargs: Unpack[RequestParams]): # type: ignore
|
|
560
|
+
return await self.request(method="OPTIONS", url=url, **kwargs)
|
|
561
|
+
|
|
562
|
+
async def delete(self, url: str, **kwargs: Unpack[RequestParams]): # type: ignore
|
|
563
|
+
return await self.request(method="DELETE", url=url, **kwargs)
|
|
564
|
+
|
|
565
|
+
async def post(self, url: str, **kwargs: Unpack[RequestParams]): # type: ignore
|
|
566
|
+
return await self.request(method="POST", url=url, **kwargs)
|
|
567
|
+
|
|
568
|
+
async def put(self, url: str, **kwargs: Unpack[RequestParams]): # type: ignore
|
|
569
|
+
return await self.request(method="PUT", url=url, **kwargs)
|
|
570
|
+
|
|
571
|
+
async def patch(self, url: str, **kwargs: Unpack[RequestParams]): # type: ignore
|
|
572
|
+
return await self.request(method="PATCH", url=url, **kwargs)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def request(
|
|
576
|
+
method: HttpMethod,
|
|
577
|
+
url: str,
|
|
578
|
+
impersonate: IMPERSONATE | None = None,
|
|
579
|
+
impersonate_os: IMPERSONATE_OS | None = None,
|
|
580
|
+
verify: bool | None = True,
|
|
581
|
+
ca_cert_file: str | None = None,
|
|
582
|
+
**kwargs: Unpack[RequestParams],
|
|
583
|
+
):
|
|
584
|
+
"""
|
|
585
|
+
Args:
|
|
586
|
+
method: the HTTP method to use (e.g., "GET", "POST").
|
|
587
|
+
url: the URL to which the request will be made.
|
|
588
|
+
impersonate: impersonate browser. Supported browsers:
|
|
589
|
+
"chrome_100", "chrome_101", "chrome_104", "chrome_105", "chrome_106",
|
|
590
|
+
"chrome_107", "chrome_108", "chrome_109", "chrome_114", "chrome_116",
|
|
591
|
+
"chrome_117", "chrome_118", "chrome_119", "chrome_120", "chrome_123",
|
|
592
|
+
"chrome_124", "chrome_126", "chrome_127", "chrome_128", "chrome_129",
|
|
593
|
+
"chrome_130", "chrome_131", "chrome_133",
|
|
594
|
+
"safari_15.3", "safari_15.5", "safari_15.6.1", "safari_16",
|
|
595
|
+
"safari_16.5", "safari_17.0", "safari_17.2.1", "safari_17.4.1",
|
|
596
|
+
"safari_17.5", "safari_18", "safari_18.2",
|
|
597
|
+
"safari_ios_16.5", "safari_ios_17.2", "safari_ios_17.4.1", "safari_ios_18.1.1",
|
|
598
|
+
"safari_ipad_18",
|
|
599
|
+
"okhttp_3.9", "okhttp_3.11", "okhttp_3.13", "okhttp_3.14", "okhttp_4.9",
|
|
600
|
+
"okhttp_4.10", "okhttp_5",
|
|
601
|
+
"edge_101", "edge_122", "edge_127", "edge_131",
|
|
602
|
+
"firefox_109", "firefox_117", "firefox_128", "firefox_133", "firefox_135".
|
|
603
|
+
Default is None.
|
|
604
|
+
impersonate_os: impersonate OS. Supported OS:
|
|
605
|
+
"android", "ios", "linux", "macos", "windows". Default is None.
|
|
606
|
+
verify: an optional boolean indicating whether to verify SSL certificates. Default is True.
|
|
607
|
+
ca_cert_file: path to CA certificate store. Default is None.
|
|
608
|
+
auth: a tuple containing the username and an optional password for basic authentication. Default is None.
|
|
609
|
+
auth_bearer: a string representing the bearer token for bearer token authentication. Default is None.
|
|
610
|
+
params: a map of query parameters to append to the URL. Default is None.
|
|
611
|
+
headers: an optional map of HTTP headers to send with requests. If `impersonate` is set, this will be ignored.
|
|
612
|
+
cookies: an optional map of cookies to send with requests as the `Cookie` header.
|
|
613
|
+
timeout: the timeout for the request in seconds. Default is 30.
|
|
614
|
+
content: the content to send in the request body as bytes. Default is None.
|
|
615
|
+
data: the form data to send in the request body. Default is None.
|
|
616
|
+
json: a JSON serializable object to send in the request body. Default is None.
|
|
617
|
+
files: a map of file fields to file paths to be sent as multipart/form-data. Default is None.
|
|
618
|
+
"""
|
|
619
|
+
with Client(
|
|
620
|
+
impersonate=impersonate,
|
|
621
|
+
impersonate_os=impersonate_os,
|
|
622
|
+
verify=verify,
|
|
623
|
+
ca_cert_file=ca_cert_file,
|
|
624
|
+
) as client:
|
|
625
|
+
return client.request(method, url, **kwargs)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def get(url: str, **kwargs: Unpack[ClientRequestParams]):
|
|
629
|
+
return request(method="GET", url=url, **kwargs)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def head(url: str, **kwargs: Unpack[ClientRequestParams]):
|
|
633
|
+
return request(method="HEAD", url=url, **kwargs)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def options(url: str, **kwargs: Unpack[ClientRequestParams]):
|
|
637
|
+
return request(method="OPTIONS", url=url, **kwargs)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def delete(url: str, **kwargs: Unpack[ClientRequestParams]):
|
|
641
|
+
return request(method="DELETE", url=url, **kwargs)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def post(url: str, **kwargs: Unpack[ClientRequestParams]):
|
|
645
|
+
return request(method="POST", url=url, **kwargs)
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def put(url: str, **kwargs: Unpack[ClientRequestParams]):
|
|
649
|
+
return request(method="PUT", url=url, **kwargs)
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def patch(url: str, **kwargs: Unpack[ClientRequestParams]):
|
|
653
|
+
return request(method="PATCH", url=url, **kwargs)
|
|
Binary file
|