Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ To install the latest release:

`pip install prometheus-api-client`

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

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

**Note:** Starting from version 0.7.0, numpy and matplotlib are optional dependencies. Pandas is included as a core dependency.
The lazy import mechanism ensures that `PrometheusConnect` can be imported without loading numpy until `get_metric_aggregation()` is called, which reduces initial memory footprint.

To install only specific extras:
- For numpy support: `pip install prometheus-api-client[numpy]`
- 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
3 changes: 2 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,8 @@ def get_metric_aggregation(
'max': 6.009373
}
"""
import numpy
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we put some try/except here? If a user has installed the minimal version and ties calling this function, they will get a generic import error. IMO we should explicitly mention that they’ll need to install the [numpy] or [full] version.

I believe this suggestion applies for Metric, MetricPlot, and other classes as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added try/except blocks with helpful error messages in commit abc430e:

  • NumPy import in get_metric_aggregation() suggests installing [analytics] or [all]
  • Pandas imports in Metric, MetricRangeDataFrame, and MetricSnapshotDataFrame suggest installing [dataframe] or [all]

All error messages clearly guide users to the correct installation command.


if not isinstance(operations, list):
raise TypeError("Operations can be only of type list")
if len(operations) == 0:
Expand Down
3 changes: 3 additions & 0 deletions requirements-core.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
requests
dateparser
pandas>=1.4.0
9 changes: 7 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,11 @@ 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={
"numpy": ["numpy"],
"plot": ["matplotlib"],
"all": ["numpy", "matplotlib"],
},
packages=setuptools.find_packages(),
package_data={"prometheus-api-client": ["py.typed"]},
tests_require=["httmock"],
Expand Down
88 changes: 88 additions & 0 deletions tests/test_lazy_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Test lazy imports to ensure pandas/matplotlib are not loaded unnecessarily."""
import unittest
import sys
import subprocess
import importlib


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

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)
"""
result = subprocess.run(
[sys.executable, '-c', code],
capture_output=True,
text=True
)

if result.returncode == 1:
self.fail("pandas should not be loaded when importing PrometheusConnect")
elif result.returncode == 2:
self.fail("matplotlib should not be loaded when importing PrometheusConnect")
elif result.returncode == 3:
self.fail("numpy should not be loaded when importing PrometheusConnect")
elif result.returncode != 0:
self.fail(f"Subprocess failed with code {result.returncode}: {result.stderr}")

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)
"""
result = subprocess.run(
[sys.executable, '-c', code],
capture_output=True,
text=True
)

if result.returncode == 1:
self.fail("numpy should not be loaded when instantiating PrometheusConnect")
elif result.returncode == 2:
self.fail("PrometheusConnect should be instantiated successfully")
elif result.returncode != 0:
self.fail(f"Subprocess failed with code {result.returncode}: {result.stderr}")

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