Repository: scikit-hep/hist
Pull request #475 (2023-02-09 16:37:11)

chore: move to using Ruff
Fixing the CI.



Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7ec121c..08dedbb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,7 +4,7 @@ ci:
 
 repos:
 - repo: https://github.com/psf/black
-  rev: 22.12.0
+  rev: 23.1.0
   hooks:
   - id: black-jupyter
 
@@ -22,17 +22,11 @@ repos:
   - id: requirements-txt-fixer
   - id: trailing-whitespace
 
-- repo: https://github.com/PyCQA/isort
-  rev: 5.11.4
+- repo: https://github.com/charliermarsh/ruff-pre-commit
+  rev: v0.0.244
   hooks:
-  - id: isort
-    args: ["-a", "from __future__ import annotations"]
-
-- repo: https://github.com/asottile/pyupgrade
-  rev: v3.3.1
-  hooks:
-  - id: pyupgrade
-    args: [--py37-plus]
+  - id: ruff
+    args: ["--fix"]
 
 - repo: https://github.com/asottile/setup-cfg-fmt
   rev: v2.2.0
@@ -44,30 +38,16 @@ repos:
   rev: 1.6.1
   hooks:
   - id: nbqa-pyupgrade
-    additional_dependencies: [pyupgrade==2.31.1]
+    additional_dependencies: [pyupgrade==3.3.1]
     args: ["--py37-plus"]
 
-- repo: https://github.com/asottile/yesqa
-  rev: v1.4.0
-  hooks:
-  - id: yesqa
-    additional_dependencies: &flake8-dependencies
-    - flake8-bugbear
-
-- repo: https://github.com/pycqa/flake8
-  rev: 6.0.0
-  hooks:
-  - id: flake8
-    exclude: docs/conf.py
-    additional_dependencies: *flake8-dependencies
-
 - repo: https://github.com/pre-commit/mirrors-mypy
-  rev: v0.991
+  rev: v1.0.0
   hooks:
   - id: mypy
     files: ^src
     args: []
-    additional_dependencies: ["numpy~=1.23.0", "matplotlib>=3.4", "boost-histogram~=1.3.1", "uhi~=0.3.1"]
+    additional_dependencies: ["numpy~=1.24.0", "matplotlib>=3.4", "boost-histogram~=1.3.1", "uhi~=0.3.1"]
 
 - repo: https://github.com/mgedmin/check-manifest
   rev: "0.49"
@@ -83,11 +63,8 @@ repos:
     exclude: ^notebooks/HistLogo.ipynb$
 
 - repo: https://github.com/pre-commit/pygrep-hooks
-  rev: v1.9.0
+  rev: v1.10.0
   hooks:
-  - id: python-check-blanket-noqa
-  - id: python-check-blanket-type-ignore
-  - id: python-no-log-warn
   - id: python-use-type-annotations
   - id: rst-backticks
   - id: rst-directive-colons
diff --git a/docs/conf.py b/docs/conf.py
index b876557..1109703 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -106,7 +106,6 @@ def clean_up(app, exception):
 
 
 def setup(app):
-
     # Copy the file in
     app.connect("builder-inited", prepare)
 
