-
-
Notifications
You must be signed in to change notification settings - Fork 90
Make pandas, numpy, and matplotlib optional dependencies with lazy loading and helpful error messages #309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
74b9105
d6dd79d
2447357
74f8772
33e0289
7832b91
b7f4b1e
08a7642
c02129e
1c8d53b
c2db4c4
cefc56c
abc430e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,6 @@ | |
| import os | ||
| import json | ||
| import logging | ||
| import numpy | ||
| from datetime import datetime, timedelta | ||
| import requests | ||
| from requests.adapters import HTTPAdapter | ||
|
|
@@ -569,6 +568,8 @@ def get_metric_aggregation( | |
| 'max': 6.009373 | ||
| } | ||
| """ | ||
| import numpy | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 I believe this suggestion applies for Metric, MetricPlot, and other classes as well
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added try/except blocks with helpful error messages in commit abc430e:
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: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| requests | ||
| dateparser |
| 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 = """ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Consider using |
||
| 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 = """ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
There was a problem hiding this comment.
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 doThere was a problem hiding this comment.
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.