diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e45b20054..d85e16f71 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -20,8 +20,8 @@ jobs: shell: bash -l {0} env: CHANS_DEV: "-c pyviz/label/dev -c bokeh" - PKG_TEST_PYTHON: "--test-python=py37" - PYTHON_VERSION: "3.7" + PKG_TEST_PYTHON: "--test-python=py38" + PYTHON_VERSION: "3.8" CHANS: "-c pyviz" MPLBACKEND: "Agg" CONDA_UPLOAD_TOKEN: ${{ secrets.CONDA_UPLOAD_TOKEN }} @@ -64,8 +64,8 @@ jobs: shell: bash -l {0} env: CHANS_DEV: "-c pyviz/label/dev -c bokeh" - PKG_TEST_PYTHON: "--test-python=py37" - PYTHON_VERSION: "3.7" + PKG_TEST_PYTHON: "--test-python=py38" + PYTHON_VERSION: "3.8" CHANS: "-c pyviz" MPLBACKEND: "Agg" PPU: ${{ secrets.PPU }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f19a07040..c6f67defc 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -39,35 +39,27 @@ jobs: matrix: os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] # Run on the full set on schedule, workflow_dispatch and push&tags events, otherwise on a subset. - python-version: ${{ ( github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || ( github.event_name == 'push' && github.ref_type == 'tag' ) ) && fromJSON('["3.7", "3.8", "3.9", "3.10", "3.11"]') || fromJSON('["3.7", "3.9", "3.11"]') }} + python-version: ${{ ( github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || ( github.event_name == 'push' && github.ref_type == 'tag' ) ) && fromJSON('["3.8", "3.9", "3.10", "3.11"]') || fromJSON('["3.8", "3.11"]') }} timeout-minutes: 90 defaults: run: shell: bash -el {0} steps: - - uses: holoviz-dev/holoviz_tasks/install@v0.1a12 + - uses: holoviz-dev/holoviz_tasks/install@v0.1a15 with: name: unit_test_suite python-version: ${{ matrix.python-version }} channel-priority: strict channels: pyviz/label/dev,conda-forge,nodefaults - envs: "-o examples_tests -o tests" + envs: "-o examples_tests -o tests -o examples_conda" cache: true conda-update: true - conda-mamba: mamba id: install - - name: patch fiona/geostack on Python 3.7 / Macos - if: steps.install.outputs.cache-hit != 'true' && contains(matrix.os, 'macos') && matrix.python-version == '3.7' - run: | - conda activate test-environment - mamba install "fiona=1.8" "gdal=3.3" - name: doit test_unit run: | conda activate test-environment doit test_unit - name: test examples - # Should be removed when numba support python 3.11 - if: matrix.python-version != '3.11' run: | conda activate test-environment bokeh sampledata @@ -76,50 +68,3 @@ jobs: run: | conda activate test-environment codecov - test_suite_36: - name: Pytest on ${{ matrix.os }} with Python ${{ matrix.python-version }} - needs: [pre_commit] - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: ['macos-latest', 'windows-latest'] - python-version: ['3.6'] - timeout-minutes: 90 - defaults: - run: - shell: bash -l {0} - steps: - - uses: holoviz-dev/holoviz_tasks/install@v0.1a12 - with: - name: unit_test_suite_36 - python-version: ${{ matrix.python-version }} - channel-priority: strict - channels: pyviz/label/dev,conda-forge,nodefaults - envs: "-o tests" - cache: true - conda-update: true - conda-mamba: mamba - id: install - - name: doit develop_install py - if: steps.install.outputs.cache-hit != 'true' - run: | - conda activate test-environment - # - Pin panel on Python 3.6 because one or more dev releases on the 0.13.* series - # can be installed on Python 3.6 but are actually not compatible with Python 3.6 - # Panel 0.13 will support Python >= 3.7 only so the pin here can stay indefinitely. - # - Install importlib_resources to fix tqdm that missed adding it as a dependency - # for 3.6 (https://github.com/conda-forge/tqdm-feedstock/pull/114) - conda install "panel=0.12" "importlib_resources" --no-update-deps - - name: doit env_capture - run: | - conda activate test-environment - doit env_capture - - name: doit test_unit - run: | - conda activate test-environment - doit test_unit - - name: codecov - run: | - conda activate test-environment - codecov diff --git a/doc/getting_started/installation.md b/doc/getting_started/installation.md index b97bd7a6c..a6f0bb329 100644 --- a/doc/getting_started/installation.md +++ b/doc/getting_started/installation.md @@ -5,7 +5,7 @@ | Latest release | [![Github release](https://img.shields.io/github/release/holoviz/hvplot.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/hvplot/releases) [![PyPI version](https://img.shields.io/pypi/v/hvplot.svg?colorB=cc77dd)](https://pypi.python.org/pypi/hvplot) [![hvplot version](https://img.shields.io/conda/v/pyviz/hvplot.svg?colorB=4488ff&style=flat)](https://anaconda.org/pyviz/hvplot) [![conda-forge version](https://img.shields.io/conda/v/conda-forge/hvplot.svg?label=conda%7Cconda-forge&colorB=4488ff)](https://anaconda.org/conda-forge/hvplot) [![defaults version](https://img.shields.io/conda/v/anaconda/hvplot.svg?label=conda%7Cdefaults&style=flat&colorB=4488ff)](https://anaconda.org/anaconda/hvplot) | | Python | [![Python support](https://img.shields.io/pypi/pyversions/hvplot.svg)](https://pypi.org/project/hvplot/) | -hvPlot supports Python 3.6, 3.7, 3.8, 3.9 and 3.10 on Linux, Windows, or Mac. The recommended way to install hvPlot is using the [conda](https://conda.io/en/latest/) command provided by [Anaconda](https://docs.anaconda.com/anaconda/install/index.html) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html): +hvPlot supports Python 3.8, 3.9, 3.10 and 3.11 on Linux, Windows, or Mac. The recommended way to install hvPlot is using the [conda](https://conda.io/en/latest/) command provided by [Anaconda](https://docs.anaconda.com/anaconda/install/index.html) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html): conda install -c pyviz hvplot diff --git a/hvplot/converter.py b/hvplot/converter.py index 89b52507c..6eb4a7249 100644 --- a/hvplot/converter.py +++ b/hvplot/converter.py @@ -417,9 +417,9 @@ def __init__( if self.geo: if self.kind not in self._geo_types: param.main.param.warning( - "geo option cannot be used with kind=%r plot " + f"geo option cannot be used with kind={self.kind!r} plot " "type. Geographic plots are only supported for " - "following plot types: %r" % (self.kind, self._geo_types)) + f"following plot types: {self._geo_types!r}") from cartopy import crs as ccrs from geoviews.util import project_extents @@ -436,7 +436,7 @@ def __init__( else: raise ValueError( "Projection must be defined as cartopy CRS or " - "one of the following CRS string:\n {}".format(all_crs)) + f"one of the following CRS string:\n {all_crs}") projection = projection or (ccrs.GOOGLE_MERCATOR if tiles else self.crs) if tiles and projection != ccrs.GOOGLE_MERCATOR: @@ -664,8 +664,8 @@ def _process_crs(self, data, crs): # only raise error if crs was specified in kwargs if crs: raise ValueError( - "'{}' must be either a valid crs or an reference to " - "a `data.attr` containing a valid crs.".format(crs)) + f"'{crs}' must be either a valid crs or an reference to " + "a `data.attr` containing a valid crs.") def _process_data(self, kind, data, x, y, by, groupby, row, col, use_dask, persist, backlog, label, group_label, @@ -851,14 +851,14 @@ def _process_data(self, kind, data, x, y, by, groupby, row, col, groupby.append(data_dim) self.variables = list(data.coords) + data_vars if groupby and not_found: - raise ValueError('The supplied groupby dimension(s) %s ' + raise ValueError(f'The supplied groupby dimension(s) {not_found} ' 'could not be found, expected one or ' - 'more of: %s' % (not_found, list(data.coords))) + f'more of: {list(data.coords)}') else: if gridded and kind not in ('points', 'dataset'): - raise ValueError('%s plot type requires gridded data, ' + raise ValueError(f'{kind} plot type requires gridded data, ' 'e.g. a NumPy array or xarray Dataset, ' - 'found %s type' % (kind, type(self.data).__name__)) + f'found {type(self.data).__name__} type') if hasattr(data, 'columns') and hasattr(data.columns, 'name') and data.columns.name and not group_label: group_label = data.columns.name @@ -903,9 +903,9 @@ def _process_data(self, kind, data, x, y, by, groupby, row, col, not_found = [g for g in groupby+by_cols if g not in list(self.data.columns)+indexes] not_found, self.data = process_derived_datetime_pandas(self.data, not_found, indexes) if groupby and not_found: - raise ValueError('The supplied groupby dimension(s) %s ' + raise ValueError(f'The supplied groupby dimension(s) {not_found} ' 'could not be found, expected one or ' - 'more of: %s' % (not_found, list(self.data.columns))) + f'more of: {list(self.data.columns)}') if transforms: self.data = Dataset(self.data, indexes).transform(**transforms).data @@ -967,7 +967,7 @@ def _process_data(self, kind, data, x, y, by, groupby, row, col, except Exception as e: if attr_labels is True: param.main.param.warning('Unable to auto label using xarray attrs ' - 'because {e}'.format(e=e)) + f'because {e}') def _process_plot(self): kind = self.kind @@ -1387,8 +1387,7 @@ def _apply_layers(self, obj): if self.tiles: tile_source = 'EsriImagery' if self.tiles == 'ESRI' else self.tiles - warning = ("%s tiles not recognized, must be one of: %s or a tile object" % - (tile_source, sorted(hv.element.tile_sources))) + warning = ("{} tiles not recognized, must be one of: {} or a tile object".format(tile_source, sorted(hv.element.tile_sources))) if tile_source is True: tiles = hv.element.tiles.OSM() elif tile_source in hv.element.tile_sources.keys(): diff --git a/hvplot/interactive.py b/hvplot/interactive.py index baabfd574..844f9b9a4 100644 --- a/hvplot/interactive.py +++ b/hvplot/interactive.py @@ -227,7 +227,7 @@ def __new__(cls, obj, **kwargs): for subcls in cls.__subclasses__(): if subcls.applies(obj): clss = subcls - inst = super(Interactive, cls).__new__(clss) + inst = super().__new__(clss) inst._shared_obj = kwargs.get('_shared_obj', [obj]) inst._fn = fn return inst diff --git a/hvplot/plotting/core.py b/hvplot/plotting/core.py index 6d7e99e10..6315ff498 100644 --- a/hvplot/plotting/core.py +++ b/hvplot/plotting/core.py @@ -57,7 +57,7 @@ def __call__(self, x=None, y=None, kind=None, **kwds): if isinstance(kind, str) and kind not in self.__all__: raise NotImplementedError( - "kind='{kind}' for data of type {type}".format(kind=kind, type=type(self._data)) + f"kind='{kind}' for data of type {type(self._data)}" ) if panel_available: @@ -115,8 +115,8 @@ def __getattribute__(self, name): if "kind" in plot_opts and name in HoloViewsConverter._kind_mapping: param.main.param.warning( "Custom options for existing plot types should not " - "declare the 'kind' argument. The .%s plot method " - "was unexpectedly customized with kind=%r." % (plot_opts["kind"], name) + "declare the 'kind' argument. The .{} plot method " + "was unexpectedly customized with kind={!r}.".format(plot_opts["kind"], name) ) plot_opts["kind"] = name return hvPlotBase(self._data, **dict(self._metadata, **plot_opts)) diff --git a/hvplot/plotting/scatter_matrix.py b/hvplot/plotting/scatter_matrix.py index ecd975f97..17595c003 100644 --- a/hvplot/plotting/scatter_matrix.py +++ b/hvplot/plotting/scatter_matrix.py @@ -82,11 +82,9 @@ def scatter_matrix(data, c=None, chart='scatter', diagonal='hist', data = _hv.Dataset(_convert_col_names_to_str(data)) supported = list(HoloViewsConverter._kind_mapping) if diagonal not in supported: - raise ValueError('diagonal type must be one of: %s, found %s' % - (supported, diagonal)) + raise ValueError(f'diagonal type must be one of: {supported}, found {diagonal}') if chart not in supported: - raise ValueError('Chart type must be one of: %s, found %s' % - (supported, chart)) + raise ValueError(f'Chart type must be one of: {supported}, found {chart}') diagonal = HoloViewsConverter._kind_mapping[diagonal] chart = HoloViewsConverter._kind_mapping[chart] diff --git a/hvplot/tests/test_links.py b/hvplot/tests/test_links.py index feced6fe3..3f8d8d34e 100644 --- a/hvplot/tests/test_links.py +++ b/hvplot/tests/test_links.py @@ -60,7 +60,7 @@ def _clean_url(url: str): def _find_urls(text): url = re.findall(URL_REGEX, text) - return set(_clean_url(x[0]) for x in url if not _skip_url(x[0])) + return {_clean_url(x[0]) for x in url if not _skip_url(x[0])} def _request_a_response(url): diff --git a/hvplot/tests/testoptions.py b/hvplot/tests/testoptions.py index 215d67005..e961a1261 100644 --- a/hvplot/tests/testoptions.py +++ b/hvplot/tests/testoptions.py @@ -7,7 +7,6 @@ from holoviews import Store from holoviews.core.options import Options, OptionTree -from packaging.version import Version @pytest.fixture(scope='class') @@ -544,8 +543,4 @@ def test_dataset_scatter_with_title(self, ds2, backend): ds_sel = ds2.sel(time=0, band=0, x=0, y=0) plot = ds_sel.hvplot.scatter(x='foo', y='bar') # Image plot opts = Store.lookup_options(backend, plot, 'plot') - # First assertion to remove when support for Python 3.7 is dropped. - if Version(xr.__version__) < Version('2022.6.0'): - assert opts.kwargs['title'] == 'y = 0, x = 0, time = 0, band = 0' - else: - assert opts.kwargs['title'] == 'time = 0, y = 0, x = 0, band = 0' + assert opts.kwargs['title'] == 'time = 0, y = 0, x = 0, band = 0' diff --git a/hvplot/tests/testui.py b/hvplot/tests/testui.py index d80889ce9..12f60172f 100644 --- a/hvplot/tests/testui.py +++ b/hvplot/tests/testui.py @@ -1,20 +1,10 @@ import holoviews as hv import hvplot.pandas -import pytest - -try: - from bokeh.sampledata import penguins -except ImportError: - penguins = None +from bokeh.sampledata import penguins from hvplot.ui import hvDataFrameExplorer -pytestmark = pytest.mark.skipif( - penguins is None, - reason='Penguins dataset not available on Python 3.6', -) - -df = penguins.data if penguins is not None else None +df = penguins.data def test_explorer_basic(): diff --git a/hvplot/tests/testutil.py b/hvplot/tests/testutil.py index dd578c925..704b6e7f3 100644 --- a/hvplot/tests/testutil.py +++ b/hvplot/tests/testutil.py @@ -281,10 +281,6 @@ def test_check_crs(): assert p is None -@pytest.mark.skipif( - sys.version_info < (3, 8), - reason="PyProj is no longer releasing for Python 3.7", -) @pytest.mark.parametrize("input", [ "+init=epsg:26911", "4326", diff --git a/hvplot/ui.py b/hvplot/ui.py index 301aca48d..09e76a496 100644 --- a/hvplot/ui.py +++ b/hvplot/ui.py @@ -577,7 +577,7 @@ def settings(self): """ settings = {} for controller in self._controllers.values(): - params = set(controller.param) - set(['name', 'explorer']) + params = set(controller.param) - {'name', 'explorer'} for p in params: value = getattr(controller, p) if value != controller.param[p].default: diff --git a/hvplot/util.py b/hvplot/util.py index 90497af16..83043c8f2 100644 --- a/hvplot/util.py +++ b/hvplot/util.py @@ -69,7 +69,7 @@ def check_crs(crs): try: crs_type = pyproj.crs.CRS except AttributeError: - class Dummy(): + class Dummy: pass crs_type = Dummy diff --git a/setup.py b/setup.py index ccb00f84b..0bc3a79cc 100644 --- a/setup.py +++ b/setup.py @@ -86,23 +86,18 @@ def get_setup_version(reponame): 'pooch >=1.6.0', 'fiona', 'rioxarray', - # Extra dependency of cartopy on Python 3.6 only - 'pyepsg', 'matplotlib', 'plotly', 'pygraphviz', - 'ipykernel <6.18.0', # temporary - 'numpy < 1.24', # temporary, for a numba error 'ipywidgets', + 'numba >=0.51.0', + 'datashader >=0.6.5', + 'spatialpandas >=0.4.3', ] -# Packages not working on python 3.11 because of numba -if sys.version_info < (3, 11): - extras_require['examples'] += [ - 'numba >=0.51.0', - 'datashader >=0.6.5', - 'spatialpandas >=0.4.3', - ] +extras_require['examples_conda'] = [ + 'hdf5 !=1.14.1', # Gives coredump in test suite on Linux and Mac +] # Run the example tests by installing examples_tests together with tests extras_require["examples_tests"] = extras_require["examples"] + extras_require['tests_nb'] @@ -128,7 +123,7 @@ def get_setup_version(reponame): name='hvplot', version=get_setup_version("hvplot"), description='A high-level plotting API for the PyData ecosystem built on HoloViews.', - long_description=open("README.md", mode="r", encoding="utf-8").read(), + long_description=open("README.md", encoding="utf-8").read(), long_description_content_type="text/markdown", author= "Philipp Rudiger", author_email= "developers@pyviz.org", @@ -142,8 +137,6 @@ def get_setup_version(reponame): classifiers = [ "License :: OSI Approved :: BSD License", "Development Status :: 5 - Production/Stable", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -154,7 +147,7 @@ def get_setup_version(reponame): "Natural Language :: English", "Topic :: Scientific/Engineering", "Topic :: Software Development :: Libraries"], - python_requires=">=3.6", + python_requires=">=3.8", install_requires=install_requires, extras_require=extras_require, tests_require=extras_require['tests'], diff --git a/tox.ini b/tox.ini index 6ed70eb3c..2d82b8664 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ [tox] # python version test group extra envs extra commands -envlist = {py36,py37,py38,py39,py310,py311}-{unit,examples,all}-{default}-{dev,pkg} +envlist = {py38,py39,py310,py311}-{unit,examples,all}-{default}-{dev,pkg} [_unit] description = Run unit tests with coverage