diff --git a/changelog.d/19379.bugfix b/changelog.d/19379.bugfix new file mode 100644 index 0000000000..6de9543bcc --- /dev/null +++ b/changelog.d/19379.bugfix @@ -0,0 +1 @@ +Fix `InFlightGauge` typing to allow upgrading to `prometheus_client` 0.24. diff --git a/scripts-dev/mypy_synapse_plugin.py b/scripts-dev/mypy_synapse_plugin.py index 24794a1925..7fe4d6cd86 100644 --- a/scripts-dev/mypy_synapse_plugin.py +++ b/scripts-dev/mypy_synapse_plugin.py @@ -133,6 +133,7 @@ prometheus_metric_fullname_to_label_arg_map: Mapping[str, ArgLocation | None] = "prometheus_client.metrics.Info": ArgLocation("labelnames", 2), "prometheus_client.metrics.Enum": ArgLocation("labelnames", 2), "synapse.metrics.LaterGauge": ArgLocation("labelnames", 2), + "synapse.metrics._InFlightGaugeRuntime": ArgLocation("labels", 2), "synapse.metrics.InFlightGauge": ArgLocation("labels", 2), "synapse.metrics.GaugeBucketCollector": ArgLocation("labelnames", 2), "prometheus_client.registry.Collector": None, diff --git a/synapse/metrics/__init__.py b/synapse/metrics/__init__.py index b5ad6581e1..ba4334e372 100644 --- a/synapse/metrics/__init__.py +++ b/synapse/metrics/__init__.py @@ -27,6 +27,8 @@ import platform import threading from importlib import metadata from typing import ( + TYPE_CHECKING, + Any, Callable, Generic, Iterable, @@ -262,8 +264,12 @@ shutdown. MetricsEntry = TypeVar("MetricsEntry") -class InFlightGauge(Generic[MetricsEntry], Collector): - """Tracks number of things (e.g. requests, Measure blocks, etc) in flight +class _InFlightGaugeRuntime(Collector): + """ + Runtime class for InFlightGauge. Contains all actual logic. + Does not inherit from Generic to avoid method resolution order (MRO) conflicts. + + Tracks number of things (e.g. requests, Measure blocks, etc) in flight at any given time. Each InFlightGauge will create a metric called `_total` that counts @@ -292,16 +298,20 @@ class InFlightGauge(Generic[MetricsEntry], Collector): # Create a class which have the sub_metrics values as attributes, which # default to 0 on initialization. Used to pass to registered callbacks. - self._metrics_class: type[MetricsEntry] = attr.make_class( + self._metrics_class = attr.make_class( "_MetricsEntry", attrs={x: attr.ib(default=0) for x in sub_metrics}, slots=True, ) # Counts number of in flight blocks for a given set of label values - self._registrations: dict[ - tuple[str, ...], set[Callable[[MetricsEntry], None]] - ] = {} + # `Callable` should be of type `Callable[[MetricsEntry], None]`, but + # `MetricsEntry` has no meaning in this context without the higher level + # `InFlightGauge` typing information. + # Instead, the typing is enforced by having `_registrations` be private and all + # accessor functions have proper `Callable[[MetricsEntry], None]` type + # annotations. + self._registrations: dict[tuple[str, ...], set[Callable[[Any], None]]] = {} # Protects access to _registrations self._lock = threading.Lock() @@ -398,6 +408,17 @@ class InFlightGauge(Generic[MetricsEntry], Collector): yield gauge +if TYPE_CHECKING: + + class InFlightGauge(_InFlightGaugeRuntime, Generic[MetricsEntry]): + """ + Typing-only generic wrapper. + Provides InFlightGauge[T] support to type checkers. + """ +else: + InFlightGauge = _InFlightGaugeRuntime + + class GaugeHistogramMetricFamilyWithLabels(GaugeHistogramMetricFamily): """ Custom version of `GaugeHistogramMetricFamily` from `prometheus_client` that allows diff --git a/synapse/util/metrics.py b/synapse/util/metrics.py index 3daba79124..5e86939e37 100644 --- a/synapse/util/metrics.py +++ b/synapse/util/metrics.py @@ -19,6 +19,13 @@ # # +# These imports are necessary for python <= 3.13 in order for the `InFlightGauge` type +# annotations not to be evaluated at runtime. +# Starting with python 3.14, annotations are lazily evaluated by default, which is the +# behaviour we desire. +# More info here: https://docs.python.org/3/reference/compound_stmts.html#annotations +from __future__ import annotations + import logging from functools import wraps from types import TracebackType diff --git a/tests/metrics/test_metrics.py b/tests/metrics/test_metrics.py index 084eba3a5a..174b165679 100644 --- a/tests/metrics/test_metrics.py +++ b/tests/metrics/test_metrics.py @@ -18,6 +18,14 @@ # [This file includes modifications made by New Vector Limited] # # + +# These imports are necessary for python <= 3.13 in order for the `InFlightGauge` type +# annotations not to be evaluated at runtime. +# Starting with python 3.14, annotations are lazily evaluated by default, which is the +# behaviour we desire. +# More info here: https://docs.python.org/3/reference/compound_stmts.html#annotations +from __future__ import annotations + from typing import NoReturn, Protocol from prometheus_client.core import Sample