diff --git a/otdf-local/src/otdf_local/cli.py b/otdf-local/src/otdf_local/cli.py index d8e3597ff..da55b854a 100644 --- a/otdf-local/src/otdf_local/cli.py +++ b/otdf-local/src/otdf_local/cli.py @@ -94,14 +94,27 @@ def up( bool, typer.Option("--no-provision", help="Skip provisioning step"), ] = False, + tracing: Annotated[ + bool, + typer.Option( + "--tracing", + help="Start Jaeger and export OpenTelemetry traces from platform/KAS", + ), + ] = False, ) -> None: """Start the test environment. By default starts all services: docker (keycloak, postgres), platform, and all KAS instances. """ settings = get_settings() + # Enable tracing on the shared settings so every service (docker profile, + # platform/KAS config generation) picks it up. + settings.tracing = tracing settings.ensure_directories() + if tracing: + print_info(f"Tracing enabled — Jaeger UI at {settings.jaeger_ui_url}") + # Parse services to start if services: service_list = [s.strip().lower() for s in services.split(",")] @@ -591,6 +604,16 @@ def env( root_key = get_nested(platform_config, "services.kas.root_key") if root_key: env_vars["OT_ROOT_KEY"] = root_key + + # If the running platform is exporting traces, point clients (pytest, + # SDK CLIs) at the same collector so their spans join the trace. + if get_nested(platform_config, "server.trace.enabled"): + endpoint = get_nested( + platform_config, + "server.trace.provider.otlp.endpoint", + settings.otlp_endpoint, + ) + env_vars["OTEL_EXPORTER_OTLP_ENDPOINT"] = endpoint except Exception as e: print_warning(f"Could not read root key from platform config: {e}") diff --git a/otdf-local/src/otdf_local/config/settings.py b/otdf-local/src/otdf_local/config/settings.py index 96a4c20e8..61e66b33a 100644 --- a/otdf-local/src/otdf_local/config/settings.py +++ b/otdf-local/src/otdf_local/config/settings.py @@ -2,6 +2,7 @@ from functools import lru_cache from pathlib import Path +from typing import Any from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict @@ -146,6 +147,25 @@ def docker_compose_file(self) -> Path: # Log level log_level: str = "info" + # OpenTelemetry tracing (opt-in via `otdf-local up --tracing`) + tracing: bool = False + otlp_endpoint: str = "localhost:4317" + jaeger_ui_url: str = "http://localhost:16686" + + def trace_config_updates(self) -> dict[str, Any]: + """Platform/KAS config overrides that export OTel traces to the local + collector (Jaeger). Empty when tracing is disabled so the generated + config is unchanged for normal runs. + """ + if not self.tracing: + return {} + return { + "server.trace.enabled": True, + "server.trace.provider.name": "otlp", + "server.trace.provider.otlp.endpoint": self.otlp_endpoint, + "server.trace.provider.otlp.insecure": True, + } + def get_kas_port(self, name: str) -> int: """Get port for a KAS instance.""" return Ports.get_kas_port(name) diff --git a/otdf-local/src/otdf_local/services/docker.py b/otdf-local/src/otdf_local/services/docker.py index 911b42e3c..4edc25105 100644 --- a/otdf-local/src/otdf_local/services/docker.py +++ b/otdf-local/src/otdf_local/services/docker.py @@ -32,13 +32,22 @@ def service_type(self) -> ServiceType: def health_url(self) -> str: return f"http://localhost:{Ports.KEYCLOAK}/auth/realms/master" + def _compose_cmd(self, *args: str) -> list[str]: + """Build a `docker compose` command, opting into the `tracing` profile + (which includes the Jaeger all-in-one container) when tracing is enabled. + """ + cmd = ["docker", "compose", "-f", str(self._compose_file)] + if self.settings.tracing: + cmd += ["--profile", "tracing"] + return cmd + list(args) + def start(self) -> bool: """Start Docker compose services.""" if not self._compose_file.exists(): return False result = subprocess.run( - ["docker", "compose", "-f", str(self._compose_file), "up", "-d"], + self._compose_cmd("up", "-d"), capture_output=True, text=True, cwd=self._compose_file.parent, @@ -51,7 +60,7 @@ def stop(self) -> bool: return False result = subprocess.run( - ["docker", "compose", "-f", str(self._compose_file), "down"], + self._compose_cmd("down"), capture_output=True, text=True, cwd=self._compose_file.parent, diff --git a/otdf-local/src/otdf_local/services/kas.py b/otdf-local/src/otdf_local/services/kas.py index 00de6a2cd..4ebcc8156 100644 --- a/otdf-local/src/otdf_local/services/kas.py +++ b/otdf-local/src/otdf_local/services/kas.py @@ -81,6 +81,9 @@ def _generate_config(self) -> Path: # registered_kas_uri should NOT have /kas suffix updates["services.kas.registered_kas_uri"] = f"http://localhost:{self.port}" + # Export traces to the local collector (Jaeger) when tracing is enabled + updates.update(self.settings.trace_config_updates()) + copy_yaml_with_updates(template_path, config_path, updates) return config_path diff --git a/otdf-local/src/otdf_local/services/platform.py b/otdf-local/src/otdf_local/services/platform.py index 15f7f4e5e..724951d4e 100644 --- a/otdf-local/src/otdf_local/services/platform.py +++ b/otdf-local/src/otdf_local/services/platform.py @@ -67,6 +67,9 @@ def _generate_config(self) -> Path: "logger.output": logger_output, } + # Export traces to the local collector (Jaeger) when tracing is enabled + updates.update(self.settings.trace_config_updates()) + copy_yaml_with_updates(template_path, config_path, updates) # Set up golden keys for legacy TDF tests diff --git a/xtest/conftest.py b/xtest/conftest.py index 4f39176be..00ab7f662 100644 --- a/xtest/conftest.py +++ b/xtest/conftest.py @@ -52,6 +52,7 @@ def pytest_report_header() -> list[str]: "fixtures.keys", "fixtures.audit", "fixtures.encryption", + "fixtures.tracing", ] @@ -129,6 +130,12 @@ def pytest_addoption(parser: pytest.Parser): action="store_true", help="skip round-trip tests where all SDKs are released artifacts", ) + parser.addoption( + "--tracing", + action="store_true", + help="wrap each test in an OpenTelemetry span and export it to the OTLP " + "collector (also enabled by setting OTEL_EXPORTER_OTLP_ENDPOINT)", + ) parser.addoption( "--sdks", help=f"select which sdks to run by default, unless overridden; one or more of {englist(typing.get_args(tdfs.sdk_type))}, optionally version-qualified (e.g. go@main, go@v0.18.0, go@*)", diff --git a/xtest/fixtures/tracing.py b/xtest/fixtures/tracing.py new file mode 100644 index 000000000..2a7f32117 --- /dev/null +++ b/xtest/fixtures/tracing.py @@ -0,0 +1,142 @@ +"""Pytest fixtures for end-to-end OpenTelemetry tracing. + +Wraps each test in a ``pytest.test`` span and exports a ``TRACEPARENT`` into the +environment so the SDK CLI subprocess (and, through it, platform/KAS) join the +same trace. On failure the trace's Jaeger URL is printed so the failure links +directly to the full request chain. + +Tracing is opt-in and a strict no-op unless enabled — it activates when either +``--tracing`` is passed or ``OTEL_EXPORTER_OTLP_ENDPOINT`` is set. When disabled +nothing is imported or initialized, so normal runs pay no cost. + +See ``fixtures/audit.py`` for the sibling log-collection fixture this mirrors. +""" + +import logging +import os +from collections.abc import Iterator +from dataclasses import dataclass + +import pytest + +logger = logging.getLogger("xtest") + +# Default local collector (Jaeger all-in-one, OTLP gRPC) and UI, matching the +# `tracing` docker-compose profile started by `otdf-local up --tracing`. +_DEFAULT_OTLP_ENDPOINT = "localhost:4317" +_DEFAULT_JAEGER_UI = "http://localhost:16686" + + +@dataclass +class TracingSession: + """Holds the initialized tracer and the Jaeger UI base URL for a session.""" + + tracer: object # opentelemetry.trace.Tracer + provider: object # opentelemetry.sdk.trace.TracerProvider + jaeger_ui_url: str + + +@pytest.fixture(scope="session") +def _tracing(request: pytest.FixtureRequest) -> Iterator[TracingSession | None]: + """Initialize an OTLP tracer for the session, or yield None when disabled. + + Enabled when ``--tracing`` is passed or ``OTEL_EXPORTER_OTLP_ENDPOINT`` is + set; otherwise this is a no-op and no OpenTelemetry code runs. + """ + endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT") + enabled = bool(request.config.getoption("--tracing", default=False)) or bool( + endpoint + ) + if not enabled: + yield None + return + + endpoint = endpoint or _DEFAULT_OTLP_ENDPOINT + + # Imported lazily so the disabled path never requires the OTel packages. + from opentelemetry import trace + from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter + from opentelemetry.sdk.resources import Resource + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + + provider = TracerProvider(resource=Resource.create({"service.name": "xtest"})) + provider.add_span_processor( + BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint, insecure=True)) + ) + trace.set_tracer_provider(provider) + logger.info("xtest tracing enabled, exporting to %s", endpoint) + + session = TracingSession( + tracer=provider.get_tracer("xtest"), + provider=provider, + jaeger_ui_url=os.getenv("JAEGER_UI_URL", _DEFAULT_JAEGER_UI), + ) + try: + yield session + finally: + # Flush any buffered spans before the process exits. + provider.shutdown() + + +def _test_span_attributes(request: pytest.FixtureRequest) -> dict[str, str]: + """Derive span attributes (sdk, container) from the test's parametrization.""" + attrs: dict[str, str] = {"test.name": request.node.name} + callspec = getattr(request.node, "callspec", None) + if callspec is None: + return attrs + params = callspec.params + for key in ("encrypt_sdk", "decrypt_sdk", "sdk"): + if key in params: + attrs["test.sdk"] = str(params[key]) + break + if "container" in params: + attrs["test.container"] = str(params["container"]) + return attrs + + +@pytest.fixture(autouse=True) +def _trace_test( + request: pytest.FixtureRequest, _tracing: TracingSession | None +) -> Iterator[None]: + """Wrap each test in a ``pytest.test`` span and propagate it to subprocesses. + + Sets ``TRACEPARENT`` in the environment for the duration of the test so the + SDK CLI (invoked via ``subprocess.run`` in ``tdfs.py``, which copies + ``os.environ``) starts a child span under this one. Prints the Jaeger trace + URL on failure. + """ + if _tracing is None: + yield + return + + from opentelemetry import trace + from opentelemetry.propagate import inject + + tracer = _tracing.tracer + with tracer.start_as_current_span("pytest.test") as span: # type: ignore[attr-defined] + for key, value in _test_span_attributes(request).items(): + span.set_attribute(key, value) + + # Export the active context so child processes continue this trace. + carrier: dict[str, str] = {} + inject(carrier) + prev_traceparent = os.environ.get("TRACEPARENT") + if "traceparent" in carrier: + os.environ["TRACEPARENT"] = carrier["traceparent"] + + trace_id = format(span.get_span_context().trace_id, "032x") + trace_url = f"{_tracing.jaeger_ui_url}/trace/{trace_id}" + try: + yield + finally: + # Restore prior TRACEPARENT to avoid leaking across tests. + if prev_traceparent is None: + os.environ.pop("TRACEPARENT", None) + else: + os.environ["TRACEPARENT"] = prev_traceparent + + rep = getattr(request.node, "rep_call", None) + if rep is not None and rep.failed: + span.set_status(trace.Status(trace.StatusCode.ERROR)) + print(f"\nTrace: {trace_url}") diff --git a/xtest/pyproject.toml b/xtest/pyproject.toml index 7141bbb6e..7ab88eff0 100644 --- a/xtest/pyproject.toml +++ b/xtest/pyproject.toml @@ -30,6 +30,9 @@ dependencies = [ "jsonschema>=4.25.1", "jsonschema-specifications>=2025.9.1", "MarkupSafe>=3.0.3", + "opentelemetry-api>=1.29.0", + "opentelemetry-sdk>=1.29.0", + "opentelemetry-exporter-otlp-proto-grpc>=1.29.0", "packaging>=26.2", "pluggy>=1.6.0", "pycparser>=3.0", diff --git a/xtest/uv.lock b/xtest/uv.lock index 7deb030b5..28902efe4 100644 --- a/xtest/uv.lock +++ b/xtest/uv.lock @@ -220,6 +220,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl", hash = "sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9", size = 212507, upload-time = "2026-05-06T04:01:23.799Z" }, ] +[[package]] +name = "googleapis-common-protos" +version = "1.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, +] + +[[package]] +name = "grpcio" +version = "1.81.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/b5/1ff353970a87eda4c98251e34d2dfd214abd4982dc89119c9252a2a482d2/grpcio-1.81.1.tar.gz", hash = "sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b", size = 13026582, upload-time = "2026-06-11T12:46:51.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/58/19414622b1bf6981bc9c05a365bd548e71876c89000083b3af489251e9c0/grpcio-1.81.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:506f48f2f9c29b143fca3dad7b0d518c188b6c9648c75a2ae6e2d9f2c13a060b", size = 6055336, upload-time = "2026-06-11T12:46:20.557Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/2ec88adb92b0eba970dd0e0e7dd086341daa3c75eba4f735f9e44bf684b0/grpcio-1.81.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d865db4a6318e1c1bea83292e0ed231090538fc4ca45425b0f0480eb338bbc6e", size = 12056279, upload-time = "2026-06-11T12:46:24.255Z" }, + { url = "https://files.pythonhosted.org/packages/41/36/e8c5f8c6ec71de73733695ebc809e98b178b534ec6d8eaa31a7ebab4ad4c/grpcio-1.81.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2aa72e3ce1770317ef534f63d397b55e130725f5149bd36077c3b539019db27", size = 6608225, upload-time = "2026-06-11T12:46:27.601Z" }, + { url = "https://files.pythonhosted.org/packages/30/22/96fc577a845ab093326d9ab1adb874bd4936c8cf98ac8ed2f3db13a0a2fb/grpcio-1.81.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0490c30c261eded63f3f354979f9dc4502a9fb944cccb60cd9dc85f5a7349854", size = 7306576, upload-time = "2026-06-11T12:46:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/76/7b/61dab5d5969f28d97fb1009cead1df0a5cd987d3315e1b37f18a4449f8bc/grpcio-1.81.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:410482da976329fe5f4067270401b12cf2bd552ff8020f054ecfaddb5475f9d6", size = 6812165, upload-time = "2026-06-11T12:46:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/6e501929d4f5f96462fd82fd9f0f06e5f9612207582b862868d68757b27d/grpcio-1.81.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3657301562ac3cb8018d30d0d3ebfa39932239f7b5703422057ef14b69949f5", size = 7422962, upload-time = "2026-06-11T12:46:36.511Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7e/f2157589e66daa78ebb3165942d05a08bdea93b9d11c2bc1e172aef89685/grpcio-1.81.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24c8e57504c8f45b237e40b99262d181071e5099a07053695b75d97bb53053a0", size = 8408176, upload-time = "2026-06-11T12:46:39.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/df/c6717fef716e00d235ffb96123baf6dce76d6004f6233fa767c502861460/grpcio-1.81.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b427c19380991a4eaab2f6144b64b99b412043314c6bf4ab544f97bb31ee4190", size = 7846681, upload-time = "2026-06-11T12:46:43.013Z" }, + { url = "https://files.pythonhosted.org/packages/36/84/3502e9f210a6a5c4438c8aca3f88edd2e04f6a27f3d41b26cf0a0024b096/grpcio-1.81.1-cp314-cp314-win32.whl", hash = "sha256:61233fe8951e5c85dff81c2458b6528624760166946b5b47ea150a589168411f", size = 4264615, upload-time = "2026-06-11T12:46:45.741Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/4af731ff7492c68a96e4c71bfd0f4590acde92b31c6fe4894e6465c10ff6/grpcio-1.81.1-cp314-cp314-win_amd64.whl", hash = "sha256:3768a5ff1b2125e6f552e561b6b2dca0e64982d8949689b4df145cf8b98d7821", size = 5070275, upload-time = "2026-06-11T12:46:48.486Z" }, +] + [[package]] name = "idna" version = "3.18" @@ -316,6 +349,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/cc/e4c9584181f86494df0f6bdec1a4f3280c50db44704dc2a407e994fc87bb/opentelemetry_api-1.43.0.tar.gz", hash = "sha256:107d0d03857ea8fc7c5fcbbbd83f800c281f0d560553d61c1d675fccfd1761c1", size = 73476, upload-time = "2026-06-24T15:19:55.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/83/6dba32b85f31868400440dc7ad2ca1eab94cbbf3a7b0459ed39f8311a9e2/opentelemetry_api-1.43.0-py3-none-any.whl", hash = "sha256:20acf45e9b21851926835292e4045d290acade1edd2ff3de86d2f069687ba1fd", size = 61912, upload-time = "2026-06-24T15:19:35.434Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/c1/e8098490ab15abf116dcaf9fa89ededcb35547c7d08d4b5a62f573dc1e63/opentelemetry_exporter_otlp_proto_common-1.43.0.tar.gz", hash = "sha256:c4e32ba6d6b13bdb2b8f6764c4fd28d00192826561aa04f6d14eedfce7ac076f", size = 20197, upload-time = "2026-06-24T15:20:00.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/b2/41ebc74ae1d5859901f1b69305de58724bf043381103d6ef413521cbc35a/opentelemetry_exporter_otlp_proto_common-1.43.0-py3-none-any.whl", hash = "sha256:123c3f9cc87218562490c63b36f497bf3a722faf174a515d1443f31ababa6264", size = 17048, upload-time = "2026-06-24T15:19:41.264Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/1d/6336453716ca0a240d4417d19e6d5b77a5e7163e5670ec4f7ec4d3ede7bf/opentelemetry_exporter_otlp_proto_grpc-1.43.0.tar.gz", hash = "sha256:1b3e0627daa9bc21884d4a13946807c255eb558bfe5bdd543dffb6f4c9faee0d", size = 27213, upload-time = "2026-06-24T15:20:00.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/74/2700b5d5c946bf2dba87073fce3dfc198c46bc92ea3d5693f54bc51c90b1/opentelemetry_exporter_otlp_proto_grpc-1.43.0-py3-none-any.whl", hash = "sha256:6a10d1feacffffda19acacbf277b736094b1e2f4dbb98c90ccb2c6e1962e2ec6", size = 19626, upload-time = "2026-06-24T15:19:42.233Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/b9/d357faefb40bda1d4799913e6af611171ff22a2dedcb93576bc92242d056/opentelemetry_proto-1.43.0.tar.gz", hash = "sha256:224778df17e1f3fafeaaa21d874236ca5f6ffc2f86e0899298ec7351aac27924", size = 46481, upload-time = "2026-06-24T15:20:07.625Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/a7/3e5308cf548b8f72529c7db1afdb3a404211982376a12927fd7759f77bf3/opentelemetry_proto-1.43.0-py3-none-any.whl", hash = "sha256:c58f1f7ef84bc7dc2834016c0c37fe0081dde7ca9f6339be1970fbf9cdaaa90d", size = 72489, upload-time = "2026-06-24T15:19:51.164Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/eb/5041074274ac0956b03637cc039d434569112468e875eddfcc9a0674ce06/opentelemetry_sdk-1.43.0.tar.gz", hash = "sha256:d8187c81c162df9913e4003dd6485f7390d9a24fc17026ec7387b8b8218b08e9", size = 254744, upload-time = "2026-06-24T15:20:08.467Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e3/b17be23af124201c9f52eececd4cc8ddfed1597d37b4ee771895d325805c/opentelemetry_sdk-1.43.0-py3-none-any.whl", hash = "sha256:d1323a547c1ce69d6a069a17a44b7da82bb8b332051ecb074041f87642c86823", size = 178852, upload-time = "2026-06-24T15:19:52.169Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.64b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/30/5f26df29509eccd86b99b481ac9ffa39da49ba9577cc69071c552ae30447/opentelemetry_semantic_conventions-0.64b0.tar.gz", hash = "sha256:72f76fb2d1582d9d033dd1fcd84532e961e6ff3d90d24ba6fabc72975a83864c", size = 148340, upload-time = "2026-06-24T15:20:09.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/ca/23ba87a221b574a7c5a99d48849d80bfe8b047624681357e2b002e566187/opentelemetry_semantic_conventions-0.64b0-py3-none-any.whl", hash = "sha256:ea77e85e354b8f604ddbe5f3d9135216f982fa4d77e5859ac30f6d8a50505aa6", size = 203713, upload-time = "2026-06-24T15:19:53.339Z" }, +] + [[package]] name = "packaging" version = "26.2" @@ -334,6 +448,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + [[package]] name = "pycparser" version = "3.0" @@ -655,6 +784,9 @@ dependencies = [ { name = "jsonschema" }, { name = "jsonschema-specifications" }, { name = "markupsafe" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "opentelemetry-sdk" }, { name = "packaging" }, { name = "pluggy" }, { name = "pycparser" }, @@ -697,6 +829,9 @@ requires-dist = [ { name = "jsonschema", specifier = ">=4.25.1" }, { name = "jsonschema-specifications", specifier = ">=2025.9.1" }, { name = "markupsafe", specifier = ">=3.0.3" }, + { name = "opentelemetry-api", specifier = ">=1.29.0" }, + { name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.29.0" }, + { name = "opentelemetry-sdk", specifier = ">=1.29.0" }, { name = "packaging", specifier = ">=26.2" }, { name = "pluggy", specifier = ">=1.6.0" }, { name = "pycparser", specifier = ">=3.0" },