Fix all ruff linting errors

- Fix import ordering (isort)
- Remove unused imports
- Fix line too long (>100 chars)
- Fix variable naming (PIECES -> piece_chars)
- Fix bare except clause
- Remove unused variable
This commit is contained in:
Greg Hendrickson
2026-01-27 20:54:19 +00:00
parent ff643e1265
commit 8a5e1785dc
13 changed files with 111 additions and 94 deletions

View File

@@ -1,8 +1,7 @@
"""Stockfish AI engine wrapper with move explanations."""
import asyncio
from dataclasses import dataclass
from typing import Optional
import chess
from stockfish import Stockfish

View File

@@ -3,7 +3,7 @@
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional
import chess
import chess.pgn
@@ -25,8 +25,8 @@ class Move:
fen_after: str
timestamp: datetime = field(default_factory=datetime.utcnow)
think_time_ms: int = 0
evaluation: Optional[float] = None
explanation: Optional[str] = None
evaluation: float | None = None
explanation: str | None = None
@dataclass
@@ -51,7 +51,7 @@ class ChessGame:
"""Return the ID of the player whose turn it is."""
return self.white_player_id if self.board.turn == chess.WHITE else self.black_player_id
def make_move(self, uci: str) -> Optional[Move]:
def make_move(self, uci: str) -> Move | None:
"""Make a move and return Move object if valid."""
try:
chess_move = chess.Move.from_uci(uci)
@@ -86,7 +86,10 @@ class ChessGame:
def _check_game_end(self) -> None:
"""Check if the game has ended and set result."""
if self.board.is_checkmate():
self.result = GameResult.BLACK_WINS if self.board.turn == chess.WHITE else GameResult.WHITE_WINS
if self.board.turn == chess.WHITE:
self.result = GameResult.BLACK_WINS
else:
self.result = GameResult.WHITE_WINS
elif self.board.is_stalemate() or self.board.is_insufficient_material():
self.result = GameResult.DRAW
elif self.board.can_claim_draw():

View File

@@ -3,7 +3,6 @@
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional
class PlayerType(Enum):
@@ -25,7 +24,7 @@ class Player:
losses: int = 0
draws: int = 0
created_at: datetime = field(default_factory=datetime.utcnow)
last_seen: Optional[datetime] = None
last_seen: datetime | None = None
@property
def winrate(self) -> float:

View File

