never-primp 1.0.2__cp38-abi3-macosx_10_12_x86_64.whl → 1.1.1__cp38-abi3-macosx_10_12_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
@@ -142,7 +142,7 @@ class Client(RClient):
142
142
  auth_bearer: str | None = None,
143
143
  params: dict[str, str] | None = None,
144
144
  headers: dict[str, str] | None = None,
145
- ordered_headers: dict[str, str] | None = None,
145
+ cookies: dict[str, str] | None = None,
146
146
  cookie_store: bool | None = True,
147
147
  split_cookies: bool | None = False,
148
148
  referer: bool | None = True,
@@ -155,26 +155,29 @@ class Client(RClient):
155
155
  verify: bool | None = True,
156
156
  ca_cert_file: str | None = None,
157
157
  https_only: bool | None = False,
158
+ http1_only: bool | None = False,
158
159
  http2_only: bool | None = False,
159
- # Performance optimization parameters
160
160
  pool_idle_timeout: float | None = None,
161
161
  pool_max_idle_per_host: int | None = None,
162
162
  tcp_nodelay: bool | None = None,
163
163
  tcp_keepalive: float | None = None,
164
- # Retry mechanism
165
- retry_count: int | None = None,
166
- retry_backoff: float | None = None,
167
164
  ):
168
165
  """
169
166
  Args:
170
167
  auth: a tuple containing the username and an optional password for basic authentication. Default is None.
171
168
  auth_bearer: a string representing the bearer token for bearer token authentication. Default is None.
172
169
  params: a map of query parameters to append to the URL. Default is None.
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.
170
+ headers: an optional ordered map of HTTP headers with strict order preservation.
171
+ Headers will be sent in the exact order specified, with automatic positioning of:
172
+ - Host (first position)
173
+ - Content-Length (second position for POST/PUT/PATCH)
174
+ - Content-Type (third position if auto-calculated)
175
+ - cookie (second-to-last position)
176
+ - priority (last position)
176
177
  Example: {"User-Agent": "...", "Accept": "...", "Accept-Language": "..."}
177
178
  Note: Python 3.7+ dict maintains insertion order by default.
179
+ cookies: initial cookies to set for the client. These cookies will be included in all requests.
180
+ Can be updated later using client.cookies.update(). Default is None.
178
181
  cookie_store: enable a persistent cookie store. Received cookies will be preserved and included
179
182
  in additional requests. Default is True.
180
183
  split_cookies: split cookies into multiple `cookie` headers (HTTP/2 style) instead of a single
@@ -207,11 +210,17 @@ class Client(RClient):
207
210
  verify: an optional boolean indicating whether to verify SSL certificates. Default is True.
208
211
  ca_cert_file: path to CA certificate store. Default is None.
209
212
  https_only: restrict the Client to be used with HTTPS only requests. Default is False.
210
- http2_only: if true - use only HTTP/2, if false - use only HTTP/1. Default is False.
213
+ http1_only: if true - use only HTTP/1.1. Default is False.
214
+ http2_only: if true - use only HTTP/2. Default is False.
215
+ Note: http1_only and http2_only are mutually exclusive. If both are true, http1_only takes precedence.
211
216
  """
212
217
  super().__init__()
213
218
  self._cookies_jar: CookieJar | None = None
214
219
 
220
+ # Set initial cookies if provided
221
+ if cookies:
222
+ self.update_cookies(cookies)
223
+
215
224
  @property
216
225
  def cookies(self) -> CookieJar:
