never-primp 1.0.0__cp38-abi3-manylinux_2_28_x86_64.whl → 1.0.2__cp38-abi3-manylinux_2_28_x86_64.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
@@ -3,7 +3,8 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import sys
5
5
  from functools import partial
6
- from typing import TYPE_CHECKING, TypedDict
6
+ from typing import TYPE_CHECKING, TypedDict, Iterator
7
+ from collections.abc import MutableMapping
7
8
 
8
9
  if sys.version_info <= (3, 11):
9
10
  from typing_extensions import Unpack
@@ -26,6 +27,112 @@ else:
26
27
  RequestParams = ClientRequestParams = TypedDict
27
28
 
28
29
 
30
+ class CookieJar(MutableMapping):
31
+ """
32
+ A dict-like container for managing HTTP cookies, compatible with requests.Session.cookies API.
33
+
34
+ Examples:
35
+ client = Client()
36
+
37
+ # Set cookies
38
+ client.cookies['session_id'] = 'abc123'
39
+ client.cookies['user_token'] = 'xyz789'
40
+
41
+ # Get cookies
42
+ value = client.cookies['session_id']
43
+ value = client.cookies.get('user_token', 'default')
44
+
45
+ # Update multiple cookies
46
+ client.cookies.update({'key1': 'value1', 'key2': 'value2'})
47
+
48
+ # Delete cookies
49
+ del client.cookies['session_id']
50
+
51
+ # Clear all cookies
52
+ client.cookies.clear()
53
+
54
+ # Check existence
55
+ if 'session_id' in client.cookies:
56
+ print("Session exists")
57
+
58
+ # Convert to dict
59
+ all_cookies = dict(client.cookies)
60
+ """
61
+
62
+ def __init__(self, client: RClient):
63
+ """Initialize CookieJar with a reference to the client."""
64
+ self._client = client
65
+
66
+ def __getitem__(self, name: str) -> str:
67
+ """Get a cookie value by name."""
68
+ value = self._client.get_cookie(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 cookie by name."""
75
+ self._client.set_cookie(name, value)
76
+
77
+ def __delitem__(self, name: str) -> None:
78
+ """Delete a cookie by name."""
79
+ # Check if cookie exists first
80
+ if self._client.get_cookie(name) is None:
81
+ raise KeyError(name)
82
+ self._client.delete_cookie(name)
83
+
84
+ def __iter__(self) -> Iterator[str]:
85
+ """Iterate over cookie names."""
86
+ return iter(self._client.get_all_cookies().keys())
87
+
88
+ def __len__(self) -> int:
89
+ """Return the number of cookies."""
90
+ return len(self._client.get_all_cookies())
91
+
92
+ def __contains__(self, name: object) -> bool:
93
+ """Check if a cookie exists."""
94
+ if not isinstance(name, str):
95
+ return False
96
+ return self._client.get_cookie(name) is not None
97
+
98
+ def __repr__(self) -> str:
99
+ """Return string representation of cookies."""
100
+ cookies = self._client.get_all_cookies()
101
+ return f"CookieJar({cookies})"
102
+
103
+ def get(self, name: str, default: str | None = None) -> str | None:
104
+ """Get a cookie value with a default fallback."""
105
+ value = self._client.get_cookie(name)
106
+ return value if value is not None else default
107
+
108
+ def update(self, cookies: dict[str, str], domain: str | None = None, path: str | None = None) -> None:
109
+ """
110
+ Update multiple cookies at once.
111
+
112
+ Args:
113
+ cookies: Dictionary of cookie names to values
114
+ domain: Optional domain for the cookies (e.g., ".example.com")
115
+ path: Optional path for the cookies (e.g., "/")
116
+ """
117
+ self._client.update_cookies(cookies, domain=domain, path=path)
118
+
119
+ def clear(self) -> None:
120
+ """Remove all cookies."""
121
+ self._client.clear_cookies()
122
+
123
+ def set(self, name: str, value: str, domain: str | None = None, path: str | None = None) -> None:
124
+ """
125
+ Set a single cookie with optional domain and path.
126
+
127
+ Args:
128
+ name: Cookie name
129
+ value: Cookie value
130
+ domain: Optional domain (e.g., ".example.com")
131
+ path: Optional path (e.g., "/")
132
+ """
133
+ self._client.set_cookie(name, value, domain=domain, path=path)
134
+
135
+
29
136
  class Client(RClient):
30
137
  """Initializes an HTTP client that can impersonate web browsers."""
31
138
 
@@ -35,7 +142,9 @@ class Client(RClient):
35
142
  auth_bearer: str | None = None,
36
143
  params: dict[str, str] | None = None,
37
144
  headers: dict[str, str] | None = None,
145
+ ordered_headers: dict[str, str] | None = None,
38
146
  cookie_store: bool | None = True,
147
+ split_cookies: bool | None = False,
39
148
  referer: bool | None = True,
40
149
  proxy: str | None = None,
41
150
  timeout: float | None = 30,
@@ -62,8 +171,16 @@ class Client(RClient):
62
171
  auth_bearer: a string representing the bearer token for bearer token authentication. Default is None.
63
172
  params: a map of query parameters to append to the URL. Default is None.
64
173
  headers: an optional map of HTTP headers to send with requests. Ignored if `impersonate` is set.
174
+ ordered_headers: an optional ordered map of HTTP headers with strict order preservation.
175
+ Takes priority over `headers`. Use this for bypassing anti-bot detection that checks header order.
176
+ Example: {"User-Agent": "...", "Accept": "...", "Accept-Language": "..."}
177
+ Note: Python 3.7+ dict maintains insertion order by default.
65
178
  cookie_store: enable a persistent cookie store. Received cookies will be preserved and included
66
179
  in additional requests. Default is True.
180
+ split_cookies: split cookies into multiple `cookie` headers (HTTP/2 style) instead of a single
181
+ `Cookie` header. Useful for mimicking browser behavior in HTTP/2. Default is False.
182
+ When True: cookie: a=1 \n cookie: b=2 \n cookie: c=3
183
+ When False: Cookie: a=1; b=2; c=3
67
184
  referer: automatic setting of the `Referer` header. Default is True.
68
185
  proxy: proxy URL for HTTP requests, example: "socks5://127.0.0.1:9150". Default is None.
69
186
  timeout: timeout for HTTP requests in seconds. Default is 30.
@@ -93,6 +210,35 @@ class Client(RClient):
93
210
  http2_only: if true - use only HTTP/2, if false - use only HTTP/1. Default is False.
94
211
  """
95
212
  super().__init__()
213
+ self._cookies_jar: CookieJar | None = None
214
+
215
+ @property
216
+ def cookies(self) -> CookieJar:
217
+ """
218
+ Access the cookie jar for dict-like cookie management.
219
+
220
+ Returns:
221
+ CookieJar: A dict-like container for managing cookies.
222
+
223
+ Examples:
224
+ # Set cookies
225
+ client.cookies['session_id'] = 'abc123'
226
+
227
+ # Get cookies
228
+ value = client.cookies['session_id']
229
+
230
+ # Update cookies
231
+ client.cookies.update({'key': 'value'})
232
+
233
+ # Delete cookies
234
+ del client.cookies['session_id']
235
+
236
+ # Clear all
237
+ client.cookies.clear()
238
+ """
239
+ if self._cookies_jar is None:
240
+ self._cookies_jar = CookieJar(self)
241
+ return self._cookies_jar
96
242
 
97
243
  def __enter__(self) -> Client:
98
244
  return self
@@ -131,7 +277,9 @@ class AsyncClient(Client):
131
277
  auth_bearer: str | None = None,
132
278
  params: dict[str, str] | None = None,
133
279
  headers: dict[str, str] | None = None,
280
+ ordered_headers: dict[str, str] | None = None,
134
281
  cookie_store: bool | None = True,
282
+ split_cookies: bool | None = False,
135
283
  referer: bool | None = True,
136
284
  proxy: str | None = None,
137
285
  timeout: float | None = 30,
@@ -150,7 +298,8 @@ class AsyncClient(Client):
150
298
  tcp_keepalive: float | None = None,
151
299
  # Retry mechanism
152
300
  retry_count: int | None = None,
153
- retry_backoff: float | None = None):
301
+ retry_backoff: float | None = None,
302
+ ):
154
303
  super().__init__()
155
304
 
156
305
  async def __aenter__(self) -> AsyncClient:
Binary file
@@ -38,6 +38,7 @@ class RequestParams(TypedDict, total=False):
38
38
  auth_bearer: str | None
39
39
  params: dict[str, str] | None
40
40
  headers: dict[str, str] | None
41
+ ordered_headers: dict[str, str] | None
41
42
  cookies: dict[str, str] | None
42
43
  timeout: float | None
43
44
  content: bytes | None
@@ -82,8 +83,10 @@ class RClient:
82
83
  auth_bearer: str | None = None,
83
84
  params: dict[str, str] | None = None,
84
85
  headers: dict[str, str] | None = None,
86
+ ordered_headers: dict[str, str] | None = None,
85
87
  timeout: float | None = None,
86
88
  cookie_store: bool | None = True,
89
+ split_cookies: bool | None = False,
87
90
  referer: bool | None = True,
88
91
  proxy: str | None = None,
89
92
  impersonate: IMPERSONATE | None = None,
@@ -94,14 +97,59 @@ class RClient:
94
97
  ca_cert_file: str | None = None,
95
98
  https_only: bool | None = False,
96
99
  http2_only: bool | None = False,
100
+ pool_idle_timeout: float | None = None,
101
+ pool_max_idle_per_host: int | None = None,
102
+ tcp_nodelay: bool | None = None,
103
+ tcp_keepalive: float | None = None,
104
+ retry_count: int | None = None,
105
+ retry_backoff: float | None = None,
97
106
  ): ...
