Skip to content

Commit d215273

Browse files
authored
refactor: make base instruments generic over their config type (#286)
1 parent 176cbae commit d215273

10 files changed

Lines changed: 48 additions & 59 deletions

‎lite_bootstrap/bootstrappers/fastapi_bootstrapper.py‎

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,7 @@ async def send_wrapper(message: "Message") -> None:
155155

156156

157157
@dataclasses.dataclass(kw_only=True)
158-
class FastAPILoggingInstrument(LoggingInstrument):
159-
bootstrap_config: FastAPIConfig # ty: ignore[invalid-mutable-override]
160-
158+
class FastAPILoggingInstrument(LoggingInstrument[FastAPIConfig]):
161159
def bootstrap(self) -> None:
162160
super().bootstrap()
163161
if not self.bootstrap_config.fastapi_logging_middleware_enabled:
@@ -166,17 +164,13 @@ def bootstrap(self) -> None:
166164

167165

168166
@dataclasses.dataclass(kw_only=True, slots=True)
169-
class FastAPICorsInstrument(CorsInstrument):
170-
bootstrap_config: FastAPIConfig # ty: ignore[invalid-mutable-override]
171-
167+
class FastAPICorsInstrument(CorsInstrument[FastAPIConfig]):
172168
def bootstrap(self) -> None:
173169
self.bootstrap_config.app.add_middleware(CORSMiddleware, **self.cors_kwargs)
174170

175171

176172
@dataclasses.dataclass(kw_only=True, slots=True)
177-
class FastAPIHealthChecksInstrument(HealthChecksInstrument):
178-
bootstrap_config: FastAPIConfig # ty: ignore[invalid-mutable-override]
179-
173+
class FastAPIHealthChecksInstrument(HealthChecksInstrument[FastAPIConfig]):
180174
def build_fastapi_health_check_router(self) -> "fastapi.APIRouter":
181175
fastapi_router = fastapi.APIRouter(
182176
tags=["probes"],
@@ -194,9 +188,7 @@ def bootstrap(self) -> None:
194188

195189

196190
@dataclasses.dataclass(kw_only=True)
197-
class FastAPIOpenTelemetryInstrument(OpenTelemetryInstrument):
198-
bootstrap_config: FastAPIConfig # ty: ignore[invalid-mutable-override]
199-
191+
class FastAPIOpenTelemetryInstrument(OpenTelemetryInstrument[FastAPIConfig]):
200192
def bootstrap(self) -> None:
201193
super().bootstrap()
202194
FastAPIInstrumentor.instrument_app(
@@ -213,8 +205,7 @@ def teardown(self) -> None:
213205

214206

215207
@dataclasses.dataclass(kw_only=True)
216-
class FastAPIPrometheusInstrument(PrometheusInstrument):
217-
bootstrap_config: FastAPIConfig # ty: ignore[invalid-mutable-override]
208+
class FastAPIPrometheusInstrument(PrometheusInstrument[FastAPIConfig]):
218209
missing_dependency_message = "prometheus_fastapi_instrumentator is not installed"
219210

220211
@staticmethod
@@ -236,9 +227,7 @@ def bootstrap(self) -> None:
236227

237228

238229
@dataclasses.dataclass(kw_only=True)
239-
class FastAPISwaggerInstrument(SwaggerInstrument):
240-
bootstrap_config: FastAPIConfig # ty: ignore[invalid-mutable-override]
241-
230+
class FastAPISwaggerInstrument(SwaggerInstrument[FastAPIConfig]):
242231
def bootstrap(self) -> None:
243232
config = self.bootstrap_config
244233
application = config.app

‎lite_bootstrap/bootstrappers/fastmcp_bootstrapper.py‎

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,7 @@ class FastMcpConfig(
199199

200200

201201
@dataclasses.dataclass(kw_only=True)
202-
class FastMcpHealthChecksInstrument(HealthChecksInstrument):
203-
bootstrap_config: FastMcpConfig # ty: ignore[invalid-mutable-override]
204-
202+
class FastMcpHealthChecksInstrument(HealthChecksInstrument[FastMcpConfig]):
205203
def bootstrap(self) -> None:
206204
config = self.bootstrap_config
207205

@@ -216,8 +214,7 @@ async def health_check_handler(_: "Request") -> "JSONResponse":
216214

217215

218216
@dataclasses.dataclass(kw_only=True)
219-
class FastMcpOpenTelemetryInstrument(OpenTelemetryInstrument):
220-
bootstrap_config: FastMcpConfig # ty: ignore[invalid-mutable-override]
217+
class FastMcpOpenTelemetryInstrument(OpenTelemetryInstrument[FastMcpConfig]):
221218
missing_dependency_message = "opentelemetry-instrumentation-asgi is not installed"
222219
_restore_http_app: typing.Callable[[], None] | None = dataclasses.field(
223220
default=None, init=False, repr=False, compare=False
@@ -257,8 +254,7 @@ def teardown(self) -> None:
257254

258255

259256
@dataclasses.dataclass(kw_only=True)
260-
class FastMcpPrometheusInstrument(PrometheusInstrument):
261-
bootstrap_config: FastMcpConfig # ty: ignore[invalid-mutable-override]
257+
class FastMcpPrometheusInstrument(PrometheusInstrument[FastMcpConfig]):
262258
missing_dependency_message = "prometheus_client or prometheus_fastapi_instrumentator is not installed"
263259
_restore_http_app: typing.Callable[[], None] | None = dataclasses.field(
264260
default=None, init=False, repr=False, compare=False
@@ -319,9 +315,7 @@ def teardown(self) -> None:
319315

320316

321317
@dataclasses.dataclass(kw_only=True)
322-
class FastMcpLoggingInstrument(LoggingInstrument):
323-
bootstrap_config: FastMcpConfig # ty: ignore[invalid-mutable-override]
324-
318+
class FastMcpLoggingInstrument(LoggingInstrument[FastMcpConfig]):
325319
def bootstrap(self) -> None:
326320
super().bootstrap()
327321
if not self.bootstrap_config.fastmcp_logging_middleware_enabled:

‎lite_bootstrap/bootstrappers/faststream_bootstrapper.py‎

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ class FastStreamConfig(
8181

8282

8383
@dataclasses.dataclass(kw_only=True, slots=True)
84-
class FastStreamHealthChecksInstrument(HealthChecksInstrument):
85-
bootstrap_config: FastStreamConfig # ty: ignore[invalid-mutable-override]
86-
84+
class FastStreamHealthChecksInstrument(HealthChecksInstrument[FastStreamConfig]):
8785
def bootstrap(self) -> None:
8886
config = self.bootstrap_config
8987

@@ -113,8 +111,7 @@ async def _define_health_status(self) -> bool:
113111

114112

115113
@dataclasses.dataclass(kw_only=True)
116-
class FastStreamLoggingInstrument(LoggingInstrument):
117-
bootstrap_config: FastStreamConfig # ty: ignore[invalid-mutable-override]
114+
class FastStreamLoggingInstrument(LoggingInstrument[FastStreamConfig]):
118115
_prior_broker_params_storage: typing.Any = dataclasses.field(default=None, init=False, repr=False, compare=False)
119116
_broker_logger_replaced: bool = dataclasses.field(default=False, init=False, repr=False, compare=False)
120117

@@ -141,9 +138,7 @@ def teardown(self) -> None:
141138

142139

143140
@dataclasses.dataclass(kw_only=True)
144-
class FastStreamOpenTelemetryInstrument(OpenTelemetryInstrument):
145-
bootstrap_config: FastStreamConfig # ty: ignore[invalid-mutable-override]
146-
141+
class FastStreamOpenTelemetryInstrument(OpenTelemetryInstrument[FastStreamConfig]):
147142
def bootstrap(self) -> None:
148143
super().bootstrap()
149144
config = self.bootstrap_config
@@ -160,8 +155,7 @@ def _make_collector_registry() -> "prometheus_client.CollectorRegistry":
160155

161156

162157
@dataclasses.dataclass(kw_only=True)
163-
class FastStreamPrometheusInstrument(PrometheusInstrument):
164-
bootstrap_config: FastStreamConfig # ty: ignore[invalid-mutable-override]
158+
class FastStreamPrometheusInstrument(PrometheusInstrument[FastStreamConfig]):
165159
collector_registry: "prometheus_client.CollectorRegistry" = dataclasses.field(init=False)
166160
not_configured_reason = (
167161
PrometheusInstrument.not_configured_reason

‎lite_bootstrap/bootstrappers/litestar_bootstrapper.py‎

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,13 @@ def __post_init__(self) -> None:
139139

140140

141141
@dataclasses.dataclass(kw_only=True, slots=True)
142-
class LitestarCorsInstrument(CorsInstrument):
143-
bootstrap_config: LitestarConfig # ty: ignore[invalid-mutable-override]
144-
142+
class LitestarCorsInstrument(CorsInstrument[LitestarConfig]):
145143
def bootstrap(self) -> None:
146144
self.bootstrap_config.application_config.cors_config = CORSConfig(**self.cors_kwargs)
147145

148146

149147
@dataclasses.dataclass(kw_only=True, slots=True)
150-
class LitestarHealthChecksInstrument(HealthChecksInstrument):
151-
bootstrap_config: LitestarConfig # ty: ignore[invalid-mutable-override]
152-
148+
class LitestarHealthChecksInstrument(HealthChecksInstrument[LitestarConfig]):
153149
def build_litestar_health_check_router(self) -> "litestar.Router":
154150
@litestar.get(media_type=litestar.MediaType.JSON)
155151
async def health_check_handler() -> HealthCheckTypedDict:
@@ -167,9 +163,7 @@ def bootstrap(self) -> None:
167163

168164

169165
@dataclasses.dataclass(kw_only=True)
170-
class LitestarLoggingInstrument(LoggingInstrument):
171-
bootstrap_config: LitestarConfig # ty: ignore[invalid-mutable-override]
172-
166+
class LitestarLoggingInstrument(LoggingInstrument[LitestarConfig]):
173167
def _build_logging_middleware_excluded_paths(self) -> list[str]:
174168
"""Regex-escaped path prefixes for infrastructure routes not worth an access log line."""
175169
# Litestar matches exclude patterns with an unanchored search, so anchor each one to the
@@ -209,8 +203,7 @@ def _configure_structlog_loggers(self) -> None:
209203

210204

211205
@dataclasses.dataclass(kw_only=True)
212-
class LitestarOpenTelemetryInstrument(OpenTelemetryInstrument):
213-
bootstrap_config: LitestarConfig # ty: ignore[invalid-mutable-override]
206+
class LitestarOpenTelemetryInstrument(OpenTelemetryInstrument[LitestarConfig]):
214207
missing_dependency_message = "opentelemetry-instrumentation-asgi or litestar>=2.22 is not installed"
215208

216209
@staticmethod
@@ -251,8 +244,7 @@ def bootstrap(self) -> None:
251244

252245

253246
@dataclasses.dataclass(kw_only=True)
254-
class LitestarPrometheusInstrument(PrometheusInstrument):
255-
bootstrap_config: LitestarConfig # ty: ignore[invalid-mutable-override]
247+
class LitestarPrometheusInstrument(PrometheusInstrument[LitestarConfig]):
256248
missing_dependency_message = "prometheus_client is not installed"
257249

258250
@staticmethod
@@ -279,12 +271,11 @@ class LitestarPrometheusController(PrometheusController):
279271

280272

281273
@dataclasses.dataclass(kw_only=True)
282-
class LitestarSwaggerInstrument(SwaggerInstrument):
283-
bootstrap_config: LitestarConfig # ty: ignore[invalid-mutable-override]
274+
class LitestarSwaggerInstrument(SwaggerInstrument[LitestarConfig]):
284275
not_configured_reason = "swagger_path is empty or not valid"
285276

286277
@classmethod
287-
def is_configured(cls, bootstrap_config: "LitestarConfig") -> bool: # ty: ignore[invalid-method-override]
278+
def is_configured(cls, bootstrap_config: "LitestarConfig") -> bool:
288279
return bool(bootstrap_config.swagger_path) and is_valid_path(bootstrap_config.swagger_path)
289280

290281
def bootstrap(self) -> None:

‎lite_bootstrap/instruments/cors_instrument.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ def __post_init__(self) -> None:
3232
super().__post_init__()
3333

3434

35+
CorsConfigT = typing.TypeVar("CorsConfigT", bound=CorsConfig)
36+
37+
3538
@dataclasses.dataclass(kw_only=True, slots=True)
36-
class CorsInstrument(BaseInstrument[CorsConfig]):
39+
class CorsInstrument(BaseInstrument[CorsConfigT]):
3740
not_configured_reason = "cors_allowed_origins or cors_allowed_origin_regex must be provided"
3841

3942
@classmethod

‎lite_bootstrap/instruments/healthchecks_instrument.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import dataclasses
2+
import typing
23

34
import typing_extensions
45

@@ -26,8 +27,11 @@ def health_check_data(self) -> HealthCheckTypedDict:
2627
}
2728

2829

30+
HealthChecksConfigT = typing.TypeVar("HealthChecksConfigT", bound=HealthChecksConfig)
31+
32+
2933
@dataclasses.dataclass(kw_only=True, slots=True)
30-
class HealthChecksInstrument(BaseInstrument[HealthChecksConfig]):
34+
class HealthChecksInstrument(BaseInstrument[HealthChecksConfigT]):
3135
not_configured_reason = "health_checks_enabled is False"
3236

3337
@classmethod

‎lite_bootstrap/instruments/logging_instrument.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,11 @@ class LoggingConfig(BaseConfig):
7878
logging_enabled: bool = True
7979

8080

81+
LoggingConfigT = typing.TypeVar("LoggingConfigT", bound=LoggingConfig)
82+
83+
8184
@dataclasses.dataclass(kw_only=True, slots=True)
82-
class LoggingInstrument(BaseInstrument[LoggingConfig]):
85+
class LoggingInstrument(BaseInstrument[LoggingConfigT]):
8386
not_configured_reason = "logging_enabled is False"
8487
missing_dependency_message = "structlog is not installed"
8588
_logger_factory: "MemoryLoggerFactory | None" = dataclasses.field(

‎lite_bootstrap/instruments/opentelemetry_instrument.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,11 @@ def force_flush(self, timeout_millis: int = 30000) -> bool: # noqa: ARG002
145145
return True # pragma: no cover - matters only at the sdk 1.28 floor, whose base returns None
146146

147147

148+
OpenTelemetryConfigT = typing.TypeVar("OpenTelemetryConfigT", bound=OpenTelemetryConfig)
149+
150+
148151
@dataclasses.dataclass(kw_only=True, slots=True)
149-
class OpenTelemetryInstrument(BaseInstrument[OpenTelemetryConfig]):
152+
class OpenTelemetryInstrument(BaseInstrument[OpenTelemetryConfigT]):
150153
"""OpenTelemetry tracing instrument.
151154
152155
Lifecycle note: ``bootstrap()`` calls ``opentelemetry.trace.set_tracer_provider``,

‎lite_bootstrap/instruments/prometheus_instrument.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Prometheus config and readiness check; framework-specific bootstrap lives in the bootstrapper subclasses."""
22

33
import dataclasses
4+
import typing
45

56
from lite_bootstrap.helpers.path import is_valid_path
67
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument
@@ -12,8 +13,11 @@ class PrometheusConfig(BaseConfig):
1213
prometheus_metrics_include_in_schema: bool = False
1314

1415

16+
PrometheusConfigT = typing.TypeVar("PrometheusConfigT", bound=PrometheusConfig)
17+
18+
1519
@dataclasses.dataclass(kw_only=True, slots=True)
16-
class PrometheusInstrument(BaseInstrument[PrometheusConfig]):
20+
class PrometheusInstrument(BaseInstrument[PrometheusConfigT]):
1721
not_configured_reason = "prometheus_metrics_path is empty or not valid"
1822

1923
@classmethod

‎lite_bootstrap/instruments/swagger_instrument.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Swagger config and minimal base instrument; framework-specific behavior lives in the bootstrapper subclasses."""
22

33
import dataclasses
4+
import typing
45

56
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument
67

@@ -12,6 +13,9 @@ class SwaggerConfig(BaseConfig):
1213
swagger_offline_docs: bool = False
1314

1415

16+
SwaggerConfigT = typing.TypeVar("SwaggerConfigT", bound=SwaggerConfig)
17+
18+
1519
@dataclasses.dataclass(kw_only=True, slots=True)
16-
class SwaggerInstrument(BaseInstrument[SwaggerConfig]):
20+
class SwaggerInstrument(BaseInstrument[SwaggerConfigT]):
1721
pass

0 commit comments

Comments
 (0)