diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d4d92828..971196c0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -114,7 +114,7 @@ jobs: - os: windows-2019 only: cp38-win32 - os: windows-2019 - only: cp37-win_amd64 + only: cp313-win_amd64 - os: macos-13 only: cp39-macosx_x86_64 - os: macos-14 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 9386144d..4f24b3fe 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -45,7 +45,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python: [37, 38, 39, 310, 311, 312, 313] + python: ["38", "39", "310", "311", "312", "313", "313t"] arch: [aarch64] steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1af831f4..3694c804 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,19 +36,8 @@ repos: - id: prettier types_or: [yaml, markdown, html, css, scss, javascript, json] - - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.5.0 - hooks: - - id: setup-cfg-fmt - args: - [ - --include-version-classifiers, - --min-py3-version=3.7, - --max-py-version=3.12, - ] - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.5.6" + rev: "v0.6.2" hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -61,7 +50,7 @@ repos: hooks: - id: mypy files: ^src - additional_dependencies: [numpy~=1.26.0, pytest, uhi, types-dataclasses] + additional_dependencies: [numpy~=1.26.0, pytest, uhi] - repo: https://github.com/codespell-project/codespell rev: v2.3.0 @@ -92,12 +81,12 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.1 + rev: 0.29.2 hooks: - id: check-readthedocs - id: check-github-workflows - repo: https://github.com/henryiii/validate-pyproject-schema-store - rev: 2024.07.29 + rev: 2024.08.19 hooks: - id: validate-pyproject diff --git a/README.md b/README.md index 2900b78f..71045e7c 100644 --- a/README.md +++ b/README.md @@ -178,10 +178,11 @@ python3 -m pip install boost-histogram ``` All the normal best-practices for Python apply; Pip should not be very old (Pip -9 is very old), you should be in a virtual environment, etc. Python 3.7+ is +9 is very old), you should be in a virtual environment, etc. Python 3.8+ is required; for older versions of Python (3.5 and 2.7), `0.13` will be installed instead, which is API equivalent to 1.0, but will not be gaining new features. -1.3.x was the last series to support Python 3.6. +1.3.x was the last series to support Python 3.6. 1.4.x was the last series to +support Python 3.7. #### Binaries available: @@ -190,15 +191,15 @@ when you run the above command on a supported platform. Wheels are produced usin [cibuildwheel](https://cibuildwheel.readthedocs.io/en/stable/); all common platforms have wheels provided in boost-histogram: -| System | Arch | Python versions | PyPy versions | -| ----------------- | ------ | ------------------------------- | ------------- | -| manylinux2014 | 64-bit | 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 | 3.9, 3.10 | -| manylinux2014 | ARM64 | 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 | 3.9, 3.10 | -| musllinux_1_1 | 64-bit | 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 | | -| macOS 10.9+ Intel | 64-bit | 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 | 3.9, 3.10 | -| macOS 11+ AS | Arm64 | 3.8, 3.9, 3.10, 3.11, 3.12 | 3.9, 3.10 | -| Windows | 32-bit | 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 | | -| Windows | 64-bit | 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 | 3.9, 3.10 | +| System | Arch | Python versions | PyPy versions | +| ----------------- | ------ | --------------------------------------- | ------------- | +| manylinux2014 | 64-bit | 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.13t | 3.9, 3.10 | +| manylinux2014 | ARM64 | 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.13t | 3.9, 3.10 | +| musllinux_1_1 | 64-bit | 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.13t | | +| macOS 10.9+ Intel | 64-bit | 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.13t | 3.9, 3.10 | +| macOS 11+ AS | Arm64 | 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.13t | 3.9, 3.10 | +| Windows | 32-bit | 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.13t | | +| Windows | 64-bit | 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, 3.13t | 3.9, 3.10 | PowerPC or IBM-Z wheels are not provided but are available on request. diff --git a/dev-requirements.txt b/dev-requirements.txt index 19cefd0e..824811b3 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -4,5 +4,4 @@ numpy pytest >=6,!=6.1.0,!=7.1.0 pytest-benchmark setuptools_scm[toml] >=3.4,!=4.0.0 -typing_extensions; python_version<'3.8' uproot diff --git a/pyproject.toml b/pyproject.toml index 0a75fc61..4acc2b65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "boost-histogram" dynamic = ["version"] description = "The Boost::Histogram Python wrapper." readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" authors = [ { name = "Hans Dembinski", email = "hans.dembinski@gmail.com" }, { name = "Henry Schreiner", email = "hschrein@cern.ch" }, @@ -29,7 +29,6 @@ classifiers = [ "Programming Language :: C++", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -49,7 +48,6 @@ classifiers = [ ] dependencies = [ "numpy", - "typing-extensions;python_version<'3.8'", ] [project.optional-dependencies] @@ -165,7 +163,7 @@ build-frontend = "build[uv]" test-extras = "test" test-command = "pytest --benchmark-disable {project}/tests" skip = [ - "pp3[78]-*", + "pp38-*", ] test-skip = [ "cp*-musllinux_*", # Segfaults @@ -187,7 +185,7 @@ environment.MACOSX_DEPLOYMENT_TARGET = "14.0" [tool.pylint] -py-version = "3.7" +py-version = "3.8" ignore-patterns = ['.*\.pyi'] extension-pkg-allow-list = ["boost_histogram._core"] reports.output-format = "colorized" @@ -212,10 +210,6 @@ messages_control.disable = [ "wrong-import-position", ] -[tool.ruff] -target-version = "py37" -src = ["src"] - [tool.ruff.lint] extend-select = [ "B", # flake8-bugbear diff --git a/src/boost_histogram/_core/accumulators.pyi b/src/boost_histogram/_core/accumulators.pyi index 612f281f..1ff58dae 100644 --- a/src/boost_histogram/_core/accumulators.pyi +++ b/src/boost_histogram/_core/accumulators.pyi @@ -29,9 +29,9 @@ class WeightedSum(_BaseAccumulator): def __iadd__(self: T, arg0: float) -> T: ... def fill(self: T, value: ArrayLike, variance: ArrayLike | None = None) -> T: ... @staticmethod - def _make(self: T, a: ArrayLike, b: ArrayLike) -> T: ... + def _make(a: ArrayLike, b: ArrayLike) -> WeightedSum: ... @staticmethod - def _array(self: T, a: ArrayLike, b: ArrayLike) -> T: ... + def _array(a: ArrayLike, b: ArrayLike) -> WeightedSum: ... def __getitem__(self, key: str) -> float: ... def __setitem__(self, key: str, value: float) -> None: ... @@ -76,12 +76,12 @@ class WeightedMean(_BaseAccumulator): def fill(self: T, value: ArrayLike, *, weight: ArrayLike | None = None) -> T: ... @staticmethod def _make( - self: T, arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike, arg3: ArrayLike - ) -> T: ... + arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike, arg3: ArrayLike + ) -> WeightedMean: ... @staticmethod def _array( - self: T, arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike, arg3: ArrayLike - ) -> T: ... + arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike, arg3: ArrayLike + ) -> WeightedMean: ... def __getitem__(self, key: str) -> float: ... def __setitem__(self, key: str, value: float) -> None: ... @@ -103,8 +103,8 @@ class Mean(_BaseAccumulator): ) -> T: ... def fill(self: T, value: ArrayLike, *, weight: ArrayLike | None = None) -> T: ... @staticmethod - def _make(self: T, arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike) -> T: ... + def _make(arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike) -> Mean: ... @staticmethod - def _array(self: T, arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike) -> T: ... + def _array(arg0: ArrayLike, arg1: ArrayLike, arg2: ArrayLike) -> Mean: ... def __getitem__(self, key: str) -> float: ... def __setitem__(self, key: str, value: float) -> None: ... diff --git a/src/boost_histogram/_internal/axestuple.py b/src/boost_histogram/_internal/axestuple.py index 3247836e..83bcf0ba 100644 --- a/src/boost_histogram/_internal/axestuple.py +++ b/src/boost_histogram/_internal/axestuple.py @@ -1,12 +1,11 @@ from __future__ import annotations from functools import partial -from typing import Any, ClassVar, Iterable, TypeVar +from typing import Any, ClassVar, Iterable, Literal, TypedDict, TypeVar import numpy as np from .axis import Axis -from .typing import Literal, TypedDict from .utils import set_module, zip_strict A = TypeVar("A", bound="ArrayTuple") diff --git a/src/boost_histogram/_internal/hist.py b/src/boost_histogram/_internal/hist.py index 4976c631..da1e152c 100644 --- a/src/boost_histogram/_internal/hist.py +++ b/src/boost_histogram/_internal/hist.py @@ -16,6 +16,7 @@ List, Mapping, NewType, + SupportsIndex, Tuple, TypeVar, Union, @@ -30,7 +31,7 @@ from .axis import Axis from .enum import Kind from .storage import Double, Storage -from .typing import Accumulator, ArrayLike, CppHistogram, SupportsIndex +from .typing import Accumulator, ArrayLike, CppHistogram from .utils import cast, register, set_module from .view import MeanView, WeightedMeanView, WeightedSumView, _to_view diff --git a/src/boost_histogram/_internal/typing.py b/src/boost_histogram/_internal/typing.py index efa6a555..d44268af 100644 --- a/src/boost_histogram/_internal/typing.py +++ b/src/boost_histogram/_internal/typing.py @@ -1,12 +1,6 @@ from __future__ import annotations -import sys -from typing import TYPE_CHECKING, Any, Tuple, Union - -if sys.version_info < (3, 8): - from typing_extensions import Literal, Protocol, SupportsIndex, TypedDict -else: - from typing import Literal, Protocol, SupportsIndex, TypedDict +from typing import TYPE_CHECKING, Any, Protocol, Tuple, Union if TYPE_CHECKING: from builtins import ellipsis @@ -26,16 +20,12 @@ __all__ = ( - "Protocol", "CppHistogram", - "SupportsIndex", "AxisLike", "ArrayLike", "Ufunc", "StdIndex", "StrIndex", - "Literal", - "TypedDict", ) diff --git a/src/boost_histogram/_internal/utils.py b/src/boost_histogram/_internal/utils.py index 1eb75062..92ebd7c4 100644 --- a/src/boost_histogram/_internal/utils.py +++ b/src/boost_histogram/_internal/utils.py @@ -3,12 +3,10 @@ import itertools import sys import typing -from typing import Any, Callable, ClassVar, Iterator, TypeVar +from typing import Any, Callable, ClassVar, Iterator, Protocol, TypeVar import boost_histogram -from .typing import Protocol - class Registerable(Protocol): _types: ClassVar[set[type[object]]] diff --git a/src/boost_histogram/_internal/view.py b/src/boost_histogram/_internal/view.py index c459584d..b8f650c1 100644 --- a/src/boost_histogram/_internal/view.py +++ b/src/boost_histogram/_internal/view.py @@ -1,11 +1,11 @@ from __future__ import annotations -from typing import Any, Callable, ClassVar, Mapping, MutableMapping +from typing import Any, Callable, ClassVar, Literal, Mapping, MutableMapping import numpy as np from ..accumulators import Mean, WeightedMean, WeightedSum -from .typing import ArrayLike, Literal, StrIndex, Ufunc +from .typing import ArrayLike, StrIndex, Ufunc UFMethod = Literal["__call__", "reduce", "reduceat", "accumulate", "outer", "inner"] @@ -50,7 +50,7 @@ def __setitem__(self, ind: StrIndex, value: ArrayLike) -> None: msg = "Needs matching ndarray or n+1 dim array" if array.ndim == current_ndim + 1: if len(self._FIELDS) == array.shape[-1]: - self.__setitem__(ind, self._PARENT._array(*np.moveaxis(array, -1, 0))) + self.__setitem__(ind, self._PARENT._array(*np.moveaxis(array, -1, 0))) # type: ignore[assignment] return msg += f", final dimension should be {len(self._FIELDS)} for this storage, got {array.shape[-1]} instead" raise ValueError(msg) @@ -224,7 +224,7 @@ def __array_ufunc__( # ufuncs that are allowed to reduce if ufunc in {np.add} and method == "reduce" and len(raw_inputs) == 1: results = (ufunc.reduce(self[field], **kwargs) for field in self._FIELDS) - return self._PARENT._make(*results) # type: ignore[no-any-return] + return self._PARENT._make(*results) # type: ignore[return-value] # ufuncs that are allowed to accumulate if ufunc in {np.add} and method == "accumulate" and len(raw_inputs) == 1: diff --git a/tests/conftest.py b/tests/conftest.py index aaae4715..dd674bb6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ from __future__ import annotations import contextlib +import importlib.metadata import sys from pathlib import Path @@ -74,11 +75,6 @@ def count_single_storage(request): def pytest_report_header() -> str: - if sys.version_info < (3, 8): - import importlib_metadata as metadata - else: - from importlib import metadata - with BASE.joinpath("pyproject.toml").open("rb") as f: pyproject = tomllib.load(f) project = pyproject.get("project", {}) @@ -94,7 +90,7 @@ def pytest_report_header() -> str: valid = [] for package in sorted(interesting_packages): with contextlib.suppress(ModuleNotFoundError): - valid.append(f"{package}=={metadata.version(package)}") + valid.append(f"{package}=={importlib.metadata.version(package)}") reqs = " ".join(valid) lines = [ f"installed packages of interest: {reqs}", diff --git a/tests/test_axes_object.py b/tests/test_axes_object.py index 8965f708..9d02160b 100644 --- a/tests/test_axes_object.py +++ b/tests/test_axes_object.py @@ -7,7 +7,7 @@ import boost_histogram as bh -@pytest.fixture() +@pytest.fixture def h(): return bh.Histogram( bh.axis.Regular(10, 0, 10, metadata=2), diff --git a/tests/test_histogram.py b/tests/test_histogram.py index da3a95e1..acaf9bbd 100644 --- a/tests/test_histogram.py +++ b/tests/test_histogram.py @@ -1104,10 +1104,6 @@ def test_fill_with_sequence_3(): assert_array_equal(h.view(True), [3, 1]) -@pytest.mark.skipif( - platform.machine() == "ppc64le" and sys.version_info < (3, 8), - reason="ppc64le segfault", -) def test_fill_with_sequence_4(): h = bh.Histogram( bh.axis.StrCategory([], growth=True), bh.axis.Integer(0, 0, growth=True) diff --git a/tests/test_views.py b/tests/test_views.py index 8a5b9c90..5affde20 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -8,7 +8,7 @@ import boost_histogram as bh -@pytest.fixture() +@pytest.fixture def v(): h = bh.Histogram(bh.axis.Integer(0, 4), storage=bh.storage.Weight()) h.fill([1, 1, 1, 2, 2, 3])