Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ To install the latest release:

`pip install prometheus-api-client`

To install with all optional dependencies (pandas, numpy, matplotlib):

`pip install prometheus-api-client[all]`

**Note:** Starting from version 0.7.0, pandas, numpy, and matplotlib are optional dependencies.
If you only need `PrometheusConnect` without DataFrame support or plotting capabilities, you can install the minimal version which significantly reduces memory footprint and installation time, especially on Alpine-based Docker images.

To install only specific extras:
- For DataFrame support: `pip install prometheus-api-client[dataframe]`
- For analytics/aggregation operations: `pip install prometheus-api-client[analytics]`
- For plotting support: `pip install prometheus-api-client[plot]`

To install directly from this branch:

`pip install https://github.com/4n4nd/prometheus-api-client-python/zipball/master`
Expand Down
2 changes: 1 addition & 1 deletion prometheus_api_client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""A collection of tools to collect and manipulate prometheus metrics."""

__title__ = "prometheus-connect"
__version__ = "0.6.0"
__version__ = "0.7.0"

from .exceptions import PrometheusApiClientException, MetricValueConversionError
def __getattr__(name):
Expand Down
10 changes: 9 additions & 1 deletion prometheus_api_client/metric.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
"""A Class for metric object."""
from copy import deepcopy
import datetime
import pandas

try:
import pandas
except ImportError as e:
raise ImportError(
"Pandas is required for Metric class. "
"Please install it with: pip install prometheus-api-client[dataframe] "
"or pip install prometheus-api-client[all]"
) from e

from prometheus_api_client.exceptions import MetricValueConversionError

Expand Down
12 changes: 10 additions & 2 deletions prometheus_api_client/metric_range_df.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
"""A pandas.DataFrame subclass for Prometheus range vector responses."""
from pandas import DataFrame, to_datetime
from pandas._typing import Axes, Dtype
try:
from pandas import DataFrame, to_datetime
from pandas._typing import Axes, Dtype
except ImportError as e:
raise ImportError(
"Pandas is required for MetricRangeDataFrame class. "
"Please install it with: pip install prometheus-api-client[dataframe] "
"or pip install prometheus-api-client[all]"
) from e

from typing import Optional, Sequence

from prometheus_api_client.exceptions import MetricValueConversionError
Expand Down
12 changes: 10 additions & 2 deletions prometheus_api_client/metric_snapshot_df.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
"""A pandas.DataFrame subclass for Prometheus query response."""
from pandas import DataFrame, to_datetime
from pandas._typing import Axes, Dtype
try:
from pandas import DataFrame, to_datetime
from pandas._typing import Axes, Dtype
except ImportError as e:
raise ImportError(
"Pandas is required for MetricSnapshotDataFrame class. "
"Please install it with: pip install prometheus-api-client[dataframe] "
"or pip install prometheus-api-client[all]"
) from e

from typing import Optional, Sequence

from prometheus_api_client.exceptions import MetricValueConversionError
Expand Down
10 changes: 9 additions & 1 deletion prometheus_api_client/prometheus_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import os
import json
import logging
import numpy
from datetime import datetime, timedelta
import requests
from requests.adapters import HTTPAdapter
Expand Down Expand Up @@ -569,6 +568,15 @@ def get_metric_aggregation(
'max': 6.009373
}
"""
try:
import numpy
except ImportError as e:
raise ImportError(
"NumPy is required for metric aggregation operations. "
"Please install it with: pip install prometheus-api-client[analytics] "
"or pip install prometheus-api-client[all]"
) from e

if not isinstance(operations, list):
raise TypeError("Operations can be only of type list")
if len(operations) == 0:
Expand Down
2 changes: 2 additions & 0 deletions requirements-core.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests
dateparser
11 changes: 9 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@


def get_install_requires():
"""Get requirements from requirements.txt."""
with open("requirements.txt", "r") as requirements_file:
"""Get core requirements from requirements-core.txt."""
with open("requirements-core.txt", "r") as requirements_file:
res = requirements_file.readlines()
return [req.split(" ", maxsplit=1)[0] for req in res if req]

