servicekit 2.0.3__tar.gz → 3.0.1__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.
- {servicekit-2.0.3 → servicekit-3.0.1}/PKG-INFO +5 -6
- {servicekit-2.0.3 → servicekit-3.0.1}/README.md +1 -1
- {servicekit-2.0.3 → servicekit-3.0.1}/pyproject.toml +4 -5
- {servicekit-2.0.3 → servicekit-3.0.1}/pyproject.toml.orig +4 -5
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/monitoring.py +6 -18
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/service_builder.py +28 -5
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/__init__.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/alembic/env.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/alembic/script.py.mako +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/alembic/versions/20251010_0927_4d869b5fb06e_initial_schema.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/__init__.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/app.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/apps/landing/index.html +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/apps/landing/manifest.json +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/auth.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/crud.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/dependencies.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/middleware.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/pagination.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/registration.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/router.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/routers/__init__.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/routers/health.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/routers/job.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/routers/metrics.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/routers/system.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/sse.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/utilities.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/database.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/exceptions.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/gunicorn.conf.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/logging.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/manager.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/models.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/py.typed +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/repository.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/scheduler.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/schemas.py +0 -0
- {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: servicekit
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.1
|
|
4
4
|
Summary: Async SQLAlchemy framework with FastAPI integration - reusable foundation for building data services
|
|
5
5
|
Keywords: fastapi,sqlalchemy,async,database,rest-api,crud,framework
|
|
6
6
|
Author: Morten Hansen
|
|
@@ -15,13 +15,12 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
15
15
|
Classifier: Topic :: Database
|
|
16
16
|
Requires-Dist: aiosqlite>=0.21.0
|
|
17
17
|
Requires-Dist: alembic>=1.17.0
|
|
18
|
-
Requires-Dist: fastapi[standard]>=0.
|
|
18
|
+
Requires-Dist: fastapi[standard]>=0.142.2
|
|
19
19
|
Requires-Dist: gunicorn>=23.0.0
|
|
20
|
-
Requires-Dist: opentelemetry-api>=1.
|
|
20
|
+
Requires-Dist: opentelemetry-api>=1.44.0
|
|
21
21
|
Requires-Dist: opentelemetry-exporter-prometheus>=0.58b0
|
|
22
|
-
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.58b0
|
|
23
22
|
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.58b0
|
|
24
|
-
Requires-Dist: opentelemetry-sdk>=1.
|
|
23
|
+
Requires-Dist: opentelemetry-sdk>=1.44.0
|
|
25
24
|
Requires-Dist: pydantic>=2.12.0
|
|
26
25
|
Requires-Dist: python-ulid>=3.1.0
|
|
27
26
|
Requires-Dist: sqlalchemy[asyncio]>=2.0.43
|
|
@@ -115,7 +114,7 @@ app = (
|
|
|
115
114
|
.with_database(url) # Database configuration
|
|
116
115
|
.with_jobs(max_concurrency=10) # Job scheduler
|
|
117
116
|
.with_auth() # API key authentication
|
|
118
|
-
.with_monitoring() # Prometheus metrics
|
|
117
|
+
.with_monitoring() # Prometheus metrics (on by default)
|
|
119
118
|
.with_app("./webapp") # Static web app
|
|
120
119
|
.include_router(custom_router) # Custom routes
|
|
121
120
|
.build()
|
|
@@ -79,7 +79,7 @@ app = (
|
|
|
79
79
|
.with_database(url) # Database configuration
|
|
80
80
|
.with_jobs(max_concurrency=10) # Job scheduler
|
|
81
81
|
.with_auth() # API key authentication
|
|
82
|
-
.with_monitoring() # Prometheus metrics
|
|
82
|
+
.with_monitoring() # Prometheus metrics (on by default)
|
|
83
83
|
.with_app("./webapp") # Static web app
|
|
84
84
|
.include_router(custom_router) # Custom routes
|
|
85
85
|
.build()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "servicekit"
|
|
3
|
-
version = "
|
|
3
|
+
version = "3.0.1"
|
|
4
4
|
description = "Async SQLAlchemy framework with FastAPI integration - reusable foundation for building data services"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13,<3.14"
|
|
@@ -25,13 +25,12 @@ classifiers = [
|
|
|
25
25
|
dependencies = [
|
|
26
26
|
"aiosqlite>=0.21.0",
|
|
27
27
|
"alembic>=1.17.0",
|
|
28
|
-
"fastapi[standard]>=0.
|
|
28
|
+
"fastapi[standard]>=0.142.2",
|
|
29
29
|
"gunicorn>=23.0.0",
|
|
30
|
-
"opentelemetry-api>=1.
|
|
30
|
+
"opentelemetry-api>=1.44.0",
|
|
31
31
|
"opentelemetry-exporter-prometheus>=0.58b0",
|
|
32
|
-
"opentelemetry-instrumentation-fastapi>=0.58b0",
|
|
33
32
|
"opentelemetry-instrumentation-sqlalchemy>=0.58b0",
|
|
34
|
-
"opentelemetry-sdk>=1.
|
|
33
|
+
"opentelemetry-sdk>=1.44.0",
|
|
35
34
|
"pydantic>=2.12.0",
|
|
36
35
|
"python-ulid>=3.1.0",
|
|
37
36
|
"sqlalchemy[asyncio]>=2.0.43",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "servicekit"
|
|
3
|
-
version = "
|
|
3
|
+
version = "3.0.1"
|
|
4
4
|
description = "Async SQLAlchemy framework with FastAPI integration - reusable foundation for building data services"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Morten Hansen", email = "morten@dhis2.org" }]
|
|
@@ -27,13 +27,12 @@ classifiers = [
|
|
|
27
27
|
dependencies = [
|
|
28
28
|
"aiosqlite>=0.21.0",
|
|
29
29
|
"alembic>=1.17.0",
|
|
30
|
-
"fastapi[standard]>=0.
|
|
30
|
+
"fastapi[standard]>=0.142.2",
|
|
31
31
|
"gunicorn>=23.0.0",
|
|
32
|
-
"opentelemetry-api>=1.
|
|
32
|
+
"opentelemetry-api>=1.44.0",
|
|
33
33
|
"opentelemetry-exporter-prometheus>=0.58b0",
|
|
34
|
-
"opentelemetry-instrumentation-fastapi>=0.58b0",
|
|
35
34
|
"opentelemetry-instrumentation-sqlalchemy>=0.58b0",
|
|
36
|
-
"opentelemetry-sdk>=1.
|
|
35
|
+
"opentelemetry-sdk>=1.44.0",
|
|
37
36
|
"pydantic>=2.12.0",
|
|
38
37
|
"python-ulid>=3.1.0",
|
|
39
38
|
"sqlalchemy[asyncio]>=2.0.43",
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
"""OpenTelemetry monitoring setup
|
|
1
|
+
"""OpenTelemetry monitoring setup: Prometheus metrics, SQLAlchemy instrumentation, FastAPI OpenTelemetry."""
|
|
2
2
|
|
|
3
3
|
from fastapi import FastAPI
|
|
4
4
|
from opentelemetry import metrics
|
|
5
5
|
from opentelemetry.exporter.prometheus import PrometheusMetricReader
|
|
6
|
-
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
7
6
|
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
8
7
|
from opentelemetry.sdk.metrics import MeterProvider
|
|
9
8
|
from opentelemetry.sdk.resources import Resource
|
|
@@ -28,7 +27,7 @@ def setup_monitoring(
|
|
|
28
27
|
service_name: str | None = None,
|
|
29
28
|
enable_traces: bool = False,
|
|
30
29
|
) -> PrometheusMetricReader:
|
|
31
|
-
"""Setup
|
|
30
|
+
"""Setup the global Prometheus meter provider and SQLAlchemy instrumentation; FastAPI OpenTelemetry records HTTP."""
|
|
32
31
|
global _meter_provider_initialized, _sqlalchemy_instrumented, _process_collector_registered, _metric_reader
|
|
33
32
|
|
|
34
33
|
# Use app title as service name if not provided
|
|
@@ -54,15 +53,11 @@ def setup_monitoring(
|
|
|
54
53
|
# Already registered
|
|
55
54
|
pass
|
|
56
55
|
|
|
57
|
-
# Auto-instrument FastAPI - check if already instrumented
|
|
58
|
-
instrumentor = FastAPIInstrumentor()
|
|
59
|
-
if not instrumentor.is_instrumented_by_opentelemetry:
|
|
60
|
-
instrumentor.instrument_app(app)
|
|
61
|
-
|
|
62
56
|
# Auto-instrument SQLAlchemy - only once globally
|
|
63
57
|
if not _sqlalchemy_instrumented:
|
|
64
58
|
try:
|
|
65
|
-
|
|
59
|
+
# The instrumentor's declared range lags SQLAlchemy releases (0.66b1 stops below 2.1) and works on 2.1.
|
|
60
|
+
SQLAlchemyInstrumentor().instrument(skip_dep_check=True)
|
|
66
61
|
_sqlalchemy_instrumented = True
|
|
67
62
|
except RuntimeError:
|
|
68
63
|
# Already instrumented
|
|
@@ -86,19 +81,12 @@ def setup_monitoring(
|
|
|
86
81
|
|
|
87
82
|
|
|
88
83
|
def teardown_monitoring() -> None:
|
|
89
|
-
"""Teardown
|
|
90
|
-
global
|
|
84
|
+
"""Teardown SQLAlchemy instrumentation; the global meter provider cannot be replaced, so its reader stays."""
|
|
85
|
+
global _sqlalchemy_instrumented
|
|
91
86
|
|
|
92
|
-
_meter_provider_initialized = False
|
|
93
87
|
_sqlalchemy_instrumented = False
|
|
94
|
-
_process_collector_registered = False
|
|
95
|
-
_metric_reader = None
|
|
96
88
|
|
|
97
89
|
try:
|
|
98
|
-
# Uninstrument FastAPI
|
|
99
|
-
FastAPIInstrumentor().uninstrument()
|
|
100
|
-
|
|
101
|
-
# Uninstrument SQLAlchemy
|
|
102
90
|
SQLAlchemyInstrumentor().uninstrument()
|
|
103
91
|
|
|
104
92
|
logger.info("monitoring.disabled")
|
|
@@ -8,13 +8,14 @@ import re
|
|
|
8
8
|
import secrets
|
|
9
9
|
import signal
|
|
10
10
|
import sys
|
|
11
|
-
from collections.abc import Sequence
|
|
11
|
+
from collections.abc import MutableMapping, Sequence
|
|
12
12
|
from contextlib import asynccontextmanager
|
|
13
13
|
from dataclasses import dataclass
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import Any, AsyncContextManager, AsyncGenerator, Awaitable, Callable, Self
|
|
16
16
|
|
|
17
17
|
from fastapi import APIRouter, FastAPI
|
|
18
|
+
from fastapi.telemetry import TelemetryConfig
|
|
18
19
|
from pydantic import BaseModel, ConfigDict, field_validator
|
|
19
20
|
from sqlalchemy import text
|
|
20
21
|
from starlette.types import ASGIApp, Message, Receive, Scope, Send
|
|
@@ -168,7 +169,12 @@ class BaseServiceBuilder:
|
|
|
168
169
|
self._system_options: _SystemOptions | None = None
|
|
169
170
|
self._job_options: JobOptions | None = None
|
|
170
171
|
self._auth_options: _AuthOptions | None = None
|
|
171
|
-
self._monitoring_options: _MonitoringOptions | None =
|
|
172
|
+
self._monitoring_options: _MonitoringOptions | None = _MonitoringOptions(
|
|
173
|
+
prefix="/metrics",
|
|
174
|
+
tags=["Observability"],
|
|
175
|
+
service_name=None,
|
|
176
|
+
enable_traces=False,
|
|
177
|
+
)
|
|
172
178
|
self._registration_options: _RegistrationOptions | None = None
|
|
173
179
|
self._app_configs: list[App] = []
|
|
174
180
|
self._custom_routers: list[APIRouter] = []
|
|
@@ -316,13 +322,17 @@ class BaseServiceBuilder:
|
|
|
316
322
|
|
|
317
323
|
def with_monitoring(
|
|
318
324
|
self,
|
|
325
|
+
enabled: bool = True,
|
|
319
326
|
*,
|
|
320
327
|
prefix: str = "/metrics",
|
|
321
328
|
tags: list[str] | None = None,
|
|
322
329
|
service_name: str | None = None,
|
|
323
330
|
enable_traces: bool = False,
|
|
324
331
|
) -> Self:
|
|
325
|
-
"""
|
|
332
|
+
"""Configure OpenTelemetry monitoring (enabled by default); pass enabled=False to turn it off."""
|
|
333
|
+
if not enabled:
|
|
334
|
+
self._monitoring_options = None
|
|
335
|
+
return self
|
|
326
336
|
self._monitoring_options = _MonitoringOptions(
|
|
327
337
|
prefix=prefix,
|
|
328
338
|
tags=list(tags) if tags is not None else ["Observability"],
|
|
@@ -426,6 +436,7 @@ class BaseServiceBuilder:
|
|
|
426
436
|
description=self._app_description,
|
|
427
437
|
version=self._version,
|
|
428
438
|
lifespan=lifespan,
|
|
439
|
+
telemetry=self._build_telemetry_config(),
|
|
429
440
|
)
|
|
430
441
|
app.state.database_url = self._database_url
|
|
431
442
|
|
|
@@ -552,14 +563,26 @@ class BaseServiceBuilder:
|
|
|
552
563
|
for dependency, override in self._dependency_overrides.items():
|
|
553
564
|
app.dependency_overrides[dependency] = override
|
|
554
565
|
|
|
555
|
-
# Last, so it wraps any build_middleware_stack patch made above
|
|
556
|
-
# expects the stack it builds to start with ServerErrorMiddleware)
|
|
566
|
+
# Last, so it wraps any build_middleware_stack patch made above
|
|
557
567
|
if self._registration_options is not None:
|
|
558
568
|
app.state.readiness_token = secrets.token_hex(16)
|
|
559
569
|
app.build_middleware_stack = _wrap_with_readiness_token(app) # type: ignore[method-assign]
|
|
560
570
|
|
|
561
571
|
return app
|
|
562
572
|
|
|
573
|
+
def _build_telemetry_config(self) -> TelemetryConfig:
|
|
574
|
+
"""Build FastAPI OpenTelemetry settings: skip metrics scrapes when enabled, turn everything off when not."""
|
|
575
|
+
if self._monitoring_options is None:
|
|
576
|
+
return {"tracing": False, "metrics": False, "logs": False, "auto_configure": False}
|
|
577
|
+
metrics_prefix = self._monitoring_options.prefix
|
|
578
|
+
|
|
579
|
+
def exclude_metrics_scrape(scope: MutableMapping[str, Any]) -> bool:
|
|
580
|
+
"""Skip telemetry for requests to the metrics endpoint."""
|
|
581
|
+
path = str(scope.get("path", ""))
|
|
582
|
+
return path == metrics_prefix or path.startswith(f"{metrics_prefix}/")
|
|
583
|
+
|
|
584
|
+
return {"exclude": exclude_metrics_scrape}
|
|
585
|
+
|
|
563
586
|
# --------------------------------------------------------------------- Extension points
|
|
564
587
|
|
|
565
588
|
def _validate_module_configuration(self) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|