diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 4c79c583..e3fe5323 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -769,6 +769,10 @@ def __init__(self, _labelvalues: Optional[Sequence[str]] = None, states: Optional[Sequence[str]] = None, ): + if name in labelnames: + raise ValueError(f'Overlapping labels for Enum metric: {name}') + if not states: + raise ValueError(f'No states provided for Enum metric: {name}') super().__init__( name=name, documentation=documentation, @@ -779,10 +783,6 @@ def __init__(self, registry=registry, _labelvalues=_labelvalues, ) - if name in labelnames: - raise ValueError(f'Overlapping labels for Enum metric: {name}') - if not states: - raise ValueError(f'No states provided for Enum metric: {name}') self._kwargs['states'] = self._states = states def _metric_init(self) -> None: diff --git a/tests/test_core.py b/tests/test_core.py index f339b6df..2d17d5c5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -595,6 +595,19 @@ def test_overlapping_labels(self): with pytest.raises(ValueError): Enum('e', 'help', registry=None, labelnames=['e']) + def test_failed_init_does_not_pollute_registry(self): + registry = CollectorRegistry() + # A validation failure in __init__ must not leave a half-built collector + # registered: otherwise the name stays permanently taken and any later + # scrape of the registry crashes on the missing _states attribute. + with pytest.raises(ValueError): + Enum('task_state', 'help', states=None, registry=registry) + with pytest.raises(ValueError): + Enum('task_state', 'help', states=['a'], labelnames=['task_state'], registry=registry) + # The name is still free, so a correct definition registers and scrapes. + Enum('task_state', 'help', states=['a', 'b'], registry=registry) + self.assertEqual(1, registry.get_sample_value('task_state', {'task_state': 'a'})) + class TestMetricWrapper(unittest.TestCase): def setUp(self):