pangea-sdk 3.8.0b1__tar.gz → 5.4.0b1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/PKG-INFO +43 -35
  2. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/README.md +31 -15
  3. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/__init__.py +1 -1
  4. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/file_uploader.py +1 -1
  5. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/request.py +56 -34
  6. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/services/__init__.py +4 -0
  7. pangea_sdk-5.4.0b1/pangea/asyncio/services/ai_guard.py +75 -0
  8. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/services/audit.py +192 -31
  9. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/services/authn.py +187 -109
  10. pangea_sdk-5.4.0b1/pangea/asyncio/services/authz.py +285 -0
  11. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/services/base.py +21 -2
  12. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/services/embargo.py +2 -2
  13. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/services/file_scan.py +24 -9
  14. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/services/intel.py +108 -34
  15. pangea_sdk-5.4.0b1/pangea/asyncio/services/prompt_guard.py +73 -0
  16. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/services/redact.py +72 -4
  17. pangea_sdk-5.4.0b1/pangea/asyncio/services/sanitize.py +217 -0
  18. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/services/share.py +246 -73
  19. pangea_sdk-5.4.0b1/pangea/asyncio/services/vault.py +2240 -0
  20. pangea_sdk-5.4.0b1/pangea/crypto/rsa.py +135 -0
  21. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/deep_verify.py +7 -1
  22. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/dump_audit.py +9 -8
  23. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/request.py +87 -59
  24. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/response.py +49 -31
  25. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/__init__.py +4 -0
  26. pangea_sdk-5.4.0b1/pangea/services/ai_guard.py +128 -0
  27. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/audit/audit.py +205 -42
  28. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/audit/models.py +56 -8
  29. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/audit/signing.py +6 -5
  30. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/audit/util.py +3 -3
  31. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/authn/authn.py +140 -70
  32. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/authn/models.py +167 -11
  33. pangea_sdk-5.4.0b1/pangea/services/authz.py +400 -0
  34. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/base.py +39 -8
  35. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/embargo.py +2 -2
  36. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/file_scan.py +32 -15
  37. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/intel.py +157 -32
  38. pangea_sdk-5.4.0b1/pangea/services/prompt_guard.py +83 -0
  39. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/redact.py +152 -4
  40. pangea_sdk-5.4.0b1/pangea/services/sanitize.py +371 -0
  41. pangea_sdk-5.4.0b1/pangea/services/share/share.py +1440 -0
  42. pangea_sdk-5.4.0b1/pangea/services/vault/models/asymmetric.py +169 -0
  43. pangea_sdk-5.4.0b1/pangea/services/vault/models/common.py +727 -0
  44. pangea_sdk-5.4.0b1/pangea/services/vault/models/keys.py +94 -0
  45. pangea_sdk-5.4.0b1/pangea/services/vault/models/secret.py +48 -0
  46. pangea_sdk-5.4.0b1/pangea/services/vault/models/symmetric.py +109 -0
  47. pangea_sdk-5.4.0b1/pangea/services/vault/vault.py +2237 -0
  48. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/tools.py +6 -7
  49. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/utils.py +16 -27
  50. pangea_sdk-5.4.0b1/pangea/verify_audit.py +519 -0
  51. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pyproject.toml +32 -16
  52. pangea_sdk-3.8.0b1/pangea/asyncio/services/vault.py +0 -1280
  53. pangea_sdk-3.8.0b1/pangea/services/share/share.py +0 -864
  54. pangea_sdk-3.8.0b1/pangea/services/vault/models/asymmetric.py +0 -67
  55. pangea_sdk-3.8.0b1/pangea/services/vault/models/common.py +0 -429
  56. pangea_sdk-3.8.0b1/pangea/services/vault/models/secret.py +0 -24
  57. pangea_sdk-3.8.0b1/pangea/services/vault/models/symmetric.py +0 -63
  58. pangea_sdk-3.8.0b1/pangea/services/vault/vault.py +0 -1296
  59. pangea_sdk-3.8.0b1/pangea/verify_audit.py +0 -332
  60. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/asyncio/__init__.py +0 -0
  61. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/audit_logger.py +0 -0
  62. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/config.py +0 -0
  63. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/deprecated.py +0 -0
  64. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/exceptions.py +0 -0
  65. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/file_uploader.py +0 -0
  66. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/py.typed +0 -0
  67. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/audit/exceptions.py +0 -0
  68. {pangea_sdk-3.8.0b1 → pangea_sdk-5.4.0b1}/pangea/services/share/file_format.py +0 -0
