Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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.6.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 numpy support: `pip install prometheus-api-client[numpy]`
- For plotting support: `pip install prometheus-api-client[plot]`
Comment on lines 21 to 24
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we consider making these installations use-case oriented instead of package oriented, e.g. something like [analytics] instead of [numpy]? It's not obvious to me (let alone end users) what installing with [numpy] would enable me to do

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 use-case oriented [analytics] extra in commit abc430e. Updated README to highlight this instead of [numpy]. The [numpy] extra still exists for backward compatibility.


To install directly from this branch:

`pip install https://github.com/4n4nd/prometheus-api-client-python/zipball/master`
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
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
10 changes: 8 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,12 @@ 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"],
"all": ["pandas>=1.4.0", "numpy", "matplotlib"],
},
packages=setuptools.find_packages(),
package_data={"prometheus-api-client": ["py.typed"]},
tests_require=["httmock"],
Expand Down
81 changes: 81 additions & 0 deletions tests/test_lazy_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Test lazy imports to ensure pandas/matplotlib are not loaded unnecessarily."""
import unittest
import sys
import importlib


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

@staticmethod
def _remove_modules(module_names):
"""Remove specified modules and their submodules from sys.modules.

Args:
module_names: List of module names to remove
"""
for module_name in module_names:
modules_to_remove = [
key for key in sys.modules.keys()
if key == module_name or key.startswith(module_name + '.')
]
for module in modules_to_remove:
del sys.modules[module]

@staticmethod
def _is_module_loaded(module_name):
"""Check if a module is loaded in sys.modules.

Args:
module_name: Name of the module to check

Returns:
bool: True if module is loaded, False otherwise
"""
return any(m == module_name or m.startswith(module_name + '.') for m in sys.modules.keys())

def test_prometheus_connect_import_without_pandas_matplotlib_numpy(self):
"""Test that importing PrometheusConnect doesn't load pandas, matplotlib, or numpy."""
# Remove any previously loaded modules
self._remove_modules(['prometheus_api_client', 'numpy', 'pandas', 'matplotlib'])

# Import PrometheusConnect
from prometheus_api_client import PrometheusConnect

# Check that pandas, matplotlib, and numpy are not loaded
self.assertFalse(self._is_module_loaded('pandas'),
"pandas should not be loaded when importing PrometheusConnect")
self.assertFalse(self._is_module_loaded('matplotlib'),
"matplotlib should not be loaded when importing PrometheusConnect")
self.assertFalse(self._is_module_loaded('numpy'),
"numpy should not be loaded when importing PrometheusConnect")

def test_prometheus_connect_instantiation_without_numpy(self):
"""Test that PrometheusConnect can be instantiated without loading numpy."""
# Remove any previously loaded modules
self._remove_modules(['prometheus_api_client', 'numpy'])

# Import and instantiate PrometheusConnect
from prometheus_api_client import PrometheusConnect
pc = PrometheusConnect(url='http://test.local:9090')

# Check that numpy is still not loaded after instantiation
self.assertFalse(self._is_module_loaded('numpy'),
"numpy should not be loaded when instantiating PrometheusConnect")
self.assertIsNotNone(pc, "PrometheusConnect should be instantiated successfully")

def test_metric_import_loads_pandas(self):
"""Test that importing Metric does load pandas (expected behavior)."""
# Remove any previously loaded modules
self._remove_modules(['prometheus_api_client', 'pandas'])

# Import Metric
from prometheus_api_client import Metric

# Check that pandas is loaded (this is expected for Metric)
self.assertTrue(self._is_module_loaded('pandas'),
"pandas should be loaded when importing Metric")


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