weheat 2025.1.14__py3-none-any.whl → 2025.1.15__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.

Potentially problematic release.


This version of weheat might be problematic. Click here for more details.

weheat/api_client.py CHANGED
@@ -17,18 +17,26 @@ import datetime
17
17
  from dateutil.parser import parse
18
18
  import json
19
19
  import mimetypes
20
- from multiprocessing.pool import ThreadPool
21
20
  import os
22
21
  import re
23
22
  import tempfile
24
23
 
25
24
  from urllib.parse import quote
25
+ from typing import Tuple, Optional, List
26
26
 
27
27
  from weheat.configuration import Configuration
28
28
  from weheat.api_response import ApiResponse
29
29
  import weheat.models
30
30
  from weheat import rest
31
- from weheat.exceptions import ApiValueError, ApiException
31
+ from weheat.exceptions import (
32
+ ApiValueError,
33
+ ApiException,
34
+ BadRequestException,
35
+ UnauthorizedException,
36
+ ForbiddenException,
37
+ NotFoundException,
38
+ ServiceException
39
+ )
32
40
 
33
41
 
34
42
  class ApiClient:
@@ -45,8 +53,6 @@ class ApiClient:
45
53
  the API.
46
54
  :param cookie: a cookie to include in the header when making calls
47
55
  to the API
48
- :param pool_threads: The number of threads to use for async requests
49
- to the API. More threads means more concurrent API requests.
50
56
  """
51
57
 
52
58
  PRIMITIVE_TYPES = (float, bool, bytes, str, int)
@@ -62,13 +68,17 @@ class ApiClient:
62
68
  }
63
69
  _pool = None
64
70
 
65
- def __init__(self, configuration=None, header_name=None, header_value=None,
66
- cookie=None, pool_threads=1) -> None:
71
+ def __init__(
72
+ self,
73
+ configuration=None,
74
+ header_name=None,
75
+ header_value=None,
76
+ cookie=None
77
+ ) -> None:
67
78
  # use default configuration if none is provided
68
79
  if configuration is None:
69
80
  configuration = Configuration.get_default()
70
81
  self.configuration = configuration
71
- self.pool_threads = pool_threads
72
82
 
73
83
  self.rest_client = rest.RESTClientObject(configuration)
74
84
  self.default_headers = {}
@@ -76,32 +86,17 @@ class ApiClient:
76
86
  self.default_headers[header_name] = header_value
77
87
  self.cookie = cookie
78
88
  # Set default User-Agent.
79
- self.user_agent = 'OpenAPI-Generator/2024.07.08/python'
89
+ self.user_agent = 'OpenAPI-Generator/2024.11.15/python'
80
90
  self.client_side_validation = configuration.client_side_validation
81
91
 
82
- def __enter__(self):
92
+ async def __aenter__(self):
83
93
  return self
84
94
 
85
- def __exit__(self, exc_type, exc_value, traceback):
86
- self.close()
95
+ async def __aexit__(self, exc_type, exc_value, traceback):
96
+ await self.close()
87
97
 
88
- def close(self):
89
- if self._pool:
90
- self._pool.close()
91
- self._pool.join()
92
- self._pool = None
93
- if hasattr(atexit, 'unregister'):
94
- atexit.unregister(self.close)
95
-
96
- @property
97
- def pool(self):
98
- """Create thread pool on first request
99
- avoids instantiating unused threadpool for blocking clients.
100
- """
101
- if self._pool is None:
102
- atexit.register(self.close)
103
- self._pool = ThreadPool(self.pool_threads)
104
- return self._pool
98
+ async def close(self):
99
+ await self.rest_client.close()
105
100
 
106
101
  @property
107
102
  def user_agent(self):
@@ -142,13 +137,42 @@ class ApiClient:
142
137
  """
143
138
  cls._default = default
144
139
 
