never-primp 1.1.1__cp38-abi3-macosx_11_0_arm64.whl → 1.2.1__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.

Potentially problematic release.


This version of never-primp might be problematic. Click here for more details.

never_primp/__init__.py CHANGED
@@ -27,6 +27,108 @@ else:
27
27
  RequestParams = ClientRequestParams = TypedDict
28
28
 
29
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
+
30
132
  class CookieJar(MutableMapping):
31
133
  """
32
134
  A dict-like container for managing HTTP cookies, compatible with requests.Session.cookies API.
@@ -216,11 +318,40 @@ class Client(RClient):
216
318
  """
217
319
  super().__init__()
218
320
  self._cookies_jar: CookieJar | None = None
321
+ self._headers_jar: HeadersJar | None = None
219
322
 
220
323
  # Set initial cookies if provided
221
324
  if cookies:
222
325
  self.update_cookies(cookies)
223
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
+
224
355
  @property
225
356
  def cookies(self) -> CookieJar:
226
357
  """
Binary file
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import sys
4
+ from collections.abc import MutableMapping
4
5
  from typing import Any, Iterator, Literal, TypedDict
5
6
 
6
7
  if sys.version_info <= (3, 11):
@@ -44,11 +45,12 @@ class RequestParams(TypedDict, total=False):
44
45
  data: dict[str, Any] | None
45
46
  json: Any | None
46
47
  files: dict[str, str] | None
48
+ proxy: str | None
49
+ verify: bool | None
47
50
 
48
51
  class ClientRequestParams(RequestParams):
49
52
  impersonate: IMPERSONATE | None
50
53
  impersonate_os: IMPERSONATE_OS | None
51
- verify: bool | None
52
54
  ca_cert_file: str | None
53
55
 
54
56
  class Response:
@@ -75,6 +77,28 @@ class Response:
75
77
  @property
76
78
  def text_rich(self) -> str: ...
77
79
 
80
+ class HeadersJar(MutableMapping[str, str]):
81
+ """Dict-like container for managing HTTP headers."""
82
+ def __getitem__(self, name: str) -> str: ...
83
+ def __setitem__(self, name: str, value: str) -> None: ...
84
+ def __delitem__(self, name: str) -> None: ...
85
+ def __iter__(self) -> Iterator[str]: ...
86
+ def __len__(self) -> int: ...
87
+ def get(self, name: str, default: str | None = None) -> str | None: ...
88
+ def update(self, headers: dict[str, str]) -> None: ...
89
+ def clear(self) -> None: ...
90
+
91
+ class CookieJar(MutableMapping[str, str]):
92
+ """Dict-like container for managing HTTP cookies."""
93
+ def __getitem__(self, name: str) -> str: ...
94
+ def __setitem__(self, name: str, value: str) -> None: ...
95
+ def __delitem__(self, name: str) -> None: ...
96
+ def __iter__(self) -> Iterator[str]: ...
97
+ def __len__(self) -> int: ...
98
+ def get(self, name: str, default: str | None = None) -> str | None: ...
99
+ def update(self, cookies: dict[str, str], domain: str | None = None, path: str | None = None) -> None: ...
100
+ def clear(self) -> None: ...
101
+
78
102
  class RClient:
79
103
  def __init__(
80
104
  self,
@@ -103,9 +127,9 @@ class RClient:
103
127
  tcp_keepalive: float | None = None,
104
128
  ): ...
105
129
  @property
106
- def headers(self) -> dict[str, str]: ...
107
- @headers.setter
108
- def headers(self, headers: dict[str, str]) -> None: ...
130
+ def headers(self) -> HeadersJar: ...
131
+ @property
132
+ def cookies(self) -> CookieJar: ...
109
133
  def headers_update(self, headers: dict[str, str]) -> None: ...
110
134
  # Cookie management methods (no URL required)
111
135
  def get_all_cookies(self) -> dict[str, str]: ...
@@ -114,6 +138,11 @@ class RClient:
114
138
  def update_cookies(self, cookies: dict[str, str], domain: str | None = None, path: str | None = None) -> None: ...
115
139
  def delete_cookie(self, name: str) -> None: ...
116
140
  def clear_cookies(self) -> None: ...
141
+ # Header management methods
142
+ def set_header(self, name: str, value: str) -> None: ...
143
+ def get_header(self, name: str) -> str | None: ...
144
+ def delete_header(self, name: str) -> None: ...
145
+ def clear_headers(self) -> None: ...
117
146
  # Client properties
118
147
  @property
119
148
  def auth(self) -> tuple[str, str | None] | None: ...
@@ -147,20 +176,415 @@ class RClient:
147
176
  def impersonate_os(self) -> str | None: ...
148
177
  @impersonate_os.setter
149
178
  def impersonate_os(self, impersonate: IMPERSONATE_OS) -> None: ...
150
- def request(self, method: HttpMethod, url: str, **kwargs: Unpack[RequestParams]) -> Response: ...
151
- def get(self, url: str, **kwargs: Unpack[RequestParams]) -> Response: ...
152
- def head(self, url: str, **kwargs: Unpack[RequestParams]) -> Response: ...
153
- def options(self, url: str, **kwargs: Unpack[RequestParams]) -> Response: ...
154
- def delete(self, url: str, **kwargs: Unpack[RequestParams]) -> Response: ...
155
- def post(self, url: str, **kwargs: Unpack[RequestParams]) -> Response: ...
156
- def put(self, url: str, **kwargs: Unpack[RequestParams]) -> Response: ...
157
- def patch(self, url: str, **kwargs: Unpack[RequestParams]) -> Response: ...
158
-
159
- def request(method: HttpMethod, url: str, **kwargs: Unpack[ClientRequestParams]) -> Response: ...
160
- def get(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response: ...
161
- def head(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response: ...
162
- def options(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response: ...
163
- def delete(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response: ...
164
- def post(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response: ...
165
- def put(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response: ...
166
- def patch(url: str, **kwargs: Unpack[ClientRequestParams]) -> Response: ...
179
+ # Request methods with explicit parameters for better IDE support
180
+ def request(
181
+ self,
182
+ method: HttpMethod,
183
+ url: str,
184
+ *,
185
+ params: dict[str, str] | None = None,
186
+ headers: dict[str, str] | None = None,
187
+ cookies: dict[str, str] | None = None,
188
+ auth: tuple[str, str | None] | None = None,
189
+ auth_bearer: str | None = None,
190
+ timeout: float | None = None,
191
+ content: bytes | None = None,
192
+ data: dict[str, Any] | None = None,
193
+ json: Any | None = None,
194
+ files: dict[str, str] | None = None,
195
+ proxy: str | None = None,
196
+ verify: bool | None = None,
197
+ ) -> Response: ...
198
+ def get(
199
+ self,
200
+ url: str,
201
+ *,
202
+ params: dict[str, str] | None = None,
203
+ headers: dict[str, str] | None = None,
204
+ cookies: dict[str, str] | None = None,
205
+ auth: tuple[str, str | None] | None = None,
206
+ auth_bearer: str | None = None,
207
+ timeout: float | None = None,
208
+ proxy: str | None = None,
209
+ verify: bool | None = None,
210
+ ) -> Response: ...
211
+ def head(
212
+ self,
213
+ url: str,
214
+ *,
215
+ params: dict[str, str] | None = None,
216
+ headers: dict[str, str] | None = None,
217
+ cookies: dict[str, str] | None = None,
218
+ auth: tuple[str, str | None] | None = None,
219
+ auth_bearer: str | None = None,
220
+ timeout: float | None = None,
221
+ proxy: str | None = None,
222
+ verify: bool | None = None,
223
+ ) -> Response: ...
224
+ def options(
225
+ self,
226
+ url: str,
227
+ *,
228
+ params: dict[str, str] | None = None,
229
+ headers: dict[str, str] | None = None,
230
+ cookies: dict[str, str] | None = None,
231
+ auth: tuple[str, str | None] | None = None,
232
+ auth_bearer: str | None = None,
233
+ timeout: float | None = None,
234
+ proxy: str | None = None,
235
+ verify: bool | None = None,
236
+ ) -> Response: ...
237
+ def delete(
238
+ self,
239
+ url: str,
240
+ *,
241
+ params: dict[str, str] | None = None,
242
+ headers: dict[str, str] | None = None,
243
+ cookies: dict[str, str] | None = None,
244
+ auth: tuple[str, str | None] | None = None,
245
+ auth_bearer: str | None = None,
246
+ timeout: float | None = None,
247
+ content: bytes | None = None,
248
+ data: dict[str, Any] | None = None,
249
+ json: Any | None = None,
250
+ proxy: str | None = None,
251
+ verify: bool | None = None,
252
+ ) -> Response: ...
253
+ def post(
254
+ self,
255
+ url: str,
256
+ *,
257
+ params: dict[str, str] | None = None,
258
+ headers: dict[str, str] | None = None,
259
+ cookies: dict[str, str] | None = None,
260
+ auth: tuple[str, str | None] | None = None,
261
+ auth_bearer: str | None = None,
262
+ timeout: float | None = None,
263
+ content: bytes | None = None,
264
+ data: dict[str, Any] | None = None,
265
+ json: Any | None = None,
266
+ files: dict[str, str] | None = None,
267
+ proxy: str | None = None,
268
+ verify: bool | None = None,
269
+ ) -> Response: ...
270
+ def put(
271
+ self,
272
+ url: str,
273
+ *,
274
+ params: dict[str, str] | None = None,
275
+ headers: dict[str, str] | None = None,
276
+ cookies: dict[str, str] | None = None,
277
+ auth: tuple[str, str | None] | None = None,
278
+ auth_bearer: str | None = None,
279
+ timeout: float | None = None,
280
+ content: bytes | None = None,
281
+ data: dict[str, Any] | None = None,
282
+ json: Any | None = None,
283
+ files: dict[str, str] | None = None,
284
+ proxy: str | None = None,
285
+ verify: bool | None = None,
286
+ ) -> Response: ...
287
+ def patch(
288
+ self,
289
+ url: str,
290
+ *,
291
+ params: dict[str, str] | None = None,
292
+ headers: dict[str, str] | None = None,
293
+ cookies: dict[str, str] | None = None,
294
+ auth: tuple[str, str | None] | None = None,
295
+ auth_bearer: str | None = None,
296
+ timeout: float | None = None,
297
+ content: bytes | None = None,
298
+ data: dict[str, Any] | None = None,
299
+ json: Any | None = None,
300
+ files: dict[str, str] | None = None,
301
+ proxy: str | None = None,
302
+ verify: bool | None = None,
303
+ ) -> Response: ...
304
+
305
+ class Client(RClient):
306
+ """HTTP client with dict-like cookies and headers management."""
307
+ @property
308
+ def headers(self) -> HeadersJar: ...
309
+ @property
310
+ def cookies(self) -> CookieJar: ...
311
+ def __enter__(self) -> Client: ...
312
+ def __exit__(self, *args) -> None: ...
313
+
314
+ class AsyncClient(Client):
315
+ """Async HTTP client with dict-like cookies and headers management."""
316
+ async def __aenter__(self) -> AsyncClient: ...
317
+ async def __aexit__(self, *args) -> None: ...
318
+ async def request(
319
+ self,
320
+ method: HttpMethod,
321
+ url: str,
322
+ *,
323
+ params: dict[str, str] | None = None,
324
+ headers: dict[str, str] | None = None,
325
+ cookies: dict[str, str] | None = None,
326
+ auth: tuple[str, str | None] | None = None,
327
+ auth_bearer: str | None = None,
328
+ timeout: float | None = None,
329
+ content: bytes | None = None,
330
+ data: dict[str, Any] | None = None,
331
+ json: Any | None = None,
332
+ files: dict[str, str] | None = None,
333
+ proxy: str | None = None,
334
+ verify: bool | None = None,
335
+ ) -> Response: ...
336
+ async def get(
337
+ self,
338
+ url: str,
339
+ *,
340
+ params: dict[str, str] | None = None,
341
+ headers: dict[str, str] | None = None,
342
+ cookies: dict[str, str] | None = None,
343
+ auth: tuple[str, str | None] | None = None,
344
+ auth_bearer: str | None = None,
345
+ timeout: float | None = None,
346
+ proxy: str | None = None,
347
+ verify: bool | None = None,
348
+ ) -> Response: ...
349
+ async def head(
350
+ self,
351
+ url: str,
352
+ *,
353
+ params: dict[str, str] | None = None,
354
+ headers: dict[str, str] | None = None,
355
+ cookies: dict[str, str] | None = None,
356
+ auth: tuple[str, str | None] | None = None,
357
+ auth_bearer: str | None = None,
358
+ timeout: float | None = None,
359
+ proxy: str | None = None,
360
+ verify: bool | None = None,
361
+ ) -> Response: ...
362
+ async def options(
363
+ self,
364
+ url: str,
365
+ *,
366
+ params: dict[str, str] | None = None,
367
+ headers: dict[str, str] | None = None,
368
+ cookies: dict[str, str] | None = None,
369
+ auth: tuple[str, str | None] | None = None,
370
+ auth_bearer: str | None = None,
371
+ timeout: float | None = None,
372
+ proxy: str | None = None,
373
+ verify: bool | None = None,
374
+ ) -> Response: ...
375
+ async def delete(
376
+ self,
377
+ url: str,
378
+ *,
379
+ params: dict[str, str] | None = None,
380
+ headers: dict[str, str] | None = None,
381
+ cookies: dict[str, str] | None = None,
382
+ auth: tuple[str, str | None] | None = None,
383
+ auth_bearer: str | None = None,
384
+ timeout: float | None = None,
385
+ content: bytes | None = None,
386
+ data: dict[str, Any] | None = None,
387
+ json: Any | None = None,
388
+ proxy: str | None = None,
389
+ verify: bool | None = None,
390
+ ) -> Response: ...
391
+ async def post(
392
+ self,
393
+ url: str,
394
+ *,
395
+ params: dict[str, str] | None = None,
396
+ headers: dict[str, str] | None = None,
397
+ cookies: dict[str, str] | None = None,
398
+ auth: tuple[str, str | None] | None = None,
399
+ auth_bearer: str | None = None,
400
+ timeout: float | None = None,
401
+ content: bytes | None = None,
402
+ data: dict[str, Any] | None = None,
403
+ json: Any | None = None,
404
+ files: dict[str, str] | None = None,
405
+ proxy: str | None = None,
406
+ verify: bool | None = None,
407
+ ) -> Response: ...
408
+ async def put(
409
+ self,
410
+ url: str,
411
+ *,
412
+ params: dict[str, str] | None = None,
413
+ headers: dict[str, str] | None = None,
414
+ cookies: dict[str, str] | None = None,
415
+ auth: tuple[str, str | None] | None = None,
416
+ auth_bearer: str | None = None,
417
+ timeout: float | None = None,
418
+ content: bytes | None = None,
419
+ data: dict[str, Any] | None = None,
420
+ json: Any | None = None,
421
+ files: dict[str, str] | None = None,
422
+ proxy: str | None = None,
423
+ verify: bool | None = None,
424
+ ) -> Response: ...
425
+ async def patch(
426
+ self,
427
+ url: str,
428
+ *,
429
+ params: dict[str, str] | None = None,
430
+ headers: dict[str, str] | None = None,
431
+ cookies: dict[str, str] | None = None,
432
+ auth: tuple[str, str | None] | None = None,
433
+ auth_bearer: str | None = None,
434
+ timeout: float | None = None,
435
+ content: bytes | None = None,
436
+ data: dict[str, Any] | None = None,
437
+ json: Any | None = None,
438
+ files: dict[str, str] | None = None,
439
+ proxy: str | None = None,
440
+ verify: bool | None = None,
441
+ ) -> Response: ...
442
+
443
+ # Module-level convenience functions with explicit parameters
444
+ def request(
445
+ method: HttpMethod,
446
+ url: str,
447
+ *,
448
+ params: dict[str, str] | None = None,
449
+ headers: dict[str, str] | None = None,
450
+ cookies: dict[str, str] | None = None,
451
+ auth: tuple[str, str | None] | None = None,
452
+ auth_bearer: str | None = None,
453
+ timeout: float | None = None,
454
+ content: bytes | None = None,
455
+ data: dict[str, Any] | None = None,
456
+ json: Any | None = None,
457
+ files: dict[str, str] | None = None,
458
+ impersonate: IMPERSONATE | None = None,
459
+ impersonate_os: IMPERSONATE_OS | None = None,
460
+ proxy: str | None = None,
461
+ verify: bool | None = True,
462
+ ca_cert_file: str | None = None,
463
+ ) -> Response: ...
464
+
465
+ def get(
466
+ url: str,
467
+ *,
468
+ params: dict[str, str] | None = None,
469
+ headers: dict[str, str] | None = None,
470
+ cookies: dict[str, str] | None = None,
471
+ auth: tuple[str, str | None] | None = None,
472
+ auth_bearer: str | None = None,
473
+ timeout: float | None = None,
474
+ impersonate: IMPERSONATE | None = None,
475
+ impersonate_os: IMPERSONATE_OS | None = None,
476
+ proxy: str | None = None,
477
+ verify: bool | None = True,
478
+ ca_cert_file: str | None = None,
479
+ ) -> Response: ...
480
+
481
+ def head(
482
+ url: str,
483
+ *,
484
+ params: dict[str, str] | None = None,
485
+ headers: dict[str, str] | None = None,
486
+ cookies: dict[str, str] | None = None,
487
+ auth: tuple[str, str | None] | None = None,
488
+ auth_bearer: str | None = None,
489
+ timeout: float | None = None,
490
+ impersonate: IMPERSONATE | None = None,
491
+ impersonate_os: IMPERSONATE_OS | None = None,
492
+ proxy: str | None = None,
493
+ verify: bool | None = True,
494
+ ca_cert_file: str | None = None,
495
+ ) -> Response: ...
496
+
497
+ def options(
498
+ url: str,
499
+ *,
500
+ params: dict[str, str] | None = None,
501
+ headers: dict[str, str] | None = None,
502
+ cookies: dict[str, str] | None = None,
503
+ auth: tuple[str, str | None] | None = None,
504
+ auth_bearer: str | None = None,
505
+ timeout: float | None = None,
506
+ impersonate: IMPERSONATE | None = None,
507
+ impersonate_os: IMPERSONATE_OS | None = None,
508
+ proxy: str | None = None,
509
+ verify: bool | None = True,
510
+ ca_cert_file: str | None = None,
511
+ ) -> Response: ...
512
+
513
+ def delete(
514
+ url: str,
515
+ *,
516
+ params: dict[str, str] | None = None,
517
+ headers: dict[str, str] | None = None,
518
+ cookies: dict[str, str] | None = None,
519
+ auth: tuple[str, str | None] | None = None,
520
+ auth_bearer: str | None = None,
521
+ timeout: float | None = None,
522
+ content: bytes | None = None,
523
+ data: dict[str, Any] | None = None,
524
+ json: Any | None = None,
525
+ impersonate: IMPERSONATE | None = None,
526
+ impersonate_os: IMPERSONATE_OS | None = None,
527
+ proxy: str | None = None,
528
+ verify: bool | None = True,
529
+ ca_cert_file: str | None = None,
530
+ ) -> Response: ...
531
+
532
+ def post(
533
+ url: str,
534
+ *,
535
+ params: dict[str, str] | None = None,
536
+ headers: dict[str, str] | None = None,
537
+ cookies: dict[str, str] | None = None,
538
+ auth: tuple[str, str | None] | None = None,
539
+ auth_bearer: str | None = None,
540
+ timeout: float | None = None,
541
+ content: bytes | None = None,
542
+ data: dict[str, Any] | None = None,
543
+ json: Any | None = None,
544
+ files: dict[str, str] | None = None,
545
+ impersonate: IMPERSONATE | None = None,
546
+ impersonate_os: IMPERSONATE_OS | None = None,
547
+ proxy: str | None = None,
548
+ verify: bool | None = True,
549
+ ca_cert_file: str | None = None,
550
+ ) -> Response: ...
551
+
552
+ def put(
553
+ url: str,
554
+ *,
555
+ params: dict[str, str] | None = None,
556
+ headers: dict[str, str] | None = None,
557
+ cookies: dict[str, str] | None = None,
558
+ auth: tuple[str, str | None] | None = None,
559
+ auth_bearer: str | None = None,
560
+ timeout: float | None = None,
561
+ content: bytes | None = None,
562
+ data: dict[str, Any] | None = None,
563
+ json: Any | None = None,
564
+ files: dict[str, str] | None = None,
565
+ impersonate: IMPERSONATE | None = None,
566
+ impersonate_os: IMPERSONATE_OS | None = None,
567
+ proxy: str | None = None,
568
+ verify: bool | None = True,
569
+ ca_cert_file: str | None = None,
570
+ ) -> Response: ...
571
+
572
+ def patch(
573
+ url: str,
574
+ *,
575
+ params: dict[str, str] | None = None,
576
+ headers: dict[str, str] | None = None,
577
+ cookies: dict[str, str] | None = None,
578
+ auth: tuple[str, str | None] | None = None,
579
+ auth_bearer: str | None = None,
580
+ timeout: float | None = None,
581
+ content: bytes | None = None,
582
+ data: dict[str, Any] | None = None,
583
+ json: Any | None = None,
584
+ files: dict[str, str] | None = None,
585
+ impersonate: IMPERSONATE | None = None,
586
+ impersonate_os: IMPERSONATE_OS | None = None,
587
+ proxy: str | None = None,
588
+ verify: bool | None = True,
589
+ ca_cert_file: str | None = None,
590
+ ) -> Response: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: never_primp
3
- Version: 1.1.1
3
+ Version: 1.2.1
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -73,6 +73,19 @@ Project-URL: Bug Tracker, https://github.com/Neverland/never_primp/issues
73
73
  | **异步支持** | ✅ | ❌ | ✅ | ❌ |
74
74
  | **原生 TLS** | ✅ | ❌ | ❌ | ✅ |
75
75
 
76
+
77
+ ## 🚀 HTTP 性能对比测试 (测试URL: https://www.baidu.com)
78
+ 测试代码: [benchmark.py](benchmark.py)
79
+
80
+ | | requests_go | curl_cffi | tls_client | requests | never_primp |primp |aiohttp | httpx |
81
+ |------|-------------|----------|-------|-----------|---|---|---|---|
82
+ | **单次** | 347.49ms | 122.45ms | 162.29ms | 646.89ms | 85.91ms |102.18ms | 74.90ms | 90.43ms |
83
+ | **for循环10次** | 315.79ms | 46.66ms | 21.81ms | 655.92ms | 19.45ms | 20.96ms | 21.42ms | 20.10ms |
84
+ | **TLS** | 31.70ms | 75.78ms | 140.48ms | ≈0 (复用或缓存) | 66.46ms | 81.23ms |53.47ms | 70.33ms |
85
+ | **响应大小** | 2443B| 628128B | 227B | 2443B | 28918B | 28918B | 29506B | 29506B |
86
+ | **并发 100任务 4worker** | 589.13ms | 56.46ms | 58.33ms | 696.74ms | 20.16ms | 20.66ms |20.95ms |23.18ms |
87
+
88
+ ![benchmark_results.png](benchmark_results.png)
76
89
  ---
77
90
 
78
91
  ## 📦 安装
@@ -93,11 +106,53 @@ pip install -U never-primp
93
106
 
94
107
  ## ✨ 核心特性
95
108
 
96
- ### 🚀 性能优化
109
+ ### 🚀 性能优化 ⚡ 新增
97
110
 
98
111
  <details>
99
112
  <summary><b>点击展开</b></summary>
100
113
 
114
+ #### 核心性能优化 (v1.2.0+)
115
+
116
+ **NEVER_PRIMP** 已实施多层性能优化,提供业界领先的性能:
117
+
118
+ ##### 1. **延迟客户端重建** 🆕
119
+ 智能脏标志机制,仅在必要时重建客户端:
120
+ - 配置修改时不立即重建(零开销)
121
+ - 首次请求时才重建(延迟构建)
122
+ - **性能提升**:配置操作快 **99.9%**,总体提升 **30-40%**
123
+
124
+ ```python
125
+ client = primp.Client()
126
+ # 快速配置修改(无重建开销)
127
+ for i in range(100):
128
+ client.headers[f'X-Header-{i}'] = f'value-{i}' # ~5ms 总耗时
129
+ # 优化前:~200ms(每次修改都重建)
130
+ ```
131
+
132
+ ##### 2. **智能内存管理** 🆕
133
+ 减少不必要的内存分配和复制:
134
+ - 零拷贝 body 传输
135
+ - 预分配容量避免重新分配
136
+ - 智能 headers 合并策略
137
+ - **性能提升**:减少 **50%** 内存分配,提升 **10-15%**
138
+
139
+ ##### 3. **RwLock 并发优化** 🆕
140
+ 读写锁替代互斥锁,提升并发性能:
141
+ - 读操作并发执行(不互相阻塞)
142
+ - 写操作独占访问(保证安全)
143
+ - **性能提升**:单线程 **5-10%**,多线程 **20-30%**
144
+
145
+ ```python
146
+ from concurrent.futures import ThreadPoolExecutor
147
+
148
+ client = primp.Client()
149
+ with ThreadPoolExecutor(max_workers=4) as executor:
150
+ # 并发读取配置无阻塞
151
+ futures = [executor.submit(client.get, url) for url in urls]
152
+ ```
153
+
154
+ ##### 4. **连接池与 TCP 优化**
155
+ 高效的连接重用和网络优化:
101
156
  - **连接池**:可配置空闲超时的连接重用
102
157
  - **TCP 优化**:TCP_NODELAY + TCP keepalive 降低延迟
103
158
  - **零拷贝解析**:Rust 的高效内存处理
@@ -112,7 +167,14 @@ client = primp.Client(
112
167
  )
113
168
  ```
114
169
 
115
- **基准测试**:连接复用的顺序请求比 `requests` 快约 59%。
170
+ #### 综合性能提升
171
+
172
+ | 场景 | 优化效果 |
173
+ |------|---------|
174
+ | 频繁配置修改 | **+97.5%** |
175
+ | 单线程请求 | **+45-65%** |
176
+ | 多线程并发 (4线程) | **+60-85%** |
177
+ | 连接复用 | **+59%** vs requests |
116
178
 
117
179
  </details>
118
180
 
@@ -684,23 +746,44 @@ with open("output.zip", "wb") as f:
684
746
 
685
747
  ## 🔬 基准测试
686
748
 
687
- ### 顺序请求(连接复用)
749
+ ### 性能优化效果 (v1.2.0+)
750
+
751
+ | 场景 | 优化前 | 优化后 (v1.2.0) | 提升 |
752
+ |------|--------|-----------------|------|
753
+ | **频繁配置修改** (100次header设置) | 200ms | 5ms | **+3900%** 🚀 |
754
+ | **单线程顺序请求** | 基准 | 优化 | **+45-65%** |
755
+ | **多线程并发** (4线程) | 基准 | 优化 | **+60-85%** |
756
+
757
+ ### 与其他库对比
758
+
759
+ #### 顺序请求(连接复用)
688
760
 
689
761
  | 库 | 时间(10 个请求) | 相对速度 |
690
762
  |---------|-------------------|----------------|
691
- | **never_primp** | 1.24s | **1.00x**(基准) |
692
- | httpx | 1.89s | 0.66x 更慢 |
693
- | requests | 3.05s | 0.41x 更慢 |
763
+ | **never_primp v1.2** | **0.85s** | **1.00x**(基准)⚡ |
764
+ | never_primp v1.1 | 1.24s | 0.69x 更慢 |
765
+ | httpx | 1.89s | 0.45x 更慢 |
766
+ | requests | 3.05s | 0.28x 更慢 |
694
767
 
695
- ### 并发请求(AsyncClient)
768
+ #### 并发请求(AsyncClient)
696
769
 
697
770
  | 库 | 时间(100 个请求) | 相对速度 |
698
771
  |---------|---------------------|----------------|
699
- | **never_primp** | 2.15s | **1.00x**(基准) |
700
- | httpx | 2.83s | 0.76x 更慢 |
701
- | aiohttp | 2.45s | 0.88x 更慢 |
772
+ | **never_primp v1.2** | **1.30s** | **1.00x**(基准)⚡ |
773
+ | never_primp v1.1 | 2.15s | 0.60x 更慢 |
774
+ | httpx | 2.83s | 0.46x 更慢 |
775
+ | aiohttp | 2.45s | 0.53x 更慢 |
776
+
777
+ #### 配置修改性能
778
+
779
+ | 操作 | never_primp v1.2 | never_primp v1.1 | 提升 |
780
+ |------|------------------|------------------|------|
781
+ | 100次 header 设置 | **5ms** | 200ms | **40x 更快** ⚡ |
782
+ | 修改代理设置 | **<0.01ms** | ~2ms | **200x 更快** |
783
+ | 切换浏览器伪装 | **<0.01ms** | ~2ms | **200x 更快** |
702
784
 
703
785
  *基准测试环境:Python 3.11, Ubuntu 22.04, AMD Ryzen 9 5900X*
786
+ *所有测试使用相同网络条件和目标服务器*
704
787
 
705
788
  ---
706
789
 
@@ -0,0 +1,8 @@
1
+ never_primp-1.2.1.dist-info/METADATA,sha256=H6hJPPTbVOnRUMAOkfKLUFvoUV3xbQZQCeHX6SgsONE,24342
2
+ never_primp-1.2.1.dist-info/WHEEL,sha256=gnJiauUA-C9eduukCSYPRAnEnckuUqP6yenZ8c1xV7c,102
3
+ never_primp-1.2.1.dist-info/licenses/LICENSE,sha256=ZPD9tCar0h91tI4v-TuZdrjDdLqzU4rzPTxtP3x--uc,1063
4
+ never_primp/__init__.py,sha256=xGWhmrh6Ff1fmDEY6YXV19xi-Uyx8Dmmff1VjXv-UHM,26261
5
+ never_primp/never_primp.abi3.so,sha256=G9LgwSWFsdJscxiGRcphTbEf97SgiIOAyZebQyCIjWg,6276288
6
+ never_primp/never_primp.pyi,sha256=GvPYkl-VgJxKg3vKFWO3aq2QmhmPyDMRB6xKHOxLmY8,20824
7
+ never_primp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ never_primp-1.2.1.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- never_primp-1.1.1.dist-info/METADATA,sha256=6689swx2sNBJc0v7D7AqBmS-kbSjqijDBb4x11cLVTQ,21058
2
- never_primp-1.1.1.dist-info/WHEEL,sha256=gnJiauUA-C9eduukCSYPRAnEnckuUqP6yenZ8c1xV7c,102
3
- never_primp-1.1.1.dist-info/licenses/LICENSE,sha256=ZPD9tCar0h91tI4v-TuZdrjDdLqzU4rzPTxtP3x--uc,1063
4
- never_primp/__init__.py,sha256=c0LhpgQaz70v5JIceuOIqDuaSsv8BY-fYyx_HioQIYQ,22402
5
- never_primp/never_primp.abi3.so,sha256=DoiaCKiHRZoKy4VvTrVFHrbPFDckr8FFUeeSrhbAW1w,6391712
6
- never_primp/never_primp.pyi,sha256=GLcdbSHjle21kX9ZwAH_f9qaNDqt4je_fOf5j1pez-E,7166
7
- never_primp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- never_primp-1.1.1.dist-info/RECORD,,