Skip to content
Open
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
41 changes: 41 additions & 0 deletions test/test_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Purpose: tests related to dependencies
e.g.: hvplot is optional, so it shouldn't be imported by default.
Related issues: #1224, #1539
"""
import subprocess
import sys


def _assert_not_imported_after_import_uxarray(module_name):
"""Run ``import uxarray`` in a fresh interpreter and assert that
``module_name`` was not imported as a side effect.

A subprocess is used so the check is immune to test ordering: other tests
in the same session may have already imported optional deps (e.g. plotting
tests import ``hvplot``), which would pollute this process's ``sys.modules``.
"""
code = (
"import sys; import uxarray; "
f"assert {module_name!r} not in sys.modules, "
f"'{module_name} was imported by `import uxarray`'"
)
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"{module_name} should not be imported by default:\n{result.stderr}"
)


def test_hvplot_optional():
"""Test that hvplot is actually optional and not imported by default.
(hvplot in particular is "slow" to import (~1s to import hvplot.pandas),
so it should not be imported until actually plotting something.)
"""
_assert_not_imported_after_import_uxarray("hvplot")


# TODO: similar tests for cartopy, holoviews, and other optional deps.
37 changes: 34 additions & 3 deletions uxarray/plot/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,39 @@
from uxarray.core.dataset import UxDataset
from uxarray.grid import Grid

import hvplot.pandas
import hvplot.xarray

from uxarray.plot.utils import backend as plotting_backend

# import speedup trick:
# code here uses obj.hvplot, which requires import hvplot.pandas and/or hvplot.xarray.
# But, those lines are "slow", so instead of doing them at top-level here,
# only run them when actually creating a plot accessor, inside the code later in this file.
# (timing test: import uxarray takes ~1.7 seconds before change; 0.7 seconds after change.)

_IMPORTED_HVPLOT = False


def _ensure_hvplot_imported() -> None:
"""Import hvplot.pandas and hvplot.xarray if not already imported.
(importing hvplot.pandas may output horizontal gray bar(s) in Jupyter, so only ever do it once.)
"""
global _IMPORTED_HVPLOT
if not _IMPORTED_HVPLOT:
# workaround for hvplot issue #1735;
# import hvplot.pandas and hvplot.xarray always adjust the hvplot.extension().
# To respect previously-setup extension value, need to remember and restore it.
import hvplot

_store = getattr(hvplot, "Store", None)
if _store is not None:
_backend_orig = _store.current_backend
import hvplot.pandas
import hvplot.xarray

if _store is not None:
hvplot.extension(_backend_orig)

_IMPORTED_HVPLOT = True


class GridPlotAccessor:
"""Plotting accessor for :class:`Grid`.
Expand All @@ -36,6 +64,7 @@ class GridPlotAccessor:
__slots__ = ("_uxgrid",)

def __init__(self, uxgrid: Grid) -> None:
_ensure_hvplot_imported()
self._uxgrid = uxgrid

def __call__(self, **kwargs) -> Any:
Expand Down Expand Up @@ -340,6 +369,7 @@ class UxDataArrayPlotAccessor:
__slots__ = ("_uxda",)

def __init__(self, uxda: UxDataArray) -> None:
_ensure_hvplot_imported()
self._uxda = uxda

def __call__(self, **kwargs) -> Any:
Expand Down Expand Up @@ -521,6 +551,7 @@ class UxDatasetPlotAccessor:
__slots__ = ("_uxds",)

def __init__(self, uxds: UxDataset) -> None:
_ensure_hvplot_imported()
self._uxds = uxds

def __call__(self, **kwargs) -> Any:
Expand Down
Loading