pangea-sdk 6.2.0b1__py3-none-any.whl → 6.3.0__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.
Files changed (55) hide show
  1. pangea/__init__.py +9 -1
  2. pangea/asyncio/__init__.py +1 -0
  3. pangea/asyncio/file_uploader.py +4 -2
  4. pangea/asyncio/request.py +70 -169
  5. pangea/asyncio/services/__init__.py +2 -1
  6. pangea/asyncio/services/ai_guard.py +9 -12
  7. pangea/asyncio/services/audit.py +13 -307
  8. pangea/asyncio/services/authn.py +40 -32
  9. pangea/asyncio/services/authz.py +51 -17
  10. pangea/asyncio/services/base.py +4 -0
  11. pangea/asyncio/services/file_scan.py +8 -2
  12. pangea/asyncio/services/intel.py +26 -28
  13. pangea/asyncio/services/redact.py +11 -268
  14. pangea/asyncio/services/sanitize.py +5 -1
  15. pangea/asyncio/services/share.py +5 -1
  16. pangea/asyncio/services/vault.py +71 -55
  17. pangea/audit_logger.py +3 -1
  18. pangea/deep_verify.py +13 -13
  19. pangea/deprecated.py +1 -1
  20. pangea/dump_audit.py +2 -3
  21. pangea/exceptions.py +8 -5
  22. pangea/file_uploader.py +4 -0
  23. pangea/request.py +80 -200
  24. pangea/response.py +21 -18
  25. pangea/services/__init__.py +2 -1
  26. pangea/services/ai_guard.py +35 -24
  27. pangea/services/audit/audit.py +17 -314
  28. pangea/services/audit/models.py +69 -307
  29. pangea/services/audit/signing.py +1 -1
  30. pangea/services/audit/util.py +10 -10
  31. pangea/services/authn/authn.py +39 -31
  32. pangea/services/authn/models.py +183 -148
  33. pangea/services/authz.py +108 -60
  34. pangea/services/base.py +7 -4
  35. pangea/services/embargo.py +6 -0
  36. pangea/services/file_scan.py +8 -2
  37. pangea/services/intel.py +36 -19
  38. pangea/services/redact.py +14 -476
  39. pangea/services/sanitize.py +5 -1
  40. pangea/services/share/share.py +13 -7
  41. pangea/services/vault/models/asymmetric.py +4 -0
  42. pangea/services/vault/models/common.py +15 -12
  43. pangea/services/vault/models/keys.py +4 -9
  44. pangea/services/vault/models/secret.py +3 -8
  45. pangea/services/vault/models/symmetric.py +4 -0
  46. pangea/services/vault/vault.py +69 -59
  47. pangea/tools.py +13 -9
  48. pangea/utils.py +3 -5
  49. pangea/verify_audit.py +23 -27
  50. {pangea_sdk-6.2.0b1.dist-info → pangea_sdk-6.3.0.dist-info}/METADATA +36 -17
  51. pangea_sdk-6.3.0.dist-info/RECORD +60 -0
  52. {pangea_sdk-6.2.0b1.dist-info → pangea_sdk-6.3.0.dist-info}/WHEEL +1 -1
  53. pangea/asyncio/services/management.py +0 -576
  54. pangea/services/management.py +0 -720
  55. pangea_sdk-6.2.0b1.dist-info/RECORD +0 -62
pangea/request.py CHANGED
@@ -1,5 +1,9 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
4
+ # TODO: Modernize.
5
+ # ruff: noqa: UP006, UP035
6
+
3
7
  from __future__ import annotations
4
8
 
5
9
  import copy
@@ -7,14 +11,14 @@ import json
7
11
  import logging
8
12
  import time
9
13
  from collections.abc import Iterable, Mapping
10
- from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Sequence, Tuple, Type, Union, cast, overload
14
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union, cast
11
15
 
12
16
  import requests
13
- from pydantic import BaseModel, TypeAdapter
17
+ from pydantic import BaseModel
14
18
  from pydantic_core import to_jsonable_python
15
19
  from requests.adapters import HTTPAdapter, Retry
