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
4 changes: 2 additions & 2 deletions test_optional_deps/test_installed_with_geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_check_requires_no_opts():

def test_check_requires_only_viz():
"""ensure failure for checks which should require viz optional dependencies"""
with pytest.raises(ImportError):
with pytest.raises(ImportError, match=r'pip install "uxarray\[viz\]"'):
check_requires_only_viz()


Expand All @@ -31,5 +31,5 @@ def test_check_requires_only_geo():

def test_check_requires_viz_and_geo():
"""ensure failure for checks which should require both viz and geo optional dependencies"""
with pytest.raises(ImportError):
with pytest.raises(ImportError, match=r'pip install "uxarray\[viz\]"'):
check_requires_viz_and_geo()
6 changes: 4 additions & 2 deletions test_optional_deps/test_installed_with_no_opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@ def test_check_requires_no_opts():

def test_check_requires_only_viz():
"""ensure failure for checks which should require viz optional dependencies"""
with pytest.raises(ImportError):
with pytest.raises(ImportError, match=r'pip install "uxarray\[viz\]"'):
check_requires_only_viz()


def test_check_requires_only_geo():
"""ensure failure for checks which should require geo optional dependencies"""
with pytest.raises(ImportError):
with pytest.raises(ImportError, match=r'pip install "uxarray\[geo\]"'):
check_requires_only_geo()


def test_check_requires_viz_and_geo():
"""ensure failure for checks which should require both viz and geo optional dependencies"""
with pytest.raises(ImportError):
# ^no match "uxarray[geo,viz]" here; might crash in a viz-only or a geo-only method,
# even though the check itself ultimately requires both viz and geo.
check_requires_viz_and_geo()
4 changes: 2 additions & 2 deletions test_optional_deps/test_installed_with_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ def test_check_requires_only_viz():

def test_check_requires_only_geo():
"""ensure failure for checks which should require geo optional dependencies"""
with pytest.raises(ImportError):
with pytest.raises(ImportError, match=r'pip install "uxarray\[geo\]"'):
check_requires_only_geo()


def test_check_requires_viz_and_geo():
"""ensure failure for checks which should require both viz and geo optional dependencies"""
with pytest.raises(ImportError):
with pytest.raises(ImportError, match=r'pip install "uxarray\[geo\]"'):
check_requires_viz_and_geo()
3 changes: 3 additions & 0 deletions uxarray/core/dataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from uxarray.plot.accessor import UxDataArrayPlotAccessor
from uxarray.remap.accessor import RemapAccessor
from uxarray.subset import DataArraySubsetAccessor
from uxarray.utils.imports import _raise_hint_if_optional_deps_missing