diff --git a/pyproject.toml b/pyproject.toml
index efcb590..03ec224 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -127,9 +127,6 @@ filterwarnings = [
 [tool.nbqa.mutate]
 pyupgrade = 1
 
-[tool.isort]
-profile = "black"
-
 [tool.mypy]
 warn_unused_configs = true
 files = "src"
@@ -182,3 +179,38 @@ messages_control.disable = [
   "disallowed-name",
   "cyclic-import",
 ]
+
+[tool.ruff]
+select = [
+  "E", "F", "W", # flake8
+  "B",  "B904",  # flake8-bugbear
+  "I",           # isort
+  "ARG",         # flake8-unused-arguments
+  "C4",          # flake8-comprehensions
+  "ICN",         # flake8-import-conventions
+  "ISC",         # flake8-implicit-str-concat
+  "PGH",         # pygrep-hooks
+  "PIE",         # flake8-pie
+  "PL",          # pylint
+  "PT",          # flake8-pytest-style
+  "PTH",         # flake8-use-pathlib
+  "RET",         # flake8-return
+  "RUF",         # Ruff-specific
+  "SIM",         # flake8-simplify
+  "T20",         # flake8-print
+  "UP",          # pyupgrade
+  "YTT",         # flake8-2020
+]
+extend-ignore = ["PLR", "E501", "PT011", "PT013", "PT004"]
+target-version = "py37"
+typing-modules = ["hist.typing"]
+src = ["src"]
+unfixable = ["T20", "F841"]
+exclude = []
+isort.required-imports = ["from __future__ import annotations"]
+flake8-unused-arguments.ignore-variadic-names = true
+
+[tool.ruff.per-file-ignores]
+"tests/test_plot.py" = ["B008"]
+"docs/conf.py" = ["ARG001", "PTH"]
+"noxfile.py" = ["T20"]
diff --git a/src/hist/__init__.py b/src/hist/__init__.py
index bbfeb66..409a3e3 100644
--- a/src/hist/__init__.py
+++ b/src/hist/__init__.py
@@ -52,7 +52,6 @@ def __dir__() -> tuple[str, ...]:
 
 
 def __getattr__(name: str) -> ModuleType:
-
     if name == "axes":
         msg = f"Misspelling error, '{name}' should be 'axis'"
         warnings.warn(msg)
diff --git a/src/hist/basehist.py b/src/hist/basehist.py
index 8245c1a..d995a4d 100644
--- a/src/hist/basehist.py
+++ b/src/hist/basehist.py
@@ -141,18 +141,16 @@ class BaseHist(bh.Histogram, metaclass=MetaConstructor, family=hist):
         if self.size == 0:
             return None
 
-        if self.ndim == 1:
-            if len(self.axes[0]) <= 1000:
-                return str(
-                    html_hist(
-                        self,
-                        svg_hist_1d_c if self.axes[0].traits.circular else svg_hist_1d,
-                    )
+        if self.ndim == 1 and len(self.axes[0]) <= 1000:
+            return str(
+                html_hist(
+                    self,
+                    svg_hist_1d_c if self.axes[0].traits.circular else svg_hist_1d,
                 )
+            )
 
-        if self.ndim == 2:
-            if len(self.axes[0]) <= 200 and len(self.axes[1]) <= 200:
-                return str(html_hist(self, svg_hist_2d))
+        if self.ndim == 2 and len(self.axes[0]) <= 200 and len(self.axes[1]) <= 200:
+            return str(html_hist(self, svg_hist_2d))
 
         return None
 
@@ -191,7 +189,7 @@ class BaseHist(bh.Histogram, metaclass=MetaConstructor, family=hist):
                     )
             elif not ax.name or ax.name not in data:
                 raise TypeError("All axes must have names present in the data")
-            else:
+            else:  # noqa: RET506
                 axes_list.append(ax)
 
         weight_arr = data[weight] if weight else None
@@ -514,7 +512,6 @@ class BaseHist(bh.Histogram, metaclass=MetaConstructor, family=hist):
         ax: matplotlib.axes.Axes | None = None,
         **kwargs: Any,
     ) -> Any:
-
         from hist import plot
 
         return plot.plot_pie(self, ax=ax, **kwargs)
diff --git a/src/hist/classichist.py b/src/hist/classichist.py
index f3f773a..5644775 100644
--- a/src/hist/classichist.py
+++ b/src/hist/classichist.py
@@ -3,6 +3,7 @@ from __future__ import annotations
 import argparse
 import shutil
 import sys
+from pathlib import Path
 
 import boost_histogram as bh
 from histoprint import print_hist
@@ -27,11 +28,11 @@ def main() -> None:
     parser.add_argument("-o", "--output-image", type=str, help="save image to file")
     args = parser.parse_args()
 
