peyeeye 1.0.0__tar.gz
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.
- peyeeye-1.0.0/.gitignore +15 -0
- peyeeye-1.0.0/LICENSE +21 -0
- peyeeye-1.0.0/PKG-INFO +280 -0
- peyeeye-1.0.0/README.md +246 -0
- peyeeye-1.0.0/peyeeye/__init__.py +41 -0
- peyeeye-1.0.0/peyeeye/client.py +529 -0
- peyeeye-1.0.0/peyeeye/errors.py +35 -0
- peyeeye-1.0.0/peyeeye/models.py +240 -0
- peyeeye-1.0.0/peyeeye/py.typed +0 -0
- peyeeye-1.0.0/pyproject.toml +87 -0
- peyeeye-1.0.0/tests/__init__.py +0 -0
- peyeeye-1.0.0/tests/conftest.py +104 -0
- peyeeye-1.0.0/tests/test_client_init.py +57 -0
- peyeeye-1.0.0/tests/test_entities.py +149 -0
- peyeeye-1.0.0/tests/test_errors_and_retries.py +159 -0
- peyeeye-1.0.0/tests/test_redact_rehydrate.py +140 -0
- peyeeye-1.0.0/tests/test_sessions.py +48 -0
- peyeeye-1.0.0/tests/test_shield.py +188 -0
- peyeeye-1.0.0/tests/test_streaming.py +86 -0
peyeeye-1.0.0/.gitignore
ADDED
peyeeye-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 peyeeye.ai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
peyeeye-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: peyeeye
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python client for peyeeye.ai — PII redaction & rehydration for LLM prompts.
|
|
5
|
+
Project-URL: Homepage, https://peyeeye.ai
|
|
6
|
+
Project-URL: Documentation, https://peyeeye.ai/docs
|
|
7
|
+
Author-email: "peyeeye.ai" <support@peyeeye.ai>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai,ai-safety,anonymization,anthropic,claude,compliance,data-masking,data-privacy,data-protection,gdpr,hipaa,llm,openai,peyeeye,pii,pii-detection,privacy,prompt-engineering,rag,redaction,rehydrate,security,sensitive-data,tokenization
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Security
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Requires-Dist: httpx>=0.27
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
32
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# peyeeye
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/peyeeye/)
|
|
38
|
+
[](https://pypi.org/project/peyeeye/)
|
|
39
|
+
[](https://opensource.org/licenses/MIT)
|
|
40
|
+
|
|
41
|
+
Official Python client for [**peyeeye.ai**](https://peyeeye.ai) — redact PII on
|
|
42
|
+
the way _into_ your LLM prompts and rehydrate it on the way out.
|
|
43
|
+
|
|
44
|
+
- **Homepage**: <https://peyeeye.ai>
|
|
45
|
+
- **API reference**: <https://peyeeye.ai/docs>
|
|
46
|
+
- **PyPI**: <https://pypi.org/project/peyeeye/>
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install peyeeye
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Python 3.9+. Single runtime dependency: `httpx`. Fully type-hinted (`py.typed`).
|
|
53
|
+
|
|
54
|
+
## Quickstart
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import os
|
|
58
|
+
from peyeeye import Peyeeye
|
|
59
|
+
from anthropic import Anthropic
|
|
60
|
+
|
|
61
|
+
peyeeye = Peyeeye(api_key=os.environ["PEYEEYE_KEY"])
|
|
62
|
+
claude = Anthropic()
|
|
63
|
+
|
|
64
|
+
with peyeeye.shield() as shield:
|
|
65
|
+
safe = shield.redact("Hi, I'm Ada, ada@a-e.com")
|
|
66
|
+
reply = claude.messages.create(
|
|
67
|
+
model="claude-sonnet-*",
|
|
68
|
+
max_tokens=256,
|
|
69
|
+
messages=[{"role": "user", "content": safe}],
|
|
70
|
+
)
|
|
71
|
+
print(shield.rehydrate(reply.content[0].text))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`shield()` opens a session, redacts, and cleans up on exit. Inside the block,
|
|
75
|
+
the same real value always maps to the same token — `Ada Lovelace` is always
|
|
76
|
+
`[PERSON_1]` — and tokens never leak across sessions.
|
|
77
|
+
|
|
78
|
+
## Low-level calls
|
|
79
|
+
|
|
80
|
+
Skip the `shield` helper when you need more control:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
r = peyeeye.redact("Card: 4242 4242 4242 4242")
|
|
84
|
+
# r.redacted → "Card: [CARD_1]"
|
|
85
|
+
# r.session → "ses_…"
|
|
86
|
+
# r.entities → [DetectedEntity(token="[CARD_1]", type="CARD", span=(6, 25), confidence=0.99)]
|
|
87
|
+
|
|
88
|
+
clean = peyeeye.rehydrate("Confirmation for [CARD_1].", session=r.session)
|
|
89
|
+
# clean.text → "Confirmation for 4242 4242 4242 4242."
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Stateless sealed mode
|
|
93
|
+
|
|
94
|
+
Pass `stateless=True` and peyeeye never stores the mapping — the redact
|
|
95
|
+
response carries a sealed `skey_…` blob you hand back to rehydrate. Nothing
|
|
96
|
+
lives on the server between calls.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
with peyeeye.shield(stateless=True) as shield:
|
|
100
|
+
safe = shield.redact("Email ada@a-e.com")
|
|
101
|
+
clean = shield.rehydrate("Reply: [EMAIL_1]")
|
|
102
|
+
# shield.rehydration_key is the skey_... blob, if you need to persist it
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Or with raw calls:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
r = peyeeye.redact("Email ada@a-e.com", session="stateless")
|
|
109
|
+
# r.rehydration_key → "skey_…"
|
|
110
|
+
clean = peyeeye.rehydrate("[EMAIL_1] received.", session=r.rehydration_key)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Streaming rehydration
|
|
114
|
+
|
|
115
|
+
When piping an LLM token stream straight to a user, naive rehydration breaks
|
|
116
|
+
on mid-token boundaries. `rehydrate_chunk()` buffers partial tokens across
|
|
117
|
+
chunks; call `flush()` once upstream closes.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
with peyeeye.shield() as shield:
|
|
121
|
+
safe = shield.redact(prompt)
|
|
122
|
+
for chunk in your_llm_stream(safe):
|
|
123
|
+
sys.stdout.write(shield.rehydrate_chunk(chunk))
|
|
124
|
+
sys.stdout.write(shield.flush())
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Never call `flush()` while the stream is still delivering chunks — you'll emit
|
|
128
|
+
a half-formed placeholder.
|
|
129
|
+
|
|
130
|
+
## Streaming redact (SSE)
|
|
131
|
+
|
|
132
|
+
For the `/v1/redact/stream` endpoint (Build plan and higher):
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
for event in peyeeye.redact_stream(["Hi, I'm Ada", " — card 4242 4242 4242 4242"]):
|
|
136
|
+
if event.event == "session":
|
|
137
|
+
session_id = event.data["session"]
|
|
138
|
+
elif event.event == "redacted":
|
|
139
|
+
print(event.data["text"])
|
|
140
|
+
elif event.event == "done":
|
|
141
|
+
print("chars:", event.data["chars"])
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Custom detectors
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
peyeeye.create_entity(
|
|
148
|
+
id="ORDER_ID",
|
|
149
|
+
kind="regex",
|
|
150
|
+
pattern=r"#A-\d{6,}",
|
|
151
|
+
examples=["#A-884217", "#A-007431"],
|
|
152
|
+
confidence_floor=0.9,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# dry-run a pattern before saving
|
|
156
|
+
peyeeye.test_pattern(pattern=r"#A-\d{6,}", text="ref #A-884217 and #A-1")
|
|
157
|
+
# → TestPatternResponse(count=1, matches=[PatternMatch(value="#A-884217", ...)])
|
|
158
|
+
|
|
159
|
+
# inspect / update / retire
|
|
160
|
+
peyeeye.list_entities()
|
|
161
|
+
peyeeye.update_entity("ORDER_ID", enabled=False)
|
|
162
|
+
peyeeye.delete_entity("ORDER_ID")
|
|
163
|
+
|
|
164
|
+
# starter templates (Twilio SIDs, Stripe keys, AWS access keys, etc.)
|
|
165
|
+
for tpl in peyeeye.entity_templates():
|
|
166
|
+
print(tpl.id, tpl.pattern)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Sessions
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
peyeeye.get_session("ses_…") # SessionInfo
|
|
173
|
+
peyeeye.delete_session("ses_…") # drop immediately
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Errors
|
|
177
|
+
|
|
178
|
+
Every non-2xx response raises `PeyeeyeError` with `.code`, `.status`,
|
|
179
|
+
`.message`, and `.request_id`. 429 and 5xx responses are retried with
|
|
180
|
+
exponential backoff (`Retry-After` honoured); terminal errors raise
|
|
181
|
+
immediately.
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from peyeeye import PeyeeyeError
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
peyeeye.redact("…")
|
|
188
|
+
except PeyeeyeError as e:
|
|
189
|
+
if e.code == "rate_limited":
|
|
190
|
+
...
|
|
191
|
+
elif e.code == "forbidden":
|
|
192
|
+
...
|
|
193
|
+
else:
|
|
194
|
+
raise
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Configuration
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
Peyeeye(
|
|
201
|
+
api_key="pk_live_…",
|
|
202
|
+
base_url="https://api.peyeeye.ai",
|
|
203
|
+
timeout=30.0,
|
|
204
|
+
max_retries=3,
|
|
205
|
+
)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
For CI / air-gapped use, `Peyeeye(transport=httpx.MockTransport(handler))`
|
|
209
|
+
lets you mount a mock transport without monkey-patching.
|
|
210
|
+
|
|
211
|
+
## Method reference
|
|
212
|
+
|
|
213
|
+
| Method | HTTP | Purpose |
|
|
214
|
+
| --- | --- | --- |
|
|
215
|
+
| `peyeeye.redact(text, ...)` | `POST /v1/redact` | Redact PII; returns token stream + session. |
|
|
216
|
+
| `peyeeye.rehydrate(text, session=...)` | `POST /v1/rehydrate` | Substitute tokens back. Accepts `ses_…` or `skey_…`. |
|
|
217
|
+
| `peyeeye.redact_stream(chunks, ...)` | `POST /v1/redact/stream` (SSE) | Stream-safe redact. |
|
|
218
|
+
| `peyeeye.get_session(id)` | `GET /v1/sessions/{id}` | Inspect mapping metadata. |
|
|
219
|
+
| `peyeeye.delete_session(id)` | `DELETE /v1/sessions/{id}` | Evict a session. |
|
|
220
|
+
| `peyeeye.list_entities()` | `GET /v1/entities` | Built-ins + your custom detectors. |
|
|
221
|
+
| `peyeeye.create_entity(...)` | `POST /v1/entities` | Custom detector. |
|
|
222
|
+
| `peyeeye.update_entity(id, ...)` | `PATCH /v1/entities/{id}` | Toggle / tweak. |
|
|
223
|
+
| `peyeeye.delete_entity(id)` | `DELETE /v1/entities/{id}` | Retire. |
|
|
224
|
+
| `peyeeye.test_pattern(pattern, text)` | `POST /v1/entities/test` | Dry-run a regex. |
|
|
225
|
+
| `peyeeye.entity_templates()` | `GET /v1/entities/templates` | Starter patterns. |
|
|
226
|
+
|
|
227
|
+
Full request / response schemas: <https://peyeeye.ai/docs>.
|
|
228
|
+
|
|
229
|
+
## Using this SDK from an AI coding assistant
|
|
230
|
+
|
|
231
|
+
Drop these into your agent's context. Each snippet is self-contained and
|
|
232
|
+
compiles as-is.
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
# Install
|
|
236
|
+
# pip install peyeeye
|
|
237
|
+
|
|
238
|
+
from peyeeye import Peyeeye, PeyeeyeError
|
|
239
|
+
import os
|
|
240
|
+
|
|
241
|
+
client = Peyeeye(api_key=os.environ["PEYEEYE_KEY"]) # or explicit base_url
|
|
242
|
+
|
|
243
|
+
# Round-trip: redact → call LLM → rehydrate (session-scoped)
|
|
244
|
+
with client.shield() as shield:
|
|
245
|
+
safe = shield.redact("Hi, I'm Ada, ada@a-e.com")
|
|
246
|
+
# ... send `safe` to the LLM, get `reply` back ...
|
|
247
|
+
out = shield.rehydrate(reply)
|
|
248
|
+
|
|
249
|
+
# Stateless (zero server-side state; key is yours to persist)
|
|
250
|
+
with client.shield(stateless=True) as shield:
|
|
251
|
+
safe = shield.redact("...")
|
|
252
|
+
key = shield.rehydration_key # skey_...
|
|
253
|
+
clean = shield.rehydrate("[EMAIL_1] confirmed.")
|
|
254
|
+
|
|
255
|
+
# Low-level one-shot
|
|
256
|
+
r = client.redact("Card 4242 4242 4242 4242")
|
|
257
|
+
clean = client.rehydrate("Receipt: [CARD_1].", session=r.session)
|
|
258
|
+
|
|
259
|
+
# Error handling
|
|
260
|
+
try:
|
|
261
|
+
client.redact(text)
|
|
262
|
+
except PeyeeyeError as e:
|
|
263
|
+
# e.code ∈ {"rate_limited","forbidden","invalid_request","server_error", ...}
|
|
264
|
+
# e.status, e.message, e.request_id
|
|
265
|
+
raise
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Endpoint envelope**: all requests use `Authorization: Bearer <api_key>` against
|
|
269
|
+
`https://api.peyeeye.ai/v1/*`. Errors follow `{code, message, request_id}`
|
|
270
|
+
and surface as `PeyeeyeError`. Responses are plain JSON (dataclasses via
|
|
271
|
+
`from_dict`).
|
|
272
|
+
|
|
273
|
+
**Do**: reuse one `Peyeeye(...)` per process; call `.close()` or use it as a
|
|
274
|
+
context manager at shutdown.
|
|
275
|
+
**Don't**: open a new client per request, call `flush()` mid-stream, or parse
|
|
276
|
+
`skey_` blobs yourself — the API opens them.
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT.
|
peyeeye-1.0.0/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# peyeeye
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/peyeeye/)
|
|
4
|
+
[](https://pypi.org/project/peyeeye/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
Official Python client for [**peyeeye.ai**](https://peyeeye.ai) — redact PII on
|
|
8
|
+
the way _into_ your LLM prompts and rehydrate it on the way out.
|
|
9
|
+
|
|
10
|
+
- **Homepage**: <https://peyeeye.ai>
|
|
11
|
+
- **API reference**: <https://peyeeye.ai/docs>
|
|
12
|
+
- **PyPI**: <https://pypi.org/project/peyeeye/>
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install peyeeye
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Python 3.9+. Single runtime dependency: `httpx`. Fully type-hinted (`py.typed`).
|
|
19
|
+
|
|
20
|
+
## Quickstart
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import os
|
|
24
|
+
from peyeeye import Peyeeye
|
|
25
|
+
from anthropic import Anthropic
|
|
26
|
+
|
|
27
|
+
peyeeye = Peyeeye(api_key=os.environ["PEYEEYE_KEY"])
|
|
28
|
+
claude = Anthropic()
|
|
29
|
+
|
|
30
|
+
with peyeeye.shield() as shield:
|
|
31
|
+
safe = shield.redact("Hi, I'm Ada, ada@a-e.com")
|
|
32
|
+
reply = claude.messages.create(
|
|
33
|
+
model="claude-sonnet-*",
|
|
34
|
+
max_tokens=256,
|
|
35
|
+
messages=[{"role": "user", "content": safe}],
|
|
36
|
+
)
|
|
37
|
+
print(shield.rehydrate(reply.content[0].text))
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`shield()` opens a session, redacts, and cleans up on exit. Inside the block,
|
|
41
|
+
the same real value always maps to the same token — `Ada Lovelace` is always
|
|
42
|
+
`[PERSON_1]` — and tokens never leak across sessions.
|
|
43
|
+
|
|
44
|
+
## Low-level calls
|
|
45
|
+
|
|
46
|
+
Skip the `shield` helper when you need more control:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
r = peyeeye.redact("Card: 4242 4242 4242 4242")
|
|
50
|
+
# r.redacted → "Card: [CARD_1]"
|
|
51
|
+
# r.session → "ses_…"
|
|
52
|
+
# r.entities → [DetectedEntity(token="[CARD_1]", type="CARD", span=(6, 25), confidence=0.99)]
|
|
53
|
+
|
|
54
|
+
clean = peyeeye.rehydrate("Confirmation for [CARD_1].", session=r.session)
|
|
55
|
+
# clean.text → "Confirmation for 4242 4242 4242 4242."
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Stateless sealed mode
|
|
59
|
+
|
|
60
|
+
Pass `stateless=True` and peyeeye never stores the mapping — the redact
|
|
61
|
+
response carries a sealed `skey_…` blob you hand back to rehydrate. Nothing
|
|
62
|
+
lives on the server between calls.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
with peyeeye.shield(stateless=True) as shield:
|
|
66
|
+
safe = shield.redact("Email ada@a-e.com")
|
|
67
|
+
clean = shield.rehydrate("Reply: [EMAIL_1]")
|
|
68
|
+
# shield.rehydration_key is the skey_... blob, if you need to persist it
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Or with raw calls:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
r = peyeeye.redact("Email ada@a-e.com", session="stateless")
|
|
75
|
+
# r.rehydration_key → "skey_…"
|
|
76
|
+
clean = peyeeye.rehydrate("[EMAIL_1] received.", session=r.rehydration_key)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Streaming rehydration
|
|
80
|
+
|
|
81
|
+
When piping an LLM token stream straight to a user, naive rehydration breaks
|
|
82
|
+
on mid-token boundaries. `rehydrate_chunk()` buffers partial tokens across
|
|
83
|
+
chunks; call `flush()` once upstream closes.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
with peyeeye.shield() as shield:
|
|
87
|
+
safe = shield.redact(prompt)
|
|
88
|
+
for chunk in your_llm_stream(safe):
|
|
89
|
+
sys.stdout.write(shield.rehydrate_chunk(chunk))
|
|
90
|
+
sys.stdout.write(shield.flush())
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Never call `flush()` while the stream is still delivering chunks — you'll emit
|
|
94
|
+
a half-formed placeholder.
|
|
95
|
+
|
|
96
|
+
## Streaming redact (SSE)
|
|
97
|
+
|
|
98
|
+
For the `/v1/redact/stream` endpoint (Build plan and higher):
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
for event in peyeeye.redact_stream(["Hi, I'm Ada", " — card 4242 4242 4242 4242"]):
|
|
102
|
+
if event.event == "session":
|
|
103
|
+
session_id = event.data["session"]
|
|
104
|
+
elif event.event == "redacted":
|
|
105
|
+
print(event.data["text"])
|
|
106
|
+
elif event.event == "done":
|
|
107
|
+
print("chars:", event.data["chars"])
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Custom detectors
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
peyeeye.create_entity(
|
|
114
|
+
id="ORDER_ID",
|
|
115
|
+
kind="regex",
|
|
116
|
+
pattern=r"#A-\d{6,}",
|
|
117
|
+
examples=["#A-884217", "#A-007431"],
|
|
118
|
+
confidence_floor=0.9,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# dry-run a pattern before saving
|
|
122
|
+
peyeeye.test_pattern(pattern=r"#A-\d{6,}", text="ref #A-884217 and #A-1")
|
|
123
|
+
# → TestPatternResponse(count=1, matches=[PatternMatch(value="#A-884217", ...)])
|
|
124
|
+
|
|
125
|
+
# inspect / update / retire
|
|
126
|
+
peyeeye.list_entities()
|
|
127
|
+
peyeeye.update_entity("ORDER_ID", enabled=False)
|
|
128
|
+
peyeeye.delete_entity("ORDER_ID")
|
|
129
|
+
|
|
130
|
+
# starter templates (Twilio SIDs, Stripe keys, AWS access keys, etc.)
|
|
131
|
+
for tpl in peyeeye.entity_templates():
|
|
132
|
+
print(tpl.id, tpl.pattern)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Sessions
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
peyeeye.get_session("ses_…") # SessionInfo
|
|
139
|
+
peyeeye.delete_session("ses_…") # drop immediately
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Errors
|
|
143
|
+
|
|
144
|
+
Every non-2xx response raises `PeyeeyeError` with `.code`, `.status`,
|
|
145
|
+
`.message`, and `.request_id`. 429 and 5xx responses are retried with
|
|
146
|
+
exponential backoff (`Retry-After` honoured); terminal errors raise
|
|
147
|
+
immediately.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from peyeeye import PeyeeyeError
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
peyeeye.redact("…")
|
|
154
|
+
except PeyeeyeError as e:
|
|
155
|
+
if e.code == "rate_limited":
|
|
156
|
+
...
|
|
157
|
+
elif e.code == "forbidden":
|
|
158
|
+
...
|
|
159
|
+
else:
|
|
160
|
+
raise
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
Peyeeye(
|
|
167
|
+
api_key="pk_live_…",
|
|
168
|
+
base_url="https://api.peyeeye.ai",
|
|
169
|
+
timeout=30.0,
|
|
170
|
+
max_retries=3,
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
For CI / air-gapped use, `Peyeeye(transport=httpx.MockTransport(handler))`
|
|
175
|
+
lets you mount a mock transport without monkey-patching.
|
|
176
|
+
|
|
177
|
+
## Method reference
|
|
178
|
+
|
|
179
|
+
| Method | HTTP | Purpose |
|
|
180
|
+
| --- | --- | --- |
|
|
181
|
+
| `peyeeye.redact(text, ...)` | `POST /v1/redact` | Redact PII; returns token stream + session. |
|
|
182
|
+
| `peyeeye.rehydrate(text, session=...)` | `POST /v1/rehydrate` | Substitute tokens back. Accepts `ses_…` or `skey_…`. |
|
|
183
|
+
| `peyeeye.redact_stream(chunks, ...)` | `POST /v1/redact/stream` (SSE) | Stream-safe redact. |
|
|
184
|
+
| `peyeeye.get_session(id)` | `GET /v1/sessions/{id}` | Inspect mapping metadata. |
|
|
185
|
+
| `peyeeye.delete_session(id)` | `DELETE /v1/sessions/{id}` | Evict a session. |
|
|
186
|
+
| `peyeeye.list_entities()` | `GET /v1/entities` | Built-ins + your custom detectors. |
|
|
187
|
+
| `peyeeye.create_entity(...)` | `POST /v1/entities` | Custom detector. |
|
|
188
|
+
| `peyeeye.update_entity(id, ...)` | `PATCH /v1/entities/{id}` | Toggle / tweak. |
|
|
189
|
+
| `peyeeye.delete_entity(id)` | `DELETE /v1/entities/{id}` | Retire. |
|
|
190
|
+
| `peyeeye.test_pattern(pattern, text)` | `POST /v1/entities/test` | Dry-run a regex. |
|
|
191
|
+
| `peyeeye.entity_templates()` | `GET /v1/entities/templates` | Starter patterns. |
|
|
192
|
+
|
|
193
|
+
Full request / response schemas: <https://peyeeye.ai/docs>.
|
|
194
|
+
|
|
195
|
+
## Using this SDK from an AI coding assistant
|
|
196
|
+
|
|
197
|
+
Drop these into your agent's context. Each snippet is self-contained and
|
|
198
|
+
compiles as-is.
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
# Install
|
|
202
|
+
# pip install peyeeye
|
|
203
|
+
|
|
204
|
+
from peyeeye import Peyeeye, PeyeeyeError
|
|
205
|
+
import os
|
|
206
|
+
|
|
207
|
+
client = Peyeeye(api_key=os.environ["PEYEEYE_KEY"]) # or explicit base_url
|
|
208
|
+
|
|
209
|
+
# Round-trip: redact → call LLM → rehydrate (session-scoped)
|
|
210
|
+
with client.shield() as shield:
|
|
211
|
+
safe = shield.redact("Hi, I'm Ada, ada@a-e.com")
|
|
212
|
+
# ... send `safe` to the LLM, get `reply` back ...
|
|
213
|
+
out = shield.rehydrate(reply)
|
|
214
|
+
|
|
215
|
+
# Stateless (zero server-side state; key is yours to persist)
|
|
216
|
+
with client.shield(stateless=True) as shield:
|
|
217
|
+
safe = shield.redact("...")
|
|
218
|
+
key = shield.rehydration_key # skey_...
|
|
219
|
+
clean = shield.rehydrate("[EMAIL_1] confirmed.")
|
|
220
|
+
|
|
221
|
+
# Low-level one-shot
|
|
222
|
+
r = client.redact("Card 4242 4242 4242 4242")
|
|
223
|
+
clean = client.rehydrate("Receipt: [CARD_1].", session=r.session)
|
|
224
|
+
|
|
225
|
+
# Error handling
|
|
226
|
+
try:
|
|
227
|
+
client.redact(text)
|
|
228
|
+
except PeyeeyeError as e:
|
|
229
|
+
# e.code ∈ {"rate_limited","forbidden","invalid_request","server_error", ...}
|
|
230
|
+
# e.status, e.message, e.request_id
|
|
231
|
+
raise
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Endpoint envelope**: all requests use `Authorization: Bearer <api_key>` against
|
|
235
|
+
`https://api.peyeeye.ai/v1/*`. Errors follow `{code, message, request_id}`
|
|
236
|
+
and surface as `PeyeeyeError`. Responses are plain JSON (dataclasses via
|
|
237
|
+
`from_dict`).
|
|
238
|
+
|
|
239
|
+
**Do**: reuse one `Peyeeye(...)` per process; call `.close()` or use it as a
|
|
240
|
+
context manager at shutdown.
|
|
241
|
+
**Don't**: open a new client per request, call `flush()` mid-stream, or parse
|
|
242
|
+
`skey_` blobs yourself — the API opens them.
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""peyeeye — PII redaction & rehydration client.
|
|
2
|
+
|
|
3
|
+
from peyeeye import Peyeeye
|
|
4
|
+
|
|
5
|
+
pe = Peyeeye(api_key="pk_live_...")
|
|
6
|
+
with pe.shield() as shield:
|
|
7
|
+
safe = shield.redact("Hi, I'm Ada, ada@a-e.com")
|
|
8
|
+
reply = call_your_model(safe)
|
|
9
|
+
print(shield.rehydrate(reply))
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .client import Peyeeye, Shield
|
|
13
|
+
from .errors import PeyeeyeError
|
|
14
|
+
from .models import (
|
|
15
|
+
CustomDetector,
|
|
16
|
+
DetectedEntity,
|
|
17
|
+
EntitiesList,
|
|
18
|
+
EntityTemplate,
|
|
19
|
+
RedactResponse,
|
|
20
|
+
RehydrateResponse,
|
|
21
|
+
SessionInfo,
|
|
22
|
+
StreamEvent,
|
|
23
|
+
TestPatternResponse,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"Peyeeye",
|
|
28
|
+
"PeyeeyeError",
|
|
29
|
+
"Shield",
|
|
30
|
+
"DetectedEntity",
|
|
31
|
+
"RedactResponse",
|
|
32
|
+
"RehydrateResponse",
|
|
33
|
+
"SessionInfo",
|
|
34
|
+
"CustomDetector",
|
|
35
|
+
"EntitiesList",
|
|
36
|
+
"EntityTemplate",
|
|
37
|
+
"TestPatternResponse",
|
|
38
|
+
"StreamEvent",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
__version__ = "1.0.0"
|