From 106ae65d9c480ab328c0eda0e9ce1ae8c0325bdb Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 2 Nov 2021 17:33:19 +0200 Subject: [PATCH 1/4] Drop support for EOL Python 2.7 and 3.4-3.5 Signed-off-by: Hugo van Kemenade --- .circleci/config.yml | 5 --- README.md | 2 +- prometheus_client/__init__.py | 6 +--- prometheus_client/exposition.py | 53 ++++++-------------------------- prometheus_client/metrics.py | 16 +++------- setup.py | 6 +--- tests/openmetrics/test_parser.py | 2 -- tests/test_core.py | 2 -- tests/test_gc_collector.py | 5 ++- tests/test_wsgi.py | 3 -- tox.ini | 24 ++------------- 11 files changed, 22 insertions(+), 102 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b555a052..ced00806 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,9 +68,6 @@ workflows: matrix: parameters: python: - - "2.7" - - "3.4" - - "3.5" - "3.6" - "3.7" - "3.8" @@ -80,11 +77,9 @@ workflows: matrix: parameters: python: - - "2.7" - "3.9" - test_pypy: matrix: parameters: python: - - "2.7" - "3.7" diff --git a/README.md b/README.md index efd74d93..7fbebca5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Prometheus Python Client -The official Python 2 and 3 client for [Prometheus](http://prometheus.io). +The official Python 3 client for [Prometheus](https://prometheus.io). ## Three Step Demo diff --git a/prometheus_client/__init__.py b/prometheus_client/__init__.py index cc8f841a..f2aa98a9 100644 --- a/prometheus_client/__init__.py +++ b/prometheus_client/__init__.py @@ -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 diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py index 0de9558b..d4543023 100644 --- a/prometheus_client/exposition.py +++ b/prometheus_client/exposition.py @@ -2,38 +2,25 @@ 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 of the latest text format""" -PYTHON27_OR_OLDER = sys.version_info < (3, ) PYTHON376_OR_NEWER = sys.version_info > (3, 7, 5) @@ -88,11 +75,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 @@ -273,19 +256,7 @@ def write_to_textfile(path, 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) @@ -495,8 +466,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 diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 399830ae..207eb26f 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -1,4 +1,3 @@ -import sys from threading import Lock import time import types @@ -13,13 +12,6 @@ from .samples import Exemplar from .utils import floatToGoString, INF -if sys.version_info > (3,): - unicode = str - create_bound_method = types.MethodType -else: - def create_bound_method(func, obj): - return types.MethodType(func, obj, obj.__class__) - def _build_full_name(metric_type, name, namespace, subsystem, unit): full_name = '' @@ -173,11 +165,11 @@ def labels(self, *labelvalues, **labelkwargs): if labelkwargs: if sorted(labelkwargs) != sorted(self._labelnames): raise ValueError('Incorrect label names') - labelvalues = tuple(unicode(labelkwargs[l]) for l in self._labelnames) + labelvalues = tuple(str(labelkwargs[l]) for l in self._labelnames) else: if len(labelvalues) != len(self._labelnames): raise ValueError('Incorrect label count') - labelvalues = tuple(unicode(l) for l in labelvalues) + labelvalues = tuple(str(l) for l in labelvalues) with self._lock: if labelvalues not in self._metrics: self._metrics[labelvalues] = self.__class__( @@ -197,7 +189,7 @@ def remove(self, *labelvalues): """Remove the given labelset from the metric.""" if len(labelvalues) != len(self._labelnames): raise ValueError('Incorrect label count (expected %d, got %s)' % (len(self._labelnames), labelvalues)) - labelvalues = tuple(unicode(l) for l in labelvalues) + labelvalues = tuple(str(l) for l in labelvalues) with self._lock: del self._metrics[labelvalues] @@ -419,7 +411,7 @@ def set_function(self, f): def samples(self): return (('', {}, float(f()), None, None),) - self._child_samples = create_bound_method(samples, self) + self._child_samples = types.MethodType(samples, self) def _child_samples(self): return (('', {}, self._value.get(), None, None),) diff --git a/setup.py b/setup.py index 95adc296..fb9710a9 100644 --- a/setup.py +++ b/setup.py @@ -27,18 +27,14 @@ 'twisted': ['twisted'], }, test_suite="tests", - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + python_requires=">=3.6", classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", diff --git a/tests/openmetrics/test_parser.py b/tests/openmetrics/test_parser.py index 8fd2319d..0c699fc6 100644 --- a/tests/openmetrics/test_parser.py +++ b/tests/openmetrics/test_parser.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import math -import sys import unittest from prometheus_client.core import ( @@ -482,7 +481,6 @@ def test_exemplars_with_hash_in_label_values(self): hfm.add_sample("a_bucket", {"le": "+Inf", "foo": "bar # "}, 3.0, None, Exemplar({"a": "d", "foo": "bar # bar"}, 4)) self.assertEqual([hfm], list(families)) - @unittest.skipIf(sys.version_info < (3, 3), "Test requires Python 3.3+.") def test_fallback_to_state_machine_label_parsing(self): from unittest.mock import patch diff --git a/tests/test_core.py b/tests/test_core.py index 733e9dd5..b874f32b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from concurrent.futures import ThreadPoolExecutor -import sys import time import pytest @@ -875,7 +874,6 @@ def test_target_info_restricted_registry(self): m.samples = [Sample('target_info', {'foo': 'bar'}, 1)] self.assertEqual([m], list(registry.restricted_registry(['target_info']).collect())) - @unittest.skipIf(sys.version_info < (3, 3), "Test requires Python 3.3+.") def test_restricted_registry_does_not_call_extra(self): from unittest.mock import MagicMock registry = CollectorRegistry() diff --git a/tests/test_gc_collector.py b/tests/test_gc_collector.py index 00714422..85e5337f 100644 --- a/tests/test_gc_collector.py +++ b/tests/test_gc_collector.py @@ -2,15 +2,14 @@ import gc import platform -import sys import unittest from prometheus_client import CollectorRegistry, GCCollector -SKIP = sys.version_info < (3, 4) or platform.python_implementation() != "CPython" +SKIP = platform.python_implementation() != "CPython" -@unittest.skipIf(SKIP, "Test requires CPython 3.4 +") +@unittest.skipIf(SKIP, "Test requires CPython") class TestGCCollector(unittest.TestCase): def setUp(self): gc.disable() diff --git a/tests/test_wsgi.py b/tests/test_wsgi.py index 7a285a9c..16aa0f08 100644 --- a/tests/test_wsgi.py +++ b/tests/test_wsgi.py @@ -1,7 +1,5 @@ from __future__ import absolute_import, unicode_literals -import sys -import unittest from unittest import TestCase from wsgiref.util import setup_testing_defaults @@ -57,7 +55,6 @@ def test_report_metrics_3(self): def test_report_metrics_4(self): self.validate_metrics("failed_requests", "Number of failed requests", 7) - @unittest.skipIf(sys.version_info < (3, 3), "Test requires Python 3.3+.") def test_favicon_path(self): from unittest.mock import patch diff --git a/tox.ini b/tox.ini index 29845187..9dddf7b3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,38 +1,20 @@ [tox] -envlist = coverage-clean,py2.7,py3.4,py3.5,py3.6,py3.7,py3.8,py3.9,py3.10,pypy2.7,pypy3.7,{py2.7,py3.9}-nooptionals,coverage-report,flake8,isort +envlist = coverage-clean,py3.6,py3.7,py3.8,py3.9,py3.10,pypy3.7,py3.9-nooptionals,coverage-report,flake8,isort [base] deps = coverage pytest -; Python 3.4 fails if using 21.1.0. - attrs!=21.1.0 - -[testenv:py2.7] -deps = - {[base]deps} - futures - -[testenv:pypy2.7] -deps = - {[base]deps} - futures + attrs [testenv] deps = {[base]deps} - {py2.7,py3.7,pypy2.7,pypy3.7}: twisted + {py3.7,pypy3.7}: twisted {py3.7,pypy3.7}: asgiref commands = coverage run --parallel -m pytest {posargs} -; Ensure test suite passes if no optional dependencies are present. -[testenv:py2.7-nooptionals] -deps = - {[base]deps} - futures -commands = coverage run --parallel -m pytest {posargs} - [testenv:py3.9-nooptionals] commands = coverage run --parallel -m pytest {posargs} From 4f6ea76deb622bdbe2e4cca50661e5ec5cb0129c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 2 Nov 2021 17:38:09 +0200 Subject: [PATCH 2/4] Upgrade Python syntax with pyupgrade --py36-plus Signed-off-by: Hugo van Kemenade --- prometheus_client/bridge/graphite.py | 10 +++--- prometheus_client/context_managers.py | 8 ++--- prometheus_client/core.py | 2 -- prometheus_client/decorator.py | 8 ++--- prometheus_client/exposition.py | 40 ++++++++++----------- prometheus_client/gc_collector.py | 4 +-- prometheus_client/metrics.py | 20 +++++------ prometheus_client/metrics_core.py | 4 +-- prometheus_client/mmap_dict.py | 4 +-- prometheus_client/multiprocess.py | 10 +++--- prometheus_client/openmetrics/exposition.py | 23 ++++++------ prometheus_client/openmetrics/parser.py | 14 ++++---- prometheus_client/parser.py | 5 +-- prometheus_client/platform_collector.py | 5 +-- prometheus_client/process_collector.py | 10 +++--- prometheus_client/registry.py | 9 +++-- prometheus_client/samples.py | 8 ++--- prometheus_client/twisted/_exposition.py | 2 -- prometheus_client/utils.py | 4 +-- prometheus_client/values.py | 8 ++--- tests/openmetrics/test_exposition.py | 16 ++++----- tests/openmetrics/test_parser.py | 4 +-- tests/test_asgi.py | 2 -- tests/test_core.py | 9 ++--- tests/test_exposition.py | 16 ++++----- tests/test_gc_collector.py | 2 -- tests/test_multiprocess.py | 14 ++++---- tests/test_parser.py | 6 ++-- tests/test_platform_collector.py | 4 +-- tests/test_process_collector.py | 2 -- tests/test_twisted.py | 4 +-- tests/test_wsgi.py | 2 -- 32 files changed, 112 insertions(+), 167 deletions(-) diff --git a/prometheus_client/bridge/graphite.py b/prometheus_client/bridge/graphite.py index 35ef8493..38bc9416 100755 --- a/prometheus_client/bridge/graphite.py +++ b/prometheus_client/bridge/graphite.py @@ -1,5 +1,4 @@ #!/usr/bin/python -from __future__ import unicode_literals import logging import re @@ -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 @@ -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 @@ -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')) diff --git a/prometheus_client/context_managers.py b/prometheus_client/context_managers.py index 2b271a7c..0704d1e5 100644 --- a/prometheus_client/context_managers.py +++ b/prometheus_client/context_managers.py @@ -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 @@ -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 @@ -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 diff --git a/prometheus_client/core.py b/prometheus_client/core.py index 148573fb..ad3a4542 100644 --- a/prometheus_client/core.py +++ b/prometheus_client/core.py @@ -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, diff --git a/prometheus_client/decorator.py b/prometheus_client/decorator.py index 1ad2c977..d3e2b355 100644 --- a/prometheus_client/decorator.py +++ b/prometheus_client/decorator.py @@ -31,9 +31,9 @@ Decorator module, see http://pypi.python.org/pypi/decorator for the documentation. """ -from __future__ import print_function import collections +from contextlib import _GeneratorContextManager import inspect import itertools import operator @@ -85,7 +85,7 @@ def getargspec(f): # basic functionality -class FunctionMaker(object): +class FunctionMaker: """ An object with the ability to create functions with a given signature. It has attributes name, doc, module, signature, defaults, dict and @@ -181,7 +181,7 @@ def make(self, src_templ, evaldict=None, addsource=False, **attrs): self.shortsignature.split(',')]) for n in names: if n in ('_func_', '_call_'): - raise NameError('%s is overridden in\n%s' % (n, src)) + raise NameError(f'{n} is overridden in\n{src}') if not src.endswith('\n'): # add a newline for old Pythons src += '\n' @@ -367,7 +367,7 @@ def ancestors(*types): n_vas = len(vas) if n_vas > 1: raise RuntimeError( - 'Ambiguous dispatch for %s: %s' % (t, vas)) + f'Ambiguous dispatch for {t}: {vas}') elif n_vas == 1: va, = vas mro = type('t', (t, va), {}).__mro__[1:] diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py index d4543023..edb7ae70 100644 --- a/prometheus_client/exposition.py +++ b/prometheus_client/exposition.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import base64 from contextlib import closing from http.server import BaseHTTPRequestHandler @@ -19,7 +17,7 @@ from .registry import REGISTRY from .utils import floatToGoString -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""" PYTHON376_OR_NEWER = sys.version_info > (3, 7, 5) @@ -85,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): @@ -97,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 @@ -143,7 +141,7 @@ 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: @@ -151,9 +149,8 @@ def sample_line(line): 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(): @@ -175,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: @@ -193,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') @@ -250,7 +247,7 @@ 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)) @@ -270,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 @@ -308,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]) @@ -418,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': @@ -430,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( diff --git a/prometheus_client/gc_collector.py b/prometheus_client/gc_collector.py index 845ed758..0695670e 100644 --- a/prometheus_client/gc_collector.py +++ b/prometheus_client/gc_collector.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import gc import platform @@ -7,7 +5,7 @@ from .registry import REGISTRY -class GCCollector(object): +class GCCollector: """Collector for Garbage collection statistics.""" def __init__(self, registry=REGISTRY): diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 207eb26f..ea797e9d 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -55,7 +55,7 @@ def _validate_exemplar(exemplar): raise ValueError('Exemplar labels have %d UTF-8 characters, exceeding the limit of 128') -class MetricWrapperBase(object): +class MetricWrapperBase: _type = None _reserved_labelnames = () @@ -88,11 +88,11 @@ def collect(self): return [metric] def __str__(self): - return "{0}:{1}".format(self._type, self._name) + return f"{self._type}:{self._name}" def __repr__(self): metric_type = type(self) - return "{0}.{1}({2})".format(metric_type.__module__, metric_type.__name__, self._name) + return f"{metric_type.__module__}.{metric_type.__name__}({self._name})" def __init__(self, name, @@ -154,7 +154,7 @@ def labels(self, *labelvalues, **labelkwargs): raise ValueError('No label names were set when constructing %s' % self) if self._labelvalues: - raise ValueError('%s already has labels set (%s); can not chain calls to .labels()' % ( + raise ValueError('{} already has labels set ({}); can not chain calls to .labels()'.format( self, dict(zip(self._labelnames, self._labelvalues)) )) @@ -344,7 +344,7 @@ def __init__(self, self._multiprocess_mode = multiprocess_mode if multiprocess_mode not in self._MULTIPROC_MODES: raise ValueError('Invalid multiprocess mode: ' + multiprocess_mode) - super(Gauge, self).__init__( + super().__init__( name=name, documentation=documentation, labelnames=labelnames, @@ -537,7 +537,7 @@ def __init__(self, buckets=DEFAULT_BUCKETS, ): self._prepare_buckets(buckets) - super(Histogram, self).__init__( + super().__init__( name=name, documentation=documentation, labelnames=labelnames, @@ -641,7 +641,7 @@ def _metric_init(self): def info(self, val): """Set info metric.""" if self._labelname_set.intersection(val.keys()): - raise ValueError('Overlapping labels for Info metric, metric: %s child: %s' % ( + raise ValueError('Overlapping labels for Info metric, metric: {} child: {}'.format( self._labelnames, val)) with self._lock: self._value = dict(val) @@ -677,7 +677,7 @@ def __init__(self, _labelvalues=None, states=None, ): - super(Enum, self).__init__( + super().__init__( name=name, documentation=documentation, labelnames=labelnames, @@ -688,9 +688,9 @@ def __init__(self, _labelvalues=_labelvalues, ) if name in labelnames: - raise ValueError('Overlapping labels for Enum metric: %s' % (name,)) + raise ValueError(f'Overlapping labels for Enum metric: {name}') if not states: - raise ValueError('No states provided for Enum metric: %s' % (name,)) + raise ValueError(f'No states provided for Enum metric: {name}') self._kwargs['states'] = self._states = states def _metric_init(self): diff --git a/prometheus_client/metrics_core.py b/prometheus_client/metrics_core.py index 4ab2af16..a0408786 100644 --- a/prometheus_client/metrics_core.py +++ b/prometheus_client/metrics_core.py @@ -11,7 +11,7 @@ RESERVED_METRIC_LABEL_NAME_RE = re.compile(r'^__.*$') -class Metric(object): +class Metric: """A single metric family and its samples. This is intended only for internal use by the instrumentation client. @@ -50,7 +50,7 @@ def __eq__(self, other): and self.samples == other.samples) def __repr__(self): - return "Metric(%s, %s, %s, %s, %s)" % ( + return "Metric({}, {}, {}, {}, {})".format( self.name, self.documentation, self.type, diff --git a/prometheus_client/mmap_dict.py b/prometheus_client/mmap_dict.py index 615d0033..e8b2df9e 100644 --- a/prometheus_client/mmap_dict.py +++ b/prometheus_client/mmap_dict.py @@ -45,7 +45,7 @@ def _read_all_values(data, used=0): pos += 8 -class MmapedDict(object): +class MmapedDict: """A dict of doubles, backed by an mmapped file. The file starts with a 4 byte int, indicating how much of it is used. @@ -94,7 +94,7 @@ def _init_value(self, key): encoded = key.encode('utf-8') # Pad to be 8-byte aligned. padded = encoded + (b' ' * (8 - (len(encoded) + 4) % 8)) - value = struct.pack('i{0}sd'.format(len(padded)).encode(), len(encoded), padded, 0.0) + value = struct.pack(f'i{len(padded)}sd'.encode(), len(encoded), padded, 0.0) while self._used + len(value) > self._capacity: self._capacity *= 2 self._f.truncate(self._capacity) diff --git a/prometheus_client/multiprocess.py b/prometheus_client/multiprocess.py index 03e3f4d5..6c953747 100644 --- a/prometheus_client/multiprocess.py +++ b/prometheus_client/multiprocess.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from collections import defaultdict import glob import json @@ -19,7 +17,7 @@ MP_METRIC_HELP = 'Multiprocess metric' -class MultiProcessCollector(object): +class MultiProcessCollector: """Collector for files for multi-process mode.""" def __init__(self, registry, path=None): @@ -97,7 +95,7 @@ def _accumulate_metrics(metrics, accumulate): for s in metric.samples: name, labels, value, timestamp, exemplar = s if metric.type == 'gauge': - without_pid_key = (name, tuple([l for l in labels if l[0] != 'pid'])) + without_pid_key = (name, tuple(l for l in labels if l[0] != 'pid')) if metric._multiprocess_mode == 'min': current = samples_setdefault(without_pid_key, value) if value < current: @@ -158,7 +156,7 @@ def mark_process_dead(pid, path=None): """Do bookkeeping for when one process dies in a multi-process setup.""" if path is None: path = os.environ.get('PROMETHEUS_MULTIPROC_DIR', os.environ.get('prometheus_multiproc_dir')) - for f in glob.glob(os.path.join(path, 'gauge_livesum_{0}.db'.format(pid))): + for f in glob.glob(os.path.join(path, f'gauge_livesum_{pid}.db')): os.remove(f) - for f in glob.glob(os.path.join(path, 'gauge_liveall_{0}.db'.format(pid))): + for f in glob.glob(os.path.join(path, f'gauge_liveall_{pid}.db')): os.remove(f) diff --git a/prometheus_client/openmetrics/exposition.py b/prometheus_client/openmetrics/exposition.py index 56d00b5f..d1c1f062 100644 --- a/prometheus_client/openmetrics/exposition.py +++ b/prometheus_client/openmetrics/exposition.py @@ -1,10 +1,9 @@ #!/usr/bin/python -from __future__ import unicode_literals from ..utils import floatToGoString -CONTENT_TYPE_LATEST = str('application/openmetrics-text; version=0.0.1; charset=utf-8') +CONTENT_TYPE_LATEST = 'application/openmetrics-text; version=0.0.1; charset=utf-8' """Content type of the latest OpenMetrics text format""" @@ -22,34 +21,34 @@ def generate_latest(registry): for metric in registry.collect(): try: mname = metric.name - output.append('# HELP {0} {1}\n'.format( + output.append('# HELP {} {}\n'.format( mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))) - output.append('# TYPE {0} {1}\n'.format(mname, metric.type)) + output.append(f'# TYPE {mname} {metric.type}\n') if metric.unit: - output.append('# UNIT {0} {1}\n'.format(mname, metric.unit)) + output.append(f'# UNIT {mname} {metric.unit}\n') for s in metric.samples: if s.labels: labelstr = '{{{0}}}'.format(','.join( - ['{0}="{1}"'.format( + ['{}="{}"'.format( k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"')) for k, v in sorted(s.labels.items())])) else: labelstr = '' if s.exemplar: if not _is_valid_exemplar_metric(metric, s): - raise ValueError("Metric {0} has exemplars, but is not a histogram bucket or counter".format(metric.name)) + raise ValueError(f"Metric {metric.name} has exemplars, but is not a histogram bucket or counter") labels = '{{{0}}}'.format(','.join( - ['{0}="{1}"'.format( + ['{}="{}"'.format( k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"')) for k, v in sorted(s.exemplar.labels.items())])) if s.exemplar.timestamp is not None: - exemplarstr = ' # {0} {1} {2}'.format( + exemplarstr = ' # {} {} {}'.format( labels, floatToGoString(s.exemplar.value), s.exemplar.timestamp, ) else: - exemplarstr = ' # {0} {1}'.format( + exemplarstr = ' # {} {}'.format( labels, floatToGoString(s.exemplar.value), ) @@ -57,8 +56,8 @@ def generate_latest(registry): exemplarstr = '' timestamp = '' if s.timestamp is not None: - timestamp = ' {0}'.format(s.timestamp) - output.append('{0}{1} {2}{3}{4}\n'.format( + timestamp = f' {s.timestamp}' + output.append('{}{} {}{}{}\n'.format( s.name, labelstr, floatToGoString(s.value), diff --git a/prometheus_client/openmetrics/parser.py b/prometheus_client/openmetrics/parser.py index 4a619390..ce28d00c 100644 --- a/prometheus_client/openmetrics/parser.py +++ b/prometheus_client/openmetrics/parser.py @@ -1,6 +1,5 @@ #!/usr/bin/python -from __future__ import unicode_literals import math import re @@ -21,11 +20,10 @@ def text_string_to_metric_families(text): See text_fd_to_metric_families. """ - for metric_family in text_fd_to_metric_families(StringIO.StringIO(text)): - yield metric_family + yield from text_fd_to_metric_families(StringIO.StringIO(text)) -_CANONICAL_NUMBERS = set([float("inf")]) +_CANONICAL_NUMBERS = {float("inf")} def _isUncanonicalNumber(s): @@ -83,7 +81,7 @@ def _unescape_help(text): def _parse_value(value): value = ''.join(value) if value != value.strip() or '_' in value: - raise ValueError("Invalid value: {0!r}".format(value)) + raise ValueError(f"Invalid value: {value!r}") try: return int(value) except ValueError: @@ -95,7 +93,7 @@ def _parse_timestamp(timestamp): if not timestamp: return None if timestamp != timestamp.strip() or '_' in timestamp: - raise ValueError("Invalid timestamp: {0!r}".format(timestamp)) + raise ValueError(f"Invalid timestamp: {timestamp!r}") try: # Simple int. return Timestamp(int(timestamp), 0) @@ -108,7 +106,7 @@ def _parse_timestamp(timestamp): # Float. ts = float(timestamp) if math.isnan(ts) or math.isinf(ts): - raise ValueError("Invalid timestamp: {0!r}".format(timestamp)) + raise ValueError(f"Invalid timestamp: {timestamp!r}") return ts @@ -359,7 +357,7 @@ def _parse_remaining_text(text): ts = _parse_timestamp(timestamp) exemplar = None if exemplar_labels is not None: - exemplar_length = sum([len(k) + len(v) for k, v in exemplar_labels.items()]) + exemplar_length = sum(len(k) + len(v) for k, v in exemplar_labels.items()) if exemplar_length > 128: raise ValueError("Exmplar labels are too long: " + text) exemplar = Exemplar( diff --git a/prometheus_client/parser.py b/prometheus_client/parser.py index b281a43a..041c6024 100644 --- a/prometheus_client/parser.py +++ b/prometheus_client/parser.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import re from .metrics_core import Metric @@ -17,8 +15,7 @@ def text_string_to_metric_families(text): See text_fd_to_metric_families. """ - for metric_family in text_fd_to_metric_families(StringIO.StringIO(text)): - yield metric_family + yield from text_fd_to_metric_families(StringIO.StringIO(text)) ESCAPE_SEQUENCES = { diff --git a/prometheus_client/platform_collector.py b/prometheus_client/platform_collector.py index 17f53cbf..c57d9fe8 100644 --- a/prometheus_client/platform_collector.py +++ b/prometheus_client/platform_collector.py @@ -1,13 +1,10 @@ -# -*- coding: utf-8 -from __future__ import unicode_literals - import platform as pf from .metrics_core import GaugeMetricFamily from .registry import REGISTRY -class PlatformCollector(object): +class PlatformCollector: """Collector for python platform information""" def __init__(self, registry=REGISTRY, platform=None): diff --git a/prometheus_client/process_collector.py b/prometheus_client/process_collector.py index 12c08f60..e6271ba6 100644 --- a/prometheus_client/process_collector.py +++ b/prometheus_client/process_collector.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import os from .metrics_core import CounterMetricFamily, GaugeMetricFamily @@ -14,7 +12,7 @@ _PAGESIZE = 4096 -class ProcessCollector(object): +class ProcessCollector: """Collector for Standard Exports such as cpu and memory.""" def __init__(self, namespace='', pid=lambda: 'self', proc='/proc', registry=REGISTRY): @@ -37,7 +35,7 @@ def __init__(self, namespace='', pid=lambda: 'self', proc='/proc', registry=REGI self._btime = 0 try: self._btime = self._boot_time() - except IOError: + except OSError: pass if registry: registry.register(self) @@ -73,7 +71,7 @@ def collect(self): 'Total user and system CPU time spent in seconds.', value=utime + stime) result.extend([vmem, rss, start_time, cpu]) - except IOError: + except OSError: pass try: @@ -88,7 +86,7 @@ def collect(self): 'Number of open file descriptors.', len(os.listdir(os.path.join(pid, 'fd')))) result.extend([open_fds, max_fds]) - except (IOError, OSError): + except OSError: pass return result diff --git a/prometheus_client/registry.py b/prometheus_client/registry.py index 67cde45a..fe435cd1 100644 --- a/prometheus_client/registry.py +++ b/prometheus_client/registry.py @@ -4,7 +4,7 @@ from .metrics_core import Metric -class CollectorRegistry(object): +class CollectorRegistry: """Metric collector registry. Collectors must have a no-argument method 'collect' that returns a list of @@ -27,7 +27,7 @@ def register(self, collector): duplicates = set(self._names_to_collectors).intersection(names) if duplicates: raise ValueError( - 'Duplicated timeseries in CollectorRegistry: {0}'.format( + 'Duplicated timeseries in CollectorRegistry: {}'.format( duplicates)) for name in names: self._names_to_collectors[name] = collector @@ -80,8 +80,7 @@ def collect(self): if ti: yield ti for collector in collectors: - for metric in collector.collect(): - yield metric + yield from collector.collect() def restricted_registry(self, names): """Returns object that only collects some metrics. @@ -129,7 +128,7 @@ def get_sample_value(self, name, labels=None): return None -class RestrictedRegistry(object): +class RestrictedRegistry: def __init__(self, names, registry): self._name_set = set(names) self._registry = registry diff --git a/prometheus_client/samples.py b/prometheus_client/samples.py index 9ff8ead8..da93cd52 100644 --- a/prometheus_client/samples.py +++ b/prometheus_client/samples.py @@ -1,22 +1,22 @@ from collections import namedtuple -class Timestamp(object): +class Timestamp: """A nanosecond-resolution timestamp.""" def __init__(self, sec, nsec): if nsec < 0 or nsec >= 1e9: - raise ValueError("Invalid value for nanoseconds in Timestamp: {0}".format(nsec)) + raise ValueError(f"Invalid value for nanoseconds in Timestamp: {nsec}") if sec < 0: nsec = -nsec self.sec = int(sec) self.nsec = int(nsec) def __str__(self): - return "{0}.{1:09d}".format(self.sec, self.nsec) + return f"{self.sec}.{self.nsec:09d}" def __repr__(self): - return "Timestamp({0}, {1})".format(self.sec, self.nsec) + return f"Timestamp({self.sec}, {self.nsec})" def __float__(self): return float(self.sec) + float(self.nsec) / 1e9 diff --git a/prometheus_client/twisted/_exposition.py b/prometheus_client/twisted/_exposition.py index 50cf50ac..202a7d3b 100644 --- a/prometheus_client/twisted/_exposition.py +++ b/prometheus_client/twisted/_exposition.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from twisted.internet import reactor from twisted.web.wsgi import WSGIResource diff --git a/prometheus_client/utils.py b/prometheus_client/utils.py index 1ff77c3c..0d2b0948 100644 --- a/prometheus_client/utils.py +++ b/prometheus_client/utils.py @@ -19,6 +19,6 @@ def floatToGoString(d): # Go switches to exponents sooner than Python. # We only need to care about positive values for le/quantile. if d > 0 and dot > 6: - mantissa = '{0}.{1}{2}'.format(s[0], s[1:dot], s[dot + 1:]).rstrip('0.') - return '{0}e+0{1}'.format(mantissa, dot - 1) + mantissa = f'{s[0]}.{s[1:dot]}{s[dot + 1:]}'.rstrip('0.') + return f'{mantissa}e+0{dot - 1}' return s diff --git a/prometheus_client/values.py b/prometheus_client/values.py index 842837c5..03b203be 100644 --- a/prometheus_client/values.py +++ b/prometheus_client/values.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import os from threading import Lock import warnings @@ -7,7 +5,7 @@ from .mmap_dict import mmap_key, MmapedDict -class MutexValue(object): +class MutexValue: """A float protected by a mutex.""" _multiprocess = False @@ -54,7 +52,7 @@ def MultiProcessValue(process_identifier=os.getpid): # This avoids the need to also have mutexes in __MmapDict. lock = Lock() - class MmapedValue(object): + class MmapedValue: """A float protected by a mutex backed by a per-process mmaped file.""" _multiprocess = True @@ -79,7 +77,7 @@ def __reset(self): if file_prefix not in files: filename = os.path.join( os.environ.get('PROMETHEUS_MULTIPROC_DIR'), - '{0}_{1}.db'.format(file_prefix, pid['value'])) + '{}_{}.db'.format(file_prefix, pid['value'])) files[file_prefix] = MmapedDict(filename) self._file = files[file_prefix] diff --git a/tests/openmetrics/test_exposition.py b/tests/openmetrics/test_exposition.py index 87a382c9..28a90838 100644 --- a/tests/openmetrics/test_exposition.py +++ b/tests/openmetrics/test_exposition.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import time import unittest @@ -24,7 +22,7 @@ def tearDown(self): time.time = self.old_time def custom_collector(self, metric_family): - class CustomCollector(object): + class CustomCollector: def collect(self): return [metric_family] @@ -137,7 +135,7 @@ def test_counter_exemplar(self): """, generate_latest(self.registry)) def test_untyped_exemplar(self): - class MyCollector(object): + class MyCollector: def collect(self): metric = Metric("hh", "help", 'untyped') # This is not sane, but it covers all the cases. @@ -149,7 +147,7 @@ def collect(self): generate_latest(self.registry) def test_histogram_non_bucket_exemplar(self): - class MyCollector(object): + class MyCollector: def collect(self): metric = Metric("hh", "help", 'histogram') # This is not sane, but it covers all the cases. @@ -161,7 +159,7 @@ def collect(self): generate_latest(self.registry) def test_counter_non_total_exemplar(self): - class MyCollector(object): + class MyCollector: def collect(self): metric = Metric("cc", "A counter", 'counter') metric.add_sample("cc_total", {}, 1, None, None) @@ -236,14 +234,14 @@ def test_escaping(self): """, generate_latest(self.registry)) def test_nonnumber(self): - class MyNumber(object): + class MyNumber: def __repr__(self): return "MyNumber(123)" def __float__(self): return 123.0 - class MyCollector(object): + class MyCollector: def collect(self): metric = Metric("nonnumber", "Non number", 'untyped') metric.add_sample("nonnumber", {}, MyNumber()) @@ -254,7 +252,7 @@ def collect(self): generate_latest(self.registry)) def test_timestamp(self): - class MyCollector(object): + class MyCollector: def collect(self): metric = Metric("ts", "help", 'unknown') metric.add_sample("ts", {"foo": "a"}, 0, 123.456) diff --git a/tests/openmetrics/test_parser.py b/tests/openmetrics/test_parser.py index 0c699fc6..3e0b3a66 100644 --- a/tests/openmetrics/test_parser.py +++ b/tests/openmetrics/test_parser.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import math import unittest @@ -605,7 +603,7 @@ def test_roundtrip(self): """ families = list(text_string_to_metric_families(text)) - class TextCollector(object): + class TextCollector: def collect(self): return families diff --git a/tests/test_asgi.py b/tests/test_asgi.py index 139c8717..77278bb6 100644 --- a/tests/test_asgi.py +++ b/tests/test_asgi.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from unittest import skipUnless, TestCase from prometheus_client import CollectorRegistry, Counter diff --git a/tests/test_core.py b/tests/test_core.py index b874f32b..349e4753 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,6 +1,3 @@ -# coding=utf-8 -from __future__ import unicode_literals - from concurrent.futures import ThreadPoolExecutor import time @@ -555,7 +552,7 @@ def test_labels_coerced_to_string(self): self.assertEqual(None, self.registry.get_sample_value('c_total', {'l': 'None'})) def test_non_string_labels_raises(self): - class Test(object): + class Test: __str__ = None self.assertRaises(TypeError, self.counter.labels, Test()) @@ -617,7 +614,7 @@ def setUp(self): self.registry = CollectorRegistry() def custom_collector(self, metric_family): - class CustomCollector(object): + class CustomCollector: def collect(self): return [metric_family] @@ -810,7 +807,7 @@ def test_unregister_works(self): Gauge('s_count', 'help', registry=registry) def custom_collector(self, metric_family, registry): - class CustomCollector(object): + class CustomCollector: def collect(self): return [metric_family] diff --git a/tests/test_exposition.py b/tests/test_exposition.py index c48751ef..a52805ea 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import threading import time import unittest @@ -36,7 +34,7 @@ def tearDown(self): time.time = self.old_time def custom_collector(self, metric_family): - class CustomCollector(object): + class CustomCollector: def collect(self): return [metric_family] @@ -159,14 +157,14 @@ def test_escaping(self): generate_latest(self.registry)) def test_nonnumber(self): - class MyNumber(object): + class MyNumber: def __repr__(self): return "MyNumber(123)" def __float__(self): return 123.0 - class MyCollector(object): + class MyCollector: def collect(self): metric = Metric("nonnumber", "Non number", 'untyped') metric.add_sample("nonnumber", {}, MyNumber()) @@ -177,7 +175,7 @@ def collect(self): generate_latest(self.registry)) def test_timestamp(self): - class MyCollector(object): + class MyCollector: def collect(self): metric = Metric("ts", "help", 'untyped') metric.add_sample("ts", {"foo": "a"}, 0, 123.456) @@ -231,7 +229,7 @@ def do_PUT(self): # which will cause the request handler to return 201. httpd_redirect = HTTPServer(('localhost', 0), TestHandler) self.redirect_address = TestHandler.redirect_address = \ - 'http://localhost:{0}/{1}'.format(httpd_redirect.server_address[1], redirect_flag) + f'http://localhost:{httpd_redirect.server_address[1]}/{redirect_flag}' class TestRedirectServer(threading.Thread): def run(self): @@ -243,7 +241,7 @@ def run(self): # set up the normal server to serve the example requests across test cases. httpd = HTTPServer(('localhost', 0), TestHandler) - self.address = 'http://localhost:{0}'.format(httpd.server_address[1]) + self.address = f'http://localhost:{httpd.server_address[1]}' class TestServer(threading.Thread): def run(self): @@ -383,7 +381,7 @@ def test_metrics_handler(self): self.assertEqual(handler.registry, self.registry) def test_metrics_handler_subclassing(self): - subclass = type(str('MetricsHandlerSubclass'), (MetricsHandler, object), {}) + subclass = type('MetricsHandlerSubclass', (MetricsHandler, object), {}) handler = subclass.factory(self.registry) self.assertTrue(issubclass(handler, (MetricsHandler, subclass))) diff --git a/tests/test_gc_collector.py b/tests/test_gc_collector.py index 85e5337f..59b90580 100644 --- a/tests/test_gc_collector.py +++ b/tests/test_gc_collector.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import gc import platform import unittest diff --git a/tests/test_multiprocess.py b/tests/test_multiprocess.py index 310f8c77..9ec0578f 100644 --- a/tests/test_multiprocess.py +++ b/tests/test_multiprocess.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import glob import os import shutil @@ -193,7 +191,7 @@ def files(): def test_collect(self): pid = 0 values.ValueClass = MultiProcessValue(lambda: pid) - labels = dict((i, i) for i in 'abcd') + labels = {i: i for i in 'abcd'} def add_label(key, value): l = labels.copy() @@ -214,7 +212,7 @@ def add_label(key, value): g.labels(**labels).set(1) h.labels(**labels).observe(5) - metrics = dict((m.name, m) for m in self.collector.collect()) + metrics = {m.name: m for m in self.collector.collect()} self.assertEqual( metrics['c'].samples, [Sample('c_total', labels, 2.0)] @@ -253,7 +251,7 @@ def add_label(key, value): def test_merge_no_accumulate(self): pid = 0 values.ValueClass = MultiProcessValue(lambda: pid) - labels = dict((i, i) for i in 'abcd') + labels = {i: i for i in 'abcd'} def add_label(key, value): l = labels.copy() @@ -267,9 +265,9 @@ def add_label(key, value): path = os.path.join(os.environ['PROMETHEUS_MULTIPROC_DIR'], '*.db') files = glob.glob(path) - metrics = dict( - (m.name, m) for m in self.collector.merge(files, accumulate=False) - ) + metrics = { + m.name: m for m in self.collector.merge(files, accumulate=False) + } metrics['h'].samples.sort( key=lambda x: (x[0], float(x[1].get('le', 0))) diff --git a/tests/test_parser.py b/tests/test_parser.py index 780f0bc7..1d20d276 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import math import unittest @@ -13,7 +11,7 @@ class TestParse(unittest.TestCase): def assertEqualMetrics(self, first, second, msg=None): - super(TestParse, self).assertEqual(first, second, msg) + super().assertEqual(first, second, msg) # Test that samples are actually named tuples of type Sample. for a, b in zip(first, second): @@ -325,7 +323,7 @@ def test_roundtrip(self): """ families = list(text_string_to_metric_families(text)) - class TextCollector(object): + class TextCollector: def collect(self): return families diff --git a/tests/test_platform_collector.py b/tests/test_platform_collector.py index 529d397c..069eb3cc 100644 --- a/tests/test_platform_collector.py +++ b/tests/test_platform_collector.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import unittest from prometheus_client import CollectorRegistry, PlatformCollector @@ -44,7 +42,7 @@ def assertLabels(self, name, labels): assert False -class _MockPlatform(object): +class _MockPlatform: def __init__(self): self._system = "system" diff --git a/tests/test_process_collector.py b/tests/test_process_collector.py index 86f6aa73..81be6a55 100644 --- a/tests/test_process_collector.py +++ b/tests/test_process_collector.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import os import unittest diff --git a/tests/test_twisted.py b/tests/test_twisted.py index ea1796f4..3f72d1b1 100644 --- a/tests/test_twisted.py +++ b/tests/test_twisted.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from unittest import skipUnless from prometheus_client import CollectorRegistry, Counter, generate_latest @@ -39,7 +37,7 @@ def test_reports_metrics(self): agent = Agent(reactor) port = server.getHost().port - url = "http://localhost:{port}/metrics".format(port=port) + url = f"http://localhost:{port}/metrics" d = agent.request(b"GET", url.encode("ascii")) d.addCallback(readBody) diff --git a/tests/test_wsgi.py b/tests/test_wsgi.py index 16aa0f08..050c5add 100644 --- a/tests/test_wsgi.py +++ b/tests/test_wsgi.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from unittest import TestCase from wsgiref.util import setup_testing_defaults From 768e66c0d9b3fb374e282e0931bb8bbd854d28c7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 2 Nov 2021 17:39:50 +0200 Subject: [PATCH 3/4] Universal wheels only for Python 2 and 3 Signed-off-by: Hugo van Kemenade --- prometheus_client/decorator.py | 8 ++++---- setup.py | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/prometheus_client/decorator.py b/prometheus_client/decorator.py index d3e2b355..1ad2c977 100644 --- a/prometheus_client/decorator.py +++ b/prometheus_client/decorator.py @@ -31,9 +31,9 @@ Decorator module, see http://pypi.python.org/pypi/decorator for the documentation. """ +from __future__ import print_function import collections -from contextlib import _GeneratorContextManager import inspect import itertools import operator @@ -85,7 +85,7 @@ def getargspec(f): # basic functionality -class FunctionMaker: +class FunctionMaker(object): """ An object with the ability to create functions with a given signature. It has attributes name, doc, module, signature, defaults, dict and @@ -181,7 +181,7 @@ def make(self, src_templ, evaldict=None, addsource=False, **attrs): self.shortsignature.split(',')]) for n in names: if n in ('_func_', '_call_'): - raise NameError(f'{n} is overridden in\n{src}') + raise NameError('%s is overridden in\n%s' % (n, src)) if not src.endswith('\n'): # add a newline for old Pythons src += '\n' @@ -367,7 +367,7 @@ def ancestors(*types): n_vas = len(vas) if n_vas > 1: raise RuntimeError( - f'Ambiguous dispatch for {t}: {vas}') + 'Ambiguous dispatch for %s: %s' % (t, vas)) elif n_vas == 1: va, = vas mro = type('t', (t, va), {}).__mro__[1:] diff --git a/setup.py b/setup.py index fb9710a9..290e267f 100644 --- a/setup.py +++ b/setup.py @@ -45,5 +45,4 @@ "Topic :: System :: Monitoring", "License :: OSI Approved :: Apache Software License", ], - options={'bdist_wheel': {'universal': '1'}}, ) From 9a4512532ee2eb60edc5544bd131b55da057d389 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 15 Nov 2021 17:38:36 +0200 Subject: [PATCH 4/4] Simplify text Signed-off-by: Hugo van Kemenade --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fbebca5..9582830c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Prometheus Python Client -The official Python 3 client for [Prometheus](https://prometheus.io). +The official Python client for [Prometheus](https://prometheus.io). ## Three Step Demo