66from socketserver import ThreadingMixIn
77import sys
88import threading
9+ from typing import Any , Callable , Dict , List , Optional , Sequence , Tuple
910from urllib .error import HTTPError
1011from urllib .parse import parse_qs , quote_plus , urlparse
1112from urllib .request import (
1415from wsgiref .simple_server import make_server , WSGIRequestHandler , WSGIServer
1516
1617from .openmetrics import exposition as openmetrics
17- from .registry import REGISTRY
18+ from .registry import CollectorRegistry , REGISTRY
1819from .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):
164165start_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
238239class 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
345373def 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
389422def 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
415453def 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' :
0 commit comments