pangea-sdk 6.1.1__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.
- pangea/__init__.py +9 -1
- pangea/asyncio/__init__.py +1 -0
- pangea/asyncio/file_uploader.py +4 -2
- pangea/asyncio/request.py +199 -35
- pangea/asyncio/services/__init__.py +3 -0
- pangea/asyncio/services/ai_guard.py +91 -2
- pangea/asyncio/services/audit.py +307 -2
- pangea/asyncio/services/authn.py +12 -2
- pangea/asyncio/services/base.py +4 -0
- pangea/asyncio/services/file_scan.py +7 -1
- pangea/asyncio/services/intel.py +6 -2
- pangea/asyncio/services/management.py +576 -0
- pangea/asyncio/services/prompt_guard.py +112 -2
- pangea/asyncio/services/redact.py +269 -4
- pangea/asyncio/services/sanitize.py +5 -1
- pangea/asyncio/services/share.py +5 -1
- pangea/asyncio/services/vault.py +4 -0
- pangea/audit_logger.py +3 -1
- pangea/deep_verify.py +13 -13
- pangea/deprecated.py +1 -1
- pangea/dump_audit.py +2 -3
- pangea/exceptions.py +8 -5
- pangea/file_uploader.py +4 -0
- pangea/request.py +205 -52
- pangea/response.py +15 -12
- pangea/services/__init__.py +3 -0
- pangea/services/ai_guard.py +497 -16
- pangea/services/audit/audit.py +310 -8
- pangea/services/audit/models.py +279 -0
- pangea/services/audit/signing.py +1 -1
- pangea/services/audit/util.py +10 -10
- pangea/services/authn/authn.py +12 -2
- pangea/services/authn/models.py +3 -0
- pangea/services/authz.py +4 -0
- pangea/services/base.py +5 -1
- pangea/services/embargo.py +6 -0
- pangea/services/file_scan.py +7 -1
- pangea/services/intel.py +4 -0
- pangea/services/management.py +720 -0
- pangea/services/prompt_guard.py +193 -2
- pangea/services/redact.py +477 -7
- pangea/services/sanitize.py +5 -1
- pangea/services/share/share.py +13 -7
- pangea/services/vault/models/asymmetric.py +4 -0
- pangea/services/vault/models/common.py +4 -0
- pangea/services/vault/models/symmetric.py +4 -0
- pangea/services/vault/vault.py +2 -4
- pangea/tools.py +13 -9
- pangea/utils.py +3 -5
- pangea/verify_audit.py +23 -27
- {pangea_sdk-6.1.1.dist-info → pangea_sdk-6.2.0b2.dist-info}/METADATA +4 -4
- pangea_sdk-6.2.0b2.dist-info/RECORD +62 -0
- pangea_sdk-6.1.1.dist-info/RECORD +0 -60
- {pangea_sdk-6.1.1.dist-info → pangea_sdk-6.2.0b2.dist-info}/WHEEL +0 -0
pangea/request.py
CHANGED
@@ -1,19 +1,24 @@
|
|
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
|
6
10
|
import json
|
7
11
|
import logging
|
8
12
|
import time
|
9
|
-
from
|
13
|
+
from collections.abc import Iterable, Mapping
|
14
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union, cast, overload
|
10
15
|
|
11
16
|
import requests
|
12
|
-
from pydantic import BaseModel
|
17
|
+
from pydantic import BaseModel, TypeAdapter
|
13
18
|
from pydantic_core import to_jsonable_python
|
14
19
|
from requests.adapters import HTTPAdapter, Retry
|
15
20
|
from requests_toolbelt import MultipartDecoder # type: ignore[import-untyped]
|
16
|
-
from typing_extensions import TypeVar
|
21
|
+
from typing_extensions import Literal, TypeAlias, TypeVar, override
|
17
22
|
from yarl import URL
|
18
23
|
|
19
24
|
import pangea
|
@@ -26,11 +31,24 @@ if TYPE_CHECKING:
|
|
26
31
|
import aiohttp
|
27
32
|
|
28
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
|
+
|
29
47
|
class MultipartResponse:
|
30
48
|
pangea_json: Dict[str, str]
|
31
49
|
attached_files: List = []
|
32
50
|
|
33
|
-
def __init__(self, pangea_json:
|
51
|
+
def __init__(self, pangea_json: dict[str, str], attached_files: list = []): # noqa: B006
|
34
52
|
self.pangea_json = pangea_json
|
35
53
|
self.attached_files = attached_files
|
36
54
|
|
@@ -115,6 +133,7 @@ class PangeaRequestBase:
|
|
115
133
|
headers = {
|
116
134
|
"User-Agent": self._user_agent,
|
117
135
|
"Authorization": f"Bearer {self.token}",
|
136
|
+
"Content-Type": "application/json",
|
118
137
|
}
|
119
138
|
|
120
139
|
# We want to ignore previous headers if user tried to set them, so we will overwrite them.
|
@@ -184,6 +203,9 @@ class PangeaRequestBase:
|
|
184
203
|
raise pe.AcceptedRequestException(response)
|
185
204
|
raise pe.PangeaAPIException(f"{summary} ", response)
|
186
205
|
|
206
|
+
def _init_session(self) -> requests.Session | aiohttp.ClientSession:
|
207
|
+
raise NotImplementedError
|
208
|
+
|
187
209
|
|
188
210
|
TResult = TypeVar("TResult", bound=PangeaResponseResult)
|
189
211
|
|
@@ -200,26 +222,88 @@ class PangeaRequest(PangeaRequestBase):
|
|
200
222
|
def __del__(self) -> None:
|
201
223
|
self.session.close()
|
202
224
|
|
225
|
+
def delete(self, endpoint: str) -> None:
|
226
|
+
"""
|
227
|
+
Makes a DELETE call to a Pangea endpoint.
|
228
|
+
|
229
|
+
Args:
|
230
|
+
endpoint: The Pangea API endpoint.
|
231
|
+
"""
|
232
|
+
|
233
|
+
url = self._url(endpoint)
|
234
|
+
|
235
|
+
self.logger.debug(
|
236
|
+
json.dumps({"service": self.service, "action": "delete", "url": url}, default=default_encoder)
|
237
|
+
)
|
238
|
+
|
239
|
+
requests_response = self._http_delete(url, headers=self._headers())
|
240
|
+
self._check_http_errors(requests_response)
|
241
|
+
|
242
|
+
@overload
|
203
243
|
def post(
|
204
244
|
self,
|
205
245
|
endpoint: str,
|
206
246
|
result_class: Type[TResult],
|
207
|
-
data: str | BaseModel |
|
208
|
-
files: Optional[
|
247
|
+
data: str | BaseModel | Mapping[str, Any] | None = None,
|
248
|
+
files: Optional[list[Tuple]] = None,
|
209
249
|
poll_result: bool = True,
|
210
250
|
url: Optional[str] = None,
|
251
|
+
*,
|
252
|
+
pangea_response: Literal[True] = True,
|
211
253
|
) -> PangeaResponse[TResult]:
|
212
|
-
"""
|
254
|
+
"""
|
255
|
+
Makes the POST call to a Pangea Service endpoint.
|
213
256
|
|
214
257
|
Args:
|
215
|
-
endpoint
|
216
|
-
data
|
258
|
+
endpoint: The Pangea Service API endpoint.
|
259
|
+
data: The POST body payload object
|
217
260
|
|
218
261
|
Returns:
|
219
262
|
PangeaResponse which contains the response in its entirety and
|
220
263
|
various properties to retrieve individual fields
|
221
264
|
"""
|
222
265
|
|
266
|
+
@overload
|
267
|
+
def post(
|
268
|
+
self,
|
269
|
+
endpoint: str,
|
270
|
+
result_class: Type[TResult],
|
271
|
+
data: str | BaseModel | Mapping[str, Any] | None = None,
|
272
|
+
files: Optional[list[Tuple]] = None,
|
273
|
+
poll_result: bool = True,
|
274
|
+
url: Optional[str] = None,
|
275
|
+
*,
|
276
|
+
pangea_response: Literal[False],
|
277
|
+
) -> TResult:
|
278
|
+
"""
|
279
|
+
Makes the POST call to a Pangea Service endpoint.
|
280
|
+
|
281
|
+
Args:
|
282
|
+
endpoint: The Pangea Service API endpoint.
|
283
|
+
data: The POST body payload object
|
284
|
+
"""
|
285
|
+
|
286
|
+
def post(
|
287
|
+
self,
|
288
|
+
endpoint: str,
|
289
|
+
result_class: Type[TResult],
|
290
|
+
data: str | BaseModel | Mapping[str, Any] | None = None,
|
291
|
+
files: Optional[list[Tuple]] = None,
|
292
|
+
poll_result: bool = True,
|
293
|
+
url: Optional[str] = None,
|
294
|
+
*,
|
295
|
+
pangea_response: bool = True,
|
296
|
+
) -> PangeaResponse[TResult] | TResult:
|
297
|
+
"""
|
298
|
+
Makes a POST call to a Pangea Service endpoint.
|
299
|
+
|
300
|
+
Args:
|
301
|
+
endpoint: The Pangea Service API endpoint.
|
302
|
+
data: The POST body payload object
|
303
|
+
pangea_response: Whether or not the response body follows Pangea's
|
304
|
+
standard response schema
|
305
|
+
"""
|
306
|
+
|
223
307
|
if isinstance(data, BaseModel):
|
224
308
|
data = data.model_dump(exclude_none=True)
|
225
309
|
|
@@ -250,15 +334,20 @@ class PangeaRequest(PangeaRequestBase):
|
|
250
334
|
endpoint, result_class=result_class, data=data, files=files
|
251
335
|
)
|
252
336
|
else:
|
253
|
-
|
254
|
-
|
255
|
-
|
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)
|
256
341
|
|
257
342
|
self._check_http_errors(requests_response)
|
258
343
|
|
344
|
+
if not pangea_response:
|
345
|
+
type_adapter = TypeAdapter(result_class)
|
346
|
+
return type_adapter.validate_python(requests_response.json())
|
347
|
+
|
259
348
|
if "multipart/form-data" in requests_response.headers.get("content-type", ""):
|
260
349
|
multipart_response = self._process_multipart_response(requests_response)
|
261
|
-
|
350
|
+
pangea_response_obj: PangeaResponse = PangeaResponse(
|
262
351
|
requests_response,
|
263
352
|
result_class=result_class,
|
264
353
|
json=multipart_response.pangea_json,
|
@@ -271,14 +360,14 @@ class PangeaRequest(PangeaRequestBase):
|
|
271
360
|
json.dumps({"service": self.service, "action": "post", "url": url, "response": json_resp})
|
272
361
|
)
|
273
362
|
|
274
|
-
|
363
|
+
pangea_response_obj = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
|
275
364
|
except requests.exceptions.JSONDecodeError as e:
|
276
|
-
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
|
277
366
|
|
278
367
|
if poll_result:
|
279
|
-
|
368
|
+
pangea_response_obj = self._handle_queued_result(pangea_response_obj)
|
280
369
|
|
281
|
-
return self._check_response(
|
370
|
+
return self._check_response(pangea_response_obj)
|
282
371
|
|
283
372
|
def _get_pangea_json(self, decoder: MultipartDecoder) -> Optional[Dict]:
|
284
373
|
# Iterate through parts
|
@@ -321,12 +410,20 @@ class PangeaRequest(PangeaRequestBase):
|
|
321
410
|
if resp.status_code == 503:
|
322
411
|
raise pe.ServiceTemporarilyUnavailable(resp.json())
|
323
412
|
|
413
|
+
def _http_delete(
|
414
|
+
self,
|
415
|
+
url: str,
|
416
|
+
*,
|
417
|
+
headers: Mapping[str, str | bytes | None] = {},
|
418
|
+
) -> requests.Response:
|
419
|
+
return self.session.delete(url, headers=headers)
|
420
|
+
|
324
421
|
def _http_post(
|
325
422
|
self,
|
326
423
|
url: str,
|
327
|
-
headers:
|
328
|
-
data:
|
329
|
-
files:
|
424
|
+
headers: Mapping[str, str] = {},
|
425
|
+
data: str | dict[Any, Any] = {}, # noqa: B006
|
426
|
+
files: _Files | None = None,
|
330
427
|
multipart_post: bool = True,
|
331
428
|
) -> requests.Response:
|
332
429
|
data_send, files = self._http_post_process(data=data, files=files, multipart_post=multipart_post)
|
@@ -334,31 +431,25 @@ class PangeaRequest(PangeaRequestBase):
|
|
334
431
|
|
335
432
|
def _http_post_process(
|
336
433
|
self,
|
337
|
-
data:
|
338
|
-
files:
|
434
|
+
data: str | dict[Any, Any] = {}, # noqa: B006
|
435
|
+
files: _Files | None = None,
|
339
436
|
multipart_post: bool = True,
|
340
|
-
):
|
437
|
+
) -> tuple[_Data | None, _Files | None]:
|
341
438
|
if files:
|
342
439
|
if multipart_post is True:
|
343
440
|
data_send: str = json.dumps(data, default=default_encoder) if isinstance(data, dict) else data
|
344
|
-
multi = [("request", (None, data_send, "application/json"))]
|
345
|
-
multi
|
346
|
-
|
347
|
-
|
348
|
-
#
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
files = { # type: ignore[assignment]
|
354
|
-
"file": files[0][1],
|
355
|
-
}
|
356
|
-
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
|
+
|
357
450
|
data_send = json.dumps(data, default=default_encoder) if isinstance(data, dict) else data
|
358
451
|
return data_send, None
|
359
452
|
|
360
|
-
return data, files
|
361
|
-
|
362
453
|
def _handle_queued_result(self, response: PangeaResponse[TResult]) -> PangeaResponse[TResult]:
|
363
454
|
if self._queued_retry_enabled and response.http_status == 202:
|
364
455
|
self.logger.debug(
|
@@ -371,37 +462,98 @@ class PangeaRequest(PangeaRequestBase):
|
|
371
462
|
|
372
463
|
return response
|
373
464
|
|
374
|
-
|
375
|
-
|
465
|
+
@overload
|
466
|
+
def get(
|
467
|
+
self,
|
468
|
+
path: str,
|
469
|
+
result_class: Type[TResult],
|
470
|
+
check_response: bool = True,
|
471
|
+
*,
|
472
|
+
params: (
|
473
|
+
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
|
474
|
+
| None
|
475
|
+
) = None,
|
476
|
+
pangea_response: Literal[True] = True,
|
477
|
+
) -> PangeaResponse[TResult]:
|
478
|
+
"""
|
479
|
+
Makes the GET call to a Pangea Service endpoint.
|
376
480
|
|
377
481
|
Args:
|
378
|
-
|
379
|
-
|
482
|
+
path: Additional URL path
|
483
|
+
params: Dictionary of querystring data to attach to the request
|
380
484
|
|
381
485
|
Returns:
|
382
486
|
PangeaResponse which contains the response in its entirety and
|
383
487
|
various properties to retrieve individual fields
|
384
488
|
"""
|
385
489
|
|
490
|
+
@overload
|
491
|
+
def get(
|
492
|
+
self,
|
493
|
+
path: str,
|
494
|
+
result_class: Type[TResult],
|
495
|
+
check_response: bool = True,
|
496
|
+
*,
|
497
|
+
params: (
|
498
|
+
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
|
499
|
+
| None
|
500
|
+
) = None,
|
501
|
+
pangea_response: Literal[False] = False,
|
502
|
+
) -> TResult:
|
503
|
+
"""
|
504
|
+
Makes the GET call to a Pangea Service endpoint.
|
505
|
+
|
506
|
+
Args:
|
507
|
+
path: Additional URL path
|
508
|
+
params: Dictionary of querystring data to attach to the request
|
509
|
+
"""
|
510
|
+
|
511
|
+
def get(
|
512
|
+
self,
|
513
|
+
path: str,
|
514
|
+
result_class: Type[TResult],
|
515
|
+
check_response: bool = True,
|
516
|
+
*,
|
517
|
+
params: (
|
518
|
+
Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
|
519
|
+
| None
|
520
|
+
) = None,
|
521
|
+
pangea_response: bool = True,
|
522
|
+
) -> PangeaResponse[TResult] | TResult:
|
523
|
+
"""
|
524
|
+
Makes the GET call to a Pangea Service endpoint.
|
525
|
+
|
526
|
+
Args:
|
527
|
+
path: Additional URL path
|
528
|
+
params: Dictionary of querystring data to attach to the request
|
529
|
+
pangea_response: Whether or not the response body follows Pangea's
|
530
|
+
standard response schema
|
531
|
+
"""
|
532
|
+
|
386
533
|
url = self._url(path)
|
387
534
|
self.logger.debug(json.dumps({"service": self.service, "action": "get", "url": url}))
|
388
|
-
requests_response = self.session.get(url, headers=self._headers())
|
535
|
+
requests_response = self.session.get(url, params=params, headers=self._headers())
|
389
536
|
self._check_http_errors(requests_response)
|
390
|
-
|
537
|
+
|
538
|
+
if not pangea_response:
|
539
|
+
type_adapter = TypeAdapter(result_class)
|
540
|
+
return type_adapter.validate_python(requests_response.json())
|
541
|
+
|
542
|
+
pangea_response_obj: PangeaResponse = PangeaResponse(
|
391
543
|
requests_response, result_class=result_class, json=requests_response.json()
|
392
544
|
)
|
393
545
|
|
394
546
|
self.logger.debug(
|
395
547
|
json.dumps(
|
396
|
-
{"service": self.service, "action": "get", "url": url, "response":
|
548
|
+
{"service": self.service, "action": "get", "url": url, "response": pangea_response_obj.json},
|
397
549
|
default=default_encoder,
|
398
550
|
)
|
399
551
|
)
|
400
552
|
|
401
553
|
if check_response is False:
|
402
|
-
return
|
554
|
+
return pangea_response_obj
|
403
555
|
|
404
|
-
return self._check_response(
|
556
|
+
return self._check_response(pangea_response_obj)
|
405
557
|
|
406
558
|
def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
|
407
559
|
"""
|
@@ -477,7 +629,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
477
629
|
self,
|
478
630
|
endpoint: str,
|
479
631
|
result_class: Type[PangeaResponseResult],
|
480
|
-
data: Union[str,
|
632
|
+
data: Union[str, Mapping[str, Any]] = {},
|
481
633
|
) -> PangeaResponse:
|
482
634
|
# Send request
|
483
635
|
try:
|
@@ -520,8 +672,8 @@ class PangeaRequest(PangeaRequestBase):
|
|
520
672
|
def _http_put(
|
521
673
|
self,
|
522
674
|
url: str,
|
523
|
-
files:
|
524
|
-
headers:
|
675
|
+
files: list[Tuple],
|
676
|
+
headers: Mapping[str, str] = {},
|
525
677
|
) -> requests.Response:
|
526
678
|
self.logger.debug(
|
527
679
|
json.dumps({"service": self.service, "action": "http_put", "url": url}, default=default_encoder)
|
@@ -533,7 +685,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
533
685
|
self,
|
534
686
|
endpoint: str,
|
535
687
|
result_class: Type[PangeaResponseResult],
|
536
|
-
data: Union[str,
|
688
|
+
data: Union[str, Mapping[str, Any]] = {},
|
537
689
|
files: Optional[List[Tuple]] = None,
|
538
690
|
):
|
539
691
|
if files is None or len(files) == 0:
|
@@ -603,7 +755,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
603
755
|
{"service": self.service, "action": "poll_presigned_url", "step": "exit", "cause": {str(e)}}
|
604
756
|
)
|
605
757
|
)
|
606
|
-
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
|
607
759
|
|
608
760
|
self.logger.debug(json.dumps({"service": self.service, "action": "poll_presigned_url", "step": "exit"}))
|
609
761
|
|
@@ -611,6 +763,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
611
763
|
return loop_resp
|
612
764
|
raise loop_exc
|
613
765
|
|
766
|
+
@override
|
614
767
|
def _init_session(self) -> requests.Session:
|
615
768
|
retry_config = Retry(
|
616
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
|
17
|
+
from typing_extensions import TypeVar
|
12
18
|
|
13
19
|
from pangea.utils import format_datetime
|
14
20
|
|
15
21
|
|
16
|
-
class AttachedFile
|
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:
|
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:
|
212
|
+
attached_files: list[AttachedFile] = [], # noqa: B006
|
210
213
|
):
|
211
|
-
super(
|
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
|
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
|
250
|
+
return self.raw_response.status_code
|
248
251
|
|
249
252
|
@property
|
250
253
|
def url(self) -> str:
|
pangea/services/__init__.py
CHANGED
@@ -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,6 +7,7 @@ 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
|
10
|
+
from .management import Management
|
8
11
|
from .prompt_guard import PromptGuard
|
9
12
|
from .redact import Redact
|
10
13
|
from .sanitize import Sanitize
|