aiqa-client 0.1.6__py3-none-any.whl → 0.2.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.
aiqa/__init__.py CHANGED
@@ -4,26 +4,28 @@ Python client for AIQA server - OpenTelemetry tracing decorators.
4
4
 
5
5
  from .tracing import (
6
6
  WithTracing,
7
- flush_spans,
7
+ flush_tracing,
8
8
  shutdown_tracing,
9
9
  set_span_attribute,
10
10
  set_span_name,
11
11
  get_active_span,
12
- provider,
13
- exporter,
12
+ get_provider,
13
+ get_exporter,
14
14
  )
15
+ from .client import get_client
15
16
 
16
- __version__ = "0.1.6"
17
+ __version__ = "0.2.1"
17
18
 
18
19
  __all__ = [
19
20
  "WithTracing",
20
- "flush_spans",
21
+ "flush_tracing",
21
22
  "shutdown_tracing",
22
23
  "set_span_attribute",
23
24
  "set_span_name",
24
25
  "get_active_span",
25
- "provider",
26
- "exporter",
26
+ "get_provider",
27
+ "get_exporter",
28
+ "get_client",
27
29
  "__version__",
28
30
  ]
29
31
 
aiqa/aiqa_exporter.py CHANGED
@@ -65,7 +65,7 @@ class AIQASpanExporter(SpanExporter):
65
65
  if not spans:
66
66
  logger.debug("export() called with empty spans list")
67
67
  return SpanExportResult.SUCCESS
68
-
68
+ logger.debug(f"AIQA export() called with {len(spans)} spans")
69
69
  # Serialize and add to buffer
70
70
  with self.buffer_lock:
71
71
  serialized_spans = [self._serialize_span(span) for span in spans]
aiqa/client.py ADDED
@@ -0,0 +1,49 @@
1
+ # aiqa/client.py
2
+ import os
3
+ from functools import lru_cache
4
+ from opentelemetry import trace
5
+ from opentelemetry.sdk.trace import TracerProvider
6
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
7
+
8
+ from .aiqa_exporter import AIQASpanExporter
9
+
10
+ AIQA_TRACER_NAME = "aiqa-tracer"
11
+
12
+ client = {
13
+ "provider": None,
14
+ "exporter": None,
15
+ }
16
+
17
+ @lru_cache(maxsize=1)
18
+ def get_client():
19
+ global client
20
+ _init_tracing()
21
+ # optionally return a richer client object; for now you just need init
22
+ return client
23
+
24
+ def _init_tracing():
25
+ provider = trace.get_tracer_provider()
26
+
27
+ # If it's still the default proxy, install a real SDK provider
28
+ if not isinstance(provider, TracerProvider):
29
+ provider = TracerProvider()
30
+ trace.set_tracer_provider(provider)
31
+
32
+ # Idempotently add your processor
33
+ _attach_aiqa_processor(provider)
34
+ global client
35
+ client["provider"] = provider
36
+
37
+ def _attach_aiqa_processor(provider: TracerProvider):
38
+ # Avoid double-adding if get_client() is called multiple times
39
+ for p in provider._active_span_processor._span_processors:
40
+ if isinstance(getattr(p, "exporter", None), AIQASpanExporter):
41
+ return
42
+
43
+ exporter = AIQASpanExporter(
44
+ server_url=os.getenv("AIQA_SERVER_URL"),
45
+ api_key=os.getenv("AIQA_API_KEY"),
46
+ )
47
+ provider.add_span_processor(BatchSpanProcessor(exporter))
48
+ global client
49
+ client["exporter"] = exporter
aiqa/tracing.py CHANGED
@@ -18,31 +18,12 @@ from opentelemetry.semconv.resource import ResourceAttributes
18
18
  from opentelemetry.trace import Status, StatusCode
19
19
  from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
20
20
  from .aiqa_exporter import AIQASpanExporter
21
+ from .client import get_client, AIQA_TRACER_NAME
21
22
 
22
23
  logger = logging.getLogger(__name__)
23
24
 
24
- # Load environment variables
25
- exporter = AIQASpanExporter()
26
25
 
27
- # Initialize OpenTelemetry
28
- # Use ALWAYS_ON sampler to ensure all spans are recorded (important for FastAPI async contexts)
29
- provider = TracerProvider(
30
- resource=Resource.create(
31
- {
32
- ResourceAttributes.SERVICE_NAME: os.getenv("OTEL_SERVICE_NAME", "aiqa-service"),
33
- }
34
- ),
35
- sampler=ALWAYS_ON
36
- )
37
-
38
- provider.add_span_processor(BatchSpanProcessor(exporter))
39
- trace.set_tracer_provider(provider)
40
-
41
- # Get a tracer instance
42
- tracer = trace.get_tracer("aiqa-tracer")
43
-
44
-
45
- async def flush_spans() -> None:
26
+ async def flush_tracing() -> None:
46
27
  """
47
28
  Flush all pending spans to the server.
48
29
  Flushes also happen automatically every few seconds. So you only need to call this function
@@ -50,8 +31,11 @@ async def flush_spans() -> None:
50
31
 
51
32
  This flushes both the BatchSpanProcessor and the exporter buffer.
52
33
  """
53
- provider.force_flush() # Synchronous method
54
- await exporter.flush()
34
+ client = get_client()
35
+ if client.get("provider"):
36
+ client["provider"].force_flush() # Synchronous method
37
+ if client.get("exporter"):
38
+ await client["exporter"].flush()
55
39
 
56
40
 
57
41
  async def shutdown_tracing() -> None:
@@ -59,12 +43,18 @@ async def shutdown_tracing() -> None:
59
43
  Shutdown the tracer provider and exporter.
60
44
  It is not necessary to call this function.
