From 2f28bb6bc2d360ce8190743bae558305de9e5b28 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Mon, 1 Jun 2026 00:42:03 +0300 Subject: [PATCH] refactor: extract OpenTelemetryServiceFieldsConfig mixin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit opentelemetry_service_name and opentelemetry_namespace were declared identically on both OpentelemetryConfig and PyroscopeConfig. In the four framework configs (Free, FastAPI, Litestar, FastStream) that inherit from both parents, Python's MRO happened to pick one declaration; the fact that defaults matched is what kept behavior consistent. Without the mixin, drifting defaults on one side would silently misbehave on the framework configs. Extract OpenTelemetryServiceFieldsConfig(BaseConfig) — a tiny mixin declaring just those two fields. Both OpentelemetryConfig and PyroscopeConfig now inherit from it (no longer from BaseConfig directly). The duplicate declarations are removed. PyroscopeConfig's standalone use case (without OpentelemetryConfig in the MRO) is preserved — exercised by test_pyroscope_standalone_config_accepts_otel_fields. Closes DES-2 from the audit. --- lite_bootstrap/instruments/opentelemetry_instrument.py | 8 ++++++-- lite_bootstrap/instruments/pyroscope_instrument.py | 7 +++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lite_bootstrap/instruments/opentelemetry_instrument.py b/lite_bootstrap/instruments/opentelemetry_instrument.py index bf80150..88d0333 100644 --- a/lite_bootstrap/instruments/opentelemetry_instrument.py +++ b/lite_bootstrap/instruments/opentelemetry_instrument.py @@ -33,13 +33,17 @@ class InstrumentorWithParams: @dataclasses.dataclass(kw_only=True, frozen=True) -class OpentelemetryConfig(BaseConfig): +class OpenTelemetryServiceFieldsConfig(BaseConfig): opentelemetry_service_name: str | None = None + opentelemetry_namespace: str | None = None + + +@dataclasses.dataclass(kw_only=True, frozen=True) +class OpentelemetryConfig(OpenTelemetryServiceFieldsConfig): opentelemetry_container_name: str | None = dataclasses.field( default_factory=lambda: os.environ.get("HOSTNAME") or None ) opentelemetry_endpoint: str | None = None - opentelemetry_namespace: str | None = None opentelemetry_insecure: bool = True opentelemetry_instrumentors: list[typing.Union[InstrumentorWithParams, "BaseInstrumentor"]] = dataclasses.field( default_factory=list diff --git a/lite_bootstrap/instruments/pyroscope_instrument.py b/lite_bootstrap/instruments/pyroscope_instrument.py index a5f293a..4670390 100644 --- a/lite_bootstrap/instruments/pyroscope_instrument.py +++ b/lite_bootstrap/instruments/pyroscope_instrument.py @@ -2,7 +2,8 @@ import typing from lite_bootstrap import import_checker -from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument +from lite_bootstrap.instruments.base import BaseInstrument +from lite_bootstrap.instruments.opentelemetry_instrument import OpenTelemetryServiceFieldsConfig if import_checker.is_pyroscope_installed: @@ -10,13 +11,11 @@ @dataclasses.dataclass(kw_only=True, frozen=True) -class PyroscopeConfig(BaseConfig): +class PyroscopeConfig(OpenTelemetryServiceFieldsConfig): pyroscope_endpoint: str | None = None pyroscope_sample_rate: int = 100 pyroscope_tags: dict[str, str] = dataclasses.field(default_factory=dict) pyroscope_additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict) - opentelemetry_service_name: str | None = None - opentelemetry_namespace: str | None = None @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)