lmnr 0.6.7__tar.gz → 0.6.8__tar.gz

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.
Files changed (55) hide show
  1. {lmnr-0.6.7 → lmnr-0.6.8}/PKG-INFO +1 -1
  2. {lmnr-0.6.7 → lmnr-0.6.8}/pyproject.toml +1 -1
  3. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/decorators/__init__.py +12 -15
  4. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/tracing/context_properties.py +6 -0
  5. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/decorators.py +12 -0
  6. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/laminar.py +145 -28
  7. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/utils.py +15 -0
  8. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/version.py +1 -1
  9. {lmnr-0.6.7 → lmnr-0.6.8}/LICENSE +0 -0
  10. {lmnr-0.6.7 → lmnr-0.6.8}/README.md +0 -0
  11. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/__init__.py +0 -0
  12. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/cli.py +0 -0
  13. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/.flake8 +0 -0
  14. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/__init__.py +0 -0
  15. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +0 -0
  16. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py +0 -0
  17. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +0 -0
  18. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/tracing/__init__.py +0 -0
  19. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +0 -0
  20. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/tracing/attributes.py +0 -0
  21. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/tracing/exporter.py +0 -0
  22. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/tracing/instruments.py +0 -0
  23. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/tracing/processor.py +0 -0
  24. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/tracing/tracer.py +0 -0
  25. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/utils/__init__.py +0 -0
  26. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/utils/json_encoder.py +0 -0
  27. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/opentelemetry_lib/utils/package_check.py +0 -0
  28. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/py.typed +0 -0
  29. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/__init__.py +0 -0
  30. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/browser/__init__.py +0 -0
  31. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/browser/browser_use_otel.py +0 -0
  32. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/browser/patchright_otel.py +0 -0
  33. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/browser/playwright_otel.py +0 -0
  34. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/browser/pw_utils.py +0 -0
  35. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/browser/rrweb/rrweb.umd.min.cjs +0 -0
  36. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/browser/utils.py +0 -0
  37. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/asynchronous/async_client.py +0 -0
  38. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/asynchronous/resources/__init__.py +0 -0
  39. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/asynchronous/resources/agent.py +0 -0
  40. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/asynchronous/resources/base.py +0 -0
  41. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/asynchronous/resources/browser_events.py +0 -0
  42. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/asynchronous/resources/evals.py +0 -0
  43. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/asynchronous/resources/tags.py +0 -0
  44. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/synchronous/resources/__init__.py +0 -0
  45. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/synchronous/resources/agent.py +0 -0
  46. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/synchronous/resources/base.py +0 -0
  47. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/synchronous/resources/browser_events.py +0 -0
  48. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/synchronous/resources/evals.py +0 -0
  49. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/synchronous/resources/tags.py +0 -0
  50. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/client/synchronous/sync_client.py +0 -0
  51. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/datasets.py +0 -0
  52. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/eval_control.py +0 -0
  53. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/evaluations.py +0 -0
  54. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/log.py +0 -0
  55. {lmnr-0.6.7 → lmnr-0.6.8}/src/lmnr/sdk/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lmnr
3
- Version: 0.6.7
3
+ Version: 0.6.8
4
4
  Summary: Python SDK for Laminar
5
5
  License: Apache-2.0
6
6
  Author: lmnr.ai
@@ -6,7 +6,7 @@
6
6
 
7
7
  [project]
8
8
  name = "lmnr"
9
- version = "0.6.7"
9
+ version = "0.6.8"
10
10
  description = "Python SDK for Laminar"
