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.
Files changed (53) hide show
  1. appython/__init__.py +12 -0
  2. appython/protector.py +554 -0
  3. appython/service/auth_provider.py +273 -0
  4. appython/service/auth_token_provider.py +45 -0
  5. appython/service/config.py +209 -0
  6. appython/service/payload_builder.py +141 -0
  7. appython/service/request_handler.py +115 -0
  8. appython/service/response_handler.py +78 -0
  9. appython/stats/__init__.py +3 -0
  10. appython/stats/collector.py +90 -0
  11. appython/stats/writer.py +185 -0
  12. appython/utils/codec_helper.py +86 -0
  13. appython/utils/constants.py +246 -0
  14. appython/utils/exceptions.py +141 -0
  15. appython/utils/input_preprocessor.py +325 -0
  16. appython/utils/output_postprocessor.py +99 -0
  17. protegrity_ai_developer_python-1.2.1.dist-info/METADATA +428 -0
  18. protegrity_ai_developer_python-1.2.1.dist-info/RECORD +53 -0
  19. protegrity_ai_developer_python-1.2.1.dist-info/WHEEL +5 -0
  20. protegrity_ai_developer_python-1.2.1.dist-info/entry_points.txt +2 -0
  21. protegrity_ai_developer_python-1.2.1.dist-info/licenses/LICENSE +21 -0
  22. protegrity_ai_developer_python-1.2.1.dist-info/top_level.txt +3 -0
  23. protegrity_developer_python/__init__.py +4 -0
  24. protegrity_developer_python/scan.py +37 -0
  25. protegrity_developer_python/securefind.py +83 -0
  26. protegrity_developer_python/utils/ccn_processing.py +59 -0
  27. protegrity_developer_python/utils/config.py +60 -0
  28. protegrity_developer_python/utils/constants.py +123 -0
  29. protegrity_developer_python/utils/discover.py +49 -0
  30. protegrity_developer_python/utils/logger.py +23 -0
  31. protegrity_developer_python/utils/pii_processing.py +291 -0
  32. protegrity_developer_python/utils/protector.py +23 -0
  33. protegrity_developer_python/utils/semantic_guardrails.py +240 -0
  34. protegrity_developer_python/utils/transform.py +66 -0
  35. pty_migrate/__init__.py +1 -0
  36. pty_migrate/check_cmd.py +871 -0
  37. pty_migrate/cli.py +93 -0
  38. pty_migrate/config.py +127 -0
  39. pty_migrate/create_policy_cmd.py +795 -0
  40. pty_migrate/payloads/__init__.py +51 -0
  41. pty_migrate/payloads/alphabets.json +42 -0
  42. pty_migrate/payloads/dataelements.json +342 -0
  43. pty_migrate/payloads/datastores.json +7 -0
  44. pty_migrate/payloads/deploy_policy_ta.json +1 -0
  45. pty_migrate/payloads/masks.json +18 -0
  46. pty_migrate/payloads/members.json +62 -0
  47. pty_migrate/payloads/policies.json +13 -0
  48. pty_migrate/payloads/roles.json +32 -0
  49. pty_migrate/payloads/rules.json +1639 -0
  50. pty_migrate/payloads/sources.json +10 -0
  51. pty_migrate/payloads/trusted_apps.json +8 -0
  52. pty_migrate/ppc_client.py +371 -0
  53. 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
@@ -0,0 +1 @@
1
+ """pty-migrate: DE to TE migration CLI tool."""