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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
include:
- os: macos-latest
python-version: '3.13'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lower-bound-requirements.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
matrix:
os: [ubuntu-latest]
# minimum supported Python
python-version: ['3.8']
python-version: ['3.9']

steps:
- uses: actions/checkout@v5
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
include:
- os: macos-latest
python-version: '3.13'
Expand Down
5 changes: 3 additions & 2 deletions .pre-commit-config.yaml
Copy link
Member Author

@matthewfeickert matthewfeickert Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kratsg c.f. #2566 (comment) This used Python 3.10 for mypy.

Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,15 @@ repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
# check the oldest and newest supported Pythons
# except skip python 3.9 for numpy, due to poor typing
hooks:
- &mypy
id: mypy
name: mypy with Python 3.8
name: mypy with Python 3.10
files: src
additional_dependencies:
['numpy', 'types-tqdm', 'click', 'types-jsonpatch', 'types-pyyaml', 'types-jsonschema', 'importlib_metadata', 'packaging']
args: ["--python-version=3.8"]
args: ["--python-version=3.10"]
- <<: *mypy
name: mypy with Python 3.13
args: ["--python-version=3.13"]
Expand Down
4 changes: 1 addition & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dynamic = ["version"]
description = "pure-Python HistFactory implementation with tensors and autodiff"
readme = "README.rst"
license = "Apache-2.0"
requires-python = ">=3.8"
requires-python = ">=3.9"
authors = [
{ name = "Lukas Heinrich", email = "lukas.heinrich@cern.ch" },
{ name = "Matthew Feickert", email = "matthew.feickert@cern.ch" },
Expand All @@ -32,7 +32,6 @@ classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand All @@ -44,7 +43,6 @@ classifiers = [
]
dependencies = [
"click>=8.0.0", # for console scripts
"importlib_resources>=1.4.0; python_version < '3.9'", # for resources in schema
"jsonpatch>=1.15",
"jsonschema>=4.15.0", # for utils
"pyyaml>=5.1", # for parsing CLI equal-delimited options
Expand Down
21 changes: 7 additions & 14 deletions src/pyhf/contrib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,7 @@ def download(archive_url, output_directory, force=False, compress=False):
with open(output_directory, "wb") as archive:
archive.write(response.content)
else:
# Support for file-like objects for tarfile.is_tarfile was added
# in Python 3.9, so as pyhf is currently Python 3.8+ then can't
# do tarfile.is_tarfile(BytesIO(response.content)).
# Instead, just use a 'try except' block to determine if the
# archive is a valid tarfile.
# TODO: Simplify after pyhf is Python 3.9+ only
try:
if tarfile.is_tarfile(BytesIO(response.content)):
# Use transparent compression to allow for .tar or .tar.gz
with tarfile.open(
mode="r:*", fileobj=BytesIO(response.content)
Expand All @@ -97,13 +91,7 @@ def download(archive_url, output_directory, force=False, compress=False):
archive.extractall(output_directory, filter="data")
else:
archive.extractall(output_directory)
except tarfile.ReadError:
if not zipfile.is_zipfile(BytesIO(response.content)):
raise exceptions.InvalidArchive(
f"The archive downloaded from {archive_url} is not a tarfile"
+ " or a zipfile and so can not be opened as one."
)

elif zipfile.is_zipfile(BytesIO(response.content)):
output_directory = Path(output_directory)
if output_directory.exists():
rmtree(output_directory)
Expand All @@ -129,6 +117,11 @@ def download(archive_url, output_directory, force=False, compress=False):
# from creation time
rmtree(output_directory)
_tmp_path.replace(output_directory)
else:
raise exceptions.InvalidArchive(
f"The archive downloaded from {archive_url} is not a tarfile"
+ " or a zipfile and so can not be opened as one."
)

except ModuleNotFoundError:
log.error(
Expand Down
3 changes: 2 additions & 1 deletion src/pyhf/mixins.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import annotations

import logging
from typing import Any, Sequence
from typing import Any
from collections.abc import Sequence

from pyhf.typing import Channel

Expand Down
3 changes: 1 addition & 2 deletions src/pyhf/modifiers/staterror.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
from typing import List

import pyhf
from pyhf import events
Expand All @@ -10,7 +9,7 @@
log = logging.getLogger(__name__)


def required_parset(sigmas, fixed: List[bool]):
def required_parset(sigmas, fixed: list[bool]):
n_parameters = len(sigmas)
return {
'paramset_type': 'constrained_by_normal',
Expand Down
4 changes: 1 addition & 3 deletions src/pyhf/parameters/paramsets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import List

import pyhf

__all__ = [
Expand Down Expand Up @@ -29,7 +27,7 @@ def __init__(self, **kwargs):
)

@property
def suggested_fixed(self) -> List[bool]:
def suggested_fixed(self) -> list[bool]:
if isinstance(self._suggested_fixed, bool):
return [self._suggested_fixed] * self.n_parameters
return self._suggested_fixed
Expand Down
4 changes: 2 additions & 2 deletions src/pyhf/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import copy
import logging
from typing import List, Union
from typing import Union

import pyhf.parameters
import pyhf
Expand Down Expand Up @@ -406,7 +406,7 @@ def param_set(self, name):
"""
return self.par_map[name]['paramset']

def suggested_fixed(self) -> List[bool]:
def suggested_fixed(self) -> list[bool]:
"""
Identify the fixed parameters in the model.

Expand Down
14 changes: 4 additions & 10 deletions src/pyhf/readxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,10 @@
from typing import (
IO,
Callable,
Iterable,
List,
MutableMapping,
MutableSequence,
Sequence,
Set,
Tuple,
Union,
cast,
)
from collections.abc import Iterable, MutableMapping, MutableSequence, Sequence

import xml.etree.ElementTree as ET
from pathlib import Path
Expand Down Expand Up @@ -46,8 +40,8 @@

log = logging.getLogger(__name__)

FileCacheType = MutableMapping[str, Tuple[Union[IO[str], IO[bytes]], Set[str]]]
MountPathType = Iterable[Tuple[Path, Path]]
FileCacheType = MutableMapping[str, tuple[Union[IO[str], IO[bytes]], set[str]]]
MountPathType = Iterable[tuple[Path, Path]]
ResolverType = Callable[[str], Path]

__FILECACHE__: FileCacheType = {}
Expand Down Expand Up @@ -99,7 +93,7 @@ def extract_error(hist: uproot.behaviors.TH1.TH1) -> list[float]:
"""

variance = hist.variances() if hist.weighted else hist.to_numpy()[0]
return cast(List[float], np.sqrt(variance).tolist())
return cast(list[float], np.sqrt(variance).tolist())


def import_root_histogram(
Expand Down
9 changes: 1 addition & 8 deletions src/pyhf/schema/loader.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
from pathlib import Path
import sys
import json
import pyhf.exceptions
from pyhf.schema import variables

# importlib.resources.as_file wasn't added until Python 3.9
# c.f. https://docs.python.org/3.9/library/importlib.html#importlib.resources.as_file
if sys.version_info >= (3, 9):
from importlib import resources
else:
import importlib_resources as resources
from importlib import resources


def load_schema(schema_id: str):
Expand Down
3 changes: 2 additions & 1 deletion src/pyhf/schema/validator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numbers
from pathlib import Path
from typing import Mapping, Union
from typing import Union
from collections.abc import Mapping

import jsonschema

Expand Down
8 changes: 1 addition & 7 deletions src/pyhf/schema/variables.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import sys
from importlib import resources

# importlib.resources.as_file wasn't added until Python 3.9
# c.f. https://docs.python.org/3.9/library/importlib.html#importlib.resources.as_file
if sys.version_info >= (3, 9):
from importlib import resources
else:
import importlib_resources as resources
schemas = resources.files('pyhf') / "schemas"

SCHEMA_CACHE = {}
Expand Down
9 changes: 6 additions & 3 deletions src/pyhf/tensor/numpy_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Callable, Generic, Mapping, Sequence, TypeVar, Union
from typing import TYPE_CHECKING, Callable, Generic, TypeVar, Union
from collections.abc import Mapping, Sequence

import numpy as np

Expand Down Expand Up @@ -207,7 +208,8 @@

def tolist(self, tensor_in: Tensor[T] | list[T]) -> list[T]:
try:
return tensor_in.tolist() # type: ignore[union-attr,no-any-return]
# unused-ignore for [no-any-return] in python 3.9
return tensor_in.tolist() # type: ignore[union-attr,no-any-return,unused-ignore]
except AttributeError:
if isinstance(tensor_in, list):
return tensor_in
Expand Down Expand Up @@ -654,4 +656,5 @@

.. versionadded:: 0.7.0
"""
return tensor_in.transpose()
# TODO: Casting needed for Python 3.10 mypy but not Python 3.13?

Check notice on line 659 in src/pyhf/tensor/numpy_backend.py

View check run for this annotation

codefactor.io / CodeFactor

src/pyhf/tensor/numpy_backend.py#L659

Unresolved comment '# TODO: Casting needed for Python 3.10 mypy but not Python 3.13?' (C100)
return cast(ArrayLike, tensor_in.transpose())
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kratsg this PR is mostly just a cherry-pick from your first commit in PR #2566, but to get Python 3.10 typing with mypy to pass I had to cast the output of transpose to ArrayLike.

I'm not sure if this makes sense or not, so I think you'll need to weigh in here.

9 changes: 3 additions & 6 deletions src/pyhf/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
from typing import (
Any,
Literal,
MutableSequence,
Protocol,
Sequence,
SupportsIndex,
Tuple,
TypedDict,
Union,
)
from collections.abc import MutableSequence, Sequence

__all__ = (
"Channel",
Expand All @@ -35,10 +33,9 @@
)


# TODO: Switch to os.PathLike[str] once Python 3.8 support dropped
PathOrStr = Union[str, "os.PathLike[str]"]
PathOrStr = Union[str, os.PathLike[str]]

Shape = Tuple[int, ...]
Shape = tuple[int, ...]
ShapeLike = Union[SupportsIndex, Sequence[SupportsIndex]]


Expand Down
9 changes: 1 addition & 8 deletions src/pyhf/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,7 @@
import hashlib
from gettext import gettext

import sys

# importlib.resources.as_file wasn't added until Python 3.9
# c.f. https://docs.python.org/3.9/library/importlib.html#importlib.resources.as_file
if sys.version_info >= (3, 9):
from importlib import resources
else:
import importlib_resources as resources
from importlib import resources

__all__ = [
"EqDelimStringParamType",
Expand Down
5 changes: 0 additions & 5 deletions tests/contrib/test_viz.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json
import sys

import matplotlib
import matplotlib.pyplot as plt
Expand Down Expand Up @@ -68,10 +67,6 @@ def test_plot_results(datadir):


@pytest.mark.mpl_image_compare
@pytest.mark.xfail(
sys.version_info < (3, 8),
reason="baseline image generated with matplotlib v3.6.0 which is Python 3.8+",
)
def test_plot_results_no_axis(datadir):
data = json.load(datadir.joinpath("hypotest_results.json").open(encoding="utf-8"))

Expand Down
Loading