lite-bootstrap 0.2.0__tar.gz → 0.3.0__tar.gz

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 (33) hide show
  1. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/PKG-INFO +1 -1
  2. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/lite_bootstrap/bootstraps/base.py +7 -3
  3. lite_bootstrap-0.3.0/lite_bootstrap/bootstraps/fastapi_bootstrap/__init__.py +28 -0
  4. lite_bootstrap-0.3.0/lite_bootstrap/bootstraps/fastapi_bootstrap/healthchecks_instrument.py +30 -0
  5. lite_bootstrap-0.3.0/lite_bootstrap/bootstraps/fastapi_bootstrap/opentelemetry_instrument.py +29 -0
  6. lite_bootstrap-0.3.0/lite_bootstrap/bootstraps/fastapi_bootstrap/sentry_instrument.py +7 -0
  7. lite_bootstrap-0.3.0/lite_bootstrap/instruments/base.py +13 -0
  8. lite_bootstrap-0.3.0/lite_bootstrap/instruments/healthchecks_instrument.py +30 -0
  9. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/lite_bootstrap/instruments/opentelemetry_instrument.py +26 -27
  10. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/lite_bootstrap/instruments/sentry_instrument.py +13 -10
  11. lite_bootstrap-0.3.0/lite_bootstrap/service_config.py +8 -0
  12. lite_bootstrap-0.3.0/lite_bootstrap/types.py +4 -0
  13. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/tests/conftest.py +11 -9
  14. lite_bootstrap-0.3.0/tests/instruments/__init__.py +0 -0
  15. lite_bootstrap-0.3.0/tests/instruments/test_opentelemetry_instrument.py +31 -0
  16. lite_bootstrap-0.3.0/tests/instruments/test_sentry_instrument.py +10 -0
  17. lite_bootstrap-0.3.0/tests/test_fastapi_bootstrap.py +41 -0
  18. lite_bootstrap-0.2.0/lite_bootstrap/bootstraps/fastapi_bootstrap.py +0 -51
  19. lite_bootstrap-0.2.0/lite_bootstrap/instruments/base.py +0 -12
  20. lite_bootstrap-0.2.0/tests/test_fastapi_bootstrap.py +0 -33
  21. lite_bootstrap-0.2.0/tests/test_opentelemetry_bootstrap.py +0 -24
  22. lite_bootstrap-0.2.0/tests/test_sentry_bootstrap.py +0 -9
  23. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/.github/workflows/ci.yml +0 -0
  24. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/.github/workflows/publish.yml +0 -0
  25. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/.gitignore +0 -0
  26. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/Justfile +0 -0
  27. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/README.md +0 -0
  28. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/lite_bootstrap/__init__.py +0 -0
  29. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/lite_bootstrap/bootstraps/__init__.py +0 -0
  30. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/lite_bootstrap/instruments/__init__.py +0 -0
  31. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/lite_bootstrap/py.typed +0 -0
  32. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/pyproject.toml +0 -0
  33. {lite_bootstrap-0.2.0 → lite_bootstrap-0.3.0}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lite-bootstrap
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Lite package for bootstrapping new microservices
5
5
  Project-URL: repository, https://github.com/modern-python/lite-bootstrap
6
6
  Author-email: Artur Shiriev <me@shiriev.ru>
@@ -2,17 +2,21 @@ import abc
2
2
  import typing
3
3
 
4
4
  from lite_bootstrap.instruments.base import BaseInstrument
5
+ from lite_bootstrap.service_config import ServiceConfig
6
+ from lite_bootstrap.types import ApplicationT
5
7
 
6
8
 
7
- class BaseBootstrap(abc.ABC):
9
+ class BaseBootstrap(abc.ABC, typing.Generic[ApplicationT]):
10
+ application: ApplicationT
8
11
  instruments: typing.Sequence[BaseInstrument]
12
+ service_config: ServiceConfig
9
13
 
10
14
  def bootstrap(self) -> None:
