Skip to content

Commit 7fa71ec

Browse files
s3714110Lam Tran
andauthored
Add HTTP Method as a Parameter for APIs that support POST (#302)
* Add HTTP Method as a Parameter for APIs that support POST * Update class docstring * Add test cases for the method parameter --------- Co-authored-by: Lam Tran <[email protected]>
1 parent 1d94533 commit 7fa71ec

File tree

2 files changed

+101
-20
lines changed

2 files changed

+101
-20
lines changed

prometheus_api_client/prometheus_connect.py

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class PrometheusConnect:
4141
Example: {"http_proxy": "<ip_address/hostname:port>", "https_proxy": "<ip_address/hostname:port>"}
4242
:param session (Optional) Custom requests.Session to enable complex HTTP configuration
4343
:param timeout: (Optional) A timeout (in seconds) applied to all requests
44+
:param method: (Optional) (str) HTTP Method (GET or POST) to use for Query APIs that allow POST
45+
(/query, /query_range and /labels). Use POST for large and complex queries. Default is GET.
4446
"""
4547

4648
def __init__(
@@ -53,6 +55,7 @@ def __init__(
5355
proxy: dict = None,
5456
session: Session = None,
5557
timeout: int = None,
58+
method: str = "GET"
5659
):
5760
"""Functions as a Constructor for the class PrometheusConnect."""
5861
if url is None:
@@ -64,6 +67,15 @@ def __init__(
6467
self._all_metrics = None
6568
self._timeout = timeout
6669

70+
if not isinstance(method, str):
71+
raise TypeError("Method must be a string")
72+
73+
method = method.upper()
74+
if method not in {"GET", "POST"}:
75+
raise ValueError("Method can only be GET or POST")
76+
77+
self._method = method
78+
6779
if retry is None:
6880
retry = Retry(
6981
total=MAX_REQUEST_RETRIES,
@@ -91,8 +103,9 @@ def check_prometheus_connection(self, params: dict = None) -> bool:
91103
sent along with the API request.
92104
:returns: (bool) True if the endpoint can be reached, False if cannot be reached.
93105
"""
94-
response = self._session.get(
95-
"{0}/".format(self.url),
106+
response = self._session.request(
107+
method="GET",
108+
url="{0}/".format(self.url),
96109
verify=self._session.verify,
97110
headers=self.headers,
98111
params=params,
@@ -165,8 +178,9 @@ def get_label_names(self, params: dict = None):
165178
(PrometheusApiClientException) Raises in case of non 200 response status code
166179
"""
167180
params = params or {}
168-
response = self._session.get(
169-
"{0}/api/v1/labels".format(self.url),
181+
response = self._session.request(
182+
method=self._method,
183+
url="{0}/api/v1/labels".format(self.url),
170184
verify=self._session.verify,
171185
headers=self.headers,
172186
params=params,
@@ -196,8 +210,9 @@ def get_label_values(self, label_name: str, params: dict = None):
196210
(PrometheusApiClientException) Raises in case of non 200 response status code
197211
"""
198212
params = params or {}
199-
response = self._session.get(
200-
"{0}/api/v1/label/{1}/values".format(self.url, label_name),
213+
response = self._session.request(
214+
method="GET",
215+
url="{0}/api/v1/label/{1}/values".format(self.url, label_name),
201216
verify=self._session.verify,
202217
headers=self.headers,
203218
params=params,
@@ -248,8 +263,9 @@ def get_current_metric_value(
248263
query = metric_name
249264

250265
# using the query API to get raw data
251-
response = self._session.get(
252-
"{0}/api/v1/query".format(self.url),
266+
response = self._session.request(
267+
method=self._method,
268+
url="{0}/api/v1/query".format(self.url),
253269
params={**{"query": query}, **params},
254270
verify=self._session.verify,
255271
headers=self.headers,
@@ -335,8 +351,9 @@ def get_metric_range_data(
335351
chunk_seconds = end - start
336352

337353
# using the query API to get raw data
338-
response = self._session.get(
339-
"{0}/api/v1/query".format(self.url),
354+
response = self._session.request(
355+
method=self._method,
356+
url="{0}/api/v1/query".format(self.url),
340357
params={
341358
**{
342359
"query": query + "[" + str(chunk_seconds) + "s" + "]",
@@ -443,8 +460,9 @@ def custom_query(self, query: str, params: dict = None, timeout: int = None):
443460
query = str(query)
444461
timeout = self._timeout if timeout is None else timeout
445462
# using the query API to get raw data
446-
response = self._session.get(
447-
"{0}/api/v1/query".format(self.url),
463+
response = self._session.request(
464+
method=self._method,
465+
url="{0}/api/v1/query".format(self.url),
448466
params={**{"query": query}, **params},
449467
verify=self._session.verify,
450468
headers=self.headers,
@@ -490,8 +508,9 @@ def custom_query_range(
490508
query = str(query)
491509
timeout = self._timeout if timeout is None else timeout
492510
# using the query_range API to get raw data
493-
response = self._session.get(
494-
"{0}/api/v1/query_range".format(self.url),
511+
response = self._session.request(
512+
method=self._method,
513+
url="{0}/api/v1/query_range".format(self.url),
495514
params={**{"query": query, "start": start, "end": end, "step": step}, **params},
496515
verify=self._session.verify,
497516
headers=self.headers,
@@ -626,8 +645,9 @@ def get_targets(self, state: str = None, scrape_pool: str = None):
626645
if scrape_pool:
627646
params['scrapePool'] = scrape_pool
628647

629-
response = self._session.get(
630-
"{0}/api/v1/targets".format(self.url),
648+
response = self._session.request(
649+
method="GET",
650+
url="{0}/api/v1/targets".format(self.url),
631651
verify=self._session.verify,
632652
headers=self.headers,
633653
params=params,
@@ -666,8 +686,9 @@ def get_target_metadata(self, target: dict[str, str], metric: str = None):
666686
",".join(f'{k}="{v}"' for k, v in target.items()) + "}"
667687
params['match_target'] = match_target
668688

669-
response = self._session.get(
670-
"{0}/api/v1/targets/metadata".format(self.url),
689+
response = self._session.request(
690+
method="GET",
691+
url="{0}/api/v1/targets/metadata".format(self.url),
671692
verify=self._session.verify,
672693
headers=self.headers,
673694
params=params,
@@ -708,8 +729,9 @@ def get_metric_metadata(self, metric: str, limit: int = None, limit_per_metric:
708729
if limit_per_metric:
709730
params['limit_per_metric'] = limit_per_metric
710731

711-
response = self._session.get(
712-
"{0}/api/v1/metadata".format(self.url),
732+
response = self._session.request(
733+
method="GET",
734+
url="{0}/api/v1/metadata".format(self.url),
713735
verify=self._session.verify,
714736
headers=self.headers,
715737
params=params,

tests/test_prometheus_connect.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,65 @@ def test_get_metric_metadata(self): # PR #295
219219
limited_per_metric = self.pc.get_metric_metadata(metric_name, limit_per_metric=1)
220220
self.assertIsInstance(limited_per_metric, list)
221221

222+
def test_method_argument_accepts_get_and_post(self):
223+
"""Test that PrometheusConnect accepts GET and POST for method argument, and raises on invalid values."""
224+
# Default should be GET
225+
pc_default = PrometheusConnect(url=self.prometheus_host, disable_ssl=False)
226+
self.assertEqual(pc_default._method, "GET")
227+
# Explicit GET
228+
pc_get = PrometheusConnect(url=self.prometheus_host, disable_ssl=False, method="GET")
229+
self.assertEqual(pc_get._method, "GET")
230+
# Explicit POST
231+
pc_post = PrometheusConnect(url=self.prometheus_host, disable_ssl=False, method="POST")
232+
self.assertEqual(pc_post._method, "POST")
233+
# Invalid type
234+
with self.assertRaises(TypeError):
235+
PrometheusConnect(url=self.prometheus_host, disable_ssl=False, method=123)
236+
# Invalid value
237+
with self.assertRaises(ValueError):
238+
PrometheusConnect(url=self.prometheus_host, disable_ssl=False, method="PUT")
239+
240+
def test_post_method_for_supported_functions(self):
241+
"""Test that PrometheusConnect uses POST for supported endpoints when method='POST', and returns a value."""
242+
pc = PrometheusConnect(url=self.prometheus_host, disable_ssl=False, method="POST")
243+
start_time = datetime.now() - timedelta(minutes=10)
244+
end_time = datetime.now()
245+
246+
# custom_query should use POST and return something (or raise)
247+
try:
248+
result = pc.custom_query("up")
249+
self.assertTrue(result is not None and result != [], "no metrics received from prometheus")
250+
except Exception as e:
251+
self.fail(f"custom_query('up') raised an unexpected exception: {e}")
252+
253+
# custom_query_range should use POST and return something (or raise)
254+
try:
255+
result = pc.custom_query_range("up", start_time=start_time, end_time=end_time, step="15")
256+
self.assertTrue(result is not None and result != [], "no metrics received from prometheus")
257+
except Exception as e:
258+
self.fail(f"custom_query_range('up', ...) raised an unexpected exception: {e}")
259+
260+
# get_label_names should use POST and return something (or raise)
261+
try:
262+
result = pc.get_label_names()
263+
self.assertTrue(result is not None and result != [], "no metrics received from prometheus")
264+
except Exception as e:
265+
self.fail(f"get_label_names() raised an unexpected exception: {e}")
266+
267+
# get_current_metric_value should use POST and return something (or raise)
268+
try:
269+
result = pc.get_current_metric_value("up")
270+
self.assertTrue(result is not None and result != [], "no metrics received from prometheus")
271+
except Exception as e:
272+
self.fail(f"get_current_metric_value('up') raised an unexpected exception: {e}")
273+
274+
# get_metric_range_data should use POST and return something (or raise)
275+
try:
276+
result = pc.get_metric_range_data("up", start_time=start_time, end_time=end_time)
277+
self.assertTrue(result is not None and result != [], "no metrics received from prometheus")
278+
except Exception as e:
279+
self.fail(f"get_metric_range_data('up', ...) raised an unexpected exception: {e}")
280+
222281

223282
class TestPrometheusConnectWithMockedNetwork(BaseMockedNetworkTestcase):
224283
"""Network is blocked in this testcase, see base class."""

0 commit comments

Comments
 (0)