Skip to content

Commit 95a0184

Browse files
authored
Merge pull request #117 from jerry-git/remove-python-38-39-eol
Remove support for Python 3.8 and 3.9 (end-of-life)
2 parents fd17549 + 533a763 commit 95a0184

File tree

12 files changed

+47
-112
lines changed

12 files changed

+47
-112
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
strategy:
3939
fail-fast: false
4040
matrix:
41-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
41+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
4242
steps:
4343
- uses: actions/checkout@v4
4444
- uses: ./.github/actions/python-poetry-env

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1111
### Changed
1212
- Migrated `poetry.dev-dependencies` to `poetry.group.dev.dependencies`
1313

14+
### Removed
15+
- Support for Python 3.8 and 3.9 (end-of-life)
16+
1417
## [0.10.0] - 2024-10-16
1518
### Added
1619
- Support for Python 3.13.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ The `least_duration` algorithm walks the list of tests and assigns each test to
9696
* Clone this repository
9797
* Requirements:
9898
* [Poetry](https://python-poetry.org/)
99-
* Python 3.8+
99+
* Python 3.10+
100100
* Create a virtual environment and install the dependencies
101101

102102
```sh

poetry.lock

Lines changed: 4 additions & 61 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ classifiers = [
1818
"Operating System :: OS Independent",
1919
"Programming Language :: Python",
2020
"Programming Language :: Python :: 3",
21-
"Programming Language :: Python :: 3.8",
22-
"Programming Language :: Python :: 3.9",
2321
"Programming Language :: Python :: 3.10",
2422
"Programming Language :: Python :: 3.11",
2523
"Programming Language :: Python :: 3.12",
@@ -34,7 +32,7 @@ packages = [{ include = 'pytest_split', from = 'src' }]
3432

3533

3634
[tool.poetry.dependencies]
37-
python = ">=3.8.1, <4.0"
35+
python = ">=3.10, <4.0"
3836
pytest = "^5 | ^6 | ^7 | ^8 | ^9"
3937

4038

@@ -62,7 +60,7 @@ slowest-tests = "pytest_split.cli:list_slowest_tests"
6260
pytest-split = "pytest_split.plugin"
6361

6462
[tool.black]
65-
target-version = ["py37", "py38", "py39"]
63+
target-version = ["py310", "py311", "py312", "py313", "py314"]
6664
include = '\.pyi?$'
6765

6866
[tool.pytest.ini_options]
@@ -94,7 +92,7 @@ disallow_untyped_calls = false
9492

9593

9694
[tool.ruff]
97-
target-version = "py38" # The lowest supported version
95+
target-version = "py310" # The lowest supported version
9896

9997
[tool.ruff.lint]
10098
# By default, enable all the lint rules.

src/pytest_split/algorithms.py

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@
55
from typing import TYPE_CHECKING, NamedTuple
66

77
if TYPE_CHECKING:
8-
from typing import Dict, List, Tuple
9-
108
from _pytest import nodes
119

1210

1311
class TestGroup(NamedTuple):
14-
selected: "List[nodes.Item]"
15-
deselected: "List[nodes.Item]"
12+
selected: "list[nodes.Item]"
13+
deselected: "list[nodes.Item]"
1614
duration: float
1715

1816

@@ -21,8 +19,8 @@ class AlgorithmBase(ABC):
2119

2220
@abstractmethod
2321
def __call__(
24-
self, splits: int, items: "List[nodes.Item]", durations: "Dict[str, float]"
25-
) -> "List[TestGroup]":
22+
self, splits: int, items: "list[nodes.Item]", durations: "dict[str, float]"
23+
) -> "list[TestGroup]":
2624
pass
2725

2826
def __hash__(self) -> int:
@@ -52,8 +50,8 @@ class LeastDurationAlgorithm(AlgorithmBase):
5250
"""
5351

5452
def __call__(
55-
self, splits: int, items: "List[nodes.Item]", durations: "Dict[str, float]"
56-
) -> "List[TestGroup]":
53+
self, splits: int, items: "list[nodes.Item]", durations: "dict[str, float]"
54+
) -> "list[TestGroup]":
5755
items_with_durations = _get_items_with_durations(items, durations)
5856

5957
# add index of item in list
@@ -71,12 +69,12 @@ def __call__(
7169
items_with_durations_indexed, key=lambda tup: tup[1], reverse=True
7270
)
7371

74-
selected: List[List[Tuple[nodes.Item, int]]] = [[] for _ in range(splits)]
75-
deselected: List[List[nodes.Item]] = [[] for _ in range(splits)]
76-
duration: List[float] = [0 for _ in range(splits)]
72+
selected: list[list[tuple[nodes.Item, int]]] = [[] for _ in range(splits)]
73+
deselected: list[list[nodes.Item]] = [[] for _ in range(splits)]
74+
duration: list[float] = [0 for _ in range(splits)]
7775

7876
# create a heap of the form (summed_durations, group_index)
79-
heap: List[Tuple[float, int]] = [(0, i) for i in range(splits)]
77+
heap: list[tuple[float, int]] = [(0, i) for i in range(splits)]
8078
heapq.heapify(heap)
8179
for item, item_duration, original_index in sorted_items_with_durations:
8280
# get group with smallest sum
@@ -122,14 +120,14 @@ class DurationBasedChunksAlgorithm(AlgorithmBase):
122120
"""
123121

124122
def __call__(
125-
self, splits: int, items: "List[nodes.Item]", durations: "Dict[str, float]"
126-
) -> "List[TestGroup]":
123+
self, splits: int, items: "list[nodes.Item]", durations: "dict[str, float]"
124+
) -> "list[TestGroup]":
127125
items_with_durations = _get_items_with_durations(items, durations)
128126
time_per_group = sum(map(itemgetter(1), items_with_durations)) / splits
129127

130-
selected: List[List[nodes.Item]] = [[] for i in range(splits)]
131-
deselected: List[List[nodes.Item]] = [[] for i in range(splits)]
132-
duration: List[float] = [0 for i in range(splits)]
128+
selected: list[list[nodes.Item]] = [[] for i in range(splits)]
129+
deselected: list[list[nodes.Item]] = [[] for i in range(splits)]
130+
duration: list[float] = [0 for i in range(splits)]
133131

134132
group_idx = 0
135133
for item, item_duration in items_with_durations:
@@ -151,8 +149,8 @@ def __call__(
151149

152150

153151
def _get_items_with_durations(
154-
items: "List[nodes.Item]", durations: "Dict[str, float]"
155-
) -> "List[Tuple[nodes.Item, float]]":
152+
items: "list[nodes.Item]", durations: "dict[str, float]"
153+
) -> "list[tuple[nodes.Item, float]]":
156154
durations = _remove_irrelevant_durations(items, durations)
157155
avg_duration_per_test = _get_avg_duration_per_test(durations)
158156
items_with_durations = [
@@ -161,7 +159,7 @@ def _get_items_with_durations(
161159
return items_with_durations
162160

163161

164-
def _get_avg_duration_per_test(durations: "Dict[str, float]") -> float:
162+
def _get_avg_duration_per_test(durations: "dict[str, float]") -> float:
165163
if durations:
166164
avg_duration_per_test = sum(durations.values()) / len(durations)
167165
else:
@@ -171,8 +169,8 @@ def _get_avg_duration_per_test(durations: "Dict[str, float]") -> float:
171169

172170

173171
def _remove_irrelevant_durations(
174-
items: "List[nodes.Item]", durations: "Dict[str, float]"
175-
) -> "Dict[str, float]":
172+
items: "list[nodes.Item]", durations: "dict[str, float]"
173+
) -> "dict[str, float]":
176174
# Filtering down durations to relevant ones ensures the avg isn't skewed by irrelevant data
177175
test_ids = [item.nodeid for item in items]
178176
durations = {name: durations[name] for name in test_ids if name in durations}
@@ -184,5 +182,5 @@ class Algorithms(enum.Enum):
184182
least_duration = LeastDurationAlgorithm()
185183

186184
@staticmethod
187-
def names() -> "List[str]":
185+
def names() -> "list[str]":
188186
return [x.name for x in Algorithms]

src/pytest_split/cli.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import argparse
22
import json
3-
from typing import TYPE_CHECKING
4-
5-
if TYPE_CHECKING:
6-
from typing import Dict
73

84

95
def list_slowest_tests() -> None:
@@ -28,7 +24,7 @@ def list_slowest_tests() -> None:
2824
return _list_slowest_tests(json.load(args.durations_path), args.count)
2925

3026

31-
def _list_slowest_tests(durations: "Dict[str, float]", count: int) -> None:
27+
def _list_slowest_tests(durations: "dict[str, float]", count: int) -> None:
3228
slowest_tests = tuple(
3329
sorted(durations.items(), key=lambda item: item[1], reverse=True)
3430
)[:count]

src/pytest_split/ipynb_compatibility.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from typing import TYPE_CHECKING
22

33
if TYPE_CHECKING:
4-
from typing import List
5-
64
from pytest_split.algorithms import TestGroup
75

86

@@ -45,8 +43,8 @@ def ensure_ipynb_compatibility(group: "TestGroup", items: list) -> None: # type
4543

4644

4745
def _find_sibiling_ipynb_cells(
48-
ipynb_node_id: str, item_node_ids: "List[str]"
49-
) -> "List[str]":
46+
ipynb_node_id: str, item_node_ids: "list[str]"
47+
) -> "list[str]":
5048
"""
5149
Returns all sibling IPyNb cells given an IPyNb cell nodeid.
5250
"""

0 commit comments

Comments
 (0)