microbootstrap 0__py3-none-any.whl

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 (34) hide show
  1. microbootstrap/__init__.py +44 -0
  2. microbootstrap/bootstrappers/__init__.py +0 -0
  3. microbootstrap/bootstrappers/base.py +114 -0
  4. microbootstrap/bootstrappers/fastapi.py +148 -0
  5. microbootstrap/bootstrappers/faststream.py +121 -0
  6. microbootstrap/bootstrappers/litestar.py +166 -0
  7. microbootstrap/config/__init__.py +0 -0
  8. microbootstrap/config/fastapi.py +64 -0
  9. microbootstrap/config/faststream.py +27 -0
  10. microbootstrap/config/litestar.py +21 -0
  11. microbootstrap/console_writer.py +34 -0
  12. microbootstrap/exceptions.py +10 -0
  13. microbootstrap/granian_server.py +41 -0
  14. microbootstrap/helpers.py +111 -0
  15. microbootstrap/instruments/__init__.py +0 -0
  16. microbootstrap/instruments/base.py +62 -0
  17. microbootstrap/instruments/cors_instrument.py +29 -0
  18. microbootstrap/instruments/health_checks_instrument.py +39 -0
  19. microbootstrap/instruments/instrument_box.py +50 -0
  20. microbootstrap/instruments/logging_instrument.py +202 -0
  21. microbootstrap/instruments/opentelemetry_instrument.py +203 -0
  22. microbootstrap/instruments/prometheus_instrument.py +63 -0
  23. microbootstrap/instruments/pyroscope_instrument.py +54 -0
  24. microbootstrap/instruments/sentry_instrument.py +124 -0
  25. microbootstrap/instruments/swagger_instrument.py +30 -0
  26. microbootstrap/instruments_setupper.py +72 -0
  27. microbootstrap/middlewares/__init__.py +0 -0
  28. microbootstrap/middlewares/fastapi.py +42 -0
  29. microbootstrap/middlewares/litestar.py +49 -0
  30. microbootstrap/py.typed +0 -0
  31. microbootstrap/settings.py +112 -0
  32. microbootstrap-0.dist-info/METADATA +920 -0
  33. microbootstrap-0.dist-info/RECORD +34 -0
  34. microbootstrap-0.dist-info/WHEEL +4 -0
