Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 18 additions & 63 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,66 +1,21 @@
.idea/
.vscode/
# Byte-compiled / optimized / DLL files
pycache/
__pycache__
*.py[cod]
*$py.class
__test*

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
*.pyc
*~
__pycache__/*
*.swp
*.sqlite3
*.map
.vscode
.idea
.DS_Store
.env
.mypy_cache
.pytest_cache
.ruff_cache
.coverage
.coverage.*
.cache
nosetests.xml
htmlcov/
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Environments
.env
.gen.env
pytest.xml
dist/
.python-version
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Cython debug symbols
cython_debug/
node_modules
.temp
poetry.lock
24 changes: 8 additions & 16 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
- repo: local
hooks:
- id: mypy
name: mypy
always_run: true
additional_dependencies: [pydantic>=2.3.4]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
name: ruff-check
always_run: true
args: [--fix]
- id: ruff-format
name: ruff-format
always_run: true
- id: lint
name: lint
entry: just
args: [lint]
language: system
types: [python]
pass_filenames: false
12 changes: 12 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
default: install lint test

test *args:
poetry run pytest {{ args }}

install:
poetry install --sync --no-root --all-extras

lint:
poetry run ruff format
poetry run ruff check --fix
poetry run mypy .
26 changes: 14 additions & 12 deletions microbootstrap/bootstrappers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@
import abc
import typing

import typing_extensions

from microbootstrap.console_writer import ConsoleWriter
from microbootstrap.helpers import dataclass_to_dict_no_defaults, merge_dataclasses_configs, merge_dict_configs
from microbootstrap.instruments.instrument_box import InstrumentBox
from microbootstrap.settings import SettingsT


if typing.TYPE_CHECKING:
from _typeshed import DataclassInstance
import typing_extensions

from microbootstrap.instruments.base import Instrument, InstrumentConfigT


ApplicationT = typing.TypeVar("ApplicationT")
DataclassT = typing.TypeVar("DataclassT", bound="DataclassInstance")
class DataclassInstance(typing.Protocol):
__dataclass_fields__: typing.ClassVar[dict[str, typing.Any]]


ApplicationT = typing.TypeVar("ApplicationT", bound=typing.Any)
DataclassT = typing.TypeVar("DataclassT", bound=DataclassInstance)


class ApplicationBootstrapper(abc.ABC, typing.Generic[SettingsT, ApplicationT, DataclassT]):
Expand All @@ -35,21 +37,21 @@ def __init__(self, settings: SettingsT) -> None:
self.instrument_box.initialize(self.settings)

def configure_application(
self: typing_extensions.Self,
self,
application_config: DataclassT,
) -> typing_extensions.Self:
self.application_config = merge_dataclasses_configs(self.application_config, application_config)
return self

def configure_instrument(
self: typing_extensions.Self,
self,
instrument_config: InstrumentConfigT,
) -> typing_extensions.Self:
self.instrument_box.configure_instrument(instrument_config)
return self

def configure_instruments(
self: typing_extensions.Self,
self,
*instrument_configs: InstrumentConfigT,
) -> typing_extensions.Self:
for instrument_config in instrument_configs:
Expand All @@ -67,7 +69,7 @@ def use_instrument(
cls.instrument_box = InstrumentBox()
return cls.instrument_box.extend_instruments

def bootstrap(self: typing_extensions.Self) -> ApplicationT:
def bootstrap(self) -> ApplicationT:
resulting_application_config: dict[str, typing.Any] = {}
for instrument in self.instrument_box.instruments:
if instrument.is_ready():
Expand All @@ -92,14 +94,14 @@ def bootstrap(self: typing_extensions.Self) -> ApplicationT:

return self.bootstrap_after(application)

def bootstrap_before(self: typing_extensions.Self) -> dict[str, typing.Any]:
def bootstrap_before(self) -> dict[str, typing.Any]:
"""Add some framework-related parameters to final bootstrap result before application creation."""
return {}

def bootstrap_after(self: typing_extensions.Self, application: ApplicationT) -> ApplicationT:
def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
"""Add some framework-related parameters to final bootstrap result after application creation."""
return application

def teardown(self: typing_extensions.Self) -> None:
def teardown(self) -> None:
for instrument in self.instrument_box.instruments:
instrument.teardown()
22 changes: 12 additions & 10 deletions microbootstrap/bootstrappers/fastapi.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import typing

import fastapi
import typing_extensions
from fastapi.middleware.cors import CORSMiddleware
from fastapi_offline_docs import enable_offline_docs
from health_checks.fastapi_healthcheck import build_fastapi_health_check_router
Expand All @@ -22,13 +21,16 @@
from microbootstrap.settings import FastApiSettings


ApplicationT = typing.TypeVar("ApplicationT", bound=fastapi.FastAPI)


class FastApiBootstrapper(
ApplicationBootstrapper[FastApiSettings, fastapi.FastAPI, FastApiConfig],
):
application_config = FastApiConfig()
application_type = fastapi.FastAPI

def bootstrap_before(self: typing_extensions.Self) -> dict[str, typing.Any]:
def bootstrap_before(self) -> dict[str, typing.Any]:
return {
"debug": self.settings.service_debug,
"on_shutdown": [self.teardown],
Expand Down Expand Up @@ -57,15 +59,15 @@ def bootstrap_before(self) -> dict[str, typing.Any]:
"version": self.instrument_config.service_version,
}

def bootstrap_after(self, application: fastapi.FastAPI) -> fastapi.FastAPI:
def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
if self.instrument_config.swagger_offline_docs:
enable_offline_docs(application, static_files_handler=self.instrument_config.service_static_path)
return application


@FastApiBootstrapper.use_instrument()
class FastApiCorsInstrument(CorsInstrument):
def bootstrap_after(self, application: fastapi.FastAPI) -> fastapi.FastAPI:
def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
application.add_middleware(
CORSMiddleware,
allow_origins=self.instrument_config.cors_allowed_origins,
Expand All @@ -81,7 +83,7 @@ def bootstrap_after(self, application: fastapi.FastAPI) -> fastapi.FastAPI:

@FastApiBootstrapper.use_instrument()
class FastApiOpentelemetryInstrument(OpentelemetryInstrument):
def bootstrap_after(self, application: fastapi.FastAPI) -> fastapi.FastAPI:
def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
FastAPIInstrumentor.instrument_app(
application,
tracer_provider=self.tracer_provider,
Expand All @@ -92,16 +94,16 @@ def bootstrap_after(self, application: fastapi.FastAPI) -> fastapi.FastAPI:

@FastApiBootstrapper.use_instrument()
class FastApiLoggingInstrument(LoggingInstrument):
def bootstrap_after(self, application: fastapi.FastAPI) -> fastapi.FastAPI:
application.add_middleware(
build_fastapi_logging_middleware(self.instrument_config.logging_exclude_endpoints),
def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
application.add_middleware( # type: ignore[call-arg]
build_fastapi_logging_middleware(self.instrument_config.logging_exclude_endpoints), # type: ignore[arg-type]
)
return application


@FastApiBootstrapper.use_instrument()
class FastApiPrometheusInstrument(PrometheusInstrument[FastApiPrometheusConfig]):
def bootstrap_after(self, application: fastapi.FastAPI) -> fastapi.FastAPI:
def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
Instrumentator(**self.instrument_config.prometheus_instrumentator_params).instrument(
application,
**self.instrument_config.prometheus_instrument_params,
Expand All @@ -120,7 +122,7 @@ def get_config_type(cls) -> type[FastApiPrometheusConfig]:

@FastApiBootstrapper.use_instrument()
class FastApiHealthChecksInstrument(HealthChecksInstrument):
def bootstrap_after(self, application: fastapi.FastAPI) -> fastapi.FastAPI:
def bootstrap_after(self, application: ApplicationT) -> ApplicationT:
application.include_router(
build_fastapi_health_check_router(
health_check=self.health_check,
Expand Down
8 changes: 3 additions & 5 deletions microbootstrap/config/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,15 @@


if typing.TYPE_CHECKING:
from fastapi import FastAPI, Request, routing
from fastapi import Request, routing
from fastapi.applications import AppType
from fastapi.middleware import Middleware
from fastapi.params import Depends
from starlette.responses import Response
from starlette.routing import BaseRoute
from starlette.types import Lifespan


AppType = typing.TypeVar("AppType", bound="FastAPI")


@dataclasses.dataclass
class FastApiConfig:
debug: bool = False
Expand Down Expand Up @@ -47,7 +45,7 @@ class FastApiConfig:
) = None
on_startup: typing.Sequence[typing.Callable[[], typing.Any]] | None = None
on_shutdown: typing.Sequence[typing.Callable[[], typing.Any]] | None = None
lifespan: Lifespan[AppType] | None = None
lifespan: Lifespan[AppType] | None = None # type: ignore[valid-type]
terms_of_service: str | None = None
contact: dict[str, str | typing.Any] | None = None
license_info: dict[str, str | typing.Any] | None = None
Expand Down
2 changes: 1 addition & 1 deletion microbootstrap/instruments/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@


InstrumentConfigT = typing.TypeVar("InstrumentConfigT", bound="BaseInstrumentConfig")
ApplicationT = typing.TypeVar("ApplicationT")
ApplicationT = typing.TypeVar("ApplicationT", bound=typing.Any)


class BaseInstrumentConfig(pydantic.BaseModel):
Expand Down
4 changes: 3 additions & 1 deletion microbootstrap/instruments/cors_instrument.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from __future__ import annotations
import typing

import pydantic
from litestar.types import Method # noqa: TC002

from microbootstrap.instruments.base import BaseInstrumentConfig, Instrument


class CorsConfig(BaseInstrumentConfig):
cors_allowed_origins: list[str] = pydantic.Field(default_factory=list)
cors_allowed_methods: list[str] = pydantic.Field(default_factory=list)
cors_allowed_methods: list[typing.Literal["*"] | Method] = 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
Expand Down
4 changes: 1 addition & 3 deletions microbootstrap/instruments/instrument_box.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import dataclasses
import typing

import typing_extensions

from microbootstrap import exceptions
from microbootstrap.instruments.base import Instrument, InstrumentConfigT
from microbootstrap.settings import SettingsT
Expand All @@ -21,7 +19,7 @@ def initialize(self, settings: SettingsT) -> None:
]

def configure_instrument(
self: typing_extensions.Self,
self,
instrument_config: InstrumentConfigT,
) -> None:
for instrument in self.__initialized_instruments__:
Expand Down
2 changes: 1 addition & 1 deletion microbootstrap/instruments/logging_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def make_path_with_query_string(scope: ScopeType) -> str:

def fill_log_message(
log_level: str,
request: litestar.Request | fastapi.Request,
request: litestar.Request[typing.Any, typing.Any, typing.Any] | fastapi.Request,
status_code: int,
start_time: int,
) -> None:
Expand Down
6 changes: 3 additions & 3 deletions microbootstrap/instruments/opentelemetry_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pydantic
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # noqa: TCH002
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # type: ignore[attr-defined] # noqa: TC002
from opentelemetry.sdk import resources
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
Expand Down Expand Up @@ -54,9 +54,9 @@ def bootstrap(self) -> None:
attributes={
resources.SERVICE_NAME: self.instrument_config.service_name,
resources.TELEMETRY_SDK_LANGUAGE: "python",
resources.SERVICE_NAMESPACE: self.instrument_config.opentelemetry_namespace,
resources.SERVICE_NAMESPACE: self.instrument_config.opentelemetry_namespace, # type: ignore[dict-item]
resources.SERVICE_VERSION: self.instrument_config.service_version,
resources.CONTAINER_NAME: self.instrument_config.opentelemetry_container_name,
resources.CONTAINER_NAME: self.instrument_config.opentelemetry_container_name, # type: ignore[dict-item]
},
)

Expand Down
Loading
Loading