lmnr 0.4.66__py3-none-any.whl → 0.5.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.
Files changed (37) hide show
  1. lmnr/__init__.py +30 -0
  2. lmnr/openllmetry_sdk/__init__.py +4 -16
  3. lmnr/openllmetry_sdk/tracing/attributes.py +0 -1
  4. lmnr/openllmetry_sdk/tracing/tracing.py +30 -10
  5. lmnr/sdk/browser/browser_use_otel.py +4 -4
  6. lmnr/sdk/browser/playwright_otel.py +299 -228
  7. lmnr/sdk/browser/pw_utils.py +289 -0
  8. lmnr/sdk/browser/utils.py +18 -53
  9. lmnr/sdk/client/asynchronous/async_client.py +157 -0
  10. lmnr/sdk/client/asynchronous/resources/__init__.py +13 -0
  11. lmnr/sdk/client/asynchronous/resources/agent.py +220 -0
  12. lmnr/sdk/client/asynchronous/resources/base.py +32 -0
  13. lmnr/sdk/client/asynchronous/resources/browser_events.py +40 -0
  14. lmnr/sdk/client/asynchronous/resources/evals.py +64 -0
  15. lmnr/sdk/client/asynchronous/resources/pipeline.py +89 -0
  16. lmnr/sdk/client/asynchronous/resources/semantic_search.py +60 -0
  17. lmnr/sdk/client/synchronous/resources/__init__.py +7 -0
  18. lmnr/sdk/client/synchronous/resources/agent.py +215 -0
  19. lmnr/sdk/client/synchronous/resources/base.py +32 -0
  20. lmnr/sdk/client/synchronous/resources/browser_events.py +40 -0
  21. lmnr/sdk/client/synchronous/resources/evals.py +102 -0
  22. lmnr/sdk/client/synchronous/resources/pipeline.py +89 -0
  23. lmnr/sdk/client/synchronous/resources/semantic_search.py +60 -0
  24. lmnr/sdk/client/synchronous/sync_client.py +170 -0
  25. lmnr/sdk/datasets.py +7 -2
  26. lmnr/sdk/evaluations.py +59 -35
  27. lmnr/sdk/laminar.py +34 -174
  28. lmnr/sdk/types.py +124 -23
  29. lmnr/sdk/utils.py +10 -0
  30. lmnr/version.py +6 -6
  31. {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/METADATA +88 -38
  32. lmnr-0.5.1.dist-info/RECORD +55 -0
  33. {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/WHEEL +1 -1
  34. lmnr/sdk/client.py +0 -313
  35. lmnr-0.4.66.dist-info/RECORD +0 -39
  36. {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/LICENSE +0 -0
  37. {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/entry_points.txt +0 -0
@@ -1,28 +1,32 @@
1
- import asyncio
2
1
  import logging
3
- import os
4
- import threading
5
- import time
6
2
  import uuid
7
3
 
8
- from lmnr.sdk.browser.utils import (
9
- INJECT_PLACEHOLDER,
10
- _with_tracer_wrapper,
11
- retry_sync,
12
- retry_async,
13
- )
14
- from lmnr.sdk.client import LaminarClient
15
- from lmnr.version import PYTHON_VERSION, SDK_VERSION
4
+ from lmnr.sdk.browser.pw_utils import handle_navigation_async, handle_navigation_sync
5
+ from lmnr.sdk.browser.utils import with_tracer_and_client_wrapper
6
+ from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
7
+ from lmnr.sdk.client.synchronous.sync_client import LaminarClient
8
+ from lmnr.version import __version__
16
9
 
17
10
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
18
11
  from opentelemetry.instrumentation.utils import unwrap
19
- from opentelemetry.trace import get_tracer, Tracer, get_current_span
12
+ from opentelemetry.trace import (
13
+ get_tracer,
14
+ Tracer,
15
+ get_current_span,
16
+ Span,
17
+ INVALID_SPAN,
18
+ set_span_in_context,
19
+ )
20
+ from opentelemetry.context import get_current
20
21
  from typing import Collection
21
22
  from wrapt import wrap_function_wrapper
22
23
 
23
24
  try:
24
- from playwright.async_api import Page
25
- from playwright.sync_api import Page as SyncPage
25
+ from playwright.async_api import Browser, BrowserContext
26
+ from playwright.sync_api import (
27
+ Browser as SyncBrowser,
28
+ BrowserContext as SyncBrowserContext,
29
+ )
26
30
  except ImportError as e:
27
31
  raise ImportError(
28
32
  f"Attempted to import {__file__}, but it is designed "
@@ -34,241 +38,299 @@ except ImportError as e:
34
38
  _instruments = ("playwright >= 1.9.0",)
35
39
  logger = logging.getLogger(__name__)
36
40
 
37
- WRAPPED_METHODS = [
38
- {
39
- "package": "playwright.sync_api",
40
- "object": "BrowserContext",
41
- "method": "new_page",
42
- }
43
- ]
44
-
45
- WRAPPED_METHODS_ASYNC = [
46
- {
47
- "package": "playwright.async_api",
48
- "object": "BrowserContext",
49
- "method": "new_page",
50
- }
51
- ]
52
-
53
- _original_new_page = None
54
- _original_new_page_async = None
55
-
56
- current_dir = os.path.dirname(os.path.abspath(__file__))
57
- with open(os.path.join(current_dir, "rrweb", "rrweb.min.js"), "r") as f:
58
- RRWEB_CONTENT = f"() => {{ {f.read()} }}"
59
-
60
-
61
- async def send_events_async(page: Page, session_id: str, trace_id: str):
62
- """Fetch events from the page and send them to the server"""
63
- try:
64
- # Check if function exists first
65
- has_function = await page.evaluate(
66
- """
67
- () => typeof window.lmnrGetAndClearEvents === 'function'
68
- """
69
- )
70
- if not has_function:
71
- return
72
-
73
- events = await page.evaluate("window.lmnrGetAndClearEvents()")
74
- if not events or len(events) == 0:
75
- return
76
-
77
- await LaminarClient.send_browser_events(
78
- session_id, trace_id, events, f"python@{PYTHON_VERSION}"
79
- )
41
+ _context_spans: dict[str, Span] = {}
80
42
 
81
- except Exception as e:
82
- logger.error(f"Error sending events: {e}")
83
43
 
84
-
85
- def send_events_sync(page: SyncPage, session_id: str, trace_id: str):
86
- """Synchronous version of send_events"""
87
- try:
88
- # Check if function exists first
89
- has_function = page.evaluate(
90
- """
91
- () => typeof window.lmnrGetAndClearEvents === 'function'
92
- """
93
- )
94
- if not has_function:
95
- return
96
-
97
- events = page.evaluate("window.lmnrGetAndClearEvents()")
98
- if not events or len(events) == 0:
99
- return
100
-
101
- LaminarClient.send_browser_events_sync(
102
- session_id, trace_id, events, f"python@{PYTHON_VERSION}"
103
- )
104
-
105
- except Exception as e:
106
- logger.error(f"Error sending events: {e}")
107
-
108
-
109
- def inject_rrweb(page: SyncPage):
110
- try:
111
- page.wait_for_load_state("domcontentloaded")
112
-
113
- # Wrap the evaluate call in a try-catch
114
- try:
115
- is_loaded = page.evaluate(
116
- """() => typeof window.lmnrRrweb !== 'undefined'"""
117
- )
118
- except Exception as e:
119
- logger.debug(f"Failed to check if rrweb is loaded: {e}")
120
- is_loaded = False
121
-
122
- if not is_loaded:
123
-
124
- def load_rrweb():
125
- try:
126
- page.evaluate(RRWEB_CONTENT)
127
- page.wait_for_function(
128
- """(() => typeof window.lmnrRrweb !== 'undefined')""",
129
- timeout=5000,
130
- )
131
- return True
132
- except Exception as e:
133
- logger.debug(f"Failed to load rrweb: {e}")
134
- return False
135
-
136
- if not retry_sync(
137
- load_rrweb, delay=1, error_message="Failed to load rrweb"
138
- ):
139
- return
140
-
141
- try:
142
- page.evaluate(INJECT_PLACEHOLDER)
143
- except Exception as e:
144
- logger.debug(f"Failed to inject rrweb placeholder: {e}")
145
-
146
- except Exception as e:
147
- logger.error(f"Error during rrweb injection: {e}")
148
-
149
-
150
- async def inject_rrweb_async(page: Page):
151
- try:
152
- await page.wait_for_load_state("domcontentloaded")
153
-
154
- # Wrap the evaluate call in a try-catch
155
- try:
156
- is_loaded = await page.evaluate(
157
- """() => typeof window.lmnrRrweb !== 'undefined'"""
158
- )
159
- except Exception as e:
160
- logger.debug(f"Failed to check if rrweb is loaded: {e}")
161
- is_loaded = False
162
-
163
- if not is_loaded:
164
-
165
- async def load_rrweb():
166
- try:
167
- await page.evaluate(RRWEB_CONTENT)
168
- await page.wait_for_function(
169
- """(() => typeof window.lmnrRrweb !== 'undefined')""",
170
- timeout=5000,
171
- )
172
- return True
173
- except Exception as e:
174
- logger.debug(f"Failed to load rrweb: {e}")
175
- return False
176
-
177
- if not await retry_async(
178
- load_rrweb, delay=1, error_message="Failed to load rrweb"
179
- ):
180
- return
181
-
182
- try:
183
- await page.evaluate(INJECT_PLACEHOLDER)
184
- except Exception as e:
185
- logger.debug(f"Failed to inject rrweb placeholder: {e}")
186
-
187
- except Exception as e:
188
- logger.error(f"Error during rrweb injection: {e}")
189
-
190
-
191
- def handle_navigation(page: SyncPage, session_id: str, trace_id: str):
192
- def on_load():
193
- try:
194
- inject_rrweb(page)
195
- except Exception as e:
196
- logger.error(f"Error in on_load handler: {e}")
197
-
198
- page.on("load", on_load)
199
- inject_rrweb(page)
200
-
201
- def collection_loop():
202
- while not page.is_closed(): # Stop when page closes
203
- send_events_sync(page, session_id, trace_id)
204
- time.sleep(2)
205
-
206
- thread = threading.Thread(target=collection_loop, daemon=True)
207
- thread.start()
208
-
209
-
210
- async def handle_navigation_async(page: Page, session_id: str, trace_id: str):
211
- async def on_load():
212
- try:
213
- await inject_rrweb_async(page)
214
- except Exception as e:
215
- logger.error(f"Error in on_load handler: {e}")
216
-
217
- page.on("load", lambda: asyncio.create_task(on_load()))
218
- await inject_rrweb_async(page)
219
-
220
- async def collection_loop():
221
- try:
222
- while not page.is_closed(): # Stop when page closes
223
- await send_events_async(page, session_id, trace_id)
224
- await asyncio.sleep(2)
225
- logger.info("Event collection stopped")
226
- except Exception as e:
227
- logger.error(f"Event collection stopped: {e}")
228
-
229
- # Create and store task
230
- task = asyncio.create_task(collection_loop())
231
-
232
- # Clean up task when page closes
233
- page.on("close", lambda: task.cancel())
234
-
235
-
236
- @_with_tracer_wrapper
237
- def _wrap(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
44
+ @with_tracer_and_client_wrapper
45
+ def _wrap_new_page(
46
+ tracer: Tracer, client: LaminarClient, to_wrap, wrapped, instance, args, kwargs
47
+ ):
238
48
  with tracer.start_as_current_span(
239
- f"browser_context.{to_wrap.get('method')}"
49
+ f"{to_wrap.get('object')}.{to_wrap.get('method')}"
240
50
  ) as span:
241
51
  page = wrapped(*args, **kwargs)
242
52
  session_id = str(uuid.uuid4().hex)
243
53
  trace_id = format(get_current_span().get_span_context().trace_id, "032x")
244
54
  span.set_attribute("lmnr.internal.has_browser_session", True)
245
- handle_navigation(page, session_id, trace_id)
55
+ handle_navigation_sync(page, session_id, trace_id, client)
246
56
  return page
247
57
 
248
58
 
249
- @_with_tracer_wrapper
250
- async def _wrap_async(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
59
+ @with_tracer_and_client_wrapper
60
+ async def _wrap_new_page_async(
61
+ tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
62
+ ):
251
63
  with tracer.start_as_current_span(
252
- f"browser_context.{to_wrap.get('method')}"
64
+ f"{to_wrap.get('object')}.{to_wrap.get('method')}"
253
65
  ) as span:
254
66
  page = await wrapped(*args, **kwargs)
255
67
  session_id = str(uuid.uuid4().hex)
256
- trace_id = format(get_current_span().get_span_context().trace_id, "032x")
68
+ trace_id = format(span.get_span_context().trace_id, "032x")
257
69
  span.set_attribute("lmnr.internal.has_browser_session", True)
258
- await handle_navigation_async(page, session_id, trace_id)
70
+ await handle_navigation_async(page, session_id, trace_id, client)
259
71
  return page
260
72
 
261
73
 
74
+ @with_tracer_and_client_wrapper
75
+ def _wrap_new_browser_sync(
76
+ tracer: Tracer, client: LaminarClient, to_wrap, wrapped, instance, args, kwargs
77
+ ):
78
+ global _context_spans
79
+ browser: SyncBrowser = wrapped(*args, **kwargs)
80
+ session_id = str(uuid.uuid4().hex)
81
+ for context in browser.contexts:
82
+ span = get_current_span()
83
+ if span == INVALID_SPAN:
84
+ span = tracer.start_span(
85
+ name=f"{to_wrap.get('object')}.{to_wrap.get('method')}"
86
+ )
87
+ set_span_in_context(span, get_current())
88
+ _context_spans[id(context)] = span
89
+ span.set_attribute("lmnr.internal.has_browser_session", True)
90
+ trace_id = format(span.get_span_context().trace_id, "032x")
91
+ context.on(
92
+ "page",
93
+ lambda page: handle_navigation_sync(page, session_id, trace_id, client),
94
+ )
95
+ for page in context.pages:
96
+ handle_navigation_sync(page, session_id, trace_id, client)
97
+ return browser
98
+
99
+
100
+ @with_tracer_and_client_wrapper
101
+ async def _wrap_new_browser_async(
102
+ tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
103
+ ):
104
+ global _context_spans
105
+ browser: Browser = await wrapped(*args, **kwargs)
106
+ session_id = str(uuid.uuid4().hex)
107
+ for context in browser.contexts:
108
+ span = get_current_span()
109
+ if span == INVALID_SPAN:
110
+ span = tracer.start_span(
111
+ name=f"{to_wrap.get('object')}.{to_wrap.get('method')}"
112
+ )
113
+ set_span_in_context(span, get_current())
114
+ _context_spans[id(context)] = span
115
+ span.set_attribute("lmnr.internal.has_browser_session", True)
116
+ trace_id = format(span.get_span_context().trace_id, "032x")
117
+
118
+ async def handle_page_navigation(page):
119
+ return await handle_navigation_async(page, session_id, trace_id, client)
120
+
121
+ context.on("page", handle_page_navigation)
122
+ for page in context.pages:
123
+ await handle_navigation_async(page, session_id, trace_id, client)
124
+ return browser
125
+
126
+
127
+ @with_tracer_and_client_wrapper
128
+ def _wrap_new_context_sync(
129
+ tracer: Tracer, client: LaminarClient, to_wrap, wrapped, instance, args, kwargs
130
+ ):
131
+ context: SyncBrowserContext = wrapped(*args, **kwargs)
132
+ session_id = str(uuid.uuid4().hex)
133
+ span = get_current_span()
134
+ if span == INVALID_SPAN:
135
+ span = tracer.start_span(
136
+ name=f"{to_wrap.get('object')}.{to_wrap.get('method')}"
137
+ )
138
+ set_span_in_context(span, get_current())
139
+ _context_spans[id(context)] = span
140
+ span.set_attribute("lmnr.internal.has_browser_session", True)
141
+ trace_id = format(span.get_span_context().trace_id, "032x")
142
+
143
+ context.on(
144
+ "page",
145
+ lambda page: handle_navigation_sync(page, session_id, trace_id, client),
146
+ )
147
+ for page in context.pages:
148
+ handle_navigation_sync(page, session_id, trace_id, client)
149
+ return context
150
+
151
+
152
+ @with_tracer_and_client_wrapper
153
+ async def _wrap_new_context_async(
154
+ tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
155
+ ):
156
+ context: SyncBrowserContext = await wrapped(*args, **kwargs)
157
+ session_id = str(uuid.uuid4().hex)
158
+ span = get_current_span()
159
+ if span == INVALID_SPAN:
160
+ span = tracer.start_span(
161
+ name=f"{to_wrap.get('object')}.{to_wrap.get('method')}"
162
+ )
163
+ set_span_in_context(span, get_current())
164
+ _context_spans[id(context)] = span
165
+ span.set_attribute("lmnr.internal.has_browser_session", True)
166
+ trace_id = format(span.get_span_context().trace_id, "032x")
167
+
168
+ async def handle_page_navigation(page):
169
+ return await handle_navigation_async(page, session_id, trace_id, client)
170
+
171
+ context.on("page", handle_page_navigation)
172
+ for page in context.pages:
173
+ await handle_navigation_async(page, session_id, trace_id, client)
174
+ return context
175
+
176
+
177
+ @with_tracer_and_client_wrapper
178
+ def _wrap_close_browser_sync(
179
+ tracer: Tracer,
180
+ client: LaminarClient,
181
+ to_wrap,
182
+ wrapped,
183
+ instance: SyncBrowser,
184
+ args,
185
+ kwargs,
186
+ ):
187
+ global _context_spans
188
+ for context in instance.contexts:
189
+ key = id(context)
190
+ span = _context_spans.get(key)
191
+ if span:
192
+ if span.is_recording():
193
+ span.end()
194
+ _context_spans.pop(key)
195
+ return wrapped(*args, **kwargs)
196
+
197
+
198
+ @with_tracer_and_client_wrapper
199
+ async def _wrap_close_browser_async(
200
+ tracer: Tracer,
201
+ client: AsyncLaminarClient,
202
+ to_wrap,
203
+ wrapped,
204
+ instance: Browser,
205
+ args,
206
+ kwargs,
207
+ ):
208
+ global _context_spans
209
+ for context in instance.contexts:
210
+ key = id(context)
211
+ span = _context_spans.get(key)
212
+ if span:
213
+ if span.is_recording():
214
+ span.end()
215
+ _context_spans.pop(key)
216
+ return await wrapped(*args, **kwargs)
217
+
218
+
219
+ WRAPPED_METHODS = [
220
+ {
221
+ "package": "playwright.sync_api",
222
+ "object": "BrowserContext",
223
+ "method": "new_page",
224
+ "wrapper": _wrap_new_page,
225
+ },
226
+ {
227
+ "package": "playwright.sync_api",
228
+ "object": "Browser",
229
+ "method": "new_page",
230
+ "wrapper": _wrap_new_page,
231
+ },
232
+ {
233
+ "package": "playwright.sync_api",
234
+ "object": "BrowserType",
235
+ "method": "launch",
236
+ "wrapper": _wrap_new_browser_sync,
237
+ },
238
+ {
239
+ "package": "playwright.sync_api",
240
+ "object": "BrowserType",
241
+ "method": "connect",
242
+ "wrapper": _wrap_new_browser_sync,
243
+ },
244
+ {
245
+ "package": "playwright.sync_api",
246
+ "object": "BrowserType",
247
+ "method": "connect_over_cdp",
248
+ "wrapper": _wrap_new_browser_sync,
249
+ },
250
+ {
251
+ "package": "playwright.sync_api",
252
+ "object": "Browser",
253
+ "method": "close",
254
+ "wrapper": _wrap_close_browser_sync,
255
+ },
256
+ {
257
+ "package": "playwright.sync_api",
258
+ "object": "Browser",
259
+ "method": "new_context",
260
+ "wrapper": _wrap_new_context_sync,
261
+ },
262
+ {
263
+ "package": "playwright.sync_api",
264
+ "object": "BrowserType",
265
+ "method": "launch_persistent_context",
266
+ "wrapper": _wrap_new_context_sync,
267
+ },
268
+ ]
269
+
270
+ WRAPPED_METHODS_ASYNC = [
271
+ {
272
+ "package": "playwright.async_api",
273
+ "object": "BrowserContext",
274
+ "method": "new_page",
275
+ "wrapper": _wrap_new_page_async,
276
+ },
277
+ {
278
+ "package": "playwright.async_api",
279
+ "object": "Browser",
280
+ "method": "new_page",
281
+ "wrapper": _wrap_new_page_async,
282
+ },
283
+ {
284
+ "package": "playwright.async_api",
285
+ "object": "BrowserType",
286
+ "method": "launch",
287
+ "wrapper": _wrap_new_browser_async,
288
+ },
289
+ {
290
+ "package": "playwright.async_api",
291
+ "object": "BrowserType",
292
+ "method": "connect",
293
+ "wrapper": _wrap_new_browser_async,
294
+ },
295
+ {
296
+ "package": "playwright.async_api",
297
+ "object": "BrowserType",
298
+ "method": "connect_over_cdp",
299
+ "wrapper": _wrap_new_browser_async,
300
+ },
301
+ {
302
+ "package": "playwright.async_api",
303
+ "object": "Browser",
304
+ "method": "close",
305
+ "wrapper": _wrap_close_browser_async,
306
+ },
307
+ {
308
+ "package": "playwright.async_api",
309
+ "object": "Browser",
310
+ "method": "new_context",
311
+ "wrapper": _wrap_new_context_async,
312
+ },
313
+ {
314
+ "package": "playwright.sync_api",
315
+ "object": "BrowserType",
316
+ "method": "launch_persistent_context",
317
+ "wrapper": _wrap_new_context_sync,
318
+ },
319
+ ]
320
+
321
+
262
322
  class PlaywrightInstrumentor(BaseInstrumentor):
263
- def __init__(self):
323
+ def __init__(self, client: LaminarClient, async_client: AsyncLaminarClient):
264
324
  super().__init__()
325
+ self.client = client
326
+ self.async_client = async_client
265
327
 
266
328
  def instrumentation_dependencies(self) -> Collection[str]:
267
329
  return _instruments
268
330
 
269
331
  def _instrument(self, **kwargs):
270
332
  tracer_provider = kwargs.get("tracer_provider")
271
- tracer = get_tracer(__name__, SDK_VERSION, tracer_provider)
333
+ tracer = get_tracer(__name__, __version__, tracer_provider)
272
334
 
273
335
  for wrapped_method in WRAPPED_METHODS:
274
336
  wrap_package = wrapped_method.get("package")
@@ -278,14 +340,16 @@ class PlaywrightInstrumentor(BaseInstrumentor):
278
340
  wrap_function_wrapper(
279
341
  wrap_package,
280
342
  f"{wrap_object}.{wrap_method}",
281
- _wrap(
343
+ wrapped_method.get("wrapper")(
282
344
  tracer,
345
+ self.client,
283
346
  wrapped_method,
284
347
  ),
285
348
  )
286
349
  except ModuleNotFoundError:
287
- pass # that's ok, we're not instrumenting everything
350
+ pass # that's ok, we don't want to fail if some module is missing
288
351
 
352
+ # Wrap async methods
289
353
  for wrapped_method in WRAPPED_METHODS_ASYNC:
290
354
  wrap_package = wrapped_method.get("package")
291
355
  wrap_object = wrapped_method.get("object")
@@ -294,17 +358,24 @@ class PlaywrightInstrumentor(BaseInstrumentor):
294
358
  wrap_function_wrapper(
295
359
  wrap_package,
296
360
  f"{wrap_object}.{wrap_method}",
297
- _wrap_async(
361
+ wrapped_method.get("wrapper")(
298
362
  tracer,
363
+ self.async_client,
299
364
  wrapped_method,
300
365
  ),
301
366
  )
302
367
  except ModuleNotFoundError:
303
- pass # that's ok, we're not instrumenting everything
368
+ pass # that's ok, we don't want to fail if some module is missing
304
369
 
305
370
  def _uninstrument(self, **kwargs):
306
- for wrapped_method in [*WRAPPED_METHODS, *WRAPPED_METHODS_ASYNC]:
371
+ # Unwrap methods
372
+ global _context_spans
373
+ for wrapped_method in WRAPPED_METHODS + WRAPPED_METHODS_ASYNC:
307
374
  wrap_package = wrapped_method.get("package")
308
375
  wrap_object = wrapped_method.get("object")
309
376
  wrap_method = wrapped_method.get("method")
310
377
  unwrap(wrap_package, f"{wrap_object}.{wrap_method}")
378
+ for span in _context_spans.values():
379
+ if span.is_recording():
380
+ span.end()
381
+ _context_spans = {}