145
- def __call_api(
146
- self, resource_path, method, path_params=None,
147
- query_params=None, header_params=None, body=None, post_params=None,
148
- files=None, response_types_map=None, auth_settings=None,
149
- _return_http_data_only=None, collection_formats=None,
150
- _preload_content=True, _request_timeout=None, _host=None,
151
- _request_auth=None):
140
+ def param_serialize(
141
+ self,
142
+ method,
143
+ resource_path,
144
+ path_params=None,
145
+ query_params=None,
146
+ header_params=None,
147
+ body=None,
148
+ post_params=None,
149
+ files=None, auth_settings=None,
150
+ collection_formats=None,
151
+ _host=None,
152
+ _request_auth=None
153
+ ) -> Tuple:
154
+
155
+ """Builds the HTTP request params needed by the request.
156
+ :param method: Method to call.
157
+ :param resource_path: Path to method endpoint.
158
+ :param path_params: Path parameters in the url.
159
+ :param query_params: Query parameters in the url.
160
+ :param header_params: Header parameters to be
161
+ placed in the request header.
162
+ :param body: Request body.
163
+ :param post_params dict: Request post form parameters,
164
+ for `application/x-www-form-urlencoded`, `multipart/form-data`.
165
+ :param auth_settings list: Auth Settings names for the request.
166
+ :param files dict: key -> filename, value -> filepath,
167
+ for `multipart/form-data`.
168
+ :param collection_formats: dict of collection formats for path, query,
169
+ header, and post parameters.
170
+ :param _request_auth: set to override the auth_settings for an a single
171
+ request; this effectively ignores the authentication
172
+ in the spec for a single request.
173
+ :return: tuple of form (path, http_method, query_params, header_params,
174
+ body, post_params, files)
175
+ """
152
176
 
153
177
  config = self.configuration
154
178
 
@@ -159,14 +183,17 @@ class ApiClient:
159
183
  header_params['Cookie'] = self.cookie
160
184
  if header_params:
161
185
  header_params = self.sanitize_for_serialization(header_params)
162
- header_params = dict(self.parameters_to_tuples(header_params,
163
- collection_formats))
186
+ header_params = dict(
187
+ self.parameters_to_tuples(header_params,collection_formats)
188
+ )
164
189
 
165
190
  # path parameters
166
191
  if path_params:
167
192
  path_params = self.sanitize_for_serialization(path_params)
168
- path_params = self.parameters_to_tuples(path_params,
169
- collection_formats)
193
+ path_params = self.parameters_to_tuples(
194
+ path_params,
195
+ collection_formats
196
+ )
170
197
  for k, v in path_params:
171
198
  # specified safe chars, encode everything
172
199
  resource_path = resource_path.replace(
@@ -178,15 +205,22 @@ class ApiClient:
178
205
  if post_params or files:
179
206
  post_params = post_params if post_params else []
180
207
  post_params = self.sanitize_for_serialization(post_params)
181
- post_params = self.parameters_to_tuples(post_params,
182
- collection_formats)
208
+ post_params = self.parameters_to_tuples(
209
+ post_params,
210
+ collection_formats
211
+ )
183
212
  post_params.extend(self.files_parameters(files))
184
213
 
185
214
  # auth setting
186
215
  self.update_params_for_auth(
187
- header_params, query_params, auth_settings,
188
- resource_path, method, body,
189
- request_auth=_request_auth)
216
+ header_params,
217
+ query_params,
218
+ auth_settings,
219
+ resource_path,
220
+ method,
221
+ body,
222
+ request_auth=_request_auth
223
+ )
190
224
 
191
225
  # body
192
226
  if body:
@@ -202,59 +236,99 @@ class ApiClient:
202
236
  # query parameters
203
237
  if query_params:
204
238
  query_params = self.sanitize_for_serialization(query_params)
205
- url_query = self.parameters_to_url_query(query_params,
206
- collection_formats)
239
+ url_query = self.parameters_to_url_query(
240
+ query_params,
241
+ collection_formats
242
+ )
207
243
  url += "?" + url_query
208
244
 
245
+ return method, url, header_params, body, post_params
246
+
247
+
248
+ async def call_api(
249
+ self,
250
+ method,
251
+ url,
252
+ header_params=None,
253
+ body=None,
254
+ post_params=None,
255
+ _request_timeout=None
256
+ ) -> rest.RESTResponse:
257
+ """Makes the HTTP request (synchronous)
258
+ :param method: Method to call.
259
+ :param url: Path to method endpoint.
260
+ :param header_params: Header parameters to be
261
+ placed in the request header.
262
+ :param body: Request body.
263
+ :param post_params dict: Request post form parameters,
264
+ for `application/x-www-form-urlencoded`, `multipart/form-data`.
265
+ :param _request_timeout: timeout setting for this request.
266
+ :return: RESTResponse
267
+ """
268
+
209
269
  try:
