From e5cc2834756a5613a6b317f624b6b29fde5618b0 Mon Sep 17 00:00:00 2001 From: Christopher Krah <48338417+chrikrah@users.noreply.github.com> Date: Thu, 22 Oct 2026 04:50:01 +0200 Subject: [PATCH] Reject repeated Enum states Enum(states=['a', 'b', 'a']) exposes e{e="a"} twice in one family. After state('a') the two samples disagree, 1.0 and 0.0, and the OpenMetrics parser keeps only the first. Raise at construction, before registration, as duplicate label names already do. Signed-off-by: Christopher Krah <48338417+chrikrah@users.noreply.github.com> --- prometheus_client/metrics.py | 5 +++++ tests/test_core.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index dfa158b9..f50e48ff 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -1,3 +1,4 @@ +import collections import os from threading import Lock import time @@ -780,6 +781,10 @@ def __init__(self, raise ValueError(f'Overlapping labels for Enum metric: {name}') if not states: raise ValueError(f'No states provided for Enum metric: {name}') + # A repeated state is exposed as two samples with the same labels. + duplicates = sorted(s for s, count in collections.Counter(states).items() if count > 1) + if duplicates: + raise ValueError(f'Duplicate states for Enum metric {name}: {duplicates!r}') super().__init__( name=name, documentation=documentation, diff --git a/tests/test_core.py b/tests/test_core.py index 2c29f79a..3a0c9559 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -596,6 +596,12 @@ def test_overlapping_labels(self): with pytest.raises(ValueError): Enum('e', 'help', registry=None, labelnames=['e']) + def test_duplicate_states(self): + registry = CollectorRegistry() + with self.assertRaisesRegex(ValueError, r"^Duplicate states for Enum metric e: \['a'\]$"): + Enum('e', 'help', states=['a', 'b', 'a'], registry=registry) + self.assertEqual(list(registry.collect()), []) + def test_failed_init_does_not_pollute_registry(self): registry = CollectorRegistry() # A validation failure in __init__ must not leave a half-built collector