From f63020dee5e59479753bebafe9d56792121129dc Mon Sep 17 00:00:00 2001 From: Yury Pliner Date: Tue, 8 Feb 2022 00:20:16 +0000 Subject: [PATCH 1/7] Use functools.wraps instead of decorator Signed-off-by: Yury Pliner Signed-off-by: Iurii Pliner --- prometheus_client/context_managers.py | 27 +- prometheus_client/decorator.py | 427 -------------------------- tests/test_core.py | 26 +- 3 files changed, 26 insertions(+), 454 deletions(-) delete mode 100644 prometheus_client/decorator.py diff --git a/prometheus_client/context_managers.py b/prometheus_client/context_managers.py index 3988ec22..0fc545f9 100644 --- a/prometheus_client/context_managers.py +++ b/prometheus_client/context_managers.py @@ -1,3 +1,4 @@ +import functools from timeit import default_timer from types import TracebackType from typing import ( @@ -5,8 +6,6 @@ Union, ) -from .decorator import decorate - if TYPE_CHECKING: from . import Counter F = TypeVar("F", bound=Callable[..., Any]) @@ -26,11 +25,11 @@ def __exit__(self, typ: Optional[Type[BaseException]], value: Optional[BaseExcep return False def __call__(self, f: "F") -> "F": - def wrapped(func, *args, **kwargs): + @functools.wraps(f) + def wrapped(*args: Any, **kwargs: Any) -> Any: with self: - return func(*args, **kwargs) - - return decorate(f, wrapped) + return f(*args, **kwargs) + return wrapped class InprogressTracker: @@ -44,11 +43,11 @@ def __exit__(self, typ, value, traceback): self._gauge.dec() def __call__(self, f: "F") -> "F": - def wrapped(func, *args, **kwargs): + @functools.wraps(f) + def wrapped(*args: Any, **kwargs: Any) -> Any: with self: - return func(*args, **kwargs) - - return decorate(f, wrapped) + return f(*args, **kwargs) + return wrapped class Timer: @@ -73,10 +72,10 @@ def labels(self, *args, **kw): self._metric = self._metric.labels(*args, **kw) def __call__(self, f: "F") -> "F": - def wrapped(func, *args, **kwargs): + @functools.wraps(f) + def wrapped(*args: Any, **kwargs: Any) -> Any: # Obtaining new instance of timer every time # ensures thread safety and reentrancy. with self._new_timer(): - return func(*args, **kwargs) - - return decorate(f, wrapped) + return f(*args, **kwargs) + return wrapped diff --git a/prometheus_client/decorator.py b/prometheus_client/decorator.py deleted file mode 100644 index 1ad2c977..00000000 --- a/prometheus_client/decorator.py +++ /dev/null @@ -1,427 +0,0 @@ -# ######################### LICENSE ############################ # - -# Copyright (c) 2005-2016, Michele Simionato -# All rights reserved. - -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: - -# Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# Redistributions in bytecode form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. - -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. - -""" -Decorator module, see http://pypi.python.org/pypi/decorator -for the documentation. -""" -from __future__ import print_function - -import collections -import inspect -import itertools -import operator -import re -import sys - -__version__ = '4.0.10' - -if sys.version_info >= (3,): - from inspect import getfullargspec - - - def get_init(cls): - return cls.__init__ -else: - class getfullargspec(object): - "A quick and dirty replacement for getfullargspec for Python 2.X" - - def __init__(self, f): - self.args, self.varargs, self.varkw, self.defaults = \ - inspect.getargspec(f) - self.kwonlyargs = [] - self.kwonlydefaults = None - - def __iter__(self): - yield self.args - yield self.varargs - yield self.varkw - yield self.defaults - - getargspec = inspect.getargspec - - - def get_init(cls): - return cls.__init__.__func__ - -# getargspec has been deprecated in Python 3.5 -ArgSpec = collections.namedtuple( - 'ArgSpec', 'args varargs varkw defaults') - - -def getargspec(f): - """A replacement for inspect.getargspec""" - spec = getfullargspec(f) - return ArgSpec(spec.args, spec.varargs, spec.varkw, spec.defaults) - - -DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') - - -# basic functionality -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 - methods update and make. - """ - - # Atomic get-and-increment provided by the GIL - _compile_count = itertools.count() - - def __init__(self, func=None, name=None, signature=None, - defaults=None, doc=None, module=None, funcdict=None): - self.shortsignature = signature - if func: - # func can be a class or a callable, but not an instance method - self.name = func.__name__ - if self.name == '': # small hack for lambda functions - self.name = '_lambda_' - self.doc = func.__doc__ - self.module = func.__module__ - if inspect.isfunction(func): - argspec = getfullargspec(func) - self.annotations = getattr(func, '__annotations__', {}) - for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', - 'kwonlydefaults'): - setattr(self, a, getattr(argspec, a)) - for i, arg in enumerate(self.args): - setattr(self, 'arg%d' % i, arg) - if sys.version_info < (3,): # easy way - self.shortsignature = self.signature = ( - inspect.formatargspec( - formatvalue=lambda val: "", *argspec)[1:-1]) - else: # Python 3 way - allargs = list(self.args) - allshortargs = list(self.args) - if self.varargs: - allargs.append('*' + self.varargs) - allshortargs.append('*' + self.varargs) - elif self.kwonlyargs: - allargs.append('*') # single star syntax - for a in self.kwonlyargs: - allargs.append('%s=None' % a) - allshortargs.append('%s=%s' % (a, a)) - if self.varkw: - allargs.append('**' + self.varkw) - allshortargs.append('**' + self.varkw) - self.signature = ', '.join(allargs) - self.shortsignature = ', '.join(allshortargs) - self.dict = func.__dict__.copy() - # func=None happens when decorating a caller - if name: - self.name = name - if signature is not None: - self.signature = signature - if defaults: - self.defaults = defaults - if doc: - self.doc = doc - if module: - self.module = module - if funcdict: - self.dict = funcdict - # check existence required attributes - assert hasattr(self, 'name') - if not hasattr(self, 'signature'): - raise TypeError('You are decorating a non function: %s' % func) - - def update(self, func, **kw): - "Update the signature of func with the data in self" - func.__name__ = self.name - func.__doc__ = getattr(self, 'doc', None) - func.__dict__ = getattr(self, 'dict', {}) - func.__defaults__ = getattr(self, 'defaults', ()) - func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None) - func.__annotations__ = getattr(self, 'annotations', None) - try: - frame = sys._getframe(3) - except AttributeError: # for IronPython and similar implementations - callermodule = '?' - else: - callermodule = frame.f_globals.get('__name__', '?') - func.__module__ = getattr(self, 'module', callermodule) - func.__dict__.update(kw) - - def make(self, src_templ, evaldict=None, addsource=False, **attrs): - "Make a new function from a given template and update the signature" - src = src_templ % vars(self) # expand name and signature - evaldict = evaldict or {} - mo = DEF.match(src) - if mo is None: - raise SyntaxError('not a valid function template\n%s' % src) - name = mo.group(1) # extract the function name - names = set([name] + [arg.strip(' *') for arg in - self.shortsignature.split(',')]) - for n in names: - if n in ('_func_', '_call_'): - raise NameError('%s is overridden in\n%s' % (n, src)) - - if not src.endswith('\n'): # add a newline for old Pythons - src += '\n' - - # Ensure each generated function has a unique filename for profilers - # (such as cProfile) that depend on the tuple of (, - # , ) being unique. - filename = '' % (next(self._compile_count),) - try: - code = compile(src, filename, 'single') - exec(code, evaldict) - except: - print('Error in generated code:', file=sys.stderr) - print(src, file=sys.stderr) - raise - func = evaldict[name] - if addsource: - attrs['__source__'] = src - self.update(func, **attrs) - return func - - @classmethod - def create(cls, obj, body, evaldict, defaults=None, - doc=None, module=None, addsource=True, **attrs): - """ - Create a function from the strings name, signature and body. - evaldict is the evaluation dictionary. If addsource is true an - attribute __source__ is added to the result. The attributes attrs - are added, if any. - """ - if isinstance(obj, str): # "name(signature)" - name, rest = obj.strip().split('(', 1) - signature = rest[:-1] # strip a right parens - func = None - else: # a function - name = None - signature = None - func = obj - self = cls(func, name, signature, defaults, doc, module) - ibody = '\n'.join(' ' + line for line in body.splitlines()) - return self.make('def %(name)s(%(signature)s):\n' + ibody, - evaldict, addsource, **attrs) - - -def decorate(func, caller): - """ - decorate(func, caller) decorates a function using a caller. - """ - evaldict = dict(_call_=caller, _func_=func) - fun = FunctionMaker.create( - func, "return _call_(_func_, %(shortsignature)s)", - evaldict, __wrapped__=func) - if hasattr(func, '__qualname__'): - fun.__qualname__ = func.__qualname__ - return fun - - -def decorator(caller, _func=None): - """decorator(caller) converts a caller function into a decorator""" - if _func is not None: # return a decorated function - # this is obsolete behavior; you should use decorate instead - return decorate(_func, caller) - # else return a decorator function - if inspect.isclass(caller): - name = caller.__name__.lower() - doc = 'decorator(%s) converts functions/generators into ' \ - 'factories of %s objects' % (caller.__name__, caller.__name__) - elif inspect.isfunction(caller): - if caller.__name__ == '': - name = '_lambda_' - else: - name = caller.__name__ - doc = caller.__doc__ - else: # assume caller is an object with a __call__ method - name = caller.__class__.__name__.lower() - doc = caller.__call__.__doc__ - evaldict = dict(_call_=caller, _decorate_=decorate) - return FunctionMaker.create( - '%s(func)' % name, 'return _decorate_(func, _call_)', - evaldict, doc=doc, module=caller.__module__, - __wrapped__=caller) - - -# ####################### contextmanager ####################### # - -try: # Python >= 3.2 - from contextlib import _GeneratorContextManager -except ImportError: # Python >= 2.5 - from contextlib import GeneratorContextManager as _GeneratorContextManager - - -class ContextManager(_GeneratorContextManager): - def __call__(self, func): - """Context manager decorator""" - return FunctionMaker.create( - func, "with _self_: return _func_(%(shortsignature)s)", - dict(_self_=self, _func_=func), __wrapped__=func) - - -init = getfullargspec(_GeneratorContextManager.__init__) -n_args = len(init.args) -if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7 - def __init__(self, g, *a, **k): - return _GeneratorContextManager.__init__(self, g(*a, **k)) - - - ContextManager.__init__ = __init__ -elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4 - pass -elif n_args == 4: # (self, gen, args, kwds) Python 3.5 - def __init__(self, g, *a, **k): - return _GeneratorContextManager.__init__(self, g, a, k) - - - ContextManager.__init__ = __init__ - -contextmanager = decorator(ContextManager) - - -# ############################ dispatch_on ############################ # - -def append(a, vancestors): - """ - Append ``a`` to the list of the virtual ancestors, unless it is already - included. - """ - add = True - for j, va in enumerate(vancestors): - if issubclass(va, a): - add = False - break - if issubclass(a, va): - vancestors[j] = a - add = False - if add: - vancestors.append(a) - - -# inspired from simplegeneric by P.J. Eby and functools.singledispatch -def dispatch_on(*dispatch_args): - """ - Factory of decorators turning a function into a generic function - dispatching on the given arguments. - """ - assert dispatch_args, 'No dispatch args passed' - dispatch_str = '(%s,)' % ', '.join(dispatch_args) - - def check(arguments, wrong=operator.ne, msg=''): - """Make sure one passes the expected number of arguments""" - if wrong(len(arguments), len(dispatch_args)): - raise TypeError('Expected %d arguments, got %d%s' % - (len(dispatch_args), len(arguments), msg)) - - def gen_func_dec(func): - """Decorator turning a function into a generic function""" - - # first check the dispatch arguments - argset = set(getfullargspec(func).args) - if not set(dispatch_args) <= argset: - raise NameError('Unknown dispatch arguments %s' % dispatch_str) - - typemap = {} - - def vancestors(*types): - """ - Get a list of sets of virtual ancestors for the given types - """ - check(types) - ras = [[] for _ in range(len(dispatch_args))] - for types_ in typemap: - for t, type_, ra in zip(types, types_, ras): - if issubclass(t, type_) and type_ not in t.__mro__: - append(type_, ra) - return [set(ra) for ra in ras] - - def ancestors(*types): - """ - Get a list of virtual MROs, one for each type - """ - check(types) - lists = [] - for t, vas in zip(types, vancestors(*types)): - n_vas = len(vas) - if n_vas > 1: - raise RuntimeError( - 'Ambiguous dispatch for %s: %s' % (t, vas)) - elif n_vas == 1: - va, = vas - mro = type('t', (t, va), {}).__mro__[1:] - else: - mro = t.__mro__ - lists.append(mro[:-1]) # discard t and object - return lists - - def register(*types): - """ - Decorator to register an implementation for the given types - """ - check(types) - - def dec(f): - check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__) - typemap[types] = f - return f - - return dec - - def dispatch_info(*types): - """ - An utility to introspect the dispatch algorithm - """ - check(types) - lst = [] - for anc in itertools.product(*ancestors(*types)): - lst.append(tuple(a.__name__ for a in anc)) - return lst - - def _dispatch(dispatch_args, *args, **kw): - types = tuple(type(arg) for arg in dispatch_args) - try: # fast path - f = typemap[types] - except KeyError: - pass - else: - return f(*args, **kw) - combinations = itertools.product(*ancestors(*types)) - next(combinations) # the first one has been already tried - for types_ in combinations: - f = typemap.get(types_) - if f is not None: - return f(*args, **kw) - - # else call the default implementation - return func(*args, **kw) - - return FunctionMaker.create( - func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str, - dict(_f_=_dispatch), register=register, default=func, - typemap=typemap, vancestors=vancestors, ancestors=ancestors, - dispatch_info=dispatch_info, __wrapped__=func) - - gen_func_dec.__name__ = 'dispatch_on' + dispatch_str - return gen_func_dec diff --git a/tests/test_core.py b/tests/test_core.py index f28c9abc..5d500257 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,6 @@ from concurrent.futures import ThreadPoolExecutor import os +import inspect import time import unittest @@ -12,7 +13,6 @@ HistogramMetricFamily, Info, InfoMetricFamily, Metric, Sample, StateSetMetricFamily, Summary, SummaryMetricFamily, UntypedMetricFamily, ) -from prometheus_client.decorator import getargspec from prometheus_client.metrics import _get_use_created from prometheus_client.validation import ( disable_legacy_validation, enable_legacy_validation, @@ -51,10 +51,10 @@ def test_reset(self): self.assertNotEqual(0, self.registry.get_sample_value('c_total')) created = self.registry.get_sample_value('c_created') time.sleep(0.05) - self.counter.reset() + self.counter.reset() self.assertEqual(0, self.registry.get_sample_value('c_total')) created_after_reset = self.registry.get_sample_value('c_created') - self.assertLess(created, created_after_reset) + self.assertLess(created, created_after_reset) def test_repr(self): self.assertEqual(repr(self.counter), "prometheus_client.metrics.Counter(c)") @@ -64,13 +64,13 @@ def test_negative_increment_raises(self): def test_function_decorator(self): @self.counter.count_exceptions(ValueError) - def f(r): + def f(r: bool) -> None: if r: raise ValueError else: raise TypeError - self.assertEqual((["r"], None, None, None), getargspec(f)) + self.assertEqual("(r: bool) -> None", str(inspect.signature(f))) try: f(False) @@ -198,10 +198,10 @@ def test_inprogress_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('g')) @self.gauge.track_inprogress() - def f(): + def f() -> None: self.assertEqual(1, self.registry.get_sample_value('g')) - self.assertEqual(([], None, None, None), getargspec(f)) + self.assertEqual("() -> None", str(inspect.signature(f))) f() self.assertEqual(0, self.registry.get_sample_value('g')) @@ -230,10 +230,10 @@ def test_time_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('g')) @self.gauge.time() - def f(): + def f() -> None: time.sleep(.001) - self.assertEqual(([], None, None, None), getargspec(f)) + self.assertEqual("() -> None", str(inspect.signature(f))) f() self.assertNotEqual(0, self.registry.get_sample_value('g')) @@ -309,10 +309,10 @@ def test_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('s_count')) @self.summary.time() - def f(): + def f() -> None: pass - self.assertEqual(([], None, None, None), getargspec(f)) + self.assertEqual("() -> None", str(inspect.signature(f))) f() self.assertEqual(1, self.registry.get_sample_value('s_count')) @@ -462,10 +462,10 @@ def test_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('h_bucket', {'le': '+Inf'})) @self.histogram.time() - def f(): + def f() -> None: pass - self.assertEqual(([], None, None, None), getargspec(f)) + self.assertEqual("() -> None", str(inspect.signature(f))) f() self.assertEqual(1, self.registry.get_sample_value('h_count')) From b4214d5c3d3518e07569b3f510e70e78e6ec706e Mon Sep 17 00:00:00 2001 From: Yury Pliner Date: Tue, 8 Feb 2022 00:54:45 +0000 Subject: [PATCH 2/7] Ignore mypy https://github.com/python/mypy/issues/1927 Signed-off-by: Yury Pliner Signed-off-by: Iurii Pliner --- prometheus_client/context_managers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prometheus_client/context_managers.py b/prometheus_client/context_managers.py index 0fc545f9..86c6b1a4 100644 --- a/prometheus_client/context_managers.py +++ b/prometheus_client/context_managers.py @@ -29,7 +29,7 @@ def __call__(self, f: "F") -> "F": def wrapped(*args: Any, **kwargs: Any) -> Any: with self: return f(*args, **kwargs) - return wrapped + return wrapped # type: ignore class InprogressTracker: @@ -47,7 +47,7 @@ def __call__(self, f: "F") -> "F": def wrapped(*args: Any, **kwargs: Any) -> Any: with self: return f(*args, **kwargs) - return wrapped + return wrapped # type: ignore class Timer: @@ -78,4 +78,4 @@ def wrapped(*args: Any, **kwargs: Any) -> Any: # ensures thread safety and reentrancy. with self._new_timer(): return f(*args, **kwargs) - return wrapped + return wrapped # type: ignore From 8358200c54a21c697ecb6dab370a8f212db4971e Mon Sep 17 00:00:00 2001 From: Yury Pliner Date: Tue, 8 Feb 2022 00:58:33 +0000 Subject: [PATCH 3/7] Revert typing Signed-off-by: Yury Pliner Signed-off-by: Iurii Pliner --- tests/test_core.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 5d500257..5c4b67e7 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -64,13 +64,13 @@ def test_negative_increment_raises(self): def test_function_decorator(self): @self.counter.count_exceptions(ValueError) - def f(r: bool) -> None: + def f(r): if r: raise ValueError else: raise TypeError - self.assertEqual("(r: bool) -> None", str(inspect.signature(f))) + self.assertEqual("(r)", str(inspect.signature(f))) try: f(False) @@ -198,10 +198,10 @@ def test_inprogress_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('g')) @self.gauge.track_inprogress() - def f() -> None: + def f(): self.assertEqual(1, self.registry.get_sample_value('g')) - self.assertEqual("() -> None", str(inspect.signature(f))) + self.assertEqual("()", str(inspect.signature(f))) f() self.assertEqual(0, self.registry.get_sample_value('g')) @@ -230,10 +230,10 @@ def test_time_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('g')) @self.gauge.time() - def f() -> None: + def f(): time.sleep(.001) - self.assertEqual("() -> None", str(inspect.signature(f))) + self.assertEqual("()", str(inspect.signature(f))) f() self.assertNotEqual(0, self.registry.get_sample_value('g')) @@ -309,10 +309,10 @@ def test_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('s_count')) @self.summary.time() - def f() -> None: + def f(): pass - self.assertEqual("() -> None", str(inspect.signature(f))) + self.assertEqual("()", str(inspect.signature(f))) f() self.assertEqual(1, self.registry.get_sample_value('s_count')) @@ -462,10 +462,10 @@ def test_function_decorator(self): self.assertEqual(0, self.registry.get_sample_value('h_bucket', {'le': '+Inf'})) @self.histogram.time() - def f() -> None: + def f(): pass - self.assertEqual("() -> None", str(inspect.signature(f))) + self.assertEqual("()", str(inspect.signature(f))) f() self.assertEqual(1, self.registry.get_sample_value('h_count')) From 7e78d50dc1787466f349ed1ad6cb7f989cfd81f0 Mon Sep 17 00:00:00 2001 From: Yury Pliner Date: Tue, 8 Feb 2022 15:19:40 +0000 Subject: [PATCH 4/7] Remove decorator from mypy.ini Signed-off-by: Yury Pliner Signed-off-by: Iurii Pliner --- mypy.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/mypy.ini b/mypy.ini index fe372d07..59f520bf 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,6 +2,3 @@ exclude = prometheus_client/decorator.py|prometheus_client/twisted|tests/test_twisted.py implicit_reexport = False disallow_incomplete_defs = True - -[mypy-prometheus_client.decorator] -follow_imports = skip From d7a215c7a5f1aca625cdea0cd1b36658f6708551 Mon Sep 17 00:00:00 2001 From: Yury Pliner Date: Tue, 8 Feb 2022 21:01:48 +0000 Subject: [PATCH 5/7] Remove links to decorator.py Signed-off-by: Yury Pliner Signed-off-by: Iurii Pliner --- .coveragerc | 2 -- NOTICE | 2 -- mypy.ini | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.coveragerc b/.coveragerc index 69a0f522..1727a43d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,8 +1,6 @@ [run] branch = True source = prometheus_client -omit = - prometheus_client/decorator.py [paths] source = diff --git a/NOTICE b/NOTICE index 59efb6c7..faf36007 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,3 @@ Prometheus instrumentation library for Python applications Copyright 2015 The Prometheus Authors -This product bundles decorator 4.0.10 which is available under a "2-clause BSD" -license. For details, see prometheus_client/decorator.py. diff --git a/mypy.ini b/mypy.ini index 59f520bf..222973e8 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,4 +1,4 @@ [mypy] -exclude = prometheus_client/decorator.py|prometheus_client/twisted|tests/test_twisted.py +exclude = prometheus_client/twisted|tests/test_twisted.py implicit_reexport = False disallow_incomplete_defs = True From d9aae14cfd3f6de18e50efdffece896aac38b3ed Mon Sep 17 00:00:00 2001 From: Iurii Pliner Date: Wed, 11 Dec 2024 01:24:30 +0000 Subject: [PATCH 6/7] Fix flake Signed-off-by: Iurii Pliner --- tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index 5c4b67e7..9f9c1c0e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,6 +1,6 @@ from concurrent.futures import ThreadPoolExecutor -import os import inspect +import os import time import unittest From b631c0eb28b598343b62928703b54a161f6701d7 Mon Sep 17 00:00:00 2001 From: Iurii Pliner Date: Wed, 11 Dec 2024 18:37:53 +0000 Subject: [PATCH 7/7] Use ParamSpec to properly annotate decorators Signed-off-by: Iurii Pliner --- prometheus_client/context_managers.py | 25 ++++++++++++++++--------- prometheus_client/metrics.py | 1 - setup.py | 3 +++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/prometheus_client/context_managers.py b/prometheus_client/context_managers.py index 86c6b1a4..5ce28a4b 100644 --- a/prometheus_client/context_managers.py +++ b/prometheus_client/context_managers.py @@ -1,14 +1,21 @@ import functools +import sys from timeit import default_timer from types import TracebackType from typing import ( - Any, Callable, Literal, Optional, Tuple, Type, TYPE_CHECKING, TypeVar, - Union, + Callable, Literal, Optional, Tuple, Type, TYPE_CHECKING, TypeVar, Union, ) +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + if TYPE_CHECKING: from . import Counter - F = TypeVar("F", bound=Callable[..., Any]) + +TParam = ParamSpec("TParam") +TResult = TypeVar("TResult") class ExceptionCounter: @@ -24,9 +31,9 @@ def __exit__(self, typ: Optional[Type[BaseException]], value: Optional[BaseExcep self._counter.inc() return False - def __call__(self, f: "F") -> "F": + def __call__(self, f: Callable[TParam, TResult]) -> Callable[TParam, TResult]: @functools.wraps(f) - def wrapped(*args: Any, **kwargs: Any) -> Any: + def wrapped(*args: TParam.args, **kwargs: TParam.kwargs) -> TResult: with self: return f(*args, **kwargs) return wrapped # type: ignore @@ -42,9 +49,9 @@ def __enter__(self): def __exit__(self, typ, value, traceback): self._gauge.dec() - def __call__(self, f: "F") -> "F": + def __call__(self, f: Callable[TParam, TResult]) -> Callable[TParam, TResult]: @functools.wraps(f) - def wrapped(*args: Any, **kwargs: Any) -> Any: + def wrapped(*args: TParam.args, **kwargs: TParam.kwargs) -> TResult: with self: return f(*args, **kwargs) return wrapped # type: ignore @@ -71,9 +78,9 @@ def __exit__(self, typ, value, traceback): def labels(self, *args, **kw): self._metric = self._metric.labels(*args, **kw) - def __call__(self, f: "F") -> "F": + def __call__(self, f: Callable[TParam, TResult]) -> Callable[TParam, TResult]: @functools.wraps(f) - def wrapped(*args: Any, **kwargs: Any) -> Any: + def wrapped(*args: TParam.args, **kwargs: TParam.kwargs) -> TResult: # Obtaining new instance of timer every time # ensures thread safety and reentrancy. with self._new_timer(): diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index b9f25ffc..6d5d0b28 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -19,7 +19,6 @@ ) T = TypeVar('T', bound='MetricWrapperBase') -F = TypeVar("F", bound=Callable[..., Any]) def _build_full_name(metric_type, name, namespace, subsystem, unit): diff --git a/setup.py b/setup.py index 1665ed7c..b1debfb5 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,9 @@ extras_require={ 'twisted': ['twisted'], }, + install_requires=[ + 'typing-extensions>=4; python_version<"3.10"', + ], test_suite="tests", python_requires=">=3.9", classifiers=[