-    print(
+    print(  # noqa: T201
         "Classic hist interface - please use histoprint instead; this supports multiple file formats and much more!"
     )
 
-    with open(args.input, encoding="utf-8") if args.input else sys.stdin as f:
+    with Path(args.input).open(encoding="utf-8") if args.input else sys.stdin as f:
         values = [float(v) for v in f]
 
     h = bh.numpy.histogram(values, bins=args.buckets, histogram=hist.Hist)
diff --git a/src/hist/intervals.py b/src/hist/intervals.py
index e4ec651..eef0731 100644
--- a/src/hist/intervals.py
+++ b/src/hist/intervals.py
@@ -11,7 +11,7 @@ try:
 except ModuleNotFoundError:
     from sys import stderr
 
-    print(
+    print(  # noqa: T201
         "hist.intervals requires scipy. Please install hist[plot] or manually install scipy.",
         file=stderr,
     )
@@ -74,8 +74,7 @@ def poisson_interval(
             scale * stats.chi2.ppf((1 + coverage) / 2, 2 * (counts + 1)) / 2.0
         )
         interval_max[values == 0.0] = np.nan
-    interval = np.stack((interval_min, interval_max))
-    return interval  # type: ignore[no-any-return]
+    return np.stack((interval_min, interval_max))
 
 
 def clopper_pearson_interval(
diff --git a/src/hist/namedhist.py b/src/hist/namedhist.py
index 9127949..3b25c2f 100644
--- a/src/hist/namedhist.py
+++ b/src/hist/namedhist.py
@@ -65,7 +65,7 @@ class NamedHist(BaseHist, family=hist):
         Get histogram item.
         """
 
-        if isinstance(index, dict) and any(isinstance(k, int) for k in index.keys()):
+        if isinstance(index, dict) and any(isinstance(k, int) for k in index):
             raise TypeError(
                 f"Only access by names are supported for {self.__class__.__name__} in dictionary"
             )
@@ -81,7 +81,7 @@ class NamedHist(BaseHist, family=hist):
         Set histogram item.
         """
 
-        if isinstance(index, dict) and any(isinstance(k, int) for k in index.keys()):
+        if isinstance(index, dict) and any(isinstance(k, int) for k in index):
             raise TypeError(
                 f"Only access by names are supported for {self.__class__.__name__} in dictionary"
             )
diff --git a/src/hist/plot.py b/src/hist/plot.py
index cd3baa2..4e551fb 100644
--- a/src/hist/plot.py
+++ b/src/hist/plot.py
@@ -16,7 +16,7 @@ try:
     from matplotlib import patches, transforms
     from mplhep.plot import Hist1DArtists, Hist2DArtists, hist2dplot, histplot
 except ModuleNotFoundError:
-    print(
+    print(  # noqa: T201
         "Hist requires mplhep to plot, either install hist[plot] or mplhep",
         file=sys.stderr,
     )
@@ -122,7 +122,7 @@ def _expr_to_lambda(expr: str) -> Callable[..., Any]:
     varnames = list(OrderedDict.fromkeys([name for name in varnames if name != "x"]))
     lambdastr = f"lambda x,{','.join(varnames)}: {expr}"
     # pylint: disable-next=eval-used
-    return eval(lambdastr)  # type: ignore[no-any-return]
+    return eval(lambdastr)  # type: ignore[no-any-return]  # noqa: PGH001
 
 
 def _curve_fit_wrapper(
@@ -140,7 +140,7 @@ def _curve_fit_wrapper(
     try:
         from scipy.optimize import curve_fit, minimize
     except ModuleNotFoundError:
-        print(_PLT_MISSING_MSG, file=sys.stderr)
+        print(_PLT_MISSING_MSG, file=sys.stderr)  # noqa: T201
         raise
 
     params = list(inspect.signature(func).parameters.values())
@@ -162,7 +162,7 @@ def _curve_fit_wrapper(
         try:
             from iminuit import Minuit
         except ModuleNotFoundError:
-            print(_PLT_MISSING_MSG, file=sys.stderr)
+            print(_PLT_MISSING_MSG, file=sys.stderr)  # noqa: T201
             raise
         from scipy.special import gammaln
 
@@ -698,7 +698,6 @@ def plot_pie(
     ax: matplotlib.axes.Axes | None = None,
     **kwargs: Any,
 ) -> Any:
-
     if ax is None:
         fig = plt.gcf()
         ax = fig.add_subplot(111)
@@ -717,13 +716,11 @@ def plot_stack(
     legend: bool | None = False,
     **kwargs: Any,
 ) -> Any:
-
     if self[0].ndim != 1:
         raise NotImplementedError("Please project to 1D before calling plot")
 
-    if "label" not in kwargs:
-        if all(h.name is not None for h in self):
-            kwargs["label"] = [h.name for h in self]
+    if "label" not in kwargs and all(h.name is not None for h in self):
+        kwargs["label"] = [h.name for h in self]
 
     ret = histplot(list(self), ax=ax, **kwargs)
     final_ax = ret[0].stairs.axes
diff --git a/src/hist/stack.py b/src/hist/stack.py
index 289254b..d6472ed 100644
--- a/src/hist/stack.py
+++ b/src/hist/stack.py
@@ -11,7 +11,7 @@ from .axestuple import NamedAxesTuple
 from .basehist import BaseHist
 
 if typing.TYPE_CHECKING:
-    import matplotlib
+    import matplotlib as mpl
 
 
 __all__ = ("Stack",)
@@ -124,7 +124,7 @@ class Stack:
     def axes(self) -> NamedAxesTuple:
         return self._stack[0].axes
 
-    def plot(self, *, ax: matplotlib.axes.Axes | None = None, **kwargs: Any) -> Any:
+    def plot(self, *, ax: mpl.axes.Axes | None = None, **kwargs: Any) -> Any:
         """
         Plot method for Stack object.
         """
@@ -137,9 +137,8 @@ class Stack:
         """
         Pretty print the stacked histograms to the console.
         """
-        if "labels" not in kwargs:
-            if all(h.name is not None for h in self):
-                kwargs["labels"] = [h.name for h in self]
+        if "labels" not in kwargs and all(h.name is not None for h in self):
+            kwargs["labels"] = [h.name for h in self]
 
         return histoprint.print_hist(list(self), stack=True, **kwargs)
 


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 37a80fa..6b6474a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -13,12 +13,12 @@ except ImportError:
 
 @pytest.fixture(params=[Hist, BaseHist, NamedHist])
 def named_hist(request):
-    yield request.param
+    return request.param
 
 
 @pytest.fixture(params=[Hist, BaseHist])
 def unnamed_hist(request):
-    yield request.param
+    return request.param
 
 
 dask_params_named = []
@@ -30,9 +30,9 @@ if dah is not None:
 
 @pytest.fixture(params=dask_params_named)
 def named_dask_hist(request):
-    yield request.param
+    return request.param
 
 
 @pytest.fixture(params=dask_params_unnamed)
 def unnamed_dask_hist(request):
-    yield request.param
+    return request.param
diff --git a/tests/test_axis.py b/tests/test_axis.py
index 8c67ee2..a6dfb1c 100644
--- a/tests/test_axis.py
+++ b/tests/test_axis.py
@@ -58,7 +58,6 @@ def test_axis_flow():
 
 
 def test_axis_disallowed_names():
-
     with pytest.warns(UserWarning):
         hist.Hist(axis.Regular(10, 0, 10, name="weight"))
     with pytest.warns(UserWarning):
diff --git a/tests/test_dask.py b/tests/test_dask.py
index 822bc18..ab655bb 100644
--- a/tests/test_dask.py
+++ b/tests/test_dask.py
@@ -59,10 +59,12 @@ def test_unnamed_5D_strcat_intcat_rectangular(unnamed_dask_hist, use_weights):
     if use_weights:
         assert np.allclose(h.variances(), control.variances())
 
-    assert len(h.axes[0]) == 2 and len(control.axes[0]) == 2
+    assert len(h.axes[0]) == 2
+    assert len(control.axes[0]) == 2
     assert all(cx == hx for cx, hx in zip(control.axes[0], h.axes[0]))
 
-    assert len(h.axes[1]) == 2 and len(control.axes[1]) == 2
+    assert len(h.axes[1]) == 2
+    assert len(control.axes[1]) == 2
     assert all(cx == hx for cx, hx in zip(control.axes[1], h.axes[1]))
 
 
@@ -116,8 +118,10 @@ def test_named_5D_strcat_intcat_rectangular(named_dask_hist, use_weights):
     if use_weights:
         assert np.allclose(h.variances(), control.variances())
 
-    assert len(h.axes[0]) == 2 and len(control.axes[0]) == 2
+    assert len(h.axes[0]) == 2
+    assert len(control.axes[0]) == 2
     assert all(cx == hx for cx, hx in zip(control.axes[0], h.axes[0]))
 
-    assert len(h.axes[1]) == 2 and len(control.axes[1]) == 2
+    assert len(h.axes[1]) == 2
+    assert len(control.axes[1]) == 2
     assert all(cx == hx for cx, hx in zip(control.axes[1], h.axes[1]))
diff --git a/tests/test_general.py b/tests/test_general.py
index 86e3794..29ffe77 100644
--- a/tests/test_general.py
+++ b/tests/test_general.py
@@ -664,9 +664,8 @@ def test_general_transform_proxy():
     #     4, -1, 5, forward=ftype(math.log), inverse=ftype(math.log)
     # ).Double()
 
-    with pytest.raises(ValueError):
-        with pytest.warns(RuntimeWarning):
-            Hist.new.Func(4, -1, 5, forward=ftype(np.log), inverse=ftype(np.log))
+    with pytest.raises(ValueError), pytest.warns(RuntimeWarning):
+        Hist.new.Func(4, -1, 5, forward=ftype(np.log), inverse=ftype(np.log))
 
     # lack args
     with pytest.raises(TypeError):
@@ -838,7 +837,7 @@ def test_from_columns(named_hist):
 
 
 def test_from_array(named_hist):
-    h = Hist(
+    h = named_hist(
         axis.Regular(10, 1, 2, name="A"),
         axis.Regular(7, 1, 3, name="B"),
         data=np.ones((10, 7)),
@@ -847,7 +846,7 @@ def test_from_array(named_hist):
     assert h.sum() == approx(70)
     assert h.sum(flow=True) == approx(70)
 
-    h = Hist(
+    h = named_hist(
         axis.Regular(10, 1, 2, name="A"),
         axis.Regular(7, 1, 3, name="B"),
         data=np.ones((12, 9)),
@@ -859,7 +858,7 @@ def test_from_array(named_hist):
     assert h.sum(flow=True) == approx(12 * 9)
 
     with pytest.raises(ValueError):
-        h = Hist(
+        h = named_hist(
             axis.Regular(10, 1, 2, name="A"),
             axis.Regular(7, 1, 3, name="B"),
             data=np.ones((11, 9)),
diff --git a/tests/test_named.py b/tests/test_named.py
index 026a59a..911bd57 100644
--- a/tests/test_named.py
+++ b/tests/test_named.py
@@ -705,11 +705,10 @@ def test_named_transform_proxy():
     #     4, -1, 5, name="x", forward=ftype(math.log), inverse=ftype(math.log)
     # )
 
-    with pytest.raises(ValueError):
-        with pytest.warns(RuntimeWarning):
-            NamedHist.new.Func(
-                4, -1, 5, name="x", forward=ftype(np.log), inverse=ftype(np.log)
-            )
+    with pytest.raises(ValueError), pytest.warns(RuntimeWarning):
+        NamedHist.new.Func(
+            4, -1, 5, name="x", forward=ftype(np.log), inverse=ftype(np.log)
+        )
 
     # lack args
     with pytest.raises(TypeError):
diff --git a/tests/test_reprs.py b/tests/test_reprs.py
index ad4c70a..37f84d5 100644
--- a/tests/test_reprs.py
+++ b/tests/test_reprs.py
@@ -1,10 +1,9 @@
 from __future__ import annotations
 
-from hist import Hist, Stack, axis
+from hist import Stack, axis
 
 
 def test_1D_empty_repr(named_hist):
-
     h = named_hist.new.Reg(10, -1, 1, name="x", label="y").Double()
     html = h._repr_html_()
     assert html
@@ -13,7 +12,6 @@ def test_1D_empty_repr(named_hist):
 
 
 def test_1D_var_empty_repr(named_hist):
-
     h = named_hist.new.Var(range(10), name="x", label="y").Double()
     html = h._repr_html_()
     assert html
@@ -22,7 +20,6 @@ def test_1D_var_empty_repr(named_hist):
 
 
 def test_1D_int_empty_repr(named_hist):
-
     h = named_hist.new.Int(-9, 9, name="x", label="y").Double()
     html = h._repr_html_()
     assert html
@@ -31,7 +28,6 @@ def test_1D_int_empty_repr(named_hist):
 
 
 def test_1D_intcat_empty_repr(named_hist):
-
     h = named_hist.new.IntCat([1, 3, 5], name="x", label="y").Double()
     html = h._repr_html_()
     assert html
@@ -40,7 +36,6 @@ def test_1D_intcat_empty_repr(named_hist):
 
 
 def test_1D_strcat_empty_repr(named_hist):
-
     h = named_hist.new.StrCat(["1", "3", "5"], name="x", label="y").Double()
     html = h._repr_html_()
     assert html
@@ -49,7 +44,6 @@ def test_1D_strcat_empty_repr(named_hist):
 
 
 def test_2D_empty_repr(named_hist):
-
     h = (
         named_hist.new.Reg(10, -1, 1, name="x", label="y")
         .Int(0, 15, name="p", label="q")
@@ -64,7 +58,6 @@ def test_2D_empty_repr(named_hist):
 
 
 def test_1D_circ_empty_repr(named_hist):
-
     h = named_hist.new.Reg(10, -1, 1, circular=True, name="R", label="r").Double()
     html = h._repr_html_()
     assert html
@@ -73,7 +66,6 @@ def test_1D_circ_empty_repr(named_hist):
 
 
 def test_ND_empty_repr(named_hist):
-
     h = (
         named_hist.new.Reg(10, -1, 1, name="x", label="y")
         .Reg(12, -3, 3, name="p", label="q")
@@ -85,7 +77,6 @@ def test_ND_empty_repr(named_hist):
 
 
 def test_empty_mega_repr(named_hist):
-
     h = named_hist.new.Reg(1001, -1, 1, name="x").Double()
     html = h._repr_html_()
     assert html is None
@@ -96,12 +87,11 @@ def test_empty_mega_repr(named_hist):
 
 
 def test_stack_repr(named_hist):
-
     a1 = axis.Regular(
         50, -5, 5, name="A", label="a [unit]", underflow=False, overflow=False
     )
     a2 = axis.Regular(
         50, -5, 5, name="A", label="a [unit]", underflow=False, overflow=False
     )
-    assert "name='A'" in repr(Stack(Hist(a1), Hist(a2)))
-    assert "label='a [unit]'" in repr(Stack(Hist(a1), Hist(a2)))
+    assert "name='A'" in repr(Stack(named_hist(a1), named_hist(a2)))
+    assert "label='a [unit]'" in repr(Stack(named_hist(a1), named_hist(a2)))