210
270
  # perform request and return response
211
- response_data = self.request(
271
+ response_data = await self.rest_client.request(
212
272
  method, url,
213
- query_params=query_params,
214
273
  headers=header_params,
215
- post_params=post_params, body=body,
216
- _preload_content=_preload_content,
217
- _request_timeout=_request_timeout)
274
+ body=body, post_params=post_params,
275
+ _request_timeout=_request_timeout
276
+ )
277
+
218
278
  except ApiException as e:
219
279
  if e.body:
220
280
  e.body = e.body.decode('utf-8')
221
281
  raise e
222
282
 
223
- self.last_response = response_data
224
-
225
- return_data = None # assuming deserialization is not needed
226
- # data needs deserialization or returns HTTP data (deserialized) only
227
- if _preload_content or _return_http_data_only:
228
- response_type = response_types_map.get(str(response_data.status), None)
229
- if not response_type and isinstance(response_data.status, int) and 100 <= response_data.status <= 599:
230
- # if not found, look for '1XX', '2XX', etc.
231
- response_type = response_types_map.get(str(response_data.status)[0] + "XX", None)
232
-
233
- if response_type == "bytearray":
234
- response_data.data = response_data.data
235
- else:
236
- match = None
237
- content_type = response_data.getheader('content-type')
238
- if content_type is not None:
239
- match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
240
- encoding = match.group(1) if match else "utf-8"
241
- response_data.data = response_data.data.decode(encoding)
242
-
243
- # deserialize response data
244
- if response_type == "bytearray":
245
- return_data = response_data.data
246
- elif response_type:
247
- return_data = self.deserialize(response_data, response_type)
248
- else:
249
- return_data = None
250
-
251
- if _return_http_data_only:
252
- return return_data
253
- else:
254
- return ApiResponse(status_code = response_data.status,
255
- data = return_data,
256
- headers = response_data.getheaders(),
257
- raw_data = response_data.data)
283
+ return response_data
284
+
285
+ def response_deserialize(
286
+ self,
287
+ response_data: rest.RESTResponse = None,
288
+ response_types_map=None
289
+ ) -> ApiResponse:
290
+ """Deserializes response into an object.
291
+ :param response_data: RESTResponse object to be deserialized.
292
+ :param response_types_map: dict of response types.
293
+ :return: ApiResponse
294
+ """
295
+
296
+
297
+ response_type = response_types_map.get(str(response_data.status), None)
298
+ if not response_type and isinstance(response_data.status, int) and 100 <= response_data.status <= 599:
299
+ # if not found, look for '1XX', '2XX', etc.
300
+ response_type = response_types_map.get(str(response_data.status)[0] + "XX", None)
301
+
302
+ # deserialize response data
303
+ response_text = None
304
+ return_data = None
305
+ try:
306
+ if response_type == "bytearray":
307
+ return_data = response_data.data
308
+ elif response_type == "file":
309
+ return_data = self.__deserialize_file(response_data)
310
+ elif response_type is not None:
311
+ match = None
312
+ content_type = response_data.getheader('content-type')
313
+ if content_type is not None:
314
+ match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
315
+ encoding = match.group(1) if match else "utf-8"
316
+ response_text = response_data.data.decode(encoding)
317
+ return_data = self.deserialize(response_text, response_type)
318
+ finally:
319
+ if not 200 <= response_data.status <= 299:
320
+ raise ApiException.from_response(
321
+ http_resp=response_data,
322
+ body=response_text,
323
+ data=return_data,
324
+ )
325
+
326
+ return ApiResponse(
327
+ status_code = response_data.status,
328
+ data = return_data,
329
+ headers = response_data.getheaders(),
330
+ raw_data = response_data.data
331
+ )
258
332
 
259
333
  def sanitize_for_serialization(self, obj):
260
334
  """Builds a JSON POST object.
@@ -275,15 +349,17 @@ class ApiClient:
275
349
  elif isinstance(obj, self.PRIMITIVE_TYPES):
276
350
  return obj
277
351
  elif isinstance(obj, list):
278
- return [self.sanitize_for_serialization(sub_obj)
279
- for sub_obj in obj]
352
+ return [
353
+ self.sanitize_for_serialization(sub_obj) for sub_obj in obj
354
+ ]
280
355
  elif isinstance(obj, tuple):
281
- return tuple(self.sanitize_for_serialization(sub_obj)
282
- for sub_obj in obj)
356
+ return tuple(
357
+ self.sanitize_for_serialization(sub_obj) for sub_obj in obj
358
+ )
283
359
  elif isinstance(obj, (datetime.datetime, datetime.date)):
284
360
  return obj.isoformat()
285
361
 
286
- if isinstance(obj, dict):
362
+ elif isinstance(obj, dict):
287
363
  obj_dict = obj
288
364
  else:
289
365
  # Convert model obj to dict except
@@ -293,10 +369,12 @@ class ApiClient:
293
369
  # model definition for request.
294
370
  obj_dict = obj.to_dict()
295
371
 
296
- return {key: self.sanitize_for_serialization(val)
297
- for key, val in obj_dict.items()}
372
+ return {
373
+ key: self.sanitize_for_serialization(val)
374
+ for key, val in obj_dict.items()
375
+ }
298
376
 
299
- def deserialize(self, response, response_type):
377
+ def deserialize(self, response_text, response_type):
300
378
  """Deserializes response into an object.
301
379
 
302
380
  :param response: RESTResponse object to be deserialized.
@@ -305,16 +383,12 @@ class ApiClient:
305
383
 
306
384
  :return: deserialized object.
