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.
Files changed (39) hide show
  1. {servicekit-2.0.3 → servicekit-3.0.1}/PKG-INFO +5 -6
  2. {servicekit-2.0.3 → servicekit-3.0.1}/README.md +1 -1
  3. {servicekit-2.0.3 → servicekit-3.0.1}/pyproject.toml +4 -5
  4. {servicekit-2.0.3 → servicekit-3.0.1}/pyproject.toml.orig +4 -5
  5. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/monitoring.py +6 -18
  6. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/service_builder.py +28 -5
  7. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/__init__.py +0 -0
  8. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/alembic/env.py +0 -0
  9. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/alembic/script.py.mako +0 -0
  10. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/alembic/versions/20251010_0927_4d869b5fb06e_initial_schema.py +0 -0
  11. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/__init__.py +0 -0
  12. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/app.py +0 -0
  13. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/apps/landing/index.html +0 -0
  14. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/apps/landing/manifest.json +0 -0
  15. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/auth.py +0 -0
  16. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/crud.py +0 -0
  17. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/dependencies.py +0 -0
  18. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/middleware.py +0 -0
  19. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/pagination.py +0 -0
  20. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/registration.py +0 -0
  21. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/router.py +0 -0
  22. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/routers/__init__.py +0 -0
  23. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/routers/health.py +0 -0
  24. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/routers/job.py +0 -0
  25. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/routers/metrics.py +0 -0
  26. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/routers/system.py +0 -0
  27. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/sse.py +0 -0
  28. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/api/utilities.py +0 -0
  29. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/database.py +0 -0
  30. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/exceptions.py +0 -0
  31. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/gunicorn.conf.py +0 -0
  32. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/logging.py +0 -0
  33. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/manager.py +0 -0
  34. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/models.py +0 -0
  35. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/py.typed +0 -0
  36. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/repository.py +0 -0
  37. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/scheduler.py +0 -0
  38. {servicekit-2.0.3 → servicekit-3.0.1}/src/servicekit/schemas.py +0 -0
  39. {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: 2.0.3
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.136.3
18
+ Requires-Dist: fastapi[standard]>=0.142.2
19
19
  Requires-Dist: gunicorn>=23.0.0
20
- Requires-Dist: opentelemetry-api>=1.37.0
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.37.0
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 = "2.0.3"
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.136.3",
28
+ "fastapi[standard]>=0.142.2",
29
29
  "gunicorn>=23.0.0",
30
- "opentelemetry-api>=1.37.0",
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.37.0",
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 = "2.0.3"
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.136.3",
30
+ "fastapi[standard]>=0.142.2",
31
31
  "gunicorn>=23.0.0",
32
- "opentelemetry-api>=1.37.0",
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.37.0",
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 with auto-instrumentation."""
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 OpenTelemetry with FastAPI and SQLAlchemy auto-instrumentation."""
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
- SQLAlchemyInstrumentor().instrument()
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 OpenTelemetry instrumentation and reset module state."""
90
- global _meter_provider_initialized, _sqlalchemy_instrumented, _process_collector_registered, _metric_reader
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 = 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
- """Enable OpenTelemetry monitoring with Prometheus endpoint and auto-instrumentation."""
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 (OpenTelemetry instrumentation
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: