8000 Add type annotations to everything referenced in __init__.py (#771) · chnacib/client_python@4e0e7ff · GitHub
[go: up one dir, main page]

Skip to content

Commit 4e0e7ff

Browse files
authored
Add type annotations to everything referenced in __init__.py (prometheus#771)
* Type public functions in exposition.py * Typing in registry.py and metrics.py * Update built in collectors to use the new Abstract Base Class * Add typing to metrics_core.py * Add type annotations to samples.py * Sample values can be int as well as float * Support two or three element tuples in HistogramMetricFamily * Add typing to Graphite bridge Signed-off-by: Chris Marchbanks <csmarchbanks@gmail.com>
1 parent 39d2360 commit 4e0e7ff

10 files changed

+289
-122
lines changed

prometheus_client/asgi.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
from typing import Callable
12
from urllib.parse import parse_qs
23

34
from .exposition import _bake_output
4-
from .registry import REGISTRY
5+
from .registry import CollectorRegistry, REGISTRY
56

67

7-
def make_asgi_app(registry=REGISTRY):
8+
def make_asgi_app(registry: CollectorRegistry = REGISTRY) -> Callable:
89
"""Create a ASGI app which serves the metrics from a registry."""
910

1011
async def prometheus_app(scope, receive, send):

prometheus_client/bridge/graphite.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
import threading
77
import time
88
from timeit import default_timer
9+
from typing import Callable, Tuple
910

10-
from ..registry import REGISTRY
11+
from ..registry import CollectorRegistry, REGISTRY
1112

1213
# Roughly, have to keep to what works as a file name.
1314
# We also remove periods, so labels can be distinguished.
@@ -45,14 +46,20 @@ def run(self):
4546

4647

4748
class GraphiteBridge:
48-
def __init__(self, address, registry=REGISTRY, timeout_seconds=30, _timer=time.time, tags=False):
49+
def __init__(self,
50+
address: Tuple[str, int],
51+
registry: CollectorRegistry = REGISTRY,
52+
timeout_seconds: float = 30,
53+
_timer: Callable[[], float] = time.time,
54+
tags: bool = False,
55+
):
4956
self._address = address
5057
self._registry = registry
5158
self._tags = tags
5259
self._timeout = timeout_seconds
5360
self._timer = _timer
5461

55-
def push(self, prefix=''):
62+
def push(self, prefix: str = '') -> None:
5663
now = int(self._timer())
5764
output = []
5865

@@ -81,7 +88,7 @@ def push(self, prefix=''):
8188
conn.sendall(''.join(output).encode('ascii'))
8289
conn.close()
8390

84-
def start(self, interval=60.0, prefix=''):
91+
def start(self, interval: float = 60.0, prefix: str = '') -> None:
8592
t = _RegularPush(self, interval, prefix)
8693
t.daemon = True
8794
t.start()

prometheus_client/exposition.py

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from socketserver import ThreadingMixIn
77
import sys
88
import threading
9+
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
910
from urllib.error import HTTPError
1011
from urllib.parse import parse_qs, quote_plus, urlparse
1112
from urllib.request import (
@@ -14,7 +15,7 @@
1415
from wsgiref.simple_server import make_server, WSGIRequestHandler, WSGIServer
1516

1617
from .openmetrics import exposition as openmetrics
17-
from .registry import REGISTRY
18+
from .registry import CollectorRegistry, REGISTRY
1819
from .utils import floatToGoString
1920

2021
__all__ = (
@@ -101,7 +102,7 @@ def _bake_output(registry, accept_header, params):
101102
return '200 OK', ('Content-Type', content_type), output
102103

103104

104-
def make_wsgi_app(registry=REGISTRY):
105+
def make_wsgi_app(registry: CollectorRegistry = REGISTRY) -> Callable:
105106
"""Create a WSGI app which serves the metrics from a registry."""
106107

107108
def prometheus_app(environ, start_response):
@@ -149,7 +150,7 @@ def _get_best_family(address, port):
149150
return family, sockaddr[0]
150151

151152

152-
def start_wsgi_server(port, addr='0.0.0.0', registry=REGISTRY):
153+
def start_wsgi_server(port: int, addr: str = '0.0.0.0', registry: CollectorRegistry = REGISTRY) -> None:
153154
"""Starts a WSGI server for prometheus metrics as a daemon thread."""
154155
class TmpServer(ThreadingWSGIServer):
155156
"""Copy of ThreadingWSGIServer to update address_family locally"""
@@ -164,7 +165,7 @@ class TmpServer(ThreadingWSGIServer):
164165
start_http_server = start_wsgi_server
165166

166167

167-
def generate_latest(registry=REGISTRY):
168+
def generate_latest(registry: CollectorRegistry = REGISTRY) -> bytes:
168169
"""Returns the metrics from the registry in latest text format as a string."""
169170

170171
def sample_line(line):
@@ -205,7 +206,7 @@ def sample_line(line):
205206
mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n')))
206207
output.append(f'# TYPE {mname} {mtype}\n')
207208

208-
om_samples = {}
209+
om_samples: Dict[str, List[str]] = {}
209210
for s in metric.samples:
210211
for suffix in ['_created', '_gsum', '_gcount']:
211212
if s.name == metric.name + suffix:
@@ -226,7 +227,7 @@ def sample_line(line):
226227
return ''.join(output).encode('utf-8')
227228

228229

229-
def choose_encoder(accept_header):
230+
def choose_encoder(accept_header: str) -> Tuple[Callable[[CollectorRegistry], bytes], str]:
230231
accept_header = accept_header or ''
231232
for accepted in accept_header.split(','):
232233
if accepted.split(';')[0].strip() == 'application/openmetrics-text':
@@ -237,9 +238,9 @@ def choose_encoder(accept_header):
237238

238239
class MetricsHandler(BaseHTTPRequestHandler):
239240
"""HTTP handler that gives metrics from ``REGISTRY``."""
240-
registry = REGISTRY
241+
registry: CollectorRegistry = REGISTRY
241242

242-
def do_GET(self):
243+
def do_GET(self) -> None:
243244
# Prepare parameters
244245
registry = self.registry
245246
accept_header = self.headers.get('Accept')
@@ -252,11 +253,11 @@ def do_GET(self):
252253
self.end_headers()
253254
self.wfile.write(output)
254255

255-
def log_message(self, format, *args):
256+
def log_message(self, format: str, *args: Any) -> None:
256257
"""Log nothing."""
257258

258259
@classmethod
259-
def factory(cls, registry):
260+
def factory(cls, registry: CollectorRegistry) -> type:
260261
"""Returns a dynamic MetricsHandler class tied
261262
to the passed registry.
262263
"""
@@ -271,27 +272,34 @@ def factory(cls, registry):
271272
return MyMetricsHandler
272273

273274

274-
def write_to_textfile(path, registry):
275+
def write_to_textfile(path: str, registry: CollectorRegistry) -> None:
275276
"""Write metrics to the given path.
276277
277278
This is intended for use with the Node exporter textfile collector.
278279
The path must end in .prom for the textfile collector to process it."""
279280
tmppath = f'{path}.{os.getpid()}.{threading.current_thread().ident}'
280281
with open(tmppath, 'wb') as f:
281282
f.write(generate_latest(registry))
282-
283+
283284
# rename(2) is atomic but fails on Windows if the destination file exists
284285
if os.name == 'nt':
285286
os.replace(tmppath, path)
286287
else:
287288
os.rename(tmppath, path)
288289

289290

290-
def _make_handler(url, method, timeout, headers, data, base_handler):
291+
def _make_handler(
292+
url: str,
293+
method: str,
294+
timeout: Optional[float],
295+
headers: Sequence[Tuple[str, str]],
296+
data: bytes,
297+
base_handler: type,
298+
) -> Callable[[], None]:
291299

292-
def handle():
300+
def handle() -> None:
293301
request = Request(url, data=data)
294-
request.get_method = lambda: method
302+
request.get_method = lambda: method # type: ignore
295303
for k, v in headers:
296304
request.add_header(k, v)
297305
resp = build_opener(base_handler).open(request, timeout=timeout)
@@ -301,15 +309,27 @@ def handle():
301309
return handle
302310

303311

304-
def default_handler(url, method, timeout, headers, data):
312+
def default_handler(
313+
url: str,
314+
method: str,
315+
timeout: Optional[float],
316+
headers: List[Tuple[str, str]],
317+
data: bytes,
318+
) -> Callable[[], None]:
305319
"""Default handler that implements HTTP/HTTPS connections.
306320
307321
Used by the push_to_gateway functions. Can be re-used by other handlers."""
308322

309323
return _make_handler(url, method, timeout, headers, data, HTTPHandler)
310324

311325

312-
def passthrough_redirect_handler(url, method, timeout, headers, data):
326+
def passthrough_redirect_handler(
327+
url: str,
328+
method: str,
329+
timeout: Optional[float],
330+
headers: List[Tuple[str, str]],
331+
data: bytes,
332+
) -> Callable[[], None]:
313333
"""
314334
Handler that automatically trusts redirect responses for all HTTP methods.
315335
@@ -323,7 +343,15 @@ def passthrough_redirect_handler(url, method, timeout, headers, data):
323343
return _make_handler(url, method, timeout, headers, data, _PrometheusRedirectHandler)
324344

325345

326-
def basic_auth_handler(url, method, timeout, headers, data, username=None, password=None):
346+
def basic_auth_handler(
347+
url: str,
348+
method: str,
349+
timeout: Optional[float],
350+
headers: List[Tuple[str, str]],
351+
data: bytes,
352+
username: str = None,
353+
password: str = None,
354+
) -> Callable[[], None]:
327355
"""Handler that implements HTTP/HTTPS connections with Basic Auth.
328356
329357
Sets auth headers using supplied 'username' and 'password', if set.
@@ -336,15 +364,20 @@ def handle():
336364
auth_value = f'{username}:{password}'.encode()
337365
auth_token = base64.b64encode(auth_value)
338366
auth_header = b'Basic ' + auth_token
339-
headers.append(['Authorization', auth_header])
367+
headers.append(('Authorization', auth_header))
340368
default_handler(url, method, timeout, headers, data)()
341369

342370
return handle
343371

344372

345373
def push_to_gateway(
346-
gateway, job, registry, grouping_key=None, timeout=30,
347-
handler=default_handler):
374+
gateway: str,
375+
job: str,
376+
registry: CollectorRegistry,
377+
grouping_key: Optional[Dict[str, Any]] = None,
378+
timeout: Optional[float] = 30,
379+
handler: Callable = default_handler,
380+
) -> None:
348381
"""Push metrics to the given pushgateway.
349382
350383
`gateway` the url for your push gateway. Either of the form
@@ -387,8 +420,13 @@ def push_to_gateway(
387420

388421

389422
def pushadd_to_gateway(
390-
gateway, job, registry, grouping_key=None, timeout=30,
391-
handler=default_handler):
423+
gateway: str,
424+
job: str,
425+
registry: Optional[CollectorRegistry],
426+
grouping_key: Optional[Dict[str, Any]] = None,
427+
timeout: Optional[float] = 30,
428+
handler: Callable = default_handler,
429+
) -> None:
392430
"""PushAdd metrics to the given pushgateway.
393431
394432
`gateway` the url for your push gateway. Either of the form
@@ -413,7 +451,12 @@ def pushadd_to_gateway(
413451

414452

415453
def delete_from_gateway(
416-
gateway, job, grouping_key=None, timeout=30, handler=default_handler):
454+
gateway: str,
455+
job: str,
456+
grouping_key: Optional[Dict[str, Any]] = None,
457+
timeout: Optional[float] = 30,
458+
handler: Callable = default_handler,
459+
) -> None:
417460
"""Delete metrics from the given pushgateway.
418461
419462
`gateway` the url for your push gateway. Either of the form
@@ -436,7 +479,15 @@ def delete_from_gateway(
436479
_use_gateway('DELETE', gateway, job, None, grouping_key, timeout, handler)
437480

438481

439-
def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler):
482+
def _use_gateway(
483+
method: str,
484+
gateway: str,
485+
job: str,
486+
registry: Optional[CollectorRegistry],
487+
grouping_key: Optional[Dict[str, Any]],
488+
timeout: Optional[float],
489+
handler: Callable,
490+
) -> None:
440491
gateway_url = urlparse(gateway)
441492
# See https://bugs.python.org/issue27657 for details on urlparse in py>=3.7.6.
442493
if not gateway_url.scheme or (
@@ -450,6 +501,8 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
450501

451502
data = b''
452503
if method != 'DELETE':
504+
if registry is None:
505+
registry = REGISTRY
453506
data = generate_latest(registry)
454507

455508
if grouping_key is None:
@@ -475,7 +528,7 @@ def _escape_grouping_key(k, v):
475528
return k, quote_plus(v)
476529

477530

478-
def instance_ip_grouping_key():
531+
def instance_ip_grouping_key() -> Dict[str, Any]:
479532
"""Grouping key with instance set to the IP Address of this host."""
480533
with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as s:
481534
if sys.platform == 'darwin':

prometheus_client/gc_collector.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import gc
22
import platform
3+
from typing import Iterable
34

4-
from .metrics_core import CounterMetricFamily
5-
from .registry import REGISTRY
5+
from .metrics_core import CounterMetricFamily, Metric
6+
from .registry import Collector, CollectorRegistry, REGISTRY
67

78

8-
class GCCollector:
9+
class GCCollector(Collector):
910
"""Collector for Garbage collection statistics."""
1011

11-
def __init__(self, registry=REGISTRY):
12+
def __init__(self, registry: CollectorRegistry = REGISTRY):
1213
if not hasattr(gc, 'get_stats') or platform.python_implementation() != 'CPython':
1314
return
1415
registry.register(self)
1516

16-
def collect(self):
17+
def collect(self) -> Iterable[Metric]:
1718
collected = CounterMetricFamily(
1819
'python_gc_objects_collected',
1920
'Objects collected during gc',
@@ -31,8 +32,8 @@ def collect(self):
3132
labels=['generation'],
3233
)
3334

34-
for generation, stat in enumerate(gc.get_stats()):
35-
generation = str(generation)
35+
for gen, stat in enumerate(gc.get_stats()):
36+
generation = str(gen)
3637
collected.add_metric([generation], value=stat['collected'])
3738
uncollectable.add_metric([generation], value=stat['uncollectable'])
3839
collections.add_metric([generation], value=stat['collections'])

0 commit comments

Comments
 (0)
0