pangea-sdk 6.0.0__py3-none-any.whl → 6.2.0b1__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.
@@ -2,12 +2,15 @@
2
2
  # Author: Pangea Cyber Corporation
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Dict, List, Optional, Union
5
+ from collections.abc import Mapping, Sequence
6
+ from typing import Dict, List, Literal, Optional, Union, cast, overload
7
+
8
+ from pydantic import TypeAdapter
6
9
 
7
10
  import pangea.services.redact as m
8
11
  from pangea.asyncio.services.base import ServiceBaseAsync
9
12
  from pangea.config import PangeaConfig
10
- from pangea.response import PangeaResponse
13
+ from pangea.response import PangeaResponse, PangeaResponseResult
11
14
 
12
15
 
13
16
  class RedactAsync(ServiceBaseAsync):
@@ -64,7 +67,7 @@ class RedactAsync(ServiceBaseAsync):
64
67
  rules: Optional[List[str]] = None,
65
68
  rulesets: Optional[List[str]] = None,
66
69
  return_result: Optional[bool] = None,
67
- redaction_method_overrides: Optional[m.RedactionMethodOverrides] = None,
70
+ redaction_method_overrides: Mapping[str, m.RedactionMethodOverrides] | None = None,
68
71
  llm_request: Optional[bool] = None,
69
72
  vault_parameters: Optional[m.VaultParameters] = None,
70
73
  ) -> PangeaResponse[m.RedactResult]:
@@ -119,7 +122,7 @@ class RedactAsync(ServiceBaseAsync):
119
122
  rules: Optional[List[str]] = None,
120
123
  rulesets: Optional[List[str]] = None,
121
124
  return_result: Optional[bool] = None,
122
- redaction_method_overrides: Optional[m.RedactionMethodOverrides] = None,
125
+ redaction_method_overrides: Mapping[str, m.RedactionMethodOverrides] | None = None,
123
126
  llm_request: bool | None = None,
124
127
  vault_parameters: m.VaultParameters | None = None,
125
128
  ) -> PangeaResponse[m.StructuredResult]:
@@ -200,3 +203,261 @@ class RedactAsync(ServiceBaseAsync):
200
203
  """
201
204
  input = m.UnredactRequest(redacted_data=redacted_data, fpe_context=fpe_context)
202
205
  return await self.request.post("v1/unredact", m.UnredactResult, data=input.model_dump(exclude_none=True))
206
+
207
+ async def get_service_config(self, config_id: str) -> PangeaResponse[m.ServiceConfigResult]:
208
+ """
209
+ Get a service config.
210
+
211
+
212
+ OperationId: redact_post_v1beta_config
213
+ """
214
+ response = await self.request.post("v1beta/config", PangeaResponseResult, data={"id": config_id})
215
+ response.result = TypeAdapter(m.ServiceConfigResult).validate_python(response.json["result"])
216
+ return cast(PangeaResponse[m.ServiceConfigResult], response)
217
+
218
+ @overload
219
+ async def create_service_config(
220
+ self,
221
+ name: str,
222
+ *,
223
+ version: Literal["1.0.0"],
224
+ enabled_rules: Sequence[str] | None = None,
225
+ redactions: Mapping[str, m.Redaction] | None = None,
226
+ vault_service_config_id: str | None = None,
227
+ salt_vault_secret_id: str | None = None,
228
+ rules: Mapping[str, m.RuleV1] | None = None,
229
+ rulesets: Mapping[str, m.RulesetV1] | None = None,
230
+ supported_languages: Sequence[Literal["en"]] | None = None,
231
+ ) -> PangeaResponse[m.ServiceConfigResult]:
232
+ """
233
+ Create a v1.0.0 service config.
234
+
235
+ OperationId: redact_post_v1beta_config_create
236
+
237
+ Args:
238
+ vault_service_config_id: Service config used to create the secret
239
+ salt_vault_secret_id: Pangea only allows hashing to be done using a salt value to prevent brute-force attacks.
240
+ """
241
+
242
+ @overload
243
+ async def create_service_config(
244
+ self,
245
+ name: str,
246
+ *,
247
+ version: Literal["2.0.0"] | None = None,
248
+ enabled_rules: Sequence[str] | None = None,
249
+ enforce_enabled_rules: bool | None = None,
250
+ redactions: Mapping[str, m.Redaction] | None = None,
251
+ vault_service_config_id: str | None = None,
252
+ salt_vault_secret_id: str | None = None,
253
+ fpe_vault_secret_id: str | None = None,
254
+ rules: Mapping[str, m.RuleV2] | None = None,
255
+ rulesets: Mapping[str, m.RulesetV2] | None = None,
256
+ supported_languages: Sequence[Literal["en"]] | None = None,
257
+ ) -> PangeaResponse[m.ServiceConfigResult]:
258
+ """
259
+ Create a v2.0.0 service config.
260
+
261
+ OperationId: redact_post_v1beta_config_create
262
+
263
+ Args:
264
+ enforce_enabled_rules: Always run service config enabled rules across all redact calls regardless of flags?
265
+ vault_service_config_id: Service config used to create the secret
266
+ salt_vault_secret_id: Pangea only allows hashing to be done using a salt value to prevent brute-force attacks.
267
+ fpe_vault_secret_id: The ID of the key used by FF3 Encryption algorithms for FPE.
268
+ """
269
+
270
+ async def create_service_config(
271
+ self,
272
+ name: str,
273
+ *,
274
+ version: Literal["1.0.0", "2.0.0"] | None = None,
275
+ enabled_rules: Sequence[str] | None = None,
276
+ enforce_enabled_rules: bool | None = None,
277
+ fpe_vault_secret_id: str | None = None,
278
+ redactions: Mapping[str, m.Redaction] | None = None,
279
+ rules: Mapping[str, m.RuleV1 | m.RuleV2] | None = None,
280
+ rulesets: Mapping[str, m.RulesetV1 | m.RulesetV2] | None = None,
281
+ salt_vault_secret_id: str | None = None,
282
+ supported_languages: Sequence[Literal["en"]] | None = None,
283
+ vault_service_config_id: str | None = None,
284
+ ) -> PangeaResponse[m.ServiceConfigResult]:
285
+ """
286
+ Create a service config.
287
+
288
+ OperationId: redact_post_v1beta_config_create
289
+
290
+ Args:
291
+ enforce_enabled_rules: Always run service config enabled rules across all redact calls regardless of flags?
292
+ fpe_vault_secret_id: The ID of the key used by FF3 Encryption algorithms for FPE.
293
+ salt_vault_secret_id: Pangea only allows hashing to be done using a salt value to prevent brute-force attacks.
294
+ vault_service_config_id: Service config used to create the secret
295
+ """
296
+
297
+ response = await self.request.post(
298
+ "v1beta/config/create",
299
+ PangeaResponseResult,
300
+ data={
301
+ "name": name,
302
+ "version": version,
303
+ "enabled_rules": enabled_rules,
304
+ "enforce_enabled_rules": enforce_enabled_rules,
305
+ "fpe_vault_secret_id": fpe_vault_secret_id,
306
+ "redactions": redactions,
307
+ "rules": rules,
308
+ "rulesets": rulesets,
309
+ "salt_vault_secret_id": salt_vault_secret_id,
310
+ "supported_languages": supported_languages,
311
+ "vault_service_config_id": vault_service_config_id,
312
+ },
313
+ )
314
+ response.result = TypeAdapter(m.ServiceConfigResult).validate_python(response.json["result"])
315
+ return cast(PangeaResponse[m.ServiceConfigResult], response)
316
+
317
+ @overload
318
+ async def update_service_config(
319
+ self,
320
+ config_id: str,
321
+ *,
322
+ version: Literal["1.0.0"],
323
+ name: str,
324
+ updated_at: str,
325
+ enabled_rules: Sequence[str] | None = None,
326
+ redactions: Mapping[str, m.Redaction] | None = None,
327
+ vault_service_config_id: str | None = None,
328
+ salt_vault_secret_id: str | None = None,
329
+ rules: Mapping[str, m.RuleV1] | None = None,
330
+ rulesets: Mapping[str, m.RulesetV1] | None = None,
331
+ supported_languages: Sequence[Literal["en"]] | None = None,
332
+ ) -> PangeaResponse[m.ServiceConfigResult]:
333
+ """
334
+ Update a v1.0.0 service config.
335
+
336
+ OperationId: redact_post_v1beta_config_update
337
+
338
+ Args:
339
+ vault_service_config_id: Service config used to create the secret
340
+ salt_vault_secret_id: Pangea only allows hashing to be done using a salt value to prevent brute-force attacks.
341
+ """
342
+
343
+ @overload
344
+ async def update_service_config(
345
+ self,
346
+ config_id: str,
347
+ *,
348
+ version: Literal["2.0.0"] | None = None,
349
+ name: str,
350
+ updated_at: str,
351
+ enabled_rules: Sequence[str] | None = None,
352
+ enforce_enabled_rules: bool | None = None,
353
+ redactions: Mapping[str, m.Redaction] | None = None,
354
+ vault_service_config_id: str | None = None,
355
+ salt_vault_secret_id: str | None = None,
356
+ fpe_vault_secret_id: str | None = None,
357
+ rules: Mapping[str, m.RuleV2] | None = None,
358
+ rulesets: Mapping[str, m.RulesetV2] | None = None,
359
+ supported_languages: Sequence[Literal["en"]] | None = None,
360
+ ) -> PangeaResponse[m.ServiceConfigResult]:
361
+ """
362
+ Update a v2.0.0 service config.
363
+
364
+ OperationId: redact_post_v1beta_config_update
365
+
366
+ Args:
367
+ enforce_enabled_rules: Always run service config enabled rules across all redact calls regardless of flags?
368
+ vault_service_config_id: Service config used to create the secret
369
+ salt_vault_secret_id: Pangea only allows hashing to be done using a salt value to prevent brute-force attacks.
370
+ fpe_vault_secret_id: The ID of the key used by FF3 Encryption algorithms for FPE.
371
+ """
372
+
373
+ async def update_service_config(
374
+ self,
375
+ config_id: str,
376
+ *,
377
+ version: Literal["1.0.0", "2.0.0"] | None = None,
378
+ name: str,
379
+ updated_at: str,
380
+ enabled_rules: Sequence[str] | None = None,
381
+ enforce_enabled_rules: bool | None = None,
382
+ fpe_vault_secret_id: str | None = None,
383
+ redactions: Mapping[str, m.Redaction] | None = None,
384
+ rules: Mapping[str, m.RuleV1 | m.RuleV2] | None = None,
385
+ rulesets: Mapping[str, m.RulesetV1 | m.RulesetV2] | None = None,
386
+ salt_vault_secret_id: str | None = None,
387
+ supported_languages: Sequence[Literal["en"]] | None = None,
388
+ vault_service_config_id: str | None = None,
389
+ ) -> PangeaResponse[m.ServiceConfigResult]:
390
+ """
391
+ Update a service config.
392
+
393
+ OperationId: redact_post_v1beta_config_update
394
+
395
+ Args:
396
+ enforce_enabled_rules: Always run service config enabled rules across all redact calls regardless of flags?
397
+ fpe_vault_secret_id: The ID of the key used by FF3 Encryption algorithms for FPE.
398
+ salt_vault_secret_id: Pangea only allows hashing to be done using a salt value to prevent brute-force attacks.
399
+ vault_service_config_id: Service config used to create the secret
400
+ """
401
+
402
+ response = await self.request.post(
403
+ "v1beta/config/update",
404
+ PangeaResponseResult,
405
+ data={
406
+ "id": config_id,
407
+ "updated_at": updated_at,
408
+ "name": name,
409
+ "version": version,
410
+ "enabled_rules": enabled_rules,
411
+ "enforce_enabled_rules": enforce_enabled_rules,
412
+ "fpe_vault_secret_id": fpe_vault_secret_id,
413
+ "redactions": redactions,
414
+ "rules": rules,
415
+ "rulesets": rulesets,
416
+ "salt_vault_secret_id": salt_vault_secret_id,
417
+ "supported_languages": supported_languages,
418
+ "vault_service_config_id": vault_service_config_id,
419
+ },
420
+ )
421
+ response.result = TypeAdapter(m.ServiceConfigResult).validate_python(response.json["result"])
422
+ return cast(PangeaResponse[m.ServiceConfigResult], response)
423
+
424
+ async def delete_service_config(self, config_id: str) -> PangeaResponse[m.ServiceConfigResult]:
425
+ """
426
+ Delete a service config.
427
+
428
+ OperationId: redact_post_v1beta_config_delete
429
+
430
+ Args:
431
+ config_id: An ID for a service config
432
+ """
433
+
434
+ response = await self.request.post("v1beta/config/delete", PangeaResponseResult, data={"id": config_id})
435
+ response.result = TypeAdapter(m.ServiceConfigResult).validate_python(response.json["result"])
436
+ return cast(PangeaResponse[m.ServiceConfigResult], response)
437
+
438
+ async def list_service_configs(
439
+ self,
440
+ *,
441
+ filter: m.ServiceConfigFilter | None = None,
442
+ last: str | None = None,
443
+ order: Literal["asc", "desc"] | None = None,
444
+ order_by: Literal["id", "created_at", "updated_at"] | None = None,
445
+ size: int | None = None,
446
+ ) -> PangeaResponse[m.ServiceConfigListResult]:
447
+ """
448
+ List service configs.
449
+
450
+ OperationId: redact_post_v1beta_config_list
451
+
452
+ Args:
453
+ last: Reflected value from a previous response to obtain the next page of results.
454
+ order: Order results asc(ending) or desc(ending).
455
+ order_by: Which field to order results by.
456
+ size: Maximum results to include in the response.
457
+ """
458
+
459
+ return await self.request.post(
460
+ "v1beta/config/list",
461
+ m.ServiceConfigListResult,
462
+ data={"filter": filter, "last": last, "order": order, "order_by": order_by, "size": size},
463
+ )
pangea/request.py CHANGED
@@ -6,10 +6,11 @@ import copy
6
6
  import json
7
7
  import logging
8
8
  import time
9
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple, Type, Union, cast
9
+ from collections.abc import Iterable, Mapping
10
+ from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Sequence, Tuple, Type, Union, cast, overload
10
11
 
11
12
  import requests
12
- from pydantic import BaseModel
13
+ from pydantic import BaseModel, TypeAdapter
13
14
  from pydantic_core import to_jsonable_python
14
15
  from requests.adapters import HTTPAdapter, Retry
15
16
  from requests_toolbelt import MultipartDecoder # type: ignore[import-untyped]
@@ -200,6 +201,24 @@ class PangeaRequest(PangeaRequestBase):
200
201
  def __del__(self) -> None:
201
202
  self.session.close()
202
203
 
204
+ def delete(self, endpoint: str) -> None:
205
+ """
206
+ Makes a DELETE call to a Pangea endpoint.
207
+
208
+ Args:
209
+ endpoint: The Pangea API endpoint.
210
+ """
211
+
212
+ url = self._url(endpoint)
213
+
214
+ self.logger.debug(
215
+ json.dumps({"service": self.service, "action": "delete", "url": url}, default=default_encoder)
216
+ )
217
+
218
+ requests_response = self._http_delete(url, headers=self._headers())
219
+ self._check_http_errors(requests_response)
220
+
221
+ @overload
203
222
  def post(
204
223
  self,
205
224
  endpoint: str,
@@ -208,18 +227,62 @@ class PangeaRequest(PangeaRequestBase):
208
227
  files: Optional[List[Tuple]] = None,
209
228
  poll_result: bool = True,
210
229
  url: Optional[str] = None,
230
+ *,
231
+ pangea_response: Literal[True] = True,
211
232
  ) -> PangeaResponse[TResult]:
212
- """Makes the POST call to a Pangea Service endpoint.
233
+ """
234
+ Makes the POST call to a Pangea Service endpoint.
213
235
 
214
236
  Args:
215
- endpoint(str): The Pangea Service API endpoint.
216
- data(dict): The POST body payload object
237
+ endpoint: The Pangea Service API endpoint.
238
+ data: The POST body payload object
217
239
 
218
240
  Returns:
219
241
  PangeaResponse which contains the response in its entirety and
220
242
  various properties to retrieve individual fields
221
243
  """