@@ -1,31 +1,23 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: pangea-sdk
3
- Version: 3.8.0b1
3
+ Version: 5.4.0b1
4
4
  Summary: Pangea API SDK
5
- Home-page: https://pangea.cloud/docs/sdk/python/
6
5
  License: MIT
7
6
  Keywords: Pangea,SDK,Audit
8
7
  Author: Glenn Gallien
9
8
  Author-email: glenn.gallien@pangea.cloud
10
- Requires-Python: >=3.7.2,<4.0.0
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.8
14
- Classifier: Programming Language :: Python :: 3.9
15
- Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.11
9
+ Requires-Python: >=3.9
17
10
  Classifier: Topic :: Software Development
18
11
  Classifier: Topic :: Software Development :: Libraries
19
- Requires-Dist: aiohttp (>=3.8.6,<4.0.0)
20
- Requires-Dist: asyncio (>=3.4.3,<4.0.0)
21
- Requires-Dist: cryptography (>=42.0.5,<43.0.0)
22
- Requires-Dist: deprecated (>=1.2.14,<2.0.0)
23
- Requires-Dist: google-crc32c (>=1.5.0,<2.0.0)
24
- Requires-Dist: pydantic (>=1.10.14,<2.0.0)
25
- Requires-Dist: python-dateutil (>=2.9.0,<3.0.0)
26
- Requires-Dist: requests (>=2.31.0,<3.0.0)
27
- Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
28
- Project-URL: Repository, https://github.com/pangeacyber/pangea-python/tree/main/packages/pangea-sdk
12
+ Requires-Dist: aiohttp
13
+ Requires-Dist: cryptography
14
+ Requires-Dist: deprecated
15
+ Requires-Dist: google-crc32c
16
+ Requires-Dist: pydantic
17
+ Requires-Dist: python-dateutil
18
+ Requires-Dist: requests
19
+ Requires-Dist: requests-toolbelt
20
+ Requires-Dist: typing-extensions
29
21
  Description-Content-Type: text/markdown
30
22
 
31
23
  <a href="https://pangea.cloud?utm_source=github&utm_medium=python-sdk" target="_blank" rel="noopener noreferrer">
@@ -35,15 +27,17 @@ Description-Content-Type: text/markdown
35
27
  <br />
36
28
 