11
11
  authors = [
12
12
  { name = "lmnr.ai", email = "founders@lmnr.ai" }
@@ -9,14 +9,15 @@ from opentelemetry import trace
9
9
  from opentelemetry import context as context_api
10
10
  from opentelemetry.trace import Span
11
11
 
12
- from lmnr.opentelemetry_lib.tracing.context_properties import (
13
- remove_association_properties,
14
- update_association_properties,
15
- )
16
12
  from lmnr.sdk.utils import get_input_from_func_args, is_method
17
13
  from lmnr.opentelemetry_lib import MAX_MANUAL_SPAN_PAYLOAD_SIZE
18
14
  from lmnr.opentelemetry_lib.tracing.tracer import get_tracer
19
- from lmnr.opentelemetry_lib.tracing.attributes import SPAN_INPUT, SPAN_OUTPUT, SPAN_TYPE
15
+ from lmnr.opentelemetry_lib.tracing.attributes import (
16
+ ASSOCIATION_PROPERTIES,
17
+ SPAN_INPUT,
18
+ SPAN_OUTPUT,
19
+ SPAN_TYPE,
20
+ )
20
21
  from lmnr.opentelemetry_lib.tracing import TracerWrapper
21
22
  from lmnr.opentelemetry_lib.utils.json_encoder import JSONEncoder
22
23
 
@@ -56,11 +57,11 @@ def entity_method(
56
57
 
57
58
  span_name = name or fn.__name__
58
59
 
59
- if association_properties is not None:
60
- update_association_properties(association_properties)
61
-
62
60
  with get_tracer() as tracer:
63
61
  span = tracer.start_span(span_name, attributes={SPAN_TYPE: span_type})
62
+ if association_properties is not None:
63
+ for key, value in association_properties.items():
64
+ span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{key}", value)
64
65
 
65
66
  ctx = trace.set_span_in_context(span, context_api.get_current())
66
67
  ctx_token = context_api.attach(ctx)
@@ -119,8 +120,6 @@ def entity_method(
119
120
 
120
121
  span.end()
121
122
  context_api.detach(ctx_token)
122
- if association_properties is not None:
123
- remove_association_properties(association_properties)
124
123
  return res
125
124
 
126
125
  return wrap
@@ -145,11 +144,11 @@ def aentity_method(
145
144
 
146
145
  span_name = name or fn.__name__
147
146
 
148
- if association_properties is not None:
149
- update_association_properties(association_properties)
150
-
151
147
  with get_tracer() as tracer:
152
148
  span = tracer.start_span(span_name, attributes={SPAN_TYPE: span_type})
149
+ if association_properties is not None:
150
+ for key, value in association_properties.items():
151
+ span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{key}", value)
153
152
 
154
153
  ctx = trace.set_span_in_context(span, context_api.get_current())
155
154
  ctx_token = context_api.attach(ctx)
@@ -200,8 +199,6 @@ def aentity_method(
200
199
  pass
201
200
 
202
201
  span.end()
203
- if association_properties is not None:
204
- remove_association_properties(association_properties)
205
202
  context_api.detach(ctx_token)
206
203
 
207
204
  return res
@@ -10,6 +10,8 @@ from opentelemetry.trace import Span
10
10
  from opentelemetry import trace
11
11
 
12
12
 
13
+ # TODO: delete this once deprecated Laminar.with_labels is removed. The logic
14
+ # should be moved into Laminar.set_tracing_level
13
15
  def set_association_properties(properties: dict) -> None:
14
16
  attach(set_value("association_properties", properties))
15
17
 
@@ -17,10 +19,13 @@ def set_association_properties(properties: dict) -> None:
17
19
  _set_association_properties_attributes(span, properties)
18
20
 
19
21
 
22
+ # TODO: delete this once deprecated Laminar.with_labels is removed
20
23
  def get_association_properties(context: Context | None = None) -> dict:
21
24
  return get_value("association_properties", context) or {}
22
25
 
23
26
 
27
+ # TODO: delete this once deprecated Laminar.with_labels is removed. The logic
28
+ # should be moved into Laminar.set_tracing_level
24
29
  def update_association_properties(
25
30
  properties: dict,
26
31
  set_on_current_span: bool = True,
@@ -37,6 +42,7 @@ def update_association_properties(
37
42
  _set_association_properties_attributes(span, properties)
38
43
 
39
44
 
45
+ # TODO: this logic should be moved into Laminar.set_tracing_level
40
46
  def remove_association_properties(properties: dict) -> None:
41
47
  props: dict = copy.copy(get_value("association_properties") or {})
42
48
  for k in properties.keys():
@@ -9,9 +9,11 @@ from typing import Any, Callable, Literal, TypeVar, cast
9
9
  from typing_extensions import ParamSpec
10
10
 
11
11
  from lmnr.opentelemetry_lib.tracing.attributes import SESSION_ID
12
+ from lmnr.sdk.log import get_default_logger
12
13
 
13
14
  from .utils import is_async
14
15
 
16
+ logger = get_default_logger(__name__)
15
17
 
16
18
  P = ParamSpec("P")
17
19
  R = TypeVar("R")
@@ -27,6 +29,7 @@ def observe(
27
29
  span_type: Literal["DEFAULT", "LLM", "TOOL"] = "DEFAULT",
28
30
  ignore_inputs: list[str] | None = None,
29
31
  metadata: dict[str, Any] | None = None,
32
+ tags: list[str] | None = None,
30
33
  ) -> Callable[[Callable[P, R]], Callable[P, R]]:
31
34
  """The main decorator entrypoint for Laminar. This is used to wrap
32
35
  functions and methods to create spans.
@@ -52,6 +55,8 @@ def observe(
52
55
  this argument. Defaults to None.
53
56
  metadata (dict[str, Any] | None, optional): Metadata to associate with\
54
57
  the trace. Must be JSON serializable. Defaults to None.
58
+ tags (list[str] | None, optional): Tags to associate with the trace.
59
+ Defaults to None.
55
60
  Raises:
56
61
  Exception: re-raises the exception if the wrapped function raises an\
57
62
  exception
@@ -79,6 +84,13 @@ def observe(
79
84
  for k, v in metadata.items()
80
85
  }
81
86
  )
87
+ if tags is not None:
88
+ if not isinstance(tags, list) or not all(
89
+ isinstance(tag, str) for tag in tags
90
+ ):
91
+ logger.warning("Tags must be a list of strings. Tags will be ignored.")
92
+ else:
93
+ association_properties["tags"] = tags
82
94
  result = (
83
95
  aentity_method(
84
96
  name=name,
@@ -1,10 +1,13 @@
1
1
  from contextlib import contextmanager
2
2
  from contextvars import Context
3
+ import warnings
4
+ from typing_extensions import deprecated
3
5
  from lmnr.opentelemetry_lib import TracerManager
4
6
  from lmnr.opentelemetry_lib.tracing.instruments import Instruments
5
7
  from lmnr.opentelemetry_lib.tracing.tracer import get_tracer
6
8
  from lmnr.opentelemetry_lib.tracing.attributes import (
7
9
  ASSOCIATION_PROPERTIES,
10
+ USER_ID,
8
11
  Attributes,
9
12
  SPAN_TYPE,
10
13
  )
@@ -18,7 +21,6 @@ from opentelemetry.util.types import AttributeValue
18
21
 
19
22
  from typing import Any, Literal
20
23
 
21
- import copy
22
24
  import datetime
23
25
  import logging
24
26
  import os
@@ -37,7 +39,7 @@ from lmnr.opentelemetry_lib.tracing.context_properties import (
37
39
  set_association_properties,
38
40
  update_association_properties,
39
41
  )
40
- from lmnr.sdk.utils import from_env
42
+ from lmnr.sdk.utils import from_env, is_otel_attribute_value_type
41
43
 
42
44
  from .log import VerboseColorfulFormatter
43
45
 
@@ -224,6 +226,7 @@ class Laminar:
224
226
  context: Context | None = None,
225
227
  labels: list[str] | None = None,
226
228
  parent_span_context: LaminarSpanContext | None = None,
229
+ tags: list[str] | None = None,
227
230
  ):
228
231
  """Start a new span as the current span. Useful for manual
229
232
  instrumentation. If `span_type` is set to `"LLM"`, you should report
@@ -256,8 +259,10 @@ class Laminar:
256
259
  `Laminar.get_span_context`, `Laminar.get_span_context_dict` and\
257
260
  `Laminar.get_span_context_str` for more information.
258
261
  Defaults to None.
259
- labels (list[str] | None, optional): labels to set for the\
260
- span. Defaults to None.
262
+ labels (list[str] | None, optional): [DEPRECATED] Use tags\
263
+ instead. Labels to set for the span. Defaults to None.
264
+ tags (list[str] | None, optional): tags to set for the span.
265
+ Defaults to None.
261
266
  """
262
267
 
263
268
  if not cls.is_initialized():
@@ -283,18 +288,32 @@ class Laminar:
283
288
  label_props = {}
284
289
  try:
285
290
  if labels:
291
+ warnings.warn(
292
+ "`Laminar.start_as_current_span` `labels` is deprecated. Use `tags` instead.",
293
+ DeprecationWarning,
294
+ )
286
295
  label_props = {f"{ASSOCIATION_PROPERTIES}.labels": labels}
287
296
  except Exception:
288
297
  cls.__logger.warning(
289
298
  f"`start_as_current_span` Could not set labels: {labels}. "
290
299
  "They will be propagated to the next span."
291
300
  )
301
+ tag_props = {}
302
+ if tags:
303
+ if isinstance(tags, list) and all(isinstance(tag, str) for tag in tags):
304
+ tag_props = {f"{ASSOCIATION_PROPERTIES}.tags": tags}
305
+ else:
306
+ cls.__logger.warning(
307
+ f"`start_as_current_span` Could not set tags: {tags}. Tags must be a list of strings. "
308
+ "Tags will be ignored."
309
+ )
292
310
  with tracer.start_as_current_span(
293
311
  name,
294
312
  context=ctx,
295
313
  attributes={
296
314
  SPAN_TYPE: span_type,
297
315
  **(label_props),
316
+ **(tag_props),
298
317
  },
299
318
  ) as span:
300
319
  if input is not None:
@@ -319,6 +338,10 @@ class Laminar:
319
338
 
320
339
  @classmethod
321
340
  @contextmanager
341
+ @deprecated(
342
+ "Use `Laminar.set_span_tags` or the `tags` argument of "
343
+ "`Laminar.start_as_current_span` or `Laminar.start_span` instead"
344
+ )
322
345
  def with_labels(cls, labels: list[str], context: Context | None = None):
323
346
  """Set labels for spans within this `with` context. This is useful for
324
347
  adding labels to the spans created in the auto-instrumentations.
@@ -334,6 +357,11 @@ class Laminar:
334
357
  openai_client.chat.completions.create()
335
358
  ```
336
359
  """
360
+ warnings.warn(
361
+ "`Laminar.with_labels` is deprecated. Use `Laminar.set_span_tags` or the `tags` argument of "
362
+ "`Laminar.start_as_current_span` or `Laminar.start_span` instead",
363
+ DeprecationWarning,
364
+ )
337
365
  if not cls.is_initialized():
338
366
  yield
339
367
  return
@@ -365,6 +393,7 @@ class Laminar:
365
393
  context: Context | None = None,
366
394
  parent_span_context: LaminarSpanContext | None = None,
367
395
  labels: dict[str, str] | None = None,
396
+ tags: list[str] | None = None,
368
397
  ):
369
398
  """Start a new span. Useful for manual instrumentation.
370
399
  If `span_type` is set to `"LLM"`, you should report usage and response
@@ -416,8 +445,10 @@ class Laminar:
416
445
  `Laminar.get_span_context`, `Laminar.get_span_context_dict` and\
417
446
  `Laminar.get_span_context_str` for more information.
418
447
  Defaults to None.
419
- labels (dict[str, str] | None, optional): labels to set for the\
420
- span. Defaults to None.
448
+ tags (list[str] | None, optional): tags to set for the span.
449
+ Defaults to None.
450
+ labels (dict[str, str] | None, optional): [DEPRECATED] Use tags\
451
+ instead. Labels to set for the span. Defaults to None.
421
452
  """
422
453
  if not cls.is_initialized():
423
454
  return trace.NonRecordingSpan(
@@ -440,6 +471,10 @@ class Laminar:
440
471
  label_props = {}
441
472
  try:
442
473
  if labels:
474
+ warnings.warn(
475
+ "`Laminar.start_span` `labels` is deprecated. Use `tags` instead.",
476
+ DeprecationWarning,
477
+ )
443
478
  label_props = {
444
479
  f"{ASSOCIATION_PROPERTIES}.labels": json_dumps(labels)
445
480
  }
@@ -448,12 +483,22 @@ class Laminar:
448
483
  f"`start_span` Could not set labels: {labels}. They will be "
449
484
  "propagated to the next span."
450
485
  )
486
+ tag_props = {}
487
+ if tags:
488
+ if isinstance(tags, list) and all(isinstance(tag, str) for tag in tags):
489
+ tag_props = {f"{ASSOCIATION_PROPERTIES}.tags": tags}
490
+ else:
491
+ cls.__logger.warning(
492
+ f"`start_span` Could not set tags: {tags}. Tags must be a list of strings. "
493
+ + "Tags will be ignored."
494
+ )
451
495
  span = tracer.start_span(
452
496
  name,
453
497
  context=ctx,
454
498
  attributes={
455
499
  SPAN_TYPE: span_type,
456
500
  **(label_props),
501
+ **(tag_props),
457
502
  },
458
503
  )
459
504
  if input is not None:
@@ -562,7 +607,6 @@ class Laminar:
562
607
  cls.__logger.warning(
563
608
  f"Attribute {key} is not a valid Laminar attribute."
564
609
  )
565
- continue
566
610
  if not isinstance(value, (str, int, float, bool)):
567
611
  span.set_attribute(key.value, json_dumps(value))
568
612
  else:
@@ -649,11 +693,30 @@ class Laminar:
649
693
  TracerManager.shutdown()
650
694
 
651
695
  @classmethod
696
+ def set_span_tags(cls, tags: list[str]):
697
+ """Set the tags for the current span.
698
+
699
+ Args:
700
+ tags (list[str]): Tags to set for the span.
701
+ """
702
+ span = trace.get_current_span()
703
+ if span == trace.INVALID_SPAN:
704
+ cls.__logger.warning("No active span to set tags on")
705
+ return
706
+ if not isinstance(tags, list) or not all(isinstance(tag, str) for tag in tags):
707
+ cls.__logger.warning(
708
+ "Tags must be a list of strings. Tags will be ignored."
709
+ )
710
+ return
711
+ span.set_attribute(f"{ASSOCIATION_PROPERTIES}.tags", tags)
712
+
713
+ @classmethod
714
+ @deprecated("Use `Laminar.set_trace_session_id` instead")
652
715
  def set_session(
653
716
  cls,
654
717
  session_id: str | None = None,
655
718
  ):
656
- """Set the session and user id for the current span and the context
719
+ """Set the session id for the current span and the context
657
720
  (i.e. any children spans created from the current span in the current
658
721
  thread).
659
722
 
@@ -663,12 +726,53 @@ class Laminar:
663
726
  sessions/conversations.
664
727
  Defaults to None.
665
728
  """
729
+ warnings.warn(
730
+ "`Laminar.set_session` is deprecated. Use `Laminar.set_trace_session_id` instead",
731
+ DeprecationWarning,
732
+ )
666
733
  association_properties = {}
667
734
  if session_id is not None:
668
735
  association_properties[SESSION_ID] = session_id
669
- update_association_properties(association_properties)
736
+ # update_association_properties(association_properties)
737
+ span = trace.get_current_span()
738
+ if span == trace.INVALID_SPAN:
739
+ cls.__logger.warning("No active span to set session id on")
740
+ return
741
+ if session_id is not None:
742
+ span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{SESSION_ID}", session_id)
743
+
744
+ @classmethod
745
+ def set_trace_session_id(cls, session_id: str | None = None):
746
+ """Set the session id for the current trace.
747
+ Overrides any existing session id.
748
+
749
+ Args:
750
+ session_id (str | None, optional): Custom session id. Defaults to None.
751
+ """
752
+ span = trace.get_current_span()
753
+ if span == trace.INVALID_SPAN:
754
+ cls.__logger.warning("No active span to set session id on")
755
+ return
756
+ if session_id is not None:
757
+ span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{SESSION_ID}", session_id)
670
758
 
671
759
  @classmethod
760
+ def set_trace_user_id(cls, user_id: str | None = None):
761
+ """Set the user id for the current trace.
762
+ Overrides any existing user id.
763
+
764
+ Args:
765
+ user_id (str | None, optional): Custom user id. Defaults to None.
766
+ """
767
+ span = trace.get_current_span()
768
+ if span == trace.INVALID_SPAN:
769
+ cls.__logger.warning("No active span to set user id on")
770
+ return
771
+ if user_id is not None:
772
+ span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{USER_ID}", user_id)
773
+
774
+ @classmethod
775
+ @deprecated("Use `Laminar.set_trace_metadata` instead")
672
776
  def set_metadata(cls, metadata: dict[str, str]):
673
777
  """Set the metadata for the current trace.
674
778
 
@@ -676,25 +780,37 @@ class Laminar:
676
780
  metadata (dict[str, str]): Metadata to set for the trace. Will be\
677
781
  sent as attributes, so must be json serializable.
678
782
  """
783
+ warnings.warn(
784
+ "`Laminar.set_metadata` is deprecated. Use `Laminar.set_trace_metadata` instead",
785
+ DeprecationWarning,
786
+ )
679
787
  props = {f"metadata.{k}": json_dumps(v) for k, v in metadata.items()}
680
- update_association_properties(props)
788
+ # update_association_properties(props)
789
+ span = trace.get_current_span()
790
+ if span == trace.INVALID_SPAN:
791
+ cls.__logger.warning("No active span to set metadata on")
792
+ return
793
+ for key, value in props.items():
794
+ span.set_attribute(key, value)
681
795
 
682
796
  @classmethod
683
- def clear_metadata(cls):
684
- """Clear the metadata from the context"""
685
- props: dict = copy.copy(context_api.get_value("association_properties"))
686
- metadata_keys = [k for k in props.keys() if k.startswith("metadata.")]
687
- for k in metadata_keys:
688
- props.pop(k)
689
- set_association_properties(props)
797
+ def set_trace_metadata(cls, metadata: dict[str, AttributeValue]):
798
+ """Set the metadata for the current trace.
690
799
 
691
- @classmethod
692
- def clear_session(cls):
693
- """Clear the session and user id from the context"""
694
- props: dict = copy.copy(context_api.get_value("association_properties"))
695
- props.pop("session_id", None)
696
- props.pop("user_id", None)
697
- set_association_properties(props)
800
+ Args:
801
+ metadata (dict[str, AttributeValue]): Metadata to set for the trace.
802
+ """
803
+ span = trace.get_current_span()
804
+ if span == trace.INVALID_SPAN:
805
+ cls.__logger.warning("No active span to set metadata on")
806
+ return
807
+ for key, value in metadata.items():
808
+ if is_otel_attribute_value_type(value):
809
+ span.set_attribute(f"{ASSOCIATION_PROPERTIES}.metadata.{key}", value)
810
+ else:
811
+ span.set_attribute(
812
+ f"{ASSOCIATION_PROPERTIES}.metadata.{key}", json_dumps(value)
813
+ )
698
814
 
699
815
  @classmethod
700
816
  def get_base_http_url(cls):
@@ -735,7 +851,8 @@ class Laminar:
735
851
  Args:
736
852
  trace_type (TraceType): Type of the trace
737
853
  """
738
- association_properties = {
739
- TRACE_TYPE: trace_type.value,
740
- }
741
- update_association_properties(association_properties)
854
+ span = trace.get_current_span()
855
+ if span == trace.INVALID_SPAN:
856
+ cls.__logger.warning("No active span to set trace type on")
857
+ return
858
+ span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{TRACE_TYPE}", trace_type.value)
@@ -113,3 +113,18 @@ def from_env(key: str) -> str | None:
113
113
  dotenv_path = dotenv.find_dotenv(usecwd=True)
114
114
  # use DotEnv directly so we can set verbose to False
115
115
  return dotenv.main.DotEnv(dotenv_path, verbose=False, encoding="utf-8").get(key)
116
+
117
+
118
+ def is_otel_attribute_value_type(value: typing.Any) -> bool:
119
+ def is_primitive_type(value: typing.Any) -> bool:
120
+ return isinstance(value, (int, float, str, bool))
121
+
122
+ if is_primitive_type(value):
123
+ return True
124
+ elif isinstance(value, typing.Sequence):
125
+ if len(value) > 0:
126
+ return is_primitive_type(value[0]) and all(
127
+ isinstance(v, type(value[0])) for v in value
128
+ )
129
+ return True
130
+ return False
@@ -3,7 +3,7 @@ import httpx
3
3
  from packaging import version
4
4
 
5
5
 
6
- __version__ = "0.6.7"
6
+ __version__ = "0.6.8"
7
7
  PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}"
8
8
 
9
9
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes