Repository: prometheus/client_python
Pull request #439 (2019-07-10 16:09:35)

fix minor typo

brian-brazil: Can you add the DCO?

Patch:
diff --git a/README.md b/README.md
index aeffb06..804cedb 100644
--- a/README.md
+++ b/README.md
@@ -452,7 +452,7 @@ used to predetermine the names of time series a `CollectorRegistry` exposes and
 thus to detect collisions and duplicate registrations.
 
 Usually custom collectors do not have to implement `describe`. If `describe` is
-not implemented and the CollectorRegistry was created with `auto_desribe=True`
+not implemented and the CollectorRegistry was created with `auto_describe=True`
 (which is the case for the default registry) then `collect` will be called at
 registration time instead of `describe`. If this could cause problems, either
 implement a proper `describe`, or if that's not practical have `describe`
diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py
index 6a2db15..9b89c01 100644
--- a/prometheus_client/exposition.py
+++ b/prometheus_client/exposition.py
@@ -343,7 +343,7 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
     gateway_url = urlparse(gateway)
     if not gateway_url.scheme or (PYTHON26_OR_OLDER and gateway_url.scheme not in ['http', 'https']):
         gateway = 'http://{0}'.format(gateway)
-    url = '{0}/metrics/job/{1}'.format(gateway, quote_plus(job))
+    url = '{0}/metrics/{1}/{2}'.format(gateway, *_escape_grouping_key("job", job))
 
     data = b''
     if method != 'DELETE':
@@ -352,7 +352,7 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
     if grouping_key is None:
         grouping_key = {}
     url += ''.join(
-        '/{0}/{1}'.format(quote_plus(str(k)), quote_plus(str(v)))
+        '/{0}/{1}'.format(*_escape_grouping_key(str(k), str(v)))
         for k, v in sorted(grouping_key.items()))
 
     handler(
@@ -361,6 +361,14 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
     )()
 
 
+def _escape_grouping_key(k, v):
+    if '/' in v:
+        # Added in Pushgateway 0.9.0.
+        return k + "@base64", base64.urlsafe_b64encode(v.encode("utf-8")).decode("utf-8")
+    else:
+        return k, quote_plus(v)
+
+
 def instance_ip_grouping_key():
     """Grouping key with instance set to the IP Address of this host."""
     with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as s:


Test patch:
diff --git a/tests/test_exposition.py b/tests/test_exposition.py
index 00f39b4..de116a0 100644
--- a/tests/test_exposition.py
+++ b/tests/test_exposition.py
@@ -239,7 +239,14 @@ class TestPushGateway(unittest.TestCase):
     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')
-        self.assertEqual(self.requests[0][0].path, '/metrics/job/my_job/a/9/b/a%2F+z')
+        self.assertEqual(self.requests[0][0].path, '/metrics/job/my_job/a/9/b@base64/YS8geg==')
+        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_job(self):
+        push_to_gateway(self.address, "my/job", self.registry)
+        self.assertEqual(self.requests[0][0].command, 'PUT')
+        self.assertEqual(self.requests[0][0].path, '/metrics/job@base64/bXkvam9i')
         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')