enkryptai-sdk 1.0.23__py3-none-any.whl → 1.0.25__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.
- enkryptai_sdk/__init__.py +63 -5
- enkryptai_sdk/dto/__init__.py +9 -0
- enkryptai_sdk/dto/models.py +39 -0
- enkryptai_sdk/dto/red_team.py +261 -4
- enkryptai_sdk/red_team.py +222 -6
- enkryptai_sdk/response.py +271 -0
- enkryptai_sdk/utils/__init__.py +29 -0
- enkryptai_sdk/utils/pagination.py +384 -0
- {enkryptai_sdk-1.0.23.dist-info → enkryptai_sdk-1.0.25.dist-info}/METADATA +1 -1
- {enkryptai_sdk-1.0.23.dist-info → enkryptai_sdk-1.0.25.dist-info}/RECORD +13 -11
- {enkryptai_sdk-1.0.23.dist-info → enkryptai_sdk-1.0.25.dist-info}/WHEEL +0 -0
- {enkryptai_sdk-1.0.23.dist-info → enkryptai_sdk-1.0.25.dist-info}/licenses/LICENSE +0 -0
- {enkryptai_sdk-1.0.23.dist-info → enkryptai_sdk-1.0.25.dist-info}/top_level.txt +0 -0
enkryptai_sdk/red_team.py
CHANGED
|
@@ -6,11 +6,14 @@ from .datasets import DatasetClient
|
|
|
6
6
|
from .dto import (
|
|
7
7
|
RedteamHealthResponse,
|
|
8
8
|
RedTeamModelHealthConfig,
|
|
9
|
+
RedTeamModelHealthConfigV3,
|
|
9
10
|
RedteamModelHealthResponse,
|
|
10
11
|
RedTeamConfig,
|
|
11
12
|
RedTeamConfigWithSavedModel,
|
|
12
13
|
RedTeamCustomConfig,
|
|
13
14
|
RedTeamCustomConfigWithSavedModel,
|
|
15
|
+
RedTeamCustomConfigV3,
|
|
16
|
+
RedTeamCustomConfigWithSavedModelV3,
|
|
14
17
|
RedTeamResponse,
|
|
15
18
|
RedTeamResultSummary,
|
|
16
19
|
RedTeamResultDetails,
|
|
@@ -21,7 +24,8 @@ from .dto import (
|
|
|
21
24
|
RedTeamRiskMitigationGuardrailsPolicyResponse,
|
|
22
25
|
RedTeamRiskMitigationSystemPromptConfig,
|
|
23
26
|
RedTeamRiskMitigationSystemPromptResponse,
|
|
24
|
-
RedTeamFindingsResponse
|
|
27
|
+
RedTeamFindingsResponse,
|
|
28
|
+
RedTeamDownloadLinkResponse
|
|
25
29
|
)
|
|
26
30
|
|
|
27
31
|
|
|
@@ -94,6 +98,40 @@ class RedTeamClient(BaseClient):
|
|
|
94
98
|
return RedteamModelHealthResponse.from_dict(response)
|
|
95
99
|
except Exception as e:
|
|
96
100
|
raise RedTeamClientError(str(e))
|
|
101
|
+
|
|
102
|
+
def check_model_health_v3(self, config: RedTeamModelHealthConfigV3):
|
|
103
|
+
"""
|
|
104
|
+
Get the health status of a model using V3 format with endpoint_configuration.
|
|
105
|
+
|
|
106
|
+
This method accepts endpoint_configuration (similar to add_custom_task) and
|
|
107
|
+
converts it internally to target_model_configuration format for backend compatibility.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
config (RedTeamModelHealthConfigV3): Configuration object containing endpoint_configuration
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
RedteamModelHealthResponse: Response from the API containing health status
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
RedTeamClientError: If there's an error from the API
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
config = RedTeamModelHealthConfigV3.from_dict(config)
|
|
120
|
+
|
|
121
|
+
# Convert endpoint_configuration to target_model_configuration
|
|
122
|
+
target_config = config.to_target_model_configuration()
|
|
123
|
+
|
|
124
|
+
# Create the payload in the format expected by the backend
|
|
125
|
+
payload = {
|
|
126
|
+
"target_model_configuration": target_config.to_dict()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
response = self._request("POST", "/redteam/model-health", json=payload)
|
|
130
|
+
if response.get("error") not in [None, ""]:
|
|
131
|
+
raise RedTeamClientError(f"API Error: {str(response)}")
|
|
132
|
+
return RedteamModelHealthResponse.from_dict(response)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
raise RedTeamClientError(str(e))
|
|
97
135
|
|
|
98
136
|
def add_task(
|
|
99
137
|
self,
|
|
@@ -200,6 +238,10 @@ class RedTeamClient(BaseClient):
|
|
|
200
238
|
"redteam_test_configurations": test_configs,
|
|
201
239
|
}
|
|
202
240
|
|
|
241
|
+
# Only add frameworks if provided and not empty
|
|
242
|
+
if config.frameworks:
|
|
243
|
+
payload["frameworks"] = config.frameworks
|
|
244
|
+
|
|
203
245
|
if config.dataset_configuration:
|
|
204
246
|
payload["dataset_configuration"] = DatasetClient.prepare_dataset_payload(
|
|
205
247
|
config.dataset_configuration, True)
|
|
@@ -254,6 +296,10 @@ class RedTeamClient(BaseClient):
|
|
|
254
296
|
"redteam_test_configurations": test_configs,
|
|
255
297
|
}
|
|
256
298
|
|
|
299
|
+
# Only add frameworks if provided and not empty
|
|
300
|
+
if config.frameworks:
|
|
301
|
+
payload["frameworks"] = config.frameworks
|
|
302
|
+
|
|
257
303
|
if config.dataset_configuration:
|
|
258
304
|
payload["dataset_configuration"] = DatasetClient.prepare_dataset_payload(
|
|
259
305
|
config.dataset_configuration, True)
|
|
@@ -281,6 +327,146 @@ class RedTeamClient(BaseClient):
|
|
|
281
327
|
raise RedTeamClientError(f"API Error: {str(response)}")
|
|
282
328
|
return RedTeamResponse.from_dict(response)
|
|
283
329
|
|
|
330
|
+
def add_custom_task_v3(
|
|
331
|
+
self,
|
|
332
|
+
config: RedTeamCustomConfigV3,
|
|
333
|
+
policy_name: str = None,
|
|
334
|
+
):
|
|
335
|
+
"""
|
|
336
|
+
Add a new custom red teaming task with v3 attack methods format.
|
|
337
|
+
|
|
338
|
+
V3 format supports nested attack methods:
|
|
339
|
+
{
|
|
340
|
+
"test_name": {
|
|
341
|
+
"sample_percentage": 50,
|
|
342
|
+
"attack_methods": {
|
|
343
|
+
"method_category": {
|
|
344
|
+
"method_name": {
|
|
345
|
+
"params": {}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
"""
|
|
352
|
+
headers = {
|
|
353
|
+
"Content-Type": "application/json",
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if policy_name is not None:
|
|
357
|
+
headers["X-Enkrypt-Policy"] = policy_name
|
|
358
|
+
|
|
359
|
+
config = RedTeamCustomConfigV3.from_dict(config)
|
|
360
|
+
test_configs = config.redteam_test_configurations.to_dict()
|
|
361
|
+
# Remove None or empty test configurations
|
|
362
|
+
test_configs = {k: v for k, v in test_configs.items() if v is not None}
|
|
363
|
+
|
|
364
|
+
payload = {
|
|
365
|
+
"test_name": config.test_name,
|
|
366
|
+
"redteam_test_configurations": test_configs,
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
# Only add frameworks if provided and not empty
|
|
370
|
+
if config.frameworks:
|
|
371
|
+
payload["frameworks"] = config.frameworks
|
|
372
|
+
|
|
373
|
+
if config.dataset_configuration:
|
|
374
|
+
payload["dataset_configuration"] = DatasetClient.prepare_dataset_payload(
|
|
375
|
+
config.dataset_configuration, True)
|
|
376
|
+
else:
|
|
377
|
+
raise RedTeamClientError(
|
|
378
|
+
"Please provide a dataset configuration"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if config.endpoint_configuration:
|
|
382
|
+
payload["endpoint_configuration"] = ModelClient.prepare_model_payload(
|
|
383
|
+
config.endpoint_configuration, True)
|
|
384
|
+
|
|
385
|
+
response = self._request(
|
|
386
|
+
"POST",
|
|
387
|
+
"/redteam/v3/add-custom-task",
|
|
388
|
+
headers=headers,
|
|
389
|
+
json=payload,
|
|
390
|
+
)
|
|
391
|
+
if response.get("error"):
|
|
392
|
+
raise RedTeamClientError(f"API Error: {str(response)}")
|
|
393
|
+
return RedTeamResponse.from_dict(response)
|
|
394
|
+
else:
|
|
395
|
+
raise RedTeamClientError(
|
|
396
|
+
"Please provide a endpoint configuration"
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
def add_custom_task_with_saved_model_v3(
|
|
400
|
+
self,
|
|
401
|
+
config: RedTeamCustomConfigWithSavedModelV3,
|
|
402
|
+
model_saved_name: str,
|
|
403
|
+
model_version: str,
|
|
404
|
+
policy_name: str = None,
|
|
405
|
+
):
|
|
406
|
+
"""
|
|
407
|
+
Add a new red teaming custom task using a saved model with v3 attack methods format.
|
|
408
|
+
|
|
409
|
+
V3 format supports nested attack methods:
|
|
410
|
+
{
|
|
411
|
+
"test_name": {
|
|
412
|
+
"sample_percentage": 50,
|
|
413
|
+
"attack_methods": {
|
|
414
|
+
"method_category": {
|
|
415
|
+
"method_name": {
|
|
416
|
+
"params": {}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
"""
|
|
423
|
+
if not model_saved_name:
|
|
424
|
+
raise RedTeamClientError("Please provide a model_saved_name")
|
|
425
|
+
|
|
426
|
+
if not model_version:
|
|
427
|
+
raise RedTeamClientError("Please provide a model_version. Default is 'v1'")
|
|
428
|
+
|
|
429
|
+
config = RedTeamCustomConfigWithSavedModelV3.from_dict(config)
|
|
430
|
+
test_configs = config.redteam_test_configurations.to_dict()
|
|
431
|
+
# Remove None or empty test configurations
|
|
432
|
+
test_configs = {k: v for k, v in test_configs.items() if v is not None}
|
|
433
|
+
|
|
434
|
+
payload = {
|
|
435
|
+
"test_name": config.test_name,
|
|
436
|
+
"redteam_test_configurations": test_configs,
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
# Only add frameworks if provided and not empty
|
|
440
|
+
if config.frameworks:
|
|
441
|
+
payload["frameworks"] = config.frameworks
|
|
442
|
+
|
|
443
|
+
if config.dataset_configuration:
|
|
444
|
+
payload["dataset_configuration"] = DatasetClient.prepare_dataset_payload(
|
|
445
|
+
config.dataset_configuration, True)
|
|
446
|
+
else:
|
|
447
|
+
raise RedTeamClientError(
|
|
448
|
+
"Please provide a dataset configuration"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
headers = {
|
|
452
|
+
"X-Enkrypt-Model": model_saved_name,
|
|
453
|
+
"X-Enkrypt-Model-Version": model_version,
|
|
454
|
+
"Content-Type": "application/json",
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if policy_name is not None:
|
|
458
|
+
headers["X-Enkrypt-Policy"] = policy_name
|
|
459
|
+
|
|
460
|
+
response = self._request(
|
|
461
|
+
"POST",
|
|
462
|
+
"/redteam/v3/model/add-custom-task",
|
|
463
|
+
headers=headers,
|
|
464
|
+
json=payload,
|
|
465
|
+
)
|
|
466
|
+
if response.get("error"):
|
|
467
|
+
raise RedTeamClientError(f"API Error: {str(response)}")
|
|
468
|
+
return RedTeamResponse.from_dict(response)
|
|
469
|
+
|
|
284
470
|
def status(self, task_id: str = None, test_name: str = None):
|
|
285
471
|
"""
|
|
286
472
|
Get the status of a specific red teaming task.
|
|
@@ -394,7 +580,7 @@ class RedTeamClient(BaseClient):
|
|
|
394
580
|
if test_name:
|
|
395
581
|
headers["X-Enkrypt-Test-Name"] = test_name
|
|
396
582
|
|
|
397
|
-
response = self._request("GET", "/redteam/results/summary", headers=headers)
|
|
583
|
+
response = self._request("GET", "/redteam/v3/results/summary", headers=headers)
|
|
398
584
|
if response.get("error"):
|
|
399
585
|
raise RedTeamClientError(f"API Error: {str(response)}")
|
|
400
586
|
# print(f"Response: {response}")
|
|
@@ -427,7 +613,7 @@ class RedTeamClient(BaseClient):
|
|
|
427
613
|
if test_name:
|
|
428
614
|
headers["X-Enkrypt-Test-Name"] = test_name
|
|
429
615
|
|
|
430
|
-
url = f"/redteam/
|
|
616
|
+
url = f"/redteam/v3/results/summary/{test_type}"
|
|
431
617
|
response = self._request("GET", url, headers=headers)
|
|
432
618
|
if response.get("error"):
|
|
433
619
|
raise RedTeamClientError(f"API Error: {str(response)}")
|
|
@@ -457,7 +643,7 @@ class RedTeamClient(BaseClient):
|
|
|
457
643
|
if test_name:
|
|
458
644
|
headers["X-Enkrypt-Test-Name"] = test_name
|
|
459
645
|
|
|
460
|
-
response = self._request("GET", "/redteam/
|
|
646
|
+
response = self._request("GET", "/redteam/v3/results/details", headers=headers)
|
|
461
647
|
if response.get("error"):
|
|
462
648
|
raise RedTeamClientError(f"API Error: {str(response)}")
|
|
463
649
|
return RedTeamResultDetails.from_dict(response)
|
|
@@ -488,9 +674,8 @@ class RedTeamClient(BaseClient):
|
|
|
488
674
|
headers["X-Enkrypt-Task-ID"] = task_id
|
|
489
675
|
if test_name:
|
|
490
676
|
headers["X-Enkrypt-Test-Name"] = test_name
|
|
491
|
-
|
|
492
677
|
|
|
493
|
-
url = f"/redteam/
|
|
678
|
+
url = f"/redteam/v3/results/details/{test_type}"
|
|
494
679
|
response = self._request("GET", url, headers=headers)
|
|
495
680
|
if response.get("error"):
|
|
496
681
|
raise RedTeamClientError(f"API Error: {str(response)}")
|
|
@@ -564,4 +749,35 @@ class RedTeamClient(BaseClient):
|
|
|
564
749
|
return RedTeamFindingsResponse.from_dict(response)
|
|
565
750
|
except Exception as e:
|
|
566
751
|
raise RedTeamClientError(str(e))
|
|
752
|
+
|
|
753
|
+
def get_download_link(self, task_id: str = None, test_name: str = None):
|
|
754
|
+
"""
|
|
755
|
+
Get a download link for red team test results.
|
|
756
|
+
|
|
757
|
+
Args:
|
|
758
|
+
task_id (str, optional): The ID of the task to get download link for
|
|
759
|
+
test_name (str, optional): The name of the test to get download link for
|
|
760
|
+
|
|
761
|
+
Returns:
|
|
762
|
+
RedTeamDownloadLinkResponse: Response containing download link and expiry information
|
|
763
|
+
|
|
764
|
+
Raises:
|
|
765
|
+
RedTeamClientError: If neither task_id nor test_name is provided, or if there's an error from the API
|
|
766
|
+
"""
|
|
767
|
+
if not task_id and not test_name:
|
|
768
|
+
raise RedTeamClientError("Either task_id or test_name must be provided")
|
|
769
|
+
|
|
770
|
+
headers = {}
|
|
771
|
+
if task_id:
|
|
772
|
+
headers["X-Enkrypt-Task-ID"] = task_id
|
|
773
|
+
if test_name:
|
|
774
|
+
headers["X-Enkrypt-Test-Name"] = test_name
|
|
775
|
+
|
|
776
|
+
try:
|
|
777
|
+
response = self._request("GET", "/redteam/download-link", headers=headers)
|
|
778
|
+
if response.get("error"):
|
|
779
|
+
raise RedTeamClientError(f"API Error: {str(response)}")
|
|
780
|
+
return RedTeamDownloadLinkResponse.from_dict(response)
|
|
781
|
+
except Exception as e:
|
|
782
|
+
raise RedTeamClientError(str(e))
|
|
567
783
|
|
enkryptai_sdk/response.py
CHANGED
|
@@ -1,3 +1,238 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import Dict, Any, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PaginationInfo:
|
|
6
|
+
"""
|
|
7
|
+
A class to handle pagination information and calculations.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, page: int = 1, per_page: int = 10, total_count: int = 0):
|
|
11
|
+
"""
|
|
12
|
+
Initialize pagination information.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
page (int): Current page number (1-based)
|
|
16
|
+
per_page (int): Number of items per page
|
|
17
|
+
total_count (int): Total number of items
|
|
18
|
+
"""
|
|
19
|
+
self.page = max(1, page)
|
|
20
|
+
self.per_page = max(1, min(100, per_page)) # Ensure per_page is between 1 and 100
|
|
21
|
+
self.total_count = max(0, total_count)
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def total_pages(self) -> int:
|
|
25
|
+
"""Calculate total number of pages."""
|
|
26
|
+
if self.total_count == 0:
|
|
27
|
+
return 0
|
|
28
|
+
return math.ceil(self.total_count / self.per_page)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def has_next(self) -> bool:
|
|
32
|
+
"""Check if there's a next page."""
|
|
33
|
+
return self.page < self.total_pages
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def has_previous(self) -> bool:
|
|
37
|
+
"""Check if there's a previous page."""
|
|
38
|
+
return self.page > 1
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def offset(self) -> int:
|
|
42
|
+
"""Calculate the offset for database queries."""
|
|
43
|
+
return (self.page - 1) * self.per_page
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def limit(self) -> int:
|
|
47
|
+
"""Get the limit for database queries."""
|
|
48
|
+
return self.per_page
|
|
49
|
+
|
|
50
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
51
|
+
"""Convert pagination info to dictionary."""
|
|
52
|
+
return {
|
|
53
|
+
"page": self.page,
|
|
54
|
+
"per_page": self.per_page,
|
|
55
|
+
"total_count": self.total_count,
|
|
56
|
+
"total_pages": self.total_pages,
|
|
57
|
+
"has_next": self.has_next,
|
|
58
|
+
"has_previous": self.has_previous
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_query_params(cls, query_params: Dict[str, Any], default_per_page: int = 10) -> "PaginationInfo":
|
|
63
|
+
"""
|
|
64
|
+
Create PaginationInfo from query parameters.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
query_params (Dict[str, Any]): Dictionary containing query parameters
|
|
68
|
+
default_per_page (int): Default items per page
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
PaginationInfo: Pagination information object
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
page = int(query_params.get("page", 1))
|
|
75
|
+
per_page = int(query_params.get("per_page", default_per_page))
|
|
76
|
+
except (ValueError, TypeError):
|
|
77
|
+
page = 1
|
|
78
|
+
per_page = default_per_page
|
|
79
|
+
|
|
80
|
+
# Validate pagination parameters
|
|
81
|
+
if page < 1:
|
|
82
|
+
page = 1
|
|
83
|
+
if per_page < 1 or per_page > 100:
|
|
84
|
+
per_page = min(max(1, per_page), 100)
|
|
85
|
+
|
|
86
|
+
return cls(page=page, per_page=per_page)
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def validate_params(cls, page: Union[str, int], per_page: Union[str, int]) -> tuple[int, int]:
|
|
90
|
+
"""
|
|
91
|
+
Validate pagination parameters and return validated values.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
page: Page number (can be string or int)
|
|
95
|
+
per_page: Items per page (can be string or int)
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
tuple[int, int]: Validated (page, per_page) values
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
ValueError: If parameters are invalid
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
page_num = int(page) if page is not None else 1
|
|
105
|
+
per_page_num = int(per_page) if per_page is not None else 10
|
|
106
|
+
except (ValueError, TypeError):
|
|
107
|
+
raise ValueError("Page and per_page must be valid integers")
|
|
108
|
+
|
|
109
|
+
if page_num < 1:
|
|
110
|
+
raise ValueError("Page must be >= 1")
|
|
111
|
+
if per_page_num < 1 or per_page_num > 100:
|
|
112
|
+
raise ValueError("Per_page must be between 1 and 100")
|
|
113
|
+
|
|
114
|
+
return page_num, per_page_num
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class PaginatedResponse(dict):
|
|
118
|
+
"""
|
|
119
|
+
A wrapper class for paginated API responses that provides pagination information
|
|
120
|
+
and maintains backward compatibility with dictionary access.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, data: List[Any], pagination: PaginationInfo, **kwargs):
|
|
124
|
+
"""
|
|
125
|
+
Initialize the PaginatedResponse object.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
data (List[Any]): List of items for the current page
|
|
129
|
+
pagination (PaginationInfo): Pagination information
|
|
130
|
+
**kwargs: Additional response data
|
|
131
|
+
"""
|
|
132
|
+
response_data = {
|
|
133
|
+
"data": data,
|
|
134
|
+
"pagination": pagination.to_dict(),
|
|
135
|
+
**kwargs
|
|
136
|
+
}
|
|
137
|
+
super().__init__(response_data)
|
|
138
|
+
self._data = response_data
|
|
139
|
+
self._pagination = pagination
|
|
140
|
+
|
|
141
|
+
def get_data(self) -> List[Any]:
|
|
142
|
+
"""Get the data items for the current page."""
|
|
143
|
+
return self._data.get("data", [])
|
|
144
|
+
|
|
145
|
+
def get_pagination(self) -> Dict[str, Any]:
|
|
146
|
+
"""Get pagination information."""
|
|
147
|
+
return self._data.get("pagination", {})
|
|
148
|
+
|
|
149
|
+
def get_page(self) -> int:
|
|
150
|
+
"""Get current page number."""
|
|
151
|
+
return self._pagination.page
|
|
152
|
+
|
|
153
|
+
def get_per_page(self) -> int:
|
|
154
|
+
"""Get items per page."""
|
|
155
|
+
return self._pagination.per_page
|
|
156
|
+
|
|
157
|
+
def get_total_count(self) -> int:
|
|
158
|
+
"""Get total number of items."""
|
|
159
|
+
return self._pagination.total_count
|
|
160
|
+
|
|
161
|
+
def get_total_pages(self) -> int:
|
|
162
|
+
"""Get total number of pages."""
|
|
163
|
+
return self._pagination.total_pages
|
|
164
|
+
|
|
165
|
+
def has_next_page(self) -> bool:
|
|
166
|
+
"""Check if there's a next page."""
|
|
167
|
+
return self._pagination.has_next
|
|
168
|
+
|
|
169
|
+
def has_previous_page(self) -> bool:
|
|
170
|
+
"""Check if there's a previous page."""
|
|
171
|
+
return self._pagination.has_previous
|
|
172
|
+
|
|
173
|
+
def get_next_page_url(self, base_url: str, **query_params) -> Optional[str]:
|
|
174
|
+
"""
|
|
175
|
+
Generate URL for the next page.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
base_url (str): Base URL for the endpoint
|
|
179
|
+
**query_params: Additional query parameters to include
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Optional[str]: Next page URL or None if no next page
|
|
183
|
+
"""
|
|
184
|
+
if not self.has_next_page():
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
params = {**query_params, "page": self.get_page() + 1, "per_page": self.get_per_page()}
|
|
188
|
+
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
|
|
189
|
+
return f"{base_url}?{query_string}"
|
|
190
|
+
|
|
191
|
+
def get_previous_page_url(self, base_url: str, **query_params) -> Optional[str]:
|
|
192
|
+
"""
|
|
193
|
+
Generate URL for the previous page.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
base_url (str): Base URL for the endpoint
|
|
197
|
+
**query_params: Additional query parameters to include
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Optional[str]: Previous page URL or None if no previous page
|
|
201
|
+
"""
|
|
202
|
+
if not self.has_previous_page():
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
params = {**query_params, "page": self.get_page() - 1, "per_page": self.get_per_page()}
|
|
206
|
+
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
|
|
207
|
+
return f"{base_url}?{query_string}"
|
|
208
|
+
|
|
209
|
+
def get_page_urls(self, base_url: str, **query_params) -> Dict[str, Optional[str]]:
|
|
210
|
+
"""
|
|
211
|
+
Generate URLs for all pagination actions.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
base_url (str): Base URL for the endpoint
|
|
215
|
+
**query_params: Additional query parameters to include
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Dict[str, Optional[str]]: Dictionary with pagination URLs
|
|
219
|
+
"""
|
|
220
|
+
return {
|
|
221
|
+
"first": f"{base_url}?{self._build_query_string(1, **query_params)}",
|
|
222
|
+
"previous": self.get_previous_page_url(base_url, **query_params),
|
|
223
|
+
"current": f"{base_url}?{self._build_query_string(self.get_page(), **query_params)}",
|
|
224
|
+
"next": self.get_next_page_url(base_url, **query_params),
|
|
225
|
+
"last": f"{base_url}?{self._build_query_string(self.get_total_pages(), **query_params)}" if self.get_total_pages() > 0 else None
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
def _build_query_string(self, page: int, **query_params) -> str:
|
|
229
|
+
"""Build query string for a specific page."""
|
|
230
|
+
params = {**query_params, "page": page, "per_page": self.get_per_page()}
|
|
231
|
+
return "&".join([f"{k}={v}" for k, v in params.items()])
|
|
232
|
+
|
|
233
|
+
def __str__(self) -> str:
|
|
234
|
+
"""String representation of the paginated response."""
|
|
235
|
+
return f"PaginatedResponse(page={self.get_page()}, per_page={self.get_per_page()}, total={self.get_total_count()}, items={len(self.get_data())})"
|
|
1
236
|
|
|
2
237
|
|
|
3
238
|
class GuardrailsResponse(dict):
|
|
@@ -100,7 +335,25 @@ class GuardrailsResponse(dict):
|
|
|
100
335
|
|
|
101
336
|
return f"Response Status: {status}\n{violation_str}"
|
|
102
337
|
|
|
338
|
+
def get_pagination(self) -> Optional[Dict[str, Any]]:
|
|
339
|
+
"""
|
|
340
|
+
Get pagination information if available.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Optional[Dict[str, Any]]: Pagination data or None if not available
|
|
344
|
+
"""
|
|
345
|
+
return self._data.get("pagination")
|
|
103
346
|
|
|
347
|
+
def is_paginated(self) -> bool:
|
|
348
|
+
"""
|
|
349
|
+
Check if the response contains pagination information.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
bool: True if response is paginated, False otherwise
|
|
353
|
+
"""
|
|
354
|
+
return "pagination" in self._data
|
|
355
|
+
|
|
356
|
+
|
|
104
357
|
class PIIResponse(dict):
|
|
105
358
|
"""
|
|
106
359
|
A wrapper class for Enkrypt AI PII API responses that provides additional functionality
|
|
@@ -132,4 +385,22 @@ class PIIResponse(dict):
|
|
|
132
385
|
"""
|
|
133
386
|
return self._data.get("key", "")
|
|
134
387
|
|
|
388
|
+
def get_pagination(self) -> Optional[Dict[str, Any]]:
|
|
389
|
+
"""
|
|
390
|
+
Get pagination information if available.
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Optional[Dict[str, Any]]: Pagination data or None if not available
|
|
394
|
+
"""
|
|
395
|
+
return self._data.get("pagination")
|
|
396
|
+
|
|
397
|
+
def is_paginated(self) -> bool:
|
|
398
|
+
"""
|
|
399
|
+
Check if the response contains pagination information.
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
bool: True if response is paginated, False otherwise
|
|
403
|
+
"""
|
|
404
|
+
return "pagination" in self._data
|
|
405
|
+
|
|
135
406
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Utils module for Enkrypt AI SDK
|
|
2
|
+
|
|
3
|
+
from .pagination import (
|
|
4
|
+
PaginationInfo,
|
|
5
|
+
PaginatedResponse,
|
|
6
|
+
parse_pagination_params,
|
|
7
|
+
build_pagination_url,
|
|
8
|
+
create_paginated_response,
|
|
9
|
+
validate_pagination_params,
|
|
10
|
+
get_pagination_metadata,
|
|
11
|
+
calculate_page_info,
|
|
12
|
+
create_pagination_links,
|
|
13
|
+
apply_pagination_to_list,
|
|
14
|
+
format_pagination_response
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"PaginationInfo",
|
|
19
|
+
"PaginatedResponse",
|
|
20
|
+
"parse_pagination_params",
|
|
21
|
+
"build_pagination_url",
|
|
22
|
+
"create_paginated_response",
|
|
23
|
+
"validate_pagination_params",
|
|
24
|
+
"get_pagination_metadata",
|
|
25
|
+
"calculate_page_info",
|
|
26
|
+
"create_pagination_links",
|
|
27
|
+
"apply_pagination_to_list",
|
|
28
|
+
"format_pagination_response"
|
|
29
|
+
]
|