unique_toolkit 2026.32.0.dev4__py3-none-any.whl → 2026.32.0.dev5__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.
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import time
4
+ from collections.abc import Sequence
4
5
 
5
6
  from prometheus_client import ( # pyright: ignore[reportMissingImports]
6
7
  Counter,
@@ -16,12 +17,6 @@ _http_requests_total = Counter(
16
17
  ["method", "path", "status_code"],
17
18
  registry=REGISTRY,
18
19
  )
19
- _http_request_duration_seconds = Histogram(
20
- "python_http_request_duration_seconds",
21
- "HTTP request duration",
22
- ["method", "path"],
23
- registry=REGISTRY,
24
- )
25
20
  _http_requests_in_progress = Gauge(
26
21
  "python_http_requests_in_progress",
27
22
  "In-flight HTTP requests",
@@ -30,6 +25,35 @@ _http_requests_in_progress = Gauge(
30
25
  multiprocess_mode="livesum",
31
26
  )
32
27
 
28
+ # Created lazily on first MetricsMiddleware construction so services can choose the
29
+ # bucket layout. Prometheus allows a metric name to be registered only once per
30
+ # registry, so the buckets passed to the first middleware win for the process.
31
+ _http_request_duration_seconds: Histogram | None = None
32
+
33
+
34
+ def _get_or_create_duration_histogram(
35
+ buckets: Sequence[float] | None,
36
+ ) -> Histogram:
37
+ global _http_request_duration_seconds
38
+ if _http_request_duration_seconds is None:
39
+ if buckets is None:
40
+ _http_request_duration_seconds = Histogram(
41
+ "python_http_request_duration_seconds",
42
+ "HTTP request duration",
43
+ ["method", "path"],
44
+ registry=REGISTRY,
45
+ )
46
+ else:
47
+ _http_request_duration_seconds = Histogram(
48
+ "python_http_request_duration_seconds",
49
+ "HTTP request duration",
50
+ ["method", "path"],
51
+ registry=REGISTRY,
52
+ buckets=tuple(buckets),
53
+ )
54
+ return _http_request_duration_seconds
55
+
56
+
33
57
  _DEFAULT_EXCLUDED_PATHS = frozenset({"/health", "/metrics", "/"})
34
58
 
35
59
 
@@ -51,13 +75,29 @@ class MetricsMiddleware:
51
75
 
52
76
  # Quart
53
77
  app.asgi_app = MetricsMiddleware(app.asgi_app)
78
+
79
+ ``duration_buckets`` overrides the latency histogram buckets (seconds). Without
80
+ it, prometheus_client's defaults apply and their top finite bucket is 10s —
81
+ histogram_quantile cannot resolve past the largest finite bucket, so services
82
+ with slower endpoints should pass buckets covering their full latency range::
83
+
84
+ app.add_middleware(MetricsMiddleware, duration_buckets=(1, 5, 30, 60, 120))
85
+
86
+ The histogram is registered once per process; the buckets of the first
87
+ constructed middleware apply.
54
88
  """
55
89
 
56
- def __init__(self, app, excluded_paths: set[str] | None = None):
90
+ def __init__(
91
+ self,
92
+ app,
93
+ excluded_paths: set[str] | None = None,
94
+ duration_buckets: Sequence[float] | None = None,
95
+ ):
57
96
  self.app = app
58
97
  self._excluded_paths = (
59
98
  excluded_paths if excluded_paths is not None else _DEFAULT_EXCLUDED_PATHS
60
99
  )
100
+ self._duration = _get_or_create_duration_histogram(duration_buckets)
61
101
 
62
102
  async def __call__(self, scope, receive, send):
63
103
  # Pass through non-HTTP (WebSocket, lifespan)
@@ -87,9 +127,7 @@ class MetricsMiddleware:
87
127
  finally:
88
128
  duration = time.perf_counter() - start
89
129
  _http_requests_in_progress.labels(method=method).dec()
90
- _http_request_duration_seconds.labels(method=method, path=path).observe(
91
- duration
92
- )
130
+ self._duration.labels(method=method, path=path).observe(duration)
93
131
  _http_requests_total.labels(
94
132
  method=method, path=path, status_code=status_code
95
133
  ).inc()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-toolkit
3
- Version: 2026.32.0.dev4
3
+ Version: 2026.32.0.dev5
4
4
  Summary:
5
5
  Author: Cedric Klinkert, Martin Fadler, Sadique Sheik, Fabian Schläpfer, Pascal Hauri, Ejiroghene Oki-Peter, Rami Azouz, Ricardo Visini, Sanzio Monti
6
6
  Author-email: Cedric Klinkert <cedric.klinkert@unique.ch>, Martin Fadler <martin.fadler@unique.ch>, Sadique Sheik <sadique@unique.ch>, Fabian Schläpfer <fabian@unique.ch>, Pascal Hauri <pascal@unique.ch>, Ejiroghene Oki-Peter <oki-peter.ejiroghene@unique.ai>, Rami Azouz <rami.ext@unique.ch>, Ricardo Visini <ricardo.visini@unique.ch>, Sanzio Monti <sanzio.monti@unique.ai>
@@ -16,7 +16,7 @@ Requires-Dist: sseclient>=0.0.27,<0.0.28
16
16
  Requires-Dist: openai>=1.105.0,<3
17
17
  Requires-Dist: platformdirs>=4.0.0,<5
18
18
  Requires-Dist: pillow>=12.1.1
19
- Requires-Dist: unique-sdk>=2026.32.0.dev5,<2026.32.0rc0
19
+ Requires-Dist: unique-sdk>=2026.32.0.dev8,<2026.32.0rc0
20
20
  Requires-Dist: jambo>=0.1.2,<0.2
21
21
  Requires-Dist: jinja2>=3.1.6,<4
22
22
  Requires-Dist: docxtpl>=0.20.1,<0.21
@@ -403,7 +403,7 @@ unique_toolkit/language_model/stream_transform.py,sha256=iNNGpLY_7G6Bpp9Y4KtbIkQ
403
403
  unique_toolkit/language_model/utils.py,sha256=bPQ4l6_YO71w-zaIPanUUmtbXC1_hCvLK0tAFc3VCRc,1902
404
404
  unique_toolkit/monitoring/__init__.py,sha256=z1gkOCLxGcg936MFuLSeWFa0tREcBFSUbV09NxilBw4,1065
405
405
  unique_toolkit/monitoring/gunicorn.py,sha256=urTFFS5zYHKoxb1TEQHcLqb2byi-aYgFRHGwgYRzYYI,844
406
- unique_toolkit/monitoring/middleware.py,sha256=JNh5v_INAkexuLtXhCG9n5n2t6Pnf8ZdPTnimAVS0_s,2931
406
+ unique_toolkit/monitoring/middleware.py,sha256=MNsUuKgt74mSnUwttXTB_AxX546Pz2zKoAP9OewVrwQ,4531
407
407
  unique_toolkit/monitoring/registry.py,sha256=De2Bx5QBBRJQY2MM3xtntiJ2JU2FoKkVT7rNMG7ewgA,5874
408
408
  unique_toolkit/protocols/support.py,sha256=x5edzbBmKAUuvm3oLFh52gkgQsqR5LU7M0t8RHK0DZk,5161
409
409
  unique_toolkit/services/__init__.py,sha256=b9Wl7Wvz1JmZDbDexIZzBoXZ0wscXuczGAH8LoL-Qak,572
@@ -418,6 +418,6 @@ unique_toolkit/short_term_memory/service.py,sha256=2JMBEBn6GoyK-a0xvdcWpPft-3C74
418
418
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
419
419
  unique_toolkit/smart_rules/compile.py,sha256=vRe0aMJWuVIK-VsqomgxQImzke7MDmwgSxxJfYCa7zw,1386
420
420
  unique_toolkit/test_utilities/events.py,sha256=FLrhMh0A_mQutAUjoD0FdMcVQ4Gq31r35OVGzRjvLro,6760
421
- unique_toolkit-2026.32.0.dev4.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
422
- unique_toolkit-2026.32.0.dev4.dist-info/METADATA,sha256=INpTCsVe9pxypJm__jhb4ItWNy616eN71Bn04P9Y6WU,7465
423
- unique_toolkit-2026.32.0.dev4.dist-info/RECORD,,
421
+ unique_toolkit-2026.32.0.dev5.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
422
+ unique_toolkit-2026.32.0.dev5.dist-info/METADATA,sha256=1bEknHHM6C2M0YX9ZPdElqUVEOA1AnI_u-_zmXVKrQM,7465
423
+ unique_toolkit-2026.32.0.dev5.dist-info/RECORD,,