lmnr 0.5.2__tar.gz → 0.5.3__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 (57) hide show
  1. {lmnr-0.5.2 → lmnr-0.5.3}/PKG-INFO +2 -2
  2. {lmnr-0.5.2 → lmnr-0.5.3}/README.md +1 -1
  3. {lmnr-0.5.2 → lmnr-0.5.3}/pyproject.toml +4 -1
  4. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/__init__.py +2 -2
  5. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/cli.py +10 -8
  6. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/__init__.py +3 -3
  7. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/decorators/base.py +5 -5
  8. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/instruments.py +1 -0
  9. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/opentelemetry/instrumentation/google_genai/utils.py +1 -1
  10. lmnr-0.5.3/src/lmnr/opentelemetry_lib/tracing/__init__.py +1 -0
  11. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/tracing/context_manager.py +1 -1
  12. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/tracing/tracing.py +23 -5
  13. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/browser/browser_use_otel.py +20 -3
  14. lmnr-0.5.3/src/lmnr/sdk/browser/patchright_otel.py +177 -0
  15. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/browser/playwright_otel.py +16 -7
  16. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/browser/pw_utils.py +118 -80
  17. lmnr-0.5.3/src/lmnr/sdk/browser/rrweb/rrweb.umd.min.cjs +98 -0
  18. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/asynchronous/resources/agent.py +19 -0
  19. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/synchronous/resources/agent.py +20 -0
  20. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/decorators.py +3 -3
  21. lmnr-0.5.3/src/lmnr/sdk/eval_control.py +5 -0
  22. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/evaluations.py +8 -14
  23. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/laminar.py +8 -8
  24. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/types.py +2 -0
  25. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/version.py +1 -1
  26. lmnr-0.5.2/src/lmnr/openllmetry_sdk/tracing/__init__.py +0 -1
  27. lmnr-0.5.2/src/lmnr/sdk/browser/rrweb/rrweb.min.js +0 -18
  28. lmnr-0.5.2/src/lmnr/sdk/eval_control.py +0 -4
  29. {lmnr-0.5.2 → lmnr-0.5.3}/LICENSE +0 -0
  30. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/.flake8 +0 -0
  31. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/config/__init__.py +0 -0
  32. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/decorators/__init__.py +0 -0
  33. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/opentelemetry/instrumentation/google_genai/__init__.py +0 -0
  34. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/opentelemetry/instrumentation/google_genai/config.py +0 -0
  35. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/tracing/attributes.py +0 -0
  36. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/tracing/content_allow_list.py +0 -0
  37. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/utils/__init__.py +0 -0
  38. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/utils/in_memory_span_exporter.py +0 -0
  39. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/utils/json_encoder.py +0 -0
  40. {lmnr-0.5.2/src/lmnr/openllmetry_sdk → lmnr-0.5.3/src/lmnr/opentelemetry_lib}/utils/package_check.py +0 -0
  41. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/py.typed +0 -0
  42. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/__init__.py +0 -0
  43. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/browser/__init__.py +0 -0
  44. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/browser/utils.py +0 -0
  45. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/asynchronous/async_client.py +0 -0
  46. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/asynchronous/resources/__init__.py +0 -0
  47. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/asynchronous/resources/base.py +0 -0
  48. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/asynchronous/resources/browser_events.py +0 -0
  49. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/asynchronous/resources/evals.py +0 -0
  50. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/synchronous/resources/__init__.py +0 -0
  51. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/synchronous/resources/base.py +0 -0
  52. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/synchronous/resources/browser_events.py +0 -0
  53. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/synchronous/resources/evals.py +0 -0
  54. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/client/synchronous/sync_client.py +0 -0
  55. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/datasets.py +0 -0
  56. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/log.py +0 -0
  57. {lmnr-0.5.2 → lmnr-0.5.3}/src/lmnr/sdk/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lmnr
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: Python SDK for Laminar
5
5
  License: Apache-2.0
6
6
  Author: lmnr.ai