@@ -0,0 +1,44 @@
1
+ from microbootstrap.instruments.cors_instrument import CorsConfig
2
+ from microbootstrap.instruments.health_checks_instrument import HealthChecksConfig
3
+ from microbootstrap.instruments.logging_instrument import LoggingConfig
4
+ from microbootstrap.instruments.opentelemetry_instrument import (
5
+ FastStreamOpentelemetryConfig,
6
+ FastStreamTelemetryMiddlewareProtocol,
7
+ OpentelemetryConfig,
8
+ )
9
+ from microbootstrap.instruments.prometheus_instrument import (
10
+ FastApiPrometheusConfig,
11
+ FastStreamPrometheusConfig,
12
+ FastStreamPrometheusMiddlewareProtocol,
13
+ LitestarPrometheusConfig,
14
+ )
15
+ from microbootstrap.instruments.pyroscope_instrument import PyroscopeConfig
16
+ from microbootstrap.instruments.sentry_instrument import SentryConfig
17
+ from microbootstrap.instruments.swagger_instrument import SwaggerConfig
18
+ from microbootstrap.settings import (
19
+ FastApiSettings,
20
+ FastStreamSettings,
21
+ InstrumentsSetupperSettings,
22
+ LitestarSettings,
23
+ )
24
+
25
+
26
+ __all__ = (
27
+ "CorsConfig",
28
+ "FastApiPrometheusConfig",
29
+ "FastApiSettings",
30
+ "FastStreamOpentelemetryConfig",
31
+ "FastStreamPrometheusConfig",
32
+ "FastStreamPrometheusMiddlewareProtocol",
33
+ "FastStreamSettings",
34
+ "FastStreamTelemetryMiddlewareProtocol",
35
+ "HealthChecksConfig",
36
+ "InstrumentsSetupperSettings",
37
+ "LitestarPrometheusConfig",
38
+ "LitestarSettings",
39
+ "LoggingConfig",
40
+ "OpentelemetryConfig",
41
+ "PyroscopeConfig",
42
+ "SentryConfig",
43
+ "SwaggerConfig",
44
+ )
File without changes
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+ import abc
3
+ import typing
4
+
5
+ from microbootstrap.console_writer import ConsoleWriter
6
+ from microbootstrap.helpers import dataclass_to_dict_no_defaults, merge_dataclasses_configs, merge_dict_configs
7
+ from microbootstrap.instruments.instrument_box import InstrumentBox
8
+ from microbootstrap.settings import SettingsT
9
+
10
+
11
+ if typing.TYPE_CHECKING:
12
+ import typing_extensions
13
+
14
+ from microbootstrap.instruments.base import Instrument, InstrumentConfigT
15
+
16
+
17
+ class DataclassInstance(typing.Protocol):
18
+ __dataclass_fields__: typing.ClassVar[dict[str, typing.Any]]
19
+
20
+
21
+ ApplicationT = typing.TypeVar("ApplicationT", bound=typing.Any)
22
+ DataclassT = typing.TypeVar("DataclassT", bound=DataclassInstance)
23
+
24
+
25
+ class ApplicationBootstrapper(abc.ABC, typing.Generic[SettingsT, ApplicationT, DataclassT]):
26
+ application_type: type[ApplicationT]
27
+ application_config: DataclassT
28
+ console_writer: ConsoleWriter
29
+ instrument_box: InstrumentBox
30
+
31
+ def __init__(self, settings: SettingsT) -> None:
32
+ self.settings = settings
33
+ self.console_writer = ConsoleWriter(writer_enabled=settings.service_debug)
34
+
35
+ if not hasattr(self, "instrument_box"):
36
+ self.instrument_box = InstrumentBox()
37
+ self.instrument_box.initialize(self.settings)
38
+
39
+ def configure_application(
40
+ self,
41
+ application_config: DataclassT,
42
+ ) -> typing_extensions.Self:
43
+ self.application_config = merge_dataclasses_configs(self.application_config, application_config)
44
+ return self
45
+
46
+ def configure_instrument(
47
+ self,
48
+ instrument_config: InstrumentConfigT,
49
+ ) -> typing_extensions.Self:
50
+ self.instrument_box.configure_instrument(instrument_config)
51
+ return self
52
+
53
+ def configure_instruments(
54
+ self,
55
+ *instrument_configs: InstrumentConfigT,
56
+ ) -> typing_extensions.Self:
57
+ for instrument_config in instrument_configs:
58
+ self.configure_instrument(instrument_config)
59
+ return self
60
+
61
+ @classmethod
62
+ def use_instrument(
63
+ cls,
64
+ ) -> typing.Callable[
65
+ [type[Instrument[InstrumentConfigT]]],
66
+ type[Instrument[InstrumentConfigT]],
67
+ ]:
68
+ if not hasattr(cls, "instrument_box"):
69
+ cls.instrument_box = InstrumentBox()
70
+ return cls.instrument_box.extend_instruments
71
+
72
+ def bootstrap(self) -> ApplicationT:
73
+ resulting_application_config: dict[str, typing.Any] = {}
74
+ for instrument in self.instrument_box.instruments:
75
+ if instrument.is_ready():
76
+ instrument.bootstrap()
77
+ resulting_application_config = merge_dict_configs(
78
+ resulting_application_config,
79
+ instrument.bootstrap_before(),
80
+ )
81
+ instrument.write_status(self.console_writer)
82
+
83
+ resulting_application_config = merge_dict_configs(
84
+ resulting_application_config,
85
+ dataclass_to_dict_no_defaults(self.application_config),
86
+ )
87
+ application = self.application_type(
88
+ **merge_dict_configs(resulting_application_config, self.bootstrap_before()),
89
+ )
90
+
91
+ self.bootstrap_before_instruments_after_app_created(application)
92
+
93
+ for instrument in self.instrument_box.instruments:
94
+ if instrument.is_ready():
95
+ application = instrument.bootstrap_after(application)
96
+
97
+ return self.bootstrap_after(application)
98
+
99
+ def bootstrap_before(self) -> dict[str, typing.Any]:
100
+ """Add some framework-related parameters to final bootstrap result before application creation."""
101
+ return {}
102
+
103
+ def bootstrap_before_instruments_after_app_created(self, application: ApplicationT) -> ApplicationT:
104
+ """Add some framework-related parameters to bootstrap result after application creation, but before instruments are applied.""" # noqa: E501
105
+ return application
106
+
107
+ def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
108
+ """Add some framework-related parameters to final bootstrap result after application creation."""
109
+ return application
110
+
111
+ def teardown(self) -> None:
112
+ for instrument in self.instrument_box.instruments:
113
+ if instrument.is_ready():
114
+ instrument.teardown()
@@ -0,0 +1,148 @@
1
+ import contextlib
2
+ import typing
3
+
4
+ import fastapi
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from fastapi_offline_docs import enable_offline_docs
7
+ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
8
+ from prometheus_fastapi_instrumentator import Instrumentator
9
+
10
+ from microbootstrap.bootstrappers.base import ApplicationBootstrapper
11
+ from microbootstrap.config.fastapi import FastApiConfig
12
+ from microbootstrap.instruments.cors_instrument import CorsInstrument
13
+ from microbootstrap.instruments.health_checks_instrument import HealthChecksInstrument, HealthCheckTypedDict
14
+ from microbootstrap.instruments.logging_instrument import LoggingInstrument
15
+ from microbootstrap.instruments.opentelemetry_instrument import OpentelemetryInstrument
16
+ from microbootstrap.instruments.prometheus_instrument import FastApiPrometheusConfig, PrometheusInstrument
17
+ from microbootstrap.instruments.pyroscope_instrument import PyroscopeInstrument
18
+ from microbootstrap.instruments.sentry_instrument import SentryInstrument
19
+ from microbootstrap.instruments.swagger_instrument import SwaggerInstrument
20
+ from microbootstrap.middlewares.fastapi import build_fastapi_logging_middleware
21
+ from microbootstrap.settings import FastApiSettings
22
+
23
+
24
+ ApplicationT = typing.TypeVar("ApplicationT", bound=fastapi.FastAPI)
25
+
26
+
27
+ class FastApiBootstrapper(
28
+ ApplicationBootstrapper[FastApiSettings, fastapi.FastAPI, FastApiConfig],
29
+ ):
30
+ application_config = FastApiConfig()
31
+ application_type = fastapi.FastAPI
32
+
33
+ @contextlib.asynccontextmanager
34
+ async def _lifespan_manager(self, _: fastapi.FastAPI) -> typing.AsyncIterator[None]:
35
+ try:
36
+ self.console_writer.print_bootstrap_table()
37
+ yield
38
+ finally:
39
+ self.teardown()
40
+
41
+ @contextlib.asynccontextmanager
42
+ async def _wrapped_lifespan_manager(self, app: fastapi.FastAPI) -> typing.AsyncIterator[None]:
43
+ assert self.application_config.lifespan # noqa: S101
44
+ async with self._lifespan_manager(app), self.application_config.lifespan(app):
45
+ yield None
46
+
47
+ def bootstrap_before(self) -> dict[str, typing.Any]:
48
+ return {
49
+ "debug": self.settings.service_debug,
50
+ "lifespan": self._wrapped_lifespan_manager if self.application_config.lifespan else self._lifespan_manager,
51
+ }
52
+
53
+
54
+ FastApiBootstrapper.use_instrument()(SentryInstrument)
55
+
56
+
57
+ @FastApiBootstrapper.use_instrument()
58
+ class FastApiSwaggerInstrument(SwaggerInstrument):
59
+ def bootstrap_before(self) -> dict[str, typing.Any]:
60
+ return {
61
+ "title": self.instrument_config.service_name,
62
+ "description": self.instrument_config.service_description,
63
+ "docs_url": self.instrument_config.swagger_path,
64
+ "version": self.instrument_config.service_version,
65
+ }
66
+
67
+ def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
68
+ if self.instrument_config.swagger_offline_docs:
69
+ enable_offline_docs(application, static_files_handler=self.instrument_config.service_static_path)
70
+ return application
71
+
72
+
73
+ @FastApiBootstrapper.use_instrument()
74
+ class FastApiCorsInstrument(CorsInstrument):
75
+ def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
76
+ application.add_middleware(
77
+ CORSMiddleware,
78
+ allow_origins=self.instrument_config.cors_allowed_origins,
79
+ allow_methods=self.instrument_config.cors_allowed_methods,
80
+ allow_headers=self.instrument_config.cors_allowed_headers,
81
+ allow_credentials=self.instrument_config.cors_allowed_credentials,
82
+ allow_origin_regex=self.instrument_config.cors_allowed_origin_regex,
83
+ expose_headers=self.instrument_config.cors_exposed_headers,
84
+ max_age=self.instrument_config.cors_max_age,
85
+ )
86
+ return application
87
+
88
+
89
+ FastApiBootstrapper.use_instrument()(PyroscopeInstrument)
90
+
91
+
92
+ @FastApiBootstrapper.use_instrument()
93
+ class FastApiOpentelemetryInstrument(OpentelemetryInstrument):
94
+ def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
95
+ FastAPIInstrumentor.instrument_app(
96
+ application,
97
+ tracer_provider=self.tracer_provider,
98
+ excluded_urls=",".join(self.define_exclude_urls()),
99
+ )
100
+ return application
101
+
102
+
103
+ @FastApiBootstrapper.use_instrument()
104
+ class FastApiLoggingInstrument(LoggingInstrument):
105
+ def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
106
+ if not self.instrument_config.logging_turn_off_middleware:
107
+ application.add_middleware(
108
+ build_fastapi_logging_middleware(self.instrument_config.logging_exclude_endpoints),
109
+ )
110
+ return application
111
+
112
+
113
+ @FastApiBootstrapper.use_instrument()
114
+ class FastApiPrometheusInstrument(PrometheusInstrument[FastApiPrometheusConfig]):
115
+ def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
116
+ Instrumentator(**self.instrument_config.prometheus_instrumentator_params).instrument(
117
+ application,
118
+ **self.instrument_config.prometheus_instrument_params,
119
+ ).expose(
120
+ application,
121
+ endpoint=self.instrument_config.prometheus_metrics_path,
122
+ include_in_schema=self.instrument_config.prometheus_metrics_include_in_schema,
123
+ **self.instrument_config.prometheus_expose_params,
124
+ )
125
+ return application
126
+
127
+ @classmethod
128
+ def get_config_type(cls) -> type[FastApiPrometheusConfig]:
129
+ return FastApiPrometheusConfig
130
+
131
+
132
+ @FastApiBootstrapper.use_instrument()
133
+ class FastApiHealthChecksInstrument(HealthChecksInstrument):
134
+ def build_fastapi_health_check_router(self) -> fastapi.APIRouter:
135
+ fastapi_router: typing.Final = fastapi.APIRouter(
136
+ tags=["probes"],
137
+ include_in_schema=self.instrument_config.health_checks_include_in_schema,
138
+ )
139
+
140
+ @fastapi_router.get(self.instrument_config.health_checks_path)
141
+ async def health_check_handler() -> HealthCheckTypedDict:
142
+ return self.render_health_check_data()
143
+
144
+ return fastapi_router
145
+
146
+ def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
147
+ application.include_router(self.build_fastapi_health_check_router())
148
+ return application
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+ import json
3
+ import typing
4
+
5
+ import prometheus_client
6
+ import structlog
7
+ import typing_extensions
8
+ from faststream.asgi import AsgiFastStream, AsgiResponse
9
+ from faststream.asgi import get as handle_get
10
+
11
+ from microbootstrap.bootstrappers.base import ApplicationBootstrapper
12
+ from microbootstrap.config.faststream import FastStreamConfig
13
+ from microbootstrap.instruments.health_checks_instrument import HealthChecksInstrument
14
+ from microbootstrap.instruments.logging_instrument import LoggingInstrument
15
+ from microbootstrap.instruments.opentelemetry_instrument import (
16
+ BaseOpentelemetryInstrument,
17
+ FastStreamOpentelemetryConfig,
18
+ )
19
+ from microbootstrap.instruments.prometheus_instrument import FastStreamPrometheusConfig, PrometheusInstrument
20
+ from microbootstrap.instruments.pyroscope_instrument import PyroscopeInstrument
21
+ from microbootstrap.instruments.sentry_instrument import SentryInstrument
22
+ from microbootstrap.settings import FastStreamSettings
23
+
24
+
25
+ class KwargsAsgiFastStream(AsgiFastStream):
26
+ def __init__(self, **kwargs: typing.Any) -> None: # noqa: ANN401
27
+ # `broker` argument is positional-only
28
+ super().__init__(kwargs.pop("broker", None), **kwargs)
29
+
30
+
31
+ class FastStreamBootstrapper(ApplicationBootstrapper[FastStreamSettings, AsgiFastStream, FastStreamConfig]):
32
+ application_config = FastStreamConfig()
33
+ application_type = KwargsAsgiFastStream
34
+
35
+ def bootstrap_before(self: typing_extensions.Self) -> dict[str, typing.Any]:
36
+ return {
37
+ "title": self.settings.service_name,
38
+ "version": self.settings.service_version,
39
+ "description": self.settings.service_description,
40
+ "on_shutdown": [self.teardown],
41
+ "on_startup": [self.console_writer.print_bootstrap_table],
42
+ "asyncapi_path": self.settings.asyncapi_path,
43
+ }
44
+
45
+
46
+ FastStreamBootstrapper.use_instrument()(SentryInstrument)
47
+ FastStreamBootstrapper.use_instrument()(PyroscopeInstrument)
48
+
49
+
50
+ @FastStreamBootstrapper.use_instrument()
51
+ class FastStreamOpentelemetryInstrument(BaseOpentelemetryInstrument[FastStreamOpentelemetryConfig]):
52
+ def is_ready(self) -> bool:
53
+ return bool(self.instrument_config.opentelemetry_middleware_cls and super().is_ready())
54
+
55
+ def bootstrap_after(self, application: AsgiFastStream) -> AsgiFastStream: # type: ignore[override]
56
+ if self.instrument_config.opentelemetry_middleware_cls and application.broker:
57
+ application.broker.add_middleware(
58
+ self.instrument_config.opentelemetry_middleware_cls(tracer_provider=self.tracer_provider)
59
+ )
60
+ return application
61
+
62
+ @classmethod
63
+ def get_config_type(cls) -> type[FastStreamOpentelemetryConfig]:
64
+ return FastStreamOpentelemetryConfig
65
+
66
+
67
+ @FastStreamBootstrapper.use_instrument()
68
+ class FastStreamLoggingInstrument(LoggingInstrument):
69
+ def bootstrap_before(self) -> dict[str, typing.Any]:
70
+ return {"logger": structlog.get_logger("microbootstrap-faststream")}
71
+
72
+
73
+ @FastStreamBootstrapper.use_instrument()
74
+ class FastStreamPrometheusInstrument(PrometheusInstrument[FastStreamPrometheusConfig]):
75
+ def is_ready(self) -> bool:
76
+ return bool(self.instrument_config.prometheus_middleware_cls and super().is_ready())
77
+
78
+ def bootstrap_before(self) -> dict[str, typing.Any]:
79
+ return {
80
+ "asgi_routes": (
81
+ (
82
+ self.instrument_config.prometheus_metrics_path,
83
+ prometheus_client.make_asgi_app(prometheus_client.REGISTRY),
84
+ ),
85
+ )
86
+ }
87
+
88
+ def bootstrap_after(self, application: AsgiFastStream) -> AsgiFastStream: # type: ignore[override]
89
+ if self.instrument_config.prometheus_middleware_cls and application.broker:
90
+ application.broker.add_middleware(
91
+ self.instrument_config.prometheus_middleware_cls(registry=prometheus_client.REGISTRY)
92
+ )
93
+ return application
94
+
95
+ @classmethod
96
+ def get_config_type(cls) -> type[FastStreamPrometheusConfig]:
97
+ return FastStreamPrometheusConfig
98
+
99
+
100
+ @FastStreamBootstrapper.use_instrument()
101
+ class FastStreamHealthChecksInstrument(HealthChecksInstrument):
102
+ def bootstrap(self) -> None: ...
103
+ def bootstrap_before(self) -> dict[str, typing.Any]:
104
+ @handle_get
105
+ async def check_health(scope: typing.Any) -> AsgiResponse: # noqa: ANN401, ARG001
106
+ return (
107
+ AsgiResponse(
108
+ json.dumps(self.render_health_check_data()).encode(), 200, headers={"content-type": "text/plain"}
109
+ )
110
+ if await self.define_health_status()
111
+ else AsgiResponse(b"Service is unhealthy", 500, headers={"content-type": "application/json"})
112
+ )
113
+
114
+ return {"asgi_routes": ((self.instrument_config.health_checks_path, check_health),)}
115
+
116
+ async def define_health_status(self) -> bool:
117
+ return await self.application.broker.ping(timeout=5) if self.application and self.application.broker else False
118
+
119
+ def bootstrap_after(self, application: AsgiFastStream) -> AsgiFastStream: # type: ignore[override]
120
+ self.application = application
121
+ return application
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+ import typing
3
+
4
+ import litestar
5
+ import litestar.exceptions
6
+ import litestar.types
7
+ import typing_extensions
8
+ from litestar import openapi
9
+ from litestar.config.cors import CORSConfig as LitestarCorsConfig
10
+ from litestar.contrib.opentelemetry.config import OpenTelemetryConfig as LitestarOpentelemetryConfig
11
+ from litestar.contrib.prometheus import PrometheusConfig, PrometheusController
12
+ from litestar.openapi.plugins import SwaggerRenderPlugin
13
+ from litestar_offline_docs import generate_static_files_config
14
+ from sentry_sdk.integrations.litestar import LitestarIntegration
15
+
16
+ from microbootstrap.bootstrappers.base import ApplicationBootstrapper
17
+ from microbootstrap.config.litestar import LitestarConfig
18
+ from microbootstrap.instruments.cors_instrument import CorsInstrument
19
+ from microbootstrap.instruments.health_checks_instrument import HealthChecksInstrument, HealthCheckTypedDict
20
+ from microbootstrap.instruments.logging_instrument import LoggingInstrument
21
+ from microbootstrap.instruments.opentelemetry_instrument import OpentelemetryInstrument
22
+ from microbootstrap.instruments.prometheus_instrument import LitestarPrometheusConfig, PrometheusInstrument
23
+ from microbootstrap.instruments.pyroscope_instrument import PyroscopeInstrument
24
+ from microbootstrap.instruments.sentry_instrument import SentryInstrument
25
+ from microbootstrap.instruments.swagger_instrument import SwaggerInstrument
26
+ from microbootstrap.middlewares.litestar import build_litestar_logging_middleware
27
+ from microbootstrap.settings import LitestarSettings
28
+
29
+
30
+ class LitestarBootstrapper(
31
+ ApplicationBootstrapper[LitestarSettings, litestar.Litestar, LitestarConfig],
32
+ ):
33
+ application_config = LitestarConfig()
34
+ application_type = litestar.Litestar
35
+
36
+ def bootstrap_before(self: typing_extensions.Self) -> dict[str, typing.Any]:
37
+ return {
38
+ "debug": self.settings.service_debug,
39
+ "on_shutdown": [self.teardown],
40
+ "on_startup": [self.console_writer.print_bootstrap_table],
41
+ }
42
+
43
+
44
+ @LitestarBootstrapper.use_instrument()
45
+ class LitestarSentryInstrument(SentryInstrument):
46
+ def bootstrap(self) -> None:
47
+ for sentry_integration in self.instrument_config.sentry_integrations:
48
+ if isinstance(sentry_integration, LitestarIntegration):
49
+ break
50
+ else:
51
+ self.instrument_config.sentry_integrations.append(LitestarIntegration())
52
+ super().bootstrap()
53
+
54
+
55
+ @LitestarBootstrapper.use_instrument()
56
+ class LitestarSwaggerInstrument(SwaggerInstrument):
57
+ def bootstrap_before(self) -> dict[str, typing.Any]:
58
+ render_plugins: typing.Final = (
59
+ (
60
+ SwaggerRenderPlugin(
61
+ js_url=f"{self.instrument_config.service_static_path}/swagger-ui-bundle.js",
62
+ css_url=f"{self.instrument_config.service_static_path}/swagger-ui.css",
63
+ standalone_preset_js_url=(
64
+ f"{self.instrument_config.service_static_path}/swagger-ui-standalone-preset.js"
65
+ ),
66
+ ),
67
+ )
68
+ if self.instrument_config.swagger_offline_docs
69
+ else (SwaggerRenderPlugin(),)
70
+ )
71
+
72
+ all_swagger_params: typing.Final = {
73
+ "path": self.instrument_config.swagger_path,
74
+ "title": self.instrument_config.service_name,
75
+ "version": self.instrument_config.service_version,
76
+ "description": self.instrument_config.service_description,
77
+ "render_plugins": render_plugins,
78
+ } | self.instrument_config.swagger_extra_params
79
+
80
+ bootstrap_result: typing.Final[dict[str, typing.Any]] = {
81
+ "openapi_config": openapi.OpenAPIConfig(**all_swagger_params),
82
+ }
83
+ if self.instrument_config.swagger_offline_docs:
84
+ bootstrap_result["static_files_config"] = [
85
+ generate_static_files_config(static_files_handler_path=self.instrument_config.service_static_path),
86
+ ]
87
+ return bootstrap_result
88
+
89
+
90
+ @LitestarBootstrapper.use_instrument()
91
+ class LitestarCorsInstrument(CorsInstrument):
92
+ def bootstrap_before(self) -> dict[str, typing.Any]:
93
+ return {
94
+ "cors_config": LitestarCorsConfig(
95
+ allow_origins=self.instrument_config.cors_allowed_origins,
96
+ allow_methods=self.instrument_config.cors_allowed_methods, # type: ignore[arg-type]
97
+ allow_headers=self.instrument_config.cors_allowed_headers,
98
+ allow_credentials=self.instrument_config.cors_allowed_credentials,
99
+ allow_origin_regex=self.instrument_config.cors_allowed_origin_regex,
100
+ expose_headers=self.instrument_config.cors_exposed_headers,
101
+ max_age=self.instrument_config.cors_max_age,
102
+ ),
103
+ }
104
+
105
+
106
+ LitestarBootstrapper.use_instrument()(PyroscopeInstrument)
107
+
108
+
109
+ @LitestarBootstrapper.use_instrument()
110
+ class LitestarOpentelemetryInstrument(OpentelemetryInstrument):
111
+ def bootstrap_before(self) -> dict[str, typing.Any]:
112
+ return {
113
+ "middleware": [
114
+ LitestarOpentelemetryConfig(
115
+ tracer_provider=self.tracer_provider,
116
+ exclude=self.define_exclude_urls(),
117
+ ).middleware,
118
+ ],
119
+ }
120
+
121
+
122
+ @LitestarBootstrapper.use_instrument()
123
+ class LitestarLoggingInstrument(LoggingInstrument):
124
+ def bootstrap_before(self) -> dict[str, typing.Any]:
125
+ if self.instrument_config.logging_turn_off_middleware:
126
+ return {}
127
+
128
+ return {"middleware": [build_litestar_logging_middleware(self.instrument_config.logging_exclude_endpoints)]}
129
+
130
+
131
+ @LitestarBootstrapper.use_instrument()
132
+ class LitestarPrometheusInstrument(PrometheusInstrument[LitestarPrometheusConfig]):
133
+ def bootstrap_before(self) -> dict[str, typing.Any]:
134
+ class LitestarPrometheusController(PrometheusController):
135
+ path = self.instrument_config.prometheus_metrics_path
136
+ include_in_schema = self.instrument_config.prometheus_metrics_include_in_schema
137
+ openmetrics_format = True
138
+
139
+ litestar_prometheus_config: typing.Final = PrometheusConfig(
140
+ app_name=self.instrument_config.service_name,
141
+ **self.instrument_config.prometheus_additional_params,
142
+ )
143
+
144
+ return {"route_handlers": [LitestarPrometheusController], "middleware": [litestar_prometheus_config.middleware]}
145
+
146
+ @classmethod
147
+ def get_config_type(cls) -> type[LitestarPrometheusConfig]:
148
+ return LitestarPrometheusConfig
149
+
150
+
151
+ @LitestarBootstrapper.use_instrument()
152
+ class LitestarHealthChecksInstrument(HealthChecksInstrument):
153
+ def build_litestar_health_check_router(self) -> litestar.Router:
154
+ @litestar.get(media_type=litestar.MediaType.JSON)
155
+ async def health_check_handler() -> HealthCheckTypedDict:
156
+ return self.render_health_check_data()
157
+
158
+ return litestar.Router(
159
+ path=self.instrument_config.health_checks_path,
160
+ route_handlers=[health_check_handler],
161
+ tags=["probes"],
162
+ include_in_schema=self.instrument_config.health_checks_include_in_schema,
163
+ )
164
+
165
+ def bootstrap_before(self) -> dict[str, typing.Any]:
166
+ return {"route_handlers": [self.build_litestar_health_check_router()]}
File without changes
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+ import dataclasses
3
+ import typing
4
+
5
+ from fastapi.datastructures import Default
6
+ from fastapi.utils import generate_unique_id
7
+ from starlette.responses import JSONResponse
8
+
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from fastapi import Request, routing
12
+ from fastapi.applications import AppType
13
+ from fastapi.middleware import Middleware
14
+ from fastapi.params import Depends
15
+ from starlette.responses import Response
16
+ from starlette.routing import BaseRoute
17
+ from starlette.types import Lifespan
18
+
19
+
20
+ @dataclasses.dataclass
21
+ class FastApiConfig:
22
+ debug: bool = False
23
+ routes: list[BaseRoute] | None = None
24
+ title: str = "FastAPI"
25
+ summary: str | None = None
26
+ description: str = ""
27
+ version: str = "0.1.0"
28
+ openapi_url: str | None = "/openapi.json"
29
+ openapi_tags: list[dict[str, typing.Any]] | None = None
30
+ servers: list[dict[str, str | typing.Any]] | None = None
31
+ dependencies: typing.Sequence[Depends] | None = None
32
+ default_response_class: type[Response] = dataclasses.field(default_factory=lambda: Default(JSONResponse))
33
+ redirect_slashes: bool = True
34
+ docs_url: str | None = "/docs"
35
+ redoc_url: str | None = "/redoc"
36
+ swagger_ui_oauth2_redirect_url: str | None = "/docs/oauth2-redirect"
37
+ swagger_ui_init_oauth: dict[str, typing.Any] | None = None
38
+ middleware: typing.Sequence[Middleware] | None = None
39
+ exception_handlers: (
40
+ dict[
41
+ int | type[Exception],
42
+ typing.Callable[[Request, typing.Any], typing.Coroutine[typing.Any, typing.Any, Response]],
43
+ ]
44
+ | None
45
+ ) = None
46
+ on_startup: typing.Sequence[typing.Callable[[], typing.Any]] | None = None
47
+ on_shutdown: typing.Sequence[typing.Callable[[], typing.Any]] | None = None
48
+ lifespan: Lifespan[AppType] | None = None # type: ignore[valid-type]
49
+ terms_of_service: str | None = None
50
+ contact: dict[str, str | typing.Any] | None = None
51
+ license_info: dict[str, str | typing.Any] | None = None
52
+ openapi_prefix: str = ""
53
+ root_path: str = ""
54
+ root_path_in_servers: bool = True
55
+ responses: dict[int | str, dict[str, typing.Any]] | None = None
56
+ callbacks: list[BaseRoute] | None = None
57
+ webhooks: routing.APIRouter | None = None
58
+ deprecated: bool | None = None
59
+ include_in_schema: bool = True
60
+ swagger_ui_parameters: dict[str, typing.Any] | None = None
61
+ generate_unique_id_function: typing.Callable[[routing.APIRoute], str] = dataclasses.field(
62
+ default_factory=lambda: Default(generate_unique_id),
63
+ )
64
+ separate_input_output_schemas: bool = True