Skip to content

Commit 3cebe41

Browse files
committed
implement new version
1 parent 6a11bf8 commit 3cebe41

17 files changed

+269
-184
lines changed

.github/workflows/ci.yml

+16-13
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ jobs:
1414
lint:
1515
runs-on: ubuntu-latest
1616
steps:
17-
- uses: actions/checkout@v3
18-
- uses: actions/setup-python@v4
19-
with:
20-
python-version: "3.10"
17+
- uses: actions/checkout@v4
2118
- uses: extractions/setup-just@v2
22-
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
19+
- uses: astral-sh/setup-uv@v3
20+
with:
21+
enable-cache: true
22+
cache-dependency-glob: "**/pyproject.toml"
23+
- run: uv python install 3.10
2324
- run: just install lint-ci
2425

2526
pytest:
@@ -31,16 +32,18 @@ jobs:
3132
- "3.10"
3233
- "3.11"
3334
- "3.12"
35+
- "3.13"
3436
steps:
35-
- uses: actions/checkout@v3
36-
- uses: actions/setup-python@v4
37-
with:
38-
python-version: ${{ matrix.python-version }}
37+
- uses: actions/checkout@v4
3938
- uses: extractions/setup-just@v2
40-
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
41-
- run: just install test . --cov=. --cov-report xml
42-
- name: Upload coverage to Codecov
43-
uses: codecov/[email protected]
39+
- uses: astral-sh/setup-uv@v3
40+
with:
41+
enable-cache: true
42+
cache-dependency-glob: "**/pyproject.toml"
43+
- run: uv python install ${{ matrix.python-version }}
44+
- run: just install
45+
- run: just test . --cov=. --cov-report xml
46+
- uses: codecov/[email protected]
4447
env:
4548
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
4649
with:

.github/workflows/publish.yml

+4-8
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@ jobs:
1010
runs-on: ubuntu-latest
1111
steps:
1212
- uses: actions/checkout@v4
13-
- uses: actions/setup-python@v5
14-
with:
15-
python-version: "3.12"
16-
- uses: actions/cache@v4
17-
with:
18-
path: ~/.cache/uv
19-
key: publish-${{ hashFiles('pyproject.toml') }}
2013
- uses: extractions/setup-just@v2
21-
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
14+
- uses: astral-sh/setup-uv@v3
15+
with:
16+
enable-cache: true
17+
cache-dependency-glob: "**/pyproject.toml"
2218
- run: just publish
2319
env:
2420
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}

Justfile

+14-9
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,25 @@ install:
55
uv sync --all-extras --frozen
66

77
lint:
8-
uv run ruff format .
9-
uv run ruff check . --fix
8+
uv run ruff format
9+
uv run ruff check --fix
1010
uv run mypy .
1111

1212
lint-ci:
13-
uv run ruff format . --check
14-
uv run ruff check . --no-fix
13+
uv run ruff format --check
14+
uv run ruff check --no-fix
1515
uv run mypy .
1616

1717
test *args:
18-
uv run pytest {{ args }}
18+
uv run --no-sync pytest {{ args }}
1919

2020
publish:
21-
rm -rf dist/*
22-
uv tool run --from build python -m build --installer uv
23-
uv tool run twine check dist/*
24-
uv tool run twine upload dist/* --username __token__ --password $PYPI_TOKEN
21+
rm -rf dist
22+
uv build
23+
uv publish --token $PYPI_TOKEN
24+
25+
hook:
26+
uv run pre-commit install
27+
28+
unhook:
29+
uv run pre-commit uninstall

lite_bootstrap/bootstraps/__init__.py

Whitespace-only changes.

lite_bootstrap/bootstraps/base.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import abc
2+
import typing
3+
4+
from lite_bootstrap.instruments.base import BaseInstrument
5+
6+
7+
class BaseBootstrap(abc.ABC):
8+
instruments: typing.Sequence[BaseInstrument]
9+
10+
def bootstrap(self) -> None:
11+
for one_instrument in self.instruments:
12+
if one_instrument.is_ready():
13+
one_instrument.bootstrap()
14+
15+
def teardown(self) -> None:
16+
for one_instrument in self.instruments:
17+
if one_instrument.is_ready():
18+
one_instrument.teardown()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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

lite_bootstrap/fastapi_bootstrap.py

-34
This file was deleted.

lite_bootstrap/instruments/__init__.py

Whitespace-only changes.

lite_bootstrap/instruments/base.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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: ...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import dataclasses
2+
import typing
3+
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+
from lite_bootstrap.instruments.base import BaseInstrument
12+
13+
14+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
15+
class InstrumentorWithParams:
16+
instrumentor: BaseInstrumentor
17+
additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)
18+
19+
20+
@dataclasses.dataclass(kw_only=True, slots=True)
21+
class OpenTelemetryInstrument(BaseInstrument):
22+
service_version: str = "1.0.0"
23+
service_name: str | None = None
24+
container_name: str | None = None
25+
endpoint: str | None = None
26+
namespace: str | None = None
27+
insecure: bool = pydantic.Field(default=True)
28+
instrumentors: list[InstrumentorWithParams | BaseInstrumentor] = dataclasses.field(default_factory=list)
29+
30+
tracer_provider: TracerProvider = dataclasses.field(init=False)
31+
32+
def is_ready(self) -> bool:
33+
return all(
34+
(
35+
self.endpoint,
36+
self.service_name,
37+
),
38+
)
39+
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:
48+
attributes = {
49+
resources.SERVICE_NAME: self.service_name,
50+
resources.TELEMETRY_SDK_LANGUAGE: "python",
51+
resources.SERVICE_NAMESPACE: self.namespace,
52+
resources.SERVICE_VERSION: self.service_version,
53+
resources.CONTAINER_NAME: self.container_name,
54+
}
55+
resource: typing.Final = resources.Resource.create(
56+
attributes={k: v for k, v in attributes.items() if v},
57+
)
58+
self.tracer_provider = TracerProvider(resource=resource)
59+
self.tracer_provider.add_span_processor(
60+
BatchSpanProcessor(
61+
OTLPSpanExporter(
62+
endpoint=self.endpoint,
63+
insecure=self.insecure,
64+
),
65+
),
66+
)
67+
for one_instrumentor in self.instrumentors:
68+
if isinstance(one_instrumentor, InstrumentorWithParams):
69+
one_instrumentor.instrumentor.instrument(
70+
tracer_provider=self.tracer_provider,
71+
**one_instrumentor.additional_params,
72+
)
73+
else:
74+
one_instrumentor.instrument(tracer_provider=self.tracer_provider)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import dataclasses
2+
import typing
3+
4+
import pydantic
5+
import sentry_sdk
6+
from sentry_sdk.integrations import Integration
7+
8+
from lite_bootstrap.instruments.base import BaseInstrument
9+
10+
11+
@dataclasses.dataclass(kw_only=True, slots=True)
12+
class SentryInstrument(BaseInstrument):
13+
dsn: str | None = None
14+
sample_rate: float = pydantic.Field(default=1.0, le=1.0, ge=0.0)
15+
traces_sample_rate: float | None = None
16+
environment: str | None = None
17+
max_breadcrumbs: int = 15
18+
max_value_length: int = 16384
19+
attach_stacktrace: bool = True
20+
integrations: list[Integration] = dataclasses.field(default_factory=list)
21+
additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)
22+
tags: dict[str, str] | None = None
23+
24+
def is_ready(self) -> bool:
25+
return bool(self.dsn)
26+
27+
def bootstrap(self) -> None:
28+
sentry_sdk.init(
29+
dsn=self.dsn,
30+
sample_rate=self.sample_rate,
31+
traces_sample_rate=self.traces_sample_rate,
32+
environment=self.environment,
33+
max_breadcrumbs=self.max_breadcrumbs,
34+
max_value_length=self.max_value_length,
35+
attach_stacktrace=self.attach_stacktrace,
36+
integrations=self.integrations,
37+
**self.additional_params,
38+
)
39+
tags: dict[str, str] = self.tags or {}
40+
sentry_sdk.set_tags(tags)
41+
42+
def teardown(self) -> None: ...

lite_bootstrap/opentelemetry_bootstrap.py

-46
This file was deleted.

0 commit comments

Comments
 (0)