Skip to content

Commit c9a979d

Browse files
authored
Merge pull request #18 from community-of-python/feature/fastapi-bootstrapper
New bootstrapper for FastAPI
2 parents b5b617b + 493a44d commit c9a979d

23 files changed

+1176
-566
lines changed

README.md

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ from your_application.settings import settings
3333
application: litestar.Litestar = LitestarBootstrapper(settings).bootstrap()
3434
```
3535

36-
Currently, only `litestar` is supported.
3736
With <b>microbootstrap</b>, you receive an application with lightweight built-in support for:
3837

3938
- `sentry`
@@ -43,6 +42,11 @@ With <b>microbootstrap</b>, you receive an application with lightweight built-in
4342
- `cors`
4443
- `swagger` - with additional offline version support
4544

45+
Those instruments can be bootstrapped for:
46+
47+
- `fastapi`
48+
- `litestar`
49+
4650
Interested? Let's dive right in ⚡
4751

4852
## Table of Contents
@@ -65,18 +69,22 @@ Interested? Let's dive right in ⚡
6569

6670
## Installation
6771

68-
You can install the package using either `pip` or `poetry`.
72+
You can install the package using either `pip` or `poetry`.
73+
Also, you can specify extras during installation for concrete framework:
74+
75+
- `fastapi`
76+
- `litestar`
6977

7078
For poetry:
7179

7280
```bash
73-
$ poetry add microbootstrap -E litestar
81+
$ poetry add microbootstrap -E fastapi
7482
```
7583

7684
For pip:
7785

7886
```bash
79-
$ pip install microbootstrap[litestar]
87+
$ pip install microbootstrap[fastapi]
8088
```
8189

8290
## Quickstart
@@ -122,16 +130,16 @@ This approach will provide you with an application that has all the essential in
122130

123131
The settings object is the core of microbootstrap.
124132

125-
All framework-related settings inherit from the `BaseBootstrapSettings` object. `BaseBootstrapSettings` defines parameters for the service and various instruments.
133+
All framework-related settings inherit from the `BaseServiceSettings` object. `BaseServiceSettings` defines parameters for the service and various instruments.
126134

127-
However, the number of parameters is <b>not confined</b> to those defined in `BaseBootstrapSettings`. You can add as many as you need.
135+
However, the number of parameters is <b>not confined</b> to those defined in `BaseServiceSettings`. You can add as many as you need.
128136

129137
These parameters can be sourced from your environment. By default, no prefix is added to these parameters.
130138

131139
Example:
132140

133141
```python
134-
class YourSettings(BaseBootstrapSettings):
142+
class YourSettings(BaseServiceSettings):
135143
service_debug: bool = True
136144
service_name: str = "micro-service"
137145

@@ -159,10 +167,10 @@ Each settings object for every framework includes service parameters that can be
159167
You can configure them manually, or set the corresponding environment variables and let <b>microbootstrap</b> to source them automatically.
160168

161169
```python
162-
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings
170+
from microbootstrap.settings import BaseServiceSettings
163171

164172

165-
class ServiceSettings(BaseBootstrapSettings):
173+
class ServiceSettings(BaseServiceSettings):
166174
service_debug: bool = True
167175
service_environment: str | None = None
168176
service_name: str = "micro-service"
@@ -192,10 +200,10 @@ To bootstrap Sentry, you must provide at least the `sentry_dsn`.
192200
Additional parameters can also be supplied through the settings object.
193201

194202
```python
195-
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings
203+
from microbootstrap.settings import BaseServiceSettings
196204

197205

198-
class YourSettings(BaseBootstrapSettings):
206+
class YourSettings(BaseServiceSettings):
199207
service_environment: str | None = None
200208

