99import ssl
1010import sys
1111import threading
12- from typing import Any , Callable , Dict , List , Optional , Sequence , Tuple , Union
12+ from typing import (
13+ Any , Callable , Dict , List , Literal , Optional , Sequence , Tuple , Union ,
14+ )
1315from urllib .error import HTTPError
1416from urllib .parse import parse_qs , quote_plus , urlparse
1517from urllib .request import (
2224from .registry import Collector , REGISTRY
2325from .utils import floatToGoString , parse_version
2426
27+ try :
28+ import snappy # type: ignore
29+ SNAPPY_AVAILABLE = True
30+ except ImportError :
31+ snappy = None # type: ignore
32+ SNAPPY_AVAILABLE = False
33+
2534__all__ = (
2635 'CONTENT_TYPE_LATEST' ,
2736 'CONTENT_TYPE_PLAIN_0_0_4' ,
4655"""Content type of the latest format"""
4756
4857CONTENT_TYPE_LATEST = CONTENT_TYPE_PLAIN_1_0_0
58+ CompressionType = Optional [Literal ['gzip' , 'snappy' ]]
4959
5060
5161class _PrometheusRedirectHandler (HTTPRedirectHandler ):
@@ -596,6 +606,7 @@ def push_to_gateway(
596606 grouping_key : Optional [Dict [str , Any ]] = None ,
597607 timeout : Optional [float ] = 30 ,
598608 handler : Callable = default_handler ,
609+ compression : CompressionType = None ,
599610) -> None :
600611 """Push metrics to the given pushgateway.
601612
@@ -632,10 +643,12 @@ def push_to_gateway(
632643 failure.
633644 'content' is the data which should be used to form the HTTP
634645 Message Body.
646+ `compression` selects the payload compression. Supported values are 'gzip'
647+ and 'snappy'. Defaults to None (no compression).
635648
636649 This overwrites all metrics with the same job and grouping_key.
637650 This uses the PUT HTTP method."""
638- _use_gateway ('PUT' , gateway , job , registry , grouping_key , timeout , handler )
651+ _use_gateway ('PUT' , gateway , job , registry , grouping_key , timeout , handler , compression )
639652
640653
641654def pushadd_to_gateway (
@@ -645,6 +658,7 @@ def pushadd_to_gateway(
645658 grouping_key : Optional [Dict [str , Any ]] = None ,
646659 timeout : Optional [float ] = 30 ,
647660 handler : Callable = default_handler ,
661+ compression : CompressionType = None ,
648662) -> None :
649663 """PushAdd metrics to the given pushgateway.
650664
@@ -663,10 +677,12 @@ def pushadd_to_gateway(
663677 will be carried out by a default handler.
664678 See the 'prometheus_client.push_to_gateway' documentation
665679 for implementation requirements.
680+ `compression` selects the payload compression. Supported values are 'gzip'
681+ and 'snappy'. Defaults to None (no compression).
666682
667683 This replaces metrics with the same name, job and grouping_key.
668684 This uses the POST HTTP method."""
669- _use_gateway ('POST' , gateway , job , registry , grouping_key , timeout , handler )
685+ _use_gateway ('POST' , gateway , job , registry , grouping_key , timeout , handler , compression )
670686
671687
672688def delete_from_gateway (
@@ -706,6 +722,7 @@ def _use_gateway(
706722 grouping_key : Optional [Dict [str , Any ]],
707723 timeout : Optional [float ],
708724 handler : Callable ,
725+ compression : CompressionType = None ,
709726) -> None :
710727 gateway_url = urlparse (gateway )
711728 # See https://bugs.python.org/issue27657 for details on urlparse in py>=3.7.6.
@@ -715,24 +732,53 @@ def _use_gateway(
715732 gateway = gateway .rstrip ('/' )
716733 url = '{}/metrics/{}/{}' .format (gateway , * _escape_grouping_key ("job" , job ))
717734
718- data = b''
719- if method != 'DELETE' :
720- if registry is None :
721- registry = REGISTRY
722- data = generate_latest (registry )
723-
724735 if grouping_key is None :
725736 grouping_key = {}
726737 url += '' .join (
727738 '/{}/{}' .format (* _escape_grouping_key (str (k ), str (v )))
728739 for k , v in sorted (grouping_key .items ()))
729740
741+ data = b''
742+ headers : List [Tuple [str , str ]] = []
743+ if method != 'DELETE' :
744+ if registry is None :
745+ registry = REGISTRY
746+ data = generate_latest (registry )
747+ data , headers = _compress_payload (data , compression )
748+ else :
749+ # DELETE requests still need Content-Type header per test expectations
750+ headers = [('Content-Type' , CONTENT_TYPE_PLAIN_0_0_4 )]
751+ if compression is not None :
752+ raise ValueError ('Compression is not supported for DELETE requests.' )
753+
730754 handler (
731755 url = url , method = method , timeout = timeout ,
732- headers = [( 'Content-Type' , CONTENT_TYPE_PLAIN_0_0_4 )] , data = data ,
756+ headers = headers , data = data ,
733757 )()
734758
735759
760+ def _compress_payload (data : bytes , compression : CompressionType ) -> Tuple [bytes , List [Tuple [str , str ]]]:
761+ headers = [('Content-Type' , CONTENT_TYPE_PLAIN_0_0_4 )]
762+ if compression is None :
763+ return data , headers
764+
765+ encoding = compression .lower ()
766+ if encoding == 'gzip' :
767+ headers .append (('Content-Encoding' , 'gzip' ))
768+ return gzip .compress (data ), headers
769+ if encoding == 'snappy' :
770+ if not SNAPPY_AVAILABLE :
771+ raise RuntimeError ('Snappy compression requires the python-snappy package to be installed.' )
772+ headers .append (('Content-Encoding' , 'snappy' ))
773+ compressor = snappy .StreamCompressor ()
774+ compressed = compressor .compress (data )
775+ flush = getattr (compressor , 'flush' , None )
776+ if callable (flush ):
777+ compressed += flush ()
778+ return compressed , headers
779+ raise ValueError (f"Unsupported compression type: { compression } " )
780+
781+
736782def _escape_grouping_key (k , v ):
737783 if v == "" :
738784 # Per https://github.com/prometheus/pushgateway/pull/346.
0 commit comments