netra-sdk 0.1.0__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.

Potentially problematic release.


This version of netra-sdk might be problematic. Click here for more details.

Files changed (42) hide show
  1. netra/__init__.py +148 -0
  2. netra/anonymizer/__init__.py +7 -0
  3. netra/anonymizer/anonymizer.py +79 -0
  4. netra/anonymizer/base.py +159 -0
  5. netra/anonymizer/fp_anonymizer.py +182 -0
  6. netra/config.py +111 -0
  7. netra/decorators.py +167 -0
  8. netra/exceptions/__init__.py +6 -0
  9. netra/exceptions/injection.py +33 -0
  10. netra/exceptions/pii.py +46 -0
  11. netra/input_scanner.py +142 -0
  12. netra/instrumentation/__init__.py +257 -0
  13. netra/instrumentation/aiohttp/__init__.py +378 -0
  14. netra/instrumentation/aiohttp/version.py +1 -0
  15. netra/instrumentation/cohere/__init__.py +446 -0
  16. netra/instrumentation/cohere/version.py +1 -0
  17. netra/instrumentation/google_genai/__init__.py +506 -0
  18. netra/instrumentation/google_genai/config.py +5 -0
  19. netra/instrumentation/google_genai/utils.py +31 -0
  20. netra/instrumentation/google_genai/version.py +1 -0
  21. netra/instrumentation/httpx/__init__.py +545 -0
  22. netra/instrumentation/httpx/version.py +1 -0
  23. netra/instrumentation/instruments.py +78 -0
  24. netra/instrumentation/mistralai/__init__.py +545 -0
  25. netra/instrumentation/mistralai/config.py +5 -0
  26. netra/instrumentation/mistralai/utils.py +30 -0
  27. netra/instrumentation/mistralai/version.py +1 -0
  28. netra/instrumentation/weaviate/__init__.py +121 -0
  29. netra/instrumentation/weaviate/version.py +1 -0
  30. netra/pii.py +757 -0
  31. netra/processors/__init__.py +4 -0
  32. netra/processors/session_span_processor.py +55 -0
  33. netra/processors/span_aggregation_processor.py +365 -0
  34. netra/scanner.py +104 -0
  35. netra/session.py +185 -0
  36. netra/session_manager.py +96 -0
  37. netra/tracer.py +99 -0
  38. netra/version.py +1 -0
  39. netra_sdk-0.1.0.dist-info/LICENCE +201 -0
  40. netra_sdk-0.1.0.dist-info/METADATA +573 -0
  41. netra_sdk-0.1.0.dist-info/RECORD +42 -0
  42. netra_sdk-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,378 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import functools