98
107
  @property
99
108
  def headers(self) -> dict[str, str]: ...
100
109
  @headers.setter
101
110
  def headers(self, headers: dict[str, str]) -> None: ...
102
111
  def headers_update(self, headers: dict[str, str]) -> None: ...
103
- def get_cookies(self, url: str) -> dict[str, str]: ...
104
- def set_cookies(self, url: str, cookies: dict[str, str]) -> None: ...
112
+ @property
113
+ def ordered_headers(self) -> dict[str, str]: ...
114
+ @ordered_headers.setter
115
+ def ordered_headers(self, ordered_headers: dict[str, str]) -> None: ...
116
+ def ordered_headers_update(self, ordered_headers: dict[str, str]) -> None: ...
117
+ # Cookie management methods (no URL required)
118
+ def get_all_cookies(self) -> dict[str, str]: ...
119
+ def set_cookie(self, name: str, value: str, domain: str | None = None, path: str | None = None) -> None: ...
120
+ def get_cookie(self, name: str) -> str | None: ...
121
+ def update_cookies(self, cookies: dict[str, str], domain: str | None = None, path: str | None = None) -> None: ...
122
+ def delete_cookie(self, name: str) -> None: ...
123
+ def clear_cookies(self) -> None: ...
124
+ # Client properties
125
+ @property
126
+ def auth(self) -> tuple[str, str | None] | None: ...
127
+ @auth.setter
128
+ def auth(self, auth: tuple[str, str | None] | None) -> None: ...
129
+ @property
130
+ def auth_bearer(self) -> str | None: ...
131
+ @auth_bearer.setter
132
+ def auth_bearer(self, auth_bearer: str | None) -> None: ...
133
+ @property
134
+ def params(self) -> dict[str, str] | None: ...
135
+ @params.setter
136
+ def params(self, params: dict[str, str] | None) -> None: ...
137
+ @property
138
+ def timeout(self) -> float | None: ...
139
+ @timeout.setter
140
+ def timeout(self, timeout: float | None) -> None: ...
141
+ @property
142
+ def split_cookies(self) -> bool | None: ...
143
+ @split_cookies.setter
144
+ def split_cookies(self, split_cookies: bool | None) -> None: ...
145
+ @property
146
+ def retry_count(self) -> int | None: ...
147
+ @retry_count.setter
148
+ def retry_count(self, retry_count: int | None) -> None: ...
149
+ @property
150
+ def retry_backoff(self) -> float | None: ...
151
+ @retry_backoff.setter
152
+ def retry_backoff(self, retry_backoff: float | None) -> None: ...
105
153
  @property
106
154
  def proxy(self) -> str | None: ...
107
155
  @proxy.setter