pangea-sdk 6.5.0b1__tar.gz → 6.6.0__tar.gz

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 (71) hide show
  1. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/PKG-INFO +17 -18
  2. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/README.md +1 -2
  3. pangea_sdk-6.6.0/pangea/__init__.py +7 -0
  4. pangea_sdk-6.6.0/pangea/_constants.py +4 -0
  5. pangea_sdk-6.6.0/pangea/_typing.py +30 -0
  6. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/__init__.py +2 -1
  7. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/file_uploader.py +3 -2
  8. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/request.py +66 -162
  9. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/__init__.py +19 -3
  10. pangea_sdk-6.6.0/pangea/asyncio/services/ai_guard.py +196 -0
  11. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/audit.py +1 -301
  12. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/authn.py +25 -8
  13. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/base.py +21 -6
  14. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/file_scan.py +1 -1
  15. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/intel.py +160 -95
  16. pangea_sdk-6.6.0/pangea/asyncio/services/prompt_guard.py +87 -0
  17. pangea_sdk-6.6.0/pangea/asyncio/services/redact.py +206 -0
  18. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/config.py +4 -2
  19. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/file_uploader.py +4 -1
  20. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/request.py +91 -166
  21. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/response.py +5 -1
  22. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/__init__.py +19 -3
  23. pangea_sdk-6.6.0/pangea/services/ai_guard.py +570 -0
  24. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/audit/audit.py +3 -301
  25. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/audit/models.py +1 -273
  26. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/audit/util.py +2 -0
  27. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/authn/authn.py +4 -5
  28. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/base.py +3 -0
  29. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/file_scan.py +3 -2
  30. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/intel.py +187 -252
  31. pangea_sdk-6.6.0/pangea/services/prompt_guard.py +121 -0
  32. pangea_sdk-6.6.0/pangea/services/redact.py +388 -0
  33. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/vault/vault.py +3 -0
  34. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pyproject.toml +23 -21
  35. pangea_sdk-6.5.0b1/pangea/__init__.py +0 -15
  36. pangea_sdk-6.5.0b1/pangea/asyncio/services/ai_guard.py +0 -342
  37. pangea_sdk-6.5.0b1/pangea/asyncio/services/management.py +0 -576
  38. pangea_sdk-6.5.0b1/pangea/asyncio/services/prompt_guard.py +0 -194
  39. pangea_sdk-6.5.0b1/pangea/asyncio/services/redact.py +0 -467
  40. pangea_sdk-6.5.0b1/pangea/services/ai_guard.py +0 -1180
  41. pangea_sdk-6.5.0b1/pangea/services/management.py +0 -720
  42. pangea_sdk-6.5.0b1/pangea/services/prompt_guard.py +0 -309
  43. pangea_sdk-6.5.0b1/pangea/services/redact.py +0 -854
  44. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/authz.py +0 -0
  45. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/embargo.py +0 -0
  46. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/sanitize.py +0 -0
  47. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/share.py +0 -0
  48. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/asyncio/services/vault.py +0 -0
  49. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/audit_logger.py +0 -0
  50. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/crypto/rsa.py +0 -0
  51. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/deep_verify.py +0 -0
  52. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/deprecated.py +0 -0
  53. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/dump_audit.py +0 -0
  54. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/exceptions.py +0 -0
  55. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/py.typed +0 -0
  56. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/audit/exceptions.py +0 -0
  57. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/audit/signing.py +0 -0
  58. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/authn/models.py +0 -0
  59. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/authz.py +0 -0
  60. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/embargo.py +0 -0
  61. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/sanitize.py +0 -0
  62. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/share/file_format.py +0 -0
  63. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/share/share.py +0 -0
  64. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/vault/models/asymmetric.py +0 -0
  65. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/vault/models/common.py +0 -0
  66. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/vault/models/keys.py +0 -0
  67. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/vault/models/secret.py +0 -0
  68. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/services/vault/models/symmetric.py +0 -0
  69. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/tools.py +0 -0
  70. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/utils.py +0 -0
  71. {pangea_sdk-6.5.0b1 → pangea_sdk-6.6.0}/pangea/verify_audit.py +0 -0
