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