Repository: prometheus/client_python
Pull request #497 (2019-12-22 21:30:08)

Bugfix urlparse in python >= 3.7.6
In Python 3.7.6, a patch has been released that changes the behavior of `urllib.parse.urlparse`. This causes an error when pushing to a gateway without a scheme specified in the url string, but with a port. 

Patch details here: https://bugs.python.org/issue27657

Proof of failure on my local branch:

```
client_python gmoss$ python3 --version
Python 3.7.6



client_python gmoss$ git diff
diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py
index 4cac223..5c2b8b1 100644
--- a/prometheus_client/exposition.py
+++ b/prometheus_client/exposition.py
@@ -30,7 +30,7 @@ CONTENT_TYPE_LATEST = str('text/plain; version=0.0.4; charset=utf-8')
 
 PYTHON26_OR_OLDER = sys.version_info < (2, 7)
 # Due to https://bugs.python.org/issue27657:
-PYTHON376_OR_NEWER = sys.version_info > (3, 7, 5)
+PYTHON376_OR_NEWER = False  # sys.version_info > (3, 7, 5)
 
 def make_wsgi_app(registry=REGISTRY):
     """Create a WSGI app which serves the metrics from a registry."""



client_python gmoss$ python3 -m pytest tests/test_exposition.py::TestPushGateway::test_push_schemeless_url
======================================================================================================== test session starts ========================================================================================================
platform darwin -- Python 3.7.6, pytest-5.3.2, py-1.8.0, pluggy-0.13.1
rootdir: /Users/gmoss/Documents/constructor/client_python
collected 1 item                                                                                                                                                                                                                    

tests/test_exposition.py F                                                                                                                                                                                                    [100%]

============================================================================================================= FAILURES ==============================================================================================================
_____________________________________________________________________________________________ TestPushGateway.test_push_schemeless_url ______________________________________________________________________________________________

self = <tests.test_exposition.TestPushGateway testMethod=test_push_schemeless_url>

    def test_push_schemeless_url(self):
>       push_to_gateway(self.address.replace('http://', ''), "my_job", self.registry)

tests/test_exposition.py:239: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
prometheus_client/exposition.py:290: in push_to_gateway
    _use_gateway('PUT', gateway, job, registry, grouping_key, timeout, handler)
prometheus_client/exposition.py:364: in _use_gateway
    headers=[('Content-Type', CONTENT_TYPE_LATEST)], data=data,
prometheus_client/exposition.py:222: in handle
    resp = build_opener(HTTPHandler).open(request, timeout=timeout)
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py:525: in open
    response = self._open(req, data)
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py:548: in _open
    'unknown_open', req)
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py:503: in _call_chain
    result = func(*args)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <urllib.request.UnknownHandler object at 0x1022c35d0>, req = <urllib.request.Request object at 0x1022c3310>

    def unknown_open(self, req):
        type = req.type
>       raise URLError('unknown url type: %s' % type)
E       urllib.error.URLError: <urlopen error unknown url type: localhost>

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py:1389: URLError
========================================================================================================= 1 failed in 0.32s =========================================================================================================
```

gmossessian: Proof of failure on my local branch:

```
ip-192-168-1-152:client_python gmoss$ python3 --version
Python 3.7.6
ip-192-168-1-152:client_python gmoss$ git diff
diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py
index 4cac223..5c2b8b1 100644
--- a/prometheus_client/exposition.py
+++ b/prometheus_client/exposition.py
@@ -30,7 +30,7 @@ CONTENT_TYPE_LATEST = str('text/plain; version=0.0.4; charset=utf-8')
 
 PYTHON26_OR_OLDER = sys.version_info < (2, 7)
 # Due to https://bugs.python.org/issue27657:
-PYTHON376_OR_NEWER = sys.version_info > (3, 7, 5)
+PYTHON376_OR_NEWER = False  # sys.version_info > (3, 7, 5)
 
 def make_wsgi_app(registry=REGISTRY):
     """Create a WSGI app which serves the metrics from a registry."""
ip-192-168-1-152:client_python gmoss$ python3 -m pytest tests/test_exposition.py::TestPushGateway::test_push_schemeless_url
======================================================================================================== test session starts ========================================================================================================
platform darwin -- Python 3.7.6, pytest-5.3.2, py-1.8.0, pluggy-0.13.1
rootdir: /Users/gmoss/Documents/constructor/client_python
collected 1 item                                                                                                                                                                                                                    

tests/test_exposition.py F                                                                                                                                                                                                    [100%]

============================================================================================================= FAILURES ==============================================================================================================
_____________________________________________________________________________________________ TestPushGateway.test_push_schemeless_url ______________________________________________________________________________________________

self = <tests.test_exposition.TestPushGateway testMethod=test_push_schemeless_url>

    def test_push_schemeless_url(self):
>       push_to_gateway(self.address.replace('http://', ''), "my_job", self.registry)

tests/test_exposition.py:239: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
prometheus_client/exposition.py:290: in push_to_gateway
    _use_gateway('PUT', gateway, job, registry, grouping_key, timeout, handler)
prometheus_client/exposition.py:364: in _use_gateway
    headers=[('Content-Type', CONTENT_TYPE_LATEST)], data=data,
prometheus_client/exposition.py:222: in handle
    resp = build_opener(HTTPHandler).open(request, timeout=timeout)
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py:525: in open
    response = self._open(req, data)
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py:548: in _open
    'unknown_open', req)
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py:503: in _call_chain
    result = func(*args)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <urllib.request.UnknownHandler object at 0x1022c35d0>, req = <urllib.request.Request object at 0x1022c3310>

    def unknown_open(self, req):
        type = req.type
>       raise URLError('unknown url type: %s' % type)
E       urllib.error.URLError: <urlopen error unknown url type: localhost>

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py:1389: URLError
========================================================================================================= 1 failed in 0.32s =========================================================================================================
```

Patch:
diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py
index 221cbed..6911ba7 100644
--- a/prometheus_client/exposition.py
+++ b/prometheus_client/exposition.py
@@ -29,7 +29,7 @@ CONTENT_TYPE_LATEST = str('text/plain; version=0.0.4; charset=utf-8')
 """Content type of the latest text format"""
 
 PYTHON26_OR_OLDER = sys.version_info < (2, 7)
-
+PYTHON376_OR_NEWER = sys.version_info > (3, 7, 5)
 
 def make_wsgi_app(registry=REGISTRY):
     """Create a WSGI app which serves the metrics from a registry."""
@@ -341,7 +341,11 @@ def delete_from_gateway(
 
 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']):
+    # See https://bugs.python.org/issue27657 for details on urlparse in py>=3.7.6.
+    if not gateway_url.scheme or (
+            (PYTHON376_OR_NEWER or PYTHON26_OR_OLDER)
+            and gateway_url.scheme not in ['http', 'https']
+    ):
         gateway = 'http://{0}'.format(gateway)
     url = '{0}/metrics/{1}/{2}'.format(gateway, *_escape_grouping_key("job", job))
 


Test patch:
diff --git a/tests/test_exposition.py b/tests/test_exposition.py
index db73e23..47c200f 100644
--- a/tests/test_exposition.py
+++ b/tests/test_exposition.py
@@ -235,6 +235,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_schemeless_url(self):
+        push_to_gateway(self.address.replace('http://', ''), "my_job", self.registry)
+        self.assertEqual(self.requests[0][0].command, 'PUT')
+        self.assertEqual(self.requests[0][0].path, '/metrics/job/my_job')
+        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(self):
         push_to_gateway(self.address, "my_job", self.registry, {'a': 9})
         self.assertEqual(self.requests[0][0].command, 'PUT')