lmnr 0.4.53.dev0__py3-none-any.whl → 0.7.26__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.
Files changed (133) hide show
  1. lmnr/__init__.py +32 -11
  2. lmnr/cli/__init__.py +270 -0
  3. lmnr/cli/datasets.py +371 -0
  4. lmnr/cli/evals.py +111 -0
  5. lmnr/cli/rules.py +42 -0
  6. lmnr/opentelemetry_lib/__init__.py +70 -0
  7. lmnr/opentelemetry_lib/decorators/__init__.py +337 -0
  8. lmnr/opentelemetry_lib/litellm/__init__.py +685 -0
  9. lmnr/opentelemetry_lib/litellm/utils.py +100 -0
  10. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +849 -0
  11. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/config.py +13 -0
  12. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_emitter.py +211 -0
  13. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_models.py +41 -0
  14. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py +401 -0
  15. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/streaming.py +425 -0
  16. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py +332 -0
  17. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py +1 -0
  18. lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/__init__.py +451 -0
  19. lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/proxy.py +144 -0
  20. lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_agent/__init__.py +100 -0
  21. lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/__init__.py +476 -0
  22. lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/utils.py +12 -0
  23. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +599 -0
  24. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py +9 -0
  25. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py +26 -0
  26. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +330 -0
  27. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/__init__.py +488 -0
  28. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/config.py +8 -0
  29. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_emitter.py +143 -0
  30. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_models.py +41 -0
  31. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/span_utils.py +229 -0
  32. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/utils.py +92 -0
  33. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/version.py +1 -0
  34. lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/__init__.py +381 -0
  35. lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/utils.py +36 -0
  36. lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +121 -0
  37. lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/utils.py +60 -0
  38. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/__init__.py +61 -0
  39. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +472 -0
  40. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +1185 -0
  41. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +305 -0
  42. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/config.py +16 -0
  43. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +312 -0
  44. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_emitter.py +100 -0
  45. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_models.py +41 -0
  46. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +68 -0
  47. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +197 -0
  48. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v0/__init__.py +176 -0
  49. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/__init__.py +368 -0
  50. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +325 -0
  51. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +135 -0
  52. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +786 -0
  53. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/version.py +1 -0
  54. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openhands_ai/__init__.py +388 -0
  55. lmnr/opentelemetry_lib/opentelemetry/instrumentation/opentelemetry/__init__.py +69 -0
  56. lmnr/opentelemetry_lib/opentelemetry/instrumentation/skyvern/__init__.py +191 -0
  57. lmnr/opentelemetry_lib/opentelemetry/instrumentation/threading/__init__.py +197 -0
  58. lmnr/opentelemetry_lib/tracing/__init__.py +263 -0
  59. lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +516 -0
  60. lmnr/{openllmetry_sdk → opentelemetry_lib}/tracing/attributes.py +21 -8
  61. lmnr/opentelemetry_lib/tracing/context.py +200 -0
  62. lmnr/opentelemetry_lib/tracing/exporter.py +153 -0
  63. lmnr/opentelemetry_lib/tracing/instruments.py +140 -0
  64. lmnr/opentelemetry_lib/tracing/processor.py +193 -0
  65. lmnr/opentelemetry_lib/tracing/span.py +398 -0
  66. lmnr/opentelemetry_lib/tracing/tracer.py +57 -0
  67. lmnr/opentelemetry_lib/tracing/utils.py +62 -0
  68. lmnr/opentelemetry_lib/utils/package_check.py +18 -0
  69. lmnr/opentelemetry_lib/utils/wrappers.py +11 -0
  70. lmnr/sdk/browser/__init__.py +0 -0
  71. lmnr/sdk/browser/background_send_events.py +158 -0
  72. lmnr/sdk/browser/browser_use_cdp_otel.py +100 -0
  73. lmnr/sdk/browser/browser_use_otel.py +142 -0
  74. lmnr/sdk/browser/bubus_otel.py +71 -0
  75. lmnr/sdk/browser/cdp_utils.py +518 -0
  76. lmnr/sdk/browser/inject_script.js +514 -0
  77. lmnr/sdk/browser/patchright_otel.py +151 -0
  78. lmnr/sdk/browser/playwright_otel.py +322 -0
  79. lmnr/sdk/browser/pw_utils.py +363 -0
  80. lmnr/sdk/browser/recorder/record.umd.min.cjs +84 -0
  81. lmnr/sdk/browser/utils.py +70 -0
  82. lmnr/sdk/client/asynchronous/async_client.py +180 -0
  83. lmnr/sdk/client/asynchronous/resources/__init__.py +6 -0
  84. lmnr/sdk/client/asynchronous/resources/base.py +32 -0
  85. lmnr/sdk/client/asynchronous/resources/browser_events.py +41 -0
  86. lmnr/sdk/client/asynchronous/resources/datasets.py +131 -0
  87. lmnr/sdk/client/asynchronous/resources/evals.py +266 -0
  88. lmnr/sdk/client/asynchronous/resources/evaluators.py +85 -0
  89. lmnr/sdk/client/asynchronous/resources/tags.py +83 -0
  90. lmnr/sdk/client/synchronous/resources/__init__.py +6 -0
  91. lmnr/sdk/client/synchronous/resources/base.py +32 -0
  92. lmnr/sdk/client/synchronous/resources/browser_events.py +40 -0
  93. lmnr/sdk/client/synchronous/resources/datasets.py +131 -0
  94. lmnr/sdk/client/synchronous/resources/evals.py +263 -0
  95. lmnr/sdk/client/synchronous/resources/evaluators.py +85 -0
  96. lmnr/sdk/client/synchronous/resources/tags.py +83 -0
  97. lmnr/sdk/client/synchronous/sync_client.py +191 -0
  98. lmnr/sdk/datasets/__init__.py +94 -0
  99. lmnr/sdk/datasets/file_utils.py +91 -0
  100. lmnr/sdk/decorators.py +163 -26
  101. lmnr/sdk/eval_control.py +3 -2
  102. lmnr/sdk/evaluations.py +403 -191
  103. lmnr/sdk/laminar.py +1080 -549
  104. lmnr/sdk/log.py +7 -2
  105. lmnr/sdk/types.py +246 -134
  106. lmnr/sdk/utils.py +151 -7
  107. lmnr/version.py +46 -0
  108. {lmnr-0.4.53.dev0.dist-info → lmnr-0.7.26.dist-info}/METADATA +152 -106
  109. lmnr-0.7.26.dist-info/RECORD +116 -0
  110. lmnr-0.7.26.dist-info/WHEEL +4 -0
  111. lmnr-0.7.26.dist-info/entry_points.txt +3 -0
  112. lmnr/cli.py +0 -101
  113. lmnr/openllmetry_sdk/.python-version +0 -1
  114. lmnr/openllmetry_sdk/__init__.py +0 -72
  115. lmnr/openllmetry_sdk/config/__init__.py +0 -9
  116. lmnr/openllmetry_sdk/decorators/base.py +0 -185
  117. lmnr/openllmetry_sdk/instruments.py +0 -38
  118. lmnr/openllmetry_sdk/tracing/__init__.py +0 -1
  119. lmnr/openllmetry_sdk/tracing/content_allow_list.py +0 -24
  120. lmnr/openllmetry_sdk/tracing/context_manager.py +0 -13
  121. lmnr/openllmetry_sdk/tracing/tracing.py +0 -884
  122. lmnr/openllmetry_sdk/utils/in_memory_span_exporter.py +0 -61
  123. lmnr/openllmetry_sdk/utils/package_check.py +0 -7
  124. lmnr/openllmetry_sdk/version.py +0 -1
  125. lmnr/sdk/datasets.py +0 -55
  126. lmnr-0.4.53.dev0.dist-info/LICENSE +0 -75
  127. lmnr-0.4.53.dev0.dist-info/RECORD +0 -33
  128. lmnr-0.4.53.dev0.dist-info/WHEEL +0 -4
  129. lmnr-0.4.53.dev0.dist-info/entry_points.txt +0 -3
  130. /lmnr/{openllmetry_sdk → opentelemetry_lib}/.flake8 +0 -0
  131. /lmnr/{openllmetry_sdk → opentelemetry_lib}/utils/__init__.py +0 -0
  132. /lmnr/{openllmetry_sdk → opentelemetry_lib}/utils/json_encoder.py +0 -0
  133. /lmnr/{openllmetry_sdk/decorators/__init__.py → py.typed} +0 -0