@@ -222,7 +222,7 @@ def handle_user_request(topic: str):
222
222
  Laminar allows you to automatically instrument majority of the most popular LLM, Vector DB, database, requests, and other libraries.
223
223
 
224
224
  If you want to automatically instrument a default set of libraries, then simply do NOT pass `instruments` argument to `.initialize()`.
225
- See the full list of available instrumentations in the [enum](https://github.com/lmnr-ai/lmnr-python/blob/main/src/lmnr/openllmetry_sdk/instruments.py).
225
+ See the full list of available instrumentations in the [enum](https://github.com/lmnr-ai/lmnr-python/blob/main/src/lmnr/opentelemetry_lib/instruments.py).
226
226
 
227
227
  If you want to automatically instrument only specific LLM, Vector DB, or other
228
228
  calls with OpenTelemetry-compatible instrumentation, then pass the appropriate instruments to `.initialize()`.
@@ -113,7 +113,7 @@ def handle_user_request(topic: str):
113
113
  Laminar allows you to automatically instrument majority of the most popular LLM, Vector DB, database, requests, and other libraries.
114
114
 
115
115
  If you want to automatically instrument a default set of libraries, then simply do NOT pass `instruments` argument to `.initialize()`.
116
- See the full list of available instrumentations in the [enum](https://github.com/lmnr-ai/lmnr-python/blob/main/src/lmnr/openllmetry_sdk/instruments.py).
116
+ See the full list of available instrumentations in the [enum](https://github.com/lmnr-ai/lmnr-python/blob/main/src/lmnr/opentelemetry_lib/instruments.py).
117
117
 
118
118
  If you want to automatically instrument only specific LLM, Vector DB, or other
119
119
  calls with OpenTelemetry-compatible instrumentation, then pass the appropriate instruments to `.initialize()`.
@@ -6,7 +6,7 @@
6
6
 
7
7
  [project]
8
8
  name = "lmnr"
9
- version = "0.5.2"
9
+ version = "0.5.3"
10
10
  description = "Python SDK for Laminar"
11
11
  authors = [
12
12
  { name = "lmnr.ai", email = "founders@lmnr.ai" }
@@ -120,6 +120,9 @@ dev = [
120
120
  [build-system]
121
121
  requires = ["poetry-core"]
122
122
  build-backend = "poetry.core.masonry.api"
123
+
124
+ [tool.uv.workspace]
125
+ members = ["examples/fastapi-app"]
123
126
  # we can move to uv_build, once it's more stable
124
127
  # https://github.com/astral-sh/uv/issues/3957
125
128
  # requires = ["uv_build>=0.6.16,<0.7"]
@@ -13,8 +13,8 @@ from .sdk.types import (
13
13
  )
14
14
  from .sdk.decorators import observe
15
15
  from .sdk.types import LaminarSpanContext
16
- from .openllmetry_sdk import Instruments
17
- from .openllmetry_sdk.tracing.attributes import Attributes
16
+ from .opentelemetry_lib import Instruments
17
+ from .opentelemetry_lib.tracing.attributes import Attributes
18
18
  from opentelemetry.trace import use_span
19
19
 
20
20
  __all__ = [
@@ -1,18 +1,17 @@
1
1
  from argparse import ArgumentParser
2
2
  import asyncio
3
3
  import importlib.util
4
- import logging
5
4
  import os
6
5
  import re
7
6
  import sys
7
+ from typing import Optional
8
+
9
+ from lmnr.sdk.evaluations import Evaluation
8
10
 
9
11
  from .sdk.eval_control import PREPARE_ONLY, EVALUATION_INSTANCE
10
- from .sdk.log import ColorfulFormatter
12
+ from .sdk.log import get_default_logger
11
13
 
12
- LOG = logging.getLogger(__name__)
13
- console_log_handler = logging.StreamHandler()
14
- console_log_handler.setFormatter(ColorfulFormatter())
15
- LOG.addHandler(console_log_handler)
14
+ LOG = get_default_logger(__name__)
16
15
 
17
16
 
18
17
  EVAL_DIR = "evals"
@@ -28,7 +27,10 @@ async def run_evaluation(args):
28
27
  if re.match(r".*_eval\.py$", f) or re.match(r"eval_.*\.py$", f)
29
28
  ]
30
29
  if len(files) == 0:
31
- LOG.error("No evaluation files found in evals directory")
30
+ LOG.error("No evaluation files found in `evals` directory")
31
+ LOG.info(
32
+ "Eval files must be located in the `evals` directory and must be named *_eval.py or eval_*.py"
33
+ )
32
34
  return
33
35
  files.sort()
34
36
  LOG.info(f"Located {len(files)} evaluation files in {EVAL_DIR}")
@@ -53,7 +55,7 @@ async def run_evaluation(args):
53
55
  sys.modules[name] = mod
54
56
 
55
57
  spec.loader.exec_module(mod)
56
- evaluation = EVALUATION_INSTANCE.get()
58
+ evaluation: Optional[Evaluation] = EVALUATION_INSTANCE.get()
57
59
  if evaluation is None:
58
60
  LOG.warning("Evaluation instance not found")
59
61
  if args.fail_on_error:
@@ -7,12 +7,12 @@ from opentelemetry.sdk.resources import SERVICE_NAME
7
7
  from opentelemetry.propagators.textmap import TextMapPropagator
8
8
  from opentelemetry.util.re import parse_env_headers
9
9
 
10
- from lmnr.openllmetry_sdk.instruments import Instruments
11
- from lmnr.openllmetry_sdk.config import (
10
+ from lmnr.opentelemetry_lib.instruments import Instruments
11
+ from lmnr.opentelemetry_lib.config import (
12
12
  is_content_tracing_enabled,
13
13
  is_tracing_enabled,
14
14
  )
15
- from lmnr.openllmetry_sdk.tracing.tracing import TracerWrapper
15
+ from lmnr.opentelemetry_lib.tracing.tracing import TracerWrapper
16
16
  from typing import Dict
17
17
 
18
18
 
@@ -10,11 +10,11 @@ from opentelemetry import context as context_api
10
10
  from opentelemetry.trace import Span
11
11
 
12
12
  from lmnr.sdk.utils import get_input_from_func_args, is_method
13
- from lmnr.openllmetry_sdk.tracing import get_tracer
14
- from lmnr.openllmetry_sdk.tracing.attributes import SPAN_INPUT, SPAN_OUTPUT, SPAN_TYPE
15
- from lmnr.openllmetry_sdk.tracing.tracing import TracerWrapper
16
- from lmnr.openllmetry_sdk.utils.json_encoder import JSONEncoder
17
- from lmnr.openllmetry_sdk.config import MAX_MANUAL_SPAN_PAYLOAD_SIZE
13
+ from lmnr.opentelemetry_lib.tracing import get_tracer
14
+ from lmnr.opentelemetry_lib.tracing.attributes import SPAN_INPUT, SPAN_OUTPUT, SPAN_TYPE
15
+ from lmnr.opentelemetry_lib.tracing.tracing import TracerWrapper
16
+ from lmnr.opentelemetry_lib.utils.json_encoder import JSONEncoder
17
+ from lmnr.opentelemetry_lib.config import MAX_MANUAL_SPAN_PAYLOAD_SIZE
18
18
 
19
19
 
20
20
  class CustomJSONEncoder(JSONEncoder):
@@ -24,6 +24,7 @@ class Instruments(Enum):
24
24
  OPENAI = "openai"
25
25
  PINECONE = "pinecone"
26
26
  PLAYWRIGHT = "playwright"
27
+ PATCHRIGHT = "patchright"
27
28
  QDRANT = "qdrant"
28
29
  REPLICATE = "replicate"
29
30
  SAGEMAKER = "sagemaker"
@@ -34,7 +34,7 @@ def dont_throw(func):
34
34
  return func(*args, **kwargs)
35
35
  except Exception as e:
36
36
  logger.debug(
37
- "OpenLLMetry failed to trace in %s, error: %s",
37
+ "Laminar failed to trace in %s, error: %s",
38
38
  func.__name__,
39
39
  traceback.format_exc(),
40
40
  )
@@ -0,0 +1 @@
1
+ from lmnr.opentelemetry_lib.tracing.context_manager import get_tracer
@@ -1,6 +1,6 @@
1
1
  from contextlib import contextmanager
2
2
 
3
- from lmnr.openllmetry_sdk.tracing.tracing import TracerWrapper
3
+ from lmnr.opentelemetry_lib.tracing.tracing import TracerWrapper
4
4
 
5
5
 
6
6
  @contextmanager
@@ -7,8 +7,8 @@ from contextvars import Context
7
7
  from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
8
8
  from lmnr.sdk.client.synchronous.sync_client import LaminarClient
9
9
  from lmnr.sdk.log import VerboseColorfulFormatter
10
- from lmnr.openllmetry_sdk.instruments import Instruments
11
- from lmnr.openllmetry_sdk.tracing.attributes import (
10
+ from lmnr.opentelemetry_lib.instruments import Instruments
11
+ from lmnr.opentelemetry_lib.tracing.attributes import (
12
12
  ASSOCIATION_PROPERTIES,
13
13
  SPAN_IDS_PATH,
14
14
  SPAN_INSTRUMENTATION_SOURCE,
@@ -17,9 +17,9 @@ from lmnr.openllmetry_sdk.tracing.attributes import (
17
17
  SPAN_PATH,
18
18
  TRACING_LEVEL,
19
19
  )
20
- from lmnr.openllmetry_sdk.tracing.content_allow_list import ContentAllowList
21
- from lmnr.openllmetry_sdk.utils import is_notebook
22
- from lmnr.openllmetry_sdk.utils.package_check import is_package_installed
20
+ from lmnr.opentelemetry_lib.tracing.content_allow_list import ContentAllowList
21
+ from lmnr.opentelemetry_lib.utils import is_notebook
22
+ from lmnr.opentelemetry_lib.utils.package_check import is_package_installed
23
23
  from opentelemetry import trace
24
24
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
25
25
  OTLPSpanExporter as HTTPExporter,
@@ -455,6 +455,9 @@ def init_instrumentations(
455
455
  elif instrument == Instruments.PLAYWRIGHT:
456
456
  if init_playwright_instrumentor(client, async_client):
457
457
  instrument_set = True
458
+ elif instrument == Instruments.PATCHRIGHT:
459
+ if init_patchright_instrumentor(client, async_client):
460
+ instrument_set = True
458
461
  elif instrument == Instruments.BROWSER_USE:
459
462
  if init_browser_use_instrumentor():
460
463
  instrument_set = True
@@ -499,6 +502,21 @@ def init_playwright_instrumentor(
499
502
  return False
500
503
 
501
504
 
505
+ def init_patchright_instrumentor(
506
+ client: LaminarClient, async_client: AsyncLaminarClient
507
+ ):
508
+ try:
509
+ if is_package_installed("patchright"):
510
+ from lmnr.sdk.browser.patchright_otel import PatchrightInstrumentor
511
+
512
+ instrumentor = PatchrightInstrumentor(client, async_client)
513
+ instrumentor.instrument()
514
+ return True
515
+ except Exception as e:
516
+ module_logger.error(f"Error initializing patchright instrumentor: {e}")
517
+ return False
518
+
519
+
502
520
  def init_openai_instrumentor(should_enrich_metrics: bool):
503
521
  try:
504
522
  if is_package_installed("openai") and is_package_installed(
@@ -1,4 +1,4 @@
1
- from lmnr.openllmetry_sdk.decorators.base import json_dumps
1
+ from lmnr.opentelemetry_lib.decorators.base import json_dumps
2
2
  from lmnr.sdk.browser.utils import with_tracer_wrapper
3
3
  from lmnr.sdk.utils import get_input_from_func_args
4
4
  from lmnr.version import __version__
@@ -8,6 +8,16 @@ from opentelemetry.instrumentation.utils import unwrap
8
8
  from opentelemetry.trace import get_tracer, Tracer
9
9
  from typing import Collection
10
10
  from wrapt import wrap_function_wrapper
11
+ import pydantic
12
+
13
+ try:
14
+ from browser_use import AgentHistoryList
15
+ except ImportError as e:
16
+ raise ImportError(
17
+ f"Attempted to import {__file__}, but it is designed "
18
+ "to patch Browser Use, which is not installed. Use `pip install browser-use` "
19
+ "to install Browser Use or remove this import."
20
+ ) from e
11
21
 
12
22
  _instruments = ("browser-use >= 0.1.0",)
13
23
 
@@ -18,7 +28,7 @@ WRAPPED_METHODS = [
18
28
  "method": "run",
19
29
  "span_name": "agent.run",
20
30
  "ignore_input": False,
21
- "ignore_output": True,
31
+ "ignore_output": False,
22
32
  "span_type": "DEFAULT",
23
33
  },
24
34
  {
@@ -73,7 +83,14 @@ async def _wrap(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
73
83
  span.set_attributes(attributes)
74
84
  result = await wrapped(*args, **kwargs)
75
85
  if not to_wrap.get("ignore_output"):
76
- span.set_attribute("lmnr.span.output", json_dumps(result))
86
+ if isinstance(result, AgentHistoryList):
87
+ result = result.final_result()
88
+ serialized = (
89
+ result.model_dump_json()
90
+ if isinstance(result, pydantic.BaseModel)
91
+ else json_dumps(result)
92
+ )
93
+ span.set_attribute("lmnr.span.output", serialized)
77
94
  return result
78
95
 
79
96
 
@@ -0,0 +1,177 @@
1
+ from lmnr.sdk.browser.playwright_otel import (
2
+ _wrap_new_page,
3
+ _wrap_new_page_async,
4
+ _wrap_new_browser_sync,
5
+ _wrap_new_browser_async,
6
+ _wrap_new_context_sync,
7
+ _wrap_new_context_async,
8
+ _wrap_close_browser_sync,
9
+ _wrap_close_browser_async,
10
+ )
11
+ from lmnr.sdk.client.synchronous.sync_client import LaminarClient
12
+ from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
13
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
14
+ from opentelemetry.instrumentation.utils import unwrap
15
+ from opentelemetry.trace import get_tracer
16
+ from lmnr.version import __version__
17
+ from typing import Collection
18
+ from wrapt import wrap_function_wrapper
19
+
20
+ _instruments = ("patchright >= 1.9.0",)
21
+
22
+ WRAPPED_METHODS = [
23
+ {
24
+ "package": "patchright.sync_api",
25
+ "object": "BrowserContext",
26
+ "method": "new_page",
27
+ "wrapper": _wrap_new_page,
28
+ },
29
+ {
30
+ "package": "patchright.sync_api",
31
+ "object": "Browser",
32
+ "method": "new_page",
33
+ "wrapper": _wrap_new_page,
34
+ },
35
+ {
36
+ "package": "patchright.sync_api",
37
+ "object": "BrowserType",
38
+ "method": "launch",
39
+ "wrapper": _wrap_new_browser_sync,
40
+ },
41
+ {
42
+ "package": "patchright.sync_api",
43
+ "object": "BrowserType",
44
+ "method": "connect",
45
+ "wrapper": _wrap_new_browser_sync,
46
+ },
47
+ {
48
+ "package": "patchright.sync_api",
49
+ "object": "BrowserType",
50
+ "method": "connect_over_cdp",
51
+ "wrapper": _wrap_new_browser_sync,
52
+ },
53
+ {
54
+ "package": "patchright.sync_api",
55
+ "object": "Browser",
56
+ "method": "close",
57
+ "wrapper": _wrap_close_browser_sync,
58
+ },
59
+ {
60
+ "package": "patchright.sync_api",
61
+ "object": "Browser",
62
+ "method": "new_context",
63
+ "wrapper": _wrap_new_context_sync,
64
+ },
65
+ {
66
+ "package": "patchright.sync_api",
67
+ "object": "BrowserType",
68
+ "method": "launch_persistent_context",
69
+ "wrapper": _wrap_new_context_sync,
70
+ },
71
+ ]
72
+
73
+ WRAPPED_METHODS_ASYNC = [
74
+ {
75
+ "package": "patchright.async_api",
76
+ "object": "BrowserContext",
77
+ "method": "new_page",
78
+ "wrapper": _wrap_new_page_async,
79
+ },
80
+ {
81
+ "package": "patchright.async_api",
82
+ "object": "Browser",
83
+ "method": "new_page",
84
+ "wrapper": _wrap_new_page_async,
85
+ },
86
+ {
87
+ "package": "patchright.async_api",
88
+ "object": "BrowserType",
89
+ "method": "launch",
90
+ "wrapper": _wrap_new_browser_async,
91
+ },
92
+ {
93
+ "package": "patchright.async_api",
94
+ "object": "BrowserType",
95
+ "method": "connect",
96
+ "wrapper": _wrap_new_browser_async,
97
+ },
98
+ {
99
+ "package": "patchright.async_api",
100
+ "object": "BrowserType",
101
+ "method": "connect_over_cdp",
102
+ "wrapper": _wrap_new_browser_async,
103
+ },
104
+ {
105
+ "package": "patchright.async_api",
106
+ "object": "Browser",
107
+ "method": "close",
108
+ "wrapper": _wrap_close_browser_async,
109
+ },
110
+ {
111
+ "package": "patchright.async_api",
112
+ "object": "Browser",
113
+ "method": "new_context",
114
+ "wrapper": _wrap_new_context_async,
115
+ },
116
+ {
117
+ "package": "patchright.async_api",
118
+ "object": "BrowserType",
119
+ "method": "launch_persistent_context",
120
+ "wrapper": _wrap_new_context_async,
121
+ },
122
+ ]
123
+
124
+
125
+ class PatchrightInstrumentor(BaseInstrumentor):
126
+ def __init__(self, client: LaminarClient, async_client: AsyncLaminarClient):
127
+ super().__init__()
128
+ self.client = client
129
+ self.async_client = async_client
130
+
131
+ def instrumentation_dependencies(self) -> Collection[str]:
132
+ return _instruments
133
+
134
+ def _instrument(self, **kwargs):
135
+ tracer_provider = kwargs.get("tracer_provider")
136
+ tracer = get_tracer(__name__, __version__, tracer_provider)
137
+
138
+ for wrapped_method in WRAPPED_METHODS:
139
+ wrap_package = wrapped_method.get("package")
140
+ wrap_object = wrapped_method.get("object")
141
+ wrap_method = wrapped_method.get("method")
142
+ try:
143
+ wrap_function_wrapper(
144
+ wrap_package,
145
+ f"{wrap_object}.{wrap_method}",
146
+ wrapped_method.get("wrapper")(
147
+ tracer,
148
+ self.client,
149
+ wrapped_method,
150
+ ),
151
+ )
152
+ except ModuleNotFoundError:
153
+ pass
154
+
155
+ for wrapped_method in WRAPPED_METHODS_ASYNC:
156
+ wrap_package = wrapped_method.get("package")
157
+ wrap_object = wrapped_method.get("object")
158
+ wrap_method = wrapped_method.get("method")
159
+ try:
160
+ wrap_function_wrapper(
161
+ wrap_package,
162
+ f"{wrap_object}.{wrap_method}",
163
+ wrapped_method.get("wrapper")(
164
+ tracer,
165
+ self.async_client,
166
+ wrapped_method,
167
+ ),
168
+ )
169
+ except ModuleNotFoundError:
170
+ pass
171
+
172
+ def _uninstrument(self, **kwargs):
173
+ for wrapped_method in WRAPPED_METHODS + WRAPPED_METHODS_ASYNC:
174
+ wrap_package = wrapped_method.get("package")
175
+ wrap_object = wrapped_method.get("object")
176
+ wrap_method = wrapped_method.get("method")
177
+ unwrap(wrap_package, f"{wrap_object}.{wrap_method}")
@@ -22,10 +22,11 @@ from typing import Collection
22
22
  from wrapt import wrap_function_wrapper
23
23
 
24
24
  try:
25
- from playwright.async_api import Browser, BrowserContext
25
+ from playwright.async_api import Browser, BrowserContext, Page
26
26
  from playwright.sync_api import (
27
27
  Browser as SyncBrowser,
28
28
  BrowserContext as SyncBrowserContext,
29
+ Page as SyncPage,
29
30
  )
30
31
  except ImportError as e:
31
32
  raise ImportError(
@@ -88,10 +89,15 @@ def _wrap_new_browser_sync(
88
89
  _context_spans[id(context)] = span
89
90
  span.set_attribute("lmnr.internal.has_browser_session", True)
90
91
  trace_id = format(span.get_span_context().trace_id, "032x")
92
+
93
+ def handle_page_navigation(page: SyncPage):
94
+ return handle_navigation_sync(page, session_id, trace_id, client)
95
+
91
96
  context.on(
92
97
  "page",
93
- lambda page: handle_navigation_sync(page, session_id, trace_id, client),
98
+ handle_page_navigation,
94
99
  )
100
+
95
101
  for page in context.pages:
96
102
  handle_navigation_sync(page, session_id, trace_id, client)
97
103
  return browser
@@ -115,7 +121,7 @@ async def _wrap_new_browser_async(
115
121
  span.set_attribute("lmnr.internal.has_browser_session", True)
116
122
  trace_id = format(span.get_span_context().trace_id, "032x")
117
123
 
118
- async def handle_page_navigation(page):
124
+ async def handle_page_navigation(page: Page):
119
125
  return await handle_navigation_async(page, session_id, trace_id, client)
120
126
 
121
127
  context.on("page", handle_page_navigation)
@@ -140,9 +146,12 @@ def _wrap_new_context_sync(
140
146
  span.set_attribute("lmnr.internal.has_browser_session", True)
141
147
  trace_id = format(span.get_span_context().trace_id, "032x")
142
148
 
149
+ def handle_page_navigation(page: SyncPage):
150
+ return handle_navigation_sync(page, session_id, trace_id, client)
151
+
143
152
  context.on(
144
153
  "page",
145
- lambda page: handle_navigation_sync(page, session_id, trace_id, client),
154
+ handle_page_navigation,
146
155
  )
147
156
  for page in context.pages:
148
157
  handle_navigation_sync(page, session_id, trace_id, client)
@@ -153,7 +162,7 @@ def _wrap_new_context_sync(
153
162
  async def _wrap_new_context_async(
154
163
  tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
155
164
  ):
156
- context: SyncBrowserContext = await wrapped(*args, **kwargs)
165
+ context: BrowserContext = await wrapped(*args, **kwargs)
157
166
  session_id = str(uuid.uuid4().hex)
158
167
  span = get_current_span()
159
168
  if span == INVALID_SPAN:
@@ -311,10 +320,10 @@ WRAPPED_METHODS_ASYNC = [
311
320
  "wrapper": _wrap_new_context_async,
312
321
  },
313
322
  {
314
- "package": "playwright.sync_api",
323
+ "package": "playwright.async_api",
315
324
  "object": "BrowserType",
316
325
  "method": "launch_persistent_context",
317
- "wrapper": _wrap_new_context_sync,
326
+ "wrapper": _wrap_new_context_async,
318
327
  },
319
328
  ]
320
329