Skip to content

Commit 949a55e

Browse files
committed
chore: Remove Python 3.8 and 3.9 support
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
1 parent 83db553 commit 949a55e

25 files changed

Lines changed: 175 additions & 167 deletions

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
- uses: actions/setup-python@v6.2.0
2727
with:
28-
python-version: 3.9
28+
python-version: "3.14"
2929

3030
- uses: j178/prek-action@v2.0.4
3131
with:
@@ -41,7 +41,7 @@ jobs:
4141

4242
- uses: actions/setup-python@v6.2.0
4343
with:
44-
python-version: 3.9
44+
python-version: "3.14"
4545

4646
- uses: j178/prek-action@v2.0.4
4747
with:
@@ -51,7 +51,7 @@ jobs:
5151
strategy:
5252
matrix:
5353
os: [ubuntu-24.04]
54-
python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
54+
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
5555

5656
name: ${{ matrix.os }}/tests_${{ matrix.python }}
5757
runs-on: ${{ matrix.os }}

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ jobs:
1111
runs-on: ubuntu-24.04
1212
steps:
1313
- uses: actions/checkout@v6.0.2
14-
- name: Set up Python 3.9
14+
- name: Set up Python
1515
uses: actions/setup-python@v6.2.0
1616
with:
17-
python-version: 3.9
17+
python-version: "3.14"
1818

1919
- run: pip install hatch
2020

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

1111
- Much faster generation - up to ~180x on large and negative-mode cases.
1212

13+
### Removed
14+
15+
- Python 3.8 and 3.9 support.
16+
1317
## [0.12.0] - 2026-02-04
1418

1519
### Added

benchmarks/_schemas.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import json
22
import pathlib
3-
from typing import Dict, Optional
43

54
import graphql
65
from hypothesis import strategies as st
@@ -38,12 +37,12 @@
3837
PLACEHOLDER_STRATEGY = st.just("placeholder").map(nodes.String)
3938

4039

41-
def _load_corpus() -> Dict[str, str]:
40+
def _load_corpus() -> dict[str, str]:
4241
with open(_CORPUS_PATH) as fd:
4342
return json.load(fd)
4443

4544

