Repository: prometheus/client_python (python)

Issue:
Pushgateway exposition: properly handle empty label values in grouping key
See https://github.com/prometheus/pushgateway/pull/346 for a documentation update for the PGW about empty label values in the grouping key. (Note: It's really just documenting the current state of the PGW. No change of behavior of the PGW involved.)

The code in `exposition.py` currently accepts empty label values for the grouping key. However, it then attempts to push in an invalid way, see  https://github.com/prometheus/pushgateway/issues/344 for details (the issue demonstrates it with client_golang, but presumably client_python would show exactly the same behavior).

While the use case for empty label values in the grouping key is somewhat esoteric, a case can be made for it. Thus, we should probably support it in the way now [documented](https://github.com/prometheus/pushgateway/pull/346). Alternative is to disallow it in `exposition.py`. Just the current state of pretending it works but then fail to push is clearly a bug.

Pull request description:
Handle empty label values for the pushgateway.
Fixes #546

@beorn7 

Patch:
diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py
index 634066f..191e745 100644
--- a/prometheus_client/exposition.py
+++ b/prometheus_client/exposition.py
@@ -362,7 +362,10 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
 
 
 def _escape_grouping_key(k, v):
-    if '/' in v:
+    if v == "" :
+        # Per https://github.com/prometheus/pushgateway/pull/346.
+        return k + "@base64", "="
+    elif '/' in v:
         # Added in Pushgateway 0.9.0.
         return k + "@base64", base64.urlsafe_b64encode(v.encode("utf-8")).decode("utf-8")
     else:


Test patch:
diff --git a/tests/test_exposition.py b/tests/test_exposition.py
index 21b3f59..25360b3 100644
--- a/tests/test_exposition.py
+++ b/tests/test_exposition.py
@@ -260,6 +260,13 @@ class TestPushGateway(unittest.TestCase):
         self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
         self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')
 
+    def test_push_with_groupingkey_empty_label(self):
+        push_to_gateway(self.address, "my_job", self.registry, {'a': ''})
+        self.assertEqual(self.requests[0][0].command, 'PUT')
+        self.assertEqual(self.requests[0][0].path, '/metrics/job/my_job/a@base64/=')
+        self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
+        self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')
+
     def test_push_with_complex_groupingkey(self):
         push_to_gateway(self.address, "my_job", self.registry, {'a': 9, 'b': 'a/ z'})
         self.assertEqual(self.requests[0][0].command, 'PUT')