Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ repos:
types_or: [yaml, markdown, html, css, scss, javascript, json]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.9.9"
rev: "v0.11.4"
hooks:
- id: ruff
args: ["--fix", "--show-fixes"]
Expand Down Expand Up @@ -65,7 +65,7 @@ repos:
exclude: .pre-commit-config.yaml

- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v19.1.7
rev: v20.1.0
hooks:
- id: clang-format
types_or: [c++]
Expand All @@ -78,12 +78,12 @@ repos:
- id: rst-inline-touching-normal

- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.31.2
rev: 0.32.1
hooks:
- id: check-readthedocs
- id: check-github-workflows

- repo: https://github.com/henryiii/validate-pyproject-schema-store
rev: 2025.02.24
rev: 2025.04.07
hooks:
- id: validate-pyproject
9 changes: 7 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ extend-select = [
"PT", # flake8-pytest-style
"PTH", # flake8-use-pathlib
"PYI", # flake8-pyi
"TID251", # flake8-tidy-imports.banned-api
"RET", # flake8-return
"RUF", # Ruff-specific
"SIM", # flake8-simplify
Expand All @@ -247,10 +248,13 @@ ignore = [
"PT011", # pytest.raises too broad
"PYI034", # We are returning Self, just generic
]
typing-modules = ["boost_histogram.typing"]
typing-modules = ["boost_histogram._compat.typing"]
isort.required-imports = ["from __future__ import annotations"]
exclude = ["extern/*"]

[tool.ruff.lint.flake8-tidy-imports.banned-api]
"typing.Self".msg = "Use boost_histogram._compat.typing.Self instead."
"typing_extensions.Self".msg = "Use boost_histogram._compat.typing.Self instead."

[tool.ruff.lint.mccabe]
max-complexity = 13
Expand All @@ -261,7 +265,8 @@ max-complexity = 13
"scripts/*" = ["T20"]
"tests/*" = ["T20"]
"notebooks/*" = ["T20"]
"*.pyi" = ["F401"]
"*.pyi" = ["F401", "TID251"]
"docs/conf.py" = ["ARG001"]
"src/boost_histogram/test.py" = ["PT"]
"**.ipynb" = ["I002", "E731"]
"src/boost_histogram/_compat/**.py" = ["TID251"]
Empty file.
17 changes: 17 additions & 0 deletions src/boost_histogram/_compat/typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from __future__ import annotations

import sys
import typing

if sys.version_info >= (3, 11):
from typing import Self
elif typing.TYPE_CHECKING:
from typing_extensions import Self
else:
Self = object

__all__ = ["Self"]


def __dir__() -> list[str]:
return __all__
11 changes: 6 additions & 5 deletions src/boost_histogram/axis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import boost_histogram

from .._compat.typing import Self
from .._core import axis as ca
from .._utils import cast, register, zip_strict
from . import transform
Expand Down Expand Up @@ -132,8 +133,8 @@ def __setstate__(self, state: dict[str, Any]) -> None:
def __getstate__(self) -> dict[str, Any]:
return {"_ax": self._ax}

def __copy__(self: T) -> T:
other: T = self.__class__.__new__(self.__class__)
def __copy__(self) -> Self:
other: Self = self.__class__.__new__(self.__class__)
other._ax = copy.copy(self._ax)
other.__dict__ = other._ax.raw_metadata
return other
Expand Down Expand Up @@ -172,8 +173,8 @@ def __ne__(self, other: object) -> bool:
return (not hasattr(other, "_ax")) or self._ax != other._ax

@classmethod
def _convert_cpp(cls: type[T], cpp_object: Any) -> T:
nice_ax: T = cls.__new__(cls)
def _convert_cpp(cls, cpp_object: Any) -> Self:
nice_ax: Self = cls.__new__(cls)
nice_ax._ax = cpp_object
nice_ax.__dict__ = cpp_object.raw_metadata
return nice_ax
Expand Down Expand Up @@ -821,7 +822,7 @@ def __dir__(self) -> list[str]:
def __call__(self, *args: Any, **kwargs: Any) -> Any:
return self.__class__(a(*args, **kwargs) for a in self)

def broadcast(self: A) -> A:
def broadcast(self) -> Self:
"""
The arrays in this tuple will be compressed if possible to save memory.
Use this method to broadcast them out into their full memory
Expand Down
9 changes: 5 additions & 4 deletions src/boost_histogram/axis/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import boost_histogram

from .._compat.typing import Self
from .._core import axis as ca
from .._utils import register

Expand All @@ -26,14 +27,14 @@ def __init_subclass__(cls, *, family: object) -> None:
super().__init_subclass__()
cls._family = family

def __copy__(self: T) -> T:
other: T = self.__class__.__new__(self.__class__)
def __copy__(self) -> Self:
other: Self = self.__class__.__new__(self.__class__)
other._this = copy.copy(self._this)
return other

@classmethod
def _convert_cpp(cls: type[T], this: Any) -> T:
self: T = cls.__new__(cls)
def _convert_cpp(cls, this: Any) -> Self:
self: Self = cls.__new__(cls)
self._this = this
return self

Expand Down
54 changes: 28 additions & 26 deletions src/boost_histogram/histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import boost_histogram
from boost_histogram import _core

from ._compat.typing import Self
from ._utils import cast, register
from .axis import AxesTuple, Axis, Variable
from .storage import Double, Storage
Expand All @@ -35,6 +36,7 @@
if TYPE_CHECKING:
from builtins import ellipsis


try:
from . import _core
except ImportError as err:
Expand Down Expand Up @@ -316,12 +318,12 @@ def __init__(

@classmethod
def _clone(
cls: type[H],
cls,
_hist: Histogram | CppHistogram,
*,
other: Histogram | None = None,
memo: Any = NOTHING,
) -> H:
) -> Self:
"""
Clone a histogram (possibly of a different base). Does not trigger __init__.
This will copy data from `other=` if non-None, otherwise metadata gets copied from the input.
Expand Down Expand Up @@ -353,7 +355,7 @@ def _clone(
ax.__dict__ = copy.deepcopy(ax._ax.raw_metadata, memo)
return self

def _new_hist(self: H, _hist: CppHistogram, memo: Any = NOTHING) -> H:
def _new_hist(self, _hist: CppHistogram, memo: Any = NOTHING) -> Self:
"""
Return a new histogram given a new _hist, copying current metadata.
"""
Expand Down Expand Up @@ -440,11 +442,11 @@ def __eq__(self, other: object) -> bool:
def __ne__(self, other: object) -> bool:
return (not hasattr(other, "_hist")) or self._hist != other._hist

def __add__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
def __add__(self, other: Histogram | np.typing.NDArray[Any] | float) -> Self:
result = self.copy(deep=False)
return result.__iadd__(other)

def __iadd__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
def __iadd__(self, other: Histogram | np.typing.NDArray[Any] | float) -> Self:
if isinstance(other, (int, float)) and other == 0:
return self
self._compute_inplace_op("__iadd__", other)
Expand All @@ -454,14 +456,14 @@ def __iadd__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:

return self

def __radd__(self: H, other: np.typing.NDArray[Any] | float) -> H:
def __radd__(self, other: np.typing.NDArray[Any] | float) -> Self:
return self + other

def __sub__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
def __sub__(self, other: Histogram | np.typing.NDArray[Any] | float) -> Self:
result = self.copy(deep=False)
return result.__isub__(other)

def __isub__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
def __isub__(self, other: Histogram | np.typing.NDArray[Any] | float) -> Self:
if isinstance(other, (int, float)) and other == 0:
return self
self._compute_inplace_op("__isub__", other)
Expand All @@ -471,33 +473,33 @@ def __isub__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
return self

# If these fail, the underlying object throws the correct error
def __mul__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
def __mul__(self, other: Histogram | np.typing.NDArray[Any] | float) -> Self:
result = self.copy(deep=False)
return result._compute_inplace_op("__imul__", other)

def __rmul__(self: H, other: np.typing.NDArray[Any] | float) -> H:
def __rmul__(self, other: np.typing.NDArray[Any] | float) -> Self:
return self * other

def __truediv__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
def __truediv__(self, other: Histogram | np.typing.NDArray[Any] | float) -> Self:
result = self.copy(deep=False)
return result._compute_inplace_op("__itruediv__", other)

def __div__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
def __div__(self, other: Histogram | np.typing.NDArray[Any] | float) -> Self:
result = self.copy(deep=False)
return result._compute_inplace_op("__idiv__", other)

def __idiv__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
def __idiv__(self, other: Histogram | np.typing.NDArray[Any] | float) -> Self:
return self._compute_inplace_op("__idiv__", other)

def __itruediv__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
def __itruediv__(self, other: Histogram | np.typing.NDArray[Any] | float) -> Self:
return self._compute_inplace_op("__itruediv__", other)

def __imul__(self: H, other: Histogram | np.typing.NDArray[Any] | float) -> H:
def __imul__(self, other: Histogram | np.typing.NDArray[Any] | float) -> Self:
return self._compute_inplace_op("__imul__", other)

def _compute_inplace_op(
self: H, name: str, other: Histogram | np.typing.NDArray[Any] | float
) -> H:
self, name: str, other: Histogram | np.typing.NDArray[Any] | float
) -> Self:
# Also takes CppHistogram, but that confuses mypy because it's hard to pick out
if isinstance(other, Histogram):
getattr(self._hist, name)(other._hist)
Expand Down Expand Up @@ -528,12 +530,12 @@ def _compute_inplace_op(

# TODO: Marked as too complex by flake8. Should be factored out a bit.
def fill(
self: H,
self,
*args: ArrayLike | str,
weight: ArrayLike | None = None,
sample: ArrayLike | None = None,
threads: int | None = None,
) -> H:
) -> Self:
"""
Insert data into the histogram.

Expand Down Expand Up @@ -671,13 +673,13 @@ def _storage_type(self) -> type[Storage]:
)
return cast(self, self._hist._storage_type, Storage) # type: ignore[return-value]

def _reduce(self: H, *args: Any) -> H:
def _reduce(self, *args: Any) -> Self:
return self._new_hist(self._hist.reduce(*args))

def __copy__(self: H) -> H:
def __copy__(self) -> Self:
return self._new_hist(copy.copy(self._hist))

def __deepcopy__(self: H, memo: Any) -> H:
def __deepcopy__(self, memo: Any) -> Self:
return self._new_hist(copy.deepcopy(self._hist), memo=memo)

def __getstate__(self) -> tuple[int, dict[str, Any]]:
Expand Down Expand Up @@ -837,7 +839,7 @@ def to_numpy(

return (hist, edges) if dd else (hist, *edges)

def copy(self: H, *, deep: bool = True) -> H:
def copy(self, *, deep: bool = True) -> Self:
"""
Make a copy of the histogram. Defaults to making a
deep copy (axis metadata copied); use deep=False
Expand All @@ -846,7 +848,7 @@ def copy(self: H, *, deep: bool = True) -> H:

return copy.deepcopy(self) if deep else copy.copy(self)

def reset(self: H) -> H:
def reset(self) -> Self:
"""
Clear the bin counters.
"""
Expand Down Expand Up @@ -881,7 +883,7 @@ def shape(self) -> tuple[int, ...]:
return self.axes.size

# TODO: Marked as too complex by flake8. Should be factored out a bit.
def __getitem__(self: H, index: IndexingExpr) -> H | float | Accumulator:
def __getitem__(self, index: IndexingExpr) -> Self | float | Accumulator:
indexes = self._compute_commonindex(index)

# If this is (now) all integers, return the bin contents
Expand Down Expand Up @@ -1187,7 +1189,7 @@ def __setitem__(self, index: IndexingExpr, value: ArrayLike | Accumulator) -> No

view[tuple(indexes)] = value # type: ignore[arg-type]

def project(self: H, *args: int) -> H | float | Accumulator:
def project(self, *args: int) -> Self | float | Accumulator:
"""
Project to a single axis or several axes on a multidimensional histogram.
Provided a list of axis numbers, this will produce the histogram over
Expand Down
5 changes: 3 additions & 2 deletions src/boost_histogram/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
if TYPE_CHECKING:
from uhi.typing.plottable import PlottableAxis

from ._compat.typing import Self
from .typing import AxisLike

__all__ = ("Locator", "Slicer", "at", "loc", "overflow", "rebin", "sum", "underflow")
Expand Down Expand Up @@ -45,12 +46,12 @@ def __init__(self, offset: int = 0) -> None:

self.offset = offset

def __add__(self: T, offset: int) -> T:
def __add__(self, offset: int) -> Self:
other = copy.copy(self)
other.offset += offset
return other

def __sub__(self: T, offset: int) -> T:
def __sub__(self, offset: int) -> Self:
other = copy.copy(self)
other.offset -= offset
return other
Expand Down
2 changes: 1 addition & 1 deletion tests/pickles/make_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def make_pickle(
Make a pickle file with the current boost-histogram for use in tests.
"""

VER = tuple(map(int, bh.__version__.split(".")))
VER = tuple(map(int, bh.__version__.split(".")[:3]))

if output is None:
output = DIR / f"bh_{bh.__version__}.pkl"
Expand Down
Loading