222
244
 
245
+ @overload
246
+ def post(
247
+ self,
248
+ endpoint: str,
249
+ result_class: Type[TResult],
250
+ data: str | BaseModel | dict[str, Any] | None = None,
251
+ files: Optional[List[Tuple]] = None,
252
+ poll_result: bool = True,
253
+ url: Optional[str] = None,
254
+ *,
255
+ pangea_response: Literal[False],
256
+ ) -> TResult:
257
+ """
258
+ Makes the POST call to a Pangea Service endpoint.
259
+
260
+ Args:
261
+ endpoint: The Pangea Service API endpoint.
262
+ data: The POST body payload object
263
+ """
264
+
265
+ def post(
266
+ self,
267
+ endpoint: str,
268
+ result_class: Type[TResult],
269
+ data: str | BaseModel | dict[str, Any] | None = None,
270
+ files: Optional[List[Tuple]] = None,
271
+ poll_result: bool = True,
272
+ url: Optional[str] = None,
273
+ *,
274
+ pangea_response: bool = True,
275
+ ) -> PangeaResponse[TResult] | TResult:
276
+ """
277
+ Makes the POST call to a Pangea Service endpoint.
278
+
279
+ Args:
280
+ endpoint: The Pangea Service API endpoint.
281
+ data: The POST body payload object
282
+ pangea_response: Whether or not the response body follows Pangea's
283
+ standard response schema
284
+ """
285
+
223
286
  if isinstance(data, BaseModel):
