diff --git a/examples/conftest.py b/examples/conftest.py index b8af528b5f..9341fbe427 100644 --- a/examples/conftest.py +++ b/examples/conftest.py @@ -9,7 +9,7 @@ system = platform.system() py_version = sys.version_info[:2] -PD2 = Version(pd.__version__) >= Version("2.0") +PANDAS_GE_2_0_0 = Version(pd.__version__).release >= (2, 0, 0) # Having "OMP_NUM_THREADS"=1, set as an environment variable, can be needed # to avoid crashing when running tests with pytest-xdist on Windows. @@ -30,7 +30,7 @@ # 2023-07-14 with following error: # ValueError: Buffer dtype mismatch, expected 'const int64_t' but got 'int' -if PD2 and system == "Windows": +if PANDAS_GE_2_0_0 and system == "Windows": collect_ignore_glob += [ "gallery/demos/bokeh/point_draw_triangulate.ipynb", "reference/elements/*/TriMesh.ipynb", @@ -45,7 +45,7 @@ ] # First available in Bokeh 3.2.0 -if Version(bokeh.__version__) < Version("3.2.0"): +if Version(bokeh.__version__).release < (3, 2, 0): collect_ignore_glob += [ "reference/elements/bokeh/HLines.ipynb", "reference/elements/bokeh/HSpans.ipynb", diff --git a/holoviews/core/data/dask.py b/holoviews/core/data/dask.py index 4d00343812..2b021f5a54 100644 --- a/holoviews/core/data/dask.py +++ b/holoviews/core/data/dask.py @@ -218,7 +218,7 @@ def groupby(cls, dataset, dimensions, container_type, group_type, **kwargs): for coord in indices: if any(isinstance(c, float) and np.isnan(c) for c in coord): continue - if len(coord) == 1 and not util.PANDAS_GE_220: + if len(coord) == 1 and not util.PANDAS_GE_2_2_0: coord = coord[0] group = group_type(groupby.get_group(coord), **group_kwargs) data.append((coord, group)) diff --git a/holoviews/core/data/ibis.py b/holoviews/core/data/ibis.py index 1023067a75..06011025b0 100644 --- a/holoviews/core/data/ibis.py +++ b/holoviews/core/data/ibis.py @@ -1,9 +1,7 @@ import sys from collections.abc import Iterable -from functools import lru_cache import numpy as np -from packaging.version import Version from .. import util from ..element import Element @@ -11,25 +9,10 @@ from .interface import DataError, Interface from .util import cached - -@lru_cache -def ibis_version(): - import ibis - return Version(ibis.__version__) - - -@lru_cache -def ibis4(): - return ibis_version() >= Version("4.0") - - -@lru_cache -def ibis5(): - return ibis_version() >= Version("5.0") - -@lru_cache -def ibis9_5(): - return ibis_version() >= Version("9.5") +IBIS_VERSION = util._no_import_version("ibis-framework") +IBIS_GE_4_0_0 = IBIS_VERSION >= (4, 0, 0) +IBIS_GE_5_0_0 = IBIS_VERSION >= (5, 0, 0) +IBIS_GE_9_5_0 = IBIS_VERSION >= (9, 5, 0) class IbisInterface(Interface): @@ -120,7 +103,7 @@ def persist(cls, dataset): @cached def length(self, dataset): # Get the length by counting the length of an empty query. - if ibis4(): + if IBIS_GE_4_0_0: return dataset.data.count().execute() else: return dataset.data[[]].count().execute() @@ -129,7 +112,7 @@ def length(self, dataset): @cached def nonzero(cls, dataset): # Make an empty query to see if a row is returned. - if ibis4(): + if IBIS_GE_4_0_0: return bool(len(dataset.data.head(1).execute())) else: return bool(len(dataset.data[[]].head(1).execute())) @@ -163,7 +146,7 @@ def values( dimension = dataset.get_dimension(dimension, strict=True) data = dataset.data[dimension.name] if ( - ibis_version() > Version("3") + IBIS_VERSION > (3, 0, 0) and isinstance(data, ibis.expr.types.AnyColumn) and not expanded ): @@ -178,12 +161,12 @@ def histogram(cls, expr, bins, density=True, weights=None): bins = [int(v) if bins.dtype.kind in 'iu' else float(v) for v in bins] binned = expr.bucket(bins).name('bucket') hist = np.zeros(len(bins)-1) - if ibis4(): + if IBIS_GE_4_0_0: hist_bins = binned.value_counts().order_by('bucket').execute() else: # sort_by will be removed in Ibis 5.0 hist_bins = binned.value_counts().sort_by('bucket').execute() - metric_name = 'bucket_count' if ibis5() else 'count' + metric_name = 'bucket_count' if IBIS_GE_5_0_0 else 'count' for b, v in zip(hist_bins['bucket'], hist_bins[metric_name]): if np.isnan(b): continue @@ -212,11 +195,11 @@ def dtype(cls, dataset, dimension): def sort(cls, dataset, by=None, reverse=False): if by is None: by = [] - if ibis_version() >= Version("6.0"): + if IBIS_VERSION >= (6, 0, 0): import ibis order = ibis.desc if reverse else ibis.asc return dataset.data.order_by([order(dataset.get_dimension(x).name) for x in by]) - elif ibis4(): + elif IBIS_GE_4_0_0: # Tuple syntax will be removed in Ibis 7.0: # https://github.com/ibis-project/ibis/pull/6082 return dataset.data.order_by([(dataset.get_dimension(x).name, not reverse) for x in by]) @@ -245,7 +228,7 @@ def _index_ibis_table(cls, data): if "hv_row_id__" in data.columns: return data - if ibis4(): + if IBIS_GE_4_0_0: return data.mutate(hv_row_id__=ibis.row_number()) elif cls.is_rowid_zero_indexed(data): return data.mutate(hv_row_id__=data.rowid()) @@ -290,7 +273,7 @@ def iloc(cls, dataset, index): rows = [rows] data = data.filter([data.hv_row_id__.isin(rows)]) - if ibis4(): + if IBIS_GE_4_0_0: return data.drop("hv_row_id__") else: # Passing a sequence of fields to `drop` will be removed in Ibis 5.0 @@ -302,7 +285,7 @@ def unpack_scalar(cls, dataset, data): Given a dataset object and data in the appropriate format for the interface, return a simple scalar. """ - if ibis4(): + if IBIS_GE_4_0_0: count = data.count().execute() else: count = data[[]].count().execute() @@ -326,7 +309,7 @@ def groupby(cls, dataset, dimensions, container_type, group_type, **kwargs): group_by = [d.name for d in index_dims] # execute a query against the table to find the unique groups. - if ibis4(): + if IBIS_GE_4_0_0: groups = dataset.data.group_by(group_by).aggregate().execute() else: # groupby will be removed in Ibis 5.0 @@ -392,7 +375,7 @@ def select(cls, dataset, selection_mask=None, **selection): data["hv_row_id__"].isin(list(map(int, selection_mask))) ) - if ibis4(): + if IBIS_GE_4_0_0: data = data.drop("hv_row_id__") else: # Passing a sequence of fields to `drop` will be removed in Ibis 5.0 @@ -462,7 +445,7 @@ def sample(cls, dataset, samples=None): if all(util.isscalar(s) or len(s) == 1 for s in samples): items = [s[0] if isinstance(s, tuple) else s for s in samples] subset = data[dims[0].name].isin(items) - return data.filter(subset) if ibis9_5() else data[subset] + return data.filter(subset) if IBIS_GE_9_5_0 else data[subset] predicates = None for sample in samples: @@ -508,7 +491,7 @@ def aggregate(cls, dataset, dimensions, function, **kwargs): }.get(function, function) if len(dimensions): - if ibis4(): + if IBIS_GE_4_0_0: selection = new.group_by(columns) else: # groupby will be removed in Ibis 5.0 diff --git a/holoviews/core/data/pandas.py b/holoviews/core/data/pandas.py index c09b0348e6..9db9ca488d 100644 --- a/holoviews/core/data/pandas.py +++ b/holoviews/core/data/pandas.py @@ -1,13 +1,12 @@ import numpy as np import pandas as pd -from packaging.version import Version from pandas.api.types import is_numeric_dtype from .. import util from ..dimension import Dimension, dimension_name from ..element import Element from ..ndmapping import NdMapping, item_check, sorted_context -from ..util import PANDAS_GE_210 +from ..util import PANDAS_GE_2_1_0 from .interface import DataError, Interface from .util import finite_range @@ -251,14 +250,14 @@ def groupby(cls, dataset, dimensions, container_type, group_type, **kwargs): group_kwargs['dataset'] = dataset.dataset group_by = [d.name for d in index_dims] - if len(group_by) == 1 and util.pandas_version >= Version("1.5.0"): + if len(group_by) == 1 and util.PANDAS_VERSION >= (1, 5, 0): # Because of this deprecation warning from pandas 1.5.0: # In a future version of pandas, a length 1 tuple will be returned # when iterating over a groupby with a grouper equal to a list of length 1. # Don't supply a list with a single grouper to avoid this warning. group_by = group_by[0] groupby_kwargs = {"sort": False} - if PANDAS_GE_210: + if PANDAS_GE_2_1_0: groupby_kwargs["observed"] = False data = [(k, group_type(v, **group_kwargs)) for k, v in dataset.data.groupby(group_by, **groupby_kwargs)] @@ -296,7 +295,7 @@ def aggregate(cls, dataset, dimensions, function, **kwargs): if is_numeric_dtype(d) and c not in cols ] groupby_kwargs = {"sort": False} - if PANDAS_GE_210: + if PANDAS_GE_2_1_0: groupby_kwargs["observed"] = False grouped = reindexed.groupby(cols, **groupby_kwargs) df = grouped[numeric_cols].aggregate(fn, **kwargs).reset_index() diff --git a/holoviews/core/util.py b/holoviews/core/util.py index cd25fdd6a1..279d6dbbd5 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -18,6 +18,7 @@ from collections import defaultdict, namedtuple from contextlib import contextmanager from functools import partial +from importlib.metadata import PackageNotFoundError, version from threading import Event, Thread from types import FunctionType @@ -38,13 +39,13 @@ get_keywords = operator.attrgetter('varkw') # Versions -numpy_version = Version(Version(np.__version__).base_version) -param_version = Version(param.__version__) -pandas_version = Version(pd.__version__) +NUMPY_VERSION = Version(np.__version__).release +PARAM_VERSION = Version(param.__version__).release +PANDAS_VERSION = Version(pd.__version__).release -NUMPY_GE_200 = numpy_version >= Version("2") -PANDAS_GE_210 = pandas_version >= Version("2.1") -PANDAS_GE_220 = pandas_version >= Version("2.2") +NUMPY_GE_2_0_0 = NUMPY_VERSION >= (2, 0, 0) +PANDAS_GE_2_1_0 = PANDAS_VERSION >= (2, 1, 0) +PANDAS_GE_2_2_0 = PANDAS_VERSION >= (2, 2, 0) # Types generator_types = (zip, range, types.GeneratorType) @@ -115,6 +116,14 @@ def __init__(self, msg, version=None, min_version=None, **kwargs): super().__init__(msg, **kwargs) +def _no_import_version(name) -> tuple[int, int, int]: + """ Get version number without importing the library """ + try: + return Version(version(name)).release + except PackageNotFoundError: + return (0, 0, 0) + + class Config(param.ParameterizedFunction): """ Set of boolean configuration values to change HoloViews' global diff --git a/holoviews/element/util.py b/holoviews/element/util.py index 6e6f9edce6..166aeb5f3f 100644 --- a/holoviews/element/util.py +++ b/holoviews/element/util.py @@ -10,7 +10,7 @@ from ..core.operation import Operation from ..core.sheetcoords import Slice from ..core.util import ( - PANDAS_GE_210, + PANDAS_GE_2_1_0, cartesian_product, datetime_types, is_cyclic, @@ -202,7 +202,7 @@ def _aggregate_dataset(self, obj): def _aggregate_dataset_pandas(self, obj): index_cols = [d.name for d in obj.kdims] groupby_kwargs = {"sort": False} - if PANDAS_GE_210: + if PANDAS_GE_2_1_0: groupby_kwargs["observed"] = False df = obj.data.set_index(index_cols).groupby(index_cols, **groupby_kwargs).first() label = 'unique' if len(df) == len(obj) else 'non-unique' diff --git a/holoviews/operation/element.py b/holoviews/operation/element.py index 7d302f6dd8..d7f6852cd4 100644 --- a/holoviews/operation/element.py +++ b/holoviews/operation/element.py @@ -630,7 +630,7 @@ def _process(self, element, key=None): if self.p.filled: vdims = [vdims[0].clone(range=crange)] - if Version(contourpy_version) >= Version('1.2'): + if Version(contourpy_version).release >= (1, 2, 0): line_type = LineType.ChunkCombinedNan else: line_type = LineType.ChunkCombinedOffset @@ -821,7 +821,7 @@ def _process(self, element, key=None, groupby=False): is_cupy = is_cupy_array(data) if is_cupy: import cupy - full_cupy_support = Version(cupy.__version__) > Version('8.0') + full_cupy_support = Version(cupy.__version__).release > (8, 0, 0) if not full_cupy_support and (normed or self.p.weight_dimension): data = cupy.asnumpy(data) is_cupy = False @@ -830,17 +830,17 @@ def _process(self, element, key=None, groupby=False): # Mask data if is_ibis_expr(data): - from ..core.data.ibis import ibis5, ibis9_5 + from ..core.data.ibis import IBIS_GE_5_0_0, IBIS_GE_9_5_0 mask = data.notnull() if self.p.nonzero: mask = mask & (data != 0) - if ibis5(): + if IBIS_GE_5_0_0: data = data.as_table() else: # to_projection removed in ibis 5.0.0 data = data.to_projection() - data = data.filter(mask) if ibis9_5() else data[mask] + data = data.filter(mask) if IBIS_GE_9_5_0 else data[mask] no_data = not len(data.head(1).execute()) data = data[dim.name] else: diff --git a/holoviews/plotting/bokeh/__init__.py b/holoviews/plotting/bokeh/__init__.py index d3667b271d..ee9c4136c3 100644 --- a/holoviews/plotting/bokeh/__init__.py +++ b/holoviews/plotting/bokeh/__init__.py @@ -115,7 +115,7 @@ from .stats import BivariatePlot, BoxWhiskerPlot, DistributionPlot, ViolinPlot from .tabular import TablePlot from .tiles import TilePlot -from .util import bokeh_version # noqa (API import) +from .util import BOKEH_VERSION # noqa (API import) Store.renderers['bokeh'] = BokehRenderer.instance() diff --git a/holoviews/plotting/bokeh/annotation.py b/holoviews/plotting/bokeh/annotation.py index 64f9fbdc4b..e242434a87 100644 --- a/holoviews/plotting/bokeh/annotation.py +++ b/holoviews/plotting/bokeh/annotation.py @@ -23,7 +23,7 @@ line_properties, text_properties, ) -from .util import bokeh32, date_to_integer +from .util import BOKEH_GE_3_2_0, date_to_integer arrow_start = {'<->': NormalHead, '<|-|>': NormalHead} arrow_end = {'->': NormalHead, '-[': TeeHead, '-|>': NormalHead, @@ -39,7 +39,7 @@ class _SyntheticAnnotationPlot(ColorbarPlot): _allow_implicit_categories = False def __init__(self, element, **kwargs): - if not bokeh32: + if not BOKEH_GE_3_2_0: name = type(getattr(element, "last", element)).__name__ msg = f'{name} element requires Bokeh >=3.2' raise ImportError(msg) diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index 0386a20c10..0ad8513561 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -72,7 +72,7 @@ Tap, ) from ...util.warnings import warn -from .util import bokeh33, convert_timestamp +from .util import BOKEH_GE_3_3_0, convert_timestamp class Callback: @@ -1569,7 +1569,7 @@ def initialize(self, plot_id=None): renderer = self._path_initialize() if stream.styles: self._create_style_callback(cds, renderer.glyph) - if bokeh33: + if BOKEH_GE_3_3_0: # First version with Quad support box_tool = BoxEditTool(renderers=[renderer], **kwargs) self.plot.state.tools.append(box_tool) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index b176625545..04f0a910f0 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -70,11 +70,11 @@ ) from .tabular import TablePlot from .util import ( + BOKEH_GE_3_2_0, + BOKEH_GE_3_4_0, + BOKEH_GE_3_5_0, + BOKEH_GE_3_6_0, TOOL_TYPES, - bokeh32, - bokeh34, - bokeh35, - bokeh36, cds_column_replace, compute_layout_properties, date_to_integer, @@ -553,7 +553,7 @@ def _init_tools(self, element, callbacks=None): tool = tools.HoverTool( tooltips=tooltips, tags=['hv_created'], **tool_opts ) - elif bokeh32 and isinstance(tool, str) and tool.endswith( + elif BOKEH_GE_3_2_0 and isinstance(tool, str) and tool.endswith( ('wheel_zoom', 'zoom_in', 'zoom_out') ): zoom_kwargs = {} @@ -919,7 +919,7 @@ def _init_plot(self, key, element, plots, ranges=None): axis_specs = {'x': {}, 'y': {}} axis_specs['x']['x'] = self._axis_props(plots, subplots, element, ranges, pos=0) + (self.xaxis, {}) if self.multi_y: - if not bokeh32: + if not BOKEH_GE_3_2_0: self.param.warning('Independent axis zooming for multi_y=True only supported for Bokeh >=3.2') yaxes, extra_axis_specs = self._create_extra_axes(plots, subplots, element, ranges) axis_specs['y'].update(extra_axis_specs) @@ -2417,9 +2417,9 @@ def _draw_scalebar(self, *, plot, renderer): For scalebar on a subcoordinate_y plot Bokeh 3.6 is needed. """ - if not bokeh34: + if not BOKEH_GE_3_4_0: raise RuntimeError("Scalebar requires Bokeh >= 3.4.0") - elif not bokeh36 and self._subcoord_overlaid: + elif not BOKEH_GE_3_6_0 and self._subcoord_overlaid: warn("Scalebar with subcoordinate_y requires Bokeh >= 3.6.0", RuntimeWarning) from bokeh.models import Metric, ScaleBar @@ -3329,7 +3329,7 @@ def _postprocess_subcoordinate_y_groups(self, overlay, plot): if zoom_tool.renderers: raise RuntimeError(f'Found unexpected zoom renderers {zoom_tool.renderers}') - if zoom_tool_name == 'wheel_zoom' and bokeh35: + if zoom_tool_name == 'wheel_zoom' and BOKEH_GE_3_5_0: zoom_tool.update( hit_test=True, hit_test_mode='hline', diff --git a/holoviews/plotting/bokeh/links.py b/holoviews/plotting/bokeh/links.py index 36ac59b92e..02b49ff4c2 100644 --- a/holoviews/plotting/bokeh/links.py +++ b/holoviews/plotting/bokeh/links.py @@ -13,7 +13,7 @@ VertexTableLink, ) from ..plot import GenericElementPlot, GenericOverlayPlot -from .util import bokeh34, bokeh35 +from .util import BOKEH_GE_3_4_0, BOKEH_GE_3_5_0 class LinkCallback: @@ -163,7 +163,7 @@ def __init__(self, root_model, link, source_plot, target_plot): target_range_name = range_name axes[range_name] = ax = target_plot.handles[target_range_name] interval = getattr(link, f'intervals{axis}', None) - if interval is not None and bokeh34: + if interval is not None and BOKEH_GE_3_4_0: min, max = interval if min is not None: ax.min_interval = min @@ -183,7 +183,7 @@ def __init__(self, root_model, link, source_plot, target_plot): tool = RangeTool(**axes) - if bokeh35: + if BOKEH_GE_3_5_0: use_handles = getattr(link, 'use_handles', True) start_gesture = getattr(link, 'start_gesture', 'tap') inverted = getattr(link, 'inverted', True) diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py index 8f281cc12b..ff52dd974f 100644 --- a/holoviews/plotting/bokeh/raster.py +++ b/holoviews/plotting/bokeh/raster.py @@ -11,7 +11,7 @@ from .element import ColorbarPlot, LegendPlot from .selection import BokehOverlaySelectionDisplay from .styles import base_properties, fill_properties, line_properties, mpl_to_bokeh -from .util import bokeh33, bokeh34, colormesh +from .util import BOKEH_GE_3_3_0, BOKEH_GE_3_4_0, colormesh class RasterPlot(ColorbarPlot): @@ -72,7 +72,7 @@ def _postprocess_hover(self, renderer, source): formatter += '{custom}' tooltips.append((name, formatter)) - if not bokeh34: # https://github.com/bokeh/bokeh/issues/13598 + if not BOKEH_GE_3_4_0: # https://github.com/bokeh/bokeh/issues/13598 datetime_code = """ if (value === -9223372036854776) { return "NaN" @@ -321,7 +321,7 @@ def _hover_opts(self, element): # Bokeh 3.3 has simple support for multi hover in a tuple. # https://github.com/bokeh/bokeh/pull/13193 # https://github.com/bokeh/bokeh/pull/13366 - if bokeh33: + if BOKEH_GE_3_3_0: xdim, ydim = element.kdims vdim = ", ".join([d.pprint_label for d in element.vdims]) return [(xdim.pprint_label, '$x'), (ydim.pprint_label, '$y'), (vdim, '@image')], {} diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py index 02d8cba1a5..a767beb1fc 100644 --- a/holoviews/plotting/bokeh/util.py +++ b/holoviews/plotting/bokeh/util.py @@ -62,12 +62,12 @@ from ...util.warnings import warn from ..util import dim_axis_label -bokeh_version = Version(Version(bokeh.__version__).base_version) -bokeh32 = bokeh_version >= Version("3.2") -bokeh33 = bokeh_version >= Version("3.3") -bokeh34 = bokeh_version >= Version("3.4") -bokeh35 = bokeh_version >= Version("3.5") -bokeh36 = bokeh_version >= Version("3.6") +BOKEH_VERSION = Version(bokeh.__version__).release +BOKEH_GE_3_2_0 = BOKEH_VERSION >= (3, 2, 0) +BOKEH_GE_3_3_0 = BOKEH_VERSION >= (3, 3, 0) +BOKEH_GE_3_4_0 = BOKEH_VERSION >= (3, 4, 0) +BOKEH_GE_3_5_0 = BOKEH_VERSION >= (3, 5, 0) +BOKEH_GE_3_6_0 = BOKEH_VERSION >= (3, 6, 0) TOOL_TYPES = { 'pan': tools.PanTool, diff --git a/holoviews/plotting/mpl/__init__.py b/holoviews/plotting/mpl/__init__.py index 675f9d2559..d9a386023f 100644 --- a/holoviews/plotting/mpl/__init__.py +++ b/holoviews/plotting/mpl/__init__.py @@ -3,7 +3,6 @@ from colorcet import kbc, register_cmap from matplotlib import rc_params_from_file from matplotlib.colors import LinearSegmentedColormap, ListedColormap -from packaging.version import Version from param import concrete_descendents from ...core import Collator, GridMatrix, Layout, config @@ -27,8 +26,7 @@ from .sankey import * from .stats import * from .tabular import * - -mpl_ge_150 = Version(mpl.__version__) >= Version('1.5.0') +from .util import MPL_VERSION try: from pandas.plotting import register_matplotlib_converters @@ -62,7 +60,7 @@ def set_style(key): # Define matplotlib based style cycles and Palettes def get_color_cycle(): - if mpl_ge_150: + if MPL_VERSION >= (1, 5, 0): cyl = mpl.rcParams['axes.prop_cycle'] # matplotlib 1.5 verifies that axes.prop_cycle *is* a cycler # but no guarantee that there's a `color` key. diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index 917b4a6316..e054c0700b 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -3,7 +3,6 @@ import param from matplotlib.collections import LineCollection from matplotlib.dates import DateFormatter, date2num -from packaging.version import Version from ...core.dimension import Dimension from ...core.options import Store, abbreviated_exception @@ -26,7 +25,7 @@ from .element import ColorbarPlot, ElementPlot, LegendPlot from .path import PathPlot from .plot import AdjoinedPlot, mpl_rc_context -from .util import MPL_GE_3_7, MPL_GE_3_9, mpl_version +from .util import MPL_GE_3_7_0, MPL_GE_3_9_0, MPL_VERSION class ChartPlot(ElementPlot): @@ -94,7 +93,7 @@ def get_data(self, element, ranges, style): def init_artists(self, ax, plot_args, plot_kwargs): xs, ys = plot_args if isdatetime(xs): - if MPL_GE_3_9: + if MPL_GE_3_9_0: artist = ax.plot(xs, ys, '-', **plot_kwargs)[0] else: artist = ax.plot_date(xs, ys, '-', **plot_kwargs)[0] @@ -130,7 +129,7 @@ class ErrorPlot(ColorbarPlot): def init_artists(self, ax, plot_data, plot_kwargs): handles = ax.errorbar(*plot_data, **plot_kwargs) bottoms, tops = None, None - if mpl_version >= Version('2.0'): + if MPL_VERSION >= (2, 0, 0): _, caps, verts = handles if caps: bottoms, tops = caps @@ -503,7 +502,7 @@ def _update_plot(self, key, element, bars, lims, ranges): # Get colormapping options if isinstance(range_item, (HeatMap, Raster)) or (cdim and cdim in element): style = self.lookup_options(range_item, 'style')[self.cyclic_index] - if MPL_GE_3_7: + if MPL_GE_3_7_0: # https://github.com/matplotlib/matplotlib/pull/28355 cmap = mpl.colormaps.get_cmap(style.get('cmap')) else: diff --git a/holoviews/plotting/mpl/chart3d.py b/holoviews/plotting/mpl/chart3d.py index 215efc489c..4d6da7583c 100644 --- a/holoviews/plotting/mpl/chart3d.py +++ b/holoviews/plotting/mpl/chart3d.py @@ -2,7 +2,6 @@ import param from matplotlib import cm from mpl_toolkits.mplot3d.art3d import Line3DCollection -from packaging.version import Version from ...core import Dimension from ...core.options import abbreviated_exception @@ -11,7 +10,7 @@ from .chart import PointPlot from .element import ColorbarPlot from .path import PathPlot -from .util import mpl_version +from .util import MPL_VERSION class Plot3D(ColorbarPlot): @@ -84,7 +83,7 @@ def _finalize_axis(self, key, **kwargs): if self.disable_axes: axis.set_axis_off() - if mpl_version <= Version('1.5.9'): + if MPL_VERSION <= (1, 5, 9): axis.set_axis_bgcolor(self.bgcolor) else: axis.set_facecolor(self.bgcolor) diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py index a3cd123406..79aba31afc 100644 --- a/holoviews/plotting/mpl/element.py +++ b/holoviews/plotting/mpl/element.py @@ -9,7 +9,6 @@ from matplotlib import ticker from matplotlib.dates import date2num from matplotlib.image import AxesImage -from packaging.version import Version from ...core import ( CompositeOverlay, @@ -27,7 +26,7 @@ from ..plot import GenericElementPlot, GenericOverlayPlot from ..util import color_intervals, dim_range_key, process_cmap from .plot import MPLPlot, mpl_rc_context -from .util import EqHistNormalize, mpl_version, validate, wrap_formatter +from .util import MPL_VERSION, EqHistNormalize, validate, wrap_formatter class ElementPlot(GenericElementPlot, MPLPlot): @@ -136,7 +135,7 @@ def _finalize_axis(self, key, element=None, title=None, dimensions=None, ranges= subplots = list(self.subplots.values()) if self.subplots else [] if self.zorder == 0 and key is not None: if self.bgcolor: - if mpl_version <= Version('1.5.9'): + if MPL_VERSION <= (1, 5, 9): axis.set_axis_bgcolor(self.bgcolor) else: axis.set_facecolor(self.bgcolor) diff --git a/holoviews/plotting/mpl/raster.py b/holoviews/plotting/mpl/raster.py index 6c35279903..d7d10d2e94 100644 --- a/holoviews/plotting/mpl/raster.py +++ b/holoviews/plotting/mpl/raster.py @@ -2,7 +2,6 @@ import numpy as np import param -from packaging.version import Version from ...core import CompositeOverlay, Element, traversal from ...core.util import isfinite, match_spec, max_range, unique_iterator @@ -11,7 +10,7 @@ from .chart import PointPlot from .element import ColorbarPlot, ElementPlot, LegendPlot, OverlayPlot from .plot import GridPlot, MPLPlot, mpl_rc_context -from .util import get_raster_array, mpl_version +from .util import MPL_VERSION, get_raster_array class RasterBasePlot(ElementPlot): @@ -193,7 +192,7 @@ def init_artists(self, ax, plot_args, plot_kwargs): if 'norm' in plot_kwargs: # vmin/vmax should now be exclusively in norm plot_kwargs.pop('vmin', None) plot_kwargs.pop('vmax', None) - if colorbar and mpl_version < Version('3.1'): + if colorbar and MPL_VERSION < (3, 1, 0): colorbar.set_norm(artist.norm) if hasattr(colorbar, 'set_array'): # Compatibility with mpl < 3 diff --git a/holoviews/plotting/mpl/stats.py b/holoviews/plotting/mpl/stats.py index 9764de4e11..324e1bf872 100644 --- a/holoviews/plotting/mpl/stats.py +++ b/holoviews/plotting/mpl/stats.py @@ -6,7 +6,7 @@ from .chart import AreaPlot, ChartPlot from .path import PolygonPlot from .plot import AdjoinedPlot -from .util import MPL_GE_3_9 +from .util import MPL_GE_3_9_0 class DistributionPlot(AreaPlot): @@ -78,7 +78,7 @@ def get_data(self, element, ranges, style): d = group[group.vdims[0]] data.append(d[np.isfinite(d)]) labels.append(label) - if MPL_GE_3_9: + if MPL_GE_3_9_0: style['tick_labels'] = labels else: style['labels'] = labels @@ -161,7 +161,7 @@ def init_artists(self, ax, plot_args, plot_kwargs): stats_color = plot_kwargs.pop('stats_color', 'black') facecolors = plot_kwargs.pop('facecolors', []) edgecolors = plot_kwargs.pop('edgecolors', 'black') - if MPL_GE_3_9: + if MPL_GE_3_9_0: labels = {'tick_labels': plot_kwargs.pop('tick_labels')} else: labels = {'labels': plot_kwargs.pop('labels')} @@ -206,7 +206,7 @@ def get_data(self, element, ranges, style): labels.append(label) colors.append(elstyle[i].get('facecolors', 'blue')) style['positions'] = list(range(len(data))) - if MPL_GE_3_9: + if MPL_GE_3_9_0: style['tick_labels'] = labels else: style['labels'] = labels diff --git a/holoviews/plotting/mpl/util.py b/holoviews/plotting/mpl/util.py index 99c5a9b37c..d72d13304f 100644 --- a/holoviews/plotting/mpl/util.py +++ b/holoviews/plotting/mpl/util.py @@ -36,9 +36,9 @@ from ...element import RGB, Polygons, Raster from ..util import COLOR_ALIASES, RGB_HEX_REGEX -mpl_version = Version(mpl.__version__) -MPL_GE_3_7 = mpl_version >= Version('3.7') -MPL_GE_3_9 = mpl_version >= Version('3.9') +MPL_VERSION = Version(mpl.__version__).release +MPL_GE_3_7_0 = MPL_VERSION >= (3, 7, 0) +MPL_GE_3_9_0 = MPL_VERSION >= (3, 9, 0) def is_color(color): @@ -88,7 +88,7 @@ def get_old_rcparams(): ] old_rcparams = { k: v for k, v in mpl.rcParams.items() - if mpl_version < Version('3.0') or k not in deprecated_rcparams + if MPL_VERSION < (3, 0, 0) or k not in deprecated_rcparams } return old_rcparams diff --git a/holoviews/plotting/plotly/__init__.py b/holoviews/plotting/plotly/__init__.py index 58a6761604..4b4c20c6af 100644 --- a/holoviews/plotting/plotly/__init__.py +++ b/holoviews/plotting/plotly/__init__.py @@ -21,7 +21,7 @@ from .tabular import * from .tiles import * -if Version(plotly.__version__) < Version('4.0.0'): +if Version(plotly.__version__).release < (4, 0, 0): raise VersionError( "The plotly extension requires a plotly version >=4.0.0, " f"please upgrade from plotly {plotly.__version__} to a more recent version.", plotly.__version__, '4.0.0') diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py index 361de8193b..e448d580f5 100644 --- a/holoviews/plotting/renderer.py +++ b/holoviews/plotting/renderer.py @@ -40,7 +40,7 @@ from . import Plot from .util import collate, displayable, initialize_dynamic -panel_version = Version(pn.__version__) +PANEL_VERSION = Version(pn.__version__).release # Tags used when visual output is to be embedded in HTML IMAGE_TAG = "" @@ -649,9 +649,9 @@ def load_nb(cls, inline=False, reloading=False, enable_mathjax=False): Loads any resources required for display of plots in the Jupyter notebook """ - if panel_version >= Version('1.0.2'): + if PANEL_VERSION >= (1, 0, 2): load_notebook(inline, reloading=reloading, enable_mathjax=enable_mathjax) - elif panel_version >= Version('1.0.0'): + elif PANEL_VERSION >= (1, 0, 0): load_notebook(inline, reloading=reloading) elif reloading: return diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py index 27b035ab81..94f086645b 100644 --- a/holoviews/plotting/util.py +++ b/holoviews/plotting/util.py @@ -570,7 +570,7 @@ def mplcmap_to_palette(cmap, ncolors=None, categorical=False): if cmap.startswith('Category'): cmap = cmap.replace('Category', 'tab') - if Version(mpl.__version__) < Version("3.5"): + if Version(mpl.__version__).release < (3, 5, 0): from matplotlib import cm try: cmap = cm.get_cmap(cmap) diff --git a/holoviews/tests/core/data/test_daskinterface.py b/holoviews/tests/core/data/test_daskinterface.py index 05aa2661d1..4f01120a11 100644 --- a/holoviews/tests/core/data/test_daskinterface.py +++ b/holoviews/tests/core/data/test_daskinterface.py @@ -3,7 +3,6 @@ import numpy as np import pandas as pd -from packaging.version import Version try: import dask.dataframe as dd @@ -11,7 +10,7 @@ raise SkipTest("Could not import dask, skipping DaskInterface tests.") from holoviews.core.data import Dataset -from holoviews.core.util import pandas_version +from holoviews.core.util import PANDAS_VERSION from holoviews.util.transform import dim from ...utils import dask_switcher @@ -82,14 +81,14 @@ def test_dataset_aggregate_string_types(self): raise SkipTest("Temporarily skipped") @unittest.skipIf( - pandas_version >= Version("2.0"), + PANDAS_VERSION >= (2, 0, 0), reason="Not supported yet, https://github.com/dask/dask/issues/9913" ) def test_dataset_aggregate_ht(self): super().test_dataset_aggregate_ht() @unittest.skipIf( - pandas_version >= Version("2.0"), + PANDAS_VERSION >= (2, 0, 0), reason="Not supported yet, https://github.com/dask/dask/issues/9913" ) def test_dataset_aggregate_ht_alias(self): diff --git a/holoviews/tests/core/data/test_ibisinterface.py b/holoviews/tests/core/data/test_ibisinterface.py index 907b038f39..65f66648a7 100644 --- a/holoviews/tests/core/data/test_ibisinterface.py +++ b/holoviews/tests/core/data/test_ibisinterface.py @@ -10,10 +10,9 @@ import numpy as np import pandas as pd -from packaging.version import Version from holoviews.core.data import Dataset -from holoviews.core.data.ibis import IbisInterface, ibis_version +from holoviews.core.data.ibis import IBIS_VERSION, IbisInterface from holoviews.core.spaces import HoloMap from .base import HeterogeneousColumnTests, InterfaceTests, ScalarColumnTests @@ -158,7 +157,7 @@ def test_dataset_add_dimensions_values_ht(self): raise SkipTest("Not supported") def test_dataset_dataset_ht_dtypes(self): - int_dtype = "int64" if ibis_version() >= Version("9.0") else "int32" + int_dtype = "int64" if IBIS_VERSION >= (9, 0, 0) else "int32" ds = self.table self.assertEqual(ds.interface.dtype(ds, "Gender"), np.dtype("object")) self.assertEqual(ds.interface.dtype(ds, "Age"), np.dtype(int_dtype)) @@ -166,7 +165,7 @@ def test_dataset_dataset_ht_dtypes(self): self.assertEqual(ds.interface.dtype(ds, "Height"), np.dtype("float64")) def test_dataset_dtypes(self): - int_dtype = "int64" if ibis_version() >= Version("9.0") else "int32" + int_dtype = "int64" if IBIS_VERSION >= (9, 0, 0) else "int32" self.assertEqual( self.dataset_hm.interface.dtype(self.dataset_hm, "x"), np.dtype(int_dtype) ) diff --git a/holoviews/tests/core/test_dimensions.py b/holoviews/tests/core/test_dimensions.py index 7b79e1bcea..273af05a09 100644 --- a/holoviews/tests/core/test_dimensions.py +++ b/holoviews/tests/core/test_dimensions.py @@ -5,7 +5,7 @@ import pandas as pd from holoviews.core import Dimension, Dimensioned -from holoviews.core.util import NUMPY_GE_200 +from holoviews.core.util import NUMPY_GE_2_0_0 from holoviews.element.comparison import ComparisonTestCase from ..utils import LoggingComparisonTestCase @@ -244,7 +244,7 @@ def test_tuple_clone(self): class DimensionDefaultTest(ComparisonTestCase): def test_validate_default_against_values(self): - if NUMPY_GE_200: + if NUMPY_GE_2_0_0: msg = r"Dimension\('A'\) default 1\.1 not found in declared values: \[np\.int64\(0\), np\.int64\(1\)\]" else: msg = r"Dimension\('A'\) default 1\.1 not found in declared values: \[0, 1\]" diff --git a/holoviews/tests/element/test_annotations.py b/holoviews/tests/element/test_annotations.py index 9731459eed..ec25f25119 100644 --- a/holoviews/tests/element/test_annotations.py +++ b/holoviews/tests/element/test_annotations.py @@ -15,7 +15,7 @@ class AnnotationTests(ComparisonTestCase): """ def test_hline_invalid_constructor(self): - if Version(param.__version__) > Version('2.0.0a2'): + if Version(param.__version__).release >= (2, 0, 0): err = "ClassSelector parameter 'HLine.y' value must be an instance of" else: err = "ClassSelector parameter 'y' value must be an instance of" diff --git a/holoviews/tests/element/test_comparisondimension.py b/holoviews/tests/element/test_comparisondimension.py index 3bdfb04c4e..876cd0ef09 100644 --- a/holoviews/tests/element/test_comparisondimension.py +++ b/holoviews/tests/element/test_comparisondimension.py @@ -2,7 +2,7 @@ Test cases for Dimension and Dimensioned object comparison. """ from holoviews.core import Dimension, Dimensioned -from holoviews.core.util import NUMPY_GE_200 +from holoviews.core.util import NUMPY_GE_2_0_0 from holoviews.element.comparison import ComparisonTestCase @@ -75,7 +75,7 @@ def test_dimension_comparison_values_unequal(self): try: self.assertEqual(self.dimension4, self.dimension8) except AssertionError as e: - if NUMPY_GE_200: + if NUMPY_GE_2_0_0: msg = "Dimension parameter 'values' mismatched: [] != [np.str_('a'), np.str_('b')]" else: msg = "Dimension parameter 'values' mismatched: [] != ['a', 'b']" diff --git a/holoviews/tests/plotting/bokeh/test_annotationplot.py b/holoviews/tests/plotting/bokeh/test_annotationplot.py index 7ce2986936..b52809763d 100644 --- a/holoviews/tests/plotting/bokeh/test_annotationplot.py +++ b/holoviews/tests/plotting/bokeh/test_annotationplot.py @@ -17,11 +17,11 @@ VSpan, VSpans, ) -from holoviews.plotting.bokeh.util import bokeh32, bokeh33, bokeh34 +from holoviews.plotting.bokeh.util import BOKEH_GE_3_2_0, BOKEH_GE_3_3_0, BOKEH_GE_3_4_0 from .test_plot import TestBokehPlot, bokeh_renderer -if bokeh32: +if BOKEH_GE_3_2_0: from bokeh.models import ( HSpan as BkHSpan, HStrip as BkHStrip, @@ -29,9 +29,9 @@ VStrip as BkVStrip, ) -if bokeh34: +if BOKEH_GE_3_4_0: from bokeh.models import Node -elif bokeh33: +elif BOKEH_GE_3_3_0: from bokeh.models.coordinates import Node @@ -75,7 +75,7 @@ def test_hspan_invert_axes(self): assert span.left == 1.1 assert span.right == 1.5 - if bokeh33: + if BOKEH_GE_3_3_0: assert isinstance(span.bottom, Node) assert isinstance(span.top, Node) else: @@ -87,7 +87,7 @@ def test_hspan_plot(self): hspan = HSpan(1.1, 1.5) plot = bokeh_renderer.get_plot(hspan) span = plot.handles['glyph'] - if bokeh33: + if BOKEH_GE_3_3_0: assert isinstance(span.left, Node) assert isinstance(span.right, Node) else: @@ -107,7 +107,7 @@ def test_vspan_invert_axes(self): vspan = VSpan(1.1, 1.5).opts(invert_axes=True) plot = bokeh_renderer.get_plot(vspan) span = plot.handles['glyph'] - if bokeh33: + if BOKEH_GE_3_3_0: assert isinstance(span.left, Node) assert isinstance(span.right, Node) else: @@ -123,7 +123,7 @@ def test_vspan_plot(self): span = plot.handles['glyph'] assert span.left == 1.1 assert span.right == 1.5 - if bokeh33: + if BOKEH_GE_3_3_0: assert isinstance(span.bottom, Node) assert isinstance(span.top, Node) else: @@ -245,7 +245,7 @@ def test_labels_plot_rotation_style(self): class TestHVLinesPlot(TestBokehPlot): def setUp(self): - if not bokeh32: + if not BOKEH_GE_3_2_0: raise unittest.SkipTest("Bokeh 3.2 added H/VLines") super().setUp() @@ -444,7 +444,7 @@ def test_coloring_hline(self): class TestHVSpansPlot(TestBokehPlot): def setUp(self): - if not bokeh32: + if not BOKEH_GE_3_2_0: raise unittest.SkipTest("Bokeh 3.2 added H/VSpans") super().setUp() diff --git a/holoviews/tests/plotting/bokeh/test_elementplot.py b/holoviews/tests/plotting/bokeh/test_elementplot.py index 50c34c5074..a2687442a9 100644 --- a/holoviews/tests/plotting/bokeh/test_elementplot.py +++ b/holoviews/tests/plotting/bokeh/test_elementplot.py @@ -21,7 +21,7 @@ from holoviews.core import Dimension, DynamicMap, HoloMap, NdOverlay, Overlay from holoviews.core.util import dt_to_int from holoviews.element import Curve, HeatMap, Image, Labels, Scatter -from holoviews.plotting.bokeh.util import bokeh34, bokeh36 +from holoviews.plotting.bokeh.util import BOKEH_GE_3_4_0, BOKEH_GE_3_6_0 from holoviews.plotting.util import process_cmap from holoviews.streams import PointDraw, Stream from holoviews.util import render @@ -818,7 +818,7 @@ def test_element_backend_opts_model_not_resolved(self): @pytest.mark.usefixtures("bokeh_backend") -@pytest.mark.skipif(not bokeh34, reason="requires Bokeh >= 3.4") +@pytest.mark.skipif(not BOKEH_GE_3_4_0, reason="requires Bokeh >= 3.4") class TestScalebarPlot: def get_scalebar(self, element): @@ -885,7 +885,7 @@ def test_scalebar_icon_multiple_overlay(self): scalebar_icon = [tool for tool in toolbar.tools if tool.description == "Toggle ScaleBar"] assert len(scalebar_icon) == 1 - @pytest.mark.skipif(not bokeh36, reason="requires Bokeh >= 3.6") + @pytest.mark.skipif(not BOKEH_GE_3_6_0, reason="requires Bokeh >= 3.6") @pytest.mark.parametrize("enabled1", [True, False]) @pytest.mark.parametrize("enabled2", [True, False]) @pytest.mark.parametrize("enabled3", [True, False]) diff --git a/holoviews/tests/plotting/bokeh/test_rasterplot.py b/holoviews/tests/plotting/bokeh/test_rasterplot.py index c5c404f6f9..d743e0c972 100644 --- a/holoviews/tests/plotting/bokeh/test_rasterplot.py +++ b/holoviews/tests/plotting/bokeh/test_rasterplot.py @@ -7,7 +7,7 @@ from holoviews.element import RGB, Image, ImageStack, Raster from holoviews.plotting.bokeh.raster import ImageStackPlot -from holoviews.plotting.bokeh.util import bokeh34 +from holoviews.plotting.bokeh.util import BOKEH_GE_3_4_0 from .test_plot import TestBokehPlot, bokeh_renderer @@ -150,7 +150,7 @@ def test_image_datetime_hover(self): assert hover.tooltips[-1] == ("Timestamp", "@{Timestamp}{%F %T}") assert "@{Timestamp}" in hover.formatters - if bokeh34: # https://github.com/bokeh/bokeh/issues/13598 + if BOKEH_GE_3_4_0: # https://github.com/bokeh/bokeh/issues/13598 assert hover.formatters["@{Timestamp}"] == "datetime" else: assert isinstance(hover.formatters["@{Timestamp}"], CustomJSHover) diff --git a/holoviews/tests/plotting/bokeh/test_subcoordy.py b/holoviews/tests/plotting/bokeh/test_subcoordy.py index 041cb8059e..408b90246e 100644 --- a/holoviews/tests/plotting/bokeh/test_subcoordy.py +++ b/holoviews/tests/plotting/bokeh/test_subcoordy.py @@ -6,7 +6,7 @@ from holoviews.element import Curve from holoviews.element.annotation import VSpan from holoviews.operation.normalization import subcoordinate_group_ranges -from holoviews.plotting.bokeh.util import bokeh35 +from holoviews.plotting.bokeh.util import BOKEH_GE_3_5_0 from .test_plot import TestBokehPlot, bokeh_renderer @@ -368,7 +368,7 @@ def test_multiple_groups(self): 2: 'B / 0', 3: 'B / 1', } - @pytest.mark.skipif(bokeh35, reason="test Bokeh < 3.5") + @pytest.mark.skipif(BOKEH_GE_3_5_0, reason="test Bokeh < 3.5") def test_multiple_groups_wheel_zoom_configured(self): # Same as test_tools_default_wheel_zoom_configured @@ -389,7 +389,7 @@ def test_multiple_groups_wheel_zoom_configured(self): assert zoom_tool.level == 1 assert zoom_tool.description == f'Wheel Zoom ({group})' - @pytest.mark.skipif(not bokeh35, reason="requires Bokeh >= 3.5") + @pytest.mark.skipif(not BOKEH_GE_3_5_0, reason="requires Bokeh >= 3.5") def test_multiple_groups_wheel_zoom_configured_35(self): # Same as test_tools_default_wheel_zoom_configured diff --git a/holoviews/tests/plotting/matplotlib/test_annotationplot.py b/holoviews/tests/plotting/matplotlib/test_annotationplot.py index ed10b9500f..57a297f3cd 100644 --- a/holoviews/tests/plotting/matplotlib/test_annotationplot.py +++ b/holoviews/tests/plotting/matplotlib/test_annotationplot.py @@ -2,7 +2,7 @@ import holoviews as hv from holoviews.element import HLines, HSpans, VLines, VSpans -from holoviews.plotting.mpl.util import MPL_GE_3_9 +from holoviews.plotting.mpl.util import MPL_GE_3_9_0 from .test_plot import TestMPLPlot, mpl_renderer @@ -157,7 +157,7 @@ class TestHVSpansPlot(TestMPLPlot): def _hspans_check(self, source, v0, v1): # Matplotlib 3.9+ uses a rectangle instead of polygon - if MPL_GE_3_9: + if MPL_GE_3_9_0: rect = [source.get_x(), source.get_y(), source.get_width(), source.get_height()] assert np.allclose(rect, [0, v0, 1, v1 - v0]) else: @@ -166,7 +166,7 @@ def _hspans_check(self, source, v0, v1): def _vspans_check(self, source, v0, v1): # Matplotlib 3.9+ uses a rectangle instead of polygon - if MPL_GE_3_9: + if MPL_GE_3_9_0: rect = [source.get_x(), source.get_y(), source.get_width(), source.get_height()] assert np.allclose(rect, [v0, 0, v1 - v0, 1]) else: diff --git a/holoviews/tests/plotting/matplotlib/test_boxwhisker.py b/holoviews/tests/plotting/matplotlib/test_boxwhisker.py index 2c2ec926b9..7dba8a96ae 100644 --- a/holoviews/tests/plotting/matplotlib/test_boxwhisker.py +++ b/holoviews/tests/plotting/matplotlib/test_boxwhisker.py @@ -1,7 +1,7 @@ import numpy as np from holoviews.element import BoxWhisker -from holoviews.plotting.mpl.util import MPL_GE_3_9 +from holoviews.plotting.mpl.util import MPL_GE_3_9_0 from .test_plot import TestMPLPlot, mpl_renderer @@ -14,7 +14,7 @@ def test_boxwhisker_simple(self): plot = mpl_renderer.get_plot(boxwhisker) data, style, axis_opts = plot.get_data(boxwhisker, {}, {}) self.assertEqual(data[0][0], values) - if MPL_GE_3_9: + if MPL_GE_3_9_0: self.assertEqual(style['tick_labels'], ['']) else: self.assertEqual(style['labels'], ['']) diff --git a/holoviews/tests/plotting/matplotlib/test_graphplot.py b/holoviews/tests/plotting/matplotlib/test_graphplot.py index 5a6ad1b2b1..266443e469 100644 --- a/holoviews/tests/plotting/matplotlib/test_graphplot.py +++ b/holoviews/tests/plotting/matplotlib/test_graphplot.py @@ -1,7 +1,6 @@ import numpy as np import pytest from matplotlib.collections import LineCollection, PolyCollection -from packaging.version import Version from holoviews.core.data import Dataset from holoviews.core.options import AbbreviatedException, Cycle @@ -9,7 +8,7 @@ from holoviews.element import Chord, Graph, Nodes, TriMesh, circular_layout from holoviews.util.transform import dim -from .test_plot import TestMPLPlot, mpl_renderer +from .test_plot import MPL_GE_3_4_0, TestMPLPlot, mpl_renderer class TestMplGraphPlot(TestMPLPlot): @@ -198,20 +197,19 @@ def get_graph(i): self.assertEqual(artist.get_linewidths(), [12, 3, 5]) def test_graph_op_node_alpha(self): - import matplotlib as mpl edges = [(0, 1), (0, 2)] nodes = Nodes([(0, 0, 0, 0.2), (0, 1, 1, 0.6), (1, 1, 2, 1)], vdims='alpha') graph = Graph((edges, nodes)).opts(node_alpha='alpha') - if Version(mpl.__version__) < Version("3.4.0"): + if MPL_GE_3_4_0: + plot = mpl_renderer.get_plot(graph) + artist = plot.handles['nodes'] + self.assertEqual(artist.get_alpha(), np.array([0.2, 0.6, 1])) + else: # Python 3.6 only support up to matplotlib 3.3 msg = 'TypeError: alpha must be a float or None' with pytest.raises(AbbreviatedException, match=msg): mpl_renderer.get_plot(graph) - else: - plot = mpl_renderer.get_plot(graph) - artist = plot.handles['nodes'] - self.assertEqual(artist.get_alpha(), np.array([0.2, 0.6, 1])) def test_graph_op_edge_color(self): edges = [(0, 1, 'red'), (0, 2, 'green'), (1, 3, 'blue')] @@ -392,21 +390,19 @@ def test_trimesh_op_node_size(self): self.assertEqual(artist.get_sizes(), np.array([9, 4, 64, 16])) def test_trimesh_op_node_alpha(self): - import matplotlib as mpl - edges = [(0, 1, 2), (1, 2, 3)] nodes = [(-1, -1, 0, 0.2), (0, 0, 1, 0.6), (0, 1, 2, 1), (1, 0, 3, 0.3)] trimesh = TriMesh((edges, Nodes(nodes, vdims='alpha'))).opts(node_alpha='alpha') - if Version(mpl.__version__) < Version("3.4.0"): + if MPL_GE_3_4_0: + plot = mpl_renderer.get_plot(trimesh) + artist = plot.handles['nodes'] + self.assertEqual(artist.get_alpha(), np.array([0.2, 0.6, 1, 0.3])) + else: # Python 3.6 only support up to matplotlib 3.3 msg = "TypeError: alpha must be a float or None" with pytest.raises(AbbreviatedException, match=msg): mpl_renderer.get_plot(trimesh) - else: - plot = mpl_renderer.get_plot(trimesh) - artist = plot.handles['nodes'] - self.assertEqual(artist.get_alpha(), np.array([0.2, 0.6, 1, 0.3])) def test_trimesh_op_node_line_width(self): edges = [(0, 1, 2), (1, 2, 3)] diff --git a/holoviews/tests/plotting/matplotlib/test_heatmapplot.py b/holoviews/tests/plotting/matplotlib/test_heatmapplot.py index f33bc90f30..74b945ebb6 100644 --- a/holoviews/tests/plotting/matplotlib/test_heatmapplot.py +++ b/holoviews/tests/plotting/matplotlib/test_heatmapplot.py @@ -2,7 +2,7 @@ from holoviews.element import HeatMap, Image -from .test_plot import TestMPLPlot, mpl38, mpl_renderer +from .test_plot import MPL_GE_3_8_0, TestMPLPlot, mpl_renderer class TestHeatMapPlot(TestMPLPlot): @@ -12,7 +12,7 @@ def test_heatmap_invert_axes(self): hm = HeatMap(Image(arr)).opts(invert_axes=True) plot = mpl_renderer.get_plot(hm) artist = plot.handles['artist'] - if mpl38: + if MPL_GE_3_8_0: np.testing.assert_equal(artist.get_array().data, arr.T[::-1]) else: np.testing.assert_equal(artist.get_array().data, arr.T[::-1].flatten()) @@ -26,7 +26,7 @@ def test_heatmap_invert_xaxis(self): hmap = HeatMap([('A',1, 1), ('B', 2, 2)]).opts(invert_xaxis=True) plot = mpl_renderer.get_plot(hmap) array = plot.handles['artist'].get_array() - if mpl38: + if MPL_GE_3_8_0: expected = np.array([[1, np.inf], [np.inf, 2]]) else: expected = np.array([1, np.inf, np.inf, 2]) @@ -38,7 +38,7 @@ def test_heatmap_invert_yaxis(self): plot = mpl_renderer.get_plot(hmap) array = plot.handles['artist'].get_array() expected = np.array([1, np.inf, np.inf, 2]) - if mpl38: + if MPL_GE_3_8_0: expected = np.array([[1, np.inf], [np.inf, 2]]) else: expected = np.array([1, np.inf, np.inf, 2]) diff --git a/holoviews/tests/plotting/matplotlib/test_plot.py b/holoviews/tests/plotting/matplotlib/test_plot.py index bd00393461..765924cc2a 100644 --- a/holoviews/tests/plotting/matplotlib/test_plot.py +++ b/holoviews/tests/plotting/matplotlib/test_plot.py @@ -1,17 +1,17 @@ import matplotlib.pyplot as plt import pyviz_comms as comms -from packaging.version import Version from param import concrete_descendents from holoviews.core.options import Store from holoviews.element.comparison import ComparisonTestCase -from holoviews.plotting.mpl import mpl_version +from holoviews.plotting.mpl import MPL_VERSION from holoviews.plotting.mpl.element import ElementPlot from .. import option_intersections mpl_renderer = Store.renderers['matplotlib'] -mpl38 = mpl_version >= Version("3.8") +MPL_GE_3_4_0 = MPL_VERSION >= (3, 4, 0) +MPL_GE_3_8_0 = MPL_VERSION >= (3, 8, 0) class TestPlotDefinitions(ComparisonTestCase): diff --git a/holoviews/tests/plotting/matplotlib/test_quadmeshplot.py b/holoviews/tests/plotting/matplotlib/test_quadmeshplot.py index 1d3096b658..fc0e0c2011 100644 --- a/holoviews/tests/plotting/matplotlib/test_quadmeshplot.py +++ b/holoviews/tests/plotting/matplotlib/test_quadmeshplot.py @@ -2,7 +2,7 @@ from holoviews.element import Dataset, Image, QuadMesh -from .test_plot import TestMPLPlot, mpl38, mpl_renderer +from .test_plot import MPL_GE_3_8_0, TestMPLPlot, mpl_renderer class TestQuadMeshPlot(TestMPLPlot): @@ -12,7 +12,7 @@ def test_quadmesh_invert_axes(self): qmesh = QuadMesh(Image(arr)).opts(invert_axes=True) plot = mpl_renderer.get_plot(qmesh) artist = plot.handles['artist'] - if mpl38: + if MPL_GE_3_8_0: np.testing.assert_equal(artist.get_array().data, arr[::-1].T) else: np.testing.assert_equal(artist.get_array().data, arr.T[::-1].flatten()) @@ -22,7 +22,7 @@ def test_quadmesh_nodata(self): qmesh = QuadMesh(Image(arr)).opts(nodata=0) plot = mpl_renderer.get_plot(qmesh) artist = plot.handles['artist'] - if mpl38: + if MPL_GE_3_8_0: expected = np.array([[3, 4, 5], [np.nan, 1, 2]]) else: expected = np.array([3, 4, 5, np.nan, 1, 2]) @@ -34,7 +34,7 @@ def test_quadmesh_nodata_uint(self): qmesh = QuadMesh(Image(arr)).opts(nodata=0) plot = mpl_renderer.get_plot(qmesh) artist = plot.handles['artist'] - if mpl38: + if MPL_GE_3_8_0: expected = np.array([[3, 4, 5], [np.nan, 1, 2]]) else: expected = np.array([3, 4, 5, np.nan, 1, 2]) diff --git a/holoviews/tests/plotting/matplotlib/test_violinplot.py b/holoviews/tests/plotting/matplotlib/test_violinplot.py index e87fdb5395..efd15f054d 100644 --- a/holoviews/tests/plotting/matplotlib/test_violinplot.py +++ b/holoviews/tests/plotting/matplotlib/test_violinplot.py @@ -1,7 +1,7 @@ import numpy as np from holoviews.element import Violin -from holoviews.plotting.mpl.util import MPL_GE_3_9 +from holoviews.plotting.mpl.util import MPL_GE_3_9_0 from .test_plot import TestMPLPlot, mpl_renderer @@ -15,7 +15,7 @@ def test_violin_simple(self): data, style, axis_opts = plot.get_data(violin, {}, {}) self.assertEqual(data[0][0], values) self.assertEqual(style['positions'], [0]) - if MPL_GE_3_9: + if MPL_GE_3_9_0: self.assertEqual(style['tick_labels'], ['']) else: self.assertEqual(style['labels'], ['']) @@ -38,7 +38,7 @@ def test_violin_multi(self): self.assertEqual(data[0][0], violin.select(A=0).dimension_values(1)) self.assertEqual(data[0][1], violin.select(A=1).dimension_values(1)) self.assertEqual(style['positions'], [0, 1]) - if MPL_GE_3_9: + if MPL_GE_3_9_0: self.assertEqual(style['tick_labels'], ['0', '1']) else: self.assertEqual(style['labels'], ['0', '1']) diff --git a/holoviews/tests/test_streams.py b/holoviews/tests/test_streams.py index 3f7124a85b..50bcb1c62e 100644 --- a/holoviews/tests/test_streams.py +++ b/holoviews/tests/test_streams.py @@ -11,7 +11,7 @@ import holoviews as hv from holoviews.core.spaces import DynamicMap -from holoviews.core.util import NUMPY_GE_200, Version +from holoviews.core.util import NUMPY_GE_2_0_0, PARAM_VERSION from holoviews.element import Curve, Histogram, Points, Polygons, Scatter from holoviews.element.comparison import ComparisonTestCase from holoviews.streams import * # noqa (Test all available streams) @@ -20,6 +20,7 @@ from .utils import LoggingComparisonTestCase +PARAM_GE_2_0_0 = PARAM_VERSION >= (2, 0, 0) def test_all_stream_parameters_constant(): all_stream_cls = [v for v in globals().values() if @@ -73,7 +74,7 @@ def test_XY_instance(self): self.assertEqual(xy.y, 2) def test_XY_set_invalid_class_x(self): - if Version(param.__version__) > Version('2.0.0a2'): + if PARAM_GE_2_0_0: regexp = "Number parameter 'XY.x' only takes numeric values" else: regexp = "Parameter 'x' only takes numeric values" @@ -81,7 +82,7 @@ def test_XY_set_invalid_class_x(self): self.XY.x = 'string' def test_XY_set_invalid_class_y(self): - if Version(param.__version__) > Version('2.0.0a2'): + if PARAM_GE_2_0_0: regexp = "Number parameter 'XY.y' only takes numeric values" else: regexp = "Parameter 'y' only takes numeric values" @@ -90,7 +91,7 @@ def test_XY_set_invalid_class_y(self): def test_XY_set_invalid_instance_x(self): xy = self.XY(x=1,y=2) - if Version(param.__version__) > Version('2.0.0a2'): + if PARAM_GE_2_0_0: regexp = "Number parameter 'XY.x' only takes numeric values" else: regexp = "Parameter 'x' only takes numeric values" @@ -99,7 +100,7 @@ def test_XY_set_invalid_instance_x(self): def test_XY_set_invalid_instance_y(self): xy = self.XY(x=1,y=2) - if Version(param.__version__) > Version('2.0.0a2'): + if PARAM_GE_2_0_0: regexp = "Number parameter 'XY.y' only takes numeric values" else: regexp = "Parameter 'y' only takes numeric values" @@ -343,7 +344,7 @@ def test_params_identical_names(self): class TestParamMethodStream(ComparisonTestCase): def setUp(self): - if Version(param.__version__) < Version('1.8.0'): + if PARAM_VERSION < (1, 8, 0): raise SkipTest('Params stream requires param >= 1.8.0') class Inner(param.Parameterized): @@ -1445,7 +1446,7 @@ def test_selection_expr_stream_polygon_index_cols(self): self.assertIsNone(expr_stream.bbox) self.assertIsNone(expr_stream.selection_expr) - fmt = lambda x: list(map(np.str_, x)) if NUMPY_GE_200 else x + fmt = lambda x: list(map(np.str_, x)) if NUMPY_GE_2_0_0 else x expr_stream.input_streams[2].event(index=[0, 1]) self.assertEqual( diff --git a/holoviews/tests/ui/bokeh/test_callback.py b/holoviews/tests/ui/bokeh/test_callback.py index 1fe6ba082b..e2cb70a005 100644 --- a/holoviews/tests/ui/bokeh/test_callback.py +++ b/holoviews/tests/ui/bokeh/test_callback.py @@ -5,14 +5,14 @@ import holoviews as hv from holoviews import Curve, DynamicMap, Scatter -from holoviews.plotting.bokeh.util import bokeh34 +from holoviews.plotting.bokeh.util import BOKEH_GE_3_4_0 from holoviews.streams import BoundsX, BoundsXY, BoundsY, Lasso, MultiAxisTap, RangeXY from .. import expect, wait_until pytestmark = pytest.mark.ui -skip_popup = pytest.mark.skipif(not bokeh34, reason="Pop ups needs Bokeh 3.4") +skip_popup = pytest.mark.skipif(not BOKEH_GE_3_4_0, reason="Pop ups needs Bokeh 3.4") @pytest.fixture def points(): @@ -74,7 +74,7 @@ def test_lasso_select(serve_hv): page.mouse.move(bbox['x']+50, bbox['y']+150, steps=5) page.mouse.up() - if bokeh34: + if BOKEH_GE_3_4_0: expected_array = np.array([ [3.28440367e-01, 2.31836735e00], [4.38532110e-01, 2.22040816e00], diff --git a/scripts/download_data.py b/scripts/download_data.py index 00c900eab1..21ac8a612f 100644 --- a/scripts/download_data.py +++ b/scripts/download_data.py @@ -3,7 +3,7 @@ import bokeh from packaging.version import Version -if Version(Version(bokeh.__version__).base_version) < Version("3.5"): +if Version(bokeh.__version__).release < (3, 5, 0): import bokeh.sampledata bokeh.sampledata.download()