From 6adbad75ed47c88e8e81e7c27a412a5bcd56e535 Mon Sep 17 00:00:00 2001 From: Christopher Krah <48338417+chrikrah@users.noreply.github.com> Date: Mon, 2 Nov 2026 19:29:32 +0100 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 79c3438c..bff73567 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -1,3 +1,4 @@ +import collections import math import os from threading import Lock @@ -787,6 +788,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 2ed2da90..5f4888ab 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -618,6 +618,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