37
29
  [![documentation](https://img.shields.io/badge/documentation-pangea-blue?style=for-the-badge&labelColor=551B76)][Documentation]
38
- [![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)][Slack]
30
+ [![Discourse](https://img.shields.io/badge/Discourse-4A154B?style=for-the-badge&logo=discourse&logoColor=white)][Discourse]
39
31
 
40
32
  # Pangea Python SDK
41
33
 
42
- A Python SDK for integrating with Pangea services. Supports Python v3.7 and
34
+ A Python SDK for integrating with Pangea services. Supports Python v3.9 and
43
35
  above.
44
36
 
45
37
  ## Installation
46
38
 
39
+ #### GA releases
40
+
47
41
  Via pip:
48
42
 
49
43
  ```bash
@@ -56,10 +50,33 @@ Via poetry:
56
50
  $ poetry add pangea-sdk
57
51
  ```
58
52
 
53
+ <a name="beta-releases"></a>
54
+
55
+ #### Beta releases
56
+
57
+ Pre-release versions may be available with the `b` (beta) denotation in the
58
+ version number. These releases serve to preview Beta and Early Access services
59
+ and APIs. Per Semantic Versioning, they are considered unstable and do not carry
60
+ the same compatibility guarantees as stable releases.
61
+ [Beta changelog](https://github.com/pangeacyber/pangea-python/blob/beta/CHANGELOG.md).
62
+
63
+ Via pip:
64
+
65
+ ```bash
66
+ $ pip3 install pangea-sdk==5.4.0b1
67
+ ```
68
+
69
+ Via poetry:
70
+
71
+ ```bash
72
+ $ poetry add pangea-sdk==5.4.0b1
73
+ ```
74
+
59
75
  ## Usage
60
76
 
61
77
  - [Documentation][]
62
- - [Examples][]
78
+ - [GA Examples][]
79
+ - [Beta Examples][]
63
80
 
64
81
  General usage would be to create a token for a service through the
65
82
  [Pangea Console][] and then construct an API client for that respective service.
@@ -118,16 +135,6 @@ if __name__ == "__main__":
118
135
  asyncio.run(main())
119
136
  ```
120
137
 
121
-
122
- <a name="beta-releases"></a>
123
-
124
- ## Beta releases
125
-
126
- Pre-release versions may be available with the `beta` denotation in the version
127
- number. These releases serve to preview beta services and APIs. Per Semantic
128
- Versioning, they are considered unstable and do not carry the same compatibility
129
- guarantees as stable releases.
130
-
131
138
  ## Secure Audit Log - Integrity Tools
132
139
 
133
140
  The Python Pangea SDK also includes some extra features to validate Audit Service log's integrity. Here we explain how to run them.
@@ -213,8 +220,9 @@ It accepts multiple file formats:
213
220
 
214
221
 
215
222
  [Documentation]: https://pangea.cloud/docs/sdk/python/
216
- [Examples]: https://github.com/pangeacyber/pangea-python/tree/main/examples
223
+ [GA Examples]: https://github.com/pangeacyber/pangea-python/tree/main/examples
224
+ [Beta Examples]: https://github.com/pangeacyber/pangea-python/tree/beta/examples
217
225
  [Pangea Console]: https://console.pangea.cloud/
218
- [Slack]: https://pangea.cloud/join-slack/
226
+ [Discourse]: https://l.pangea.cloud/Jd4wlGs
219
227
  [Secure Audit Log]: https://pangea.cloud/docs/audit
220
228
 
@@ -5,15 +5,17 @@
5
5
  <br />
6
6
 
7
7
  [![documentation](https://img.shields.io/badge/documentation-pangea-blue?style=for-the-badge&labelColor=551B76)][Documentation]
8
- [![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)][Slack]
8
+ [![Discourse](https://img.shields.io/badge/Discourse-4A154B?style=for-the-badge&logo=discourse&logoColor=white)][Discourse]
9
9
 
10
10
  # Pangea Python SDK
11
11
 
12
- A Python SDK for integrating with Pangea services. Supports Python v3.7 and
12
+ A Python SDK for integrating with Pangea services. Supports Python v3.9 and
13
13
  above.
14
14
 
15
15
  ## Installation
16
16
 
17
+ #### GA releases
18
+
17
19
  Via pip:
18
20
 
19
21
  ```bash
@@ -26,10 +28,33 @@ Via poetry:
26
28
  $ poetry add pangea-sdk
27
29
  ```
28
30
 
31
+ <a name="beta-releases"></a>
32
+
33
+ #### Beta releases
34
+
35
+ Pre-release versions may be available with the `b` (beta) denotation in the
36
+ version number. These releases serve to preview Beta and Early Access services
37
+ and APIs. Per Semantic Versioning, they are considered unstable and do not carry
38
+ the same compatibility guarantees as stable releases.
39
+ [Beta changelog](https://github.com/pangeacyber/pangea-python/blob/beta/CHANGELOG.md).
40
+
41
+ Via pip:
42
+
43
+ ```bash
44
+ $ pip3 install pangea-sdk==5.4.0b1
45
+ ```
46
+
47
+ Via poetry:
48
+
49
+ ```bash
50
+ $ poetry add pangea-sdk==5.4.0b1
51
+ ```
52
+
29
53
  ## Usage
30
54
 
31
55
  - [Documentation][]
32
- - [Examples][]
56
+ - [GA Examples][]
57
+ - [Beta Examples][]
33
58
 
34
59
  General usage would be to create a token for a service through the
35
60
  [Pangea Console][] and then construct an API client for that respective service.
@@ -88,16 +113,6 @@ if __name__ == "__main__":
88
113
  asyncio.run(main())
89
114
  ```
90
115
 
91
-
92
- <a name="beta-releases"></a>
93
-
94
- ## Beta releases
95
-
96
- Pre-release versions may be available with the `beta` denotation in the version
97
- number. These releases serve to preview beta services and APIs. Per Semantic
98
- Versioning, they are considered unstable and do not carry the same compatibility
99
- guarantees as stable releases.
100
-
101
116
  ## Secure Audit Log - Integrity Tools
102
117
 
103
118
  The Python Pangea SDK also includes some extra features to validate Audit Service log's integrity. Here we explain how to run them.
@@ -183,7 +198,8 @@ It accepts multiple file formats:
183
198
 
184
199
 
185
200
  [Documentation]: https://pangea.cloud/docs/sdk/python/
186
- [Examples]: https://github.com/pangeacyber/pangea-python/tree/main/examples
201
+ [GA Examples]: https://github.com/pangeacyber/pangea-python/tree/main/examples
202
+ [Beta Examples]: https://github.com/pangeacyber/pangea-python/tree/beta/examples
187
203
  [Pangea Console]: https://console.pangea.cloud/
188
- [Slack]: https://pangea.cloud/join-slack/
204
+ [Discourse]: https://l.pangea.cloud/Jd4wlGs
189
205
  [Secure Audit Log]: https://pangea.cloud/docs/audit
@@ -1,4 +1,4 @@
1
- __version__ = "3.8.0beta1"
1
+ __version__ = "5.4.0beta1"
2
2
 
3
3
  from pangea.asyncio.request import PangeaRequestAsync
4
4
  from pangea.config import PangeaConfig
@@ -28,7 +28,7 @@ class FileUploaderAsync:
28
28
  ) -> None:
29
29
  if transfer_method == TransferMethod.PUT_URL:
30
30
  files = [("file", ("filename", file, "application/octet-stream"))]
31
- await self._request.put_presigned_url(url=url, files=files) # type: ignore[arg-type]
31
+ await self._request.put_presigned_url(url=url, files=files)
32
32
  elif transfer_method == TransferMethod.POST_URL:
33
33
  files = [("file", ("filename", file, "application/octet-stream"))]
34
34
  await self._request.post_presigned_url(url=url, data=file_details, files=files) # type: ignore[arg-type]
@@ -1,20 +1,25 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+ from __future__ import annotations
3
4
 
4
5
  import asyncio
5
6
  import json
6
- import os
7
7
  import time
8
- from typing import Dict, List, Optional, Tuple, Type, Union
8
+ from typing import Dict, List, Optional, Sequence, Tuple, Type, Union, cast
9
9
 
10
10
  import aiohttp
11
11
  from aiohttp import FormData
12
+ from pydantic import BaseModel
13
+ from pydantic_core import to_jsonable_python
14
+ from typing_extensions import Any, TypeVar
12
15
 
13
16
  import pangea.exceptions as pe
14
17
  from pangea.request import MultipartResponse, PangeaRequestBase
15
18
  from pangea.response import AttachedFile, PangeaResponse, PangeaResponseResult, ResponseStatus, TransferMethod
16
19
  from pangea.utils import default_encoder
17
20
 
21
+ TResult = TypeVar("TResult", bound=PangeaResponseResult)
22
+
18
23
 
19
24
  class PangeaRequestAsync(PangeaRequestBase):
20
25
  """An object that makes direct calls to Pangea Service APIs.
@@ -28,12 +33,12 @@ class PangeaRequestAsync(PangeaRequestBase):
28
33
  async def post(
29
34
  self,
30
35
  endpoint: str,
31
- result_class: Type[PangeaResponseResult],
32
- data: Union[str, Dict] = {},
33
- files: List[Tuple] = [],
36
+ result_class: Type[TResult],
37
+ data: str | BaseModel | dict[str, Any] | None = None,
38
+ files: Optional[List[Tuple]] = None,
34
39
  poll_result: bool = True,
35
40
  url: Optional[str] = None,
36
- ) -> PangeaResponse:
41
+ ) -> PangeaResponse[TResult]:
37
42
  """Makes the POST call to a Pangea Service endpoint.
38
43
 
39
44
  Args:
@@ -44,17 +49,27 @@ class PangeaRequestAsync(PangeaRequestBase):
44
49
  PangeaResponse which contains the response in its entirety and
45
50
  various properties to retrieve individual fields
46
51
  """
52
+
53
+ if isinstance(data, BaseModel):
54
+ data = data.model_dump(exclude_none=True)
55
+
56
+ if data is None:
57
+ data = {}
58
+
59
+ # Normalize.
60
+ data = cast(dict[str, Any], to_jsonable_python(data))
61
+
47
62
  if url is None:
48
63
  url = self._url(endpoint)
49
64
 
50
65
  # Set config ID if available
51
- if self.config_id and data.get("config_id", None) is None: # type: ignore[union-attr]
52
- data["config_id"] = self.config_id # type: ignore[index]
66
+ if self.config_id and data.get("config_id", None) is None:
67
+ data["config_id"] = self.config_id
53
68
 
54
69
  self.logger.debug(
55
70
  json.dumps({"service": self.service, "action": "post", "url": url, "data": data}, default=default_encoder)
56
71
  )
57
- transfer_method = data.get("transfer_method", None) # type: ignore[union-attr]
72
+ transfer_method = data.get("transfer_method", None)
58
73
 
59
74
  if files and type(data) is dict and (transfer_method == TransferMethod.POST_URL.value):
60
75
  requests_response = await self._full_post_presigned_url(
@@ -91,9 +106,7 @@ class PangeaRequestAsync(PangeaRequestBase):
91
106
 
92
107
  return self._check_response(pangea_response)
93
108
 
94
- async def get(
95
- self, path: str, result_class: Type[PangeaResponseResult], check_response: bool = True
96
- ) -> PangeaResponse[Type[PangeaResponseResult]]:
109
+ async def get(self, path: str, result_class: Type[TResult], check_response: bool = True) -> PangeaResponse[TResult]:
97
110
  """Makes the GET call to a Pangea Service endpoint.
98
111
 
99
112
  Args:
@@ -110,7 +123,7 @@ class PangeaRequestAsync(PangeaRequestBase):
110
123
 
111
124
  async with self.session.get(url, headers=self._headers()) as requests_response:
112
125
  await self._check_http_errors(requests_response)
113
- pangea_response = PangeaResponse( # type: ignore[var-annotated]
126
+ pangea_response = PangeaResponse(
114
127
  requests_response, result_class=result_class, json=await requests_response.json()
115
128
  )
116
129
 
@@ -131,11 +144,11 @@ class PangeaRequestAsync(PangeaRequestBase):
131
144
  raise pe.ServiceTemporarilyUnavailable(await resp.json())
132
145
 
133
146
  async def poll_result_by_id(
134
- self, request_id: str, result_class: Union[Type[PangeaResponseResult], Type[dict]], check_response: bool = True
135
- ):
147
+ self, request_id: str, result_class: Type[TResult], check_response: bool = True
148
+ ) -> PangeaResponse[TResult]:
136
149
  path = self._get_poll_path(request_id)
137
150
  self.logger.debug(json.dumps({"service": self.service, "action": "poll_result_once", "url": path}))
138
- return await self.get(path, result_class, check_response=check_response) # type: ignore[arg-type]
151
+ return await self.get(path, result_class, check_response=check_response)
139
152
 
140
153
  async def poll_result_once(self, response: PangeaResponse, check_response: bool = True):
141
154
  request_id = response.request_id
@@ -160,7 +173,7 @@ class PangeaRequestAsync(PangeaRequestBase):
160
173
  if resp.status < 200 or resp.status >= 300:
161
174
  raise pe.PresignedUploadError(f"presigned POST failure: {resp.status}", await resp.text())
162
175
 
163
- async def put_presigned_url(self, url: str, files: List[Tuple]):
176
+ async def put_presigned_url(self, url: str, files: Sequence[Tuple]):
164
177
  # Send put request with file as body
165
178
  resp = await self._http_put(url=url, files=files)
166
179
  self.logger.debug(
@@ -173,7 +186,20 @@ class PangeaRequestAsync(PangeaRequestBase):
173
186
  if resp.status < 200 or resp.status >= 300:
174
187
  raise pe.PresignedUploadError(f"presigned PUT failure: {resp.status}", await resp.text())
175
188
 
176
- async def download_file(self, url: str, filename: Optional[str] = None) -> AttachedFile:
189
+ async def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
190
+ """
191
+ Download file
192
+
193
+ Download a file from the specified URL and save it with the given
194
+ filename.
195
+
196
+ Args:
197
+ url: URL of the file to download
198
+ filename: Name to save the downloaded file as. If not provided, the
199
+ filename will be determined from the Content-Disposition header or
200
+ the URL.
201
+ """
202
+
177
203
  self.logger.debug(
178
204
  json.dumps(
179
205
  {
@@ -209,16 +235,16 @@ class PangeaRequestAsync(PangeaRequestBase):
209
235
  )
210
236
 
211
237
  return AttachedFile(filename=filename, file=await response.read(), content_type=content_type)
212
- else:
213
- raise pe.DownloadFileError(f"Failed to download file. Status: {response.status}", await response.text())
238
+ raise pe.DownloadFileError(f"Failed to download file. Status: {response.status}", await response.text())
214
239
 
215
- async def _get_pangea_json(self, reader: aiohttp.MultipartReader) -> Optional[Dict]:
240
+ async def _get_pangea_json(self, reader: aiohttp.multipart.MultipartResponseWrapper) -> Optional[Dict[str, Any]]:
216
241
  # Iterate through parts
217
242
  async for part in reader:
218
- return await part.json()
243
+ if isinstance(part, aiohttp.BodyPartReader):
244
+ return await part.json()
219
245
  return None
220
246
 
221
- async def _get_attached_files(self, reader: aiohttp.MultipartReader) -> List[AttachedFile]:
247
+ async def _get_attached_files(self, reader: aiohttp.multipart.MultipartResponseWrapper) -> List[AttachedFile]:
222
248
  files = []
223
249
  i = 0
224
250
 
@@ -229,7 +255,7 @@ class PangeaRequestAsync(PangeaRequestBase):
229
255
  if name is None:
230
256
  name = f"default_file_name_{i}"
231
257
  i += 1
232
- files.append(AttachedFile(name, await part.read(), content_type))
258
+ files.append(AttachedFile(name, await part.read(), content_type)) # type: ignore[union-attr]
233
259
 
234
260
  return files
235
261
 
@@ -237,13 +263,12 @@ class PangeaRequestAsync(PangeaRequestBase):
237
263
  # Parse the multipart response
238
264
  multipart_reader = aiohttp.MultipartReader.from_response(resp)
239
265
 
240
- pangea_json = await self._get_pangea_json(multipart_reader) # type: ignore[arg-type]
266
+ pangea_json = await self._get_pangea_json(multipart_reader)
241
267
  self.logger.debug(
242
268
  json.dumps({"service": self.service, "action": "multipart response", "response": pangea_json})
243
269
  )
244
270
 
245
- multipart_reader = multipart_reader.__aiter__()
246
- attached_files = await self._get_attached_files(multipart_reader) # type: ignore[arg-type]
271
+ attached_files = await self._get_attached_files(multipart_reader)
247
272
  return MultipartResponse(pangea_json, attached_files) # type: ignore[arg-type]
248
273
 
249
274
  async def _http_post(
@@ -251,7 +276,7 @@ class PangeaRequestAsync(PangeaRequestBase):
251
276
  url: str,
252
277
  headers: Dict = {},
253
278
  data: Union[str, Dict] = {},
254
- files: List[Tuple] = [],
279
+ files: Optional[List[Tuple]] = [],
255
280
  presigned_url_post: bool = False,
256
281
  ) -> aiohttp.ClientResponse:
257
282
  if files:
@@ -276,7 +301,7 @@ class PangeaRequestAsync(PangeaRequestBase):
276
301
  async def _http_put(
277
302
  self,
278
303
  url: str,
279
- files: List[Tuple],
304
+ files: Sequence[Tuple],
280
305
  headers: Dict = {},
281
306
  ) -> aiohttp.ClientResponse:
282
307
  self.logger.debug(
@@ -328,9 +353,7 @@ class PangeaRequestAsync(PangeaRequestBase):
328
353
  # Receive 202
329
354
  return await self._poll_presigned_url(accepted_exception.response)
330
355
 
331
- async def _poll_presigned_url(
332
- self, response: PangeaResponse[Type[PangeaResponseResult]]
333
- ) -> PangeaResponse[Type[PangeaResponseResult]]:
356
+ async def _poll_presigned_url(self, response: PangeaResponse[TResult]) -> PangeaResponse[TResult]:
334
357
  if response.http_status != 202:
335
358
  raise AttributeError("Response should be 202")
336
359
 
@@ -373,8 +396,7 @@ class PangeaRequestAsync(PangeaRequestBase):
373
396
 
374
397
  if loop_resp.accepted_result is not None and not loop_resp.accepted_result.has_upload_url:
375
398
  return loop_resp
376
- else:
377
- raise loop_exc
399
+ raise loop_exc
378
400
 
379
401
  async def _handle_queued_result(self, response: PangeaResponse) -> PangeaResponse:
380
402
  if self._queued_retry_enabled and response.http_status == 202:
@@ -1,8 +1,12 @@
1
+ from .ai_guard import AIGuardAsync
1
2
  from .audit import AuditAsync
2
3
  from .authn import AuthNAsync
4
+ from .authz import AuthZAsync
3
5
  from .embargo import EmbargoAsync
4
6
  from .file_scan import FileScanAsync
5
7
  from .intel import DomainIntelAsync, FileIntelAsync, IpIntelAsync, UrlIntelAsync, UserIntelAsync
8
+ from .prompt_guard import PromptGuardAsync
6
9
  from .redact import RedactAsync
10
+ from .sanitize import SanitizeAsync
7
11
  from .share import ShareAsync
8
12
  from .vault import VaultAsync
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from pangea.asyncio.services.base import ServiceBaseAsync
4
+ from pangea.config import PangeaConfig
5
+ from pangea.response import PangeaResponse
6
+ from pangea.services.ai_guard import TextGuardResult
7
+
8
+
9
+ class AIGuardAsync(ServiceBaseAsync):
10
+ """AI Guard service client.
11
+
12
+ Provides methods to interact with Pangea's AI Guard service.
13
+
14
+ Examples:
15
+ from pangea import PangeaConfig
16
+ from pangea.asyncio.services import AIGuardAsync
17
+
18
+ config = PangeaConfig(domain="aws.us.pangea.cloud")
19
+ ai_guard = AIGuardAsync(token="pangea_token", config=config)
20
+ """
21
+
22
+ service_name = "ai-guard"
23
+
24
+ def __init__(
25
+ self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
26
+ ) -> None:
27
+ """
28
+ AI Guard service client.
29
+
30
+ Initializes a new AI Guard client.
31
+
32
+ Args:
33
+ token: Pangea API token.
34
+ config: Pangea service configuration.
35
+ logger_name: Logger name.
36
+ config_id: Configuration ID.
37
+
38
+ Examples:
39
+ from pangea import PangeaConfig
40
+ from pangea.asyncio.services import AIGuardAsync
41
+
42
+ config = PangeaConfig(domain="aws.us.pangea.cloud")
43
+ ai_guard = AIGuardAsync(token="pangea_token", config=config)
44
+ """
45
+
46
+ super().__init__(token, config, logger_name, config_id)
47
+
48
+ async def guard_text(
49
+ self,
50
+ text: str,
51
+ *,
52
+ recipe: str = "pangea_prompt_guard",
53
+ debug: bool = False,
54
+ ) -> PangeaResponse[TextGuardResult]:
55
+ """
56
+ Text guard (Beta)
57
+
58
+ Guard text.
59
+
60
+ How to install a [Beta release](https://pangea.cloud/docs/sdk/python/#beta-releases).
61
+
62
+ OperationId: ai_guard_post_v1beta_text_guard
63
+
64
+ Args:
65
+ text: Text.
66
+ recipe: Recipe.
67
+ debug: Debug.
68
+
69
+ Examples:
70
+ response = await ai_guard.guard_text("text")
71
+ """
72
+
73
+ return await self.request.post(
74
+ "v1beta/text/guard", TextGuardResult, data={"text": text, "recipe": recipe, "debug": debug}
75
+ )