217
226
  """
@@ -247,6 +256,79 @@ class Client(RClient):
247
256
  del self
248
257
 
249
258
  def request(self, method: HttpMethod, url: str, **kwargs: Unpack[RequestParams]) -> Response:
259
+ """
260
+ Send an HTTP request with support for requests-toolbelt MultipartEncoder.
261
+
262
+ Supports both native primp format and requests-toolbelt MultipartEncoder:
263
+ - Native: request(url, data={...}, files={...})
264
+ - Toolbelt: request(url, data=MultipartEncoder(...))
265
+ """
266
+ # Check if data is a MultipartEncoder from requests-toolbelt
267
+ data = kwargs.get('data')
268
+ if data is not None and hasattr(data, 'fields') and hasattr(data, 'content_type'):
269
+ # This looks like a MultipartEncoder
270
+ # Extract fields and convert to primp format
271
+ converted_data = {}
272
+ converted_files = {}
273
+
274
+ try:
275
+ # MultipartEncoder.fields is a dict-like object
276
+ for field_name, field_value in data.fields.items():
277
+ if isinstance(field_value, tuple):
278
+ # File field: (filename, file_obj, content_type)
279
+ if len(field_value) >= 2:
280
+ filename = field_value[0]
281
+ file_obj = field_value[1]
282
+
283
+ # Read the file content
284
+ if hasattr(file_obj, 'read'):
285
+ file_content = file_obj.read()
286
+ # Reset file pointer if possible
287
+ if hasattr(file_obj, 'seek'):
288
+ try:
289
+ file_obj.seek(0)
290
+ except:
291
+ pass
292
+ else:
293
+ file_content = file_obj
294
+
295
+ # Add mime type if provided
296
+ if len(field_value) >= 3:
297
+ mime_type = field_value[2]
298
+ converted_files[field_name] = (filename, file_content, mime_type)
299
+ else:
300
+ converted_files[field_name] = (filename, file_content)
301
+ else:
302
+ # Regular field
303
+ if isinstance(field_value, bytes):
304
+ converted_data[field_name] = field_value.decode('utf-8')
305
+ else:
306
+ converted_data[field_name] = str(field_value)
307
+
308
+ # Replace data and files in kwargs
309
+ if converted_data:
310
+ kwargs['data'] = converted_data
311
+ else:
312
+ kwargs.pop('data', None)
313
+
314
+ if converted_files:
315
+ kwargs['files'] = converted_files
316
+
317
+ except Exception as e:
318
+ # If conversion fails, fall back to treating it as raw content
319
+ # Read the encoder as bytes and send as content
320
+ if hasattr(data, 'read'):
321
+ kwargs['content'] = data.read()
322
+ kwargs.pop('data', None)
323
+
324
+ # Get content type from encoder
325
+ if hasattr(data, 'content_type'):
326
+ headers = kwargs.get('headers', {})
327
+ if not isinstance(headers, dict):
328
+ headers = dict(headers)
329
+ headers['Content-Type'] = data.content_type
330
+ kwargs['headers'] = headers
331
+
250
332
  return super().request(method=method, url=url, **kwargs)
251
333
 
252
334
  def get(self, url: str, **kwargs: Unpack[RequestParams]) -> Response:
@@ -277,12 +359,12 @@ class AsyncClient(Client):
277
359
  auth_bearer: str | None = None,
278
360
  params: dict[str, str] | None = None,
279
361
  headers: dict[str, str] | None = None,
280
- ordered_headers: dict[str, str] | None = None,
362
+ cookies: dict[str, str] | None = None,
281
363
  cookie_store: bool | None = True,
282
364
  split_cookies: bool | None = False,
283
365
  referer: bool | None = True,
284
366
  proxy: str | None = None,
285
- timeout: float | None = 30,
367
+ timeout: float | None = None,
286
368
  impersonate: IMPERSONATE | None = None,
287
369
  impersonate_os: IMPERSONATE_OS | None = None,
288
370
  follow_redirects: bool | None = True,
@@ -290,17 +372,39 @@ class AsyncClient(Client):
290
372
  verify: bool | None = True,
291
373
  ca_cert_file: str | None = None,
292
374
  https_only: bool | None = False,
375
+ http1_only: bool | None = False,
293
376
  http2_only: bool | None = False,
294
377
  # Performance optimization parameters
295
378
  pool_idle_timeout: float | None = None,
296
379
  pool_max_idle_per_host: int | None = None,
297
380
  tcp_nodelay: bool | None = None,
298
381
  tcp_keepalive: float | None = None,
299
- # Retry mechanism
300
- retry_count: int | None = None,
301
- retry_backoff: float | None = None,
302
382
  ):
303
- super().__init__()
383
+ super().__init__(
384
+ auth=auth,
385
+ auth_bearer=auth_bearer,
386
+ params=params,
387
+ headers=headers,
388
+ cookies=cookies,
389
+ cookie_store=cookie_store,
390
+ split_cookies=split_cookies,
391
+ referer=referer,
392
+ proxy=proxy,
393
+ timeout=timeout,
394
+ impersonate=impersonate,
395
+ impersonate_os=impersonate_os,
396
+ follow_redirects=follow_redirects,
397
+ max_redirects=max_redirects,
398
+ verify=verify,
399
+ ca_cert_file=ca_cert_file,
400
+ https_only=https_only,
401
+ http1_only=http1_only,
402
+ http2_only=http2_only,
403
+ pool_idle_timeout=pool_idle_timeout,
404
+ pool_max_idle_per_host=pool_max_idle_per_host,
405
+ tcp_nodelay=tcp_nodelay,
406
+ tcp_keepalive=tcp_keepalive,
407
+ )
304
408
 
305
409
  async def __aenter__(self) -> AsyncClient:
306
410
  return self
Binary file
@@ -38,7 +38,6 @@ 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
42
41
  cookies: dict[str, str] | None
43
42
  timeout: float | None
44
43
  content: bytes | None
@@ -83,12 +82,12 @@ class RClient:
83
82
  auth_bearer: str | None = None,
84
83
  params: dict[str, str] | None = None,
85
84
  headers: dict[str, str] | None = None,
86
- ordered_headers: dict[str, str] | None = None,
87
- timeout: float | None = None,
85
+ cookies: dict[str, str] | None = None,
88
86
  cookie_store: bool | None = True,
89
87
  split_cookies: bool | None = False,
90
88
  referer: bool | None = True,
91
89
  proxy: str | None = None,
90
+ timeout: float | None = None,
92
91
  impersonate: IMPERSONATE | None = None,
93
92
  impersonate_os: IMPERSONATE_OS | None = None,
94
93
  follow_redirects: bool | None = True,
@@ -96,24 +95,18 @@ class RClient:
96
95
  verify: bool | None = True,
97
96
  ca_cert_file: str | None = None,
98
97
  https_only: bool | None = False,
98
+ http1_only: bool | None = False,
99
99
  http2_only: bool | None = False,
100
100
  pool_idle_timeout: float | None = None,
101
101
  pool_max_idle_per_host: int | None = None,
102
102
  tcp_nodelay: bool | None = None,
103
103
  tcp_keepalive: float | None = None,
104
- retry_count: int | None = None,
105
- retry_backoff: float | None = None,
106
104
  ): ...
107
105
  @property
108
106
  def headers(self) -> dict[str, str]: ...
109
107
  @headers.setter
110
108
  def headers(self, headers: dict[str, str]) -> None: ...
111
109
  def headers_update(self, headers: 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
110
  # Cookie management methods (no URL required)
118
111
  def get_all_cookies(self) -> dict[str, str]: ...
119
112
  def set_cookie(self, name: str, value: str, domain: str | None = None, path: str | None = None) -> None: ...
@@ -143,14 +136,6 @@ class RClient:
143
136
  @split_cookies.setter
144
137
  def split_cookies(self, split_cookies: bool | None) -> None: ...
145
138
  @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: ...
153
- @property
154
139
  def proxy(self) -> str | None: ...
155
140
  @proxy.setter
156
141
  def proxy(self, proxy: str) -> None: ...