@@ -1,24 +1,25 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: pangea-sdk
3
- Version: 6.5.0b1
3
+ Version: 6.6.0
4
4
  Summary: Pangea API SDK
5
- License: MIT
6
5
  Keywords: Pangea,SDK,Audit
7
6
  Author: Glenn Gallien
8
- Author-email: glenn.gallien@pangea.cloud
9
- Requires-Python: >=3.9.2,<4.0.0
7
+ Author-email: Glenn Gallien <glenn.gallien@pangea.cloud>
8
+ License-Expression: MIT
10
9
  Classifier: Topic :: Software Development
11
10
  Classifier: Topic :: Software Development :: Libraries
12
- Requires-Dist: aiohttp (>=3.12.14,<4.0.0)
13
- Requires-Dist: cryptography (>=45.0.5,<46.0.0)
14
- Requires-Dist: deprecated (>=1.2.18,<2.0.0)
15
- Requires-Dist: google-crc32c (>=1.7.1,<2.0.0)
16
- Requires-Dist: pydantic (>=2.11.7,<3.0.0)
17
- Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
18
- Requires-Dist: requests (>=2.32.4,<3.0.0)
19
- Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
20
- Requires-Dist: typing-extensions (>=4.14.1,<5.0.0)
21
- Requires-Dist: yarl (>=1.20.1,<2.0.0)
11
+ Requires-Dist: aiohttp>=3.12.15,<4.0.0
12
+ Requires-Dist: cryptography>=45.0.6,<46.0.0
13
+ Requires-Dist: deprecated>=1.2.18,<2.0.0
14
+ Requires-Dist: google-crc32c>=1.7.1,<2.0.0
15
+ Requires-Dist: pydantic>=2.11.7,<3.0.0
16
+ Requires-Dist: python-dateutil>=2.9.0.post0,<3.0.0
17
+ Requires-Dist: requests>=2.32.5,<3.0.0
18
+ Requires-Dist: requests-toolbelt>=1.0.0,<2.0.0
19
+ Requires-Dist: typing-extensions>=4.15.0,<5.0.0
20
+ Requires-Dist: yarl>=1.20.1,<2.0.0
21
+ Requires-Python: >=3.9.2, <4.0.0
22
+ Project-URL: repository, https://github.com/pangeacyber/pangea-python
22
23
  Description-Content-Type: text/markdown
23
24
 
24
25
  <a href="https://pangea.cloud?utm_source=github&utm_medium=python-sdk" target="_blank" rel="noopener noreferrer">
@@ -116,8 +117,7 @@ The SDK supports the following configuration options via `PangeaConfig`:
116
117
  Use `base_url_template` for more control over the URL, such as setting
117
118
  service-specific paths. Defaults to `aws.us.pangea.cloud`.
118
119
  - `request_retries` — Number of retries on the initial request.
119
- - `request_backoff` — Backoff strategy passed to 'requests'.
120
- - `request_timeout` — Timeout used on initial request attempts.
120
+ - `request_backoff` — A backoff factor to apply between request attempts.
121
121
  - `poll_result_timeout` — Timeout used to poll results after 202 (in secs).
122
122
  - `queued_retry_enabled` — Enable queued request retry support.
123
123
  - `custom_user_agent` — Custom user agent to be used in the request headers.
@@ -243,4 +243,3 @@ It accepts multiple file formats:
243
243
  [Beta Examples]: https://github.com/pangeacyber/pangea-python/tree/beta/examples
244
244
  [Pangea Console]: https://console.pangea.cloud/
245
245
  [Secure Audit Log]: https://pangea.cloud/docs/audit
246
-
@@ -93,8 +93,7 @@ The SDK supports the following configuration options via `PangeaConfig`:
93
93
  Use `base_url_template` for more control over the URL, such as setting
94
94
  service-specific paths. Defaults to `aws.us.pangea.cloud`.
95
95
  - `request_retries` — Number of retries on the initial request.
96
- - `request_backoff` — Backoff strategy passed to 'requests'.
97
- - `request_timeout` — Timeout used on initial request attempts.
96
+ - `request_backoff` — A backoff factor to apply between request attempts.
98
97
  - `poll_result_timeout` — Timeout used to poll results after 202 (in secs).
99
98
  - `queued_retry_enabled` — Enable queued request retry support.
