Metadata-Version: 2.1
Name: microbootstrap
Version: 0.2.5
Summary: Package for bootstrapping new micro-services
Keywords: python,microservice,bootstrap,opentelemetry,logging,error-tracing,litestar,fastapi
Author: community-of-python
Requires-Python: >=3.9,<4.0
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Build Tools
Classifier: Typing :: Typed
Provides-Extra: fastapi
Provides-Extra: litestar
Requires-Dist: eval-type-backport (>=0.2.0,<0.3.0)
Requires-Dist: fastapi (>=0.111.0,<0.112.0) ; extra == "fastapi"
Requires-Dist: granian[reload] (>=1.4.4,<2.0.0)
Requires-Dist: litestar (>=2.9.1,<3.0.0) ; extra == "litestar"
Requires-Dist: litestar-offline-docs (>=1.0.1,<2.0.0) ; extra == "litestar"
Requires-Dist: opentelemetry-api (>=1.25.0,<2.0.0)
Requires-Dist: opentelemetry-exporter-otlp (>=1.25.0,<2.0.0)
Requires-Dist: opentelemetry-exporter-prometheus-remote-write (>=0.46b0,<0.47)
Requires-Dist: opentelemetry-instrumentation (>=0.46b0,<0.47)
Requires-Dist: opentelemetry-instrumentation-asgi (>=0.46b0,<0.47)
Requires-Dist: opentelemetry-instrumentation-fastapi (>=0.46b0,<0.47) ; extra == "fastapi"
Requires-Dist: opentelemetry-instrumentation-system-metrics (>=0.46b0,<0.47)
Requires-Dist: opentelemetry-sdk (>=1.25.0,<2.0.0)
Requires-Dist: prometheus-client (>=0.20.0,<0.21.0) ; extra == "litestar"
Requires-Dist: prometheus-fastapi-instrumentator (>=6.1.0,<7.0.0) ; extra == "fastapi"
Requires-Dist: pydantic-settings (>=2.3.4,<3.0.0)
Requires-Dist: rich (>=13.7.1,<14.0.0)
Requires-Dist: sentry-sdk (>=2.7.1,<3.0.0)
Requires-Dist: structlog (>=24.2.0,<25.0.0)
Requires-Dist: typing-extensions (>=4.12.2,<5.0.0)
Project-URL: documentation, https://pypi.org/project/microbootstrap/
Project-URL: homepage, https://github.com/community-of-python/microbootstrap
Project-URL: repository, https://github.com/community-of-python/microbootstrap
Description-Content-Type: text/markdown

<p align="center">
    <img src="https://raw.githubusercontent.com/community-of-python/microbootstrap/main/logo.svg" width="350">
</p>
<br>
<p align="center">
    <a href="https://codecov.io/gh/community-of-python/microbootstrap" target="_blank"><img src="https://codecov.io/gh/community-of-python/microbootstrap/branch/main/graph/badge.svg"></a>
    <a href="https://pypi.org/project/microbootstrap/" target="_blank"><img src="https://img.shields.io/pypi/pyversions/microbootstrap"></a>
    <a href="https://pypi.org/project/microbootstrap/" target="_blank"><img src="https://img.shields.io/pypi/v/microbootstrap"></a>
    <a href="https://pypistats.org/packages/microbootstrap" target="_blank"><img src="https://img.shields.io/pypi/dm/microbootstrap"></a>
</p>

<b>microbootstrap</b> assists you in creating applications with all the necessary instruments already set up.

```python
# settings.py
from microbootstrap import LitestarSettings


class YourSettings(LitestarSettings):
    # Your settings are stored here


settings = YourSettings()


# application.py
import litestar
from microbootstrap.bootstrappers.litestar import LitestarBootstrapper

from your_application.settings import settings

# Use the Litestar application!
application: litestar.Litestar = LitestarBootstrapper(settings).bootstrap()
```

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

