Skip to content

Commit 0ca4188

Browse files
committed
[pre-commit] Add "fix-future-annotations" & "pyupgrade" hooks
Now that the next major version of Python finally seems to land with PEP 649 implemented, it seems safe to use `from __future__ import annotations` everywhere we use non-builtin type hints :-) And thanks to the "flake8 type-checking" rules (re-implemented in Ruff), we can make sure we're still using "if TYPE_CHECKING:" for type-only imports 👌
1 parent 820c158 commit 0ca4188

File tree

82 files changed

+890
-722
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+890
-722
lines changed

.pre-commit-config.yaml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,42 @@
11
# See https://pre-commit.com for more information
22
# See https://pre-commit.com/hooks.html for more hooks
33
repos:
4+
5+
# -------------- MyPy: type checking
6+
# Although MyPy has limited abilities in a pre-commit context, as it can only
7+
# type-check each file in isolation, it can still catch errors.
48
- repo: https://github.com/pre-commit/mirrors-mypy
59
rev: v1.11.2
610
hooks:
711
- id: mypy
8-
additional_dependencies: [types-requests==2.31.0.2]
12+
additional_dependencies: [ types-requests==2.31.0.2 ]
913
exclude: "^scripts/load_testing/.*\\.py$"
14+
15+
# -------------- Ruff: linter & "à la Black" formatter
1016
- repo: https://github.com/charliermarsh/ruff-pre-commit
1117
rev: v0.6.3
1218
hooks:
1319
# Run the formatter.
1420
- id: ruff-format
1521
# Run the linter.
1622
- id: ruff
17-
args: ["--fix"]
23+
args: [ "--fix" ]
1824
exclude: "^src/project/settings/.*\\.py$"
25+
26+
# -------------- pyupgrade: make sure we're using the latest Python features
27+
- repo: https://github.com/asottile/pyupgrade
28+
rev: v3.19.0
29+
hooks:
30+
- id: pyupgrade
31+
args: [ "--py311-plus" ]
32+
33+
# -------------- fix-future-annotations: upgrade the typing annotations syntax to PEP 585 and PEP 604.
34+
- repo: https://github.com/frostming/fix-future-annotations
35+
rev: 0.5.0 # a released version tag
36+
hooks:
37+
- id: fix-future-annotations
38+
39+
# -------------- validate-pyproject: does what it says on the tin ^_^
1940
- repo: https://github.com/abravalheri/validate-pyproject
2041
rev: v0.19
2142
hooks:

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ PYTHON_BINS ?= ./.venv/bin
22
PYTHON ?= ${PYTHON_BINS}/python
33
DJANGO_SETTINGS_MODULE ?= project.settings.development
44
SUB_MAKE = ${MAKE} --no-print-directory
5+
UV_PYTHON ?= ${PYTHON}
56
UV ?= bin/uv
7+
UVX ?= bin/uvx
68

79
.DEFAULT_GOAL := help
810

@@ -90,7 +92,7 @@ test: ## Launch the pytest tests suite
9092
${PYTHON_BINS}/pytest ${pytest_opts}
9193

9294
.PHONY: code-quality/all
93-
code-quality/all: code-quality/ruff_check code-quality/ruff_lint code-quality/mypy ## Run all our code quality tools
95+
code-quality/all: code-quality/ruff_check code-quality/ruff_lint code-quality/mypy code-quality/fix-future-annotations ## Run all our code quality tools
9496

9597
.PHONY: code-quality/ruff_check
9698
code-quality/ruff_check: ruff_opts ?=
@@ -110,6 +112,13 @@ code-quality/mypy: ## Python's equivalent of TypeScript
110112
# @link https://mypy.readthedocs.io/en/stable/
111113
@${PYTHON_BINS}/mypy src/ ${mypy_opts}
112114

115+
.PHONY: code-quality/fix-future-annotations
116+
code-quality/fix-future-annotations: fix_future_annotations_opts ?=
117+
code-quality/fix-future-annotations: ## Make sure we're using PEP 585 and PEP 604
118+
# @link https://github.com/frostming/fix-future-annotations
119+
@UV_PYTHON=${UV_PYTHON} \
120+
${UVX} fix-future-annotations ${fix_future_annotations_opts} src/
121+
113122
# Here starts the frontend stuff
114123

115124
.PHONY: frontend/install

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,12 @@ select = [
8787
# https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch
8888
"TCH001",
8989
"TCH002",
90-
"TCH003",
90+
"TCH003",
91+
"FA100", # future-rewritable-type-annotation
92+
"FA102", # future-required-type-annotation
9193
]
9294
[tool.ruff.lint.per-file-ignores]
9395
"src/project/settings/*" = ["F405"]
94-
"src/lib/chess_engines/sunfish/sunfish.py" = ["F841", "F821"]
9596

9697
[tool.ruff.lint.isort]
9798
# https://docs.astral.sh/ruff/settings/#lintisort

scripts/download_assets.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env python
2+
from __future__ import annotations
23

34
import asyncio
45
from pathlib import Path
@@ -83,7 +84,7 @@
8384

8485

8586
async def download_assets(*, even_if_exists: bool) -> None:
86-
download_coros: list["Coroutine"] = []
87+
download_coros: list[Coroutine] = []
8788

