diff --git a/dimos/agents/mcp/mcp_client.py b/dimos/agents/mcp/mcp_client.py index 75b532e9cc..4cd1b9982d 100644 --- a/dimos/agents/mcp/mcp_client.py +++ b/dimos/agents/mcp/mcp_client.py @@ -35,6 +35,7 @@ from dimos.core.module import Module, ModuleConfig from dimos.core.rpc_client import RPCClient from dimos.core.stream import In, Out +from dimos.telemetry import session_attributes, span as trace_span from dimos.utils.logging_config import setup_logger from dimos.utils.sequential_ids import SequentialIds @@ -64,6 +65,7 @@ class McpClient(Module): _http_client: httpx.Client _seq_ids: SequentialIds _tool_stream_cleanup: Callable[[], None] | None + _session_id: str def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) @@ -81,6 +83,9 @@ def __init__(self, **kwargs: Any) -> None: self._http_client = httpx.Client(timeout=120.0) self._seq_ids = SequentialIds() self._tool_stream_cleanup = None + # Stable per-instance id; every agent.turn span is tagged with it so + # the observability backend groups all turns into one session. + self._session_id = str(uuid.uuid4()) def __reduce__(self) -> Any: return (self.__class__, (), {}) @@ -323,12 +328,13 @@ def _process_message( pretty_print_langchain_message(message) self.agent.publish(message) - for update in state_graph.stream({"messages": self._history}, stream_mode="updates"): - for node_output in update.values(): - for msg in node_output.get("messages", []): - self._history.append(msg) - pretty_print_langchain_message(msg) - self.agent.publish(msg) + with trace_span("agent.turn", **session_attributes(self._session_id)): + for update in state_graph.stream({"messages": self._history}, stream_mode="updates"): + for node_output in update.values(): + for msg in node_output.get("messages", []): + self._history.append(msg) + pretty_print_langchain_message(msg) + self.agent.publish(msg) if self._message_queue.empty(): self.agent_idle.publish(True) diff --git a/dimos/telemetry/__init__.py b/dimos/telemetry/__init__.py new file mode 100644 index 0000000000..50408fdd39 --- /dev/null +++ b/dimos/telemetry/__init__.py @@ -0,0 +1,152 @@ +# Copyright 2025-2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Opt-in OpenTelemetry tracing for DimOS. + +Importing this package has no side effects and triggers no +opentelemetry imports, even when the `dimos[otel]` extra is installed. +The public `span` context manager is a silent no-op until tracing is +wired up. + +Three ways to turn tracing on: + +1. Env-driven setup. Install the extra and set OTEL_EXPORTER_OTLP_ENDPOINT + (plus optional OTEL_EXPORTER_OTLP_HEADERS and OTEL_SERVICE_NAME). + `enable()` runs automatically on first import of this package. The + OTLP HTTP exporter is configured, and LangChain auto-instrumentation + is applied when `openinference-instrumentation-langchain` is present. + +2. Caller-owned provider. When the host app has its own TracerProvider: + + dimos.telemetry.configure_tracing(my_provider) + +3. Standard OTEL `BaseInstrumentor`: + + DimosInstrumentor().instrument(tracer_provider=my_provider) + +Any OTLP-compatible backend works (Langfuse, Arize Phoenix, LangSmith, +Opik, etc.). Vendor selection is by env var, not code. +""" + +import os +from typing import Any + +from dimos.telemetry._api import span +from dimos.telemetry._manager import _manager +from dimos.utils.logging_config import setup_logger + +logger = setup_logger() + +__all__ = [ + "DimosInstrumentor", + "configure_tracing", + "enable", + "session_attributes", + "span", +] + + +def configure_tracing(tracer_provider: Any, tracer_name: str = "dimos") -> None: + """Wire DimOS into a caller-owned TracerProvider. + + Raises RuntimeError when the `dimos[otel]` extra isn't installed. + """ + try: + import opentelemetry # noqa: F401 (presence check) + except ImportError: + raise RuntimeError( + "dimos.telemetry: opentelemetry is not installed. " + "Install with `pip install dimos[otel]` (or `uv sync --extra otel`)." + ) from None + _manager.configure(tracer_provider.get_tracer(tracer_name)) + + +def enable() -> bool: + """Auto-configure tracing from the standard OTEL env vars. + + Returns True when tracing was wired up, False otherwise (no exporter + endpoint set, or the `dimos[otel]` extra isn't installed). + """ + if not os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"): + return False + try: + import atexit + + from opentelemetry import trace + from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( + OTLPSpanExporter, + ) + from opentelemetry.sdk.resources import Resource + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + except ImportError: + logger.warning( + "OTEL_EXPORTER_OTLP_ENDPOINT is set but `dimos[otel]` is not " + "installed; tracing disabled." + ) + return False + + provider = TracerProvider( + resource=Resource.create({"service.name": os.environ.get("OTEL_SERVICE_NAME", "dimos")}) + ) + provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) + trace.set_tracer_provider(provider) + atexit.register(provider.shutdown) + configure_tracing(provider) + + try: + from openinference.instrumentation.langchain import LangChainInstrumentor + + LangChainInstrumentor().instrument(tracer_provider=provider) + except ImportError: + # Auto-instrumentation is best-effort and optional within the extra. + pass + + return True + + +def session_attributes(session_id: str) -> dict[str, str]: + """Span-attribute dict that groups traces under one session in + common LLM-observability backends. + + Sets three keys to cover the four major OTLP-compatible backends: + session.id — OpenInference convention. + Used by Langfuse and Arize Phoenix / AX. + langsmith.trace.session_id — LangSmith's own attribute namespace. + thread_id — Opik's Threads attribute (added in + comet-ml/opik#3441). + """ + return { + "session.id": session_id, + "langsmith.trace.session_id": session_id, + "thread_id": session_id, + } + + +def __getattr__(name: str) -> Any: + # Resolve DimosInstrumentor lazily so importing this package never + # pulls in opentelemetry.instrumentation. + if name == "DimosInstrumentor": + from dimos.telemetry.instrumentor import DimosInstrumentor + + globals()[name] = DimosInstrumentor + return DimosInstrumentor + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +# Boot-time auto-enable: treat OTEL_EXPORTER_OTLP_ENDPOINT being set as the +# user's explicit opt-in. When unset (the default), this is a single +# os.environ.get() check; no OTEL packages are imported. +if os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"): + enable() diff --git a/dimos/telemetry/_api.py b/dimos/telemetry/_api.py new file mode 100644 index 0000000000..f7e9906d1e --- /dev/null +++ b/dimos/telemetry/_api.py @@ -0,0 +1,41 @@ +# Copyright 2025-2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Public tracing helpers for `dimos.telemetry`. + +Safe to call whether or not the `dimos[otel]` extra is installed and +whether or not tracing has been wired up. Short-circuits to a no-op +when the manager is in its default off state. +""" + +from collections.abc import Iterator +from contextlib import contextmanager +from typing import Any + +from dimos.telemetry._manager import _manager + + +@contextmanager +def span(name: str, **attributes: Any) -> Iterator[Any]: + """Open a span around a block. + + Yields the active OTEL Span when tracing is configured, or None + otherwise. In the no-op case the only cost is a single boolean + check on the manager. + """ + if not _manager._export_enabled: + yield None + return + with _manager.tracer.start_as_current_span(name, attributes=attributes or None) as s: + yield s diff --git a/dimos/telemetry/_manager.py b/dimos/telemetry/_manager.py new file mode 100644 index 0000000000..b494da3979 --- /dev/null +++ b/dimos/telemetry/_manager.py @@ -0,0 +1,51 @@ +# Copyright 2025-2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Internal tracer state for `dimos.telemetry`. + +Owns a module-global `TracerManager` that holds the active OTEL tracer +and an explicit on/off flag. No opentelemetry packages are imported +here; that cost is paid only when a wiring entry point (`enable`, +`configure_tracing`, `DimosInstrumentor`) is called. +""" + +from typing import Any + +from dimos.utils.logging_config import setup_logger + +logger = setup_logger() + + +class TracerManager: + """Holds the active tracer plus an on/off flag. + + Stays inert at import time: the tracer is None and export is off + until a caller wires us up. + """ + + def __init__(self) -> None: + self.tracer: Any = None + self._export_enabled: bool = False + + def configure(self, tracer: Any) -> None: + self.tracer = tracer + self._export_enabled = True + logger.info("dimos.telemetry: tracing configured.") + + def reset(self) -> None: + self.tracer = None + self._export_enabled = False + + +_manager = TracerManager() diff --git a/dimos/telemetry/instrumentor.py b/dimos/telemetry/instrumentor.py new file mode 100644 index 0000000000..399030f138 --- /dev/null +++ b/dimos/telemetry/instrumentor.py @@ -0,0 +1,72 @@ +# Copyright 2025-2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Standard OTEL `BaseInstrumentor` integration for DimOS. + +Host applications wire DimOS into their telemetry stack the same way +they wire every other instrumented library: + + from dimos.telemetry import DimosInstrumentor + DimosInstrumentor().instrument(tracer_provider=my_provider) + +This module is imported lazily by `dimos.telemetry.__getattr__` only +when `DimosInstrumentor` is accessed — that's the gate that keeps OTEL +imports out of `import dimos.telemetry`. Inside this module the OTEL +imports run at the top level, behind a try/except that falls back to a +stub class when `dimos[otel]` isn't installed. +""" + +from typing import Any + +try: + from collections.abc import Collection + + from opentelemetry import trace as otel_trace + from opentelemetry.instrumentation.instrumentor import ( # type: ignore[attr-defined] + BaseInstrumentor, + ) + + class DimosInstrumentor(BaseInstrumentor): # type: ignore[misc, valid-type] + """OTEL instrumentor for DimOS. + + Calling `instrument(tracer_provider=...)` wires the supplied + provider into `dimos.telemetry`. If `tracer_provider` is + omitted, the global provider is used. + """ + + def instrumentation_dependencies(self) -> "Collection[str]": + return [] + + def _instrument(self, **kwargs: Any) -> None: + from dimos.telemetry import configure_tracing + + tracer_provider = kwargs.get("tracer_provider") or otel_trace.get_tracer_provider() + tracer_name = kwargs.get("tracer_name", "dimos") + configure_tracing(tracer_provider, tracer_name) + + def _uninstrument(self, **kwargs: Any) -> None: + from dimos.telemetry._manager import _manager + + _manager.reset() + +except ImportError: + + class DimosInstrumentor: # type: ignore[no-redef] + """Stub: install `dimos[otel]` to use.""" + + def __init__(self) -> None: + raise RuntimeError( + "DimosInstrumentor requires the `dimos[otel]` extra. " + "Install with `pip install dimos[otel]` (or `uv sync --extra otel`)." + ) diff --git a/dimos/telemetry/test_telemetry.py b/dimos/telemetry/test_telemetry.py new file mode 100644 index 0000000000..80c37e42b4 --- /dev/null +++ b/dimos/telemetry/test_telemetry.py @@ -0,0 +1,98 @@ +# Copyright 2025-2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for `dimos.telemetry`. + +Covers the strict-opt-in contract (no OTEL imports until the user opts +in), the no-op default state of the public helpers, and the shape of +the session-grouping attributes used by downstream backends. +""" + +import importlib.util +import os +import subprocess +import sys +import textwrap + +import pytest + +import dimos.telemetry +from dimos.telemetry._manager import _manager + + +@pytest.fixture(autouse=True) +def _reset_telemetry_state(monkeypatch: pytest.MonkeyPatch) -> None: + """Each test starts with the manager off and no auto-enable env vars.""" + monkeypatch.delenv("OTEL_EXPORTER_OTLP_ENDPOINT", raising=False) + monkeypatch.delenv("OTEL_EXPORTER_OTLP_HEADERS", raising=False) + _manager.reset() + + +def test_span_is_noop_by_default() -> None: + with dimos.telemetry.span("test") as s: + assert s is None + + +def test_span_accepts_dotted_attribute_keys() -> None: + # OpenInference and LangSmith both use dotted keys (`session.id`, + # `langsmith.trace.session_id`). The `**` unpack must accept them + # without an `invalid kwarg` error. + with dimos.telemetry.span("test", **{"session.id": "abc"}) as s: + assert s is None + + +def test_session_attributes_sets_all_backend_keys() -> None: + assert dimos.telemetry.session_attributes("session-123") == { + "session.id": "session-123", + "langsmith.trace.session_id": "session-123", + "thread_id": "session-123", + } + + +def test_enable_returns_false_without_endpoint_env_var() -> None: + assert dimos.telemetry.enable() is False + + +def test_dimos_instrumentor_resolves_via_lazy_getattr() -> None: + # The symbol is defined via module-level `__getattr__`, not as a + # top-level name. Accessing it must succeed regardless of whether + # the `dimos[otel]` extra is installed (stub class otherwise). + cls = dimos.telemetry.DimosInstrumentor + assert cls.__name__ == "DimosInstrumentor" + + +def test_importing_package_triggers_no_opentelemetry_imports() -> None: + """Strict opt-in: `import dimos.telemetry` must not load any + opentelemetry module, even when the `dimos[otel]` extra is + installed. Runs in a fresh interpreter so previous in-process + imports don't pollute the assertion. + """ + if importlib.util.find_spec("opentelemetry") is None: + pytest.skip("opentelemetry not installed; strict-opt-in is trivial here") + + code = textwrap.dedent( + """ + import sys + import dimos.telemetry # noqa: F401 + otel = [ + m for m in sys.modules + if m == "opentelemetry" or m.startswith("opentelemetry.") + ] + print(len(otel)) + """ + ) + env = {**os.environ} + env.pop("OTEL_EXPORTER_OTLP_ENDPOINT", None) + out = subprocess.check_output([sys.executable, "-c", code], env=env, text=True).strip() + assert out == "0", f"expected 0 opentelemetry modules after import, got {out}" diff --git a/pyproject.toml b/pyproject.toml index b54f5e0451..0e04349671 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -174,6 +174,19 @@ agents = [ "faster-whisper>=1.0.0", ] +# Opt-in OpenTelemetry tracing for `dimos.telemetry`. Base install ships +# no OTEL packages; `dimos.telemetry` is fully no-op until this extra is +# installed AND tracing is wired up (see dimos/telemetry/__init__.py). +otel = [ + "opentelemetry-api>=1.27.0", + "opentelemetry-sdk>=1.27.0", + "opentelemetry-exporter-otlp-proto-http>=1.27.0", + "opentelemetry-instrumentation>=0.48b0", + # Auto-instrumentation for LangChain / LangGraph calls. Optional within + # the extra: enable() falls back gracefully if missing. + "openinference-instrumentation-langchain>=0.1.30", +] + web = [ "fastapi>=0.115.6", "sse-starlette>=2.2.1", diff --git a/uv.lock b/uv.lock index 993fc21f74..d02eea6b70 100644 --- a/uv.lock +++ b/uv.lock @@ -2091,6 +2091,13 @@ misc = [ navigation = [ { name = "gtsam-extended" }, ] +otel = [ + { name = "openinference-instrumentation-langchain" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-sdk" }, +] perception = [ { name = "filterpy" }, { name = "hydra-core" }, @@ -2387,6 +2394,11 @@ requires-dist = [ { name = "opencv-contrib-python", marker = "extra == 'apriltag'", specifier = "==4.10.0.84" }, { name = "opencv-python" }, { name = "opencv-python-headless", marker = "extra == 'docker'" }, + { name = "openinference-instrumentation-langchain", marker = "extra == 'otel'", specifier = ">=0.1.30" }, + { name = "opentelemetry-api", marker = "extra == 'otel'", specifier = ">=1.27.0" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'otel'", specifier = ">=1.27.0" }, + { name = "opentelemetry-instrumentation", marker = "extra == 'otel'", specifier = ">=0.48b0" }, + { name = "opentelemetry-sdk", marker = "extra == 'otel'", specifier = ">=1.27.0" }, { name = "pillow", marker = "extra == 'perception'" }, { name = "pin", specifier = ">=3.3.0" }, { name = "piper-sdk", marker = "extra == 'manipulation'" }, @@ -2456,7 +2468,7 @@ requires-dist = [ { name = "xformers", marker = "platform_machine == 'x86_64' and extra == 'cuda'", specifier = ">=0.0.20" }, { name = "yapf", marker = "extra == 'misc'", specifier = "==0.40.2" }, ] -provides-extras = ["misc", "visualization", "agents", "web", "perception", "unitree", "unitree-dds", "manipulation", "cpu", "cuda", "psql", "sim", "navigation", "drone", "dds", "docker", "base", "apriltag", "all"] +provides-extras = ["misc", "visualization", "agents", "otel", "web", "perception", "unitree", "unitree-dds", "manipulation", "cpu", "cuda", "psql", "sim", "navigation", "drone", "dds", "docker", "base", "apriltag", "all"] [package.metadata.requires-dev] autofix = [{ name = "ruff", specifier = "==0.14.3" }] @@ -6759,6 +6771,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/90/b338326131ccb2aaa3c2c85d00f41822c0050139a4bfe723cfd95455bd2d/opencv_python_headless-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:77a82fe35ddcec0f62c15f2ba8a12ecc2ed4207c17b0902c7a3151ae29f37fb6", size = 40070414, upload-time = "2026-02-05T07:02:26.448Z" }, ] +[[package]] +name = "openinference-instrumentation" +version = "0.1.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openinference-semantic-conventions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bc/9126db2e991304d05e777c3cd107580cc1e721f2db91586dda09db7600f2/openinference_instrumentation-0.1.50.tar.gz", hash = "sha256:3ce2e19bc832cbfc5e141a1a78d6f5f9547b98ddfaecf8128edd2b5e6ed5c6e6", size = 24788, upload-time = "2026-05-11T22:04:18.14Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/0f/1eba26ef9599a69e08c60df0d9826a7530fa019ad4da66a9d6d9f1841ff1/openinference_instrumentation-0.1.50-py3-none-any.whl", hash = "sha256:e757e308df590f393d02b1cfcbd934d728cf876fca2f4432b2e7052ae9b64622", size = 31002, upload-time = "2026-05-11T22:04:17.12Z" }, +] + +[[package]] +name = "openinference-instrumentation-langchain" +version = "0.1.64" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openinference-instrumentation" }, + { name = "openinference-semantic-conventions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/d4/e0f56e25ced9ce1b98d12e3ae161f03657af709674e784dc5e876c3e9477/openinference_instrumentation_langchain-0.1.64.tar.gz", hash = "sha256:709183ab306293f262e30d059c7902ed57c5a2132f72c912c65b51a66ddb3a21", size = 75537, upload-time = "2026-05-10T03:58:11.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/8a/85c7d641c95cfa99b0131facdf891e8d1b2ce1946a39d8f662aaa88c1b90/openinference_instrumentation_langchain-0.1.64-py3-none-any.whl", hash = "sha256:649490698de0cf0815e93267c1afb81f5bd1a5ee9ed967bdafb0387e399fdf7e", size = 24748, upload-time = "2026-05-10T03:58:09.469Z" }, +] + +[[package]] +name = "openinference-semantic-conventions" +version = "0.1.29" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/6b/9ed67f9ce8c92436b297207abde730800b00bdec7e114f71b8dfe91cd26b/openinference_semantic_conventions-0.1.29.tar.gz", hash = "sha256:bbeb6472777a45a574169894bb9c4d80c6832a8befd32ab238cb875438ce1044", size = 12959, upload-time = "2026-04-22T00:39:27.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/7b/45ad1b95315b5563baa7338c8e8088bb1af66905c46e1bd1fe6ecbe30ea8/openinference_semantic_conventions-0.1.29-py3-none-any.whl", hash = "sha256:f45e0b1cf79fe407af4722bcf391a01565f0878c95be3ebcc9382245d0367cc5", size = 10582, upload-time = "2026-04-22T00:39:27.066Z" }, +] + [[package]] name = "opentelemetry-api" version = "1.39.1" @@ -6802,6 +6855,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/a3/cc9b66575bd6597b98b886a2067eea2693408d2d5f39dad9ab7fc264f5f3/opentelemetry_exporter_otlp_proto_grpc-1.39.1-py3-none-any.whl", hash = "sha256:fa1c136a05c7e9b4c09f739469cbdb927ea20b34088ab1d959a849b5cc589c18", size = 19766, upload-time = "2025-12-11T13:32:21.027Z" }, ] +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, +] + [[package]] name = "opentelemetry-proto" version = "1.39.1" @@ -9192,15 +9278,15 @@ wheels = [ [[package]] name = "reportlab" -version = "4.5.0" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "charset-normalizer" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/23/b8a8b9a5e596ce3de71237c8d6c6a976c763e930878b16340aff3d67ed53/reportlab-4.5.0.tar.gz", hash = "sha256:e595932789ab7a107ba253e83f7815622708a9fd49920d0d6a909880eb66ac75", size = 3914127, upload-time = "2026-04-29T09:12:26.785Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/3f/b3861b7e40c9d66f4a04e018958d681d16b948bfd1963c962d43a8c23f66/reportlab-4.5.1.tar.gz", hash = "sha256:9fdf68f4de9171ec66acb4a5feed8f8ca2af43479e707a6fbb0daa75d88e5494", size = 3939748, upload-time = "2026-05-12T10:14:13.663Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl", hash = "sha256:b8cc8996947d84e805368b47b2376070966f091d029351a0d8a1f238984c2c7f", size = 1957238, upload-time = "2026-04-29T09:12:22.904Z" }, + { url = "https://files.pythonhosted.org/packages/a7/45/ea7fad10122440de6e845568d106bffdc456ca0e8a1d8ae10b46016087e4/reportlab-4.5.1-py3-none-any.whl", hash = "sha256:06fce8cb56c83307cfa4909cdf4e6a2ddbb44e5d6ef4d2edca896d7e9769f091", size = 1957812, upload-time = "2026-05-12T10:14:10.622Z" }, ] [[package]]