16
20
  from requests_toolbelt import MultipartDecoder # type: ignore[import-untyped]
17
- from typing_extensions import TypeVar
21
+ from typing_extensions import TypeAlias, TypeVar, override
18
22
  from yarl import URL
19
23
 
20
24
  import pangea
@@ -27,11 +31,26 @@ if TYPE_CHECKING:
27
31
  import aiohttp
28
32
 
29
33
 
34
+ _Data: TypeAlias = Union[Iterable[bytes], str, bytes, list[tuple[Any, Any]]]
35
+
36
+ _FileName: TypeAlias = Union[str, None]
37
+ _FileContent: TypeAlias = Union[str, bytes]
38
+ _FileContentType: TypeAlias = str
39
+ _FileCustomHeaders: TypeAlias = Mapping[str, str]
40
+ _FileSpecTuple2: TypeAlias = tuple[_FileName, _FileContent]
41
+ _FileSpecTuple3: TypeAlias = tuple[_FileName, _FileContent, _FileContentType]
42
+ _FileSpecTuple4: TypeAlias = tuple[_FileName, _FileContent, _FileContentType, _FileCustomHeaders]
43
+ _FileSpec: TypeAlias = Union[_FileContent, _FileSpecTuple2, _FileSpecTuple3, _FileSpecTuple4]
44
+ _Files: TypeAlias = Union[Mapping[str, _FileSpec], Iterable[tuple[str, _FileSpec]]]
45
+
46
+ _HeadersUpdateMapping: TypeAlias = Mapping[str, str]
47
+
48
+
30
49
  class MultipartResponse:
31
50
  pangea_json: Dict[str, str]
32
51
  attached_files: List = []
33
52
 
34
- def __init__(self, pangea_json: Dict[str, str], attached_files: List = []):
53
+ def __init__(self, pangea_json: dict[str, str], attached_files: list = []): # noqa: B006
35
54
  self.pangea_json = pangea_json
36
55
  self.attached_files = attached_files
37
56
 
@@ -49,7 +68,7 @@ class PangeaRequestBase:
49
68
  self._queued_retry_enabled = config.queued_retry_enabled
50
69
 
51
70
  # Custom headers
52
- self._extra_headers: Dict = {}
71
+ self._extra_headers: _HeadersUpdateMapping = {}
53
72
  self._user_agent = ""
54
73
 
55
74
  self.set_custom_user_agent(config.custom_user_agent)
@@ -64,18 +83,17 @@ class PangeaRequestBase:
64
83
 
65
84
  return self._session
66
85
 
67
- def set_extra_headers(self, headers: dict):
86
+ def set_extra_headers(self, headers: _HeadersUpdateMapping):
68
87
  """Sets any additional headers in the request.
69
88
 
70
89
  Args:
71
- headers (dict): key-value pair containing extra headers to et
90
+ headers: key-value pairs containing extra headers to set
72
91
 
73
92
  Example:
74
93
  set_extra_headers({ "My-Header" : "foobar" })
75
94
  """
76
95
 
77
- if isinstance(headers, dict):
78
- self._extra_headers = headers
96
+ self._extra_headers = headers
79
97
 
80
98
  def set_custom_user_agent(self, user_agent: Optional[str]):
81
99
  self.config.custom_user_agent = user_agent
@@ -112,16 +130,14 @@ class PangeaRequestBase:
112
130
  url = URL(self.config.base_url_template.format(SERVICE_NAME=self.service))
113
131
  return str(url / path)
114
132
 
