Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop support for EOL Python 2.7 and 3.4-3.5 #718

Merged
merged 4 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ workflows:
matrix:
parameters:
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- "3.7"
- "3.8"
Expand All @@ -80,11 +77,9 @@ workflows:
matrix:
parameters:
python:
- "2.7"
- "3.9"
- test_pypy:
matrix:
parameters:
python:
- "2.7"
- "3.7"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Prometheus Python Client

The official Python 2 and 3 client for [Prometheus](http://prometheus.io).
The official Python client for [Prometheus](https://prometheus.io).

## Three Step Demo

Expand Down
6 changes: 1 addition & 5 deletions prometheus_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@
generate_latest = exposition.generate_latest
MetricsHandler = exposition.MetricsHandler
make_wsgi_app = exposition.make_wsgi_app
try:
# Python >3.5 only
make_asgi_app = exposition.make_asgi_app
except:
pass
make_asgi_app = exposition.make_asgi_app
start_http_server = exposition.start_http_server
start_wsgi_server = exposition.start_wsgi_server
write_to_textfile = exposition.write_to_textfile
Expand Down
10 changes: 4 additions & 6 deletions prometheus_client/bridge/graphite.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/python
from __future__ import unicode_literals

import logging
import re
Expand All @@ -22,7 +21,7 @@ def _sanitize(s):

class _RegularPush(threading.Thread):
def __init__(self, pusher, interval, prefix):
super(_RegularPush, self).__init__()
super().__init__()
self._pusher = pusher
self._interval = interval
self._prefix = prefix
Expand All @@ -41,11 +40,11 @@ def run(self):
time.sleep(wait_until - now)
try:
self._pusher.push(prefix=self._prefix)
except IOError:
except OSError:
logging.exception("Push failed")


class GraphiteBridge(object):
class GraphiteBridge:
def __init__(self, address, registry=REGISTRY, timeout_seconds=30, _timer=time.time, tags=False):
self._address = address
self._registry = registry
Expand Down Expand Up @@ -76,8 +75,7 @@ def push(self, prefix=''):
for k, v in sorted(s.labels.items())])
else:
labelstr = ''
output.append('{0}{1}{2} {3} {4}\n'.format(
prefixstr, _sanitize(s.name), labelstr, float(s.value), now))
output.append(f'{prefixstr}{_sanitize(s.name)}{labelstr} {float(s.value)} {now}\n')

conn = socket.create_connection(self._address, self._timeout)
conn.sendall(''.join(output).encode('ascii'))
Expand Down
8 changes: 3 additions & 5 deletions prometheus_client/context_managers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from __future__ import unicode_literals

from timeit import default_timer

from .decorator import decorate


class ExceptionCounter(object):
class ExceptionCounter:
def __init__(self, counter, exception):
self._counter = counter
self._exception = exception
Expand All @@ -25,7 +23,7 @@ def wrapped(func, *args, **kwargs):
return decorate(f, wrapped)


class InprogressTracker(object):
class InprogressTracker:
def __init__(self, gauge):
self._gauge = gauge

Expand All @@ -43,7 +41,7 @@ def wrapped(func, *args, **kwargs):
return decorate(f, wrapped)


class Timer(object):
class Timer:
def __init__(self, callback):
self._callback = callback

Expand Down
2 changes: 0 additions & 2 deletions prometheus_client/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import unicode_literals

