Skip to content

Commit 9e66c01

Browse files
committed
Add the ability to specify labels as a dict of labelname->labelvalue.
This is useful for code that knows some of the labels in different places. An example use case is a helper I'm trying to write that would work as such: c = Counter('http_exceptions_total', 'help', ['method', 'exception_type']) def handle_request(request): with count_exceptions_by_type(c, {'method': request.method}): do_something(request) This helper only knows about the exception_type label and only passes the other labels through.
1 parent 964fcce commit 9e66c01

File tree

3 files changed

+43
-5
lines changed

3 files changed

+43
-5
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,15 @@ c.labels('get', '/').inc()
183183
c.labels('post', '/submit').inc()
184184
```
185185

186+
Labels can also be provided as a dict:
187+
188+
```python
189+
from prometheus_client import Counter
190+
c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint'])
191+
c.labels({'method': 'get', 'endpoint': '/'}).inc()
192+
c.labels({'method': 'post', 'endpoint': '/submit'}).inc()
193+
```
194+
186195
### Process Collector
187196

188197
The Python Client automatically exports metrics about process CPU usage, RAM,

prometheus_client/__init__.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,23 @@ def __init__(self, wrappedClass, labelnames, **kwargs):
111111
raise ValueError('Invalid label metric name: ' + l)
112112

113113
def labels(self, *labelvalues):
114-
'''Return the child for the given labelset.'''
115-
if len(labelvalues) != len(self._labelnames):
116-
raise ValueError('Incorrect label count')
117-
labelvalues = tuple([unicode(l) for l in labelvalues])
114+
'''Return the child for the given labelset.
115+
116+
Labels can be provided as a tuple or as a dict:
117+
c = Counter('c', 'counter', ['l', 'm'])
118+
# Set labels by position
119+
c.labels('0', '1').inc()
120+
# Set labels by name
121+
c.labels({'l': '0', 'm': '1'}).inc()
122+
'''
123+
if len(labelvalues) == 1 and type(labelvalues[0]) == dict:
124+
if sorted(labelvalues[0].keys()) != sorted(self._labelnames):
125+
raise ValueError('Incorrect label names')
126+
labelvalues = tuple([unicode(labelvalues[0][l]) for l in self._labelnames])
127+
else:
128+
if len(labelvalues) != len(self._labelnames):
129+
raise ValueError('Incorrect label count')
130+
labelvalues = tuple([unicode(l) for l in labelvalues])
118131
with self._lock:
119132
if labelvalues not in self._metrics:
120133
self._metrics[labelvalues] = self._wrappedClass(**self._kwargs)

tests/test_client.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,8 @@ def test_incorrect_label_count_raises(self):
234234

235235
def test_labels_coerced_to_string(self):
236236
self.counter.labels(None).inc()
237-
self.assertEqual(1, self.registry.get_sample_value('c', {'l': 'None'}))
237+
self.counter.labels({'l': None}).inc()
238+
self.assertEqual(2, self.registry.get_sample_value('c', {'l': 'None'}))
238239

239240
self.counter.remove(None)
240241
self.assertEqual(None, self.registry.get_sample_value('c', {'l': 'None'}))
@@ -243,12 +244,27 @@ def test_non_string_labels_raises(self):
243244
class Test(object):
244245
__str__ = None
245246
self.assertRaises(TypeError, self.counter.labels, Test())
247+
self.assertRaises(TypeError, self.counter.labels, {'l': Test()})
246248

247249
def test_namespace_subsystem_concatenated(self):
248250
c = Counter('c', 'help', namespace='a', subsystem='b', registry=self.registry)
249251
c.inc()
250252
self.assertEqual(1, self.registry.get_sample_value('a_b_c'))
251253

254+
def test_labels_by_dict(self):
255+
self.counter.labels({'l': 'x'}).inc()
256+
self.assertEqual(1, self.registry.get_sample_value('c', {'l': 'x'}))
257+
self.assertRaises(ValueError, self.counter.labels, {'l': 'x', 'm': 'y'})
258+
self.assertRaises(ValueError, self.counter.labels, {'m': 'y'})
259+
self.assertRaises(ValueError, self.counter.labels, {})
260+
self.two_labels.labels({'a': 'x', 'b': 'y'}).inc()
261+
self.assertEqual(1, self.registry.get_sample_value('two', {'a': 'x', 'b': 'y'}))
262+
self.assertRaises(ValueError, self.two_labels.labels, {'a': 'x', 'b': 'y', 'c': 'z'})
263+
self.assertRaises(ValueError, self.two_labels.labels, {'a': 'x', 'c': 'z'})
264+
self.assertRaises(ValueError, self.two_labels.labels, {'b': 'y', 'c': 'z'})
265+
self.assertRaises(ValueError, self.two_labels.labels, {'c': 'z'})
266+
self.assertRaises(ValueError, self.two_labels.labels, {})
267+
252268
def test_invalid_names_raise(self):
253269
self.assertRaises(ValueError, Counter, '', 'help')
254270
self.assertRaises(ValueError, Counter, '^', 'help')

0 commit comments

Comments
 (0)