100
99
  - `custom_user_agent` — Custom user agent to be used in the request headers.
@@ -0,0 +1,7 @@
1
+ __version__ = "6.6.0"
2
+
3
+ from pangea.config import PangeaConfig
4
+ from pangea.file_uploader import FileUploader
5
+ from pangea.response import PangeaResponse, PangeaResponseResult, TransferMethod
6
+
7
+ __all__ = ("FileUploader", "PangeaConfig", "PangeaResponse", "PangeaResponseResult", "TransferMethod")
@@ -0,0 +1,4 @@
1
+ from __future__ import annotations
2
+
3
+ MAX_RETRY_DELAY = 8.0
4
+ RETRYABLE_HTTP_CODES = frozenset({500, 502, 503, 504})
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterator, Sequence
4
+
5
+ from typing_extensions import Any, Protocol, SupportsIndex, TypeVar, overload
6
+
7
+ T = TypeVar("T")
8
+ T_co = TypeVar("T_co", covariant=True)
9
+
10
+
11
+ class SequenceNotStr(Protocol[T_co]):
12
+ """Sequence-like object that isn't str or bytes."""
13
+
14
+ @overload
15
+ def __getitem__(self, index: SupportsIndex, /) -> T_co: ...
16
+
17
+ @overload
18
+ def __getitem__(self, index: slice, /) -> Sequence[T_co]: ...
19
+
20
+ def __contains__(self, value: object, /) -> bool: ...
21
+
22
+ def __len__(self) -> int: ...
23
+
24
+ def __iter__(self) -> Iterator[T_co]: ...
25
+
26
+ def index(self, value: Any, start: int = ..., stop: int = ..., /) -> int: ...
27
+
28
+ def count(self, value: Any, /) -> int: ...
29
+
30
+ def __reversed__(self) -> Iterator[T_co]: ...
@@ -1,2 +1,3 @@
1
- # ruff: noqa: F401
2
1
  from .file_uploader import FileUploaderAsync
2
+
3
+ __all__ = ("FileUploaderAsync",)
@@ -6,9 +6,10 @@ from __future__ import annotations
6
6
  import io
7
7
  import logging
8
8
 
9
+ from pangea import PangeaConfig, TransferMethod
9
10
  from pangea.asyncio.request import PangeaRequestAsync
10
- from pangea.request import PangeaConfig
11
- from pangea.response import TransferMethod
11
+
12
+ __all__ = ("FileUploaderAsync",)
12
13
 
13
14
 
14
15
  class FileUploaderAsync:
@@ -10,19 +10,23 @@ import asyncio
10
10
  import json
11
11
  import time
12
12
  from collections.abc import Iterable, Mapping
13
- from typing import Dict, List, Optional, Sequence, Tuple, Type, Union, cast, overload
13
+ from random import random
14
+ from typing import Dict, List, Optional, Sequence, Tuple, Type, Union, cast
14
15
 
15
16
  import aiohttp
16
17
  from aiohttp import FormData
17
- from pydantic import BaseModel, TypeAdapter
18
+ from pydantic import BaseModel
18
19
  from pydantic_core import to_jsonable_python
19
- from typing_extensions import Any, Literal, TypeAlias, TypeVar, override
20
+ from typing_extensions import Any, TypeAlias, TypeVar, override
20
21
 
21
22
  import pangea.exceptions as pe
23
+ from pangea._constants import MAX_RETRY_DELAY, RETRYABLE_HTTP_CODES
22
24
  from pangea.request import MultipartResponse, PangeaRequestBase
23
25
  from pangea.response import AttachedFile, PangeaResponse, PangeaResponseResult, ResponseStatus, TransferMethod
24
26
  from pangea.utils import default_encoder
25
27
 
28
+ __all__ = ("PangeaRequestAsync",)
29
+
26
30
  _FileName: TypeAlias = Union[str, None]
27
31
  _FileContent: TypeAlias = Union[str, bytes]
28
32
  _FileContentType: TypeAlias = str
@@ -50,24 +54,6 @@ class PangeaRequestAsync(PangeaRequestBase):
50
54
  be set in PangeaConfig.
51
55
  """
52
56
 
53
- async def delete(self, endpoint: str) -> None:
54
- """
55
- Makes a DELETE call to a Pangea endpoint.
56
-
57
- Args:
58
- endpoint: The Pangea API endpoint.
59
- """
60
-
61
- url = self._url(endpoint)
62
-
63
- self.logger.debug(
64
- json.dumps({"service": self.service, "action": "delete", "url": url}, default=default_encoder)
65
- )
66
-
67
- requests_response = await self._http_delete(url, headers=self._headers())
68
- await self._check_http_errors(requests_response)
69
-
70
- @overload
71
57
  async def post(
72
58
  self,
73
59
  endpoint: str,
@@ -76,60 +62,18 @@ class PangeaRequestAsync(PangeaRequestBase):
76
62
  files: Optional[List[Tuple]] = None,
77
63
  poll_result: bool = True,
78
64
  url: Optional[str] = None,
79
- *,
80
- pangea_response: Literal[True] = True,
81
65
  ) -> PangeaResponse[TResult]:
82
- """
83
- Makes a POST call to a Pangea Service endpoint.
66
+ """Makes the POST call to a Pangea Service endpoint.
84
67
 
85
68
  Args:
86
- endpoint: The Pangea Service API endpoint.
87
- data: The POST body payload object
69
+ endpoint(str): The Pangea Service API endpoint.
70
+ data(dict): The POST body payload object
88
71
 
89
72
  Returns:
90
73
  PangeaResponse which contains the response in its entirety and
91
74
  various properties to retrieve individual fields
92
75
  """
93
76
 
94
- @overload
95
- async def post(
96
- self,
97
- endpoint: str,
98
- result_class: Type[TResult],
99
- data: str | BaseModel | Mapping[str, Any] | None = None,
100
- files: Optional[List[Tuple]] = None,
101
- poll_result: bool = True,
102
- url: Optional[str] = None,
103
- *,
104
- pangea_response: Literal[False],
105
- ) -> TResult:
106
- """
107
- Makes a POST call to a Pangea Service endpoint.
108
-
109
- Args:
110
- endpoint: The Pangea Service API endpoint.
111
- data: The POST body payload object
112
- """
113
-
114
- async def post(
115
- self,
116
- endpoint: str,
117
- result_class: Type[TResult],
118
- data: str | BaseModel | Mapping[str, Any] | None = None,
119
- files: Optional[List[Tuple]] = None,
120
- poll_result: bool = True,
121
- url: Optional[str] = None,
122
- *,
123
- pangea_response: bool = True,
124
- ) -> PangeaResponse[TResult] | TResult:
125
- """
126
- Makes a POST call to a Pangea Service endpoint.
127
-
128
- Args:
129
- endpoint: The Pangea Service API endpoint.
130
- data: The POST body payload object
131
- """
132
-
133
77
  if isinstance(data, BaseModel):
134
78
  data = data.model_dump(exclude_none=True)
135
79
 
@@ -169,13 +113,9 @@ class PangeaRequestAsync(PangeaRequestBase):
169
113
 
170
114
  await self._check_http_errors(requests_response)
171
115
 
172
- if not pangea_response:
173
- type_adapter = TypeAdapter(result_class)
174
- return type_adapter.validate_python(await requests_response.json())
175
-
176
116
  if "multipart/form-data" in requests_response.headers.get("content-type", ""):
177
117
  multipart_response = await self._process_multipart_response(requests_response)
