netra-sdk 0.1.25__py3-none-any.whl → 0.1.28__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.

netra/__init__.py CHANGED
@@ -12,7 +12,10 @@ from .session_manager import SessionManager
12
12
  from .span_wrapper import ActionModel, SpanWrapper, UsageModel
13
13
  from .tracer import Tracer
14
14
 
15
+ # Package-level logger. Attach NullHandler by default so library does not emit logs
16
+ # unless explicitly enabled by the user via debug_mode.
15
17
  logger = logging.getLogger(__name__)
18
+ logger.addHandler(logging.NullHandler())
16
19
 
17
20
 
18
21
  class Netra:
@@ -42,6 +45,7 @@ class Netra:
42
45
  headers: Optional[str] = None,
43
46
  disable_batch: Optional[bool] = None,
44
47
  trace_content: Optional[bool] = None,
48
+ debug_mode: Optional[bool] = None,
45
49
  resource_attributes: Optional[Dict[str, Any]] = None,
46
50
  environment: Optional[str] = None,
47
51
  instruments: Optional[Set[NetraInstruments]] = None,
@@ -61,10 +65,31 @@ class Netra:
61
65
  headers=headers,
62
66
  disable_batch=disable_batch,
63
67
  trace_content=trace_content,
68
+ debug_mode=debug_mode,
64
69
  resource_attributes=resource_attributes,
65
70
  environment=environment,
66
71
  )
67
72
 
73
+ # Configure package logging based on debug mode
74
+ pkg_logger = logging.getLogger("netra")
75
+ # Prevent propagating to root to avoid duplicate logs
76
+ pkg_logger.propagate = False
77
+ # Clear existing handlers to avoid duplicates across repeated init attempts
78
+ pkg_logger.handlers.clear()
79
+ if cfg.debug_mode:
80
+ pkg_logger.setLevel(logging.DEBUG)
81
+ handler = logging.StreamHandler()
82
+ formatter = logging.Formatter(
83
+ fmt="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
84
+ datefmt="%Y-%m-%d %H:%M:%S",
85
+ )
86
+ handler.setFormatter(formatter)
87
+ pkg_logger.addHandler(handler)
88
+ else:
89
+ # Silence SDK logs entirely unless debug is enabled
90
+ pkg_logger.setLevel(logging.CRITICAL)
91
+ pkg_logger.addHandler(logging.NullHandler())
92
+
68
93
  # Initialize tracer (OTLP exporter, span processor, resource)
69
94
  Tracer(cfg)
70
95
 
@@ -89,6 +114,8 @@ class Netra:
89
114
  Args:
90
115
  session_id: Session identifier
91
116
  """
117
+ if not isinstance(session_id, str):
118
+ raise TypeError(f"session_id must be a string, got {type(session_id)}")
92
119
  if session_id:
93
120
  SessionManager.set_session_context("session_id", session_id)
94
121
  else:
@@ -102,6 +129,8 @@ class Netra:
102
129
  Args:
103
130
  user_id: User identifier
104
131
  """
132
+ if not isinstance(user_id, str):
133
+ raise TypeError(f"user_id must be a string, got {type(user_id)}")
105
134
  if user_id:
106
135
  SessionManager.set_session_context("user_id", user_id)
107
136
  else:
@@ -115,6 +144,8 @@ class Netra:
115
144
  Args:
116
145
  user_account_id: User account identifier
117
146
  """
147
+ if not isinstance(tenant_id, str):
148
+ raise TypeError(f"tenant_id must be a string, got {type(tenant_id)}")
118
149
  if tenant_id:
119
150
  SessionManager.set_session_context("tenant_id", tenant_id)
120
151
  else:
netra/config.py CHANGED
@@ -16,6 +16,7 @@ class Config:
16
16
  - headers: Additional headers (W3C Correlation-Context format)
17
17
  - disable_batch: Whether to disable batch span processor (bool)
18
18
  - trace_content: Whether to capture prompt/completion content (bool)
19
+ - debug_mode: Whether to enable SDK logging; default False (bool)
19
20
  - resource_attributes: Custom resource attributes dict (e.g., {'env': 'prod', 'version': '1.0.0'})
20
21
  """
21
22
 
