raindrop-ai 0.0.23__py3-none-any.whl → 0.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.
raindrop/analytics.py CHANGED
@@ -13,6 +13,7 @@ from threading import Timer
13
13
  from raindrop.version import VERSION
14
14
  from raindrop.models import TrackAIEvent, Attachment, SignalEvent, DefaultSignal, FeedbackSignal, EditSignal, PartialTrackAIEvent, PartialAIData
15
15
  from raindrop.interaction import Interaction
16
+ from raindrop.redact import perform_pii_redaction
16
17
 
17
18
 
18
19
 
@@ -28,6 +29,7 @@ upload_interval = 1.0
28
29
  buffer = []
29
30
  flush_lock = threading.Lock()
30
31
  debug_logs = False
32
+ redact_pii = False
31
33
  flush_thread = None
32
34
  shutdown_event = threading.Event()
33
35
  max_ingest_size_bytes = 1 * 1024 * 1024 # 1 MB
@@ -44,6 +46,14 @@ def set_debug_logs(value: bool):
44
46
  else:
45
47
  logger.setLevel(logging.INFO)
46
48
 
49
+ def set_redact_pii(value: bool):
50
+ global redact_pii
51
+ redact_pii = value
52
+ if redact_pii:
53
+ logger.info("PII redaction enabled")
54
+ else:
55
+ logger.info("PII redaction disabled")
56
+
47
57
  def start_flush_thread():
48
58
  logger.debug("Opening flush thread")
49
59
  global flush_thread