from .metrics import Counter, Enum, Gauge, Histogram, Info, Summary
from .metrics_core import (
CounterMetricFamily, GaugeHistogramMetricFamily, GaugeMetricFamily,
Expand Down
93 changes: 28 additions & 65 deletions prometheus_client/exposition.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,24 @@
from __future__ import unicode_literals

import base64
from contextlib import closing
from http.server import BaseHTTPRequestHandler
import os
import socket
from socketserver import ThreadingMixIn
import sys
import threading
from urllib.error import HTTPError
from urllib.parse import parse_qs, quote_plus, urlparse
from urllib.request import (
build_opener, HTTPHandler, HTTPRedirectHandler, Request,
)
from wsgiref.simple_server import make_server, WSGIRequestHandler, WSGIServer

from .openmetrics import exposition as openmetrics
from .registry import REGISTRY
from .utils import floatToGoString

try:
from urllib import quote_plus

from BaseHTTPServer import BaseHTTPRequestHandler
from SocketServer import ThreadingMixIn
from urllib2 import (
build_opener, HTTPError, HTTPHandler, HTTPRedirectHandler, Request,
)
from urlparse import parse_qs, urlparse
except ImportError:
# Python 3
from http.server import BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
from urllib.error import HTTPError
from urllib.parse import parse_qs, quote_plus, urlparse
from urllib.request import (
build_opener, HTTPHandler, HTTPRedirectHandler, Request,
)

CONTENT_TYPE_LATEST = str('text/plain; version=0.0.4; charset=utf-8')
CONTENT_TYPE_LATEST = 'text/plain; version=0.0.4; charset=utf-8'
"""Content type of the latest text format"""
PYTHON27_OR_OLDER = sys.version_info < (3, )
PYTHON376_OR_NEWER = sys.version_info > (3, 7, 5)


Expand Down Expand Up @@ -88,11 +73,7 @@ def redirect_request(self, req, fp, code, msg, headers, newurl):
unverifiable=True,
data=req.data,
)
if PYTHON27_OR_OLDER:
# the `method` attribute did not exist for Request in Python 2.7.
new_request.get_method = lambda: m
else:
new_request.method = m
new_request.method = m
return new_request


Expand All @@ -102,7 +83,7 @@ def _bake_output(registry, accept_header, params):
if 'name[]' in params:
registry = registry.restricted_registry(params['name[]'])
output = encoder(registry)
return str('200 OK'), (str('Content-Type'), content_type), output
return '200 OK', ('Content-Type', content_type), output


