From 83d3d78874a5a6844fc78041d4c91fa8d5158a47 Mon Sep 17 00:00:00 2001 From: June Kim Date: Sat, 9 May 2026 10:06:58 -0700 Subject: [PATCH 1/2] fix: raise ValueError when clear() called on metric without labels (#1140) Metrics without labels never initialize _lock or _metrics (only parent metrics with labelnames do). Calling clear() on such a metric raised an unhelpful AttributeError. Guard with the same ValueError pattern that remove() already uses, matching maintainer guidance that clear() is not intended for label-less metrics. Fixes #1140. Also addresses duplicate #949. --- prometheus_client/metrics.py | 2 ++ tests/test_core.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 4c53b26b..73469eaf 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -254,6 +254,8 @@ def remove_by_labels(self, labels: dict[str, str]) -> None: def clear(self) -> None: """Remove all labelsets from the metric""" + if not self._labelnames: + raise ValueError('No label names were set when constructing %s' % self) if 'prometheus_multiproc_dir' in os.environ or 'PROMETHEUS_MULTIPROC_DIR' in os.environ: warnings.warn( "Clearing labels has not been implemented in multi-process mode yet", diff --git a/tests/test_core.py b/tests/test_core.py index cee4bfb0..82b04794 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -620,6 +620,12 @@ def test_clear(self): self.assertEqual(None, self.registry.get_sample_value('c_total', {'l': 'x'})) self.assertEqual(None, self.registry.get_sample_value('c_total', {'l': 'y'})) + def test_clear_no_labels_raises(self): + """clear() on a metric without labels should raise ValueError, not AttributeError.""" + no_labels = Counter('c_no_labels', 'help', registry=self.registry) + no_labels.inc() + self.assertRaises(ValueError, no_labels.clear) + def test_incorrect_label_count_raises(self): self.assertRaises(ValueError, self.counter.labels) self.assertRaises(ValueError, self.counter.labels, 'a', 'b') From 24409dcef8e9b885bec44842742c19cc7446d36a Mon Sep 17 00:00:00 2001 From: June Kim Date: Sat, 9 May 2026 10:15:46 -0700 Subject: [PATCH 2/2] test: add remove() no-labels guard test per codex review Codex flagged that parallel tests for remove() on label-less metrics strengthen the fix. Also removed unnecessary test docstring. --- tests/test_core.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index 82b04794..049ab3c0 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -621,11 +621,15 @@ def test_clear(self): self.assertEqual(None, self.registry.get_sample_value('c_total', {'l': 'y'})) def test_clear_no_labels_raises(self): - """clear() on a metric without labels should raise ValueError, not AttributeError.""" no_labels = Counter('c_no_labels', 'help', registry=self.registry) no_labels.inc() self.assertRaises(ValueError, no_labels.clear) + def test_remove_no_labels_raises(self): + no_labels = Counter('c_no_labels2', 'help', registry=self.registry) + no_labels.inc() + self.assertRaises(ValueError, no_labels.remove, 'x') + def test_incorrect_label_count_raises(self): self.assertRaises(ValueError, self.counter.labels) self.assertRaises(ValueError, self.counter.labels, 'a', 'b')