224
287
  data = data.model_dump(exclude_none=True)
225
288
 
@@ -256,9 +319,13 @@ class PangeaRequest(PangeaRequestBase):
256
319
 
257
320
  self._check_http_errors(requests_response)
258
321
 
322
+ if not pangea_response:
323
+ type_adapter = TypeAdapter(result_class)
324
+ return type_adapter.validate_python(requests_response.json())
325
+
259
326
  if "multipart/form-data" in requests_response.headers.get("content-type", ""):
260
327
  multipart_response = self._process_multipart_response(requests_response)
261
- pangea_response: PangeaResponse = PangeaResponse(
328
+ pangea_response_obj: PangeaResponse = PangeaResponse(
262
329
  requests_response,
263
330
  result_class=result_class,
264
331
  json=multipart_response.pangea_json,
@@ -271,14 +338,14 @@ class PangeaRequest(PangeaRequestBase):
271
338
  json.dumps({"service": self.service, "action": "post", "url": url, "response": json_resp})
272
339
  )
273
340
 
274
- pangea_response = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
341
+ pangea_response_obj = PangeaResponse(requests_response, result_class=result_class, json=json_resp)
275
342
  except requests.exceptions.JSONDecodeError as e:
276
343
  raise pe.PangeaException(f"Failed to decode json response. {e}. Body: {requests_response.text}")
277
344
 
278
345
  if poll_result:
279
- pangea_response = self._handle_queued_result(pangea_response)
346
+ pangea_response_obj = self._handle_queued_result(pangea_response_obj)
280
347
 
281
- return self._check_response(pangea_response)
348
+ return self._check_response(pangea_response_obj)
282
349
 
283
350
  def _get_pangea_json(self, decoder: MultipartDecoder) -> Optional[Dict]:
284
351
  # Iterate through parts
@@ -321,10 +388,18 @@ class PangeaRequest(PangeaRequestBase):
321
388
  if resp.status_code == 503:
322
389
  raise pe.ServiceTemporarilyUnavailable(resp.json())
323
390
 
391
+ def _http_delete(
392
+ self,
393
+ url: str,
394
+ *,
395
+ headers: Mapping[str, str | bytes | None] = {},
396
+ ) -> requests.Response:
397
+ return self.session.delete(url, headers=headers)
398
+
324
399
  def _http_post(
325
400
  self,
326
401
  url: str,
327
- headers: Dict = {},
402
+ headers: Mapping[str, str | bytes | None] = {},
328
403
  data: Union[str, Dict] = {},
329
404
  files: Optional[List[Tuple]] = None,
330
405
  multipart_post: bool = True,
@@ -371,37 +446,98 @@ class PangeaRequest(PangeaRequestBase):
371
446
 
372
447
  return response
373
448
 
374
- def get(self, path: str, result_class: Type[TResult], check_response: bool = True) -> PangeaResponse[TResult]:
375
- """Makes the GET call to a Pangea Service endpoint.
449
+ @overload
450
+ def get(
451
+ self,
452
+ path: str,
453
+ result_class: Type[TResult],
454
+ check_response: bool = True,
455
+ *,
456
+ params: (
457
+ Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
458
+ | None
459
+ ) = None,
460
+ pangea_response: Literal[True] = True,
461
+ ) -> PangeaResponse[TResult]:
462
+ """
463
+ Makes the GET call to a Pangea Service endpoint.
376
464
 
377
465
  Args:
378
- endpoint(str): The Pangea Service API endpoint.
379
- path(str): Additional URL path
466
+ path: Additional URL path
467
+ params: Dictionary of querystring data to attach to the request
380
468
 
381
469
  Returns:
382
470
  PangeaResponse which contains the response in its entirety and
383
471
  various properties to retrieve individual fields
384
472
  """
385
473
 
474
+ @overload
475
+ def get(
476
+ self,
477
+ path: str,
478
+ result_class: Type[TResult],
479
+ check_response: bool = True,
480
+ *,
481
+ params: (
482
+ Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
483
+ | None
484
+ ) = None,
485
+ pangea_response: Literal[False] = False,
486
+ ) -> TResult:
487
+ """
488
+ Makes the GET call to a Pangea Service endpoint.
489
+
490
+ Args:
491
+ path: Additional URL path
492
+ params: Dictionary of querystring data to attach to the request
493
+ """
494
+
495
+ def get(
496
+ self,
497
+ path: str,
498
+ result_class: Type[TResult],
499
+ check_response: bool = True,
500
+ *,
501
+ params: (
502
+ Mapping[str | bytes | int | float, str | bytes | int | float | Iterable[str | bytes | int | float] | None]
503
+ | None
504
+ ) = None,
505
+ pangea_response: bool = True,
506
+ ) -> PangeaResponse[TResult] | TResult:
507
+ """
508
+ Makes the GET call to a Pangea Service endpoint.
509
+
510
+ Args:
511
+ path: Additional URL path
512
+ params: Dictionary of querystring data to attach to the request
513
+ pangea_response: Whether or not the response body follows Pangea's
514
+ standard response schema
515
+ """
516
+
386
517
  url = self._url(path)
387
518
  self.logger.debug(json.dumps({"service": self.service, "action": "get", "url": url}))
388
- requests_response = self.session.get(url, headers=self._headers())
519
+ requests_response = self.session.get(url, params=params, headers=self._headers())
389
520
  self._check_http_errors(requests_response)
390
- pangea_response: PangeaResponse = PangeaResponse(
521
+
522
+ if not pangea_response:
523
+ type_adapter = TypeAdapter(result_class)
524
+ return type_adapter.validate_python(requests_response.json())
525
+
526
+ pangea_response_obj: PangeaResponse = PangeaResponse(
391
527
  requests_response, result_class=result_class, json=requests_response.json()
392
528
  )
393
529
 
394
530
  self.logger.debug(
395
531
  json.dumps(
396
- {"service": self.service, "action": "get", "url": url, "response": pangea_response.json},
532
+ {"service": self.service, "action": "get", "url": url, "response": pangea_response_obj.json},
397
533
  default=default_encoder,
398
534
  )
399
535
  )
400
536
 
401
537
  if check_response is False:
402
- return pangea_response
538
+ return pangea_response_obj
403
539
 
404
- return self._check_response(pangea_response)
540
+ return self._check_response(pangea_response_obj)
405
541
 
406
542
  def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
407
543
  """
@@ -5,6 +5,7 @@ from .authz import AuthZ
5
5
  from .embargo import Embargo
6
6
  from .file_scan import FileScan
7
7
  from .intel import DomainIntel, FileIntel, IpIntel, UrlIntel, UserIntel
8
+ from .management import Management
8
9
  from .prompt_guard import PromptGuard
9
10
  from .redact import Redact
10
11
  from .sanitize import Sanitize