def make_wsgi_app(registry=REGISTRY):
Expand All @@ -114,8 +95,8 @@ def prometheus_app(environ, start_response):
params = parse_qs(environ.get('QUERY_STRING', ''))
if environ['PATH_INFO'] == '/favicon.ico':
# Serve empty response for browsers
status = str('200 OK')
header = (str(''), str(''))
status = '200 OK'
header = ('', '')
output = b''
else:
# Bake output
Expand Down Expand Up @@ -160,17 +141,16 @@ def generate_latest(registry=REGISTRY):
def sample_line(line):
if line.labels:
labelstr = '{{{0}}}'.format(','.join(
['{0}="{1}"'.format(
['{}="{}"'.format(
k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))
for k, v in sorted(line.labels.items())]))
else:
labelstr = ''
timestamp = ''
if line.timestamp is not None:
# Convert to milliseconds.
timestamp = ' {0:d}'.format(int(float(line.timestamp) * 1000))
return '{0}{1} {2}{3}\n'.format(
line.name, labelstr, floatToGoString(line.value), timestamp)
timestamp = f' {int(float(line.timestamp) * 1000):d}'
return f'{line.name}{labelstr} {floatToGoString(line.value)}{timestamp}\n'

output = []
for metric in registry.collect():
Expand All @@ -192,9 +172,9 @@ def sample_line(line):
elif mtype == 'unknown':
mtype = 'untyped'

output.append('# HELP {0} {1}\n'.format(
output.append('# HELP {} {}\n'.format(
mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n')))
output.append('# TYPE {0} {1}\n'.format(mname, mtype))
output.append(f'# TYPE {mname} {mtype}\n')

om_samples = {}
for s in metric.samples:
Expand All @@ -210,9 +190,9 @@ def sample_line(line):
raise

for suffix, lines in sorted(om_samples.items()):
output.append('# HELP {0}{1} {2}\n'.format(metric.name, suffix,
metric.documentation.replace('\\', r'\\').replace('\n', r'\n')))
output.append('# TYPE {0}{1} gauge\n'.format(metric.name, suffix))
output.append('# HELP {}{} {}\n'.format(metric.name, suffix,
metric.documentation.replace('\\', r'\\').replace('\n', r'\n')))
output.append(f'# TYPE {metric.name}{suffix} gauge\n')
output.extend(lines)
return ''.join(output).encode('utf-8')

Expand Down Expand Up @@ -267,25 +247,13 @@ def write_to_textfile(path, registry):

This is intended for use with the Node exporter textfile collector.
The path must end in .prom for the textfile collector to process it."""
tmppath = '%s.%s.%s' % (path, os.getpid(), threading.current_thread().ident)
tmppath = f'{path}.{os.getpid()}.{threading.current_thread().ident}'
with open(tmppath, 'wb') as f:
f.write(generate_latest(registry))

# rename(2) is atomic but fails on Windows if the destination file exists
if os.name == 'nt':
if sys.version_info <= (3, 3):
# Unable to guarantee atomic rename on Windows and Python<3.3
# Remove and rename instead (risks losing the file)
try:
os.remove(path)
except FileNotFoundError:
pass

os.rename(tmppath, path)
else:
# os.replace is introduced in Python 3.3 but there is some dispute whether
# it is a truly atomic file operation: https://bugs.python.org/issue8828
os.replace(tmppath, path)
os.replace(tmppath, path)
else:
os.rename(tmppath, path)

Expand All @@ -299,8 +267,7 @@ def handle():
request.add_header(k, v)
resp = build_opener(base_handler).open(request, timeout=timeout)
if resp.code >= 400:
raise IOError("error talking to pushgateway: {0} {1}".format(
resp.code, resp.msg))
raise OSError(f"error talking to pushgateway: {resp.code} {resp.msg}")

return handle

Expand Down Expand Up @@ -337,7 +304,7 @@ def handle():
"""Handler that implements HTTP Basic Auth.
"""
if username is not None and password is not None:
auth_value = '{0}:{1}'.format(username, password).encode('utf-8')
auth_value = f'{username}:{password}'.encode()
auth_token = base64.b64encode(auth_value)
auth_header = b'Basic ' + auth_token
headers.append(['Authorization', auth_header])
Expand Down Expand Up @@ -447,10 +414,10 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
PYTHON376_OR_NEWER
and gateway_url.scheme not in ['http', 'https']
):
gateway = 'http://{0}'.format(gateway)
gateway = f'http://{gateway}'

gateway = gateway.rstrip('/')
url = '{0}/metrics/{1}/{2}'.format(gateway, *_escape_grouping_key("job", job))
url = '{}/metrics/{}/{}'.format(gateway, *_escape_grouping_key("job", job))

data = b''
if method != 'DELETE':
Expand All @@ -459,7 +426,7 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
if grouping_key is None:
grouping_key = {}
url += ''.join(
'/{0}/{1}'.format(*_escape_grouping_key(str(k), str(v)))
'/{}/{}'.format(*_escape_grouping_key(str(k), str(v)))
for k, v in sorted(grouping_key.items()))

handler(
Expand Down Expand Up @@ -495,8 +462,4 @@ def instance_ip_grouping_key():
return {'instance': s.getsockname()[0]}


try:
# Python >3.5 only
from .asgi import make_asgi_app # noqa
except:
pass
from .asgi import make_asgi_app # noqa
4 changes: 1 addition & 3 deletions prometheus_client/gc_collector.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from __future__ import unicode_literals

import gc
import platform

from .metrics_core import CounterMetricFamily
from .registry import REGISTRY


class GCCollector(object):
class GCCollector:
"""Collector for Garbage collection statistics."""

def __init__(self, registry=REGISTRY):
Expand Down
Loading