178
- pangea_response_obj: PangeaResponse = PangeaResponse(
118
+ pangea_response: PangeaResponse = PangeaResponse(
179
119
  requests_response,
180
120
  result_class=result_class,
181
121
  json=multipart_response.pangea_json,
@@ -188,110 +128,49 @@ class PangeaRequestAsync(PangeaRequestBase):
188
128
  json.dumps({"service": self.service, "action": "post", "url": url, "response": json_resp})
189
129
  )
190
130
 
191
- pangea_response_obj = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
131
+ pangea_response = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
192
132
  except aiohttp.ContentTypeError as e:
193
133
  raise pe.PangeaException(
194
134
  f"Failed to decode json response. {e}. Body: {await requests_response.text()}"
195
135
  ) from e
196
136
 
197
137
  if poll_result:
198
- pangea_response_obj = await self._handle_queued_result(pangea_response_obj)
138
+ pangea_response = await self._handle_queued_result(pangea_response)
199
139
 
200
- return self._check_response(pangea_response_obj)
140
+ return self._check_response(pangea_response)
201
141
 
202
- @overload
203
- async def get(
204
- self,
205
- path: str,
206
- result_class: Type[TResult],
207
- check_response: bool = True,
208
- *,
209
- params: (
210
- Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
211
- | None
212
- ) = None,
213
- pangea_response: Literal[True] = True,
214
- ) -> PangeaResponse[TResult]:
215
- """
216
- Makes the GET call to a Pangea Service endpoint.
142
+ async def get(self, path: str, result_class: Type[TResult], check_response: bool = True) -> PangeaResponse[TResult]:
143
+ """Makes the GET call to a Pangea Service endpoint.
217
144
 
218
145
  Args:
219
- path: Additional URL path
220
- params: Dictionary of querystring data to attach to the request
146
+ endpoint(str): The Pangea Service API endpoint.
147
+ path(str): Additional URL path
221
148
 
222
149
  Returns:
223
150
  PangeaResponse which contains the response in its entirety and
224
151
  various properties to retrieve individual fields
225
152
  """
226
153
 
227
- @overload
228
- async def get(
229
- self,
230
- path: str,
231
- result_class: Type[TResult],
232
- check_response: bool = True,
233
- *,
234
- params: (
235
- Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
236
- | None
237
- ) = None,
238
- pangea_response: Literal[False] = False,
239
- ) -> TResult:
240
- """
241
- Makes the GET call to a Pangea Service endpoint.
242
-
243
- Args:
244
- path: Additional URL path
245
- params: Dictionary of querystring data to attach to the request
246
- """
247
-
248
- async def get(
249
- self,
250
- path: str,
251
- result_class: Type[TResult],
252
- check_response: bool = True,
253
- *,
254
- params: (
255
- Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
256
- | None
257
- ) = None,
258
- pangea_response: bool = True,
259
- ) -> PangeaResponse[TResult] | TResult:
260
- """
261
- Makes the GET call to a Pangea Service endpoint.
262
-
263
- Args:
264
- path: Additional URL path
265
- params: Dictionary of querystring data to attach to the request
266
- pangea_response: Whether or not the response body follows Pangea's
267
- standard response schema
268
- """
269
-
270
154
  url = self._url(path)
271
155
  self.logger.debug(json.dumps({"service": self.service, "action": "get", "url": url}))
272
156
 
273
- async with self.session.get(url, params=params, headers=self._headers()) as requests_response:
157
+ async with self.session.get(url, headers=self._headers()) as requests_response:
274
158
  await self._check_http_errors(requests_response)
275
-
276
- if not pangea_response:
277
- type_adapter = TypeAdapter(result_class)
278
- return type_adapter.validate_python(await requests_response.json())
279
-
280
- pangea_response_obj = PangeaResponse(
159
+ pangea_response = PangeaResponse(
281
160
  requests_response, result_class=result_class, json=await requests_response.json()
282
161
  )
283
162
 
284
163
  self.logger.debug(
285
164
  json.dumps(
286
- {"service": self.service, "action": "get", "url": url, "response": pangea_response_obj.json},
165
+ {"service": self.service, "action": "get", "url": url, "response": pangea_response.json},
287
166
  default=default_encoder,
288
167
  )
289
168
  )
290
169
 
291
170
  if check_response is False:
292
- return pangea_response_obj
171
+ return pangea_response
293
172
 
294
- return self._check_response(pangea_response_obj)
173
+ return self._check_response(pangea_response)
295
174
 
296
175
  async def _check_http_errors(self, resp: aiohttp.ClientResponse):
297
176
  if resp.status == 503:
@@ -425,14 +304,6 @@ class PangeaRequestAsync(PangeaRequestBase):
425
304
  attached_files = await self._get_attached_files(multipart_reader)
426
305
  return MultipartResponse(pangea_json, attached_files) # type: ignore[arg-type]
427
306
 
428
- async def _http_delete(
429
- self,
430
- url: str,
431
- *,
432
- headers: Mapping[str, str | bytes | None] = {},
433
- ) -> aiohttp.ClientResponse:
434
- return await self.session.delete(url, headers=headers)
435
-
436
307
  async def _http_post(
437
308
  self,
438
309
  url: str,
@@ -595,13 +466,46 @@ class PangeaRequestAsync(PangeaRequestBase):
595
466
 
596
467
  @override
597
468
  def _init_session(self) -> aiohttp.ClientSession:
598
- # retry_config = Retry(
599
- # total=self.config.request_retries,
600
- # backoff_factor=self.config.request_backoff,
601
- # status_forcelist=[500, 502, 503, 504],
602
- # )
603
- # adapter = HTTPAdapter(max_retries=retry_config)
604
- # TODO: Add retry config
605
-
606
- session = aiohttp.ClientSession()
607
- return session
469
+ return aiohttp.ClientSession(middlewares=[self._retry_middleware])
470
+
471
+ def _calculate_retry_timeout(self, remaining_retries: int) -> float:
472
+ max_retries = self.config.request_retries
473
+ nb_retries = min(max_retries - remaining_retries, 1000)
474
+ sleep_seconds = min(self.config.request_backoff * pow(2.0, nb_retries), MAX_RETRY_DELAY)
475
+ jitter = 1 - 0.25 * random()
476
+ timeout = sleep_seconds * jitter
477
+ return max(timeout, 0)
478
+
479
+ async def _retry_middleware(
480
+ self, request: aiohttp.ClientRequest, handler: aiohttp.ClientHandlerType
481
+ ) -> aiohttp.ClientResponse:
482
+ max_retries = self.config.request_retries
483
+ request_ids = set[str]()
484
+ retries_taken = 0
485
+ for retries_taken in range(max_retries + 1):
486
+ remaining_retries = max_retries - retries_taken
487
+
488
+ if len(request_ids) > 0:
489
+ request.headers["X-Pangea-Retried-Request-Ids"] = ",".join(request_ids)
490
+
491
+ response = await handler(request)
492
+
493
+ request_id = response.headers.get("x-request-id")
494
+ if request_id:
495
+ request_ids.add(request_id)
496
+
497
+ if not response.ok and remaining_retries > 0 and self._should_retry(response):
498
+ await self._sleep_for_retry(retries_taken=retries_taken, max_retries=max_retries)
499
+ continue
500
+
501
+ break
502
+
503
+ return response
504
+
505
+ def _should_retry(self, response: aiohttp.ClientResponse) -> bool:
506
+ return response.status in RETRYABLE_HTTP_CODES
507
+
508
+ async def _sleep_for_retry(self, *, retries_taken: int, max_retries: int) -> None:
509
+ remaining_retries = max_retries - retries_taken
510
+ timeout = self._calculate_retry_timeout(remaining_retries)
511
+ await asyncio.sleep(timeout)
@@ -1,5 +1,3 @@
1
- # ruff: noqa: F401
2
-
3
1
  from .ai_guard import AIGuardAsync
4
2
  from .audit import AuditAsync
5
3
  from .authn import AuthNAsync
@@ -7,9 +5,27 @@ from .authz import AuthZAsync
7
5
  from .embargo import EmbargoAsync
8
6
  from .file_scan import FileScanAsync
9
7
  from .intel import DomainIntelAsync, FileIntelAsync, IpIntelAsync, UrlIntelAsync, UserIntelAsync
10
- from .management import ManagementAsync
11
8
  from .prompt_guard import PromptGuardAsync
12
9
  from .redact import RedactAsync
13
10
  from .sanitize import SanitizeAsync
14
11
  from .share import ShareAsync
15
12
  from .vault import VaultAsync
13
+
14
+ __all__ = (
15
+ "AIGuardAsync",
16
+ "AuditAsync",
17
+ "AuthNAsync",
18
+ "AuthZAsync",
19
+ "DomainIntelAsync",
20
+ "EmbargoAsync",
21
+ "FileIntelAsync",
22
+ "FileScanAsync",
23
+ "IpIntelAsync",
24
+ "PromptGuardAsync",
25
+ "RedactAsync",
26
+ "SanitizeAsync",
27
+ "ShareAsync",
28
+ "UrlIntelAsync",
29
+ "UserIntelAsync",
30
+ "VaultAsync",
31
+ )