- `sentry`
- `prometheus`
- `opentelemetry`
- `logging`
- `cors`
- `swagger` - with additional offline version support

Interested? Let's dive right in ⚡

## Table of Contents

- [Installation](#installation)
- [Quickstart](#quickstart)
- [Settings](#settings)
- [Service settings](#service-settings)
- [Instruments](#instruments)
  - [Sentry](#sentry)
  - [Prometheus](#prometheus)
  - [Opentelemetry](#opentelemetry)
  - [Logging](#logging)
  - [CORS](#cors)
  - [Swagger](#swagger)
- [Configuration](#configuration)
  - [Instruments configuration](#instruments-configuration)
  - [Application configuration](#application-configuration)
- [Advanced](#advanced)

## Installation

You can install the package using either `pip` or `poetry`.

For poetry:

```bash
$ poetry add microbootstrap -E litestar
```

For pip:

```bash
$ pip install microbootstrap[litestar]
```

## Quickstart

To configure your application, you can use the settings object.

```python
from microbootstrap import LitestarSettings


class YourSettings(LitestarSettings):
    # General settings
    service_debug: bool = False
    service_name: str = "my-awesome-service"

    # Sentry settings
    sentry_dsn: str = "your-sentry-dsn"

    # Prometheus settings
    prometheus_metrics_path: str = "/my-path"

    # Opentelemetry settings
    opentelemetry_container_name: str = "your-container"
    opentelemetry_endpoint: str = "/opentelemetry-endpoint"



settings = YourSettings()
```

Next, use the `Bootstrapper` object to create an application based on your settings.

```python
import litestar
from microbootstrap.bootstrappers.litestar import LitestarBootstrapper

application: litestar.Litestar = LitestarBootstrapper(settings).bootstrap()
```

This approach will provide you with an application that has all the essential instruments already set up for you.

## Settings

The settings object is the core of microbootstrap.

All framework-related settings inherit from the `BaseBootstrapSettings` object. `BaseBootstrapSettings` defines parameters for the service and various instruments.

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

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

Example:

```python
class YourSettings(BaseBootstrapSettings):
    service_debug: bool = True
    service_name: str = "micro-service"

    your_awesome_parameter: str = "really awesome"

    ... # Other settings here
```

To source `your_awesome_parameter` from the environment, set the environment variable named `YOUR_AWESOME_PARAMETER`.

If you prefer to use a prefix when sourcing parameters, set the `ENVIRONMENT_PREFIX` environment variable in advance.

Example:

```bash
$ export ENVIRONMENT_PREFIX=YOUR_PREFIX_
```

Then the settings object will attempt to source the variable named `YOUR_PREFIX_YOUR_AWESOME_PARAMETER`.

## Service settings

Each settings object for every framework includes service parameters that can be utilized by various instruments.

You can configure them manually, or set the corresponding environment variables and let <b>microbootstrap</b> to source them automatically.

```python
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings


class ServiceSettings(BaseBootstrapSettings):
    service_debug: bool = True
    service_environment: str | None = None
    service_name: str = "micro-service"
    service_description: str = "Micro service description"
    service_version: str = "1.0.0"

    ... # Other settings here

```

## Instruments

At present, the following instruments are supported for bootstrapping:

- `sentry`
- `prometheus`
- `opentelemetry`
- `logging`
- `cors`
- `swagger`

Let's clarify the process required to bootstrap these instruments.

### Sentry

To bootstrap Sentry, you must provide at least the `sentry_dsn`.  
Additional parameters can also be supplied through the settings object.

```python
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings


class YourSettings(BaseBootstrapSettings):
    service_environment: str | None = None

    sentry_dsn: str | None = None
    sentry_traces_sample_rate: float | None = None
    sentry_sample_rate: float = pydantic.Field(default=1.0, le=1.0, ge=0.0)
    sentry_max_breadcrumbs: int = 15
    sentry_attach_stacktrace: bool = True
    sentry_integrations: list[Integration] = []
    sentry_additional_params: dict[str, typing.Any] = {}

    ... # Other settings here
```

These settings are subsequently passed to the [sentry-sdk](https://pypi.org/project/sentry-sdk/) package, finalizing your Sentry integration.

### Prometheus

To bootstrap Prometheus, you must provide at least the `prometheus_metrics_path`.  
Additional parameters can also be supplied through the settings object.

```python
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings


class YourSettings(BaseBootstrapSettings):
    service_name: str

    prometheus_metrics_path: str = "/metrics"
    prometheus_additional_params: dict[str, typing.Any] = {}

    ... # Other settings here
```

These settings are subsequently passed to the [prometheus-client](https://pypi.org/project/prometheus-client/) package.
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.

By default, metrics are accessible at the `/metrics` path.

### Opentelemetry

To bootstrap Opentelemetry, you must provide several parameters:

- `service_name`
- `service_version`
- `opentelemetry_endpoint`
- `opentelemetry_namespace`
- `opentelemetry_container_name`.

However, additional parameters can also be supplied if needed.

```python
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings
from microbootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrumentor


class YourSettings(BaseBootstrapSettings):
    service_name: str
    service_version: str

    opentelemetry_container_name: str | None = None
    opentelemetry_endpoint: str | None = None
    opentelemetry_namespace: str | None = None
    opentelemetry_insecure: bool = True
    opentelemetry_insrtumentors: list[OpenTelemetryInstrumentor] = []
    opentelemetry_exclude_urls: list[str] = []

    ... # Other settings here
```

These settings are subsequently passed to [opentelemetry](https://opentelemetry.io/), finalizing your Opentelemetry integration.

### Logging

<b>microbootstrap</b> provides in-memory JSON logging through the use of [structlog](https://pypi.org/project/structlog/).  
For more information on in-memory logging, refer to [MemoryHandler](https://docs.python.org/3/library/logging.handlers.html#memoryhandler).

To utilize this feature, your application must be in non-debug mode, meaning `service_debug` should be set to `False`.

```python
import logging

from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings


class YourSettings(BaseBootstrapSettings):
    service_debug: bool = False

    logging_log_level: int = logging.INFO
    logging_flush_level: int = logging.ERROR
    logging_buffer_capacity: int = 10
    logging_unset_handlers: list[str] = ["uvicorn", "uvicorn.access"]
    logging_extra_processors: list[typing.Any] = []
    logging_exclude_endpoints: list[str] = []
```

Parameter descriptions:

- `logging_log_level` - The default log level.
- `logging_flush_level` - All messages will be flushed from the buffer when a log with this level appears.
- `logging_buffer_capacity` - The number of messages your buffer will store before being flushed.
- `logging_unset_handlers` - Unset logger handlers.
- `logging_extra_processors` - Set additional structlog processors if needed.
- `logging_exclude_endpoints` - Exclude logging on specific endpoints.

### CORS

```python
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings


class YourSettings(BaseBootstrapSettings):
    cors_allowed_origins: list[str] = pydantic.Field(default_factory=list)
    cors_allowed_methods: list[str] = pydantic.Field(default_factory=list)
    cors_allowed_headers: list[str] = pydantic.Field(default_factory=list)
    cors_exposed_headers: list[str] = pydantic.Field(default_factory=list)
    cors_allowed_credentials: bool = False
    cors_allowed_origin_regex: str | None = None
    cors_max_age: int = 600
```

Parameter descriptions:

- `cors_allowed_origins` - A list of origins that are permitted.
- `cors_allowed_methods` - A list of HTTP methods that are allowed.
- `cors_allowed_headers` - A list of headers that are permitted.
- `cors_exposed_headers` - A list of headers that are exposed via the 'Access-Control-Expose-Headers' header.
- `cors_allowed_credentials` - A boolean value that dictates whether or not to set the 'Access-Control-Allow-Credentials' header.
- `cors_allowed_origin_regex` - A regex used to match against origins.
- `cors_max_age` - The response caching Time-To-Live (TTL) in seconds, defaults to 600.

### Swagger

```python
from microbootstrap.bootstrappers.litestar import BaseBootstrapSettings


class YourSettings(BaseBootstrapSettings):
    service_name: str = "micro-service"
    service_description: str = "Micro service description"
    service_version: str = "1.0.0"
    service_static_path: str = "/static"

    swagger_path: str = "/docs"
    swagger_offline_docs: bool = False
    swagger_extra_params: dict[str, Any] = {}
```

Parameter descriptions:

- `service_name` - The name of the service, which will be displayed in the documentation.
- `service_description` - A brief description of the service, which will also be displayed in the documentation.
- `service_version` - The current version of the service.
- `service_static_path` - The path for static files in the service.
- `swagger_path` - The path where the documentation can be found.
- `swagger_offline_docs` - A boolean value that, when set to True, allows the Swagger JS bundles to be accessed offline. This is because the service starts to host via static.
- `swagger_extra_params` - Additional parameters to pass into the OpenAPI configuration.

## Configuration

While settings provide a convenient mechanism, it's not always feasible to store everything within them.

There may be cases where you need to configure a tool directly. Here's how it can be done.

### Instruments configuration

To manually configure an instrument, you need to import one of the available configurations from <b>microbootstrap</b>:

- `SentryConfig`
- `OpentelemetryConfig`
- `PrometheusConfig`
- `LoggingConfig`
- `SwaggerConfig`
- `CorsConfig`

These configurations can then be passed into the `.configure_instrument` or `.configure_instruments` bootstrapper methods.

```python
import litestar

from microbootstrap.bootstrappers.litestar import LitestarBootstrapper
from microbootstrap import SentryConfig, OpentelemetryConfig


application: litestar.Litestar = (
    LitestarBootstrapper(settings)
    .configure_instrument(SentryConfig(sentry_dsn="https://new-dsn"))
    .configure_instrument(OpentelemetryConfig(opentelemetry_endpoint="/new-endpoint"))
    .bootstrap()
)
```

Alternatively,

```python
import litestar

from microbootstrap.bootstrappers.litestar import LitestarBootstrapper
from microbootstrap import SentryConfig, OpentelemetryConfig


application: litestar.Litestar = (
    LitestarBootstrapper(settings)
    .configure_instruments(
        SentryConfig(sentry_dsn="https://examplePublicKey@o0.ingest.sentry.io/0"),
        OpentelemetryConfig(opentelemetry_endpoint="/new-endpoint")
    )
    .bootstrap()
)
```

### Application configuration

The application can be configured in a similar manner:

```python
import litestar
from litestar.config.app import AppConfig

from microbootstrap.bootstrappers.litestar import LitestarBootstrapper
from microbootstrap import SentryConfig, OpentelemetryConfig


@litestar.get("/my-handler")
async def my_handler() -> str:
    return "Ok"

application: litestar.Litestar = (
    LitestarBootstrapper(settings)
    .configure_application(AppConfig(route_handlers=[my_handler]))
    .bootstrap()
)
```

> ### Important
>
> When configuring parameters with simple data types such as: `str`, `int`, `float`, etc., these variables overwrite previous values.
>
> Example:
>
> ```python
> from microbootstrap import LitestarSettings, SentryConfig
>
>
> class YourSettings(LitestarSettings):
>     sentry_dsn: str = "https://my-sentry-dsn"
>
>
> application: litestar.Litestar = (
>     LitestarBootstrapper(YourSettings())
>     .configure_instrument(
>         SentryConfig(sentry_dsn="https://my-new-configured-sentry-dsn")
>     )
>     .bootstrap()
> )
> ```
>
> In this example, the application will be bootstrapped with the new `https://my-new-configured-sentry-dsn` Sentry DSN, replacing the old one.
>
> However, when you configure parameters with complex data types such as: `list`, `tuple`, `dict`, or `set`, they are expanded or merged.
>
> Example:
>
> ```python
> from microbootstrap import LitestarSettings, PrometheusConfig
>
>
> class YourSettings(LitestarSettings):
>     prometheus_additional_params: dict[str, Any] = {"first_value": 1}
>
>
> application: litestar.Litestar = (
>     LitestarBootstrapper(YourSettings())
>     .configure_instrument(
>         PrometheusConfig(prometheus_additional_params={"second_value": 2})
>     )
>     .bootstrap()
> )
> ```
>
> In this case, Prometheus will receive `{"first_value": 1, "second_value": 2}` inside `prometheus_additional_params`. This is also true for `list`, `tuple`, and `set`.

## Advanced

If you need a specific instrument, you can create your own.
Essentially, an `Instrument` is just a class with some abstract methods. Each instrument uses a certain configuration, which is the first thing you need to define.

```python
from microbootstrap.instruments.base import BaseInstrumentConfig


class MyInstrumentConfig(BaseInstrumentConfig):
    your_string_parameter: str
    your_list_parameter: list
```

Next, you can create an instrument class that inherits from `Instrument` and accepts your configuration as a generic parameter.

```python
from microbootstrap.instruments.base import Instrument


class MyInstrument(Instrument[MyInstrumentConfig]):
    instrument_name: str
    ready_condition: str

    def is_ready(self) -> bool:
        pass

    def teardown(self) -> None:
        pass

    def bootstrap(self) -> None:
        pass

    @classmethod
    def get_config_type(cls) -> type[MyInstrumentConfig]:
        return MyInstrumentConfig
```

Now, you can define the behavior of your instrument.

Attributes:

- `instrument_name` - This will be displayed in your console during bootstrap.
- `ready_condition` - This will be displayed in your console during bootstrap if the instrument is not ready.

Methods:

- `is_ready` - This defines the readiness of the instrument for bootstrapping, based on its configuration values. This is required.
- `teardown` - This allows for a graceful shutdown of the instrument during application shutdown. This is not required.
- `bootstrap` - This is the main logic of the instrument. This is not required.

Once you have the framework of the instrument, you can adapt it for any existing framework. For instance, let's adapt it for litestar.

```python
import litestar

from microbootstrap.bootstrappers.litestar import LitestarBootstrapper

@LitestarBootstrapper.use_instrument()
class LitestarMyInstrument(MyInstrument):
    def bootstrap_before(self) -> dict[str, typing.Any]:
        pass

    def bootstrap_after(self, application: litestar.Litestar) -> dict[str, typing.Any]:
        pass
```

To bind the instrument to a bootstrapper, use the `.use_instrument` decorator.

To add extra parameters to the application, you can use:

- `bootstrap_before` - This adds arguments to the application configuration before creation.
- `bootstrap_after` - This adds arguments to the application after creation.

Afterwards, you can use your instrument during the bootstrap process.

```python
import litestar

from microbootstrap.bootstrappers.litestar import LitestarBootstrapper
from microbootstrap import SentryConfig, OpentelemetryConfig

from your_app import MyInstrumentConfig


application: litestar.Litestar = (
    LitestarBootstrapper(settings)
    .configure_instrument(
        MyInstrumentConfig(
            your_string_parameter="very-nice-parameter",
            your_list_parameter=["very-special-list"],
        )
    )
    .bootstrap()
)
```

Alternatively, you can fill these parameters within your main settings object.

```python
from microbootstrap import LitestarSettings
from microbootstrap.bootstrappers.litestar import LitestarBootstrapper

from your_app import MyInstrumentConfig


class YourSettings(LitestarSettings, MyInstrumentConfig):
    your_string_parameter: str = "very-nice-parameter"
    your_list_parameter: list = ["very-special-list"]

settings = YourSettings()

application: litestar.Litestar = LitestarBootstrapper(settings).bootstrap()
```