307
385
  """
308
- # handle file downloading
309
- # save response body into a tmp file and return the instance
310
- if response_type == "file":
311
- return self.__deserialize_file(response)
312
386
 
313
387
  # fetch data from response object
314
388
  try:
315
- data = json.loads(response.data)
389
+ data = json.loads(response_text)
316
390
  except ValueError:
317
- data = response.data
391
+ data = response_text
318
392
 
319
393
  return self.__deserialize(data, response_type)
320
394
 
@@ -332,13 +406,11 @@ class ApiClient:
332
406
  if isinstance(klass, str):
333
407
  if klass.startswith('List['):
334
408
  sub_kls = re.match(r'List\[(.*)]', klass).group(1)
335
-
336
-
409
+ # when there is data present, the data is wrapped in a dict
337
410
  try:
338
411
  data = data['data']
339
412
  except:
340
413
  pass
341
-
342
414
  return [self.__deserialize(sub_data, sub_kls)
343
415
  for sub_data in data]
344
416
 
@@ -364,136 +436,6 @@ class ApiClient:
364
436
  else:
365
437
  return self.__deserialize_model(data, klass)
366
438
 
367
- def call_api(self, resource_path, method,
368
- path_params=None, query_params=None, header_params=None,
369
- body=None, post_params=None, files=None,
370
- response_types_map=None, auth_settings=None,
371
- async_req=None, _return_http_data_only=None,
372
- collection_formats=None, _preload_content=True,
373
- _request_timeout=None, _host=None, _request_auth=None):
374
- """Makes the HTTP request (synchronous) and returns deserialized data.
375
-
376
- To make an async_req request, set the async_req parameter.
377
-
378
- :param resource_path: Path to method endpoint.
379
- :param method: Method to call.
380
- :param path_params: Path parameters in the url.
381
- :param query_params: Query parameters in the url.
382
- :param header_params: Header parameters to be
383
- placed in the request header.
384
- :param body: Request body.
385
- :param post_params dict: Request post form parameters,
386
- for `application/x-www-form-urlencoded`, `multipart/form-data`.
387
- :param auth_settings list: Auth Settings names for the request.
388
- :param response: Response data type.
389
- :param files dict: key -> filename, value -> filepath,
390
- for `multipart/form-data`.
391
- :param async_req bool: execute request asynchronously
392
- :param _return_http_data_only: response data instead of ApiResponse
393
- object with status code, headers, etc
394
- :param _preload_content: if False, the ApiResponse.data will
395
- be set to none and raw_data will store the
396
- HTTP response body without reading/decoding.
397
- Default is True.
398
- :param collection_formats: dict of collection formats for path, query,
399
- header, and post parameters.
400
- :param _request_timeout: timeout setting for this request. If one
401
- number provided, it will be total request
402
- timeout. It can also be a pair (tuple) of
403
- (connection, read) timeouts.
404
- :param _request_auth: set to override the auth_settings for an a single
405
- request; this effectively ignores the authentication
406
- in the spec for a single request.
407
- :type _request_token: dict, optional
408
- :return:
409
- If async_req parameter is True,
410
- the request will be called asynchronously.
411
- The method will return the request thread.
412
- If parameter async_req is False or missing,
413
- then the method will return the response directly.
414
- """
415
- args = (
416
- resource_path,
417
- method,
418
- path_params,
419
- query_params,
420
- header_params,
421
- body,
422
- post_params,
423
- files,
424
- response_types_map,
425
- auth_settings,
426
- _return_http_data_only,
427
- collection_formats,
428
- _preload_content,
429
- _request_timeout,
430
- _host,
431
- _request_auth,
432
- )
433
- if not async_req:
434
- return self.__call_api(*args)
435
-
436
- return self.pool.apply_async(self.__call_api, args)
437
-
438
- def request(self, method, url, query_params=None, headers=None,
439
- post_params=None, body=None, _preload_content=True,
440
- _request_timeout=None):
441
- """Makes the HTTP request using RESTClient."""
442
- if method == "GET":
443
- return self.rest_client.get_request(url,
444
- query_params=query_params,
445
- _preload_content=_preload_content,
446
- _request_timeout=_request_timeout,
447
- headers=headers)
448
- elif method == "HEAD":
449
- return self.rest_client.head_request(url,
450
- query_params=query_params,
451
- _preload_content=_preload_content,
452
- _request_timeout=_request_timeout,
453
- headers=headers)
454
- elif method == "OPTIONS":
455
- return self.rest_client.options_request(url,
456
- query_params=query_params,
457
- headers=headers,
458
- _preload_content=_preload_content,
459
- _request_timeout=_request_timeout)
460
- elif method == "POST":
461
- return self.rest_client.post_request(url,
462
- query_params=query_params,
463
- headers=headers,
464
- post_params=post_params,
465
- _preload_content=_preload_content,
466
- _request_timeout=_request_timeout,
467
- body=body)
468
- elif method == "PUT":
469
- return self.rest_client.put_request(url,
470
- query_params=query_params,
471
- headers=headers,
472
- post_params=post_params,
473
- _preload_content=_preload_content,
474
- _request_timeout=_request_timeout,
475
- body=body)
476
- elif method == "PATCH":
477
- return self.rest_client.patch_request(url,
478
- query_params=query_params,
479
- headers=headers,
480
- post_params=post_params,
481
- _preload_content=_preload_content,
482
- _request_timeout=_request_timeout,
483
- body=body)
484
- elif method == "DELETE":
485
- return self.rest_client.delete_request(url,
486
- query_params=query_params,
487
- headers=headers,
488
- _preload_content=_preload_content,
489
- _request_timeout=_request_timeout,
490
- body=body)
491
- else:
492
- raise ApiValueError(
493
- "http method must be `GET`, `HEAD`, `OPTIONS`,"
494
- " `POST`, `PATCH`, `PUT` or `DELETE`."
495
- )
496
-
497
439
  def parameters_to_tuples(self, params, collection_formats):
498
440
  """Get parameters as list of tuples, formatting collections.
499
441
 
@@ -504,7 +446,7 @@ class ApiClient:
504
446
  new_params = []
505
447
  if collection_formats is None:
506
448
  collection_formats = {}
507
- for k, v in params.items() if isinstance(params, dict) else params: # noqa: E501
449
+ for k, v in params.items() if isinstance(params, dict) else params:
508
450
  if k in collection_formats:
509
451
  collection_format = collection_formats[k]
510
452
  if collection_format == 'multi':
@@ -534,7 +476,7 @@ class ApiClient:
534
476
  new_params = []
535
477
  if collection_formats is None:
536
478
  collection_formats = {}
537
- for k, v in params.items() if isinstance(params, dict) else params: # noqa: E501
479
+ for k, v in params.items() if isinstance(params, dict) else params:
538
480
  if isinstance(v, bool):
539
481
  v = str(v).lower()
540
482
  if isinstance(v, (int, float)):
@@ -556,7 +498,8 @@ class ApiClient:
556
498
  else: # csv is the default
557
499
  delimiter = ','
558
500
  new_params.append(
559
- (k, delimiter.join(quote(str(value)) for value in v)))
501
+ (k, delimiter.join(quote(str(value)) for value in v))
502
+ )
560
503
  else:
561
504
  new_params.append((k, quote(str(v))))
562
505
 
@@ -579,21 +522,24 @@ class ApiClient:
579
522
  with open(n, 'rb') as f:
580
523
  filename = os.path.basename(f.name)
581
524
  filedata = f.read()
582
- mimetype = (mimetypes.guess_type(filename)[0] or
583
- 'application/octet-stream')
525
+ mimetype = (
526
+ mimetypes.guess_type(filename)[0]
527
+ or 'application/octet-stream'
528
+ )
584
529
  params.append(
585
- tuple([k, tuple([filename, filedata, mimetype])]))
530
+ tuple([k, tuple([filename, filedata, mimetype])])
531
+ )
586
532
 
587
533
  return params
588
534
 
589
- def select_header_accept(self, accepts):
535
+ def select_header_accept(self, accepts: List[str]) -> Optional[str]:
590
536
  """Returns `Accept` based on an array of accepts provided.
591
537
 
592
538
  :param accepts: List of headers.
593
539
  :return: Accept (e.g. application/json).
594
540
  """
595
541
  if not accepts:
596
- return
542
+ return None
597
543
 
598
544
  for accept in accepts:
599
545
  if re.search('json', accept, re.IGNORECASE):
@@ -616,9 +562,16 @@ class ApiClient:
616
562
 
617
563
  return content_types[0]
618
564
 
619
- def update_params_for_auth(self, headers, queries, auth_settings,
620
- resource_path, method, body,
621
- request_auth=None):
565
+ def update_params_for_auth(
566
+ self,
567
+ headers,
568
+ queries,
569
+ auth_settings,
570
+ resource_path,
571
+ method,
572
+ body,
573
+ request_auth=None
574
+ ) -> None:
622
575
  """Updates header and query params based on authentication setting.
623
576
 
624
577
  :param headers: Header parameters dict to be updated.
@@ -635,21 +588,36 @@ class ApiClient:
635
588
  return
636
589
 
637
590
  if request_auth:
638
- self._apply_auth_params(headers, queries,
639
- resource_path, method, body,
640
- request_auth)
641
- return
642
-
643
- for auth in auth_settings:
644
- auth_setting = self.configuration.auth_settings().get(auth)
645
- if auth_setting:
646
- self._apply_auth_params(headers, queries,
647
- resource_path, method, body,
648
- auth_setting)
649
-
650
- def _apply_auth_params(self, headers, queries,
651
- resource_path, method, body,
652
- auth_setting):
591
+ self._apply_auth_params(
592
+ headers,
593
+ queries,
594
+ resource_path,
595
+ method,
596
+ body,
597
+ request_auth
598
+ )
599
+ else:
600
+ for auth in auth_settings:
601
+ auth_setting = self.configuration.auth_settings().get(auth)
602
+ if auth_setting:
603
+ self._apply_auth_params(
604
+ headers,
605
+ queries,
606
+ resource_path,
607
+ method,
608
+ body,
609
+ auth_setting
610
+ )
611
+
612
+ def _apply_auth_params(
613
+ self,
614
+ headers,
615
+ queries,
616
+ resource_path,
617
+ method,
618
+ body,
619
+ auth_setting
620
+ ) -> None:
653
621
  """Updates the request parameters based on a single auth_setting
654
622
 
655
623
  :param headers: Header parameters dict to be updated.
@@ -678,6 +646,9 @@ class ApiClient:
678
646
  Saves response body into a file in a temporary folder,
679
647
  using the filename from the `Content-Disposition` header if provided.
680
648
 
649
+ handle file downloading
650
+ save response body into a tmp file and return the instance
651
+
681
652
  :param response: RESTResponse.
682
653
  :return: file path.
683
654
  """
@@ -687,8 +658,10 @@ class ApiClient:
687
658
 
688
659
  content_disposition = response.getheader("Content-Disposition")
689
660
  if content_disposition:
690
- filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?',
691
- content_disposition).group(1)
661
+ filename = re.search(
662
+ r'filename=[\'"]?([^\'"\s]+)[\'"]?',
663
+ content_disposition
664
+ ).group(1)
692
665
  path = os.path.join(os.path.dirname(path), filename)
693
666
 
694
667
  with open(path, "wb") as f: