svc-infra 0.1.603__py3-none-any.whl → 0.1.604__py3-none-any.whl

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.

Potentially problematic release.


This version of svc-infra might be problematic. Click here for more details.

svc_infra/obs/add.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Callable, Iterable, Optional
3
+ from typing import Any, Callable, Iterable, Optional, Protocol
4
4
 
5
5
  from svc_infra.obs.settings import ObservabilitySettings
6
6
 
@@ -9,12 +9,20 @@ def _want_metrics(cfg: ObservabilitySettings) -> bool:
9
9
  return bool(cfg.METRICS_ENABLED)
10
10
 
11
11
 
12
+ class RouteClassifier(Protocol):
13
+ def __call__(
14
+ self, route_path: str, method: str
15
+ ) -> str: # e.g., returns "public|internal|admin"
16
+ ...
17
+
18
+
12
19
  def add_observability(
13
20
  app: Any | None = None,
14
21
  *,
15
22
  db_engines: Optional[Iterable[Any]] = None,
16
23
  metrics_path: str | None = None,
17
24
  skip_metric_paths: Optional[Iterable[str]] = None,
25
+ route_classifier: RouteClassifier | None = None,
18
26
  ) -> Callable[[], None]:
19
27
  """
20
28
  Enable Prometheus metrics for the ASGI app and optional SQLAlchemy pool metrics.
@@ -25,14 +33,53 @@ def add_observability(
25
33
  # --- Metrics (Prometheus) — import lazily so CLIs/tests don’t require prometheus_client
26
34
  if app is not None and _want_metrics(cfg):
27
35
  try:
28
- from svc_infra.obs.metrics.asgi import add_prometheus # lazy
36
+ from svc_infra.obs.metrics.asgi import ( # lazy
37
+ PrometheusMiddleware,
38
+ add_prometheus,
39
+ metrics_endpoint,
40
+ )
29
41
 
30
42
  path = metrics_path or cfg.METRICS_PATH
31
- add_prometheus(
32
- app,
33
- path=path,
34
- skip_paths=tuple(skip_metric_paths or (path, "/health", "/healthz")),
35
- )
43
+ skip_paths = tuple(skip_metric_paths or (path, "/health", "/healthz"))
44
+ # If a route_classifier is provided, use a custom route_resolver to append class label
45
+ if route_classifier is None:
46
+ add_prometheus(
47
+ app,
48
+ path=path,
49
+ skip_paths=skip_paths,
50
+ )
51
+ else:
52
+ # Install middleware manually to pass route_resolver
53
+ def _resolver(req):
54
+ # Base template
55
+ from svc_infra.obs.metrics.asgi import _route_template # type: ignore
56
+
57
+ base = _route_template(req)
58
+ method = getattr(req, "method", "GET")
59
+ cls = route_classifier(base, method)
60
+ # Encode as base|class for downstream label splitting in dashboards
61
+ return f"{base}|{cls}"
62
+
63
+ app.add_middleware(
64
+ PrometheusMiddleware,
65
+ skip_paths=skip_paths,
66
+ route_resolver=_resolver,
67
+ )
68
+ # Mount /metrics endpoint without re-adding middleware
69
+ try:
70
+ from svc_infra.api.fastapi.dual.public import public_router
71
+ from svc_infra.app.env import CURRENT_ENVIRONMENT, DEV_ENV, LOCAL_ENV
72
+
73
+ router = public_router()
74
+ router.add_api_route(
75
+ path,
76
+ endpoint=metrics_endpoint(),
77
+ include_in_schema=CURRENT_ENVIRONMENT in (LOCAL_ENV, DEV_ENV),
78
+ tags=["observability"],
79
+ )
80
+ app.include_router(router)
81
+ except Exception:
82
+ app.add_route(path, metrics_endpoint())
36
83
  except Exception:
37
84
  pass
38
85
 
@@ -0,0 +1,45 @@
1
+ {
2
+ "title": "Service HTTP Overview",
3
+ "tags": ["svc-infra", "http"],
4
+ "timezone": "browser",
5
+ "panels": [
6
+ {
7
+ "type": "timeseries",
8
+ "title": "Success Rate (5m)",
9
+ "targets": [
10
+ {
11
+ "expr": "sum(rate(http_server_requests_total{code!~\"5..\"}[5m])) / sum(rate(http_server_requests_total[5m]))",
12
+ "legendFormat": "success_rate"
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "type": "timeseries",
18
+ "title": "Latency p99",
19
+ "targets": [
20
+ {
21
+ "expr": "histogram_quantile(0.99, sum(rate(http_server_request_duration_seconds_bucket[5m])) by (le))",
22
+ "legendFormat": "p99"
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "type": "table",
28
+ "title": "Top Routes by Error (5m)",
29
+ "targets": [
30
+ {
31
+ "expr": "topk(10, sum(rate(http_server_requests_total{code=~\"5..\"}[5m])) by (route))",
32
+ "legendFormat": "{{route}}"
33
+ }
34
+ ]
35
+ }
36
+ ],
37
+ "templating": {
38
+ "list": []
39
+ },
40
+ "time": {
41
+ "from": "now-6h",
42
+ "to": "now"
43
+ },
44
+ "refresh": "30s"
45
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: svc-infra
3
- Version: 0.1.603
3
+ Version: 0.1.604
4
4
  Summary: Infrastructure for building and deploying prod-ready services
5
5
  License: MIT
6
6
  Keywords: fastapi,sqlalchemy,alembic,auth,infra,async,pydantic
@@ -91,6 +91,7 @@ svc-infra packages the shared building blocks we use to ship production FastAPI
91
91
  | Jobs | JobQueue, scheduler, CLI worker | [Jobs quickstart](docs/jobs.md) |
92
92
  | Cache | cashews decorators, namespace management, TTL helpers | [Cache guide](docs/cache.md) |
93
93
  | Observability | Prometheus middleware, Grafana automation, OTEL hooks | [Observability guide](docs/observability.md) |
94
+ | Ops | Probes, breaker, SLOs & dashboards | [SLOs & Ops](docs/ops.md) |
94
95
  | Webhooks | Subscription store, signing, retry worker | [Webhooks framework](docs/webhooks.md) |
95
96
  | Security | Password policy, lockout, signed cookies, headers | [Security hardening](docs/security.md) |
96
97
  | Data Lifecycle | Fixtures, retention, erasure, backups | [Data lifecycle](docs/data-lifecycle.md) |
@@ -218,8 +218,9 @@ svc_infra/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
218
  svc_infra/mcp/svc_infra_mcp.py,sha256=NmBY7AM3_pnHAumE-eM5Njr8kpb7Gh1-fjcZAEammiI,1927
219
219
  svc_infra/obs/README.md,sha256=pmd6AyFZW3GCCi0sr3uTHrPj5KgAI8rrXw8QPkrf1R8,8021
220
220
  svc_infra/obs/__init__.py,sha256=t5DgkiuuhHnfAHChzYqCI1-Fpr68iQ0A1nHOLFIlAuM,75
221
- svc_infra/obs/add.py,sha256=j8Nsv6k7mGM7tGFIoCxgSpFNV93G_WmtSbCIBohHRT4,2026
221
+ svc_infra/obs/add.py,sha256=Qa8pswZDxspIn3oniqe8NYeHmVhFwiYOYxF9xNAyCOs,4016
222
222
  svc_infra/obs/cloud_dash.py,sha256=1rg6NO9kjhN3zCugfBqDxkTN5nQqjQRC5ye2gFxb6g4,4329
223
+ svc_infra/obs/grafana/dashboards/http-overview.json,sha256=WVwkMazdfRAAUdLwGItfzePL9sXuDdhlLVbOLtk1Vic,1042
223
224
  svc_infra/obs/metrics/__init__.py,sha256=fSqtl2CNV24j13tihK-bsflt0wSSGK8qjxFKvKWBSfk,1421
224
225
  svc_infra/obs/metrics/asgi.py,sha256=d9qvOdwJmkIHeB_YV_QcqflXYXYWiriffuqBaaLRbCg,8873
225
226
  svc_infra/obs/metrics/base.py,sha256=IHpNJk12whfBEerW9Qkj2fV5Iiw_H-Od4gqPcdOVWNs,2649
@@ -282,7 +283,7 @@ svc_infra/webhooks/fastapi.py,sha256=BCNvGNxukf6dC2a4i-6en-PrjBGV19YvCWOot5lXWsA
282
283
  svc_infra/webhooks/router.py,sha256=6JvAVPMEth_xxHX-IsIOcyMgHX7g1H0OVxVXKLuMp9w,1596
283
284
  svc_infra/webhooks/service.py,sha256=hWgiJRXKBwKunJOx91C7EcLUkotDtD3Xp0RT6vj2IC0,1797
284
285
  svc_infra/webhooks/signing.py,sha256=NCwdZzmravUe7HVIK_uXK0qqf12FG-_MVsgPvOw6lsM,784
285
- svc_infra-0.1.603.dist-info/METADATA,sha256=Y_24yijEGFoG0M5wtJE7EjMaf5BMobwLNwxJoR4ACtk,7941
286
- svc_infra-0.1.603.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
287
- svc_infra-0.1.603.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
288
- svc_infra-0.1.603.dist-info/RECORD,,
286
+ svc_infra-0.1.604.dist-info/METADATA,sha256=MWq8dgYUohrtSAQkZXS6YO0oeH29rMtGqHVlQSOtX9Q,8014
287
+ svc_infra-0.1.604.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
288
+ svc_infra-0.1.604.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
289
+ svc_infra-0.1.604.dist-info/RECORD,,