@@ -30,6 +31,7 @@ class Config:
30
31
  headers: Optional[str] = None,
31
32
  disable_batch: Optional[bool] = None,
32
33
  trace_content: Optional[bool] = None,
34
+ debug_mode: Optional[bool] = None,
33
35
  resource_attributes: Optional[Dict[str, Any]] = None,
34
36
  environment: Optional[str] = None,
35
37
  ):
@@ -91,6 +93,13 @@ class Config:
91
93
  else:
92
94
  os.environ["TRACELOOP_TRACE_CONTENT"] = "true"
93
95
 
96
+ # Debug mode: enable SDK logging only when True. Default False.
97
+ if debug_mode is not None:
98
+ self.debug_mode = debug_mode
99
+ else:
100
+ env_dbg = os.getenv("NETRA_DEBUG")
101
+ self.debug_mode = True if (env_dbg is not None and env_dbg.lower() in ("1", "true")) else False
102
+
94
103
  # 7. Environment: param override, else env
95
104
  if environment is not None:
96
105
  self.environment = environment
netra/decorators.py CHANGED
@@ -176,3 +176,18 @@ def task(
176
176
  if target is not None:
177
177
  return decorator(target)
178
178
  return decorator
179
+
180
+
181
+ def span(
182
+ target: Union[Callable[P, R], C, None] = None, *, name: Optional[str] = None
183
+ ) -> Union[Callable[P, R], C, Callable[[Callable[P, R]], Callable[P, R]]]:
184
+ def decorator(obj: Union[Callable[P, R], C]) -> Union[Callable[P, R], C]:
185
+ if inspect.isclass(obj):
186
+ return _wrap_class_methods(cast(C, obj), "span", name)
187
+ else:
188
+ # When obj is a function, it should be type Callable[P, R]
189
+ return _create_function_wrapper(cast(Callable[P, R], obj), "span", name)
190
+
191
+ if target is not None:
192
+ return decorator(target)
193
+ return decorator
netra/session_manager.py CHANGED
@@ -26,6 +26,7 @@ class SessionManager:
26
26
  _workflow_stack: List[str] = []
27
27
  _task_stack: List[str] = []
28
28
  _agent_stack: List[str] = []
29
+ _span_stack: List[str] = []
29
30
 
30
31
  @classmethod
31
32
  def set_current_span(cls, span: Optional[trace.Span]) -> None:
@@ -53,7 +54,7 @@ class SessionManager:
53
54
  Push an entity onto the appropriate entity stack.
54
55
 
55
56
  Args:
56
- entity_type: Type of entity (workflow, task, agent)
57
+ entity_type: Type of entity (workflow, task, agent, span)
57
58
  entity_name: Name of the entity
58
59
  """
59
60
  if entity_type == "workflow":
@@ -62,6 +63,8 @@ class SessionManager:
62
63
  cls._task_stack.append(entity_name)
63
64
  elif entity_type == "agent":
64
65
  cls._agent_stack.append(entity_name)
66
+ elif entity_type == "span":
67
+ cls._span_stack.append(entity_name)
65
68
 
66
69
  @classmethod
67
70
  def pop_entity(cls, entity_type: str) -> Optional[str]:
@@ -69,7 +72,7 @@ class SessionManager:
69
72
  Pop the most recent entity from the specified entity stack.
70
73
 
71
74
  Args:
72
- entity_type: Type of entity (workflow, task, agent)
75
+ entity_type: Type of entity (workflow, task, agent, span)
73
76
 
74
77
  Returns:
75
78
  Entity name or None if stack is empty
@@ -80,6 +83,8 @@ class SessionManager:
80
83
  return cls._task_stack.pop()
81
84
  elif entity_type == "agent" and cls._agent_stack:
82
85
  return cls._agent_stack.pop()
86
+ elif entity_type == "span" and cls._span_stack:
87
+ return cls._span_stack.pop()
83
88
  return None
84
89
 
85
90
  @classmethod
@@ -104,6 +109,10 @@ class SessionManager:
104
109
  if cls._agent_stack:
105
110
  attributes[f"{Config.LIBRARY_NAME}.agent.name"] = cls._agent_stack[-1]
106
111
 
112
+ # Add current span if exists
113
+ if cls._span_stack:
114
+ attributes[f"{Config.LIBRARY_NAME}.span.name"] = cls._span_stack[-1]
115
+
107
116
  return attributes
108
117
 
109
118
  @classmethod
@@ -112,6 +121,7 @@ class SessionManager:
112
121
  cls._workflow_stack.clear()
113
122
  cls._task_stack.clear()
114
123
  cls._agent_stack.clear()
124
+ cls._span_stack.clear()
115
125
 
116
126
  @classmethod
117
127
  def get_stack_info(cls) -> Dict[str, List[str]]:
@@ -125,6 +135,7 @@ class SessionManager:
125
135
  "workflows": cls._workflow_stack.copy(),
126
136
  "tasks": cls._task_stack.copy(),
127
137
  "agents": cls._agent_stack.copy(),
138
+ "spans": cls._span_stack.copy(),
128
139
  }
129
140
 
130
141
  @staticmethod
netra/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.25"
1
+ __version__ = "0.1.28"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: netra-sdk
3
- Version: 0.1.25
3
+ Version: 0.1.28
4
4
  Summary: A Python SDK for AI application observability that provides OpenTelemetry-based monitoring, tracing, and PII protection for LLM and vector database applications. Enables easy instrumentation, session tracking, and privacy-focused data collection for AI systems in production environments.
5
5
  License: Apache-2.0
6
6
  Keywords: netra,tracing,observability,sdk,ai,llm,vector,database
@@ -72,7 +72,6 @@ Requires-Dist: presidio-anonymizer (==2.2.358) ; extra == "presidio"
72
72
  Requires-Dist: stanza (>=1.10.1,<2.0.0) ; extra == "presidio"
73
73
  Requires-Dist: traceloop-sdk (>=0.40.7,<0.43.0)
74
74
  Requires-Dist: transformers (==4.51.3) ; extra == "presidio"
75
- Requires-Dist: twine (>=6.1.0,<7.0.0)
76
75
  Project-URL: Bug Tracker, https://github.com/KeyValueSoftwareSystems/netra-sdk-py/issues
77
76
  Project-URL: Documentation, https://github.com/KeyValueSoftwareSystems/netra-sdk-py/blob/main/README.md
78
77
  Project-URL: Homepage, https://github.com/KeyValueSoftwareSystems/netra-sdk-py
@@ -174,7 +173,7 @@ Netra.init(
174
173
  Use decorators to automatically trace your functions and classes:
175
174
 
176
175
  ```python
177
- from netra.decorators import workflow, agent, task
176
+ from netra.decorators import workflow, agent, task, span
178
177
 
179
178
  @workflow
180
179
  def data_processing_workflow(data):
@@ -192,6 +191,11 @@ def data_validation_task(data):
192
191
  """Task for validating input data"""
193
192
  return validate_schema(data)
194
193
 
194
+ @span
195
+ def data_processing_span(data):
196
+ """Span for processing data"""
197
+ return process_data(data)
198
+
195
199
  # Works with async functions too
196
200
  @workflow(name="Async Data Pipeline")
197
201
  async def async_workflow(data):
@@ -206,6 +210,16 @@ class CustomerSupportAgent:
206
210
 
207
211
  def escalate_issue(self, issue):
208
212
  return self.forward_to_human(issue)
213
+
214
+ @task
215
+ async def async_task(data):
216
+ """Task for processing data"""
217
+ return await process_data_async(data)
218
+
219
+ @span
220
+ async def async_span(data):
221
+ """Span for processing data"""
222
+ return await process_data_async(data)
209
223
  ```
210
224
 
211
225
  ## 🔍 Supported Instrumentations
@@ -1,10 +1,10 @@
1
- netra/__init__.py,sha256=49R2bZrVu30HhpE3gfpu0b5C5cNovC6WyNyUwHCOfQ8,5392
1
+ netra/__init__.py,sha256=hboj9svp8pHeMk59ifwyNgXNElaHvTEpUKhjD1-1azU,6997
2
2
  netra/anonymizer/__init__.py,sha256=KeGPPZqKVZbtkbirEKYTYhj6aZHlakjdQhD7QHqBRio,133
3
3
  netra/anonymizer/anonymizer.py,sha256=IcrYkdwWrFauGWUeAW-0RwrSUM8VSZCFNtoywZhvIqU,3778
4
4
  netra/anonymizer/base.py,sha256=ytPxHCUD2OXlEY6fNTuMmwImNdIjgj294I41FIgoXpU,5946
5
5
  netra/anonymizer/fp_anonymizer.py,sha256=_6svIYmE0eejdIMkhKBUWCNjGtGimtrGtbLvPSOp8W4,6493
6
- netra/config.py,sha256=S9GsCvwtakrmryAaV-AhyVB_wAQ6tjwPLLZQemLgXko,5006
7
- netra/decorators.py,sha256=V_WpZ2IgW2Y7B_WnSXmKUGGhkM5Cra2TwONddmJpPaI,6837
6
+ netra/config.py,sha256=MiJXqQ1GntU9U4MyzZnQ38Lma0EE96Tx1l5wH3rTrus,5452
7
+ netra/decorators.py,sha256=TqORBs0Xv_CZHOAwrWkpvscMj4FgOJsgo1CRYoG6C7Y,7435
8
8
  netra/exceptions/__init__.py,sha256=uDgcBxmC4WhdS7HRYQk_TtJyxH1s1o6wZmcsnSHLAcM,174
9
9
  netra/exceptions/injection.py,sha256=ke4eUXRYUFJkMZgdSyPPkPt5PdxToTI6xLEBI0hTWUQ,1332
10
10
  netra/exceptions/pii.py,sha256=MT4p_x-zH3VtYudTSxw1Z9qQZADJDspq64WrYqSWlZc,2438
@@ -40,11 +40,11 @@ netra/pii.py,sha256=Rn4SjgTJW_aw9LcbjLuMqF3fKd9b1ndlYt1CaK51Ge0,33125
40
40
  netra/processors/__init__.py,sha256=wfnSskRBtMT90hO7LqFJoEW374LgoH_gnTxhynqtByI,109
41
41
  netra/processors/session_span_processor.py,sha256=qcsBl-LnILWefsftI8NQhXDGb94OWPc8LvzhVA0JS_c,2432
42
42
  netra/scanner.py,sha256=kyDpeZiscCPb6pjuhS-sfsVj-dviBFRepdUWh0sLoEY,11554
43
- netra/session_manager.py,sha256=EVcnWcSj4NdkH--HmqHx0mmzivQiM4GCyFLu6lwi33M,6252
43
+ netra/session_manager.py,sha256=Ks8A6B9RYe0tV8PeWYuN0M-UMZqa47uquuw6D7C1vCE,6701
44
44
  netra/span_wrapper.py,sha256=ec2WLYTRLZ02WSSCYEsMn1PgUGVji9rFyq_CRCV9rog,7388
45
45
  netra/tracer.py,sha256=In5QPVLz_6BxrolWpav9EuR9_hirD2UUIlyY75QUaKk,3450
46
- netra/version.py,sha256=Ej7LsXg-6CASlaEHsZkUoLDpYEfHeFKdIeXMIM0esgA,23
47
- netra_sdk-0.1.25.dist-info/LICENCE,sha256=8B_UoZ-BAl0AqiHAHUETCgd3I2B9yYJ1WEQtVb_qFMA,11359
48
- netra_sdk-0.1.25.dist-info/METADATA,sha256=AnDcjfLzY00vGhSd_3kOjoVPK9W15JqeVPj2LyIYn7A,27855
49
- netra_sdk-0.1.25.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
50
- netra_sdk-0.1.25.dist-info/RECORD,,
46
+ netra/version.py,sha256=MWZDdAHrdUZS0c3VlLqX4O1eaxPodI7irMtEvknKQ94,23
47
+ netra_sdk-0.1.28.dist-info/LICENCE,sha256=8B_UoZ-BAl0AqiHAHUETCgd3I2B9yYJ1WEQtVb_qFMA,11359
48
+ netra_sdk-0.1.28.dist-info/METADATA,sha256=T63YlGVzQCx6cxKp8fXMK136IYwpcFC5VOUeLTKg_8E,28151
49
+ netra_sdk-0.1.28.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
50
+ netra_sdk-0.1.28.dist-info/RECORD,,