61
45
  """
62
- provider.shutdown() # Synchronous method
63
- await exporter.shutdown()
46
+ try:
47
+ client = get_client()
48
+ if client.get("provider"):
49
+ client["provider"].shutdown() # Synchronous method
50
+ if client.get("exporter"):
51
+ await client["exporter"].shutdown() # async method
52
+ except Exception as e:
53
+ logger.error(f"Error shutting down tracing: {e}", exc_info=True)
64
54
 
65
55
 
66
- # Export provider and exporter for advanced usage
67
- __all__ = ["provider", "exporter", "flush_spans", "shutdown_tracing", "WithTracing", "set_span_attribute", "set_span_name", "get_active_span"]
56
+ # Export provider and exporter accessors for advanced usage
57
+ __all__ = ["get_provider", "get_exporter", "flush_tracing", "shutdown_tracing", "WithTracing", "set_span_attribute", "set_span_name", "get_active_span"]
68
58
 
69
59
 
70
60
  class TracingOptions:
@@ -129,6 +119,181 @@ def _prepare_input(args: tuple, kwargs: dict) -> Any:
129
119
  return result
130
120
 
131
121
 
122
+ def _prepare_and_filter_input(
123
+ args: tuple,
124
+ kwargs: dict,
125
+ filter_input: Optional[Callable[[Any], Any]],
126
+ ignore_input: Optional[Any],
127
+ ) -> Any:
128
+ """Prepare and filter input for span attributes."""
129
+ input_data = _prepare_input(args, kwargs)
130
+ if filter_input:
131
+ input_data = filter_input(input_data)
132
+ if ignore_input and isinstance(input_data, dict):
133
+ for key in ignore_input:
134
+ if key in input_data:
135
+ del input_data[key]
136
+ return input_data
137
+
138
+
139
+ def _prepare_and_filter_output(
140
+ result: Any,
141
+ filter_output: Optional[Callable[[Any], Any]],
142
+ ignore_output: Optional[Any],
143
+ ) -> Any:
144
+ """Prepare and filter output for span attributes."""
145
+ output_data = result
146
+ if filter_output:
147
+ output_data = filter_output(output_data)
148
+ if ignore_output and isinstance(output_data, dict):
149
+ output_data = output_data.copy()
150
+ for key in ignore_output:
151
+ if key in output_data:
152
+ del output_data[key]
153
+ return output_data
154
+
155
+
156
+ def _handle_span_exception(span: trace.Span, exception: Exception) -> None:
157
+ """Record exception on span and set error status."""
158
+ error = exception if isinstance(exception, Exception) else Exception(str(exception))
159
+ span.record_exception(error)
160
+ span.set_status(Status(StatusCode.ERROR, str(error)))
161
+
162
+
163
+ class TracedGenerator:
164
+ """Wrapper for sync generators that traces iteration."""
165
+
166
+ def __init__(
167
+ self,
168
+ generator: Any,
169
+ span: trace.Span,
170
+ fn_name: str,
171
+ filter_output: Optional[Callable[[Any], Any]],
172
+ ignore_output: Optional[Any],
173
+ context_token: Any,
174
+ ):
175
+ self._generator = generator
176
+ self._span = span
177
+ self._fn_name = fn_name
178
+ self._filter_output = filter_output
179
+ self._ignore_output = ignore_output
180
+ self._context_token = context_token
181
+ self._yielded_values = []
182
+ self._exhausted = False
183
+
184
+ def __iter__(self):
185
+ return self
186
+
187
+ def __next__(self):
188
+ if self._exhausted:
189
+ raise StopIteration
190
+
191
+ try:
192
+ value = next(self._generator)
193
+ self._yielded_values.append(value)
194
+ return value
195
+ except StopIteration:
196
+ self._exhausted = True
197
+ self._finalize_span_success()
198
+ trace.context_api.detach(self._context_token)
199
+ self._span.end()
200
+ raise
201
+ except Exception as exception:
202
+ self._exhausted = True
203
+ _handle_span_exception(self._span, exception)
204
+ trace.context_api.detach(self._context_token)
205
+ self._span.end()
206
+ raise
207
+
208
+ def _finalize_span_success(self):
209
+ """Set output and success status on span."""
210
+ # Record summary of yielded values
211
+ output_data = {
212
+ "type": "generator",
213
+ "yielded_count": len(self._yielded_values),
214
+ }
215
+
216
+ # Optionally include sample values (limit to avoid huge spans)
217
+ if self._yielded_values:
218
+ sample_size = min(10, len(self._yielded_values))
219
+ output_data["sample_values"] = [
220
+ _serialize_for_span(v) for v in self._yielded_values[:sample_size]
221
+ ]
222
+ if len(self._yielded_values) > sample_size:
223
+ output_data["truncated"] = True
224
+
225
+ output_data = _prepare_and_filter_output(output_data, self._filter_output, self._ignore_output)
226
+ self._span.set_attribute("output", _serialize_for_span(output_data))
227
+ self._span.set_status(Status(StatusCode.OK))
228
+
229
+
230
+ class TracedAsyncGenerator:
231
+ """Wrapper for async generators that traces iteration."""
232
+
233
+ def __init__(
234
+ self,
235
+ generator: Any,
236
+ span: trace.Span,
237
+ fn_name: str,
238
+ filter_output: Optional[Callable[[Any], Any]],
239
+ ignore_output: Optional[Any],
240
+ context_token: Any,
241
+ ):
242
+ self._generator = generator
243
+ self._span = span
244
+ self._fn_name = fn_name
245
+ self._filter_output = filter_output
246
+ self._ignore_output = ignore_output
247
+ self._context_token = context_token
248
+ self._yielded_values = []
249
+ self._exhausted = False
250
+
251
+ def __aiter__(self):
252
+ return self
253
+
254
+ async def __anext__(self):
255
+ if self._exhausted:
256
+ raise StopAsyncIteration
257
+
258
+ try:
259
+ value = await self._generator.__anext__()
260
+ self._yielded_values.append(value)
261
+ return value
262
+ except StopAsyncIteration:
263
+ self._exhausted = True
264
+ self._finalize_span_success()
265
+ trace.context_api.detach(self._context_token)
266
+ self._span.end()
267
+ raise
268
+ except Exception as exception:
269
+ self._exhausted = True
270
+ _handle_span_exception(self._span, exception)
271
+ trace.context_api.detach(self._context_token)
272
+ self._span.end()
273
+ raise
274
+
275
+ def _finalize_span_success(self):
276
+ """Set output and success status on span."""
277
+ # Record summary of yielded values
278
+ output_data = {
279
+ "type": "async_generator",
280
+ "yielded_count": len(self._yielded_values),
281
+ }
282
+
283
+ # Optionally include sample values (limit to avoid huge spans)
284
+ if self._yielded_values:
285
+ sample_size = min(10, len(self._yielded_values))
286
+ output_data["sample_values"] = [
287
+ _serialize_for_span(v) for v in self._yielded_values[:sample_size]
288
+ ]
289
+ if len(self._yielded_values) > sample_size:
290
+ output_data["truncated"] = True
291
+
292
+ output_data = _prepare_and_filter_output(output_data, self._filter_output, self._ignore_output)
293
+ self._span.set_attribute("output", _serialize_for_span(output_data))
294
+ self._span.set_status(Status(StatusCode.OK))
295
+
296
+
132
297
  def WithTracing(
133
298
  func: Optional[Callable] = None,
134
299
  *,
@@ -142,7 +307,7 @@ def WithTracing(
142
307
  Decorator to automatically create spans for function calls.
143
308
  Records input/output as span attributes. Spans are automatically linked via OpenTelemetry context.
144
309
 
145
- Works with both synchronous and asynchronous functions.
310
+ Works with synchronous functions, asynchronous functions, generator functions, and async generator functions.
146
311
 
147
312
  Args:
148
313
  func: The function to trace (when used as @WithTracing)
@@ -161,6 +326,16 @@ def WithTracing(
161
326
  async def my_async_function(x, y):
162
327
  return x + y
163
328
 
329
+ @WithTracing
330
+ def my_generator(n):
331
+ for i in range(n):
332
+ yield i * 2
333
+
334
+ @WithTracing
335
+ async def my_async_generator(n):
336
+ for i in range(n):
337
+ yield i * 2
338
+
164
339
  @WithTracing(name="custom_name")
165
340
  def another_function():
166
341
  pass
@@ -174,64 +349,137 @@ def WithTracing(
174
349
  return fn
175
350
 
176
351
  is_async = inspect.iscoroutinefunction(fn)
352
+ is_generator = inspect.isgeneratorfunction(fn)
353
+ is_async_generator = inspect.isasyncgenfunction(fn) if hasattr(inspect, 'isasyncgenfunction') else False
354
+
355
+ tracer = trace.get_tracer(AIQA_TRACER_NAME)
356
+
357
+ def _setup_span(span: trace.Span, input_data: Any) -> bool:
358
+ """Setup span with input data. Returns True if span is recording."""
359
+ if not span.is_recording():
360
+ logger.warning(f"Span {fn_name} is not recording - will not be exported")
361
+ return False
362
+
363
+ logger.debug(f"Span {fn_name} is recording, trace_id={format(span.get_span_context().trace_id, '032x')}")
364
+
365
+ if input_data is not None:
366
+ span.set_attribute("input", _serialize_for_span(input_data))
367
+
368
+ trace_id = format(span.get_span_context().trace_id, "032x")
369
+ logger.debug(f"do traceable stuff {fn_name} {trace_id}")
370
+ return True
371
+
372
+ def _finalize_span_success(span: trace.Span, result: Any) -> None:
373
+ """Set output and success status on span."""
374
+ output_data = _prepare_and_filter_output(result, filter_output, ignore_output)
375
+ span.set_attribute("output", _serialize_for_span(output_data))
376
+ span.set_status(Status(StatusCode.OK))
377
+
378
+ def _execute_with_span_sync(executor: Callable[[], Any], input_data: Any) -> Any:
379
+ """Execute sync function within span context, handling input/output and exceptions."""
380
+ with tracer.start_as_current_span(fn_name) as span:
381
+ if not _setup_span(span, input_data):
382
+ return executor()
383
+
384
+ try:
385
+ result = executor()
386
+ _finalize_span_success(span, result)
387
+ return result
388
+ except Exception as exception:
389
+ _handle_span_exception(span, exception)
390
+ raise
391
+
392
+ async def _execute_with_span_async(executor: Callable[[], Any], input_data: Any) -> Any:
393
+ """Execute async function within span context, handling input/output and exceptions."""
394
+ with tracer.start_as_current_span(fn_name) as span:
395
+ if not _setup_span(span, input_data):
396
+ return await executor()
397
+
398
+ try:
399
+ result = await executor()
400
+ _finalize_span_success(span, result)
401
+ logger.debug(f"Span {fn_name} completed successfully, is_recording={span.is_recording()}")
402
+ return result
403
+ except Exception as exception:
404
+ _handle_span_exception(span, exception)
405
+ raise
406
+ finally:
407
+ logger.debug(f"Span {fn_name} context exiting, is_recording={span.is_recording()}")
408
+
409
+ def _execute_generator_sync(executor: Callable[[], Any], input_data: Any) -> Any:
410
+ """Execute sync generator function, returning a traced generator."""
411
+ # Create span but don't use 'with' - span will be closed by TracedGenerator
412
+ span = tracer.start_span(fn_name)
413
+ token = trace.context_api.attach(trace.context_api.set_span_in_context(span))
414
+
415
+ try:
416
+ if not _setup_span(span, input_data):
417
+ generator = executor()
418
+ trace.context_api.detach(token)
419
+ span.end()
420
+ return generator
421
+
422
+ generator = executor()
423
+ return TracedGenerator(generator, span, fn_name, filter_output, ignore_output, token)
424
+ except Exception as exception:
425
+ trace.context_api.detach(token)
426
+ _handle_span_exception(span, exception)
427
+ span.end()
428
+ raise
429
+
430
+ async def _execute_generator_async(executor: Callable[[], Any], input_data: Any) -> Any:
431
+ """Execute async generator function, returning a traced async generator."""
432
+ # Create span but don't use 'with' - span will be closed by TracedAsyncGenerator
433
+ span = tracer.start_span(fn_name)
434
+ token = trace.context_api.attach(trace.context_api.set_span_in_context(span))
435
+
436
+ try:
437
+ if not _setup_span(span, input_data):
438
+ generator = await executor()
439
+ trace.context_api.detach(token)
440
+ span.end()
441
+ return generator
442
+
443
+ generator = await executor()
444
+ return TracedAsyncGenerator(generator, span, fn_name, filter_output, ignore_output, token)
445
+ except Exception as exception:
446
+ trace.context_api.detach(token)
447
+ _handle_span_exception(span, exception)
448
+ span.end()
449
+ raise
177
450
 
178
- if is_async:
451
+ if is_async_generator:
452
+ @wraps(fn)
453
+ async def async_gen_traced_fn(*args, **kwargs):
454
+ input_data = _prepare_and_filter_input(args, kwargs, filter_input, ignore_input)
455
+ return await _execute_generator_async(
456
+ lambda: fn(*args, **kwargs),
457
+ input_data
458
+ )
459
+
460
+ async_gen_traced_fn._is_traced = True
461
+ logger.debug(f"Function {fn_name} is now traced (async generator)")
462
+ return async_gen_traced_fn
463
+ elif is_generator:
464
+ @wraps(fn)
465
+ def gen_traced_fn(*args, **kwargs):
466
+ input_data = _prepare_and_filter_input(args, kwargs, filter_input, ignore_input)
467
+ return _execute_generator_sync(
468
+ lambda: fn(*args, **kwargs),
469
+ input_data
470
+ )
471
+
472
+ gen_traced_fn._is_traced = True
473
+ logger.debug(f"Function {fn_name} is now traced (generator)")
474
+ return gen_traced_fn
475
+ elif is_async:
179
476
  @wraps(fn)
180
477
  async def async_traced_fn(*args, **kwargs):
181
- # Prepare input
182
- input_data = _prepare_input(args, kwargs)
183
- if filter_input:
184
- input_data = filter_input(input_data)
185
- if ignore_input and isinstance(input_data, dict):
186
- for key in ignore_input:
187
- if key in input_data:
188
- del input_data[key]
189
-
190
- # Use start_as_current_span to ensure span is recorded by BatchSpanProcessor
191
- with tracer.start_as_current_span(fn_name) as span:
192
- # Check if span is recording - if not, spans won't be exported
193
- if not span.is_recording():
194
- logger.warning(f"Span {fn_name} is not recording - will not be exported")
195
- return await fn(*args, **kwargs)
196
-
197
- logger.debug(f"Span {fn_name} is recording, trace_id={format(span.get_span_context().trace_id, '032x')}")
198
-
199
- if input_data is not None:
200
- # Serialize for span attributes (OpenTelemetry only accepts primitives or JSON strings)
201
- serialized_input = _serialize_for_span(input_data)
202
- span.set_attribute("input", serialized_input)
203
-
204
- try:
205
- # Call the function within the span context
206
- trace_id = format(span.get_span_context().trace_id, "032x")
207
- logger.debug(f"do traceable stuff {fn_name} {trace_id}")
208
-
209
- result = await fn(*args, **kwargs)
210
-
211
- # Prepare output
212
- output_data = result
213
- if filter_output:
214
- output_data = filter_output(output_data)
215
- if ignore_output and isinstance(output_data, dict):
216
- # Make a copy of output_data to avoid modifying the original
217
- output_data = output_data.copy()
218
- for key in ignore_output:
219
- if key in output_data:
220
- del output_data[key]
221
-
222
- span.set_attribute("output", _serialize_for_span(output_data))
223
- span.set_status(Status(StatusCode.OK))
224
-
225
- logger.debug(f"Span {fn_name} completed successfully, is_recording={span.is_recording()}")
226
- return result
227
- except Exception as exception:
228
- error = exception if isinstance(exception, Exception) else Exception(str(exception))
229
- span.record_exception(error)
230
- span.set_status(Status(StatusCode.ERROR, str(error)))
231
- raise
232
- finally:
233
- # Log when span context exits - span should be ended automatically by context manager
234
- logger.debug(f"Span {fn_name} context exiting, is_recording={span.is_recording()}")
478
+ input_data = _prepare_and_filter_input(args, kwargs, filter_input, ignore_input)
479
+ return await _execute_with_span_async(
480
+ lambda: fn(*args, **kwargs),
481
+ input_data
482
+ )
235
483
 
236
484
  async_traced_fn._is_traced = True
237
485
  logger.debug(f"Function {fn_name} is now traced (async)")
@@ -239,56 +487,11 @@ def WithTracing(
239
487
  else:
240
488
  @wraps(fn)
241
489
  def sync_traced_fn(*args, **kwargs):
242
- # Prepare input
243
- input_data = _prepare_input(args, kwargs)
244
- if filter_input:
245
- input_data = filter_input(input_data)
246
- if ignore_input and isinstance(input_data, dict):
247
- for key in ignore_input:
248
- if key in input_data:
249
- del input_data[key]
250
-
251
- # Use start_as_current_span to ensure span is recorded by BatchSpanProcessor
252
- with tracer.start_as_current_span(fn_name) as span:
253
- # Check if span is recording - if not, spans won't be exported
254
- if not span.is_recording():
255
- logger.warning(f"Span {fn_name} is not recording - will not be exported")
256
- return fn(*args, **kwargs)
257
-
258
- logger.debug(f"Span {fn_name} is recording, trace_id={format(span.get_span_context().trace_id, '032x')}")
259
-
260
- if input_data is not None:
261
- # Serialize for span attributes (OpenTelemetry only accepts primitives or JSON strings)
262
- serialized_input = _serialize_for_span(input_data)
263
- span.set_attribute("input", serialized_input)
264
-
265
- try:
266
- # Call the function within the span context
267
- trace_id = format(span.get_span_context().trace_id, "032x")
268
- logger.debug(f"do traceable stuff {fn_name} {trace_id}")
269
-
270
- result = fn(*args, **kwargs)
271
-
272
- # Prepare output
273
- output_data = result
274
- if filter_output:
275
- output_data = filter_output(output_data)
276
- if ignore_output and isinstance(output_data, dict):
277
- # Make a copy of output_data to avoid modifying the original
278
- output_data = output_data.copy()
279
- for key in ignore_output:
280
- if key in output_data:
281
- del output_data[key]
282
-
283
- span.set_attribute("output", _serialize_for_span(output_data))
284
- span.set_status(Status(StatusCode.OK))
285
-
286
- return result
287
- except Exception as exception:
288
- error = exception if isinstance(exception, Exception) else Exception(str(exception))
289
- span.record_exception(error)
290
- span.set_status(Status(StatusCode.ERROR, str(error)))
291
- raise
490
+ input_data = _prepare_and_filter_input(args, kwargs, filter_input, ignore_input)
491
+ return _execute_with_span_sync(
492
+ lambda: fn(*args, **kwargs),
493
+ input_data
494
+ )
292
495
 
293
496
  sync_traced_fn._is_traced = True
294
497
  logger.debug(f"Function {fn_name} is now traced (sync)")
@@ -328,3 +531,13 @@ def get_active_span() -> Optional[trace.Span]:
328
531
  """Get the currently active span."""
329
532
  return trace.get_current_span()
330
533
 
534
+ def get_provider() -> Optional[TracerProvider]:
535
+ """Get the tracer provider for advanced usage."""
536
+ client = get_client()
537
+ return client.get("provider")
538
+
539
+ def get_exporter() -> Optional[AIQASpanExporter]:
540
+ """Get the exporter for advanced usage."""
541
+ client = get_client()
542
+ return client.get("exporter")
543
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiqa-client
3
- Version: 0.1.6
3
+ Version: 0.2.1
4
4
  Summary: OpenTelemetry-based Python client for tracing functions and sending traces to the AIQA server
5
5
  Author-email: AIQA <info@aiqa.dev>
6
6
  License: MIT
@@ -0,0 +1,10 @@
1
+ aiqa/__init__.py,sha256=Co9t4kYAXI4WKhnTlIhIcQTeJJgFF0mzgmj0yhaS6p4,539
2
+ aiqa/aiqa_exporter.py,sha256=aEXMfdUL2OILt9H4CRkpoi9EgOqr1UthyQFrimZoDFk,19200
3
+ aiqa/client.py,sha256=1QSZhPdpRJilASuS7YtYlzcTNARY0O6lnQB7m2Jm-jA,1421
4
+ aiqa/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ aiqa/tracing.py,sha256=rtMXbcU13nQYepV-KAP5Sugui8Ox-eQCgksRp3VlwpM,20367
6
+ aiqa_client-0.2.1.dist-info/licenses/LICENSE,sha256=kIzkzLuzG0HHaWYm4F4W5FeJ1Yxut3Ec6bhLWyw798A,1062
7
+ aiqa_client-0.2.1.dist-info/METADATA,sha256=WgHjtG5pmyViDill6UnD3DjzE3mLl_ZLE-rE7x2ECak,3772
8
+ aiqa_client-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ aiqa_client-0.2.1.dist-info/top_level.txt,sha256=nwcsuVVSuWu27iLxZd4n1evVzv1W6FVTrSnCXCc-NQs,5
10
+ aiqa_client-0.2.1.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- aiqa/__init__.py,sha256=KXcAhHf_aZCgs6bn5Gw3IHccunqZzjbj8md0RuWSRAg,470
2
- aiqa/aiqa_exporter.py,sha256=vXyX6Q_iOjrDz3tCPOMXuBTQg7ocACdOOqzpkUqhy9g,19131
3
- aiqa/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- aiqa/tracing.py,sha256=mWYIZ2Pla91oUY5a6OXOJWUR2eYtjoQkYPaJdBTpho8,13522
5
- aiqa_client-0.1.6.dist-info/licenses/LICENSE,sha256=kIzkzLuzG0HHaWYm4F4W5FeJ1Yxut3Ec6bhLWyw798A,1062
6
- aiqa_client-0.1.6.dist-info/METADATA,sha256=Yu-H7t101WTN5rdwJl8-7AAVemL9hHZVxSGjZifurKk,3772
7
- aiqa_client-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- aiqa_client-0.1.6.dist-info/top_level.txt,sha256=nwcsuVVSuWu27iLxZd4n1evVzv1W6FVTrSnCXCc-NQs,5
9
- aiqa_client-0.1.6.dist-info/RECORD,,