pangea-sdk 6.2.0b1__py3-none-any.whl → 6.2.0b2__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 (54) 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 +51 -21
  5. pangea/asyncio/services/__init__.py +2 -0
  6. pangea/asyncio/services/ai_guard.py +91 -2
  7. pangea/asyncio/services/audit.py +14 -8
  8. pangea/asyncio/services/authn.py +33 -23
  9. pangea/asyncio/services/authz.py +6 -6
  10. pangea/asyncio/services/base.py +4 -0
  11. pangea/asyncio/services/file_scan.py +8 -2
  12. pangea/asyncio/services/intel.py +6 -2
  13. pangea/asyncio/services/prompt_guard.py +112 -2
  14. pangea/asyncio/services/redact.py +7 -3
  15. pangea/asyncio/services/sanitize.py +5 -1
  16. pangea/asyncio/services/share.py +5 -1
  17. pangea/asyncio/services/vault.py +19 -15
  18. pangea/audit_logger.py +3 -1
  19. pangea/deep_verify.py +13 -13
  20. pangea/deprecated.py +1 -1
  21. pangea/dump_audit.py +2 -3
  22. pangea/exceptions.py +8 -5
  23. pangea/file_uploader.py +4 -0
  24. pangea/request.py +58 -41
  25. pangea/response.py +15 -12
  26. pangea/services/__init__.py +2 -0
  27. pangea/services/ai_guard.py +497 -16
  28. pangea/services/audit/audit.py +15 -13
  29. pangea/services/audit/models.py +4 -0
  30. pangea/services/audit/signing.py +1 -1
  31. pangea/services/audit/util.py +10 -10
  32. pangea/services/authn/authn.py +33 -23
  33. pangea/services/authn/models.py +3 -0
  34. pangea/services/authz.py +10 -6
  35. pangea/services/base.py +5 -1
  36. pangea/services/embargo.py +6 -0
  37. pangea/services/file_scan.py +8 -2
  38. pangea/services/intel.py +4 -0
  39. pangea/services/management.py +8 -8
  40. pangea/services/prompt_guard.py +193 -2
  41. pangea/services/redact.py +7 -3
  42. pangea/services/sanitize.py +5 -1
  43. pangea/services/share/share.py +13 -7
  44. pangea/services/vault/models/asymmetric.py +4 -0
  45. pangea/services/vault/models/common.py +4 -0
  46. pangea/services/vault/models/symmetric.py +4 -0
  47. pangea/services/vault/vault.py +17 -19
  48. pangea/tools.py +13 -9
  49. pangea/utils.py +3 -5
  50. pangea/verify_audit.py +23 -27
  51. {pangea_sdk-6.2.0b1.dist-info → pangea_sdk-6.2.0b2.dist-info}/METADATA +6 -6
  52. pangea_sdk-6.2.0b2.dist-info/RECORD +62 -0
  53. {pangea_sdk-6.2.0b1.dist-info → pangea_sdk-6.2.0b2.dist-info}/WHEEL +1 -1
  54. 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, overload
11
15
 
12
16
  import requests
13
17
  from pydantic import BaseModel, TypeAdapter
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 Literal, TypeAlias, TypeVar, override
18
22
  from yarl import URL
19
23
 
20
24
  import pangea
@@ -27,11 +31,24 @@ 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
+
30
47
  class MultipartResponse:
31
48
  pangea_json: Dict[str, str]
32
49
  attached_files: List = []
33
50
 
34
- def __init__(self, pangea_json: Dict[str, str], attached_files: List = []):
51
+ def __init__(self, pangea_json: dict[str, str], attached_files: list = []): # noqa: B006
35
52
  self.pangea_json = pangea_json
36
53
  self.attached_files = attached_files
37
54
 
@@ -116,6 +133,7 @@ class PangeaRequestBase:
116
133
  headers = {
117
134
  "User-Agent": self._user_agent,
118
135
  "Authorization": f"Bearer {self.token}",
136
+ "Content-Type": "application/json",
119
137
  }
120
138
 
121
139
  # We want to ignore previous headers if user tried to set them, so we will overwrite them.
@@ -185,6 +203,9 @@ class PangeaRequestBase:
185
203
  raise pe.AcceptedRequestException(response)