201209
sentry_dsn: str | None = None
@@ -213,14 +221,46 @@ These settings are subsequently passed to the [sentry-sdk](https://pypi.org/proj
213221

214222
### Prometheus
215223

216-
To bootstrap Prometheus, you must provide at least the `prometheus_metrics_path`.
217-
Additional parameters can also be supplied through the settings object.
224+
Prometheus is not an easy case, because two underlying libraries for `fastapi` and `litestar` are so different, that could not be cast to a single interface. For that reason prometheus settings for `fastapi` and `litestar` are a little bit different
225+
226+
#### Fastapi
227+
228+
To bootstrap prometheus you have to provide `prometheus_metrics_path`
229+
230+
```python
231+
from microbootstrap.settings import FastApiSettings
232+
233+
234+
class YourFastApiSettings(FastApiSettings):
235+
service_name: str
236+
237+
prometheus_metrics_path: str = "/metrics"
238+
prometheus_instrumentator_params: dict[str, typing.Any] = {}
239+
prometheus_instrument_params: dict[str, typing.Any] = {}
240+
prometheus_expose_params: dict[str, typing.Any] = {}
241+
242+
... # Other settings here
243+
```
244+
245+
Parameters description:
246+
247+
- `service_name` - will be attached to metric's names, but has to be named in [snake_case](https://en.wikipedia.org/wiki/Snake_case).
248+
- `prometheus_metrics_path` - path to metrics handler.
249+
- `prometheus_instrumentator_params` - will be passed to `Instrumentor` during initialization.
250+
- `prometheus_instrument_params` - will be passed to `Instrumentor.instrument(...)`.
251+
- `prometheus_expose_params` - will be passed to `Instrumentor.expose(...)`.
252+
253+
FastApi prometheus bootstrapper uses [prometheus-fastapi-instrumentator](https://github.com/trallnag/prometheus-fastapi-instrumentator) that's why there are three different dict for parameters.
254+
255+
#### Fastapi
256+
257+
To bootstrap prometheus you have to provide `prometheus_metrics_path`
218258

219259
```python
220-
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings
260+
from microbootstrap.settings import LitestarSettings
221261

222262

223-
class YourSettings(BaseBootstrapSettings):
263+
class YourFastApiSettings(LitestarSettings):
224264
service_name: str
225265

226266
prometheus_metrics_path: str = "/metrics"
@@ -229,10 +269,11 @@ class YourSettings(BaseBootstrapSettings):
229269
... # Other settings here
230270
```
231271

232-
These settings are subsequently passed to the [prometheus-client](https://pypi.org/project/prometheus-client/) package.
233-
The underlying top-level Prometheus library may vary from framework to framework, but in general, a metrics handler will be available at the provided path.
272+
Parameters description:
234273

235-
By default, metrics are accessible at the `/metrics` path.
274+
- `service_name` - will be attached to metric's names, there are no name restrictions.
275+
- `prometheus_metrics_path` - path to metrics handler.
276+
- `prometheus_additional_params` - will be passed to `litestar.contrib.prometheus.PrometheusConfig`.
236277

237278
### Opentelemetry
238279

@@ -242,29 +283,40 @@ To bootstrap Opentelemetry, you must provide several parameters:
242283
- `service_version`
243284
- `opentelemetry_endpoint`
244285
- `opentelemetry_namespace`
245-
- `opentelemetry_container_name`.
286+
- `opentelemetry_container_name`
246287

247288
However, additional parameters can also be supplied if needed.
248289

249290
```python
250-
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings
291+
from microbootstrap.settings import BaseServiceSettings
251292
from microbootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrumentor
252293

253294

254-
class YourSettings(BaseBootstrapSettings):
295+
class YourSettings(BaseServiceSettings):
255296
service_name: str
256297
service_version: str
257298

258299
opentelemetry_container_name: str | None = None
259300
opentelemetry_endpoint: str | None = None
260301
opentelemetry_namespace: str | None = None
261302
opentelemetry_insecure: bool = True
262-
opentelemetry_insrtumentors: list[OpenTelemetryInstrumentor] = []
303+
opentelemetry_instrumentors: list[OpenTelemetryInstrumentor] = []
263304
opentelemetry_exclude_urls: list[str] = []
264305

265306
... # Other settings here
266307
```
267308

309+
Parameters description:
310+
311+
- `service_name` - will be passed to the `Resource`.
312+
- `service_version` - will be passed to the `Resource`.
313+
- `opentelemetry_endpoint` - will be passed to `OTLPSpanExporter` as endpoint.
314+
- `opentelemetry_namespace` - will be passed to the `Resource`.
315+
- `opentelemetry_insecure` - is opentelemetry connection secure.
316+
- `opentelemetry_container_name` - will be passed to the `Resource`.
317+
- `opentelemetry_instrumentors` - a list of extra instrumentors.
318+
- `opentelemetry_exclude_urls` - list of ignored urls.
319+
268320
These settings are subsequently passed to [opentelemetry](https://opentelemetry.io/), finalizing your Opentelemetry integration.
269321

270322
### Logging
@@ -277,10 +329,10 @@ To utilize this feature, your application must be in non-debug mode, meaning `se
277329
```python
278330
import logging
279331

280-
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings
332+
from microbootstrap.settings import BaseServiceSettings
281333

282334

283-
class YourSettings(BaseBootstrapSettings):
335+
class YourSettings(BaseServiceSettings):
284336
service_debug: bool = False
285337

286338
logging_log_level: int = logging.INFO
@@ -291,7 +343,7 @@ class YourSettings(BaseBootstrapSettings):
291343
logging_exclude_endpoints: list[str] = []
292344
```
293345

294-
Parameter descriptions:
346+
Parameters description:
295347

296348
- `logging_log_level` - The default log level.
297349
- `logging_flush_level` - All messages will be flushed from the buffer when a log with this level appears.
@@ -303,10 +355,10 @@ Parameter descriptions:
303355
### CORS
304356

305357
```python
306-
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings
358+
from microbootstrap.settings import BaseServiceSettings
307359

308360

309-
class YourSettings(BaseBootstrapSettings):
361+
class YourSettings(BaseServiceSettings):
310362
cors_allowed_origins: list[str] = pydantic.Field(default_factory=list)
311363
cors_allowed_methods: list[str] = pydantic.Field(default_factory=list)
312364
cors_allowed_headers: list[str] = pydantic.Field(default_factory=list)
@@ -329,10 +381,10 @@ Parameter descriptions:
329381
### Swagger
330382

331383
```python
332-
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings
384+
from microbootstrap.settings import BaseServiceSettings
333385

334386

335-
class YourSettings(BaseBootstrapSettings):
387+
class YourSettings(BaseServiceSettings):
336388
service_name: str = "micro-service"
337389
service_description: str = "Micro service description"
338390
service_version: str = "1.0.0"

microbootstrap/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
from microbootstrap.instruments.cors_instrument import CorsConfig
22
from microbootstrap.instruments.logging_instrument import LoggingConfig
33
from microbootstrap.instruments.opentelemetry_instrument import OpentelemetryConfig
4-
from microbootstrap.instruments.prometheus_instrument import PrometheusConfig
4+
from microbootstrap.instruments.prometheus_instrument import FastApiPrometheusConfig, LitestarPrometheusConfig
55
from microbootstrap.instruments.sentry_instrument import SentryConfig
66
from microbootstrap.instruments.swagger_instrument import SwaggerConfig
7-
from microbootstrap.settings import LitestarSettings
7+
from microbootstrap.settings import FastApiSettings, LitestarSettings
88

99

1010
__all__ = (
1111
"SentryConfig",
1212
"OpentelemetryConfig",
13-
"PrometheusConfig",
13+
"FastApiPrometheusConfig",
14+
"LitestarPrometheusConfig",
1415
"LoggingConfig",
1516
"LitestarBootstrapper",
1617
"LitestarSettings",
18+
"FastApiSettings",
1719
"CorsConfig",
1820
"SwaggerConfig",
1921
)

microbootstrap/bootstrappers/base.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ class ApplicationBootstrapper(abc.ABC, typing.Generic[SettingsT, ApplicationT, D
2424
application_type: type[ApplicationT]
2525
application_config: DataclassT
2626
console_writer: ConsoleWriter
27-
__instrument_box: InstrumentBox
27+
instrument_box: InstrumentBox
2828

2929
def __init__(self, settings: SettingsT) -> None:
3030
self.settings = settings
3131
self.console_writer = ConsoleWriter(writer_enabled=settings.service_debug)
3232

33-
if not hasattr(self, "__instrument_box"):
34-
self.__instrument_box = InstrumentBox()
35-
self.__instrument_box.initialize(self.settings)
33+
if not hasattr(self, "instrument_box"):
34+
self.instrument_box = InstrumentBox()
35+
self.instrument_box.initialize(self.settings)
3636

3737
def configure_application(
3838
self: typing_extensions.Self,
@@ -45,7 +45,7 @@ def configure_instrument(
4545
self: typing_extensions.Self,
4646
instrument_config: InstrumentConfigT,
4747
) -> typing_extensions.Self:
48-
self.__instrument_box.configure_instrument(instrument_config)
48+
self.instrument_box.configure_instrument(instrument_config)
4949
return self
5050

5151
def configure_instruments(
@@ -63,27 +63,30 @@ def use_instrument(
6363
[type[Instrument[InstrumentConfigT]]],
6464
type[Instrument[InstrumentConfigT]],
6565
]:
66-
if not hasattr(cls, "__instrument_box"):
67-
cls.__instrument_box = InstrumentBox()
68-
return cls.__instrument_box.extend_instruments
66+
if not hasattr(cls, "instrument_box"):
67+
cls.instrument_box = InstrumentBox()
68+
return cls.instrument_box.extend_instruments
6969

7070
def bootstrap(self: typing_extensions.Self) -> ApplicationT:
71-
resulting_application_config = dataclass_to_dict_no_defaults(self.application_config)
72-
for instrument in self.__instrument_box.instruments:
71+
resulting_application_config: dict[str, typing.Any] = {}
72+
for instrument in self.instrument_box.instruments:
7373
if instrument.is_ready():
7474
instrument.bootstrap()
75-
7675
resulting_application_config = merge_dict_configs(
7776
resulting_application_config,
7877
instrument.bootstrap_before(),
7978
)
8079
instrument.write_status(self.console_writer)
8180

81+
resulting_application_config = merge_dict_configs(
82+
resulting_application_config,
83+
dataclass_to_dict_no_defaults(self.application_config),
84+
)
8285
application = self.application_type(
8386
**merge_dict_configs(resulting_application_config, self.bootstrap_before()),
8487
)
8588

86-
for instrument in self.__instrument_box.instruments:
89+
for instrument in self.instrument_box.instruments:
8790
if instrument.is_ready():
8891
application = instrument.bootstrap_after(application)
8992

@@ -98,5 +101,5 @@ def bootstrap_after(self: typing_extensions.Self, application: ApplicationT) ->
98101
return application
99102

100103
def teardown(self: typing_extensions.Self) -> None:
101-
for instrument in self.__instrument_box.instruments:
104+
for instrument in self.instrument_box.instruments:
102105
instrument.teardown()

0 commit comments

Comments
 (0)