@@ -3,11 +3,8 @@
import asyncio
import logging
import os
import sys
from typing import Optional
import asyncssh
from shellmate.tui.app import ShellMateApp
import asyncssh
logger = logging.getLogger(__name__)
@@ -16,13 +13,13 @@ class ShellMateSSHServer(asyncssh.SSHServer):
"""SSH server that launches ShellMate TUI for each connection."""
def __init__(self):
self._username: Optional[str] = None
self._username: str | None = None
def connection_made(self, conn: asyncssh.SSHServerConnection) -> None:
peername = conn.get_extra_info('peername')
logger.info(f"SSH connection from {peername}")
def connection_lost(self, exc: Optional[Exception]) -> None:
def connection_lost(self, exc: Exception | None) -> None:
if exc:
logger.error(f"SSH connection error: {exc}")
else:
@@ -130,15 +127,13 @@ async def handle_client(process: asyncssh.SSHServerProcess) -> None:
async def run_simple_menu(process, session: TerminalSession, username: str, mode: str) -> None:
"""Beautiful Rich-based menu that scales to terminal size."""
from rich.align import Align
from rich.box import ROUNDED
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
from rich.align import Align
from rich.box import HEAVY, ROUNDED, DOUBLE
from rich.table import Table
from rich.layout import Layout
from rich.style import Style
import io
from rich.text import Text
class ProcessWriter:
def __init__(self, sess):
@@ -154,7 +149,10 @@ async def run_simple_menu(process, session: TerminalSession, username: str, mode
session._update_size()
writer = ProcessWriter(session)
console = Console(file=writer, width=session.width, height=session.height, force_terminal=True, color_system="truecolor")
console = Console(
file=writer, width=session.width, height=session.height,
force_terminal=True, color_system="truecolor"
)
session.clear()
@@ -186,10 +184,10 @@ async def run_simple_menu(process, session: TerminalSession, username: str, mode
menu_table.add_column(justify="center")
menu_table.add_row(Text(f"Welcome, {username}!", style="cyan"))
menu_table.add_row("")
menu_table.add_row("[bright_white on blue] 1 [/bright_white on blue] Play vs AI [dim]♔ vs ♚[/dim]")
menu_table.add_row("[bright_white on magenta] 2 [/bright_white on magenta] Play vs Human [dim]♔ vs ♔[/dim]")
menu_table.add_row("[bright_white on green] 3 [/bright_white on green] Learn & Practice [dim]📖[/dim]")
menu_table.add_row("[bright_white on red] q [/bright_white on red] Quit [dim]👋[/dim]")
menu_table.add_row("[bright_white on blue] 1 [/] Play vs AI [dim]♔ vs ♚[/]")
menu_table.add_row("[bright_white on magenta] 2 [/] Play vs Human [dim]♔ vs ♔[/]")
menu_table.add_row("[bright_white on green] 3 [/] Learn & Practice [dim]📖[/]")
menu_table.add_row("[bright_white on red] q [/] Quit [dim]👋[/]")
menu_table.add_row("")
menu_table.add_row(Text("Press a key to select...", style="dim italic"))
@@ -205,7 +203,8 @@ async def run_simple_menu(process, session: TerminalSession, username: str, mode
# Footer
console.print()
console.print(Align.center(Text(f"Terminal: {session.width}×{session.height}", style="dim")))
term_info = f"Terminal: {session.width}×{session.height}"
console.print(Align.center(Text(term_info, style="dim")))
render_menu()
@@ -248,11 +247,10 @@ async def run_simple_menu(process, session: TerminalSession, username: str, mode
async def run_chess_game(process, session: TerminalSession, username: str, opponent: str) -> None:
"""Run a beautiful chess game session with Stockfish AI."""
import chess
from rich.align import Align
from rich.box import ROUNDED
from rich.console import Console
from rich.panel import Panel
from rich.align import Align
from rich.text import Text
from rich.box import ROUNDED
class ProcessWriter:
def __init__(self, sess):
@@ -276,7 +274,7 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
logger.warning(f"Stockfish not available: {e}")
# Unicode pieces
PIECES = {
piece_chars = {
'K': '', 'Q': '', 'R': '', 'B': '', 'N': '', 'P': '',
'k': '', 'q': '', 'r': '', 'b': '', 'n': '', 'p': '',
}
@@ -331,7 +329,7 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
bg, bg_end = get_cell_style(square, piece, is_light)
if piece:
char = PIECES.get(piece.symbol(), '?')
char = piece_chars.get(piece.symbol(), '?')
if piece.color == chess.WHITE:
piece_row += f"{bg} \033[1;97m{char}\033[0m{bg} {bg_end}"
else:
@@ -378,9 +376,9 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
# Instructions based on state
if selected_square is not None:
sq_name = chess.square_name(selected_square).upper()
lines.append(f" \033[36mSelected: {sq_name}\033[0m → Type destination (or ESC to cancel)")
lines.append(f" \033[36mSelected: {sq_name}\033[0m → Type dest (ESC cancel)")
else:
lines.append(" Type \033[36msquare\033[0m (e.g. E2) to select\033[31mQ\033[0m = quit")
lines.append(" Type \033[36msquare\033[0m (e.g. E2) │ \033[31mQ\033[0m quit")
lines.append("")
@@ -398,9 +396,9 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
# Input prompt
if selected_square is not None:
session.write(pad + f" \033[32m→ \033[0m")
session.write(pad + " \033[32m→ \033[0m")
else:
session.write(pad + f" \033[36m> \033[0m")
session.write(pad + " \033[36m> \033[0m")
session.show_cursor()
def parse_square(text):
@@ -497,9 +495,14 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
# Check for promotion
piece = board.piece_at(selected_square)
if piece and piece.piece_type == chess.PAWN:
if (piece.color == chess.WHITE and chess.square_rank(sq) == 7) or \
(piece.color == chess.BLACK and chess.square_rank(sq) == 0):
move = chess.Move(selected_square, sq, promotion=chess.QUEEN)
is_white_promo = (piece.color == chess.WHITE and
chess.square_rank(sq) == 7)
is_black_promo = (piece.color == chess.BLACK and
chess.square_rank(sq) == 0)
if is_white_promo or is_black_promo:
move = chess.Move(
selected_square, sq, promotion=chess.QUEEN
)
board.push(move)
move_history.append(move.uci())
@@ -516,7 +519,7 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
stockfish_engine.set_fen_position(board.fen())
best_move = stockfish_engine.get_best_move()
ai_move = chess.Move.from_uci(best_move)
except:
except Exception:
import random
ai_move = random.choice(list(board.legal_moves))
else:
@@ -560,7 +563,10 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
if board.is_game_over():
session.clear()
writer = ProcessWriter(session)
console = Console(file=writer, width=session.width, height=session.height, force_terminal=True)
console = Console(
file=writer, width=session.width, height=session.height,
force_terminal=True
)
console.print()
if board.is_checkmate():

View File

@@ -1,19 +1,18 @@
"""Main TUI application."""
from typing import Any
from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal, Vertical
from textual.widgets import Footer, Header, Static
from textual.binding import Binding
from textual.screen import Screen
import chess
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Container, Horizontal, Vertical
from textual.screen import Screen
from textual.widgets import Footer, Header
from shellmate.tui.widgets import (
ChessBoardWidget,
MoveInput,
MoveListWidget,
GameStatusWidget,
MainMenu,
MoveInput,
MoveListWidget,
)
from shellmate.tui.widgets.move_input import parse_move

View File

@@ -1,10 +1,10 @@
"""TUI widgets for ShellMate."""
from .board import ChessBoardWidget
from .menu import MainMenu
from .move_input import MoveInput
from .move_list import MoveListWidget
from .status import GameStatusWidget
from .menu import MainMenu
__all__ = [
"ChessBoardWidget",

View File

@@ -1,13 +1,12 @@
"""Chess board widget with Unicode pieces - Hyper-polished version."""
from textual.widget import Widget
from textual.reactive import reactive
from textual.geometry import Size
from rich.text import Text
from rich.style import Style
from rich.console import RenderableType
import chess
from rich.console import RenderableType
from rich.style import Style
from rich.text import Text
from textual.geometry import Size
from textual.reactive import reactive
from textual.widget import Widget
# Unicode chess pieces - using filled variants for better visibility
PIECES_FILLED = {
@@ -120,7 +119,6 @@ class ChessBoardWidget(Widget):
def _render_compact(self) -> RenderableType:
"""Compact board for small terminals."""
text = Text()
cw = 3 # Fixed cell width for compact
files = "abcdefgh" if not self.flipped else "hgfedcba"

View File

@@ -1,11 +1,9 @@
"""Main menu widget."""
from textual.containers import Vertical
from textual.message import Message
from textual.widget import Widget
from textual.widgets import Button, Static
from textual.containers import Vertical, Center
from textual.message import Message
from rich.text import Text
from rich.console import RenderableType
class MainMenu(Widget):

View File

@@ -1,8 +1,8 @@
"""Move input widget."""
from textual.widgets import Input
from textual.message import Message
import chess
from textual.message import Message
from textual.widgets import Input
class MoveInput(Input):

View File

@@ -1,10 +1,9 @@
"""Move list/history widget."""
from textual.widget import Widget
from textual.reactive import reactive
from rich.text import Text
from rich.console import RenderableType
import chess
from rich.text import Text
from textual.reactive import reactive
from textual.widget import Widget
class MoveListWidget(Widget):

View File

@@ -1,11 +1,9 @@
"""Game status widget."""
from textual.widget import Widget
from textual.reactive import reactive
from rich.text import Text
from rich.panel import Panel
from rich.console import RenderableType
import chess
from rich.console import RenderableType
from rich.text import Text
from textual.widget import Widget
class GameStatusWidget(Widget):
@@ -43,7 +41,7 @@ class GameStatusWidget(Widget):
if self._is_checkmate:
winner = self._black_name if self._turn == chess.WHITE else self._white_name
text.append(f"♚ CHECKMATE!\n", style="bold red")
text.append("♚ CHECKMATE!\n", style="bold red")
text.append(f"{winner} wins!", style="bold yellow")
elif self._is_stalemate:
text.append("½ STALEMATE\n", style="bold yellow")
@@ -70,9 +68,13 @@ class GameStatusWidget(Widget):
text.append(f"{black_time_str}", style=black_style)
# Evaluation (if available)
if self._evaluation is not None and not (self._is_checkmate or self._is_stalemate or self._is_draw):
game_ended = self._is_checkmate or self._is_stalemate or self._is_draw
if self._evaluation is not None and not game_ended:
text.append("\n\n")
eval_str = f"+{self._evaluation:.1f}" if self._evaluation > 0 else f"{self._evaluation:.1f}"
if self._evaluation > 0:
eval_str = f"+{self._evaluation:.1f}"
else:
eval_str = f"{self._evaluation:.1f}"
bar = self._eval_bar(self._evaluation)
text.append(f"Eval: {eval_str}\n", style="dim")
text.append(bar)

View File

@@ -1,9 +1,9 @@
"""Tests for SSH server functionality."""
import asyncio
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
class TestShellMateSSHServer:
"""Test SSH server authentication."""
@@ -113,9 +113,10 @@ class TestChessBoard:
def test_board_initialization(self):
"""Board should initialize with standard position."""
from shellmate.tui.widgets.board import ChessBoardWidget
import chess
from shellmate.tui.widgets.board import ChessBoardWidget
widget = ChessBoardWidget()
assert widget.board.fen() == chess.STARTING_FEN
@@ -136,9 +137,10 @@ class TestChessBoard:
def test_square_selection(self):
"""Selecting a square should show legal moves."""
from shellmate.tui.widgets.board import ChessBoardWidget
import chess
from shellmate.tui.widgets.board import ChessBoardWidget
widget = ChessBoardWidget()
# Select e2 pawn
@@ -152,9 +154,10 @@ class TestChessBoard:
def test_square_deselection(self):
"""Deselecting should clear legal moves."""
from shellmate.tui.widgets.board import ChessBoardWidget
import chess
from shellmate.tui.widgets.board import ChessBoardWidget
widget = ChessBoardWidget()
widget.select_square(chess.E2)
widget.select_square(None)
@@ -251,9 +254,10 @@ class TestIntegration:
@pytest.mark.asyncio
async def test_server_starts(self):
"""Server should start without errors."""
from shellmate.ssh.server import start_server
import tempfile
import os
import tempfile
from shellmate.ssh.server import start_server
# Create a temporary host key
with tempfile.TemporaryDirectory() as tmpdir:

View File

@@ -1,20 +1,22 @@
"""Tests for UI rendering - catches Rich markup errors before deployment."""
import pytest
from io import StringIO
from rich.align import Align
from rich.box import ROUNDED
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from rich.align import Align
from rich.box import ROUNDED
def test_menu_render_no_markup_errors():
"""Test that the main menu renders without Rich markup errors."""
# Simulate the menu rendering code
output = StringIO()
console = Console(file=output, width=80, height=24, force_terminal=True, color_system="truecolor")
console = Console(
file=output, width=80, height=24, force_terminal=True, color_system="truecolor"
)
username = "testuser"
width = 80
@@ -35,10 +37,18 @@ def test_menu_render_no_markup_errors():
menu_table.add_column(justify="center")
menu_table.add_row(Text(f"Welcome, {username}!", style="cyan"))
menu_table.add_row("")
menu_table.add_row("[bright_white on blue] 1 [/bright_white on blue] Play vs AI [dim]♔ vs ♚[/dim]")
menu_table.add_row("[bright_white on magenta] 2 [/bright_white on magenta] Play vs Human [dim]♔ vs [/dim]")
menu_table.add_row("[bright_white on green] 3 [/bright_white on green] Learn & Practice [dim]📖[/dim]")
menu_table.add_row("[bright_white on red] q [/bright_white on red] Quit [dim]👋[/dim]")
menu_table.add_row(
"[bright_white on blue] 1 [/] Play vs AI [dim]♔ vs [/dim]"
)
menu_table.add_row(
"[bright_white on magenta] 2 [/] Play vs Human [dim]♔ vs ♔[/dim]"
)
menu_table.add_row(
"[bright_white on green] 3 [/] Learn & Practice [dim]📖[/dim]"
)
menu_table.add_row(
"[bright_white on red] q [/] Quit [dim]👋[/dim]"
)
menu_table.add_row("")
menu_table.add_row(Text("Press a key to select...", style="dim italic"))
@@ -93,7 +103,7 @@ def test_chess_board_render():
"""Test that chess board renders without errors."""
output = StringIO()
PIECES = {
piece_map = {
'K': '', 'Q': '', 'R': '', 'B': '', 'N': '', 'P': '',
'k': '', 'q': '', 'r': '', 'b': '', 'n': '', 'p': '',
}
@@ -106,8 +116,8 @@ def test_chess_board_render():
# Render rank 8 with pieces
pieces_row = ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']
row = " 8 |"
for file, piece_char in enumerate(pieces_row):
char = PIECES.get(piece_char, '?')
for piece_char in pieces_row:
char = piece_map.get(piece_char, '?')
row += f" {char} |"
row += " 8"
lines.append(row)