186
204
  raise pe.PangeaAPIException(f"{summary} ", response)
187
205
 
206
+ def _init_session(self) -> requests.Session | aiohttp.ClientSession:
207
+ raise NotImplementedError
208
+
188
209
 
189
210
  TResult = TypeVar("TResult", bound=PangeaResponseResult)
190
211
 
@@ -223,8 +244,8 @@ class PangeaRequest(PangeaRequestBase):
223
244
  self,
224
245
  endpoint: str,
225
246
  result_class: Type[TResult],
226
- data: str | BaseModel | dict[str, Any] | None = None,
227
- files: Optional[List[Tuple]] = None,
247
+ data: str | BaseModel | Mapping[str, Any] | None = None,
248
+ files: Optional[list[Tuple]] = None,
228
249
  poll_result: bool = True,
229
250
  url: Optional[str] = None,
230
251
  *,
@@ -247,8 +268,8 @@ class PangeaRequest(PangeaRequestBase):
247
268
  self,
248
269
  endpoint: str,
249
270
  result_class: Type[TResult],
250
- data: str | BaseModel | dict[str, Any] | None = None,
251
- files: Optional[List[Tuple]] = None,
271
+ data: str | BaseModel | Mapping[str, Any] | None = None,
272
+ files: Optional[list[Tuple]] = None,
252
273
  poll_result: bool = True,
253
274
  url: Optional[str] = None,
254
275
  *,
@@ -266,15 +287,15 @@ class PangeaRequest(PangeaRequestBase):
266
287
  self,
267
288
  endpoint: str,
268
289
  result_class: Type[TResult],
269
- data: str | BaseModel | dict[str, Any] | None = None,
270
- files: Optional[List[Tuple]] = None,
290
+ data: str | BaseModel | Mapping[str, Any] | None = None,
291
+ files: Optional[list[Tuple]] = None,
271
292
  poll_result: bool = True,
272
293
  url: Optional[str] = None,
273
294
  *,
274
295
  pangea_response: bool = True,
275
296
  ) -> PangeaResponse[TResult] | TResult:
276
297
  """
277
- Makes the POST call to a Pangea Service endpoint.
298
+ Makes a POST call to a Pangea Service endpoint.
278
299
 
279
300
  Args:
280
301
  endpoint: The Pangea Service API endpoint.
@@ -294,7 +315,7 @@ class PangeaRequest(PangeaRequestBase):
294
315
  data = {}
295
316
 
296
317
  # Normalize.
297
- data = cast(dict[str, Any], to_jsonable_python(data))
318
+ data = cast(dict[str, Any], to_jsonable_python(data, exclude_none=True))
298
319
 
299
320
  if url is None:
300
321
  url = self._url(endpoint)
@@ -313,9 +334,10 @@ class PangeaRequest(PangeaRequestBase):
313
334
  endpoint, result_class=result_class, data=data, files=files
314
335
  )
315
336
  else:
316
- requests_response = self._http_post(
317
- url, headers=self._headers(), data=data, files=files, multipart_post=True
318
- )
337
+ headers = self._headers()
338
+ if transfer_method == TransferMethod.MULTIPART.value:
339
+ del headers["Content-Type"]
340
+ requests_response = self._http_post(url, headers=headers, data=data, files=files)
319
341
 
320
342
  self._check_http_errors(requests_response)
321
343
 
@@ -340,7 +362,7 @@ class PangeaRequest(PangeaRequestBase):
340
362
 
341
363
  pangea_response_obj = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
342
364
  except requests.exceptions.JSONDecodeError as e:
343
- raise pe.PangeaException(f"Failed to decode json response. {e}. Body: {requests_response.text}")
365
+ raise pe.PangeaException(f"Failed to decode json response. {e}. Body: {requests_response.text}") from e
344
366
 
345
367
  if poll_result:
346
368
  pangea_response_obj = self._handle_queued_result(pangea_response_obj)
@@ -399,9 +421,9 @@ class PangeaRequest(PangeaRequestBase):
399
421
  def _http_post(
400
422
  self,
401
423
  url: str,
402
- headers: Mapping[str, str | bytes | None] = {},
403
- data: Union[str, Dict] = {},
404
- files: Optional[List[Tuple]] = None,
424
+ headers: Mapping[str, str] = {},
425
+ data: str | dict[Any, Any] = {}, # noqa: B006
426
+ files: _Files | None = None,
405
427
  multipart_post: bool = True,
406
428
  ) -> requests.Response:
407
429
  data_send, files = self._http_post_process(data=data, files=files, multipart_post=multipart_post)
@@ -409,31 +431,25 @@ class PangeaRequest(PangeaRequestBase):
409
431
 
410
432
  def _http_post_process(
411
433
  self,
412
- data: Union[str, Dict] = {},
413
- files: Optional[Sequence[Tuple[str, Tuple[Any, str, str]]]] = None,
434
+ data: str | dict[Any, Any] = {}, # noqa: B006
435
+ files: _Files | None = None,
414
436
  multipart_post: bool = True,
415
- ):
437
+ ) -> tuple[_Data | None, _Files | None]:
416
438
  if files:
417
439
  if multipart_post is True:
418
440
  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
441
+ multi: list[tuple[str, _FileSpec]] = [("request", (None, data_send, "application/json")), *files]
442
+ return None, multi
443
+
444
+ # Post to presigned URL as form.
445
+ # When posting to presigned URL, file key should be 'file'.
446
+ assert isinstance(data, dict)
447
+ assert isinstance(files, list)
448
+ return [(k, v) for k, v in data.items()], {"file": files[0][1]}
449
+
432
450
  data_send = json.dumps(data, default=default_encoder) if isinstance(data, dict) else data
433
451
  return data_send, None
434
452
 
435
- return data, files
436
-
437
453
  def _handle_queued_result(self, response: PangeaResponse[TResult]) -> PangeaResponse[TResult]:
438
454
  if self._queued_retry_enabled and response.http_status == 202:
439
455
  self.logger.debug(
@@ -613,7 +629,7 @@ class PangeaRequest(PangeaRequestBase):
613
629
  self,
614
630
  endpoint: str,
615
631
  result_class: Type[PangeaResponseResult],
616
- data: Union[str, Dict] = {},
632
+ data: Union[str, Mapping[str, Any]] = {},
617
633
  ) -> PangeaResponse:
618
634
  # Send request
619
635
  try:
@@ -656,8 +672,8 @@ class PangeaRequest(PangeaRequestBase):
656
672
  def _http_put(
657
673
  self,
658
674
  url: str,
659
- files: List[Tuple],
660
- headers: Dict = {},
675
+ files: list[Tuple],
676
+ headers: Mapping[str, str] = {},
661
677
  ) -> requests.Response:
662
678
  self.logger.debug(
663
679
  json.dumps({"service": self.service, "action": "http_put", "url": url}, default=default_encoder)
@@ -669,7 +685,7 @@ class PangeaRequest(PangeaRequestBase):
669
685
  self,
670
686
  endpoint: str,
671
687
  result_class: Type[PangeaResponseResult],
672
- data: Union[str, Dict] = {},
688
+ data: Union[str, Mapping[str, Any]] = {},
673
689
  files: Optional[List[Tuple]] = None,
674
690
  ):
675
691
  if files is None or len(files) == 0:
@@ -739,7 +755,7 @@ class PangeaRequest(PangeaRequestBase):
739
755
  {"service": self.service, "action": "poll_presigned_url", "step": "exit", "cause": {str(e)}}
740
756
  )
741
757
  )
742
- raise pe.PresignedURLException("Failed to pull Presigned URL", loop_resp, e)
758
+ raise pe.PresignedURLException("Failed to pull Presigned URL", loop_resp, e) from e
743
759
 
744
760
  self.logger.debug(json.dumps({"service": self.service, "action": "poll_presigned_url", "step": "exit"}))
745
761
 
@@ -747,6 +763,7 @@ class PangeaRequest(PangeaRequestBase):
747
763
  return loop_resp
748
764
  raise loop_exc
749
765
 
766
+ @override
750
767
  def _init_session(self) -> requests.Session:
751
768
  retry_config = Retry(
752
769
  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
 
@@ -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