115
- def _headers(self) -> dict:
116
- headers = {
117
- "User-Agent": self._user_agent,
133
+ def _headers(self) -> dict[str, str]:
134
+ return {
135
+ **self._extra_headers,
118
136
  "Authorization": f"Bearer {self.token}",
137
+ "Content-Type": "application/json",
138
+ "User-Agent": self._user_agent,
119
139
  }
120
140
 
121
- # We want to ignore previous headers if user tried to set them, so we will overwrite them.
122
- self._extra_headers.update(headers)
123
- return self._extra_headers
124
-
125
141
  def _get_filename_from_content_disposition(self, content_disposition: str) -> Optional[str]:
126
142
  filename_parts = content_disposition.split("name=")
127
143
  if len(filename_parts) > 1:
@@ -185,6 +201,9 @@ class PangeaRequestBase:
185
201
  raise pe.AcceptedRequestException(response)
186
202
  raise pe.PangeaAPIException(f"{summary} ", response)
187
203
 
204
+ def _init_session(self) -> requests.Session | aiohttp.ClientSession:
205
+ raise NotImplementedError
206
+
188
207
 
189
208
  TResult = TypeVar("TResult", bound=PangeaResponseResult)
190
209
 
@@ -201,88 +220,26 @@ class PangeaRequest(PangeaRequestBase):
201
220
  def __del__(self) -> None:
202
221
  self.session.close()
203
222
 
204
- def delete(self, endpoint: str) -> None:
205
- """
206
- Makes a DELETE call to a Pangea endpoint.
207
-
208
- Args:
209
- endpoint: The Pangea API endpoint.
210
- """
211
-
212
- url = self._url(endpoint)
213
-
214
- self.logger.debug(
215
- json.dumps({"service": self.service, "action": "delete", "url": url}, default=default_encoder)
216
- )
217
-
218
- requests_response = self._http_delete(url, headers=self._headers())
219
- self._check_http_errors(requests_response)
220
-
221
- @overload
222
223
  def post(
223
224
  self,
224
225
  endpoint: str,
225
226
  result_class: Type[TResult],
226
- data: str | BaseModel | dict[str, Any] | None = None,
227
- files: Optional[List[Tuple]] = None,
227
+ data: str | BaseModel | Mapping[str, Any] | None = None,
228
+ files: Optional[list[Tuple]] = None,
228
229
  poll_result: bool = True,
229
230
  url: Optional[str] = None,
230
- *,
231
- pangea_response: Literal[True] = True,
232
231
  ) -> PangeaResponse[TResult]:
233
- """
234
- Makes the POST call to a Pangea Service endpoint.
232
+ """Makes the POST call to a Pangea Service endpoint.
235
233
 
236
234
  Args:
237
- endpoint: The Pangea Service API endpoint.
238
- data: The POST body payload object
235
+ endpoint(str): The Pangea Service API endpoint.
236
+ data(dict): The POST body payload object
239
237
 
240
238
  Returns:
241
239
  PangeaResponse which contains the response in its entirety and
242
240
  various properties to retrieve individual fields
243
241
  """
244
242
 
245
- @overload
246
- def post(
247
- self,
248
- endpoint: str,
249
- result_class: Type[TResult],
250
- data: str | BaseModel | dict[str, Any] | None = None,
251
- files: Optional[List[Tuple]] = None,
252
- poll_result: bool = True,
253
- url: Optional[str] = None,
254
- *,
255
- pangea_response: Literal[False],
256
- ) -> TResult:
257
- """
258
- Makes the POST call to a Pangea Service endpoint.
259
-
260
- Args:
261
- endpoint: The Pangea Service API endpoint.
262
- data: The POST body payload object
263
- """
264
-
265
- def post(
266
- self,
267
- endpoint: str,
268
- result_class: Type[TResult],
269
- data: str | BaseModel | dict[str, Any] | None = None,
270
- files: Optional[List[Tuple]] = None,
271
- poll_result: bool = True,
272
- url: Optional[str] = None,
273
- *,
274
- pangea_response: bool = True,
275
- ) -> PangeaResponse[TResult] | TResult:
276
- """
277
- Makes the POST call to a Pangea Service endpoint.
278
-
279
- Args:
280
- endpoint: The Pangea Service API endpoint.
281
- data: The POST body payload object
282
- pangea_response: Whether or not the response body follows Pangea's
283
- standard response schema
284
- """
285
-
286
243
  if isinstance(data, BaseModel):
287
244
  data = data.model_dump(exclude_none=True)
288
245
 
@@ -294,7 +251,7 @@ class PangeaRequest(PangeaRequestBase):
294
251
  data = {}
295
252
 
296
253
  # Normalize.
297
- data = cast(dict[str, Any], to_jsonable_python(data))
254
+ data = cast(dict[str, Any], to_jsonable_python(data, exclude_none=True))
298
255
 
299
256
  if url is None:
300
257
  url = self._url(endpoint)
@@ -313,19 +270,16 @@ class PangeaRequest(PangeaRequestBase):
313
270
  endpoint, result_class=result_class, data=data, files=files
314
271
  )
315
272
  else:
316
- requests_response = self._http_post(
317
- url, headers=self._headers(), data=data, files=files, multipart_post=True
318
- )
273
+ headers = self._headers()
274
+ if transfer_method == TransferMethod.MULTIPART.value:
275
+ del headers["Content-Type"]
276
+ requests_response = self._http_post(url, headers=headers, data=data, files=files)
319
277
 
320
278
  self._check_http_errors(requests_response)
321
279
 
322
- if not pangea_response:
323
- type_adapter = TypeAdapter(result_class)
324
- return type_adapter.validate_python(requests_response.json())
325
-
326
280
  if "multipart/form-data" in requests_response.headers.get("content-type", ""):
327
281
  multipart_response = self._process_multipart_response(requests_response)
328
- pangea_response_obj: PangeaResponse = PangeaResponse(
282
+ pangea_response: PangeaResponse = PangeaResponse(
329
283
  requests_response,
330
284
  result_class=result_class,
331
285
  json=multipart_response.pangea_json,
@@ -338,14 +292,14 @@ class PangeaRequest(PangeaRequestBase):
338
292
  json.dumps({"service": self.service, "action": "post", "url": url, "response": json_resp})
339
293
  )
340
294
 
341
- pangea_response_obj = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
295
+ pangea_response = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
342
296
  except requests.exceptions.JSONDecodeError as e:
343
- raise pe.PangeaException(f"Failed to decode json response. {e}. Body: {requests_response.text}")
297
+ raise pe.PangeaException(f"Failed to decode json response. {e}. Body: {requests_response.text}") from e
344
298
 
345
299
  if poll_result:
346
- pangea_response_obj = self._handle_queued_result(pangea_response_obj)
300
+ pangea_response = self._handle_queued_result(pangea_response)
347
301
 
348
- return self._check_response(pangea_response_obj)
302
+ return self._check_response(pangea_response)
349
303
 
350
304
  def _get_pangea_json(self, decoder: MultipartDecoder) -> Optional[Dict]:
351
305
  # Iterate through parts
@@ -388,20 +342,12 @@ class PangeaRequest(PangeaRequestBase):
388
342
  if resp.status_code == 503:
389
343
  raise pe.ServiceTemporarilyUnavailable(resp.json())
390
344
 
391
- def _http_delete(
392
- self,
393
- url: str,
394
- *,
395
- headers: Mapping[str, str | bytes | None] = {},
396
- ) -> requests.Response:
397
- return self.session.delete(url, headers=headers)
398
-
399
345
  def _http_post(
400
346
  self,
401
347
  url: str,
402
- headers: Mapping[str, str | bytes | None] = {},
403
- data: Union[str, Dict] = {},
404
- files: Optional[List[Tuple]] = None,
348
+ headers: _HeadersUpdateMapping = {}, # noqa: B006
349
+ data: str | dict[Any, Any] = {}, # noqa: B006
350
+ files: _Files | None = None,
405
351
  multipart_post: bool = True,
406
352
  ) -> requests.Response:
407
353
  data_send, files = self._http_post_process(data=data, files=files, multipart_post=multipart_post)
@@ -409,31 +355,25 @@ class PangeaRequest(PangeaRequestBase):
409
355
 
410
356
  def _http_post_process(
411
357
  self,
412
- data: Union[str, Dict] = {},
413
- files: Optional[Sequence[Tuple[str, Tuple[Any, str, str]]]] = None,
358
+ data: str | dict[Any, Any] = {}, # noqa: B006
359
+ files: _Files | None = None,
414
360
  multipart_post: bool = True,
415
- ):
361
+ ) -> tuple[_Data | None, _Files | None]:
416
362
  if files:
417
363
  if multipart_post is True:
418
364
  data_send: str = json.dumps(data, default=default_encoder) if isinstance(data, dict) else data
419
- multi = [("request", (None, data_send, "application/json"))]
420
- multi.extend(files)
421
- files = multi
422
- return None, files
423
- # Post to presigned url as form
424
- data_send: list = [] # type: ignore[no-redef]
425
- for k, v in data.items(): # type: ignore[union-attr]
426
- data_send.append((k, v)) # type: ignore[attr-defined]
427
- # When posting to presigned url, file key should be 'file'
428
- files = { # type: ignore[assignment]
429
- "file": files[0][1],
430
- }
431
- return data_send, files
365
+ multi: list[tuple[str, _FileSpec]] = [("request", (None, data_send, "application/json")), *files]
366
+ return None, multi
367
+
368
+ # Post to presigned URL as form.
369
+ # When posting to presigned URL, file key should be 'file'.
370
+ assert isinstance(data, dict)
371
+ assert isinstance(files, list)
372
+ return [(k, v) for k, v in data.items()], {"file": files[0][1]}
373
+
432
374
  data_send = json.dumps(data, default=default_encoder) if isinstance(data, dict) else data
433
375
  return data_send, None
434
376
 
435
- return data, files
436
-
437
377
  def _handle_queued_result(self, response: PangeaResponse[TResult]) -> PangeaResponse[TResult]:
438
378
  if self._queued_retry_enabled and response.http_status == 202:
439
379
  self.logger.debug(
@@ -446,98 +386,37 @@ class PangeaRequest(PangeaRequestBase):
446
386
 
447
387
  return response
448
388
 
449
- @overload
450
- def get(
451
- self,
452
- path: str,
453
- result_class: Type[TResult],
454
- check_response: bool = True,
455
- *,
456
- params: (
457
- Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
458
- | None
459
- ) = None,
460
- pangea_response: Literal[True] = True,
461
- ) -> PangeaResponse[TResult]:
462
- """
463
- Makes the GET call to a Pangea Service endpoint.
389
+ def get(self, path: str, result_class: Type[TResult], check_response: bool = True) -> PangeaResponse[TResult]:
390
+ """Makes the GET call to a Pangea Service endpoint.
464
391
 
465
392
  Args:
466
- path: Additional URL path
467
- params: Dictionary of querystring data to attach to the request
393
+ endpoint(str): The Pangea Service API endpoint.
394
+ path(str): Additional URL path
468
395
 
469
396
  Returns:
470
397
  PangeaResponse which contains the response in its entirety and
471
398
  various properties to retrieve individual fields
472
399
  """
473
400
 
474
- @overload
475
- def get(
476
- self,
477
- path: str,
478
- result_class: Type[TResult],
479
- check_response: bool = True,
480
- *,
481
- params: (
482
- Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
483
- | None
484
- ) = None,
485
- pangea_response: Literal[False] = False,
486
- ) -> TResult:
487
- """
488
- Makes the GET call to a Pangea Service endpoint.
489
-
490
- Args:
491
- path: Additional URL path
492
- params: Dictionary of querystring data to attach to the request
493
- """
494
-
495
- def get(
496
- self,
497
- path: str,
498
- result_class: Type[TResult],
499
- check_response: bool = True,
500
- *,
501
- params: (
502
- Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
503
- | None
504
- ) = None,
505
- pangea_response: bool = True,
506
- ) -> PangeaResponse[TResult] | TResult:
507
- """
508
- Makes the GET call to a Pangea Service endpoint.
509
-
510
- Args:
511
- path: Additional URL path
512
- params: Dictionary of querystring data to attach to the request
513
- pangea_response: Whether or not the response body follows Pangea's
514
- standard response schema
515
- """
516
-
517
401
  url = self._url(path)
518
402
  self.logger.debug(json.dumps({"service": self.service, "action": "get", "url": url}))
519
- requests_response = self.session.get(url, params=params, headers=self._headers())
403
+ requests_response = self.session.get(url, headers=self._headers())
520
404
  self._check_http_errors(requests_response)
521
-
522
- if not pangea_response:
523
- type_adapter = TypeAdapter(result_class)
524
- return type_adapter.validate_python(requests_response.json())
525
-
526
- pangea_response_obj: PangeaResponse = PangeaResponse(
405
+ pangea_response: PangeaResponse = PangeaResponse(
527
406
  requests_response, result_class=result_class, json=requests_response.json()
528
407
  )
529
408
 
530
409
  self.logger.debug(
531
410
  json.dumps(
532
- {"service": self.service, "action": "get", "url": url, "response": pangea_response_obj.json},
411
+ {"service": self.service, "action": "get", "url": url, "response": pangea_response.json},
533
412
  default=default_encoder,
534
413
  )
535
414
  )
536
415
 
537
416
  if check_response is False:
538
- return pangea_response_obj
417
+ return pangea_response
539
418
 
540
- return self._check_response(pangea_response_obj)
419
+ return self._check_response(pangea_response)
541
420
 
542
421
  def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
543
422
  """
@@ -613,7 +492,7 @@ class PangeaRequest(PangeaRequestBase):
613
492
  self,
614
493
  endpoint: str,
615
494
  result_class: Type[PangeaResponseResult],
616
- data: Union[str, Dict] = {},
495
+ data: Union[str, Mapping[str, Any]] = {},
617
496
  ) -> PangeaResponse:
618
497
  # Send request
619
498
  try:
@@ -656,8 +535,8 @@ class PangeaRequest(PangeaRequestBase):
656
535
  def _http_put(
657
536
  self,
658
537
  url: str,
659
- files: List[Tuple],
660
- headers: Dict = {},
538
+ files: list[Tuple],
539
+ headers: Mapping[str, str] = {},
661
540
  ) -> requests.Response:
662
541
  self.logger.debug(
663
542
  json.dumps({"service": self.service, "action": "http_put", "url": url}, default=default_encoder)
@@ -669,7 +548,7 @@ class PangeaRequest(PangeaRequestBase):
669
548
  self,
670
549
  endpoint: str,
671
550
  result_class: Type[PangeaResponseResult],
672
- data: Union[str, Dict] = {},
551
+ data: Union[str, Mapping[str, Any]] = {},
673
552
  files: Optional[List[Tuple]] = None,
674
553
  ):
675
554
  if files is None or len(files) == 0:
@@ -739,7 +618,7 @@ class PangeaRequest(PangeaRequestBase):
739
618
  {"service": self.service, "action": "poll_presigned_url", "step": "exit", "cause": {str(e)}}
740
619
  )
741
620
  )
742
- raise pe.PresignedURLException("Failed to pull Presigned URL", loop_resp, e)
621
+ raise pe.PresignedURLException("Failed to pull Presigned URL", loop_resp, e) from e
743
622
 
744
623
  self.logger.debug(json.dumps({"service": self.service, "action": "poll_presigned_url", "step": "exit"}))
745
624
 
@@ -747,6 +626,7 @@ class PangeaRequest(PangeaRequestBase):
747
626
  return loop_resp
748
627
  raise loop_exc
749
628
 
629
+ @override
750
630
  def _init_session(self) -> requests.Session:
751
631
  retry_config = Retry(
752
632
  total=self.config.request_retries,
pangea/response.py CHANGED
@@ -1,19 +1,25 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
4
+ # TODO: Modernize.
5
+ # ruff: noqa: UP006, UP035
6
+
7
+ from __future__ import annotations
8
+
3
9
  import datetime
4
10
  import enum
5
11
  import os
6
- from typing import Any, Dict, Generic, List, Optional, Type, Union
12
+ from typing import Annotated, Any, Dict, Generic, List, Optional, Type, Union
7
13
 
8
14
  import aiohttp
9
15
  import requests
10
16
  from pydantic import BaseModel, ConfigDict, PlainSerializer
11
- from typing_extensions import Annotated, TypeVar
17
+ from typing_extensions import TypeVar
12
18
 
13
19
  from pangea.utils import format_datetime
14
20
 
15
21
 
16
- class AttachedFile(object):
22
+ class AttachedFile:
17
23
  filename: str
18
24
  file: bytes
19
25
  content_type: str
@@ -40,10 +46,7 @@ class AttachedFile(object):
40
46
  base_name, ext = os.path.splitext(file_path)
41
47
  counter = 1
42
48
  while os.path.exists(file_path):
43
- if ext:
44
- file_path = f"{base_name}_{counter}{ext}"
45
- else:
46
- file_path = f"{base_name}_{counter}"
49
+ file_path = f"{base_name}_{counter}{ext}" if ext else f"{base_name}_{counter}"
47
50
  counter += 1
48
51
  return file_path
49
52
 
@@ -79,14 +82,12 @@ class TransferMethod(str, enum.Enum):
79
82
  PangeaDateTime = Annotated[datetime.datetime, PlainSerializer(format_datetime)]
80
83
 
81
84
 
82
- # API response should accept arbitrary fields to make them accept possible new parameters
83
- class APIResponseModel(BaseModel):
84
- model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
85
+ class APIRequestModel(BaseModel):
86
+ model_config = ConfigDict(extra="forbid")
85
87
 
86
88
 
87
- # API request models doesn't not allow arbitrary fields
88
- class APIRequestModel(BaseModel):
89
- model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
89
+ class APIResponseModel(BaseModel):
90
+ model_config = ConfigDict(extra="allow")
90
91
 
91
92
 
92
93
  class PangeaResponseResult(APIResponseModel):
@@ -192,6 +193,8 @@ T = TypeVar("T", bound=PangeaResponseResult)
192
193
 
193
194
 
194
195
  class PangeaResponse(ResponseHeader, Generic[T]):
196
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")
197
+
195
198
  raw_result: Optional[Dict[str, Any]] = None
196
199
  raw_response: Optional[Union[requests.Response, aiohttp.ClientResponse]] = None
197
200
  result: Optional[T] = None
@@ -199,16 +202,16 @@ class PangeaResponse(ResponseHeader, Generic[T]):
199
202
  accepted_result: Optional[AcceptedResult] = None
200
203
  result_class: Type[T] = PangeaResponseResult # type: ignore[assignment]
201
204
  _json: Any
202
- attached_files: List[AttachedFile] = []
205
+ attached_files: list[AttachedFile] = []
203
206
 
204
207
  def __init__(
205
208
  self,
206
209
  response: requests.Response,
207
210
  result_class: Type[T],
208
211
  json: dict,
209
- attached_files: List[AttachedFile] = [],
212
+ attached_files: list[AttachedFile] = [], # noqa: B006
210
213
  ):
211
- super(PangeaResponse, self).__init__(**json)
214
+ super().__init__(**json)
212
215
  self._json = json
213
216
  self.raw_response = response
214
217
  self.raw_result = self._json["result"]
@@ -241,10 +244,10 @@ class PangeaResponse(ResponseHeader, Generic[T]):
241
244
  @property
242
245
  def http_status(self) -> int: # type: ignore[return]
243
246
  if self.raw_response:
244
- if type(self.raw_response) == aiohttp.ClientResponse:
247
+ if isinstance(self.raw_response, aiohttp.ClientResponse):
245
248
  return self.raw_response.status
246
249
  else:
247
- return self.raw_response.status_code # type: ignore[union-attr]
250
+ return self.raw_response.status_code
248
251
 
249
252
  @property
250
253
  def url(self) -> str:
@@ -1,3 +1,5 @@
1
+ # ruff: noqa: F401
2
+
1
3
  from .ai_guard import AIGuard
2
4
  from .audit.audit import Audit
3
5
  from .authn.authn import AuthN
@@ -5,7 +7,6 @@ from .authz import AuthZ
5
7
  from .embargo import Embargo
6
8
  from .file_scan import FileScan
7
9
  from .intel import DomainIntel, FileIntel, IpIntel, UrlIntel, UserIntel
8
- from .management import Management
9
10
  from .prompt_guard import PromptGuard
10
11
  from .redact import Redact
11
12
  from .sanitize import Sanitize