Skip to content

Commit 5362c41

Browse files
committed
Add Management and Config APIs
1 parent 984514f commit 5362c41

File tree

14 files changed

+3225
-53
lines changed

14 files changed

+3225
-53
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- AI Guard: detector overrides.
1414
- AI Guard: topic detector.
1515
- AI Guard: `ignore_recipe` in detector overrides.
16+
- Management: new API client.
17+
- Redact: config APIs.
18+
- Secure Audit Log: config APIs.
1619

1720
### Changed
1821

packages/pangea-sdk/pangea/asyncio/request.py

Lines changed: 153 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
import asyncio
66
import json
77
import time
8-
from typing import Dict, List, Optional, Sequence, Tuple, Type, Union, cast
8+
from collections.abc import Iterable, Mapping
9+
from typing import Dict, List, Literal, Optional, Sequence, Tuple, Type, Union, cast, overload
910

1011
import aiohttp
1112
from aiohttp import FormData
12-
from pydantic import BaseModel
13+
from pydantic import BaseModel, TypeAdapter
1314
from pydantic_core import to_jsonable_python
1415
from typing_extensions import Any, TypeVar
1516

@@ -30,6 +31,24 @@ class PangeaRequestAsync(PangeaRequestBase):
3031
be set in PangeaConfig.
3132
"""
3233

34+
async def delete(self, endpoint: str) -> None:
35+
"""
36+
Makes a DELETE call to a Pangea endpoint.
37+
38+
Args:
39+
endpoint: The Pangea API endpoint.
40+
"""
41+
42+
url = self._url(endpoint)
43+
44+
self.logger.debug(
45+
json.dumps({"service": self.service, "action": "delete", "url": url}, default=default_encoder)
46+
)
47+
48+
requests_response = await self._http_delete(url, headers=self._headers())
49+
await self._check_http_errors(requests_response)
50+
51+
@overload
3352
async def post(
3453
self,
3554
endpoint: str,
@@ -38,18 +57,60 @@ async def post(
3857
files: Optional[List[Tuple]] = None,
3958
poll_result: bool = True,
4059
url: Optional[str] = None,
60+
*,
61+
pangea_response: Literal[True] = True,
4162
) -> PangeaResponse[TResult]:
42-
"""Makes the POST call to a Pangea Service endpoint.
63+
"""
64+
Makes a POST call to a Pangea Service endpoint.
4365
4466
Args:
45-
endpoint(str): The Pangea Service API endpoint.
46-
data(dict): The POST body payload object
67+
endpoint: The Pangea Service API endpoint.
68+
data: The POST body payload object
4769
4870
Returns:
4971
PangeaResponse which contains the response in its entirety and
5072
various properties to retrieve individual fields
5173
"""
5274

75+
@overload
76+
async def post(
77+
self,
78+
endpoint: str,
79+
result_class: Type[TResult],
80+
data: str | BaseModel | dict[str, Any] | None = None,
81+
files: Optional[List[Tuple]] = None,
82+
poll_result: bool = True,
83+
url: Optional[str] = None,
84+
*,
85+
pangea_response: Literal[False],
86+
) -> TResult:
87+
"""
88+
Makes a POST call to a Pangea Service endpoint.
89+
90+
Args:
91+
endpoint: The Pangea Service API endpoint.
92+
data: The POST body payload object
93+
"""
94+
95+
async def post(
96+
self,
97+
endpoint: str,
98+
result_class: Type[TResult],
99+
data: str | BaseModel | dict[str, Any] | None = None,
100+
files: Optional[List[Tuple]] = None,
101+
poll_result: bool = True,
102+
url: Optional[str] = None,
103+
*,
104+
pangea_response: bool = True,
105+
) -> PangeaResponse[TResult] | 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+
53114
if isinstance(data, BaseModel):
54115
data = data.model_dump(exclude_none=True)
55116