46-
def custom_scalars_for(schema: str) -> Optional[Dict[str, st.SearchStrategy]]:
45+
def custom_scalars_for(schema: str) -> dict[str, st.SearchStrategy] | None:
4746
parsed = graphql.build_schema(schema)
4847
custom = {
4948
name: PLACEHOLDER_STRATEGY
@@ -53,7 +52,7 @@ def custom_scalars_for(schema: str) -> Optional[Dict[str, st.SearchStrategy]]:
5352
return custom or None
5453

5554

56-
def load_schemas() -> Dict[str, str]:
55+
def load_schemas() -> dict[str, str]:
5756
corpus = _load_corpus()
5857
schemas = {"recursive": RECURSIVE_SCHEMA}
5958
for label, name in _CORPUS_SELECTION.items():

mypy.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[mypy]
2-
python_version = 3.9
2+
python_version = 3.10
33
show_error_context = true
44
verbosity = 0
55
ignore_missing_imports = true

pyproject.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ classifiers = [
1515
"License :: OSI Approved :: MIT License",
1616
"Operating System :: OS Independent",
1717
"Programming Language :: Python :: 3 :: Only",
18-
"Programming Language :: Python :: 3.8",
19-
"Programming Language :: Python :: 3.9",
2018
"Programming Language :: Python :: 3.10",
2119
"Programming Language :: Python :: 3.11",
2220
"Programming Language :: Python :: 3.12",
@@ -30,7 +28,7 @@ maintainers = [{ name = "Dmitry Dygalo", email = "dmitry@dygalo.dev" }]
3028
readme = "README.md"
3129
license = "MIT"
3230
include = ["src/hypothesis_graphql/py.typed"]
33-
requires-python = ">=3.8"
31+
requires-python = ">=3.10"
3432
dependencies = ["hypothesis>=6.84.3,<7.0", "graphql-core>=3.1.0,<3.3.0"]
3533

3634
[project.optional-dependencies]
@@ -64,7 +62,7 @@ exclude_lines = ["pragma: no cover", "raise NotImplementedError", "if TYPE_CHECK
6462

6563
[tool.ruff]
6664
line-length = 120
67-
target-version = "py38"
65+
target-version = "py310"
6866

6967
[tool.ruff.lint]
7068
select = [

src/hypothesis_graphql/_strategies/aliases.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
from typing import Dict, List, Optional, Tuple
1+
from __future__ import annotations
22

33
import graphql
44

55
from ..types import SelectionNodes
66

77

8-
def maybe_add_alias_to_node(node: graphql.SelectionNode, seen: Dict[Tuple[str, str], List]) -> None:
8+
def maybe_add_alias_to_node(node: graphql.SelectionNode, seen: dict[tuple[str, str], list]) -> None:
99
if isinstance(node, graphql.FieldNode):
1010
maybe_add_alias(node, node.arguments, seen)
1111
if node.selection_set.selections:
@@ -21,8 +21,8 @@ def maybe_add_alias_to_node(node: graphql.SelectionNode, seen: Dict[Tuple[str, s
2121

2222
def maybe_add_alias(
2323
field_node: graphql.FieldNode,
24-
arguments: List[graphql.ArgumentNode],
25-
seen: Dict[Tuple[str, str], List],
24+
arguments: list[graphql.ArgumentNode],
25+
seen: dict[tuple[str, str], list],
2626
) -> None:
2727
for argument in arguments:
2828
key = (field_node.name.value, argument.name.value)
@@ -36,10 +36,10 @@ def maybe_add_alias(
3636
seen[key] = [value]
3737

3838

39-
def add_selection_aliases(nodes: Optional[SelectionNodes]) -> Optional[SelectionNodes]:
39+
def add_selection_aliases(nodes: SelectionNodes | None) -> SelectionNodes | None:
4040
"""Add aliases to fields that have conflicting argument types."""
4141
if nodes and len(nodes) > 1:
42-
seen: Dict[Tuple[str, str], List] = {}
42+
seen: dict[tuple[str, str], list] = {}
4343
for node in nodes:
4444
maybe_add_alias_to_node(node, seen)
4545
return nodes

src/hypothesis_graphql/_strategies/ast.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import graphql
24

35
from ..types import SelectionNodes

src/hypothesis_graphql/_strategies/builder.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,42 @@
1-
from typing import Dict, List, Optional, Sequence, Tuple
1+
from __future__ import annotations
2+
3+
from collections.abc import Sequence
24

35
import graphql
46
from graphql import is_equal_type
57

68
from .strategy import make_type_name
79

810
# (field_name, on_type, children, arguments)
9-
SelectionNode = Tuple[str, Optional[str], "List[SelectionNode]", List[graphql.ArgumentNode]]
11+
SelectionNode = tuple[str, str | None, "list[SelectionNode]", list[graphql.ArgumentNode]]
1012

1113

1214
def _field_node(
13-
name: str, children: Sequence[graphql.SelectionNode], args: List[graphql.ArgumentNode]
15+
name: str, children: Sequence[graphql.SelectionNode], args: list[graphql.ArgumentNode]
1416
) -> graphql.FieldNode:
1517
selection_set = graphql.SelectionSetNode(selections=tuple(children)) if children else None
1618
return graphql.FieldNode(name=graphql.NameNode(value=name), arguments=tuple(args), selection_set=selection_set)
1719

1820

1921
def build_selection_set(
20-
nodes: "List[SelectionNode]", type_map: Optional[Dict[str, graphql.GraphQLNamedType]] = None
22+
nodes: list[SelectionNode], type_map: dict[str, graphql.GraphQLNamedType] | None = None
2123
) -> graphql.SelectionSetNode:
22-
selections: List[graphql.SelectionNode] = []
24+
selections: list[graphql.SelectionNode] = []
2325
for name, on, children, args in nodes:
2426
if on is not None:
2527
continue
2628
child = list(build_selection_set(children, type_map).selections) if children else []
2729
selections.append(_field_node(name, child, args))
2830

29-
seen: Dict[str, graphql.GraphQLType] = {}
30-
by_type: Dict[str, List[SelectionNode]] = {}
31+
seen: dict[str, graphql.GraphQLType] = {}
32+
by_type: dict[str, list[SelectionNode]] = {}
3133
for node in nodes:
3234
if node[1] is not None:
3335
by_type.setdefault(node[1], []).append(node)
3436
for on_type in sorted(by_type):
3537
assert type_map is not None
3638
fragment_type = type_map[on_type]
37-
frag_fields: List[graphql.SelectionNode] = []
39+
frag_fields: list[graphql.SelectionNode] = []
3840
for name, _on_type, children, args in by_type[on_type]:
3941
field_type = fragment_type.fields[name].type
4042
child = list(build_selection_set(children, type_map).selections) if children else []
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
from typing import List, Tuple, TypeVar
1+
from __future__ import annotations
2+
3+
from typing import TypeVar
24

35
T = TypeVar("T")
46

57

6-
def flatten(items: Tuple[List[T], T]) -> List[T]:
8+
def flatten(items: tuple[list[T], T]) -> list[T]:
79
output = items[0]
810
output.extend(items[1:])
911
return output

0 commit comments

Comments
 (0)