xecurecode-reliability-sdk 0.1.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.
@@ -0,0 +1,17 @@
1
+ """
2
+ XecureCode Reliability SDK for Python
3
+ """
4
+
5
+ from .config import ReliabilityConfig
6
+ from .client import ReliabilityClient, init, get_client
7
+ from .payload import create_error_payload
8
+
9
+ __version__ = "0.1.1"
10
+
11
+ __all__ = [
12
+ "ReliabilityConfig",
13
+ "ReliabilityClient",
14
+ "create_error_payload",
15
+ "init",
16
+ "get_client",
17
+ ]
reliability/client.py ADDED
@@ -0,0 +1,265 @@
1
+ """
2
+ Reliability Client for Python SDK
3
+ """
4
+
5
+ import asyncio
6
+ import json
7
+ import logging
8
+ import sys
9
+ import threading
10
+ import time
11
+ from concurrent.futures import ThreadPoolExecutor
12
+ from typing import Any, Dict, Optional
13
+ from urllib.request import Request, urlopen
14
+
15
+ from .config import ReliabilityConfig
16
+ from .payload import create_error_payload
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ DEFAULT_ENDPOINT = "https://api.xecurecode.in/api/v1/ingest"
21
+
22
+ DEDUP_WINDOW_MS = 60000
23
+ MIN_INTERVAL_MS = 100
24
+ MAX_PENDING = 100
25
+
26
+
27
+ class ReliabilityClient:
28
+ """Main reliability client for capturing and sending errors"""
29
+
30
+ def __init__(self, config: ReliabilityConfig):
31
+ self.config = config
32
+ self._executor = ThreadPoolExecutor(
33
+ max_workers=1, thread_name_prefix="reliability-sender"
34
+ )
35
+ self._shutdown_flag = False
36
+
37
+ # Rate limiting
38
+ self._last_sent_time = 0
39
+ self._pending_sends = 0
40
+ self._lock = threading.Lock()
41
+
42
+ # Deduplication cache
43
+ self._error_cache: Dict[str, float] = {}
44
+ self._error_counts: Dict[str, int] = {}
45
+ self._cache_cleanup_interval = 60
46
+ self._cleanup_stop = threading.Event()
47
+ self._cleanup_thread: Optional[threading.Thread] = None
48
+ self._start_cache_cleanup()
49
+
50
+ # Global exception handlers
51
+ self._setup_global_handlers()
52
+
53
+ logger.info(f"ReliabilityClient initialized for service: {config.service_id}")
54
+
55
+ def _setup_global_handlers(self):
56
+ """Setup global exception handlers"""
57
+ self._original_excepthook = sys.excepthook
58
+ self._original_thread_excepthook = threading.excepthook
59
+
60
+ sys.excepthook = self._custom_excepthook
61
+ threading.excepthook = self._custom_thread_excepthook
62
+
63
+ def _custom_excepthook(self, exc_type, exc_value, exc_traceback):
64
+ """Custom uncaught exception handler"""
65
+ try:
66
+ if issubclass(exc_type, Exception):
67
+ self.capture(exc_value)
68
+ except Exception:
69
+ pass
70
+ if self._original_excepthook:
71
+ self._original_excepthook(exc_type, exc_value, exc_traceback)
72
+
73
+ def _custom_thread_excepthook(self, args):
74
+ """Custom thread exception handler"""
75
+ try:
76
+ if args.exc_type and issubclass(args.exc_type, Exception):
77
+ self.capture(args.exc_value)
78
+ except Exception:
79
+ pass
80
+ if self._original_thread_excepthook:
81
+ self._original_thread_excepthook(args)
82
+
83
+ def _start_cache_cleanup(self):
84
+ """Start background cache cleanup"""
85
+
86
+ def cleanup():
87
+ while not self._cleanup_stop.wait(self._cache_cleanup_interval):
88
+ self._cleanup_cache()
89
+
90
+ self._cleanup_thread = threading.Thread(
91
+ target=cleanup,
92
+ name="reliability-cache-cleanup",
93
+ daemon=True,
94
+ )
95
+ self._cleanup_thread.start()
96
+
97
+ def _cleanup_cache(self):
98
+ """Remove old entries from cache"""
99
+ now = time.time() * 1000
100
+ expired_keys = [
101
+ k for k, v in self._error_cache.items() if now - v > DEDUP_WINDOW_MS
102
+ ]
103
+ for k in expired_keys:
104
+ self._error_cache.pop(k, None)
105
+ self._error_counts.pop(k, None)
106
+
107
+ def _record_fingerprint(self, fingerprint: str) -> int:
108
+ """Record a fingerprint occurrence and return the accumulated count."""
109
+ now = time.time() * 1000
110
+ last_time = self._error_cache.get(fingerprint)
111
+ current_count = self._error_counts.get(fingerprint, 0)
112
+
113
+ if last_time is None:
114
+ self._error_cache[fingerprint] = now
115
+ self._error_counts[fingerprint] = 1
116
+ return 1
117
+
118
+ if now - last_time > DEDUP_WINDOW_MS:
119
+ self._error_cache[fingerprint] = now
120
+ self._error_counts[fingerprint] = 1
121
+ return 1
122
+
123
+ self._error_cache[fingerprint] = now
124
+ current_count += 1
125
+ self._error_counts[fingerprint] = current_count
126
+ return current_count
127
+
128
+ def _can_send(self) -> bool:
129
+ """Check if we can send (rate limiting)"""
130
+ now = time.time() * 1000
131
+ with self._lock:
132
+ if now - self._last_sent_time < MIN_INTERVAL_MS:
133
+ return False
134
+ if self._pending_sends >= MAX_PENDING:
135
+ return False
136
+ return True
137
+
138
+ def capture(self, error: Exception, request: Any = None):
139
+ """Capture an error - never throws"""
140
+ try:
141
+ if error is None:
142
+ return
143
+ if self._shutdown_flag:
144
+ logger.debug("Client shut down, dropping event")
145
+ return
146
+
147
+ payload = create_error_payload(
148
+ error,
149
+ self.config.service_id,
150
+ self.config.mode,
151
+ request,
152
+ version=self.config.version,
153
+ release=self.config.release,
154
+ commit_hash=self.config.commit_hash,
155
+ branch=self.config.branch,
156
+ build_id=self.config.build_id,
157
+ deployment_id=self.config.deployment_id,
158
+ )
159
+
160
+ occurrence_count = self._record_fingerprint(payload["fingerprint"])
161
+
162
+ if not self._can_send():
163
+ logger.debug(f"Rate limited, skipping: {error}")
164
+ return
165
+
166
+ with self._lock:
167
+ self._pending_sends += 1
168
+ self._last_sent_time = time.time() * 1000
169
+
170
+ payload["occurrenceCount"] = occurrence_count
171
+
172
+ try:
173
+ self._executor.submit(self._send_to_backend, payload)
174
+ except RuntimeError:
175
+ with self._lock:
176
+ self._pending_sends -= 1
177
+ logger.debug("Executor unavailable, dropping event")
178
+ except Exception as e:
179
+ logger.exception(f"Reliability SDK internal error in capture(): {e}")
180
+
181
+ def _send_to_backend(self, payload: Dict[str, Any]):
182
+ """Send payload to backend with retry"""
183
+ max_retries = 3
184
+ retry_delay = 1
185
+ try:
186
+ for attempt in range(max_retries):
187
+ try:
188
+ self._do_send(payload)
189
+ logger.debug(f"Error sent successfully: {payload['message']}")
190
+ break
191
+ except Exception as e:
192
+ logger.warning(f"Failed to send error (attempt {attempt + 1}): {e}")
193
+ if attempt < max_retries - 1:
194
+ time.sleep(retry_delay * (attempt + 1))
195
+ finally:
196
+ with self._lock:
197
+ self._pending_sends -= 1
198
+
199
+ def _do_send(self, payload: Dict[str, Any]):
200
+ """Perform actual HTTP request"""
201
+ data = json.dumps(payload).encode("utf-8")
202
+ endpoint = self.config.endpoint or DEFAULT_ENDPOINT
203
+
204
+ request = Request(
205
+ endpoint,
206
+ data=data,
207
+ headers={
208
+ "Content-Type": "application/json",
209
+ "xc-api-key": self.config.api_key,
210
+ "xc-service-id": self.config.service_id,
211
+ },
212
+ method="POST",
213
+ )
214
+
215
+ with urlopen(request, timeout=self.config.timeout / 1000) as response:
216
+ if response.status >= 400:
217
+ raise Exception(f"HTTP {response.status}")
218
+
219
+ def middleware(self):
220
+ """Create middleware for frameworks"""
221
+ return ReliabilityMiddleware(self)
222
+
223
+ async def flush(self):
224
+ """Flush pending sends"""
225
+ while self._pending_sends > 0:
226
+ await asyncio.sleep(0.1)
227
+
228
+ def shutdown(self):
229
+ """Shutdown the client"""
230
+ self._shutdown_flag = True
231
+ if getattr(self, "_original_excepthook", None):
232
+ sys.excepthook = self._original_excepthook
233
+ if getattr(self, "_original_thread_excepthook", None):
234
+ threading.excepthook = self._original_thread_excepthook
235
+ self._cleanup_stop.set()
236
+ self._executor.shutdown(wait=True)
237
+
238
+
239
+ class ReliabilityMiddleware:
240
+ """Middleware for FastAPI/Flask"""
241
+
242
+ def __init__(self, client: ReliabilityClient):
243
+ self.client = client
244
+
245
+ async def __call__(self, request, call_next):
246
+ """Process request"""
247
+ try:
248
+ response = await call_next(request)
249
+ return response
250
+ except Exception as e:
251
+ self.client.capture(e, request)
252
+ raise
253
+
254
+
255
+ def get_client() -> ReliabilityClient:
256
+ """Get global client instance"""
257
+ if not hasattr(get_client, "_instance"):
258
+ raise RuntimeError("ReliabilityClient not initialized. Call init() first.")
259
+ return get_client._instance
260
+
261
+
262
+ def init(config: ReliabilityConfig) -> ReliabilityClient:
263
+ """Initialize global client"""
264
+ get_client._instance = ReliabilityClient(config)
265
+ return get_client._instance
reliability/config.py ADDED
@@ -0,0 +1,33 @@
1
+ """
2
+ Reliability Config for Python SDK
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Literal, Optional
7
+
8
+ Mode = Literal["development", "production"]
9
+
10
+
11
+ @dataclass
12
+ class ReliabilityConfig:
13
+ api_key: str
14
+ service_id: str
15
+ mode: Mode = "development"
16
+ timeout: int = 5000
17
+ endpoint: Optional[str] = None
18
+ version: Optional[str] = None
19
+ release: Optional[str] = None
20
+ commit_hash: Optional[str] = None
21
+ branch: Optional[str] = None
22
+ build_id: Optional[str] = None
23
+ deployment_id: Optional[str] = None
24
+
25
+ def __post_init__(self):
26
+ if not self.api_key:
27
+ raise ValueError("API Key is required")
28
+ if not self.service_id:
29
+ raise ValueError("Service ID is required")
30
+ if self.mode not in ["development", "production"]:
31
+ raise ValueError("Mode must be 'development' or 'production'")
32
+ if self.timeout <= 0:
33
+ raise ValueError("Timeout must be greater than 0")
@@ -0,0 +1,66 @@
1
+ """
2
+ Django Integration
3
+ """
4
+
5
+ import logging
6
+ import sys
7
+ from typing import Optional
8
+
9
+ from django.utils.deprecation import MiddlewareMixin
10
+
11
+ from .client import ReliabilityClient
12
+ from .config import ReliabilityConfig
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ _client: Optional[ReliabilityClient] = None
17
+
18
+
19
+ def get_client() -> Optional[ReliabilityClient]:
20
+ """Get or create Django client"""
21
+ global _client
22
+ if _client is None:
23
+ from django.conf import settings
24
+
25
+ api_key = getattr(settings, "RELIABILITY_API_KEY", None)
26
+ service_id = getattr(settings, "RELIABILITY_SERVICE_ID", None)
27
+ mode = getattr(settings, "RELIABILITY_MODE", "development")
28
+
29
+ if not api_key or not service_id:
30
+ logger.warning(
31
+ "Reliability SDK not configured. Add API_KEY and SERVICE_ID to settings."
32
+ )
33
+ return None
34
+
35
+ config = ReliabilityConfig(api_key=api_key, service_id=service_id, mode=mode)
36
+ _client = ReliabilityClient(config)
37
+
38
+ return _client
39
+
40
+
41
+ class ReliabilityMiddleware(MiddlewareMixin):
42
+ """Django middleware for error capture"""
43
+
44
+ def process_exception(self, request, exception):
45
+ """Capture exceptions"""
46
+ client = get_client()
47
+ if client:
48
+ client.capture(exception, request)
49
+ return None
50
+
51
+
52
+ # Django signals for automatic capture
53
+ def setup_django_signals():
54
+ """Setup Django signals for automatic error capture"""
55
+ try:
56
+ from django.core.signals import got_request_exception
57
+ from django.dispatch import receiver
58
+
59
+ @receiver(got_request_exception)
60
+ def capture_exception(sender, request=None, **kwargs):
61
+ client = get_client()
62
+ exception = sys.exc_info()[1]
63
+ if client and exception:
64
+ client.capture(exception, request)
65
+ except ImportError:
66
+ pass
@@ -0,0 +1,38 @@
1
+ """
2
+ FastAPI Integration
3
+ """
4
+
5
+ from fastapi import FastAPI, Request, Response
6
+ from starlette.middleware.base import BaseHTTPMiddleware
7
+ from typing import Callable, Type
8
+
9
+ from .client import ReliabilityClient
10
+
11
+
12
+ class FastAPIMiddleware(BaseHTTPMiddleware):
13
+ """FastAPI middleware for error capture"""
14
+
15
+ def __init__(self, app: FastAPI, client: ReliabilityClient):
16
+ super().__init__(app)
17
+ self.client = client
18
+
19
+ async def dispatch(self, request: Request, call_next: Callable) -> Response:
20
+ try:
21
+ response = await call_next(request)
22
+ return response
23
+ except Exception as e:
24
+ self.client.capture(e, request)
25
+ raise
26
+
27
+
28
+ def setup_fastapi(app: FastAPI, client: ReliabilityClient):
29
+ """Setup FastAPI integration"""
30
+ app.add_middleware(FastAPIMiddleware, client=client)
31
+
32
+
33
+ def create_fastapi_middleware(client: ReliabilityClient) -> Type[FastAPIMiddleware]:
34
+ """Create a configured FastAPI middleware class for manual use with add_middleware"""
35
+ class _Configured(FastAPIMiddleware):
36
+ def __init__(self, app: FastAPI):
37
+ super().__init__(app, client)
38
+ return _Configured
@@ -0,0 +1,56 @@
1
+ """
2
+ Flask Integration
3
+ """
4
+
5
+ from flask import Flask, Response
6
+ from typing import Callable
7
+
8
+ from .client import ReliabilityClient
9
+
10
+
11
+ class FlaskMiddleware:
12
+ """Flask middleware for error capture"""
13
+
14
+ def __init__(self, app: Callable, client: ReliabilityClient):
15
+ self.app = app
16
+ self.client = client
17
+
18
+ def __call__(self, environ: dict, start_response: Callable) -> Response:
19
+ # Wrap start_response to capture exceptions
20
+ def wrapped_start_response(status, headers, exc_info=None):
21
+ return start_response(status, headers, exc_info)
22
+
23
+ try:
24
+ # Use Flask's internal mechanism
25
+ from flask import g
26
+
27
+ return self.app(environ, wrapped_start_response)
28
+ except Exception as e:
29
+ # Get request object from Flask
30
+ from flask import request
31
+
32
+ self.client.capture(e, request)
33
+ raise
34
+
35
+
36
+ def init_flask(app: Flask, client: ReliabilityClient):
37
+ """Initialize Flask integration"""
38
+
39
+ @app.errorhandler(Exception)
40
+ def handle_error(error):
41
+ from flask import request
42
+
43
+ client.capture(error, request)
44
+ return {"error": "Internal server error"}, 500
45
+
46
+
47
+ def create_flask_error_handler(client: ReliabilityClient):
48
+ """Create Flask error handler for manual use"""
49
+
50
+ def handle_error(error):
51
+ from flask import request
52
+
53
+ client.capture(error, request)
54
+ return {"error": "Internal server error"}, 500
55
+
56
+ return handle_error
reliability/payload.py ADDED
@@ -0,0 +1,239 @@
1
+ """
2
+ Error Payload for Python SDK
3
+ """
4
+
5
+ import hashlib
6
+ import platform
7
+ import os
8
+ import re
9
+ import traceback
10
+ from types import TracebackType
11
+ from typing import Any, Dict, Optional, TypedDict, Union
12
+ from datetime import datetime
13
+
14
+
15
+ class ServiceContext(TypedDict, total=False):
16
+ pid: int
17
+ runtime: str
18
+ runtimeVersion: str
19
+ pythonVersion: str
20
+ platform: str
21
+ hostname: str
22
+
23
+
24
+ class RequestContext(TypedDict, total=False):
25
+ method: str
26
+ url: str
27
+ ip: str
28
+ userAgent: str
29
+ headers: Dict[str, Any]
30
+ body: Any
31
+ query: Any
32
+ cookies: Any
33
+
34
+
35
+ class ErrorPayload(TypedDict, total=False):
36
+ message: str
37
+ name: str
38
+ stack: str
39
+ fingerprint: str
40
+ timestamp: int
41
+ service: str
42
+ environment: str
43
+ version: str
44
+ release: str
45
+ commitHash: str
46
+ branch: str
47
+ buildId: str
48
+ deploymentId: str
49
+ occurrenceCount: int
50
+ severity: str
51
+ errorType: str
52
+ serviceContext: ServiceContext
53
+ requestContext: RequestContext
54
+
55
+
56
+ def _generate_fingerprint(error: Exception, stack: Optional[str] = None) -> str:
57
+ signature = str(error)
58
+ if stack:
59
+ first_line = stack.split("\n")[0] if stack else ""
60
+ signature += first_line
61
+ return hashlib.sha256(signature.encode()).hexdigest()
62
+
63
+
64
+ _SENSITIVE_KEYS = {
65
+ "authorization",
66
+ "cookie",
67
+ "set-cookie",
68
+ "x-api-key",
69
+ "api-key",
70
+ "apikey",
71
+ "password",
72
+ "token",
73
+ "access_token",
74
+ "refresh_token",
75
+ "secret",
76
+ "signature",
77
+ }
78
+
79
+
80
+ def _sanitize_text(value: str) -> str:
81
+ value = re.sub(r"Bearer\s+\S+", "Bearer [REDACTED]", value)
82
+ for keyword in ("token", "password", "secret", "api_key", "apikey"):
83
+ value = re.sub(rf"{re.escape(keyword)}=\S*", f"{keyword}=[REDACTED]", value)
84
+ return value
85
+
86
+
87
+ def _sanitize_value(value: Any) -> Any:
88
+ if isinstance(value, str):
89
+ return _sanitize_text(value)
90
+ if isinstance(value, dict):
91
+ return {
92
+ key: "[REDACTED]" if key.lower() in _SENSITIVE_KEYS else _sanitize_value(item)
93
+ for key, item in value.items()
94
+ }
95
+ if isinstance(value, list):
96
+ return [_sanitize_value(item) for item in value]
97
+ return value
98
+
99
+
100
+ def _classify_error(error: Exception) -> str:
101
+ msg = str(error).lower()
102
+ if any(
103
+ keyword in msg
104
+ for keyword in ["database", "sql", "psycopg", "mysql", "sqlite"]
105
+ ):
106
+ return "DATABASE"
107
+ if any(
108
+ keyword in msg
109
+ for keyword in ["network", "http", "socket", "connection", "timeout", "ssl"]
110
+ ):
111
+ return "NETWORK"
112
+ if any(
113
+ keyword in msg
114
+ for keyword in ["validation", "invalid", "required", "constraint", "type"]
115
+ ):
116
+ return "VALIDATION"
117
+ if any(
118
+ keyword in msg
119
+ for keyword in ["permission", "denied", "forbidden", "unauthorized"]
120
+ ):
121
+ return "AUTH"
122
+ return "RUNTIME"
123
+
124
+
125
+ def _determine_severity(error: Exception) -> str:
126
+ msg = str(error).lower()
127
+ if any(
128
+ keyword in msg
129
+ for keyword in ["database", "connection", "timeout", "fatal", "crash"]
130
+ ):
131
+ return "critical"
132
+ return "medium"
133
+
134
+
135
+ def _format_stack_trace(stack: Optional[Union[str, TracebackType]] = None) -> Optional[str]:
136
+ if not stack:
137
+ return None
138
+ if isinstance(stack, str):
139
+ return stack
140
+ try:
141
+ return "".join(traceback.format_tb(stack))
142
+ except Exception:
143
+ return None
144
+
145
+
146
+ def _build_service_context() -> ServiceContext:
147
+ return {
148
+ "pid": os.getpid(),
149
+ "runtime": "python",
150
+ "runtimeVersion": platform.python_version(),
151
+ "pythonVersion": platform.python_version(),
152
+ "platform": platform.platform(),
153
+ "hostname": platform.node() or os.environ.get("HOSTNAME", "unknown"),
154
+ }
155
+
156
+
157
+ def _extract_request_context(request: Optional[Any] = None) -> Optional[RequestContext]:
158
+ if request is None:
159
+ return None
160
+ context: RequestContext = {}
161
+
162
+ if hasattr(request, "method"):
163
+ context["method"] = request.method
164
+ if hasattr(request, "url"):
165
+ context["url"] = str(request.url)
166
+ if hasattr(request, "client") and request.client:
167
+ context["ip"] = request.client.host
168
+ if hasattr(request, "remote_addr"):
169
+ context["ip"] = request.remote_addr
170
+ if hasattr(request, "path"):
171
+ context["url"] = context.get("url", request.path)
172
+ if hasattr(request, "META"):
173
+ context["ip"] = request.META.get("REMOTE_ADDR")
174
+ context["method"] = request.method
175
+ context["url"] = request.path
176
+ if hasattr(request, "headers"):
177
+ headers = dict(request.headers)
178
+ forwarded = headers.get("x-forwarded-for")
179
+ if forwarded and not context.get("ip"):
180
+ context["ip"] = forwarded.split(",")[0].strip()
181
+ user_agent = headers.get("user-agent")
182
+ if user_agent:
183
+ context["userAgent"] = user_agent
184
+ context["headers"] = _sanitize_value(headers)
185
+
186
+ body = request.json if hasattr(request, "json") and not callable(request.json) else None
187
+ if body is not None:
188
+ context["body"] = _sanitize_value(body)
189
+ elif hasattr(request, "body") and request.body is not None:
190
+ context["body"] = _sanitize_value(request.body)
191
+
192
+ if hasattr(request, "args") and request.args is not None:
193
+ context["query"] = _sanitize_value(dict(request.args))
194
+ if hasattr(request, "cookies") and request.cookies is not None:
195
+ context["cookies"] = _sanitize_value(dict(request.cookies))
196
+ return context if context else None
197
+
198
+
199
+ def create_error_payload(
200
+ error: Exception,
201
+ service_id: str,
202
+ mode: str,
203
+ request: Optional[Any] = None,
204
+ *,
205
+ version: Optional[str] = None,
206
+ release: Optional[str] = None,
207
+ commit_hash: Optional[str] = None,
208
+ branch: Optional[str] = None,
209
+ build_id: Optional[str] = None,
210
+ deployment_id: Optional[str] = None,
211
+ ) -> ErrorPayload:
212
+ stack = getattr(error, "__fake_tb__", None) or _format_stack_trace(
213
+ error.__traceback__
214
+ )
215
+ fingerprint = _generate_fingerprint(error, stack)
216
+ return {
217
+ "message": str(error),
218
+ "name": type(error).__name__,
219
+ "stack": stack or "",
220
+ "fingerprint": fingerprint,
221
+ "timestamp": int(datetime.now().timestamp() * 1000),
222
+ "service": service_id,
223
+ "environment": mode,
224
+ **({"version": version} if version else {}),
225
+ **({"release": release} if release else {}),
226
+ **({"commitHash": commit_hash} if commit_hash else {}),
227
+ **({"branch": branch} if branch else {}),
228
+ **({"buildId": build_id} if build_id else {}),
229
+ **({"deploymentId": deployment_id} if deployment_id else {}),
230
+ "occurrenceCount": 1,
231
+ "severity": _determine_severity(error),
232
+ "errorType": _classify_error(error),
233
+ "serviceContext": _build_service_context(),
234
+ **(
235
+ {"requestContext": request_context}
236
+ if (request_context := _extract_request_context(request)) is not None
237
+ else {}
238
+ ),
239
+ }
reliability/py.typed ADDED
File without changes
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.4
2
+ Name: xecurecode-reliability-sdk
3
+ Version: 0.1.1
4
+ Summary: AI-driven reliability SDK for Python applications
5
+ Author-email: XecureCode Team <team@xecurecode.in>
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: fastapi
20
+ Requires-Dist: fastapi>=0.100; extra == "fastapi"
21
+ Provides-Extra: flask
22
+ Requires-Dist: flask>=2.0; extra == "flask"
23
+ Provides-Extra: django
24
+ Requires-Dist: django>=3.0; extra == "django"
25
+ Dynamic: license-file
26
+
27
+ # XecureCode Reliability SDK - Python
28
+
29
+ AI-driven reliability SDK for Python applications.
30
+
31
+ The official Python SDK for the XecureCode Reliability Platform — a human-first failure analysis and recovery recommendation system.
32
+
33
+ ---
34
+
35
+ ## Why This SDK Exists
36
+
37
+ Modern backend systems fail in unpredictable ways.
38
+
39
+ This SDK:
40
+
41
+ - Captures structured runtime errors
42
+ - Generates deterministic fingerprints
43
+ - Classifies error types (DATABASE, NETWORK, VALIDATION, etc.)
44
+ - Determines severity
45
+ - Sends non-blocking telemetry to your backend
46
+ - Never crashes your application
47
+
48
+ **AI assists. Humans decide. Nothing executes automatically.**
49
+
50
+ ---
51
+
52
+ ## Requirements
53
+
54
+ - Python 3.8+
55
+ - FastAPI, Flask, or Django (optional)
56
+
57
+ ---
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install xecurecode-reliability-sdk
63
+ ```
64
+
65
+ Or install with framework support:
66
+
67
+ ```bash
68
+ pip install xecurecode-reliability-sdk[fastapi,flask,django]
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Quick Start
74
+
75
+ ### 1️⃣ Initialize the SDK
76
+
77
+ ```python
78
+ from reliability import ReliabilityClient, ReliabilityConfig
79
+
80
+ config = ReliabilityConfig(
81
+ api_key="your-api-key",
82
+ service_id="your-service",
83
+ mode="development"
84
+ )
85
+ client = ReliabilityClient(config)
86
+ ```
87
+
88
+ **Configuration Options:**
89
+
90
+ | Parameter | Required | Description |
91
+ |-----------|----------|-------------|
92
+ | `api_key` | Yes | Your API key |
93
+ | `service_id` | Yes | Service identifier |
94
+ | `mode` | Yes | `development` or `production` |
95
+ | `timeout` | No | Request timeout (default: 5000ms) |
96
+
97
+ ---
98
+
99
+ ### 2️⃣ Automatic Global Error Capture
100
+
101
+ The SDK automatically handles:
102
+ - Uncaught exceptions
103
+ - Thread exceptions
104
+
105
+ ```python
106
+ # This will be automatically captured
107
+ raise ValueError("Database timeout")
108
+ ```
109
+
110
+ ---
111
+
112
+ ### 3️⃣ FastAPI Integration
113
+
114
+ ```python
115
+ from fastapi import FastAPI
116
+ from reliability import ReliabilityClient, ReliabilityConfig
117
+ from reliability.fastapi_integration import FastAPIMiddleware
118
+
119
+ config = ReliabilityConfig(...)
120
+ client = ReliabilityClient(config)
121
+
122
+ app = FastAPI()
123
+ app.add_middleware(FastAPIMiddleware, client=client)
124
+ ```
125
+
126
+ ---
127
+
128
+ ### 4️⃣ Flask Integration
129
+
130
+ ```python
131
+ from flask import Flask
132
+ from reliability import ReliabilityClient, ReliabilityConfig
133
+ from reliability.flask_integration import init_flask
134
+
135
+ config = ReliabilityConfig(...)
136
+ client = ReliabilityClient(config)
137
+
138
+ app = Flask(__name__)
139
+ init_flask(app, client)
140
+ ```
141
+
142
+ ---
143
+
144
+ ### 5️⃣ Django Integration
145
+
146
+ In `settings.py`:
147
+
148
+ ```python
149
+ RELIABILITY_API_KEY = "your-api-key"
150
+ RELIABILITY_SERVICE_ID = "your-service"
151
+ RELIABILITY_MODE = "development"
152
+ ```
153
+
154
+ In `settings.py` MIDDLEWARE:
155
+
156
+ ```python
157
+ MIDDLEWARE = [
158
+ ...
159
+ 'reliability.django_integration.ReliabilityMiddleware',
160
+ ...
161
+ ]
162
+ ```
163
+
164
+ ---
165
+
166
+ ### 6️⃣ Manual Capture
167
+
168
+ ```python
169
+ try:
170
+ risky_operation()
171
+ except Exception as e:
172
+ client.capture(e)
173
+
174
+ # With request context
175
+ client.capture(e, request)
176
+ ```
177
+
178
+ ---
179
+
180
+ ## What Gets Sent
181
+
182
+ Example structured payload:
183
+
184
+ ```json
185
+ {
186
+ "message": "Database connection failed",
187
+ "name": "ValueError",
188
+ "fingerprint": "a8c39c21...",
189
+ "timestamp": 1700000000000,
190
+ "service": "my-service",
191
+ "environment": "development",
192
+ "severity": "critical",
193
+ "errorType": "DATABASE",
194
+ "serviceContext": {
195
+ "pid": 12345,
196
+ "runtime": "python",
197
+ "runtimeVersion": "3.11.0",
198
+ "pythonVersion": "3.11.0",
199
+ "platform": "Linux-5.10.0",
200
+ "hostname": "server-01"
201
+ },
202
+ "requestContext": {
203
+ "method": "GET",
204
+ "url": "/api/users",
205
+ "ip": "10.0.0.5"
206
+ },
207
+ "occurrenceCount": 1
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Error Classification
214
+
215
+ | Type | Example |
216
+ |------|---------|
217
+ | DATABASE | Timeout, SQL errors, connection issues |
218
+ | NETWORK | HTTP failures, socket errors |
219
+ | VALIDATION | Invalid input, type errors |
220
+ | AUTH | Permission denied |
221
+ | RUNTIME | Standard Python errors |
222
+
223
+ ---
224
+
225
+ ## Severity Detection
226
+
227
+ - **Critical** → Database, timeout, connection errors
228
+ - **Medium** → Validation, recoverable issues
229
+
230
+ ---
231
+
232
+ ## Design Guarantees
233
+
234
+ - Non-blocking HTTP calls
235
+ - Timeout protected (5s default)
236
+ - Retry logic (3 attempts)
237
+ - Deduplication (1-minute window)
238
+ - Rate limiting (max 100 concurrent)
239
+ - Never throws inside SDK
240
+ - Never crashes host application
241
+ - Works with FastAPI, Flask, Django
242
+
243
+ ---
244
+
245
+ ## License
246
+
247
+ MIT
@@ -0,0 +1,13 @@
1
+ reliability/__init__.py,sha256=Cw_L9TJjEXCp6AM0Fbp8oLlfTsU5TOz2o3VJ0pufeyI,329
2
+ reliability/client.py,sha256=IYTvdGLcfPi6t0OlOKmCbrQBFDNCRnShTfywpORvXzE,8805
3
+ reliability/config.py,sha256=SBclrncIc7ntVdrTnXHEU3DJ8HZVyDjhtkjiPa8AuxQ,963
4
+ reliability/django_integration.py,sha256=6iw8sc0FybV1DyJwBLxN08L2eOtLZYMiXm_KusluBFE,1871
5
+ reliability/fastapi_integration.py,sha256=nx6znZGQK_oIu7pYWF-nRcFzoyhNFn9AQ1pgRI18KBs,1170
6
+ reliability/flask_integration.py,sha256=9WigBhUJJl0ATbNc_KAdEBniaHRhpGPsWBrUDKTbrPU,1490
7
+ reliability/payload.py,sha256=oVBkT8CtxLL1JhMU9_gKja7vt3i3y-g8aTz3bDgxAGQ,6988
8
+ reliability/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ xecurecode_reliability_sdk-0.1.1.dist-info/licenses/LICENSE,sha256=n61q08jxPdC1UKnkZMidrcD_iAksOya6fWZzXWg5ydE,1068
10
+ xecurecode_reliability_sdk-0.1.1.dist-info/METADATA,sha256=j2JLuKBezwjP-UqZEwBRllubMme0ZrbY14tLDTikh0g,5081
11
+ xecurecode_reliability_sdk-0.1.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
12
+ xecurecode_reliability_sdk-0.1.1.dist-info/top_level.txt,sha256=fVLL6FAOvV9ryG189ku0Ed6AhXksyefSi9HvqgmBBHo,12
13
+ xecurecode_reliability_sdk-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 XecureTrace
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.
@@ -0,0 +1 @@
1
+ reliability