8889
limits = httpx.Limits(
8990
max_connections=DOWNLOADS_CONCURRENCY,

src/apps/chess/business_logic/_calculate_fen_before_move.py

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

35
import chess
@@ -11,10 +13,10 @@
1113
def calculate_fen_before_move(
1214
# TODO: change move_uci to a MoveTuple type
1315
*,
14-
fen_after_move: "FEN",
16+
fen_after_move: FEN,
1517
move_uci: str,
16-
moving_player_side: "PlayerSide",
17-
) -> "FEN":
18+
moving_player_side: PlayerSide,
19+
) -> FEN:
1820
"""
1921
Calculate the FEN of the chess board before the given move.
2022
Raises a ValueError if the move is invalid.

src/apps/chess/business_logic/_calculate_piece_available_targets.py

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

35
import chess
@@ -9,10 +11,10 @@
911

1012

1113
def calculate_piece_available_targets(
12-
*, chess_board: chess.Board, piece_square: "Square"
13-
) -> frozenset["Square"]:
14+
*, chess_board: chess.Board, piece_square: Square
15+
) -> frozenset[Square]:
1416
square_index = chess.parse_square(piece_square)
15-
result: list["Square"] = []
17+
result: list[Square] = []
1618
for move in chess_board.legal_moves:
1719
if move.from_square == square_index:
1820
result.append(chess_lib_square_to_square(move.to_square))

src/apps/chess/business_logic/_do_chess_move.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from typing import TYPE_CHECKING, Literal, cast
24

35
import chess
@@ -18,12 +20,12 @@
1820

1921
from apps.chess.types import FEN, GameEndReason, MoveTuple, PlayerSide, Rank, Square
2022

21-
_CHESS_COLOR_TO_PLAYER_SIDE_MAPPING: "Mapping[chess.Color, PlayerSide]" = {
23+
_CHESS_COLOR_TO_PLAYER_SIDE_MAPPING: Mapping[chess.Color, PlayerSide] = {
2224
True: "w",
2325
False: "b",
2426
}
2527

26-
_CHESS_OUTCOME_TO_GAME_END_REASON_MAPPING: "Mapping[chess.Termination, GameEndReason]" = {
28+
_CHESS_OUTCOME_TO_GAME_END_REASON_MAPPING: Mapping[chess.Termination, GameEndReason] = {
2729
chess.Termination.CHECKMATE: "checkmate",
2830
chess.Termination.STALEMATE: "stalemate",
2931
chess.Termination.INSUFFICIENT_MATERIAL: "insufficient_material",
@@ -44,15 +46,15 @@
4446
("e8", "c8"),
4547
)
4648

47-
_CASTLING_ROOK_MOVE: "Mapping[_CastlingPossibleTo, tuple[Square, Square]]" = {
49+
_CASTLING_ROOK_MOVE: Mapping[_CastlingPossibleTo, tuple[Square, Square]] = {
4850
# {king new square: (rook previous square, rook new square)} dict:
4951
"g1": ("h1", "f1"),
5052
"c1": ("a1", "d1"),
5153
"g8": ("h8", "f8"),
5254
"c8": ("a8", "d8"),
5355
}
5456

55-
_EN_PASSANT_CAPTURED_PIECES_RANK_CONVERSION: dict["Rank", "Rank"] = {
57+
_EN_PASSANT_CAPTURED_PIECES_RANK_CONVERSION: dict[Rank, Rank] = {
5658
# if a pawn was captured by en passant targeting a6, its position on the board
5759
# before at the moment it's been captured was a5:
5860
"6": "5",
@@ -63,9 +65,9 @@
6365

6466
def do_chess_move(
6567
*,
66-
from_: "Square",
67-
to: "Square",
68-
fen: "FEN | None" = None,
68+
from_: Square,
69+
to: Square,
70+
fen: FEN | None = None,
6971
chess_board: chess.Board | None = None,
7072
) -> ChessMoveResult:
7173
"""
@@ -77,8 +79,8 @@ def do_chess_move(
7779
"You must provide either a FEN string or a `chess.Board` object"
7880
)
7981

80-
moves: list["MoveTuple"] = []
81-
captured: "Square | None" = None
82+
moves: list[MoveTuple] = []
83+
captured: Square | None = None
8284

8385
if not chess_board:
8486
chess_board = chess.Board(fen)

src/apps/chess/business_logic/_do_chess_move_with_piece_role_by_square.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from typing import TYPE_CHECKING, NamedTuple, cast
24

35
from ..chess_helpers import (
@@ -20,18 +22,18 @@
2022

2123

2224
class ChessMoveWithPieceRoleBySquareResult(NamedTuple):
23-
move_result: "ChessMoveResult"
24-
piece_role_by_square: "PieceRoleBySquare"
25-
captured_piece: "PieceRole | None"
25+
move_result: ChessMoveResult
26+
piece_role_by_square: PieceRoleBySquare
27+
captured_piece: PieceRole | None
2628

2729

2830
def do_chess_move_with_piece_role_by_square(
2931
*,
30-
from_: "Square",
31-
to: "Square",
32-
piece_role_by_square: "PieceRoleBySquare",
33-
fen: "FEN | None" = None,
34-
chess_board: "chess.Board | None" = None,
32+
from_: Square,
33+
to: Square,
34+
piece_role_by_square: PieceRoleBySquare,
35+
fen: FEN | None = None,
36+
chess_board: chess.Board | None = None,
3537
) -> ChessMoveWithPieceRoleBySquareResult:
3638
from ._do_chess_move import do_chess_move
3739

@@ -63,7 +65,7 @@ def do_chess_move_with_piece_role_by_square(
6365
)
6466
piece_role_by_square[from_] += piece_promotion # type: ignore
6567

66-
captured_piece: "PieceRole | None" = None
68+
captured_piece: PieceRole | None = None
6769
if captured := move_result["captured"]:
6870
assert move_result["is_capture"]
6971
captured_piece = piece_role_by_square[captured]

0 commit comments

Comments
 (0)