11
15
  for one_instrument in self.instruments:
12
16
  if one_instrument.is_ready():
13
- one_instrument.bootstrap()
17
+ one_instrument.bootstrap(self.service_config, self.application)
14
18
 
15
19
  def teardown(self) -> None:
16
20
  for one_instrument in self.instruments:
17
21
  if one_instrument.is_ready():
18
- one_instrument.teardown()
22
+ one_instrument.teardown(self.application)
@@ -0,0 +1,28 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ import fastapi
5
+
6
+ from lite_bootstrap.bootstraps.base import BaseBootstrap
7
+ from lite_bootstrap.bootstraps.fastapi_bootstrap.healthchecks_instrument import FastAPIHealthChecksInstrument
8
+ from lite_bootstrap.bootstraps.fastapi_bootstrap.opentelemetry_instrument import FastAPIOpenTelemetryInstrument
9
+ from lite_bootstrap.bootstraps.fastapi_bootstrap.sentry_instrument import FastAPISentryInstrument
10
+
11
+
12
+ __all__ = [
13
+ "FastAPIBootstrap",
14
+ "FastAPIHealthChecksInstrument",
15
+ "FastAPIOpenTelemetryInstrument",
16
+ "FastAPISentryInstrument",
17
+ ]
18
+
19
+ from lite_bootstrap.service_config import ServiceConfig
20
+
21
+
22
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
23
+ class FastAPIBootstrap(BaseBootstrap[fastapi.FastAPI]):
24
+ application: fastapi.FastAPI
25
+ instruments: typing.Sequence[
26
+ FastAPIOpenTelemetryInstrument | FastAPISentryInstrument | FastAPIHealthChecksInstrument
27
+ ]
28
+ service_config: ServiceConfig
@@ -0,0 +1,30 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ import fastapi
5
+
6
+ from lite_bootstrap.instruments.healthchecks_instrument import HealthChecksInstrument, HealthCheckTypedDict
7
+ from lite_bootstrap.service_config import ServiceConfig
8
+
9
+
10
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
11
+ class FastAPIHealthChecksInstrument(HealthChecksInstrument):
12
+ enabled: bool = True
13
+ path: str = "/health/"
14
+ include_in_schema: bool = False
15
+
16
+ def build_fastapi_health_check_router(self, service_config: ServiceConfig) -> fastapi.APIRouter:
17
+ fastapi_router: typing.Final = fastapi.APIRouter(
18
+ tags=["probes"],
19
+ include_in_schema=self.include_in_schema,
20
+ )
21
+
22
+ @fastapi_router.get(self.path)
23
+ async def health_check_handler() -> HealthCheckTypedDict:
24
+ return self.render_health_check_data(service_config)
25
+
26
+ return fastapi_router
27
+
28
+ def bootstrap(self, service_config: ServiceConfig, application: fastapi.FastAPI | None = None) -> None:
29
+ if application:
30
+ application.include_router(self.build_fastapi_health_check_router(service_config))
@@ -0,0 +1,29 @@
1
+ import contextlib
2
+ import dataclasses
3
+
4
+ import fastapi
5
+
6
+ from lite_bootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrument
7
+ from lite_bootstrap.service_config import ServiceConfig
8
+
9
+
10
+ with contextlib.suppress(ImportError):
11
+ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
12
+
13
+
14
+ @dataclasses.dataclass(kw_only=True)
15
+ class FastAPIOpenTelemetryInstrument(OpenTelemetryInstrument):
16
+ excluded_urls: list[str] = dataclasses.field(default_factory=list)
17
+
18
+ def bootstrap(self, service_config: ServiceConfig, application: fastapi.FastAPI | None = None) -> None:
19
+ super().bootstrap(service_config, application)
20
+ FastAPIInstrumentor.instrument_app(
21
+ app=application,
22
+ tracer_provider=self.tracer_provider,
23
+ excluded_urls=",".join(self.excluded_urls),
24
+ )
25
+
26
+ def teardown(self, application: fastapi.FastAPI | None = None) -> None:
27
+ if application:
28
+ FastAPIInstrumentor.uninstrument_app(application)
29
+ super().teardown()
@@ -0,0 +1,7 @@
1
+ import dataclasses
2
+
3
+ from lite_bootstrap.instruments.sentry_instrument import SentryInstrument
4
+
5
+
6
+ @dataclasses.dataclass(kw_only=True, frozen=True)
7
+ class FastAPISentryInstrument(SentryInstrument): ...
@@ -0,0 +1,13 @@
1
+ import abc
2
+
3
+ from lite_bootstrap.service_config import ServiceConfig
4
+ from lite_bootstrap.types import ApplicationT
5
+
6
+
7
+ class BaseInstrument(abc.ABC):
8
+ def bootstrap(self, service_config: ServiceConfig, application: ApplicationT | None = None) -> None: ... # noqa: B027
9
+
10
+ def teardown(self, application: ApplicationT | None = None) -> None: ... # noqa: B027
11
+
12
+ @abc.abstractmethod
13
+ def is_ready(self) -> bool: ...
@@ -0,0 +1,30 @@
1
+ import dataclasses
2
+
3
+ import typing_extensions
4
+
5
+ from lite_bootstrap.instruments.base import BaseInstrument
6
+ from lite_bootstrap.service_config import ServiceConfig
7
+
8
+
9
+ class HealthCheckTypedDict(typing_extensions.TypedDict, total=False):
10
+ service_version: str | None
11
+ service_name: str | None
12
+ health_status: bool
13
+
14
+
15
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
16
+ class HealthChecksInstrument(BaseInstrument):
17
+ enabled: bool = True
18
+ path: str = "/health/"
19
+ include_in_schema: bool = False
20
+
21
+ def is_ready(self) -> bool:
22
+ return self.enabled
23
+
24
+ @staticmethod
25
+ def render_health_check_data(service_config: ServiceConfig) -> HealthCheckTypedDict:
26
+ return {
27
+ "service_version": service_config.service_version,
28
+ "service_name": service_config.service_name,
29
+ "health_status": True,
30
+ }
@@ -1,14 +1,18 @@
1
+ import contextlib
1
2
  import dataclasses
2
3
  import typing
3
4
 
4
- import pydantic
5
- from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
6
- from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # type: ignore[attr-defined]
7
- from opentelemetry.sdk import resources
8
- from opentelemetry.sdk.trace import TracerProvider
9
- from opentelemetry.sdk.trace.export import BatchSpanProcessor
10
-
11
5
  from lite_bootstrap.instruments.base import BaseInstrument
6
+ from lite_bootstrap.service_config import ServiceConfig
7
+ from lite_bootstrap.types import ApplicationT
8
+
9
+
10
+ with contextlib.suppress(ImportError):
11
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
12
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # type: ignore[attr-defined]
13
+ from opentelemetry.sdk import resources
14
+ from opentelemetry.sdk.trace import TracerProvider
15
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter
12
16
 
13
17
 
14
18
  @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
@@ -19,37 +23,24 @@ class InstrumentorWithParams:
19
23
 
20
24
  @dataclasses.dataclass(kw_only=True, slots=True)
21
25
  class OpenTelemetryInstrument(BaseInstrument):
22
- service_version: str = "1.0.0"
23
- service_name: str | None = None
24
26
  container_name: str | None = None
25
27
  endpoint: str | None = None
26
28
  namespace: str | None = None
27
- insecure: bool = pydantic.Field(default=True)
29
+ insecure: bool = True
28
30
  instrumentors: list[InstrumentorWithParams | BaseInstrumentor] = dataclasses.field(default_factory=list)
31
+ span_exporter: SpanExporter | None = None
29
32
 
30
33
  tracer_provider: TracerProvider = dataclasses.field(init=False)
31
34
 
32
35
  def is_ready(self) -> bool:
33
- return all(
34
- (
35
- self.endpoint,
36
- self.service_name,
37
- ),
38
- )
36
+ return bool(self.endpoint)
39
37
 
40
- def teardown(self) -> None:
41
- for one_instrumentor in self.instrumentors:
42
- if isinstance(one_instrumentor, InstrumentorWithParams):
43
- one_instrumentor.instrumentor.uninstrument(**one_instrumentor.additional_params)
44
- else:
45
- one_instrumentor.uninstrument()
46
-
47
- def bootstrap(self) -> None:
38
+ def bootstrap(self, service_config: ServiceConfig, _: ApplicationT | None = None) -> None:
48
39
  attributes = {
49
- resources.SERVICE_NAME: self.service_name,
40
+ resources.SERVICE_NAME: service_config.service_name,
50
41
  resources.TELEMETRY_SDK_LANGUAGE: "python",
51
42
  resources.SERVICE_NAMESPACE: self.namespace,
52
- resources.SERVICE_VERSION: self.service_version,
43
+ resources.SERVICE_VERSION: service_config.service_version,
53
44
  resources.CONTAINER_NAME: self.container_name,
54
45
  }
55
46
  resource: typing.Final = resources.Resource.create(
@@ -58,7 +49,8 @@ class OpenTelemetryInstrument(BaseInstrument):
58
49
  self.tracer_provider = TracerProvider(resource=resource)
59
50
  self.tracer_provider.add_span_processor(
60
51
  BatchSpanProcessor(
61
- OTLPSpanExporter(
52
+ self.span_exporter
53
+ or OTLPSpanExporter(
62
54
  endpoint=self.endpoint,
63
55
  insecure=self.insecure,
64
56
  ),
@@ -72,3 +64,10 @@ class OpenTelemetryInstrument(BaseInstrument):
72
64
  )
73
65
  else:
74
66
  one_instrumentor.instrument(tracer_provider=self.tracer_provider)
67
+
68
+ def teardown(self, _: ApplicationT | None = None) -> None:
69
+ for one_instrumentor in self.instrumentors:
70
+ if isinstance(one_instrumentor, InstrumentorWithParams):
71
+ one_instrumentor.instrumentor.uninstrument(**one_instrumentor.additional_params)
72
+ else:
73
+ one_instrumentor.uninstrument()
@@ -1,19 +1,22 @@
1
+ import contextlib
1
2
  import dataclasses
2
3
  import typing
3
4
 
4
- import pydantic
5
- import sentry_sdk
6
- from sentry_sdk.integrations import Integration
7
-
8
5
  from lite_bootstrap.instruments.base import BaseInstrument
6
+ from lite_bootstrap.service_config import ServiceConfig
7
+ from lite_bootstrap.types import ApplicationT
8
+
9
+
10
+ with contextlib.suppress(ImportError):
11
+ import sentry_sdk
12
+ from sentry_sdk.integrations import Integration
9
13
 
10
14
 
11
- @dataclasses.dataclass(kw_only=True, slots=True)
15
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
12
16
  class SentryInstrument(BaseInstrument):
13
17
  dsn: str | None = None
14
- sample_rate: float = pydantic.Field(default=1.0, le=1.0, ge=0.0)
18
+ sample_rate: float = dataclasses.field(default=1.0)
15
19
  traces_sample_rate: float | None = None
16
- environment: str | None = None
17
20
  max_breadcrumbs: int = 15
18
21
  max_value_length: int = 16384
19
22
  attach_stacktrace: bool = True
@@ -24,12 +27,12 @@ class SentryInstrument(BaseInstrument):
24
27
  def is_ready(self) -> bool:
25
28
  return bool(self.dsn)
26
29
 
27
- def bootstrap(self) -> None:
30
+ def bootstrap(self, service_config: ServiceConfig, _: ApplicationT | None = None) -> None:
28
31
  sentry_sdk.init(
29
32
  dsn=self.dsn,
30
33
  sample_rate=self.sample_rate,
31
34
  traces_sample_rate=self.traces_sample_rate,
32
- environment=self.environment,
35
+ environment=service_config.service_environment,
33
36
  max_breadcrumbs=self.max_breadcrumbs,
34
37
  max_value_length=self.max_value_length,
35
38
  attach_stacktrace=self.attach_stacktrace,
@@ -39,4 +42,4 @@ class SentryInstrument(BaseInstrument):
39
42
  tags: dict[str, str] = self.tags or {}
40
43
  sentry_sdk.set_tags(tags)
41
44
 
42
- def teardown(self) -> None: ...
45
+ def teardown(self, application: ApplicationT | None = None) -> None: ...
@@ -0,0 +1,8 @@
1
+ import dataclasses
2
+
3
+
4
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
5
+ class ServiceConfig:
6
+ service_name: str = "micro-service"
7
+ service_version: str = "1.0.0"
8
+ service_environment: str | None = None
@@ -0,0 +1,4 @@
1
+ import typing
2
+
3
+
4
+ ApplicationT = typing.TypeVar("ApplicationT", bound=typing.Any)
@@ -1,10 +1,11 @@
1
1
  import typing
2
2
 
3
3
  import pytest
4
- from fastapi import APIRouter, FastAPI
5
- from fastapi.responses import JSONResponse
4
+ from fastapi import FastAPI
6
5
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor # type: ignore[attr-defined]
7
6
 
7
+ from lite_bootstrap.service_config import ServiceConfig
8
+
8
9
 
9
10
  class CustomInstrumentor(BaseInstrumentor): # type: ignore[misc]
10
11
  def instrumentation_dependencies(self) -> typing.Collection[str]:
@@ -16,12 +17,13 @@ class CustomInstrumentor(BaseInstrumentor): # type: ignore[misc]
16
17
 
17
18
  @pytest.fixture
18
19
  def fastapi_app() -> FastAPI:
19
- app: typing.Final = FastAPI()
20
- router: typing.Final = APIRouter()
20
+ return FastAPI()
21
21
 
22
- @router.get("/test")
23
- async def for_test_endpoint() -> JSONResponse:
24
- return JSONResponse(content={"key": "value"})
25
22
 
26
- app.include_router(router)
27
- return app
23
+ @pytest.fixture
24
+ def service_config() -> ServiceConfig:
25
+ return ServiceConfig(
26
+ service_name="microservice",
27
+ service_version="2.0.0",
28
+ service_environment="test",
29
+ )
File without changes
@@ -0,0 +1,31 @@
1
+ from opentelemetry.sdk.trace.export import ConsoleSpanExporter
2
+
3
+ from lite_bootstrap.instruments.opentelemetry_instrument import InstrumentorWithParams, OpenTelemetryInstrument
4
+ from lite_bootstrap.service_config import ServiceConfig
5
+ from tests.conftest import CustomInstrumentor
6
+
7
+
8
+ def test_opentelemetry_instrument(service_config: ServiceConfig) -> None:
9
+ opentelemetry = OpenTelemetryInstrument(
10
+ endpoint="otl",
11
+ instrumentors=[
12
+ InstrumentorWithParams(instrumentor=CustomInstrumentor(), additional_params={"key": "value"}),
13
+ CustomInstrumentor(),
14
+ ],
15
+ span_exporter=ConsoleSpanExporter(),
16
+ )
17
+ try:
18
+ opentelemetry.bootstrap(service_config)
19
+ finally:
20
+ opentelemetry.teardown()
21
+
22
+
23
+ def test_opentelemetry_instrument_empty_instruments(service_config: ServiceConfig) -> None:
24
+ opentelemetry = OpenTelemetryInstrument(
25
+ endpoint="otl",
26
+ span_exporter=ConsoleSpanExporter(),
27
+ )
28
+ try:
29
+ opentelemetry.bootstrap(service_config)
30
+ finally:
31
+ opentelemetry.teardown()
@@ -0,0 +1,10 @@
1
+ from lite_bootstrap.instruments.sentry_instrument import SentryInstrument
2
+ from lite_bootstrap.service_config import ServiceConfig
3
+
4
+
5
+ def test_sentry_instrument(service_config: ServiceConfig) -> None:
6
+ SentryInstrument(dsn="https://testdsn@localhost/1", tags={"tag": "value"}).bootstrap(service_config)
7
+
8
+
9
+ def test_sentry_instrument_empty_dsn(service_config: ServiceConfig) -> None:
10
+ SentryInstrument(dsn="").bootstrap(service_config)
@@ -0,0 +1,41 @@
1
+ from fastapi import FastAPI
2
+ from opentelemetry.sdk.trace.export import ConsoleSpanExporter
3
+ from starlette import status
4
+ from starlette.testclient import TestClient
5
+
6
+ from lite_bootstrap.bootstraps.fastapi_bootstrap import (
7
+ FastAPIBootstrap,
8
+ FastAPIHealthChecksInstrument,
9
+ FastAPIOpenTelemetryInstrument,
10
+ FastAPISentryInstrument,
11
+ )
12
+ from lite_bootstrap.service_config import ServiceConfig
13
+ from tests.conftest import CustomInstrumentor
14
+
15
+
16
+ def test_fastapi_bootstrap(fastapi_app: FastAPI, service_config: ServiceConfig) -> None:
17
+ fastapi_bootstrap = FastAPIBootstrap(
18
+ application=fastapi_app,
19
+ service_config=service_config,
20
+ instruments=[
21
+ FastAPIOpenTelemetryInstrument(
22
+ endpoint="otl",
23
+ instrumentors=[CustomInstrumentor()],
24
+ span_exporter=ConsoleSpanExporter(),
25
+ ),
26
+ FastAPISentryInstrument(
27
+ dsn="https://testdsn@localhost/1",
28
+ ),
29
+ FastAPIHealthChecksInstrument(
30
+ path="/health/",
31
+ ),
32
+ ],
33
+ )
34
+ fastapi_bootstrap.bootstrap()
35
+
36
+ try:
37
+ response = TestClient(fastapi_app).get("/health/")
38
+ assert response.status_code == status.HTTP_200_OK
39
+ assert response.json() == {"health_status": True, "service_name": "microservice", "service_version": "2.0.0"}
40
+ finally:
41
+ fastapi_bootstrap.teardown()
@@ -1,51 +0,0 @@
1
- import dataclasses
2
- import typing
3
-
4
- import fastapi
5
- from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
6
- from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
7
-
8
- from lite_bootstrap.bootstraps.base import BaseBootstrap
9
- from lite_bootstrap.instruments.opentelemetry_instrument import OpenTelemetryInstrument
10
- from lite_bootstrap.instruments.sentry_instrument import SentryInstrument
11
-
12
-
13
- @dataclasses.dataclass(kw_only=True)
14
- class FastAPIOpenTelemetryInstrument(OpenTelemetryInstrument):
15
- excluded_urls: list[str] = dataclasses.field(default_factory=list)
16
- app: fastapi.FastAPI = dataclasses.field(init=False)
17
-
18
- def bootstrap(self) -> None:
19
- super().bootstrap()
20
- FastAPIInstrumentor.instrument_app(
21
- app=self.app,
22
- tracer_provider=self.tracer_provider,
23
- excluded_urls=",".join(self.excluded_urls),
24
- )
25
-
26
- def teardown(self) -> None:
27
- FastAPIInstrumentor.uninstrument_app(self.app)
28
- super().teardown()
29
-
30
-
31
- @dataclasses.dataclass(kw_only=True)
32
- class FastAPISentryInstrument(SentryInstrument):
33
- app: fastapi.FastAPI = dataclasses.field(init=False)
34
-
35
- def bootstrap(self) -> None:
36
- super().bootstrap()
37
- self.app.add_middleware(SentryAsgiMiddleware) # type: ignore[arg-type]
38
-
39
- def teardown(self) -> None:
40
- FastAPIInstrumentor.uninstrument_app(self.app)
41
- super().teardown()
42
-
43
-
44
- @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
45
- class FastAPIBootstrap(BaseBootstrap):
46
- app: fastapi.FastAPI
47
- instruments: typing.Sequence[FastAPIOpenTelemetryInstrument | FastAPISentryInstrument]
48
-
49
- def __post_init__(self) -> None:
50
- for one_instrument in self.instruments:
51
- one_instrument.app = self.app
@@ -1,12 +0,0 @@
1
- import abc
2
-
3
-
4
- class BaseInstrument(abc.ABC):
5
- @abc.abstractmethod
6
- def bootstrap(self) -> None: ...
7
-
8
- @abc.abstractmethod
9
- def teardown(self) -> None: ...
10
-
11
- @abc.abstractmethod
12
- def is_ready(self) -> bool: ...
@@ -1,33 +0,0 @@
1
- from fastapi import FastAPI
2
- from starlette import status
3
- from starlette.testclient import TestClient
4
-
5
- from lite_bootstrap.bootstraps.fastapi_bootstrap import (
6
- FastAPIBootstrap,
7
- FastAPIOpenTelemetryInstrument,
8
- FastAPISentryInstrument,
9
- )
10
- from tests.conftest import CustomInstrumentor
11
-
12
-
13
- def test_fastapi_bootstrap(fastapi_app: FastAPI) -> None:
14
- fastapi_bootstrap = FastAPIBootstrap(
15
- app=fastapi_app,
16
- instruments=[
17
- FastAPIOpenTelemetryInstrument(
18
- endpoint="localhost",
19
- service_name="test_service",
20
- instrumentors=[CustomInstrumentor()],
21
- ),
22
- FastAPISentryInstrument(
23
- dsn="https://testdsn@test.sentry.com/1",
24
- ),
25
- ],
26
- )
27
- fastapi_bootstrap.bootstrap()
28
- fastapi_bootstrap.teardown()
29
-
30
- response = TestClient(fastapi_app).get("/test")
31
- assert response.status_code == status.HTTP_200_OK
32
-
33
- fastapi_bootstrap.teardown()
@@ -1,24 +0,0 @@
1
- from lite_bootstrap.instruments.opentelemetry_instrument import InstrumentorWithParams, OpenTelemetryInstrument
2
- from tests.conftest import CustomInstrumentor
3
-
4
-
5
- def test_bootstrap_opentelemetry() -> None:
6
- opentelemetry = OpenTelemetryInstrument(
7
- endpoint="localhost",
8
- service_name="test_service",
9
- instrumentors=[
10
- InstrumentorWithParams(instrumentor=CustomInstrumentor(), additional_params={"key": "value"}),
11
- CustomInstrumentor(),
12
- ],
13
- )
14
- opentelemetry.bootstrap()
15
- opentelemetry.teardown()
16
-
17
-
18
- def test_bootstrap_opentelemetry_empty_instruments() -> None:
19
- opentelemetry = OpenTelemetryInstrument(
20
- endpoint="localhost",
21
- service_name="test_service",
22
- )
23
- opentelemetry.bootstrap()
24
- opentelemetry.teardown()
@@ -1,9 +0,0 @@
1
- from lite_bootstrap.instruments.sentry_instrument import SentryInstrument
2
-
3
-
4
- def test_sentry_bootstrap() -> None:
5
- SentryInstrument(dsn="https://testdsn@test.sentry.com/1", tags={"tag": "value"}).bootstrap()
6
-
7
-
8
- def test_sentry_bootstrap_empty_dsn() -> None:
9
- SentryInstrument(dsn="").bootstrap()
File without changes
File without changes