@@ -0,0 +1,197 @@
1
+ # Copyright The OpenTelemetry Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """
15
+ Instrument threading to propagate OpenTelemetry context.
16
+
17
+ Copied from opentelemetry-instrumentation-threading at commit:
18
+ ad2fe813abb2ab0b6e25bedeebef5041ca3189f7
19
+ https://github.com/open-telemetry/opentelemetry-python-contrib/blob/ad2fe813abb2ab0b6e25bedeebef5041ca3189f7/instrumentation/opentelemetry-instrumentation-threading/src/opentelemetry/instrumentation/threading/__init__.py
20
+
21
+ Modified to use the Laminar isolated context.
22
+
23
+ Usage
24
+ -----
25
+
26
+ .. code-block:: python
27
+
28
+ from opentelemetry.instrumentation.threading import ThreadingInstrumentor
29
+
30
+ ThreadingInstrumentor().instrument()
31
+
32
+ This library provides instrumentation for the `threading` module to ensure that
33
+ the OpenTelemetry context is propagated across threads. It is important to note
34
+ that this instrumentation does not produce any telemetry data on its own. It
35
+ merely ensures that the context is correctly propagated when threads are used.
36
+
37
+
38
+ When instrumented, new threads created using threading.Thread, threading.Timer,
39
+ or within futures.ThreadPoolExecutor will have the current OpenTelemetry
40
+ context attached, and this context will be re-activated in the thread's
41
+ run method or the executor's worker thread."
42
+ """
43
+
44
+ from __future__ import annotations
45
+
46
+ import threading
47
+ from concurrent import futures
48
+ from typing import TYPE_CHECKING, Any, Callable, Collection
49
+
50
+ from wrapt import (
51
+ wrap_function_wrapper, # type: ignore[reportUnknownVariableType]
52
+ )
53
+
54
+ from lmnr.opentelemetry_lib.tracing.context import (
55
+ get_current_context,
56
+ attach_context,
57
+ detach_context,
58
+ )
59
+ from opentelemetry import context
60
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
61
+ from opentelemetry.instrumentation.utils import unwrap
62
+
63
+ _instruments = ()
64
+
65
+ if TYPE_CHECKING:
66
+ from typing import Protocol, TypeVar
67
+
68
+ R = TypeVar("R")
69
+
70
+ class HasOtelContext(Protocol):
71
+ _otel_context: context.Context
72
+
73
+
74
+ class ThreadingInstrumentor(BaseInstrumentor):
75
+ __WRAPPER_START_METHOD = "start"
76
+ __WRAPPER_RUN_METHOD = "run"
77
+ __WRAPPER_SUBMIT_METHOD = "submit"
78
+
79
+ def instrumentation_dependencies(self) -> Collection[str]:
80
+ return _instruments
81
+
82
+ def _instrument(self, **kwargs: Any):
83
+ self._instrument_thread()
84
+ self._instrument_timer()
85
+ self._instrument_thread_pool()
86
+
87
+ def _uninstrument(self, **kwargs: Any):
88
+ self._uninstrument_thread()
89
+ self._uninstrument_timer()
90
+ self._uninstrument_thread_pool()
91
+
92
+ @staticmethod
93
+ def _instrument_thread():
94
+ wrap_function_wrapper(
95
+ threading.Thread,
96
+ ThreadingInstrumentor.__WRAPPER_START_METHOD,
97
+ ThreadingInstrumentor.__wrap_threading_start,
98
+ )
99
+ wrap_function_wrapper(
100
+ threading.Thread,
101
+ ThreadingInstrumentor.__WRAPPER_RUN_METHOD,
102
+ ThreadingInstrumentor.__wrap_threading_run,
103
+ )
104
+
105
+ @staticmethod
106
+ def _instrument_timer():
107
+ wrap_function_wrapper(
108
+ threading.Timer,
109
+ ThreadingInstrumentor.__WRAPPER_START_METHOD,
110
+ ThreadingInstrumentor.__wrap_threading_start,
111
+ )
112
+ wrap_function_wrapper(
113
+ threading.Timer,
114
+ ThreadingInstrumentor.__WRAPPER_RUN_METHOD,
115
+ ThreadingInstrumentor.__wrap_threading_run,
116
+ )
117
+
118
+ @staticmethod
119
+ def _instrument_thread_pool():
120
+ wrap_function_wrapper(
121
+ futures.ThreadPoolExecutor,
122
+ ThreadingInstrumentor.__WRAPPER_SUBMIT_METHOD,
123
+ ThreadingInstrumentor.__wrap_thread_pool_submit,
124
+ )
125
+
126
+ @staticmethod
127
+ def _uninstrument_thread():
128
+ unwrap(threading.Thread, ThreadingInstrumentor.__WRAPPER_START_METHOD)
129
+ unwrap(threading.Thread, ThreadingInstrumentor.__WRAPPER_RUN_METHOD)
130
+
131
+ @staticmethod
132
+ def _uninstrument_timer():
133
+ unwrap(threading.Timer, ThreadingInstrumentor.__WRAPPER_START_METHOD)
134
+ unwrap(threading.Timer, ThreadingInstrumentor.__WRAPPER_RUN_METHOD)
135
+
136
+ @staticmethod
137
+ def _uninstrument_thread_pool():
138
+ unwrap(
139
+ futures.ThreadPoolExecutor,
140
+ ThreadingInstrumentor.__WRAPPER_SUBMIT_METHOD,
141
+ )
142
+
143
+ @staticmethod
144
+ def __wrap_threading_start(
145
+ call_wrapped: Callable[[], None],
146
+ instance: HasOtelContext,
147
+ args: tuple[()],
148
+ kwargs: dict[str, Any],
149
+ ) -> None:
150
+ instance._lmnr_otel_context = get_current_context()
151
+ return call_wrapped(*args, **kwargs)
152
+
153
+ @staticmethod
154
+ def __wrap_threading_run(
155
+ call_wrapped: Callable[..., R],
156
+ instance: HasOtelContext,
157
+ args: tuple[Any, ...],
158
+ kwargs: dict[str, Any],
159
+ ) -> R:
160
+ token = None
161
+ try:
162
+ # Genearally, this must be set in __wrap_threading_start, but it is
163
+ # possible to Thread().run() without Thread().start(), so in that case,
164
+ # we need to capture the context here.
165
+ # We still want to capture the context in __wrap_threading_start,
166
+ # in order to stay close to the original implementation.
167
+ if not hasattr(instance, "_lmnr_otel_context"):
168
+ instance._lmnr_otel_context = get_current_context()
169
+ token = attach_context(instance._lmnr_otel_context)
170
+ return call_wrapped(*args, **kwargs)
171
+ finally:
172
+ if token is not None:
173
+ detach_context(token)
174
+
175
+ @staticmethod
176
+ def __wrap_thread_pool_submit(
177
+ call_wrapped: Callable[..., R],
178
+ instance: futures.ThreadPoolExecutor,
179
+ args: tuple[Callable[..., Any], ...],
180
+ kwargs: dict[str, Any],
181
+ ) -> R:
182
+ # obtain the original function and wrapped kwargs
183
+ original_func = args[0]
184
+ otel_context = get_current_context()
185
+
186
+ def wrapped_func(*func_args: Any, **func_kwargs: Any) -> R:
187
+ token = None
188
+ try:
189
+ token = attach_context(otel_context)
190
+ return original_func(*func_args, **func_kwargs)
191
+ finally:
192
+ if token is not None:
193
+ detach_context(token)
194
+
195
+ # replace the original function with the wrapped function
196
+ new_args: tuple[Callable[..., Any], ...] = (wrapped_func,) + args[1:]
197
+ return call_wrapped(*new_args, **kwargs)
@@ -0,0 +1,263 @@
1
+ import atexit
2
+ import logging
3
+ import threading
4
+
5
+ from lmnr.opentelemetry_lib.tracing.processor import LaminarSpanProcessor
6
+ from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
7
+ from lmnr.sdk.types import SessionRecordingOptions
8
+ from lmnr.sdk.log import VerboseColorfulFormatter
9
+ from lmnr.opentelemetry_lib.tracing.instruments import (
10
+ Instruments,
11
+ init_instrumentations,
12
+ )
13
+ from lmnr.opentelemetry_lib.tracing.context import (
14
+ attach_context,
15
+ clear_context,
16
+ pop_span_context as ctx_pop_span_context,
17
+ get_current_context,
18
+ get_token_stack,
19
+ push_span_context as ctx_push_span_context,
20
+ set_token_stack,
21
+ )
22
+
23
+ from opentelemetry import trace
24
+ from opentelemetry.context import Context
25
+
26
+ # instead of importing from opentelemetry.instrumentation.threading,
27
+ # we import from our modified copy to use Laminar's isolated context.
28
+ from ..opentelemetry.instrumentation.threading import ThreadingInstrumentor
29
+ from opentelemetry.sdk.resources import Resource
30
+ from opentelemetry.sdk.trace import TracerProvider, SpanProcessor
31
+ from opentelemetry.sdk.trace.export import SpanExporter
32
+
33
+ TRACER_NAME = "lmnr.tracer"
34
+
35
+ MAX_EVENTS_OR_ATTRIBUTES_PER_SPAN = 5000
36
+
37
+
38
+ class TracerWrapper(object):
39
+ resource_attributes: dict = {}
40
+ enable_content_tracing: bool = True
41
+ session_recording_options: SessionRecordingOptions = {}
42
+ _lock = threading.Lock()
43
+ _tracer_provider: TracerProvider | None = None
44
+ _logger: logging.Logger
45
+ _async_client: AsyncLaminarClient
46
+ _resource: Resource
47
+ _span_processor: SpanProcessor
48
+ _original_thread_init = None
49
+
50
+ def __new__(
51
+ cls,
52
+ disable_batch=False,
53
+ exporter: SpanExporter | None = None,
54
+ instruments: set[Instruments] | None = None,
55
+ block_instruments: set[Instruments] | None = None,
56
+ base_url: str | None = None,
57
+ port: int = 8443,
58
+ http_port: int = 443,
59
+ project_api_key: str | None = None,
60
+ max_export_batch_size: int | None = None,
61
+ force_http: bool = False,
62
+ timeout_seconds: int = 30,
63
+ set_global_tracer_provider: bool = True,
64
+ otel_logger_level: int = logging.ERROR,
65
+ session_recording_options: SessionRecordingOptions | None = None,
66
+ ) -> "TracerWrapper":
67
+ # Silence some opentelemetry warnings
68
+ logging.getLogger("opentelemetry.trace").setLevel(otel_logger_level)
69
+
70
+ base_http_url = f"{base_url}:{http_port}" if base_url else None
71
+ with cls._lock:
72
+ if not hasattr(cls, "instance"):
73
+ cls._initialize_logger(cls)
74
+ obj = super(TracerWrapper, cls).__new__(cls)
75
+
76
+ # Store session recording options
77
+ cls.session_recording_options = session_recording_options or {}
78
+
79
+ if project_api_key:
80
+ obj._async_client = AsyncLaminarClient(
81
+ base_url=base_http_url or "https://api.lmnr.ai",
82
+ project_api_key=project_api_key,
83
+ )
84
+ else:
85
+ obj._async_client = None
86
+
87
+ obj._resource = Resource(attributes=TracerWrapper.resource_attributes)
88
+
89
+ obj._span_processor = LaminarSpanProcessor(
90
+ base_url=base_url,
91
+ api_key=project_api_key,
92
+ port=http_port if force_http else port,
93
+ exporter=exporter,
94
+ max_export_batch_size=max_export_batch_size,
95
+ timeout_seconds=timeout_seconds,
96
+ force_http=force_http,
97
+ disable_batch=disable_batch,
98
+ )
99
+
100
+ lmnr_provider = TracerProvider(resource=obj._resource)
101
+ global_provider = trace.get_tracer_provider()
102
+ if set_global_tracer_provider and isinstance(
103
+ global_provider, trace.ProxyTracerProvider
104
+ ):
105
+ trace.set_tracer_provider(lmnr_provider)
106
+
107
+ obj._tracer_provider = lmnr_provider
108
+
109
+ obj._tracer_provider.add_span_processor(obj._span_processor)
110
+
111
+ # Setup threading context inheritance
112
+ obj._setup_threading_inheritance()
113
+
114
+ # This is not a real instrumentation and does not generate telemetry
115
+ # data, but it is required to ensure that OpenTelemetry context
116
+ # propagation is enabled.
117
+ # See the README at:
118
+ # https://pypi.org/project/opentelemetry-instrumentation-threading/
119
+ ThreadingInstrumentor().instrument()
120
+
121
+ init_instrumentations(
122
+ tracer_provider=obj._tracer_provider,
123
+ instruments=instruments,
124
+ block_instruments=block_instruments,
125
+ async_client=obj._async_client,
126
+ )
127
+
128
+ cls.instance = obj
129
+
130
+ # Force flushes for debug environments (e.g. local development)
131
+ atexit.register(obj.exit_handler)
132
+
133
+ return cls.instance
134
+
135
+ def _setup_threading_inheritance(self):
136
+ """Setup threading inheritance for isolated context."""
137
+ if TracerWrapper._original_thread_init is None:
138
+ # Monkey patch Thread.__init__ to capture context inheritance
139
+ TracerWrapper._original_thread_init = threading.Thread.__init__
140
+
141
+ def patched_thread_init(thread_self, *args, **kwargs):
142
+ # Capture current isolated context and token stack for inheritance
143
+ current_context = get_current_context()
144
+ current_token_stack = get_token_stack().copy()
145
+
146
+ # Get the original target function
147
+ original_target = kwargs.get("target")
148
+ if not original_target and args:
149
+ original_target = args[0]
150
+
151
+ # Only inherit if we have a target function
152
+ if original_target:
153
+ # Create a wrapper function that sets up context
154
+ def thread_wrapper(*target_args, **target_kwargs):
155
+ # Set inherited context and token stack in the new thread
156
+ attach_context(current_context)
157
+ set_token_stack(current_token_stack)
158
+ # Run original target
159
+ return original_target(*target_args, **target_kwargs)
160
+
161
+ # Replace the target with our wrapper
162
+ if "target" in kwargs:
163
+ kwargs["target"] = thread_wrapper
164
+ elif args:
165
+ args = (thread_wrapper,) + args[1:]
166
+
167
+ # Call original init
168
+ TracerWrapper._original_thread_init(thread_self, *args, **kwargs)
169
+
170
+ threading.Thread.__init__ = patched_thread_init
171
+
172
+ def exit_handler(self):
173
+ if isinstance(self._span_processor, LaminarSpanProcessor):
174
+ self._span_processor.clear()
175
+ self.flush()
176
+
177
+ def _initialize_logger(self):
178
+ self._logger = logging.getLogger(__name__)
179
+ console_log_handler = logging.StreamHandler()
180
+ console_log_handler.setFormatter(VerboseColorfulFormatter())
181
+ self._logger.addHandler(console_log_handler)
182
+
183
+ def get_isolated_context(self) -> Context:
184
+ """Get the current isolated context."""
185
+ return get_current_context()
186
+
187
+ def push_span_context(self, span: trace.Span) -> Context:
188
+ """Push a new context with the given span onto the stack."""
189
+ current_ctx = get_current_context()
190
+ new_context = trace.set_span_in_context(span, current_ctx)
191
+ ctx_push_span_context(new_context)
192
+ return new_context
193
+
194
+ def pop_span_context(self) -> None:
195
+ """Pop the current span context from the stack."""
196
+ ctx_pop_span_context()
197
+
198
+ @staticmethod
199
+ def set_static_params(
200
+ resource_attributes: dict,
201
+ enable_content_tracing: bool,
202
+ ) -> None:
203
+ TracerWrapper.resource_attributes = resource_attributes
204
+ TracerWrapper.enable_content_tracing = enable_content_tracing
205
+
206
+ @classmethod
207
+ def verify_initialized(cls) -> bool:
208
+ # This is not using lock, but it is fine to return False from here
209
+ # even if initialization is going on.
210
+
211
+ # If we try to acquire the lock here, it may deadlock if an automatic
212
+ # instrumentation is importing a file that (at the top level) has a
213
+ # function annotated with Laminar's `observe` decorator.
214
+ # The decorator is evaluated at import time, inside `init_instrumentations`,
215
+ # which is called by the `TracerWrapper` constructor while holding the lock.
216
+ # Without the lock here, we will simply return False, which will cause
217
+ # the decorator to return the original function. This is fine, at runtime,
218
+ # the next import statement will re-evaluate the decorator, and Laminar will
219
+ # have been initialized by that time.
220
+ return hasattr(cls, "instance") and hasattr(cls.instance, "_span_processor")
221
+
222
+ @classmethod
223
+ def clear(cls):
224
+ if not cls.verify_initialized():
225
+ return
226
+ # Any state cleanup. Now used in between tests
227
+ if isinstance(cls.instance._span_processor, LaminarSpanProcessor):
228
+ cls.instance._span_processor.clear()
229
+ # Clear the isolated context state for clean test state
230
+ clear_context()
231
+
232
+ def shutdown(self):
233
+ if self._tracer_provider is None:
234
+ return
235
+ self._tracer_provider.shutdown()
236
+
237
+ def flush(self):
238
+ if not hasattr(self, "_span_processor"):
239
+ self._logger.warning("TracerWrapper not fully initialized, cannot flush")
240
+ return False
241
+ return self._span_processor.force_flush()
242
+
243
+ def force_reinit_processor(self):
244
+ if isinstance(self._span_processor, LaminarSpanProcessor):
245
+ self._span_processor.force_flush()
246
+ self._span_processor.force_reinit()
247
+ # Clear the isolated context to prevent subsequent invocations
248
+ # (e.g., in Lambda) from continuing traces from previous invocations
249
+ clear_context()
250
+ else:
251
+ self._logger.warning(
252
+ "Not using LaminarSpanProcessor, cannot force reinit processor"
253
+ )
254
+
255
+ @classmethod
256
+ def get_session_recording_options(cls) -> SessionRecordingOptions:
257
+ """Get the session recording options set during initialization."""
258
+ return cls.session_recording_options
259
+
260
+ def get_tracer(self) -> trace.Tracer:
261
+ if self._tracer_provider is None:
262
+ return trace.get_tracer_provider().get_tracer(TRACER_NAME)
263
+ return self._tracer_provider.get_tracer(TRACER_NAME)