protegrity-ai-developer-python 1.2.1__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.
- appython/__init__.py +12 -0
- appython/protector.py +554 -0
- appython/service/auth_provider.py +273 -0
- appython/service/auth_token_provider.py +45 -0
- appython/service/config.py +209 -0
- appython/service/payload_builder.py +141 -0
- appython/service/request_handler.py +115 -0
- appython/service/response_handler.py +78 -0
- appython/stats/__init__.py +3 -0
- appython/stats/collector.py +90 -0
- appython/stats/writer.py +185 -0
- appython/utils/codec_helper.py +86 -0
- appython/utils/constants.py +246 -0
- appython/utils/exceptions.py +141 -0
- appython/utils/input_preprocessor.py +325 -0
- appython/utils/output_postprocessor.py +99 -0
- protegrity_ai_developer_python-1.2.1.dist-info/METADATA +428 -0
- protegrity_ai_developer_python-1.2.1.dist-info/RECORD +53 -0
- protegrity_ai_developer_python-1.2.1.dist-info/WHEEL +5 -0
- protegrity_ai_developer_python-1.2.1.dist-info/entry_points.txt +2 -0
- protegrity_ai_developer_python-1.2.1.dist-info/licenses/LICENSE +21 -0
- protegrity_ai_developer_python-1.2.1.dist-info/top_level.txt +3 -0
- protegrity_developer_python/__init__.py +4 -0
- protegrity_developer_python/scan.py +37 -0
- protegrity_developer_python/securefind.py +83 -0
- protegrity_developer_python/utils/ccn_processing.py +59 -0
- protegrity_developer_python/utils/config.py +60 -0
- protegrity_developer_python/utils/constants.py +123 -0
- protegrity_developer_python/utils/discover.py +49 -0
- protegrity_developer_python/utils/logger.py +23 -0
- protegrity_developer_python/utils/pii_processing.py +291 -0
- protegrity_developer_python/utils/protector.py +23 -0
- protegrity_developer_python/utils/semantic_guardrails.py +240 -0
- protegrity_developer_python/utils/transform.py +66 -0
- pty_migrate/__init__.py +1 -0
- pty_migrate/check_cmd.py +871 -0
- pty_migrate/cli.py +93 -0
- pty_migrate/config.py +127 -0
- pty_migrate/create_policy_cmd.py +795 -0
- pty_migrate/payloads/__init__.py +51 -0
- pty_migrate/payloads/alphabets.json +42 -0
- pty_migrate/payloads/dataelements.json +342 -0
- pty_migrate/payloads/datastores.json +7 -0
- pty_migrate/payloads/deploy_policy_ta.json +1 -0
- pty_migrate/payloads/masks.json +18 -0
- pty_migrate/payloads/members.json +62 -0
- pty_migrate/payloads/policies.json +13 -0
- pty_migrate/payloads/roles.json +32 -0
- pty_migrate/payloads/rules.json +1639 -0
- pty_migrate/payloads/sources.json +10 -0
- pty_migrate/payloads/trusted_apps.json +8 -0
- pty_migrate/ppc_client.py +371 -0
- pty_migrate/stats_cmd.py +87 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Synchronous client for Semantic Guardrails API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal, Any
|
|
6
|
+
import requests
|
|
7
|
+
import json
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from protegrity_developer_python.utils.constants import get_config
|
|
11
|
+
from protegrity_developer_python.utils.logger import get_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_config = get_config("semantic-guardrails")
|
|
15
|
+
BASE_URL = _config["endpoint_url"]
|
|
16
|
+
|
|
17
|
+
logger = get_logger()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# --------------------------------------------------
|
|
21
|
+
# Exceptions
|
|
22
|
+
# --------------------------------------------------
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SemanticGuardrailsError(Exception):
|
|
26
|
+
"""Base exception for Semantic Guardrails SDK."""
|
|
27
|
+
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class APIError(SemanticGuardrailsError):
|
|
32
|
+
"""Raised when the API returns an error response."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, message: str, status_code: int | None = None) -> None:
|
|
35
|
+
self.message = message
|
|
36
|
+
self.status_code = status_code
|
|
37
|
+
super().__init__(self.message)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# --------------------------------------------------
|
|
41
|
+
# Client
|
|
42
|
+
# --------------------------------------------------
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def scan_messages(request: MessageBatchRiskRequest) -> MessageBatchRiskResponse:
|
|
46
|
+
"""
|
|
47
|
+
Scan a batch of messages for security risks.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
request: Batch of messages to scan
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Risk assessment for each message and overall batch assessment
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
response = requests.post(
|
|
58
|
+
f"{BASE_URL}/conversations/messages/scan",
|
|
59
|
+
json=request.model_dump(mode="json", by_alias=True, exclude_none=True),
|
|
60
|
+
)
|
|
61
|
+
response.raise_for_status()
|
|
62
|
+
return MessageBatchRiskResponse.model_validate(response.json())
|
|
63
|
+
except requests.exceptions.RequestException as e:
|
|
64
|
+
logger.error(f"HTTP request failed: {e}")
|
|
65
|
+
raise
|
|
66
|
+
except json.JSONDecodeError as e:
|
|
67
|
+
logger.error(f"Failed to decode JSON response: {e}")
|
|
68
|
+
raise
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.error(f"Unexpected error: {e}")
|
|
71
|
+
raise
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def list_domain_models() -> list[DomainModelResponse]:
|
|
75
|
+
"""
|
|
76
|
+
Get information about available domain models.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
List of domain models with their configurations
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
response = requests.get(f"{BASE_URL}/domain-models/")
|
|
83
|
+
response.raise_for_status()
|
|
84
|
+
return [DomainModelResponse.model_validate(item) for item in response.json()]
|
|
85
|
+
except requests.exceptions.RequestException as e:
|
|
86
|
+
logger.error(f"HTTP request failed: {e}")
|
|
87
|
+
raise
|
|
88
|
+
except json.JSONDecodeError as e:
|
|
89
|
+
logger.error(f"Failed to decode JSON response: {e}")
|
|
90
|
+
raise
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.error(f"Unexpected error: {e}")
|
|
93
|
+
raise
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# --------------------------------------------------
|
|
97
|
+
# Data models
|
|
98
|
+
# --------------------------------------------------
|
|
99
|
+
|
|
100
|
+
SenderRole = Literal["user", "ai", "context"]
|
|
101
|
+
RiskOutcome = Literal["approved", "rejected", "skipped"]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class ProcessorResult(BaseModel):
|
|
105
|
+
"""Individual processor execution result."""
|
|
106
|
+
|
|
107
|
+
name: str
|
|
108
|
+
score: float
|
|
109
|
+
explanation: str | None = None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class MessageRiskRequest(BaseModel):
|
|
113
|
+
"""Request model for scanning a single message.
|
|
114
|
+
|
|
115
|
+
from_ field is mapped to 'from' in JSON output.
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
id: str | None = Field(
|
|
120
|
+
None, description="Optional message ID. SGR will generate one if not provided."
|
|
121
|
+
)
|
|
122
|
+
from_: SenderRole = Field(..., description="Sender role")
|
|
123
|
+
to: SenderRole = Field(..., description="Recipient role")
|
|
124
|
+
content: str = Field(
|
|
125
|
+
..., min_length=0, max_length=10024, description="Message content"
|
|
126
|
+
)
|
|
127
|
+
processors: list[str] | None = Field(
|
|
128
|
+
None,
|
|
129
|
+
max_length=1,
|
|
130
|
+
description="Processing methods to be used. If None, message is skipped.",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
model_config = {"populate_by_name": True}
|
|
134
|
+
|
|
135
|
+
def model_dump(
|
|
136
|
+
self, mode: str = "json", by_alias: bool = True, exclude_none: bool = True
|
|
137
|
+
) -> dict[str, Any]:
|
|
138
|
+
"""Custom model dump to convert to JSON-compatible dict.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
mode (str): The mode for dumping the model. Only 'json' is supported.
|
|
142
|
+
by_alias (bool): Whether to use field aliases.
|
|
143
|
+
exclude_none (bool): Whether to exclude fields with None values.
|
|
144
|
+
Returns:
|
|
145
|
+
dict[str, Any]: The dumped model as a dictionary.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
if mode != "json":
|
|
149
|
+
raise ValueError(
|
|
150
|
+
"Only 'json' mode is supported in this custom dump method."
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
out = super().model_dump(
|
|
154
|
+
mode=mode, by_alias=by_alias, exclude_none=exclude_none
|
|
155
|
+
)
|
|
156
|
+
out["from"] = out.pop("from_") # Rename 'from_' to 'from'
|
|
157
|
+
|
|
158
|
+
return out
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class MessageRiskResponse(BaseModel):
|
|
162
|
+
"""Response model for a single scanned message."""
|
|
163
|
+
|
|
164
|
+
id: str | None = Field(None, description="Message ID")
|
|
165
|
+
outcome: RiskOutcome = Field(..., description="Risk assessment outcome")
|
|
166
|
+
score: float | None = Field(
|
|
167
|
+
None, ge=0.0, le=1.0, description="Risk score. Higher means riskier."
|
|
168
|
+
)
|
|
169
|
+
processors: list[ProcessorResult] = Field(
|
|
170
|
+
default_factory=list, description="Processor results"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class BatchRiskResponse(BaseModel):
|
|
175
|
+
"""Overall batch risk assessment."""
|
|
176
|
+
|
|
177
|
+
outcome: Literal["approved", "rejected"] = Field(
|
|
178
|
+
..., description="Overall batch risk assessment outcome"
|
|
179
|
+
)
|
|
180
|
+
score: float = Field(
|
|
181
|
+
...,
|
|
182
|
+
ge=0.0,
|
|
183
|
+
le=1.0,
|
|
184
|
+
description="Aggregate risk score for the batch. Higher means riskier.",
|
|
185
|
+
)
|
|
186
|
+
rejected_messages: list[str] = Field(
|
|
187
|
+
..., description="List of rejected message IDs"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class MessageBatchRiskRequest(BaseModel):
|
|
192
|
+
"""Request model for scanning a batch of messages."""
|
|
193
|
+
|
|
194
|
+
messages: list[MessageRiskRequest] = Field(
|
|
195
|
+
..., min_length=1, description="List of messages to risk score"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def model_dump(
|
|
199
|
+
self, mode: str = "json", by_alias: bool = True, exclude_none: bool = True
|
|
200
|
+
) -> dict[str, Any]:
|
|
201
|
+
"""Custom model dump to convert to JSON-compatible dict.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
mode (str): The mode for dumping the model. Only 'json' is supported.
|
|
205
|
+
by_alias (bool): Whether to use field aliases.
|
|
206
|
+
exclude_none (bool): Whether to exclude fields with None values.
|
|
207
|
+
Returns:
|
|
208
|
+
dict[str, Any]: The dumped model as a dictionary.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
if mode != "json":
|
|
212
|
+
raise ValueError(
|
|
213
|
+
"Only 'json' mode is supported in this custom dump method."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
out = {"messages": []}
|
|
217
|
+
for _msg in self.messages:
|
|
218
|
+
msg = _msg.model_dump(
|
|
219
|
+
mode=mode, by_alias=by_alias, exclude_none=exclude_none
|
|
220
|
+
)
|
|
221
|
+
out["messages"].append(msg)
|
|
222
|
+
|
|
223
|
+
return out
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class MessageBatchRiskResponse(BaseModel):
|
|
227
|
+
"""Response model for a batch of scanned messages."""
|
|
228
|
+
|
|
229
|
+
messages: list[MessageRiskResponse] = Field(
|
|
230
|
+
..., description="Individual message risk responses"
|
|
231
|
+
)
|
|
232
|
+
batch: BatchRiskResponse = Field(..., description="Overall batch risk assessment")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class DomainModelResponse(BaseModel):
|
|
236
|
+
"""Domain model information."""
|
|
237
|
+
|
|
238
|
+
domain: str = Field(..., description="Namespace domain of the domain model")
|
|
239
|
+
model_name: str = Field(..., description="Domain model name")
|
|
240
|
+
threshold: float = Field(..., description="In-domain threshold")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for redacting PII entities using the Data Discovery transform/label API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import requests
|
|
7
|
+
from protegrity_developer_python.utils.constants import get_config
|
|
8
|
+
from protegrity_developer_python.utils.logger import get_logger
|
|
9
|
+
from protegrity_developer_python.utils.pii_processing import redact_data, collect_entity_spans
|
|
10
|
+
|
|
11
|
+
_config = get_config("data-discovery")
|
|
12
|
+
# Get logger instance
|
|
13
|
+
logger = get_logger()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def transform_label(text: str) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Redact or mask PII entities in the input text using the transform/label API.
|
|
19
|
+
|
|
20
|
+
When method is "redact" (default), calls the Data Discovery v2 transform/label
|
|
21
|
+
endpoint and returns the redacted text with [ENTITY_TYPE] labels.
|
|
22
|
+
|
|
23
|
+
When method is "mask", uses include_classification_details to get entity
|
|
24
|
+
positions from the same API call and replaces them with the masking_char.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
text (str): Input text to redact or mask.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str: Redacted or masked text.
|
|
31
|
+
"""
|
|
32
|
+
headers = {"Content-Type": "text/plain; charset=utf-8"}
|
|
33
|
+
method = _config.get("method", "redact")
|
|
34
|
+
params = {"include_classification_details": "yes"} if method == "mask" else {}
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
response = requests.post(
|
|
38
|
+
_config["transform_url"],
|
|
39
|
+
headers=headers,
|
|
40
|
+
params=params,
|
|
41
|
+
data=text.encode("utf-8"),
|
|
42
|
+
timeout=30,
|
|
43
|
+
)
|
|
44
|
+
response.raise_for_status()
|
|
45
|
+
response_json = response.json()
|
|
46
|
+
logger.debug("Transform/label response: %s", response_json)
|
|
47
|
+
|
|
48
|
+
if method == "mask":
|
|
49
|
+
classifications = response_json.get("classifications", {})
|
|
50
|
+
if classifications:
|
|
51
|
+
pii_entity_spans = collect_entity_spans(classifications)
|
|
52
|
+
return redact_data(pii_entity_spans, text)
|
|
53
|
+
logger.info("No PII entities found.")
|
|
54
|
+
return text
|
|
55
|
+
|
|
56
|
+
redacted = response_json.get("transform", {}).get("text", text)
|
|
57
|
+
return redacted
|
|
58
|
+
except requests.exceptions.RequestException as e:
|
|
59
|
+
logger.error("HTTP request failed: %s", e)
|
|
60
|
+
raise
|
|
61
|
+
except json.JSONDecodeError as e:
|
|
62
|
+
logger.error("Failed to decode JSON response: %s", e)
|
|
63
|
+
raise
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error("Unexpected error: %s", e)
|
|
66
|
+
raise
|
pty_migrate/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""pty-migrate: DE to TE migration CLI tool."""
|