From 5ec3d93588a13aabb2f858d8d5b5543468d2ea2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro?= Date: Wed, 16 Nov 2022 09:16:06 +0100 Subject: [PATCH 1/8] Added pre-commit --- .github/workflows/test.yaml | 6 +- .pre-commit-config.yaml | 22 ++++ doc/developer_guide/index.rst | 4 +- doc/getting_started/installation.md | 1 - doc/releases.md | 3 +- doc/user_guide/Streaming.rst | 1 - hvplot/__init__.py | 10 +- hvplot/backend_transforms.py | 32 +++--- hvplot/converter.py | 18 +-- hvplot/ibis.py | 2 +- hvplot/interactive.py | 123 ++++++++++----------- hvplot/plotting/parallel_coordinates.py | 2 +- hvplot/plotting/scatter_matrix.py | 18 +-- hvplot/tests/plotting/testscattermatrix.py | 10 +- hvplot/tests/testcharts.py | 4 +- hvplot/tests/testgridplots.py | 2 +- hvplot/tests/testinteractive.py | 15 ++- hvplot/tests/teststreaming.py | 2 +- hvplot/tests/testui.py | 2 +- hvplot/ui.py | 12 +- hvplot/util.py | 6 +- pyproject.toml | 3 + 22 files changed, 157 insertions(+), 141 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fc8730d5e..f0a321e57 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -36,7 +36,7 @@ jobs: timeout-minutes: 90 defaults: run: - shell: bash -l {0} + shell: bash -l {0} env: DESC: "Python ${{ matrix.python-version }} tests" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -126,7 +126,7 @@ jobs: timeout-minutes: 90 defaults: run: - shell: bash -l {0} + shell: bash -l {0} env: DESC: "Python ${{ matrix.python-version }} tests" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -169,7 +169,7 @@ jobs: # 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) + # for 3.6 (https://github.com/conda-forge/tqdm-feedstock/pull/114) conda install "panel=0.12" "importlib_resources" --no-update-deps - name: hvplot install if env cached if: steps.cache.outputs.cache-hit == 'true' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..0127b8206 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,22 @@ +# This is the configuration for pre-commit, a local framework for managing pre-commit hooks +# Check out the docs at: https://pre-commit.com/ + +default_stages: [commit] +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-builtin-literals + - id: check-case-conflict + - id: check-docstring-first + - id: check-executables-have-shebangs + - id: check-toml + - id: detect-private-key + - id: end-of-file-fixer + exclude: (\.min\.js$|\.svg) + - id: trailing-whitespace +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.121 + hooks: + - id: ruff + files: hvplot/ diff --git a/doc/developer_guide/index.rst b/doc/developer_guide/index.rst index 8fc7ce35e..cc207d77d 100644 --- a/doc/developer_guide/index.rst +++ b/doc/developer_guide/index.rst @@ -123,8 +123,8 @@ You can list the available `doit` commands with `doit list`. env_create Create named environment if it doesn't already exist env_dependency_graph Write out dependency graph of named environment. env_export Generate a pinned environment.yaml from specified env, filtering - env_export2 - list_envs + env_export2 + list_envs miniconda_download Download Miniconda3-latest miniconda_install Install Miniconda3-latest to location if not already present package_build Build and then test conda.recipe/ (or specified alternative). diff --git a/doc/getting_started/installation.md b/doc/getting_started/installation.md index 57a1b2119..b97bd7a6c 100644 --- a/doc/getting_started/installation.md +++ b/doc/getting_started/installation.md @@ -21,4 +21,3 @@ For versions of `jupyterlab>=3.0` the necessary extension is automatically bundl To run the guides in this site create an environment with the required dependencies: conda create -n hvplot-env -c pyviz -c conda-forge -c nodefaults hvplot geoviews datashader xarray pandas geopandas dask streamz networkx intake intake-xarray intake-parquet s3fs scipy spatialpandas pooch rasterio fiona plotly matplotlib jupyterlab - diff --git a/doc/releases.md b/doc/releases.md index f0bd2b394..5cd8d3b15 100644 --- a/doc/releases.md +++ b/doc/releases.md @@ -250,7 +250,7 @@ This is a major release that includes bug fixes, changes to default behavior, an Features: - Widget handling capabilities to facilitate interactivity ([#323](https://github.com/holoviz/hvplot/pull/323), [#331](https://github.com/holoviz/hvplot/pull/331)) - - New default colormaps ([#258](https://github.com/holoviz/hvplot/pull/258), [#316](https://github.com/holoviz/hvplot/pull/316), [#206](https://github.com/holoviz/hvplot/pull/206)) + - New default colormaps ([#258](https://github.com/holoviz/hvplot/pull/258), [#316](https://github.com/holoviz/hvplot/pull/316), [#206](https://github.com/holoviz/hvplot/pull/206)) - long_name(units) used to label xarray objects ([#173](https://github.com/holoviz/hvplot/pull/173)) - Derived datetime accessor handlind ([#263](https://github.com/holoviz/hvplot/pull/263), [#286](https://github.com/holoviz/hvplot/pull/286)) - `coastline` and `tiles` options for easy geo plots. @@ -283,4 +283,3 @@ This release includes a number of major improvements to the documentation and co - Exposed bokeh styling options for all plot types ([#134](https://github.com/pyviz/hvplot/pull/134)) - Compatibility with latest HoloViews/GeoViews releases ([#113](https://github.com/pyviz/hvplot/pull/113), [#118](https://github.com/pyviz/hvplot/pull/118), [#134](https://github.com/pyviz/hvplot/pull/134)) - Added control over tools ([#120](https://github.com/pyviz/hvplot/pull/120)) and legend position ([#119](https://github.com/pyviz/hvplot/pull/119)) - diff --git a/doc/user_guide/Streaming.rst b/doc/user_guide/Streaming.rst index c2b83d1f8..2c3d0ba31 100644 --- a/doc/user_guide/Streaming.rst +++ b/doc/user_guide/Streaming.rst @@ -3,4 +3,3 @@ _________ .. notebook:: hvplot ../../examples/user_guide/Streaming.ipynb :skip_execute: True - diff --git a/hvplot/__init__.py b/hvplot/__init__.py index 8a9c179f6..c77c43745 100644 --- a/hvplot/__init__.py +++ b/hvplot/__init__.py @@ -212,9 +212,9 @@ def bind(function, *args, **kwargs): """ Returns a *reactive* function that can be used to start your `.interactive` pipeline by running a model or loading data depending on inputs from widgets, parameters or python objects. - + The widgets can be Panel or ipywidgets. - + Reference: https://hvplot.holoviz.org/user_guide/Interactive.html#functions-as-inputs Parameters @@ -239,16 +239,16 @@ def bind(function, *args, **kwargs): >>> import pandas as pd >>> import numpy as np - + >>> def algorithm(alpha): ... # An example algorithm that uses alpha ... ... return pd.DataFrame({"output": (np.array(range(0,100)) ** alpha)*50}) - + Make it **interactive** using `.bind`, `.interactive` and widgets. >>> import hvplot >>> import panel as pn - + >>> alpha = pn.widgets.FloatSlider(value=0.5, start=0, end=1.0, step=0.1, name="Alpha") >>> top = pn.widgets.RadioButtonGroup(value=10, options=[5, 10, 25], name="Top") >>> interactive_table = ( diff --git a/hvplot/backend_transforms.py b/hvplot/backend_transforms.py index 611b5b3c1..e2b25db4b 100644 --- a/hvplot/backend_transforms.py +++ b/hvplot/backend_transforms.py @@ -202,7 +202,7 @@ def _transfer_opts_cur_backend(element): 'text_alpha': lambda k, v: ('alpha', v), 'text_baseline': lambda k, v: ('verticalalignment', _text_baseline_bk_mpl_mapping.get(v, UNSET)), 'text_color': lambda k, v: ('color', v), - 'text_font': UNSET, + 'text_font': UNSET, 'text_font_size': lambda k, v: ('size', v), 'text_font_style': UNSET, 'box_alpha': UNSET, @@ -265,21 +265,21 @@ def _transfer_opts_cur_backend(element): 'edge_line_dash': lambda k, v: ('edge_linestyle', v), 'edge_line_join': UNSET, 'edge_line_width': lambda k, v: ('edge_linewidth', v), - 'edge_visible': UNSET, - 'node_alpha': lambda k, v: ('node_alpha', v), - 'node_cmap': lambda k, v: ('node_cmap', v), - 'node_color': lambda k, v: ('node_color', v), - 'node_fill_alpha': UNSET, - 'node_fill_color': lambda k, v: ('node_facecolors', v), - 'node_line_alpha': UNSET, - 'node_line_cap': UNSET, - 'node_line_color': lambda k, v: ('node_edgecolors', v), - 'node_line_dash': UNSET, - 'node_line_join': UNSET, - 'node_line_width': lambda k, v: ('node_linewidth', v), - 'node_marker': lambda k, v: ('node_marker', v), - 'node_radius': UNSET, - 'node_size': lambda k, v: ('node_size', v), + 'edge_visible': UNSET, + 'node_alpha': lambda k, v: ('node_alpha', v), + 'node_cmap': lambda k, v: ('node_cmap', v), + 'node_color': lambda k, v: ('node_color', v), + 'node_fill_alpha': UNSET, + 'node_fill_color': lambda k, v: ('node_facecolors', v), + 'node_line_alpha': UNSET, + 'node_line_cap': UNSET, + 'node_line_color': lambda k, v: ('node_edgecolors', v), + 'node_line_dash': UNSET, + 'node_line_join': UNSET, + 'node_line_width': lambda k, v: ('node_linewidth', v), + 'node_marker': lambda k, v: ('node_marker', v), + 'node_radius': UNSET, + 'node_size': lambda k, v: ('node_size', v), 'node_visible': lambda k, v: ('visible', v), }, } diff --git a/hvplot/converter.py b/hvplot/converter.py index 3d7617dcb..bd8e3e705 100644 --- a/hvplot/converter.py +++ b/hvplot/converter.py @@ -132,7 +132,7 @@ class HoloViewsConverter: If `cnorm='eq_hist` and there are only a few discrete values, then `rescale_discrete_levels=True` (the default) decreases the lower limit of the autoranged span so that the values are - rendering towards the (more visible) top of the `cmap` range, + rendering towards the (more visible) top of the `cmap` range, thus avoiding washout of the lower values. Has no effect if `cnorm!=`eq_hist`. responsive: boolean @@ -194,15 +194,15 @@ class HoloViewsConverter: very large if dynamic=False) datashade (default=False): Whether to apply rasterization and shading (colormapping) using - the Datashader library, returning an RGB object instead of + the Datashader library, returning an RGB object instead of individual points dynspread (default=False): - For plots generated with datashade=True or rasterize=True, + For plots generated with datashade=True or rasterize=True, automatically increase the point size when the data is sparse so that individual points become more visible rasterize (default=False): Whether to apply rasterization using the Datashader library, - returning an aggregated Image (to be colormapped by the + returning an aggregated Image (to be colormapped by the plotting backend) instead of individual points x_sampling/y_sampling (default=None): Specifies the smallest allowed sampling interval along the x/y axis. @@ -630,7 +630,7 @@ def _process_symmetric(self, symmetric, clim, check_symmetric_max): if data.size == 0: return False - + cmin = np.nanquantile(data, 0.05) cmax = np.nanquantile(data, 0.95) @@ -786,13 +786,13 @@ def _process_data(self, kind, data, x, y, by, groupby, row, col, groupby = [g for g in groupby if g not in grid] # Add a title to hvplot.xarray plots that displays scalar coords values, - # as done by xarray.plot() + # as done by xarray.plot() if not groupby and not grid: if isinstance(da, xr.DataArray): self._title = da._title_for_slice() elif isinstance(da, xr.Dataset): self._title = partial(xr.DataArray._title_for_slice, da)() - + self.data = data else: raise ValueError('Supplied data type %s not understood' % type(data).__name__) @@ -1325,7 +1325,7 @@ def method_wrapper(ds, x, y): if self.dynspread: processed = dynspread(processed, max_px=self.kwds.get('max_px', 3), threshold=self.kwds.get('threshold', 0.5)) - + opts = filter_opts(eltype, dict(self._plot_opts, **style), backend='bokeh') layers = self._apply_layers(processed).opts(eltype, **opts, backend='bokeh') layers = _transfer_opts_cur_backend(layers) @@ -1486,7 +1486,7 @@ def single_chart(self, element, x, y, data=None): labelled.append('x') if 'ylabel' in self._plot_opts and 'y' not in labelled: labelled.append('y') - + cur_el_opts = self._get_opts(element.name, labelled=labelled, backend='bokeh') compat_el_opts = self._get_opts(element.name, labelled=labelled, backend=self._backend_compat) for opts_ in [cur_el_opts, compat_el_opts]: diff --git a/hvplot/ibis.py b/hvplot/ibis.py index 1fe05d3e3..c8f8a3717 100644 --- a/hvplot/ibis.py +++ b/hvplot/ibis.py @@ -10,7 +10,7 @@ def patch(name='hvplot', extension='bokeh', logo=False): _patch_plot.__doc__ = hvPlotTabular.__call__.__doc__ patch_property = property(_patch_plot) setattr(ibis.Expr, name, patch_property) - + post_patch(extension, logo) patch() diff --git a/hvplot/interactive.py b/hvplot/interactive.py index b7f4f76d1..3a748abb2 100644 --- a/hvplot/interactive.py +++ b/hvplot/interactive.py @@ -1,64 +1,3 @@ -""" -interactive API -""" - -import abc -import operator -import sys -from functools import partial -from packaging.version import Version -from types import FunctionType, MethodType - -import holoviews as hv -import pandas as pd -import panel as pn -import param - -from panel.layout import Column, Row, VSpacer, HSpacer -from panel.util import get_method_owner, full_groupby -from panel.widgets.base import Widget - -from .converter import HoloViewsConverter -from .util import _flatten, is_tabular, is_xarray, is_xarray_dataarray - - -def _find_widgets(op): - widgets = [] - op_args = list(op['args']) + list(op['kwargs'].values()) - op_args = _flatten(op_args) - for op_arg in op_args: - # Find widgets introduced as `widget` in an expression - if isinstance(op_arg, Widget) and op_arg not in widgets: - widgets.append(op_arg) - # TODO: Find how to execute this path? - if isinstance(op_arg, hv.dim): - for nested_op in op_arg.ops: - for widget in _find_widgets(nested_op): - if widget not in widgets: - widgets.append(widget) - # Find Ipywidgets - if 'ipywidgets' in sys.modules: - from ipywidgets import Widget as IPyWidget - if isinstance(op_arg, IPyWidget) and op_arg not in widgets: - widgets.append(op_arg) - # Find widgets introduced as `widget.param.value` in an expression - if (isinstance(op_arg, param.Parameter) and - isinstance(op_arg.owner, pn.widgets.Widget) and - op_arg.owner not in widgets): - widgets.append(op_arg.owner) - if isinstance(op_arg, slice): - if Version(hv.__version__) < Version("1.15.1"): - raise ValueError( - "Using interactive with slices needs to have " - "Holoviews 1.15.1 or greater installed." - ) - nested_op = {"args": [op_arg.start, op_arg.stop, op_arg.step], "kwargs": {}} - for widget in _find_widgets(nested_op): - if widget not in widgets: - widgets.append(widget) - return widgets - - """ How Interactive works --------------------- @@ -129,7 +68,7 @@ def _find_widgets(op): is a Pandas Series, and no longer and DataFrame. The `_obj` attribute is implemented as a property which gets/sets the value -from a list that contains the shared attribute. This is required for the +from a list that contains the shared attribute. This is required for the "function as input" to be able to update the object from a callback set up on the root Interactive instance. @@ -146,6 +85,62 @@ def _find_widgets(op): display its repr. """ +import abc +import operator +import sys +from functools import partial +from packaging.version import Version +from types import FunctionType, MethodType + +import holoviews as hv +import pandas as pd +import panel as pn +import param + +from panel.layout import Column, Row, VSpacer, HSpacer +from panel.util import get_method_owner, full_groupby +from panel.widgets.base import Widget + +from .converter import HoloViewsConverter +from .util import _flatten, is_tabular, is_xarray, is_xarray_dataarray + + +def _find_widgets(op): + widgets = [] + op_args = list(op['args']) + list(op['kwargs'].values()) + op_args = _flatten(op_args) + for op_arg in op_args: + # Find widgets introduced as `widget` in an expression + if isinstance(op_arg, Widget) and op_arg not in widgets: + widgets.append(op_arg) + # TODO: Find how to execute this path? + if isinstance(op_arg, hv.dim): + for nested_op in op_arg.ops: + for widget in _find_widgets(nested_op): + if widget not in widgets: + widgets.append(widget) + # Find Ipywidgets + if 'ipywidgets' in sys.modules: + from ipywidgets import Widget as IPyWidget + if isinstance(op_arg, IPyWidget) and op_arg not in widgets: + widgets.append(op_arg) + # Find widgets introduced as `widget.param.value` in an expression + if (isinstance(op_arg, param.Parameter) and + isinstance(op_arg.owner, pn.widgets.Widget) and + op_arg.owner not in widgets): + widgets.append(op_arg.owner) + if isinstance(op_arg, slice): + if Version(hv.__version__) < Version("1.15.1"): + raise ValueError( + "Using interactive with slices needs to have " + "Holoviews 1.15.1 or greater installed." + ) + nested_op = {"args": [op_arg.start, op_arg.stop, op_arg.step], "kwargs": {}} + for widget in _find_widgets(nested_op): + if widget not in widgets: + widgets.append(widget) + return widgets + class Interactive: """ @@ -161,7 +156,7 @@ class Interactive: available on a data structure when it has been patched, e.g. after executing `import hvplot.pandas`. The accessor can also be called which allows to pass down kwargs. - + A pipeline can then be created from this object, the pipeline will render with its widgets and its interactive output. @@ -207,7 +202,7 @@ class Interactive: _fig = None def __new__(cls, obj, **kwargs): - # __new__ implemented to support functions as input, e.g. + # __new__ implemented to support functions as input, e.g. # hvplot.find(foo, widget).interactive().max() if 'fn' in kwargs: fn = kwargs.pop('fn') diff --git a/hvplot/plotting/parallel_coordinates.py b/hvplot/plotting/parallel_coordinates.py index 1b73ac34f..71f630e83 100644 --- a/hvplot/plotting/parallel_coordinates.py +++ b/hvplot/plotting/parallel_coordinates.py @@ -17,7 +17,7 @@ def parallel_coordinates(data, class_column, cols=None, alpha=0.5, consisting of n parallel lines. A point in n-dimensional space is represented as a polyline with vertices on the parallel axes; the position of the vertex on the i-th axis corresponds to the i-th coordinate - of the point. + of the point. Parameters ---------- diff --git a/hvplot/plotting/scatter_matrix.py b/hvplot/plotting/scatter_matrix.py index eef073aab..50e386ad7 100644 --- a/hvplot/plotting/scatter_matrix.py +++ b/hvplot/plotting/scatter_matrix.py @@ -51,19 +51,19 @@ def scatter_matrix(data, c=None, chart='scatter', diagonal='hist', Keyword options for the diagonal plots datashade (default=False): Whether to apply rasterization and shading (colormapping) using - the Datashader library, returning an RGB object instead of + the Datashader library, returning an RGB object instead of individual points rasterize (default=False): Whether to apply rasterization using the Datashader library, - returning an aggregated Image (to be colormapped by the + returning an aggregated Image (to be colormapped by the plotting backend) instead of individual points dynspread (default=False): - For plots generated with datashade=True or rasterize=True, + For plots generated with datashade=True or rasterize=True, automatically increase the point size when the data is sparse so that individual points become more visible. kwds supported include ``max_px``, ``threshold``, ``shape``, ``how`` and ``mask``. spread (default=False): - Make plots generated with datashade=True or rasterize=True + Make plots generated with datashade=True or rasterize=True increase the point size to make points more visible, by applying a fixed spreading of a certain number of cells/pixels. kwds supported include: ``px``, ``shape``, ``how`` and ``mask``. @@ -78,7 +78,7 @@ def scatter_matrix(data, c=None, chart='scatter', diagonal='hist', -------- :func:`pandas.plotting.scatter_matrix` : Equivalent pandas function. """ - + data = _hv.Dataset(data) supported = list(HoloViewsConverter._kind_mapping) if diagonal not in supported: @@ -103,7 +103,7 @@ def scatter_matrix(data, c=None, chart='scatter', diagonal='hist', "dynamic update of rasterized/datashaded scatter matrix. " "Update holoviews to a newer version." ) - + if rasterize and datashade: raise ValueError("Choose to either rasterize or " "datashade the scatter matrix, not both.") @@ -122,7 +122,7 @@ def scatter_matrix(data, c=None, chart='scatter', diagonal='hist', #remove datashade kwds if datashade or rasterize: - import holoviews.operation.datashader as hd + import holoviews.operation.datashader as hd ds_kwds = {} if 'aggregator' in kwds: @@ -194,7 +194,7 @@ def scatter_matrix(data, c=None, chart='scatter', diagonal='hist', {chart.__name__: chart_opts, diagonal.__name__: diagonal_opts}, backend='bokeh', ) - + # Perform datashade options after all the coloring is finished. if datashade or rasterize: aggregatefn = hd.datashade if datashade else hd.rasterize @@ -203,6 +203,6 @@ def scatter_matrix(data, c=None, chart='scatter', diagonal='hist', spreadfn = hd.dynspread if dynspread else (hd.spread if spread else lambda z, **_: z) eltype = _hv.RGB if datashade else _hv.Image grid = grid.map(partial(spreadfn, **sp_kwds), specs=eltype) - + grid = _transfer_opts_cur_backend(grid) return grid diff --git a/hvplot/tests/plotting/testscattermatrix.py b/hvplot/tests/plotting/testscattermatrix.py index e11854296..ca837af7a 100644 --- a/hvplot/tests/plotting/testscattermatrix.py +++ b/hvplot/tests/plotting/testscattermatrix.py @@ -35,7 +35,7 @@ def test_wrong_chart(self): def test_diagonal_default(self): sm = scatter_matrix(self.df) self.assertIsInstance(sm['a', 'a'], Histogram) - + def test_offdiagonal_default(self): sm = scatter_matrix(self.df) self.assertIsInstance(sm['a', 'b'], Scatter) @@ -43,11 +43,11 @@ def test_offdiagonal_default(self): def test_diagonal_kde(self): sm = scatter_matrix(self.df, diagonal='kde') self.assertIsInstance(sm['a', 'a'], Distribution) - + def test_offdiagonal_bivariate(self): sm = scatter_matrix(self.df, chart='bivariate') self.assertIsInstance(sm['a', 'b'], Bivariate) - + def test_offdiagonal_hexbin(self): sm = scatter_matrix(self.df, chart='hexbin') self.assertIsInstance(sm['a', 'b'], HexTiles) @@ -68,12 +68,12 @@ def test_c(self): df = self.df.copy(deep=True) df['e'] = np.random.choice(list('xyz'), size=len(df)) sm = scatter_matrix(df, c='e') - + self.assertIsInstance(sm['a', 'a'], NdOverlay) diag_kdims = sm['a', 'a'].kdims self.assertEqual(len(diag_kdims), 1) self.assertEqual(diag_kdims[0].name, 'e') - + self.assertIsInstance(sm['a', 'b'], Scatter) offdiag_vdims = sm['a', 'b'].vdims self.assertTrue('e' in (d.name for d in offdiag_vdims)) diff --git a/hvplot/tests/testcharts.py b/hvplot/tests/testcharts.py index 51d93c412..3adf2587c 100644 --- a/hvplot/tests/testcharts.py +++ b/hvplot/tests/testcharts.py @@ -139,7 +139,7 @@ def test_wide_chart(self, kind, element): def test_by_datetime_accessor(self): plot = self.dt_df.hvplot.line('index.dt.day', '0', by='index.dt.month') obj = NdOverlay({m: Curve((g.index.day, g[0]), 'index.dt.day', '0') - for m, g in self.dt_df.groupby(self.dt_df.index.month)}, 'index.dt.month') + for m, g in self.dt_df.groupby(self.dt_df.index.month)}, 'index.dt.month') self.assertEqual(plot, obj) @parameterized.expand([('line', Curve), ('area', Area), ('scatter', Scatter)]) @@ -340,7 +340,7 @@ def test_default_y_not_in_by(self): assert plot.kdims == ['x'] assert plot[1].kdims == ['index'] assert plot[1].vdims == ['y'] - + def test_errorbars_no_hover(self): plot = self.df_desc.hvplot.errorbars(y='mean', yerr1='std') assert list(plot.dimensions()) == ['index', 'mean', 'std'] diff --git a/hvplot/tests/testgridplots.py b/hvplot/tests/testgridplots.py index 278139f66..ca9e22ed9 100644 --- a/hvplot/tests/testgridplots.py +++ b/hvplot/tests/testgridplots.py @@ -214,7 +214,7 @@ def test_symmetric_dataset_not_in_memory(self): # not be done and return False. assert not plot_opts.kwargs['symmetric'] ds.close() - + def test_symmetric_dataset_in_memory(self): da = xr.DataArray( data=np.arange(-100, 100).reshape(10, 10, 2), diff --git a/hvplot/tests/testinteractive.py b/hvplot/tests/testinteractive.py index 20325d9e9..3f5eb0c76 100644 --- a/hvplot/tests/testinteractive.py +++ b/hvplot/tests/testinteractive.py @@ -99,7 +99,7 @@ def test_spy(clone_spy, series): assert clone_spy.calls[0].is_empty() si._clone(x='X') - + assert clone_spy.count == 2 assert not clone_spy.calls[1].is_empty() assert clone_spy.calls[1].kwargs == dict(x='X') @@ -252,7 +252,7 @@ def test_interactive_pandas_dataframe_hvplot_accessor_dmap(df): dfi = df.interactive() dfi = dfi.hvplot.line(y='A') - # TODO: Not sure about the logic + # TODO: Not sure about the logic assert dfi._dmap is True @@ -482,7 +482,7 @@ def test_interactive_pandas_frame_attrib(df, clone_spy): def test_interactive_pandas_series_operator_and_method(series, clone_spy): si = Interactive(series) - + si = (si + 2).head(2) assert isinstance(si, Interactive) @@ -638,7 +638,7 @@ def test_interactive_pandas_series_operator_and_method_out_widgets(series): w1 = pn.widgets.FloatSlider(value=2., start=1., end=5.) w2 = pn.widgets.IntSlider(value=2, start=1, end=5) si = Interactive(series) - + si = (si + w1).head(w2) widgets = si.widgets() @@ -690,7 +690,7 @@ def test_interactive_pandas_series_operator_widget_update(series): w.value = 3. assert repr(si._transform) == "dim('*').pd+FloatSlider(end=5.0, start=1.0, value=3.0)" - + out = si._callback() assert isinstance(out, pn.pane.DataFrame) pd.testing.assert_series_equal(out.object.A, series + 3.) @@ -703,7 +703,7 @@ def test_interactive_pandas_series_method_widget_update(series): w.value = 3 assert repr(si._transform) =="dim('*').pd.head(IntSlider(end=5, start=1, value=3))" - + out = si._callback() assert isinstance(out, pn.pane.DataFrame) pd.testing.assert_series_equal(out.object.A, series.head(3)) @@ -1143,7 +1143,7 @@ def test_interactive_pandas_series_plot_kind_attr(series, clone_spy): # _clone in _resolve_accessor in __getattribute__(name='line') assert clone_spy.calls[1].depth == 2 assert len(clone_spy.calls[1].args) == 1 - # assert repr(clone_spy.calls[1].args[0]) == "dim('*').pd.plot()" + # assert repr(clone_spy.calls[1].args[0]) == "dim('*').pd.plot()" assert len(clone_spy.calls[1].kwargs) == 1 assert 'inherit_kwargs' in clone_spy.calls[1].kwargs assert 'ax' in clone_spy.calls[1].kwargs['inherit_kwargs'] @@ -1367,4 +1367,3 @@ def piped(df, msg): df.interactive.pipe(piped, msg="1").pipe(piped, msg="2") assert len(msgs) == 3 - diff --git a/hvplot/tests/teststreaming.py b/hvplot/tests/teststreaming.py index 461547fec..d62865a73 100644 --- a/hvplot/tests/teststreaming.py +++ b/hvplot/tests/teststreaming.py @@ -6,7 +6,7 @@ class TestExplicitStreamPlotting(TestCase): - + def setUp(self): import hvplot.pandas # noqa self.df = pd.DataFrame([[1, 2], [3, 4], [5, 6]], columns=['x', 'y']) diff --git a/hvplot/tests/testui.py b/hvplot/tests/testui.py index 30b6cfd30..d80889ce9 100644 --- a/hvplot/tests/testui.py +++ b/hvplot/tests/testui.py @@ -59,7 +59,7 @@ def test_explorer_plot_code(): hvplot_code = explorer.plot_code() assert hvplot_code == "df.hvplot(by=['species'], kind='scatter', x='bill_length_mm', y=['bill_depth_mm'])" - + hvplot_code = explorer.plot_code(var_name='othername') assert hvplot_code == "othername.hvplot(by=['species'], kind='scatter', x='bill_length_mm', y=['bill_depth_mm'])" diff --git a/hvplot/ui.py b/hvplot/ui.py index e907ea489..2879e32b0 100644 --- a/hvplot/ui.py +++ b/hvplot/ui.py @@ -33,8 +33,8 @@ def explorer(data, **kwargs): """Explore your data and design your plot via an interactive user interface. This function returns an interactive Panel component that enable you to quickly change the - settings of your plot via widgets. - + settings of your plot via widgets. + Reference: https://hvplot.holoviz.org/getting_started/explorer.html Parameters @@ -56,7 +56,7 @@ def explorer(data, **kwargs): >>> import pandas as pd >>> df = pd.DataFrame({"x": [1, 2, 3], "y": [1, 4, 9]}) >>> hvplot.explorer(df) - + You can also specify initial values >>> hvplot.explorer(df, kind='bar', x='x') @@ -536,10 +536,10 @@ def plot_code(self, var_name='df'): args += f'{k}={v!r}, ' args = args[:-2] return f'{var_name}.hvplot({args})' - + def save(self, filename, **kwargs): """Save the plot to file. - + Calls the `holoviews.save` utility, refer to its documentation for a full description of the available kwargs. @@ -552,7 +552,7 @@ def save(self, filename, **kwargs): def settings(self): """Return a dictionary of the customized settings. - + This dictionary can be reused as an unpacked input to the explorer or a call to the `.hvplot` accessor. diff --git a/hvplot/util.py b/hvplot/util.py index ffac18ef5..4ec1ac6c6 100644 --- a/hvplot/util.py +++ b/hvplot/util.py @@ -149,9 +149,9 @@ def proj_to_cartopy(proj): km_std = {'lat_1': 'lat_1', 'lat_2': 'lat_2', } - kw_proj = dict() - kw_globe = dict() - kw_std = dict() + kw_proj = {} + kw_globe = {} + kw_std = {} for s in srs.split('+'): s = s.split('=') if len(s) != 2: diff --git a/pyproject.toml b/pyproject.toml index fec98ab1a..e9b669913 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,3 +4,6 @@ requires = [ "pyct >=0.4.4", "setuptools >=30.3.0" ] + +[tool.ruff] +ignore = ["E", "W"] From faa8ca3cb07e93eaf4bf75075e0d66309238dd4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro?= Date: Wed, 16 Nov 2022 09:18:56 +0100 Subject: [PATCH 2/8] Added pre-commit to github actions --- .github/workflows/test.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f0a321e57..59ac2e8af 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,8 +14,24 @@ env: CACHE_VERSION: 3 jobs: + pre_commit: + name: Run pre-commit hooks + runs-on: 'ubuntu-latest' + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: "1" + - name: set PY + run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV + - uses: actions/cache@v3 + with: + path: ~/.cache/pre-commit + key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} + - name: pre-commit + uses: pre-commit/action@v3.0.0 test_suite: name: Pytest on ${{ matrix.os }} with Python ${{ matrix.python-version }} + needs: [pre_commit] runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -110,6 +126,7 @@ jobs: 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 From fbc821205a318b0d45fda41eac3514350115043b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro?= Date: Wed, 16 Nov 2022 12:54:10 +0100 Subject: [PATCH 3/8] Add feedback from review --- .pre-commit-config.yaml | 10 ++-- hvplot/interactive.py | 117 +++++++++++++++++++++------------------- pyproject.toml | 3 -- 3 files changed, 67 insertions(+), 63 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0127b8206..a723a2620 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,14 +9,16 @@ repos: - id: check-builtin-literals - id: check-case-conflict - id: check-docstring-first + exclude: hvplot/interactive.py - id: check-executables-have-shebangs - id: check-toml - id: detect-private-key - id: end-of-file-fixer - exclude: (\.min\.js$|\.svg) + exclude: (\.min\.js$|\.svg$) - id: trailing-whitespace -- repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.121 +- repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 hooks: - - id: ruff + - id: flake8 # See 'setup.cfg' for args + args: [hvplot] files: hvplot/ diff --git a/hvplot/interactive.py b/hvplot/interactive.py index 3a748abb2..261d1f960 100644 --- a/hvplot/interactive.py +++ b/hvplot/interactive.py @@ -1,3 +1,64 @@ +""" +interactive API +""" + +import abc +import operator +import sys +from functools import partial +from packaging.version import Version +from types import FunctionType, MethodType + +import holoviews as hv +import pandas as pd +import panel as pn +import param + +from panel.layout import Column, Row, VSpacer, HSpacer +from panel.util import get_method_owner, full_groupby +from panel.widgets.base import Widget + +from .converter import HoloViewsConverter +from .util import _flatten, is_tabular, is_xarray, is_xarray_dataarray + + +def _find_widgets(op): + widgets = [] + op_args = list(op['args']) + list(op['kwargs'].values()) + op_args = _flatten(op_args) + for op_arg in op_args: + # Find widgets introduced as `widget` in an expression + if isinstance(op_arg, Widget) and op_arg not in widgets: + widgets.append(op_arg) + # TODO: Find how to execute this path? + if isinstance(op_arg, hv.dim): + for nested_op in op_arg.ops: + for widget in _find_widgets(nested_op): + if widget not in widgets: + widgets.append(widget) + # Find Ipywidgets + if 'ipywidgets' in sys.modules: + from ipywidgets import Widget as IPyWidget + if isinstance(op_arg, IPyWidget) and op_arg not in widgets: + widgets.append(op_arg) + # Find widgets introduced as `widget.param.value` in an expression + if (isinstance(op_arg, param.Parameter) and + isinstance(op_arg.owner, pn.widgets.Widget) and + op_arg.owner not in widgets): + widgets.append(op_arg.owner) + if isinstance(op_arg, slice): + if Version(hv.__version__) < Version("1.15.1"): + raise ValueError( + "Using interactive with slices needs to have " + "Holoviews 1.15.1 or greater installed." + ) + nested_op = {"args": [op_arg.start, op_arg.stop, op_arg.step], "kwargs": {}} + for widget in _find_widgets(nested_op): + if widget not in widgets: + widgets.append(widget) + return widgets + + """ How Interactive works --------------------- @@ -85,62 +146,6 @@ display its repr. """ -import abc -import operator -import sys -from functools import partial -from packaging.version import Version -from types import FunctionType, MethodType - -import holoviews as hv -import pandas as pd -import panel as pn -import param - -from panel.layout import Column, Row, VSpacer, HSpacer -from panel.util import get_method_owner, full_groupby -from panel.widgets.base import Widget - -from .converter import HoloViewsConverter -from .util import _flatten, is_tabular, is_xarray, is_xarray_dataarray - - -def _find_widgets(op): - widgets = [] - op_args = list(op['args']) + list(op['kwargs'].values()) - op_args = _flatten(op_args) - for op_arg in op_args: - # Find widgets introduced as `widget` in an expression - if isinstance(op_arg, Widget) and op_arg not in widgets: - widgets.append(op_arg) - # TODO: Find how to execute this path? - if isinstance(op_arg, hv.dim): - for nested_op in op_arg.ops: - for widget in _find_widgets(nested_op): - if widget not in widgets: - widgets.append(widget) - # Find Ipywidgets - if 'ipywidgets' in sys.modules: - from ipywidgets import Widget as IPyWidget - if isinstance(op_arg, IPyWidget) and op_arg not in widgets: - widgets.append(op_arg) - # Find widgets introduced as `widget.param.value` in an expression - if (isinstance(op_arg, param.Parameter) and - isinstance(op_arg.owner, pn.widgets.Widget) and - op_arg.owner not in widgets): - widgets.append(op_arg.owner) - if isinstance(op_arg, slice): - if Version(hv.__version__) < Version("1.15.1"): - raise ValueError( - "Using interactive with slices needs to have " - "Holoviews 1.15.1 or greater installed." - ) - nested_op = {"args": [op_arg.start, op_arg.stop, op_arg.step], "kwargs": {}} - for widget in _find_widgets(nested_op): - if widget not in widgets: - widgets.append(widget) - return widgets - class Interactive: """ diff --git a/pyproject.toml b/pyproject.toml index e9b669913..fec98ab1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,3 @@ requires = [ "pyct >=0.4.4", "setuptools >=30.3.0" ] - -[tool.ruff] -ignore = ["E", "W"] From 1e5ee158c50b908de669d6694eb3405dd3d86e68 Mon Sep 17 00:00:00 2001 From: maximlt Date: Fri, 18 Nov 2022 11:58:12 +0100 Subject: [PATCH 4/8] replace docstring by comment --- hvplot/interactive.py | 172 +++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/hvplot/interactive.py b/hvplot/interactive.py index 261d1f960..9ba4cc482 100644 --- a/hvplot/interactive.py +++ b/hvplot/interactive.py @@ -59,92 +59,92 @@ def _find_widgets(op): return widgets -""" -How Interactive works ---------------------- - -`Interactive` is a wrapper around a Python object that lets users create -interactive pipelines by calling existing APIs on an object with dynamic -parameters or widgets. - -An `Interactive` instance watches what operations are applied to the object. - -To do so, each operation returns a new `Interactive` instance - the creation -of a new instance being taken care of by the `_clone` method - which allows -the next operation to be recorded, and so on and so forth. E.g. `dfi.head()` -first records that the `'head'` attribute is accessed, this is achieved -by overriding `__getattribute__`. A new interactive object is returned, -which will then record that it is being called, and that new object will be -itself called as `Interactive` implements `__call__`. `__call__` returns -another `Interactive` instance. - -Note that under the hood even more `Interactive` instances may be created, -but this is the gist of it. - -To be able to watch all the potential operations that may be applied to an -object, `Interactive` implements on top of `__getattribute__` and -`__call__`: - -- operators such as `__gt__`, `__add__`, etc. -- the builtin functions `__abs__` and `__round__` -- `__getitem__` -- `__array_ufunc__` - -The `_depth` attribute starts at 0 and is incremented by 1 everytime -a new `Interactive` instance is created part of a chain. -The root instance in an expression has a `_depth` of 0. An expression can -consist of multiple chains, such as `dfi[dfi.A > 1]`, as the `Interactive` -instance is referenced twice in the expression. As a consequence `_depth` -is not the total count of `Interactive` instance creations of a pipeline, -it is the count of instances created in outer chain. In the example, that -would be `dfi[]`. `Interactive` instances don't have references about -the instances that created them or that they create, they just know their -current location in a chain thanks to `_depth`. However, as some parameters -need to be passed down the whole pipeline, they do have to propagate. E.g. -in `dfi.interactive(width=200)`, `width=200` will be propagated as `kwargs`. - -Recording the operations applied to an object in a pipeline is done -by gradually building a so-called "dim expression", or "dim transform", -which is an expression language provided by HoloViews. dim transform -objects are a way to express transforms on `Dataset`s, a `Dataset` being -another HoloViews object that is a wrapper around common data structures -such as Pandas/Dask/... Dataframes/Series, Xarray Dataset/DataArray, etc. -For instance a Python expression such as `(series + 2).head()` can be -expressed with a dim transform whose repr will be `(dim('*').pd+2).head(2)`, -effectively showing that the dim transfom has recorded the different -operations that are meant to be applied to the data. -The `_transform` attribute stores the dim transform. - -The `_obj` attribute holds the original data structure that feeds the -pipeline. All the `Interactive` instances created while parsing the -pipeline share the same `_obj` object. And they all wrap it in a `Dataset` -instance, and all apply the current dim transform they are aware of to -the original data structure to compute the intermediate state of the data, -that is stored it in the `_current` attribute. Doing so is particularly -useful in Notebook sessions, as this allows to inspect the transformed -object at any point of the pipeline, and as such provide correct -auto-completion and docstrings. E.g. executing `dfi.A.max?` in a Notebook -will correctly return the docstring of the Pandas Series `.max()` method, -as the pipeline evaluates `dfi.A` to hold a current object `_current` that -is a Pandas Series, and no longer and DataFrame. - -The `_obj` attribute is implemented as a property which gets/sets the value -from a list that contains the shared attribute. This is required for the -"function as input" to be able to update the object from a callback set up -on the root Interactive instance. - -The `_method` attribute is a string that temporarily stores the method/attr -accessed on the object, e.g. `_method` is 'head' in `dfi.head()`, until the -Interactive instance created in the pipeline is called at which point `_method` -is reset to None. In cases such as `dfi.head` or `dfi.A`, `_method` is not -(yet) reset to None. At this stage the Interactive instance returned has -its `_current` attribute not updated, e.g. `dfi.A._current` is still the -original dataframe, not the 'A' series. Keeping `_method` is thus useful for -instance to display `dfi.A`, as the evaluation of the object will check -whether `_method` is set or not, and if it's set it will use it to compute -the object returned, e.g. the series `df.A` or the method `df.head`, and -display its repr. -""" + +# How Interactive works +# --------------------- + +# `Interactive` is a wrapper around a Python object that lets users create +# interactive pipelines by calling existing APIs on an object with dynamic +# parameters or widgets. + +# An `Interactive` instance watches what operations are applied to the object. + +# To do so, each operation returns a new `Interactive` instance - the creation +# of a new instance being taken care of by the `_clone` method - which allows +# the next operation to be recorded, and so on and so forth. E.g. `dfi.head()` +# first records that the `'head'` attribute is accessed, this is achieved +# by overriding `__getattribute__`. A new interactive object is returned, +# which will then record that it is being called, and that new object will be +# itself called as `Interactive` implements `__call__`. `__call__` returns +# another `Interactive` instance. + +# Note that under the hood even more `Interactive` instances may be created, +# but this is the gist of it. + +# To be able to watch all the potential operations that may be applied to an +# object, `Interactive` implements on top of `__getattribute__` and +# `__call__`: + +# - operators such as `__gt__`, `__add__`, etc. +# - the builtin functions `__abs__` and `__round__` +# - `__getitem__` +# - `__array_ufunc__` + +# The `_depth` attribute starts at 0 and is incremented by 1 everytime +# a new `Interactive` instance is created part of a chain. +# The root instance in an expression has a `_depth` of 0. An expression can +# consist of multiple chains, such as `dfi[dfi.A > 1]`, as the `Interactive` +# instance is referenced twice in the expression. As a consequence `_depth` +# is not the total count of `Interactive` instance creations of a pipeline, +# it is the count of instances created in outer chain. In the example, that +# would be `dfi[]`. `Interactive` instances don't have references about +# the instances that created them or that they create, they just know their +# current location in a chain thanks to `_depth`. However, as some parameters +# need to be passed down the whole pipeline, they do have to propagate. E.g. +# in `dfi.interactive(width=200)`, `width=200` will be propagated as `kwargs`. + +# Recording the operations applied to an object in a pipeline is done +# by gradually building a so-called "dim expression", or "dim transform", +# which is an expression language provided by HoloViews. dim transform +# objects are a way to express transforms on `Dataset`s, a `Dataset` being +# another HoloViews object that is a wrapper around common data structures +# such as Pandas/Dask/... Dataframes/Series, Xarray Dataset/DataArray, etc. +# For instance a Python expression such as `(series + 2).head()` can be +# expressed with a dim transform whose repr will be `(dim('*').pd+2).head(2)`, +# effectively showing that the dim transfom has recorded the different +# operations that are meant to be applied to the data. +# The `_transform` attribute stores the dim transform. + +# The `_obj` attribute holds the original data structure that feeds the +# pipeline. All the `Interactive` instances created while parsing the +# pipeline share the same `_obj` object. And they all wrap it in a `Dataset` +# instance, and all apply the current dim transform they are aware of to +# the original data structure to compute the intermediate state of the data, +# that is stored it in the `_current` attribute. Doing so is particularly +# useful in Notebook sessions, as this allows to inspect the transformed +# object at any point of the pipeline, and as such provide correct +# auto-completion and docstrings. E.g. executing `dfi.A.max?` in a Notebook +# will correctly return the docstring of the Pandas Series `.max()` method, +# as the pipeline evaluates `dfi.A` to hold a current object `_current` that +# is a Pandas Series, and no longer and DataFrame. + +# The `_obj` attribute is implemented as a property which gets/sets the value +# from a list that contains the shared attribute. This is required for the +# "function as input" to be able to update the object from a callback set up +# on the root Interactive instance. + +# The `_method` attribute is a string that temporarily stores the method/attr +# accessed on the object, e.g. `_method` is 'head' in `dfi.head()`, until the +# Interactive instance created in the pipeline is called at which point `_method` +# is reset to None. In cases such as `dfi.head` or `dfi.A`, `_method` is not +# (yet) reset to None. At this stage the Interactive instance returned has +# its `_current` attribute not updated, e.g. `dfi.A._current` is still the +# original dataframe, not the 'A' series. Keeping `_method` is thus useful for +# instance to display `dfi.A`, as the evaluation of the object will check +# whether `_method` is set or not, and if it's set it will use it to compute +# the object returned, e.g. the series `df.A` or the method `df.head`, and +# display its repr. + class Interactive: From 3321dbf40893e38cb78a61cd9480408ea2db4c49 Mon Sep 17 00:00:00 2001 From: maximlt Date: Fri, 18 Nov 2022 11:58:31 +0100 Subject: [PATCH 5/8] don't exclude interactive --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a723a2620..e72db12e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,6 @@ repos: - id: check-builtin-literals - id: check-case-conflict - id: check-docstring-first - exclude: hvplot/interactive.py - id: check-executables-have-shebangs - id: check-toml - id: detect-private-key From ffde8b0f6a13de09050c3d11da9ceeb1e2dfc961 Mon Sep 17 00:00:00 2001 From: maximlt Date: Fri, 18 Nov 2022 11:59:37 +0100 Subject: [PATCH 6/8] remove flake8 from flakes command --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 2ccc714d3..d44a2c1e8 100644 --- a/tox.ini +++ b/tox.ini @@ -8,8 +8,7 @@ envlist = {py36,py37,py38,py39,py310}-{flakes,unit,examples,all}-{default}-{dev, [_flakes] description = Flake check python and notebooks deps = .[tests] -commands = flake8 - pytest --nbsmoke-lint -k ".ipynb" +commands = pytest --nbsmoke-lint -k ".ipynb" [_unit] description = Run unit tests with coverage From 321f1751051878eb20d6bd935b116a97a23c5b0c Mon Sep 17 00:00:00 2001 From: maximlt Date: Fri, 18 Nov 2022 12:33:28 +0100 Subject: [PATCH 7/8] add pre-commit to the tests dependencies --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 0697d03dc..d559422d4 100644 --- a/setup.py +++ b/setup.py @@ -84,6 +84,7 @@ def get_setup_version(reponame): 'pooch', 'scipy', 'ipywidgets', + 'pre-commit', ], 'examples': _examples, 'doc': _examples + [ From 6525173b1334ce2af10ba1780313f249de3e5bcf Mon Sep 17 00:00:00 2001 From: maximlt Date: Fri, 18 Nov 2022 12:33:43 +0100 Subject: [PATCH 8/8] update dev docs --- doc/developer_guide/index.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/developer_guide/index.rst b/doc/developer_guide/index.rst index cc207d77d..9d019ede4 100644 --- a/doc/developer_guide/index.rst +++ b/doc/developer_guide/index.rst @@ -78,7 +78,7 @@ development versions of the other HoloViz packages, such as HoloViews or Panel. conda install mamba -c conda-forge conda create -n hvplot_dev conda activate hvplot_dev - conda config --env --append channels pyviz/label/dev --append channels conda-forge --append channels nodefaults + conda config --env --append channels pyviz/label/dev --append channels conda-forge conda config --env --remove channels defaults Since hvPlot interfaces with a large range of different libraries the @@ -106,6 +106,18 @@ suite and all the examples: Add ``-o doc`` if you want to install the dependencies required to build the website. +Setting up pre-commit +~~~~~~~~~~~~~~~~~~~~~ + +hvPlot uses ``pre-commit`` to automatically apply linting to hvPlot code. +If you intend to contribute to hvPlot we recommend you enable it with: + +.. code-block:: sh + + pre-commit install + +This will ensure that every time you make a commit linting will automatically be applied. + .. _devguide_python_setup: Commands