@@ -86,9 +147,13 @@ async def post(
86147

87148
await self._check_http_errors(requests_response)
88149

150+
if not pangea_response:
151+
type_adapter = TypeAdapter(result_class)
152+
return type_adapter.validate_python(await requests_response.json())
153+
89154
if "multipart/form-data" in requests_response.headers.get("content-type", ""):
90155
multipart_response = await self._process_multipart_response(requests_response)
91-
pangea_response: PangeaResponse = PangeaResponse(
156+
pangea_response_obj: PangeaResponse = PangeaResponse(
92157
requests_response,
93158
result_class=result_class,
94159
json=multipart_response.pangea_json,
@@ -101,47 +166,108 @@ async def post(
101166
json.dumps({"service": self.service, "action": "post", "url": url, "response": json_resp})
102167
)
103168

104-
pangea_response = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
169+
pangea_response_obj = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
105170
except aiohttp.ContentTypeError as e:
106171
raise pe.PangeaException(f"Failed to decode json response. {e}. Body: {await requests_response.text()}")
107172

108173
if poll_result:
109-
pangea_response = await self._handle_queued_result(pangea_response)
174+
pangea_response_obj = await self._handle_queued_result(pangea_response_obj)
110175

111-
return self._check_response(pangea_response)
176+
return self._check_response(pangea_response_obj)
112177

113-
async def get(self, path: str, result_class: Type[TResult], check_response: bool = True) -> PangeaResponse[TResult]:
114-
"""Makes the GET call to a Pangea Service endpoint.
178+
@overload
179+
async def get(
180+
self,
181+
path: str,
182+
result_class: Type[TResult],
183+
check_response: bool = True,
184+
*,
185+
params: (
186+
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
187+
| None
188+
) = None,
189+
pangea_response: Literal[True] = True,
190+
) -> PangeaResponse[TResult]:
191+
"""
192+
Makes the GET call to a Pangea Service endpoint.
115193
116194
Args:
117-
endpoint(str): The Pangea Service API endpoint.
118-
path(str): Additional URL path
195+
path: Additional URL path
196+
params: Dictionary of querystring data to attach to the request
119197
120198
Returns:
121199
PangeaResponse which contains the response in its entirety and
122200
various properties to retrieve individual fields
123201
"""
124202

203+
@overload
204+
async def get(
205+
self,
206+
path: str,
207+
result_class: Type[TResult],
208+
check_response: bool = True,
209+
*,
210+
params: (
211+
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
212+
| None
213+
) = None,
214+
pangea_response: Literal[False] = False,
215+
) -> TResult:
216+
"""
217+
Makes the GET call to a Pangea Service endpoint.
218+
219+
Args:
220+
path: Additional URL path
221+
params: Dictionary of querystring data to attach to the request
222+
"""
223+
224+
async def get(
225+
self,
226+
path: str,
227+
result_class: Type[TResult],
228+
check_response: bool = True,
229+
*,
230+
params: (
231+
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
232+
| None
233+
) = None,
234+
pangea_response: bool = True,
235+
) -> PangeaResponse[TResult] | TResult:
236+
"""
237+
Makes the GET call to a Pangea Service endpoint.
238+
239+
Args:
240+
path: Additional URL path
241+
params: Dictionary of querystring data to attach to the request
242+
pangea_response: Whether or not the response body follows Pangea's
243+
standard response schema
244+
"""
245+
125246
url = self._url(path)
126247
self.logger.debug(json.dumps({"service": self.service, "action": "get", "url": url}))
127248

128-
async with self.session.get(url, headers=self._headers()) as requests_response:
249+
async with self.session.get(url, params=params, headers=self._headers()) as requests_response:
129250
await self._check_http_errors(requests_response)
130-
pangea_response = PangeaResponse(
251+
252+
if not pangea_response:
253+
type_adapter = TypeAdapter(result_class)
254+
return type_adapter.validate_python(await requests_response.json())
255+
256+
pangea_response_obj = PangeaResponse(
131257
requests_response, result_class=result_class, json=await requests_response.json()
132258
)
133259

134260
self.logger.debug(
135261
json.dumps(
136-
{"service": self.service, "action": "get", "url": url, "response": pangea_response.json},
262+
{"service": self.service, "action": "get", "url": url, "response": pangea_response_obj.json},
137263
default=default_encoder,
138264
)
139265
)
140266

141267
if check_response is False:
142-
return pangea_response
268+
return pangea_response_obj
143269

144-
return self._check_response(pangea_response)
270+
return self._check_response(pangea_response_obj)
145271

146272
async def _check_http_errors(self, resp: aiohttp.ClientResponse):
147273
if resp.status == 503:
@@ -275,10 +401,18 @@ async def _process_multipart_response(self, resp: aiohttp.ClientResponse) -> Mul
275401
attached_files = await self._get_attached_files(multipart_reader)
276402
return MultipartResponse(pangea_json, attached_files) # type: ignore[arg-type]
277403

404+
async def _http_delete(
405+
self,
406+
url: str,
407+
*,
408+
headers: Mapping[str, str | bytes | None] = {},
409+
) -> aiohttp.ClientResponse:
410+
return await self.session.delete(url, headers=headers)
411+
278412
async def _http_post(
279413
self,
280414
url: str,
281-
headers: Dict = {},
415+
headers: Mapping[str, str | bytes | None] = {},
282416
data: Union[str, Dict] = {},
283417
files: Optional[List[Tuple]] = [],
284418
presigned_url_post: bool = False,

packages/pangea-sdk/pangea/asyncio/services/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .embargo import EmbargoAsync
66
from .file_scan import FileScanAsync
77
from .intel import DomainIntelAsync, FileIntelAsync, IpIntelAsync, UrlIntelAsync, UserIntelAsync
8+
from .management import ManagementAsync
89
from .prompt_guard import PromptGuardAsync
910
from .redact import RedactAsync
1011
from .sanitize import SanitizeAsync

0 commit comments

Comments
 (0)