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.
- lmnr/__init__.py +30 -0
- lmnr/openllmetry_sdk/__init__.py +4 -16
- lmnr/openllmetry_sdk/tracing/attributes.py +0 -1
- lmnr/openllmetry_sdk/tracing/tracing.py +30 -10
- lmnr/sdk/browser/browser_use_otel.py +4 -4
- lmnr/sdk/browser/playwright_otel.py +299 -228
- lmnr/sdk/browser/pw_utils.py +289 -0
- lmnr/sdk/browser/utils.py +18 -53
- lmnr/sdk/client/asynchronous/async_client.py +157 -0
- lmnr/sdk/client/asynchronous/resources/__init__.py +13 -0
- lmnr/sdk/client/asynchronous/resources/agent.py +220 -0
- lmnr/sdk/client/asynchronous/resources/base.py +32 -0
- lmnr/sdk/client/asynchronous/resources/browser_events.py +40 -0
- lmnr/sdk/client/asynchronous/resources/evals.py +64 -0
- lmnr/sdk/client/asynchronous/resources/pipeline.py +89 -0
- lmnr/sdk/client/asynchronous/resources/semantic_search.py +60 -0
- lmnr/sdk/client/synchronous/resources/__init__.py +7 -0
- lmnr/sdk/client/synchronous/resources/agent.py +215 -0
- lmnr/sdk/client/synchronous/resources/base.py +32 -0
- lmnr/sdk/client/synchronous/resources/browser_events.py +40 -0
- lmnr/sdk/client/synchronous/resources/evals.py +102 -0
- lmnr/sdk/client/synchronous/resources/pipeline.py +89 -0
- lmnr/sdk/client/synchronous/resources/semantic_search.py +60 -0
- lmnr/sdk/client/synchronous/sync_client.py +170 -0
- lmnr/sdk/datasets.py +7 -2
- lmnr/sdk/evaluations.py +59 -35
- lmnr/sdk/laminar.py +34 -174
- lmnr/sdk/types.py +124 -23
- lmnr/sdk/utils.py +10 -0
- lmnr/version.py +6 -6
- {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/METADATA +88 -38
- lmnr-0.5.1.dist-info/RECORD +55 -0
- {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/WHEEL +1 -1
- lmnr/sdk/client.py +0 -313
- lmnr-0.4.66.dist-info/RECORD +0 -39
- {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/LICENSE +0 -0
- {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.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
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
|
25
|
-
from playwright.sync_api import
|
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
|
-
|
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
|
86
|
-
|
87
|
-
|
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"
|
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
|
-
|
55
|
+
handle_navigation_sync(page, session_id, trace_id, client)
|
246
56
|
return page
|
247
57
|
|
248
58
|
|
249
|
-
@
|
250
|
-
async def
|
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"
|
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(
|
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__,
|
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
|
-
|
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'
|
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
|
-
|
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'
|
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
|
-
|
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 = {}
|