diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 2fa146c..c05235a 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -17,9 +17,6 @@ jobs: runs-on: ubuntu-latest permissions: contents: write - defaults: - run: - shell: bash -l {0} steps: - name: checkout @@ -33,15 +30,13 @@ jobs: with: python-version: "3.x" - - - name: Install pandoc for notebook conversion - run: > - sudo apt-get update - && sudo apt-get install pandoc - - name: Install windrose run: | - python -m pip install -e .[extras,tests,docs] + python -m pip install --group dev + python -m pip install -e .[extras] + + - name: Install pandoc + uses: pandoc/actions/setup@86321b6dd4675f5014c611e05088e10d4939e09e # v1.1.1 - name: Build documentation run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4775b81..cb9d275 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,17 +13,14 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [ "3.11", "3.12", "3.13", "3.14" ] + python-version: [ "3.11", "3.15" ] os: [ ubuntu-latest ] include: - os: windows-latest - python-version: "3.14" + python-version: "3.15" - os: macos-latest - python-version: "3.14" + python-version: "3.15" fail-fast: false - defaults: - run: - shell: bash -l {0} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -38,15 +35,22 @@ jobs: - name: Install windrose run: | - python -m pip install -e .[extras,tests] + python -m pip install --group dev + python -m pip install -e .[extras] - - name: Tests + - name: Code Tests + run: | + pytest -s -rxs -vv -Werror tests/ + + - name: MPL Tests + if: ${{ matrix.os == 'ubuntu' }} run: | pytest -s -rxs -vv -Werror tests/ --mpl --mpl-generate-summary=html \ --mpl-results-path="windrose_test_output-${{ matrix.os }}-${{ matrix.python-version }}" + - name: Store mpl-results + if: ${{ matrix.os == 'ubuntu' && failure() }} uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - if: failure() with: name: "windrose_test_output-${{ matrix.os }}-${{ matrix.python-version }}" path: "windrose_test_output-${{ matrix.os }}-${{ matrix.python-version }}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b342ae6..f83563a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,14 +12,6 @@ repos: - id: check-added-large-files exclude_types: [yaml] - id: requirements-txt-fixer - - id: file-contents-sorter - files: requirements-dev.txt - -- repo: https://github.com/psf/black-pre-commit-mirror - rev: 26.10.1 - hooks: - - id: black - language_version: python3 - repo: https://github.com/keewis/blackdoc rev: v0.4.6 @@ -51,9 +43,11 @@ repos: - id: add-trailing-comma - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.17.2 + rev: v0.17.3 hooks: - id: ruff + args: ["--fix", "--show-fixes"] + - id: ruff-format - repo: https://github.com/tox-dev/pyproject-fmt rev: v2.30.3 @@ -67,7 +61,6 @@ repos: # Use `nbqa mdformat jupyterbook --nbqa-md` to run it locally. # - id: mdformat - id: nbqa-check-ast - - id: nbqa-black - id: nbqa-ruff # We are skipping: # E402 -> Module level import not at top of file diff --git a/notebooks/usage.ipynb b/notebooks/usage.ipynb index 7464737..9f5149e 100644 --- a/notebooks/usage.ipynb +++ b/notebooks/usage.ipynb @@ -399,7 +399,9 @@ " \"\"\"wrapper function to create subplots per axis\"\"\"\n", " ax = plt.gca()\n", " ax = WindroseAxes.from_ax(ax=ax)\n", - " plot_windrose(direction_or_df=data[direction], var=data[var], ax=ax, **kwargs)\n", + " plot_windrose(\n", + " direction_or_df=data[direction], var=data[var], ax=ax, **kwargs\n", + " )\n", "\n", "\n", "# this creates the raw subplot structure with a subplot per value in month.\n", @@ -431,7 +433,9 @@ "y_ticks = range(0, 17, 4)\n", "for ax in g.axes:\n", " ax.set_legend(\n", - " title=r\"$m \\cdot s^{-1}$\", bbox_to_anchor=(1.15, -0.1), loc=\"lower right\"\n", + " title=r\"$m \\cdot s^{-1}$\",\n", + " bbox_to_anchor=(1.15, -0.1),\n", + " loc=\"lower right\",\n", " )\n", " ax.set_rgrids(y_ticks, y_ticks)\n", "\n", diff --git a/notebooks/windrose_sample_poitiers_csv.ipynb b/notebooks/windrose_sample_poitiers_csv.ipynb index 2b8df8d..90de761 100644 --- a/notebooks/windrose_sample_poitiers_csv.ipynb +++ b/notebooks/windrose_sample_poitiers_csv.ipynb @@ -56,7 +56,11 @@ "\n", "ax = WindroseAxes.from_ax()\n", "ax.bar(\n", - " df.direction.values, df.speed.values, bins=np.arange(0.01, 8, 1), cmap=cm.hot, lw=3\n", + " df.direction.values,\n", + " df.speed.values,\n", + " bins=np.arange(0.01, 8, 1),\n", + " cmap=cm.hot,\n", + " lw=3,\n", ")\n", "ax.set_legend();" ] @@ -69,7 +73,9 @@ "source": [ "from windrose import plot_windrose\n", "\n", - "plot_windrose(df, kind=\"contour\", bins=np.arange(0.01, 8, 1), cmap=cm.hot, lw=3);" + "plot_windrose(\n", + " df, kind=\"contour\", bins=np.arange(0.01, 8, 1), cmap=cm.hot, lw=3\n", + ");" ] }, { @@ -123,7 +129,9 @@ " return ax\n", "\n", "\n", - "plot_month(df, (2014, 7), kind=\"contour\", bins=np.arange(0, 10, 1), cmap=cm.hot);" + "plot_month(\n", + " df, (2014, 7), kind=\"contour\", bins=np.arange(0, 10, 1), cmap=cm.hot\n", + ");" ] }, { @@ -132,7 +140,9 @@ "metadata": {}, "outputs": [], "source": [ - "plot_month(df, (2014, 8), kind=\"contour\", bins=np.arange(0, 10, 1), cmap=cm.hot);" + "plot_month(\n", + " df, (2014, 8), kind=\"contour\", bins=np.arange(0, 10, 1), cmap=cm.hot\n", + ");" ] }, { @@ -141,7 +151,9 @@ "metadata": {}, "outputs": [], "source": [ - "plot_month(df, (2014, 9), kind=\"contour\", bins=np.arange(0, 10, 1), cmap=cm.hot);" + "plot_month(\n", + " df, (2014, 9), kind=\"contour\", bins=np.arange(0, 10, 1), cmap=cm.hot\n", + ");" ] } ], diff --git a/pyproject.toml b/pyproject.toml index 98681d7..b2bd2a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,10 +14,9 @@ maintainers = [ authors = [ { name = "Lionel Roubeyrie", email = "s.celles@gmail.co" }, ] -requires-python = ">=3.10" +requires-python = ">=3.11" classifiers = [ "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", @@ -31,20 +30,23 @@ dependencies = [ "matplotlib>=3", "numpy>=1.21", ] -optional-dependencies.docs = [ "jupyter", "nbsphinx", "seaborn", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme" ] optional-dependencies.extras = [ "pandas", "scipy", ] -optional-dependencies.tests = [ + +[dependency-groups] +dev = [ "cartopy", "check-manifest", "coverage", "pytest", "pytest-cov", "pytest-mpl", - "pytest-sugar" + "pytest-sugar", + { include-group = "docs" }, ] +docs = [ "jupyter", "nbsphinx", "seaborn", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme" ] urls.documentation = "https://python-windrose.github.io/windrose" urls.homepage = "https://github.com/python-windrose/windrose" urls.repository = "https://github.com/python-windrose/windrose" @@ -65,7 +67,7 @@ write_to = "windrose/_version.py" write_to_template = "__version__ = '{version}'" [tool.ruff] -target-version = "py38" +target-version = "py311" line-length = 79 lint.select = [ "A", # flake8-builtins diff --git a/tests/test_windrose.py b/tests/test_windrose.py index 9458528..ec70e82 100644 --- a/tests/test_windrose.py +++ b/tests/test_windrose.py @@ -25,7 +25,9 @@ def test_windrose_pd_not_default_names(): kind = "scatter" - df_not_default_names = pd.DataFrame({"wind_speed": ws, "wind_direction": wd}) + df_not_default_names = pd.DataFrame( + {"wind_speed": ws, "wind_direction": wd}, + ) plot_windrose( df_not_default_names, kind=kind, diff --git a/tests/test_windrose_np_mpl_oo.py b/tests/test_windrose_np_mpl_oo.py index 565e77b..fb56733 100644 --- a/tests/test_windrose_np_mpl_oo.py +++ b/tests/test_windrose_np_mpl_oo.py @@ -78,7 +78,13 @@ def test_filled_with_colormap_calm_limit(): # A windrose in filled representation, with a controlled colormap and a calm limit ax = WindroseAxes.from_ax() # the bins most not be below the calm_limit - ax.contourf(wd, ws, bins=[0.2, 1, 2, 3, 4, 5, 6, 7], cmap=cm.hot, calm_limit=0.2) + ax.contourf( + wd, + ws, + bins=[0.2, 1, 2, 3, 4, 5, 6, 7], + cmap=cm.hot, + calm_limit=0.2, + ) ax.set_legend() return ax.figure diff --git a/tests/test_windrose_pandas.py b/tests/test_windrose_pandas.py index 119bac6..31317e8 100644 --- a/tests/test_windrose_pandas.py +++ b/tests/test_windrose_pandas.py @@ -31,7 +31,13 @@ def test_scatter(): @pytest.mark.mpl_image_compare(baseline_dir="output/df", tolerance=20) def test_bar(): kind = "bar" - ax = plot_windrose(df, kind=kind, normed=True, opening=0.8, edgecolor="white") + ax = plot_windrose( + df, + kind=kind, + normed=True, + opening=0.8, + edgecolor="white", + ) return ax.figure @@ -52,7 +58,13 @@ def test_contourf(): @pytest.mark.mpl_image_compare(baseline_dir="output/df", tolerance=20) def test_contour(): kind = "contour" - ax = plot_windrose(df, kind=kind, bins=np.arange(0.01, 8, 1), cmap=cm.hot, lw=3) + ax = plot_windrose( + df, + kind=kind, + bins=np.arange(0.01, 8, 1), + cmap=cm.hot, + lw=3, + ) return ax.figure @@ -73,7 +85,10 @@ def test_windrose_np_plot_and_pd_plot(): @pytest.mark.mpl_image_compare(baseline_dir="output/df") def test_subplots(): - fig, (ax1, ax2) = plt.subplots(ncols=2, subplot_kw={"projection": "windrose"}) + fig, (ax1, ax2) = plt.subplots( + ncols=2, + subplot_kw={"projection": "windrose"}, + ) plot_windrose(df, ax=ax1) plot_windrose(df, ax=ax2) return fig diff --git a/windrose/windrose.py b/windrose/windrose.py index f3634be..b90861e 100644 --- a/windrose/windrose.py +++ b/windrose/windrose.py @@ -9,7 +9,9 @@ from matplotlib.projections.polar import PolarAxes from numpy import histogram2d -ZBASE = -1000 # The starting zorder for all drawing, negative to have the grid on +ZBASE = ( + -1000 +) # The starting zorder for all drawing, negative to have the grid on VAR_DEFAULT = "speed" DIR_DEFAULT = "direction" FIGSIZE_DEFAULT = (8, 8) @@ -69,7 +71,9 @@ def create(typ, ax=None, *args, **kwargs): ax = cls.from_ax(ax, *args, **kwargs) return ax else: - raise NotImplementedError(f"typ={typ!r} but it might be in {d.keys()}") + raise NotImplementedError( + f"typ={typ!r} but it might be in {d.keys()}", + ) class WindroseAxes(PolarAxes): @@ -262,7 +266,8 @@ def get_labels(decimal_places=1, units=None): fmt += " " + units labels = [ - fmt.format(digits[k], digits[k + 1]) for k in range(len(digits) - 1) + fmt.format(digits[k], digits[k + 1]) + for k in range(len(digits) - 1) ] if units: labels[-1] = f">{digits[-1]} " + units @@ -275,7 +280,13 @@ def get_labels(decimal_places=1, units=None): handles = get_handles() labels = get_labels(decimal_places, units) - self.legend_ = mpl.legend.Legend(self, handles, labels, loc=loc, **kwargs) + self.legend_ = mpl.legend.Legend( + self, + handles, + labels, + loc=loc, + **kwargs, + ) return self.legend_ def set_legend(self, **pyplot_arguments): @@ -330,7 +341,9 @@ def _init_plot(self, direction, var, **kwargs): # if weibull factors are entered overwrite direction and var if "weibull_factors" in kwargs or "mean_values" in kwargs: if "weibull_factors" in kwargs and "mean_values" in kwargs: - raise TypeError("cannot specify both weibull_factors and mean_values") + raise TypeError( + "cannot specify both weibull_factors and mean_values", + ) statistic_type = "unset" if "weibull_factors" in kwargs: statistic_type = "weibull" @@ -344,7 +357,9 @@ def _init_plot(self, direction, var, **kwargs): "specify 'frequency' argument for statistical input", ) windFrequencies = kwargs.pop("frequency") - if len(windFrequencies) != len(direction) or len(direction) != len(var): + if len(windFrequencies) != len(direction) or len( + direction, + ) != len(var): if len(windFrequencies) != len(direction): raise TypeError("len(frequency) != len(direction)") elif len(direction) != len(var): @@ -355,7 +370,10 @@ def _init_plot(self, direction, var, **kwargs): for _ in range(int(windFrequencies[dbin] * 10000)): if statistic_type == "weibull": windSpeeds.append( - random.weibullvariate(var[dbin][0], var[dbin][1]), + random.weibullvariate( + var[dbin][0], + var[dbin][1], + ), ) elif statistic_type == "mean": windSpeeds.append( @@ -383,7 +401,11 @@ def _init_plot(self, direction, var, **kwargs): f"If you want to exclude values below a certain threshold, " f"try setting calm_limit={min(bins)}.", ) - elif len(bins) > 0 and calm_limit is not None and min(bins) < calm_limit: + elif ( + len(bins) > 0 + and calm_limit is not None + and min(bins) < calm_limit + ): raise ValueError( f"the lowest value in bins must be >= {calm_limit} (=calm_limits)", ) @@ -507,7 +529,13 @@ def contour(self, direction, var, **kwargs): val = vals[i, :] + origin origin += vals[i, :] zorder = ZBASE + nbins - i - patch = self.plot(angles, val, color=colors[i], zorder=zorder, **kwargs) + patch = self.plot( + angles, + val, + color=colors[i], + zorder=zorder, + **kwargs, + ) self.patches_list.extend(patch) self._update() @@ -874,7 +902,12 @@ def histogram( direction = direction + 180.0 direction[direction >= 360.0] = direction[direction >= 360.0] - 360 - table = histogram2d(x=var, y=direction, bins=[var_bins, dir_bins], density=False)[0] + table = histogram2d( + x=var, + y=direction, + bins=[var_bins, dir_bins], + density=False, + )[0] # add the last value to the first to have the table of North winds table[:, 0] = table[:, 0] + table[:, -1] # and remove the last col @@ -886,7 +919,14 @@ def histogram( @_copy_docstring(WindroseAxes.contour) -def wrcontour(direction, var, ax=None, rmax=None, figsize=FIGSIZE_DEFAULT, **kwargs): +def wrcontour( + direction, + var, + ax=None, + rmax=None, + figsize=FIGSIZE_DEFAULT, + **kwargs, +): """ Draw contour probability density function and return Weibull distribution parameters. @@ -898,7 +938,14 @@ def wrcontour(direction, var, ax=None, rmax=None, figsize=FIGSIZE_DEFAULT, **kwa @_copy_docstring(WindroseAxes.contourf) -def wrcontourf(direction, var, ax=None, rmax=None, figsize=FIGSIZE_DEFAULT, **kwargs): +def wrcontourf( + direction, + var, + ax=None, + rmax=None, + figsize=FIGSIZE_DEFAULT, + **kwargs, +): ax = WindroseAxes.from_ax(ax, rmax=rmax, figsize=figsize) ax.contourf(direction, var, **kwargs) ax.set_legend() @@ -906,7 +953,14 @@ def wrcontourf(direction, var, ax=None, rmax=None, figsize=FIGSIZE_DEFAULT, **kw @_copy_docstring(WindroseAxes.box) -def wrbox(direction, var, ax=None, rmax=None, figsize=FIGSIZE_DEFAULT, **kwargs): +def wrbox( + direction, + var, + ax=None, + rmax=None, + figsize=FIGSIZE_DEFAULT, + **kwargs, +): ax = WindroseAxes.from_ax(ax, rmax=rmax, figsize=figsize) ax.box(direction, var, **kwargs) ax.set_legend() @@ -914,7 +968,14 @@ def wrbox(direction, var, ax=None, rmax=None, figsize=FIGSIZE_DEFAULT, **kwargs) @_copy_docstring(WindroseAxes.bar) -def wrbar(direction, var, ax=None, rmax=None, figsize=FIGSIZE_DEFAULT, **kwargs): +def wrbar( + direction, + var, + ax=None, + rmax=None, + figsize=FIGSIZE_DEFAULT, + **kwargs, +): ax = WindroseAxes.from_ax(ax, rmax=rmax, figsize=figsize) ax.bar(direction, var, **kwargs) ax.set_legend() @@ -940,7 +1001,16 @@ def wrpdf( parameters """ ax = WindAxes.from_ax(ax, figsize=figsize) - ax, params = ax.pdf(var, bins, Nx, bar_color, plot_color, Nbins, *args, **kwargs) + ax, params = ax.pdf( + var, + bins, + Nx, + bar_color, + plot_color, + Nbins, + *args, + **kwargs, + ) return (ax, params) @@ -1081,7 +1151,9 @@ def plot_windrose_np( if kind in D_KIND_PLOT.keys(): f_plot = D_KIND_PLOT[kind] else: - raise Exception(f"kind={kind!r} but it must be in {D_KIND_PLOT.keys()!r}") + raise Exception( + f"kind={kind!r} but it must be in {D_KIND_PLOT.keys()!r}", + ) # if f_clean is not None: # df = f_clean(df) # var = df[var_name].values