5
+ import logging
6
+ import types
7
+ from timeit import default_timer
8
+ from typing import Any, Awaitable, Callable, Collection, Dict, Optional, Union
9
+ from urllib.parse import urlparse
10
+
11
+ from aiohttp import ClientRequest, ClientResponse, ClientSession
12
+ from aiohttp.typedefs import URL
13
+ from opentelemetry.instrumentation._semconv import (
14
+ HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
15
+ HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
16
+ _client_duration_attrs_new,
17
+ _client_duration_attrs_old,
18
+ _filter_semconv_duration_attrs,
19
+ _get_schema_url,
20
+ _OpenTelemetrySemanticConventionStability,
21
+ _OpenTelemetryStabilitySignalType,
22
+ _report_new,
23
+ _report_old,
24
+ _set_http_host_client,
25
+ _set_http_method,
26
+ _set_http_net_peer_name_client,
27
+ _set_http_network_protocol_version,
28
+ _set_http_peer_port_client,
29
+ _set_http_scheme,
30
+ _set_http_url,
31
+ _set_status,
32
+ _StabilityMode,
33
+ )
34
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
35
+ from opentelemetry.instrumentation.utils import (
36
+ is_http_instrumentation_enabled,
37
+ suppress_http_instrumentation,
38
+ )
39
+ from opentelemetry.metrics import Histogram, get_meter
40
+ from opentelemetry.propagate import inject
41
+ from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
42
+ from opentelemetry.semconv.attributes.network_attributes import (
43
+ NETWORK_PEER_ADDRESS,
44
+ NETWORK_PEER_PORT,
45
+ )
46
+ from opentelemetry.semconv.metrics import MetricInstruments
47
+ from opentelemetry.semconv.metrics.http_metrics import (
48
+ HTTP_CLIENT_REQUEST_DURATION,
49
+ )
50
+ from opentelemetry.trace import SpanKind, Tracer, get_tracer
51
+ from opentelemetry.trace.span import Span
52
+ from opentelemetry.util.http import (
53
+ ExcludeList,
54
+ get_excluded_urls,
55
+ parse_excluded_urls,
56
+ remove_url_credentials,
57
+ sanitize_method,
58
+ )
59
+ from opentelemetry.util.http.httplib import set_ip_on_next_http_connection
60
+
61
+ from netra.instrumentation.aiohttp.version import __version__
62
+
63
+ logger = logging.getLogger(__name__)
64
+
65
+ # Package info for aiohttp instrumentation
66
+ _instruments = ("aiohttp >= 3.0.0",)
67
+
68
+ _excluded_urls_from_env = get_excluded_urls("AIOHTTP_CLIENT")
69
+
70
+ _RequestHookT = Optional[Callable[[Span, ClientRequest], Awaitable[None]]]
71
+ _ResponseHookT = Optional[Callable[[Span, ClientRequest, ClientResponse], Awaitable[None]]]
72
+
73
+
74
+ def _set_http_status_code_attribute(
75
+ span: Span,
76
+ status_code: Union[int, str],
77
+ metric_attributes: Optional[Dict[str, Any]] = None,
78
+ sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
79
+ ) -> None:
80
+ status_code_str = str(status_code)
81
+ try:
82
+ status_code_int = int(status_code)
83
+ except ValueError:
84
+ status_code_int = -1
85
+ if metric_attributes is None:
86
+ metric_attributes = {}
87
+ _set_status(
88
+ span,
89
+ metric_attributes,
90
+ status_code_int,
91
+ status_code_str,
92
+ server_span=False,
93
+ sem_conv_opt_in_mode=sem_conv_opt_in_mode,
94
+ )
95
+
96
+
97
+ def _instrument(
98
+ tracer: Tracer,
99
+ duration_histogram_old: Optional[Histogram],
100
+ duration_histogram_new: Optional[Histogram],
101
+ request_hook: _RequestHookT = None,
102
+ response_hook: _ResponseHookT = None,
103
+ excluded_urls: Optional[ExcludeList] = None,
104
+ sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
105
+ ) -> None:
106
+ """Enables tracing of all aiohttp client requests."""
107
+
108
+ wrapped_request = ClientSession._request
109
+
110
+ @functools.wraps(wrapped_request)
111
+ async def instrumented_request(self: ClientSession, method: str, url: Any, **kwargs: Any) -> ClientResponse:
112
+ if excluded_urls and excluded_urls.url_disabled(str(url)):
113
+ return await wrapped_request(self, method, url, **kwargs)
114
+
115
+ if not is_http_instrumentation_enabled():
116
+ return await wrapped_request(self, method, url, **kwargs)
117
+
118
+ span_name = get_default_span_name(method)
119
+
120
+ url_str = str(url)
121
+ url = remove_url_credentials(url_str)
122
+
123
+ span_attributes: Dict[str, Any] = {}
124
+ _set_http_method(
125
+ span_attributes,
126
+ method,
127
+ sanitize_method(method),
128
+ sem_conv_opt_in_mode,
129
+ )
130
+ _set_http_url(span_attributes, url, sem_conv_opt_in_mode)
131
+
132
+ metric_labels: Dict[str, Any] = {}
133
+ _set_http_method(
134
+ metric_labels,
135
+ method,
136
+ sanitize_method(method),
137
+ sem_conv_opt_in_mode,
138
+ )
139
+
140
+ try:
141
+ parsed_url = urlparse(url)
142
+ if parsed_url.scheme:
143
+ if _report_old(sem_conv_opt_in_mode):
144
+ _set_http_scheme(metric_labels, parsed_url.scheme, sem_conv_opt_in_mode)
145
+ if parsed_url.hostname:
146
+ _set_http_host_client(metric_labels, parsed_url.hostname, sem_conv_opt_in_mode)
147
+ _set_http_net_peer_name_client(metric_labels, parsed_url.hostname, sem_conv_opt_in_mode)
148
+ if _report_new(sem_conv_opt_in_mode):
149
+ _set_http_host_client(
150
+ span_attributes,
151
+ parsed_url.hostname,
152
+ sem_conv_opt_in_mode,
153
+ )
154
+
155
+ span_attributes[NETWORK_PEER_ADDRESS] = parsed_url.hostname
156
+ if parsed_url.port:
157
+ _set_http_peer_port_client(metric_labels, parsed_url.port, sem_conv_opt_in_mode)
158
+ if _report_new(sem_conv_opt_in_mode):
159
+ _set_http_peer_port_client(span_attributes, parsed_url.port, sem_conv_opt_in_mode)
160
+ span_attributes[NETWORK_PEER_PORT] = parsed_url.port
161
+ except ValueError as error:
162
+ logger.error(error)
163
+
164
+ with (
165
+ tracer.start_as_current_span(span_name, kind=SpanKind.CLIENT, attributes=span_attributes) as span,
166
+ set_ip_on_next_http_connection(span),
167
+ ):
168
+ exception = None
169
+ response = None
170
+
171
+ headers = kwargs.get("headers", {})
172
+ if headers is None:
173
+ headers = {}
174
+ if hasattr(headers, "items"):
175
+ headers_dict = dict(headers.items())
176
+ else:
177
+ headers_dict = dict(headers)
178
+
179
+ inject(headers_dict)
180
+ kwargs["headers"] = headers_dict
181
+
182
+ with suppress_http_instrumentation():
183
+ start_time = default_timer()
184
+ try:
185
+ response = await wrapped_request(self, method, url_str, **kwargs) # *** PROCEED
186
+
187
+ # Create a ClientRequest object for hooks
188
+
189
+ request_obj = ClientRequest(
190
+ method=method,
191
+ url=URL(url_str),
192
+ headers=headers_dict,
193
+ data=kwargs.get("data"),
194
+ params=kwargs.get("params"),
195
+ )
196
+
197
+ if callable(request_hook):
198
+ await request_hook(span, request_obj)
199
+
200
+ except Exception as exc: # pylint: disable=W0703
201
+ exception = exc
202
+ response = getattr(exc, "response", None)
203
+ finally:
204
+ elapsed_time = max(default_timer() - start_time, 0)
205
+
206
+ if response is not None:
207
+ span_attributes_response: Dict[str, Any] = {}
208
+ _set_http_status_code_attribute(
209
+ span,
210
+ response.status,
211
+ metric_labels,
212
+ sem_conv_opt_in_mode,
213
+ )
214
+
215
+ # Set HTTP version if available
216
+ if hasattr(response, "version") and response.version:
217
+ version_text = f"{response.version.major}.{response.version.minor}"
218
+ _set_http_network_protocol_version(metric_labels, version_text, sem_conv_opt_in_mode)
219
+ if _report_new(sem_conv_opt_in_mode):
220
+ _set_http_network_protocol_version(
221
+ span_attributes_response,
222
+ version_text,
223
+ sem_conv_opt_in_mode,
224
+ )
225
+
226
+ for key, val in span_attributes_response.items():
227
+ span.set_attribute(key, val)
228
+
229
+ if callable(response_hook) and "request_obj" in locals():
230
+ await response_hook(span, request_obj, response)
231
+
232
+ if exception is not None and _report_new(sem_conv_opt_in_mode):
233
+ span.set_attribute(ERROR_TYPE, type(exception).__qualname__)
234
+ metric_labels[ERROR_TYPE] = type(exception).__qualname__
235
+
236
+ if duration_histogram_old is not None:
237
+ duration_attrs_old = _filter_semconv_duration_attrs(
238
+ metric_labels,
239
+ _client_duration_attrs_old,
240
+ _client_duration_attrs_new,
241
+ _StabilityMode.DEFAULT,
242
+ )
243
+ duration_histogram_old.record(
244
+ max(round(elapsed_time * 1000), 0),
245
+ attributes=duration_attrs_old,
246
+ )
247
+ if duration_histogram_new is not None:
248
+ duration_attrs_new = _filter_semconv_duration_attrs(
249
+ metric_labels,
250
+ _client_duration_attrs_old,
251
+ _client_duration_attrs_new,
252
+ _StabilityMode.HTTP,
253
+ )
254
+ duration_histogram_new.record(elapsed_time, attributes=duration_attrs_new)
255
+
256
+ if exception is not None:
257
+ raise exception.with_traceback(exception.__traceback__)
258
+
259
+ return response
260
+
261
+ # Store the attribute on the function object directly
262
+ setattr(instrumented_request, "opentelemetry_instrumentation_aiohttp_applied", True)
263
+ ClientSession._request = instrumented_request
264
+
265
+
266
+ def _uninstrument() -> None:
267
+ """Disables instrumentation of :code:`aiohttp` client through this module.
268
+
269
+ Note that this only works if no other module also patches aiohttp."""
270
+ _uninstrument_from(ClientSession)
271
+
272
+
273
+ def _uninstrument_from(instr_root: Any, restore_as_bound_func: bool = False) -> None:
274
+ for instr_func_name in ("_request",):
275
+ instr_func = getattr(instr_root, instr_func_name)
276
+ if not getattr(
277
+ instr_func,
278
+ "opentelemetry_instrumentation_aiohttp_applied",
279
+ False,
280
+ ):
281
+ continue
282
+
283
+ original = instr_func.__wrapped__ # pylint:disable=no-member
284
+ if restore_as_bound_func:
285
+ original = types.MethodType(original, instr_root)
286
+ setattr(instr_root, instr_func_name, original)
287
+
288
+
289
+ def get_default_span_name(method: str) -> str:
290
+ """
291
+ Default implementation for name_callback, returns HTTP {method_name}.
292
+ https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#name
293
+
294
+ Args:
295
+ method: string representing HTTP method
296
+ Returns:
297
+ span name
298
+ """
299
+ method = sanitize_method(method.strip())
300
+ if method == "_OTHER":
301
+ return "HTTP"
302
+ return method
303
+
304
+
305
+ class AioHttpClientInstrumentor(BaseInstrumentor): # type: ignore[misc]
306
+ """An instrumentor for aiohttp client
307
+ See `BaseInstrumentor`
308
+ """
309
+
310
+ def instrumentation_dependencies(self) -> Collection[str]:
311
+ return _instruments
312
+
313
+ def _instrument(self, **kwargs: Any) -> None:
314
+ """Instruments aiohttp client module
315
+
316
+ Args:
317
+ **kwargs: Optional arguments
318
+ ``tracer_provider``: a TracerProvider, defaults to global
319
+ ``request_hook``: An optional async callback that is invoked right after a span is created.
320
+ ``response_hook``: An optional async callback which is invoked right before the span is finished processing a response.
321
+ ``excluded_urls``: A string containing a comma-delimited list of regexes used to exclude URLs from tracking
322
+ ``duration_histogram_boundaries``: A list of float values representing the explicit bucket boundaries for the duration histogram.
323
+ """
324
+ semconv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
325
+ _OpenTelemetryStabilitySignalType.HTTP,
326
+ )
327
+ schema_url = _get_schema_url(semconv_opt_in_mode)
328
+ tracer_provider = kwargs.get("tracer_provider")
329
+ tracer = get_tracer(
330
+ __name__,
331
+ __version__,
332
+ tracer_provider,
333
+ schema_url=schema_url,
334
+ )
335
+ excluded_urls = kwargs.get("excluded_urls")
336
+ meter_provider = kwargs.get("meter_provider")
337
+ duration_histogram_boundaries = kwargs.get("duration_histogram_boundaries")
338
+ meter = get_meter(
339
+ __name__,
340
+ __version__,
341
+ meter_provider,
342
+ schema_url=schema_url,
343
+ )
344
+ duration_histogram_old = None
345
+ if _report_old(semconv_opt_in_mode):
346
+ duration_histogram_old = meter.create_histogram(
347
+ name=MetricInstruments.HTTP_CLIENT_DURATION,
348
+ unit="ms",
349
+ description="measures the duration of the outbound HTTP request",
350
+ explicit_bucket_boundaries_advisory=duration_histogram_boundaries
351
+ or HTTP_DURATION_HISTOGRAM_BUCKETS_OLD,
352
+ )
353
+ duration_histogram_new = None
354
+ if _report_new(semconv_opt_in_mode):
355
+ duration_histogram_new = meter.create_histogram(
356
+ name=HTTP_CLIENT_REQUEST_DURATION,
357
+ unit="s",
358
+ description="Duration of HTTP client requests.",
359
+ explicit_bucket_boundaries_advisory=duration_histogram_boundaries
360
+ or HTTP_DURATION_HISTOGRAM_BUCKETS_NEW,
361
+ )
362
+ _instrument(
363
+ tracer,
364
+ duration_histogram_old,
365
+ duration_histogram_new,
366
+ request_hook=kwargs.get("request_hook"),
367
+ response_hook=kwargs.get("response_hook"),
368
+ excluded_urls=(_excluded_urls_from_env if excluded_urls is None else parse_excluded_urls(excluded_urls)),
369
+ sem_conv_opt_in_mode=semconv_opt_in_mode,
370
+ )
371
+
372
+ def _uninstrument(self, **kwargs: Any) -> None:
373
+ _uninstrument()
374
+
375
+ @staticmethod
376
+ def uninstrument_session(session: ClientSession) -> None:
377
+ """Disables instrumentation on the session object."""
378
+ _uninstrument_from(session, restore_as_bound_func=True)
@@ -0,0 +1 @@
1
+ __version__ = "3.12.13"