if TYPE_CHECKING:
import cartopy.crs as ccrs
Expand Down Expand Up @@ -473,6 +474,7 @@ def to_raster(
>>> ax.imshow(raster, origin="lower", extent=ax.get_xlim() + ax.get_ylim())

"""
_raise_hint_if_optional_deps_missing("cartopy")
from cartopy.mpl.geoaxes import GeoAxes

from uxarray.constants import INT_DTYPE
Expand Down Expand Up @@ -517,6 +519,7 @@ def _is_default_extent() -> bool:

if _is_default_extent():
try:
_raise_hint_if_optional_deps_missing("cartopy")
import cartopy.crs as ccrs

lon_min = float(self.uxgrid.node_lon.min(skipna=True).values)
Expand Down
3 changes: 3 additions & 0 deletions uxarray/cross_sections/sample.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import numpy as np
from numba import njit, prange

from uxarray.utils.imports import _raise_hint_if_optional_deps_missing


@njit(parallel=True)
def _fill_numba(flat_orig, face_idx, n_face, n_steps):
Expand All @@ -16,6 +18,7 @@ def _fill_numba(flat_orig, face_idx, n_face, n_steps):
def sample_geodesic(
start: tuple[float, float], end: tuple[float, float], steps: int
) -> tuple[np.ndarray, np.ndarray]:
_raise_hint_if_optional_deps_missing("pyproj")
from pyproj import Geod

lon0, lat0 = start
Expand Down
7 changes: 7 additions & 0 deletions uxarray/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,10 @@ class GridsMismatchError(ValueError):

class YacNotAvailableError(RuntimeError):
"""Raised when the YAC backend is requested but unavailable."""


# # # ----- Miscellaneous Errors ----- # # #


class OptionalDependencyNotFoundError(ModuleNotFoundError):
"""indicates functionality relies on a not-yet-installed optional dependency."""
8 changes: 8 additions & 0 deletions uxarray/grid/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
)
from uxarray.grid.point_in_face import _face_contains_point
from uxarray.grid.utils import _get_cartesian_face_edge_nodes
from uxarray.utils.imports import _raise_hint_if_optional_deps_missing

POLE_POINTS_XYZ = {
"North": np.array([0.0, 0.0, 1.0]),
Expand Down Expand Up @@ -116,6 +117,7 @@ def _build_polygon_shells(
):
"""Builds an array of polygon shells, which can be used with Shapely to
construct polygons."""
_raise_hint_if_optional_deps_missing("cartopy")
import cartopy.crs as ccrs

closed_face_nodes = _pad_closed_face_nodes(
Expand Down Expand Up @@ -145,6 +147,7 @@ def _correct_central_longitude(node_lon, node_lat, projection):
"""Shifts the central longitude of an unstructured grid, which moves the
antimeridian when visualizing, which is used when projections have a
central longitude other than 0.0."""
_raise_hint_if_optional_deps_missing("cartopy")
import cartopy.crs as ccrs

if projection:
Expand All @@ -169,6 +172,7 @@ def _correct_central_longitude(node_lon, node_lat, projection):
def _grid_to_polygon_geodataframe(grid, periodic_elements, projection, project, engine):
"""Converts the faces of a ``Grid`` into a ``spatialpandas.GeoDataFrame``
or ``geopandas.GeoDataFrame`` with a geometry column of polygons."""
_raise_hint_if_optional_deps_missing("geopandas", "spatialpandas")
import geopandas
import shapely
import spatialpandas
Expand Down Expand Up @@ -260,6 +264,7 @@ def _build_geodataframe_without_antimeridian(
"""Builds a ``spatialpandas.GeoDataFrame`` or
``geopandas.GeoDataFrame``excluding any faces that cross the
antimeridian."""
_raise_hint_if_optional_deps_missing("geopandas", "spatialpandas")
import geopandas
import shapely
import spatialpandas
Expand Down Expand Up @@ -296,6 +301,7 @@ def _build_geodataframe_with_antimeridian(
):
"""Builds a ``spatialpandas.GeoDataFrame`` or ``geopandas.GeoDataFrame``
including any faces that cross the antimeridian."""
_raise_hint_if_optional_deps_missing("geopandas", "spatialpandas")
import geopandas
import spatialpandas
from spatialpandas.geometry import MultiPolygonArray
Expand Down Expand Up @@ -441,6 +447,7 @@ def _grid_to_matplotlib_polycollection(
grid, periodic_elements, projection=None, **kwargs
):
"""Constructs and returns a ``matplotlib.collections.PolyCollection``"""
_raise_hint_if_optional_deps_missing("cartopy", "matplotlib")
import cartopy.crs as ccrs
from matplotlib.collections import PolyCollection

Expand Down Expand Up @@ -647,6 +654,7 @@ def _grid_to_matplotlib_linecollection(
grid, periodic_elements, projection=None, **kwargs
):
"""Constructs and returns a ``matplotlib.collections.LineCollection``"""
_raise_hint_if_optional_deps_missing("cartopy", "matplotlib")
import cartopy.crs as ccrs
from matplotlib.collections import LineCollection

Expand Down
3 changes: 2 additions & 1 deletion uxarray/grid/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
from uxarray.io.utils import _parse_grid_type
from uxarray.plot.accessor import GridPlotAccessor
from uxarray.subset import GridSubsetAccessor
from uxarray.utils.imports import _raise_hint_if_optional_deps_missing

if TYPE_CHECKING:
import cartopy.crs as ccrs
Expand Down Expand Up @@ -2296,7 +2297,7 @@ def to_geodataframe(
gdf : spatialpandas.GeoDataFrame or geopandas.GeoDataFrame
The output ``GeoDataFrame`` with a filled out "geometry" column of polygons.
"""

_raise_hint_if_optional_deps_missing("spatialpandas")
from spatialpandas import GeoDataFrame

if engine not in ["spatialpandas", "geopandas"]:
Expand Down
3 changes: 2 additions & 1 deletion uxarray/io/_geopandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from uxarray.constants import INT_DTYPE, INT_FILL_VALUE, WGS84_CRS
from uxarray.conventions import ugrid
from uxarray.utils.imports import _raise_hint_if_optional_deps_missing


def _read_geodataframe(filepath, driver=None, **kwargs):
Expand Down Expand Up @@ -63,7 +64,7 @@ def _gpd_read(filepath, driver=None, **kwargs):
int
Maximum number of nodes in a polygon/multipolygon.
"""

_raise_hint_if_optional_deps_missing("geopandas")
import geopandas as gpd

try:
Expand Down
3 changes: 3 additions & 0 deletions uxarray/io/_healpix.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import uxarray.conventions.ugrid as ugrid
from uxarray.constants import INT_DTYPE
from uxarray.utils.imports import _raise_hint_if_optional_deps_missing


def get_zoom_from_cells(cells):
Expand Down Expand Up @@ -67,6 +68,7 @@ def pix2corner_ang(
----
This will be updated when https://github.com/ntessore/healpix/issues/66 is implemented.
"""
_raise_hint_if_optional_deps_missing("healpix")
import healpix as hp

if nest:
Expand Down Expand Up @@ -103,6 +105,7 @@ def _pixels_to_ugrid(zoom, nest):
A dataset containing pixel longitude and latitude coordinates along with related attributes.

"""
_raise_hint_if_optional_deps_missing("healpix")
import healpix as hp

ds = xr.Dataset()
Expand Down
4 changes: 4 additions & 0 deletions uxarray/plot/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from uxarray.grid import Grid

from uxarray.plot.utils import backend as plotting_backend
from uxarray.utils.imports import _raise_hint_if_optional_deps_missing

# import speedup trick:
# code here uses obj.hvplot, which requires import hvplot.pandas and/or hvplot.xarray.
Expand All @@ -30,6 +31,7 @@ def _ensure_hvplot_imported() -> None:
"""
global _IMPORTED_HVPLOT
if not _IMPORTED_HVPLOT:
_raise_hint_if_optional_deps_missing("holoviews", "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.
Expand Down Expand Up @@ -244,6 +246,7 @@ def edges(
gdf.hvplot.paths : hvplot.paths
A paths plot of the edges of the unstructured grid
"""
_raise_hint_if_optional_deps_missing("cartopy")
import cartopy.crs as ccrs

plotting_backend.assign(backend)
Expand Down Expand Up @@ -445,6 +448,7 @@ def polygons(
gdf.hvplot.polygons : hvplot.polygons
A shaded polygon plot
"""
_raise_hint_if_optional_deps_missing("cartopy")
import cartopy.crs as ccrs

plotting_backend.assign(backend)
Expand Down
2 changes: 2 additions & 0 deletions uxarray/plot/matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import numpy as np

from uxarray.errors import DimensionError
from uxarray.utils.imports import _raise_hint_if_optional_deps_missing

if TYPE_CHECKING:
from cartopy.mpl.geoaxes import GeoAxes
Expand Down Expand Up @@ -126,6 +127,7 @@ def _get_points_from_axis(ax: GeoAxes, *, pixel_ratio: float = 1):
ny : int
Number of rows (height) in the pixel grid.
"""
_raise_hint_if_optional_deps_missing("cartopy")
import cartopy.crs as ccrs

ax_attrs = _RasterAxAttrs.from_ax(ax, pixel_ratio=pixel_ratio)
Expand Down
6 changes: 6 additions & 0 deletions uxarray/plot/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from uxarray.utils.imports import _raise_hint_if_optional_deps_missing


class HoloviewsBackend:
"""Compare and set the HoloViews plotting backend."""

Expand All @@ -6,13 +9,15 @@ def __init__(self):

def assign(self, backend: str):
"""Assign a HoloViews backend, one of 'matplotlib', 'bokeh'."""
_raise_hint_if_optional_deps_missing("holoviews")
import holoviews as hv

if backend not in ["bokeh", "matplotlib", None]:
raise ValueError(
f"Unsupported backend. Expected one of ['bokeh', 'matplotlib'], but received {backend}"
)
if backend is not None and backend != hv.Store.current_backend:
_raise_hint_if_optional_deps_missing("matplotlib")
import matplotlib as mpl

# Capture the live backend now (not once at init) so a backend the
Expand Down Expand Up @@ -63,6 +68,7 @@ def reset_mpl_backend(self):
except Exception:
pass

_raise_hint_if_optional_deps_missing("matplotlib")
import matplotlib as mpl

mpl.use(self.matplotlib_backend)
Expand Down
Loading