@@ -177,6 +187,10 @@ def track_ai(
177
187
  payload.properties["$context"] = _get_context()
178
188
 
179
189
  data = payload.model_dump(mode="json")
190
+
191
+ # Apply PII redaction if enabled
192
+ if redact_pii:
193
+ data = perform_pii_redaction(data)
180
194
 
181
195
  size = _get_size(data)
182
196
  if size > max_ingest_size_bytes:
@@ -399,6 +413,11 @@ def _flush_partial_event(event_id: str) -> None:
399
413
 
400
414
  # convert to ordinary TrackAIEvent-ish dict before send
401
415
  data = evt.model_dump(mode="json", exclude_none=True)
416
+
417
+ # Apply PII redaction if enabled
418
+ if redact_pii:
419
+ data = perform_pii_redaction(data)
420
+
402
421
  size = _get_size(data)
403
422
  if size > max_ingest_size_bytes:
404
423
  logger.warning(f"[raindrop] partial event {event_id} > 1 MB; skipping")
raindrop/redact.py ADDED
@@ -0,0 +1,134 @@
1
+ import re
2
+ import json
3
+ import os
4
+ from typing import Dict, Any
5
+
6
+
7
+ class PIIRedactor:
8
+ """PII redactor that uses regex patterns to identify and replace PII."""
9
+
10
+ def __init__(self):
11
+
12
+ # Build regex patterns
13
+ self._build_patterns()
14
+
15
+ def _build_patterns(self):
16
+ """Build regex patterns for PII detection."""
17
+ # Email pattern
18
+ self.email_pattern = re.compile(
19
+ r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
20
+ re.IGNORECASE
21
+ )
22
+
23
+ # Credit card pattern (basic - matches 13-19 digits with optional spaces/dashes)
24
+ self.credit_card_pattern = re.compile(
25
+ r'\b(?:\d[ -]*?){13,19}\b'
26
+ )
27
+
28
+ # Phone number pattern (US-style, but flexible)
29
+ self.phone_pattern = re.compile(
30
+ r'(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b'
31
+ )
32
+
33
+ # SSN pattern
34
+ self.ssn_pattern = re.compile(
35
+ r'\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b'
36
+ )
37
+
38
+ # Password/secret pattern (matches patterns like "password: xxx" or "secret: xxx")
39
+ self.password_pattern = re.compile(
40
+ r'\b(pass(word|phrase)?|secret|pwd|passwd)\s*[:=]\s*\S+',
41
+ re.IGNORECASE
42
+ )
43
+
44
+ # Street address pattern (simplified)
45
+ self.address_pattern = re.compile(
46
+ r'\b\d+\s+[A-Za-z\s]+\s+(street|st|avenue|ave|road|rd|boulevard|blvd|lane|ln|drive|dr|court|ct|plaza|pl|terrace|ter|place|pl|way|parkway|pkwy)\b',
47
+ re.IGNORECASE
48
+ )
49
+
50
+ # Greeting/closing patterns for name detection
51
+ self.greeting_pattern = re.compile(
52
+ r'(^|\.\s+)(dear|hi|hello|greetings|hey|hey there)[\s-]*',
53
+ re.IGNORECASE
54
+ )
55
+
56
+ self.closing_pattern = re.compile(
57
+ r'(thx|thanks|thank you|regards|best|[a-z]+ly|[a-z]+ regards|all the best|happy [a-z]+ing|take care|have a [a-z]+ (weekend|night|day))\s*[,.!]*',
58
+ re.IGNORECASE
59
+ )
60
+
61
+ # Generic name pattern (capitalized words)
62
+ self.generic_name_pattern = re.compile(
63
+ r'( ?(([A-Z][a-z]+)|([A-Z]\.))+)([,.]|[,.]?$)',
64
+ re.MULTILINE
65
+ )
66
+
67
+
68
+ # Credentials pattern (API keys, tokens, etc.)
69
+ self.credentials_pattern = re.compile(
70
+ r'\b(api[_-]?key|token|bearer|authorization|auth[_-]?token|access[_-]?token|secret[_-]?key)\s*[:=]\s*["\']?[\w-]+["\']?',
71
+ re.IGNORECASE
72
+ )
73
+
74
+ def redact(self, text: str) -> str:
75
+ """Redact PII from the given text using regex patterns."""
76
+ if not isinstance(text, str):
77
+ return text
78
+
79
+ # Apply redactions in order
80
+ # Credentials
81
+ text = self.credentials_pattern.sub('<REDACTED_CREDENTIALS>', text)
82
+
83
+ # Credit card numbers
84
+ text = self.credit_card_pattern.sub('<REDACTED_CREDIT_CARD>', text)
85
+
86
+ # Email addresses
87
+ text = self.email_pattern.sub('<REDACTED_EMAIL>', text)
88
+
89
+ # Phone numbers
90
+ text = self.phone_pattern.sub('<REDACTED_PHONE>', text)
91
+
92
+ # SSN
93
+ text = self.ssn_pattern.sub('<REDACTED_SSN>', text)
94
+
95
+ # Passwords/secrets
96
+ text = self.password_pattern.sub('<REDACTED_SECRET>', text)
97
+
98
+ # Street addresses
99
+ text = self.address_pattern.sub('<REDACTED_ADDRESS>', text)
100
+
101
+ # Note: IPs, URLs, usernames, and zipcodes are disabled by default
102
+ # to match JS SDK behavior
103
+
104
+ return text
105
+
106
+
107
+ def perform_pii_redaction(event_data: Dict[str, Any]) -> Dict[str, Any]:
108
+ """
109
+ Redact PII from event data, specifically targeting ai_data input and output fields.
110
+
111
+ Args:
112
+ event_data: The event data dictionary to redact PII from
113
+
114
+ Returns:
115
+ The event data with PII redacted
116
+ """
117
+ redactor = PIIRedactor()
118
+
119
+ # Create a copy to avoid modifying the original
120
+ event_copy = event_data.copy()
121
+
122
+ # Redact PII from ai_data fields if they exist
123
+ if 'ai_data' in event_copy and event_copy['ai_data']:
124
+ ai_data = event_copy['ai_data'].copy() if event_copy['ai_data'] else {}
125
+
126
+ if 'input' in ai_data and ai_data['input']:
127
+ ai_data['input'] = redactor.redact(ai_data['input'])
128
+
129
+ if 'output' in ai_data and ai_data['output']:
130
+ ai_data['output'] = redactor.redact(ai_data['output'])
131
+
132
+ event_copy['ai_data'] = ai_data
133
+
134
+ return event_copy
raindrop/version.py CHANGED
@@ -1 +1 @@
1
- VERSION = "0.0.19"
1
+ VERSION = "0.0.25"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: raindrop-ai
3
- Version: 0.0.23
3
+ Version: 0.0.25
4
4
  Summary: Raindrop AI (Python SDK)
5
5
  License: MIT
6
6
  Author: Raindrop AI
@@ -10,7 +10,7 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
- Requires-Dist: pydantic (>=2.11,<3)
13
+ Requires-Dist: pydantic (>=2.09,<3)
14
14
  Requires-Dist: requests (>=2.32.3,<3.0.0)
15
15
  Description-Content-Type: text/markdown
16
16
 
@@ -0,0 +1,9 @@
1
+ raindrop/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ raindrop/analytics.py,sha256=XlwCh0n9zuktHCbEjqCWpci5juZvHPAtV-M56jrfxac,14153
3
+ raindrop/interaction.py,sha256=U8OapN5tZGSMVkn-dTLum5sIg_AgSYKX9lrtwgXJOuI,1613
4
+ raindrop/models.py,sha256=8YKuyB34P7sfawPFL3jOt5RgYlNXzf9dEsj8_2rjZKU,5293
5
+ raindrop/redact.py,sha256=9AkNQGT61NrRStFH0zu0Lj4PGB2ATSN2rRgQnX_fQjQ,4552
6
+ raindrop/version.py,sha256=KLRDXPxiLFqIKIkRg3nvV0u3CDx1KKih1aBFxpsuTSE,18
7
+ raindrop_ai-0.0.25.dist-info/METADATA,sha256=2_XMJLXqec95On6OFCvgmKYY_8b3RsDWhHGLf3KYkxg,774
8
+ raindrop_ai-0.0.25.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
9
+ raindrop_ai-0.0.25.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- raindrop/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- raindrop/analytics.py,sha256=kAAzxrQqRYKCVG7bk2wQrdVrpca5t2sJV-RMr5b08H0,13672
3
- raindrop/interaction.py,sha256=U8OapN5tZGSMVkn-dTLum5sIg_AgSYKX9lrtwgXJOuI,1613
4
- raindrop/models.py,sha256=8YKuyB34P7sfawPFL3jOt5RgYlNXzf9dEsj8_2rjZKU,5293
5
- raindrop/version.py,sha256=zKIDJIJluqVEGWC_VXIxFo3Hrk5Q7UGBBqP5uigDAN8,18
6
- raindrop_ai-0.0.23.dist-info/METADATA,sha256=l2QSZBwDesa-ANhaXXZVeexCBUU4f94JL6kePrVBdzw,774
7
- raindrop_ai-0.0.23.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
8
- raindrop_ai-0.0.23.dist-info/RECORD,,