Expand Down Expand Up @@ -36,6 +36,13 @@ def get_version():
long_description_content_type="text/markdown",
url="https://github.com/4n4nd/prometheus-api-client-python",
install_requires=get_install_requires(),
extras_require={
"dataframe": ["pandas>=1.4.0"],
"numpy": ["numpy"],
"plot": ["matplotlib"],
"analytics": ["numpy"],
"all": ["pandas>=1.4.0", "numpy", "matplotlib"],
},
packages=setuptools.find_packages(),
package_data={"prometheus-api-client": ["py.typed"]},
tests_require=["httmock"],
Expand Down
100 changes: 100 additions & 0 deletions tests/test_lazy_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Test lazy imports to ensure pandas/matplotlib are not loaded unnecessarily."""
import unittest
import sys
import subprocess


class TestLazyImports(unittest.TestCase):
"""Test that PrometheusConnect can be imported without loading heavy dependencies."""

def _run_in_subprocess(self, code, fail_map):
"""Run code in a subprocess and check exit codes against fail_map.

Args:
code: Python code to execute in subprocess
fail_map: Dictionary mapping exit codes to error messages

Raises:
AssertionError: If subprocess exits with a code in fail_map or any non-zero code
"""
result = subprocess.run(
[sys.executable, '-c', code],
capture_output=True,
text=True
)

if result.returncode in fail_map:
self.fail(fail_map[result.returncode])
elif result.returncode != 0:
# Include both stdout and stderr for better debugging
output = []
if result.stdout:
output.append(f"stdout: {result.stdout}")
if result.stderr:
output.append(f"stderr: {result.stderr}")
output_str = "\n".join(output) if output else "no output"
self.fail(f"Subprocess failed with code {result.returncode}: {output_str}")

def test_prometheus_connect_import_without_pandas_matplotlib_numpy(self):
"""Test that importing PrometheusConnect doesn't load pandas, matplotlib, or numpy."""
# Run in a subprocess to avoid affecting other tests
code = """

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Consider using textwrap.dedent for multi-line text.

import sys
from prometheus_api_client import PrometheusConnect

# Check that pandas, matplotlib, and numpy are not loaded
pandas_loaded = any(m == 'pandas' or m.startswith('pandas.') for m in sys.modules.keys())
matplotlib_loaded = any(m == 'matplotlib' or m.startswith('matplotlib.') for m in sys.modules.keys())
numpy_loaded = any(m == 'numpy' or m.startswith('numpy.') for m in sys.modules.keys())

if pandas_loaded:
sys.exit(1)
if matplotlib_loaded:
sys.exit(2)
if numpy_loaded:
sys.exit(3)
sys.exit(0)
"""
fail_map = {
1: "pandas should not be loaded when importing PrometheusConnect",
2: "matplotlib should not be loaded when importing PrometheusConnect",
3: "numpy should not be loaded when importing PrometheusConnect",
}
self._run_in_subprocess(code, fail_map)

def test_prometheus_connect_instantiation_without_numpy(self):
"""Test that PrometheusConnect can be instantiated without loading numpy."""
# Run in a subprocess to avoid affecting other tests
code = """

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

import sys
from prometheus_api_client import PrometheusConnect

pc = PrometheusConnect(url='http://test.local:9090')

# Check that numpy is still not loaded after instantiation
numpy_loaded = any(m == 'numpy' or m.startswith('numpy.') for m in sys.modules.keys())

if numpy_loaded:
sys.exit(1)
if pc is None:
sys.exit(2)
sys.exit(0)
"""
fail_map = {
1: "numpy should not be loaded when instantiating PrometheusConnect",
2: "PrometheusConnect should be instantiated successfully",
}
self._run_in_subprocess(code, fail_map)

def test_metric_import_loads_pandas(self):
"""Test that importing Metric does load pandas (expected behavior)."""
# This test doesn't remove modules, so it won't cause reload issues
from prometheus_api_client import Metric

# Check that pandas is loaded (this is expected for Metric)
pandas_loaded = any(m == 'pandas' or m.startswith('pandas.') for m in sys.modules.keys())
self.assertTrue(pandas_loaded, "pandas should be loaded when importing Metric")


if __name__ == '__main__':
unittest.main()
Loading