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.""" """Stockfish AI engine wrapper with move explanations."""
import asyncio
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional
import chess import chess
from stockfish import Stockfish from stockfish import Stockfish

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,18 @@
"""Main TUI application.""" """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 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 ( from shellmate.tui.widgets import (
ChessBoardWidget, ChessBoardWidget,
MoveInput,
MoveListWidget,
GameStatusWidget, GameStatusWidget,
MainMenu, MainMenu,
MoveInput,
MoveListWidget,
) )
from shellmate.tui.widgets.move_input import parse_move from shellmate.tui.widgets.move_input import parse_move

View File

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

View File

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

View File

@@ -1,11 +1,9 @@
"""Main menu widget.""" """Main menu widget."""
from textual.containers import Vertical
from textual.message import Message
from textual.widget import Widget from textual.widget import Widget
from textual.widgets import Button, Static 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): class MainMenu(Widget):

View File

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

View File

@@ -1,10 +1,9 @@
"""Move list/history widget.""" """Move list/history widget."""
from textual.widget import Widget
from textual.reactive import reactive
from rich.text import Text
from rich.console import RenderableType 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): class MoveListWidget(Widget):

View File

@@ -1,11 +1,9 @@
"""Game status widget.""" """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 import chess
from rich.console import RenderableType
from rich.text import Text
from textual.widget import Widget
class GameStatusWidget(Widget): class GameStatusWidget(Widget):
@@ -43,7 +41,7 @@ class GameStatusWidget(Widget):
if self._is_checkmate: if self._is_checkmate:
winner = self._black_name if self._turn == chess.WHITE else self._white_name 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") text.append(f"{winner} wins!", style="bold yellow")
elif self._is_stalemate: elif self._is_stalemate:
text.append("½ STALEMATE\n", style="bold yellow") text.append("½ STALEMATE\n", style="bold yellow")
@@ -70,9 +68,13 @@ class GameStatusWidget(Widget):
text.append(f"{black_time_str}", style=black_style) text.append(f"{black_time_str}", style=black_style)
# Evaluation (if available) # 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") 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) bar = self._eval_bar(self._evaluation)
text.append(f"Eval: {eval_str}\n", style="dim") text.append(f"Eval: {eval_str}\n", style="dim")
text.append(bar) text.append(bar)

View File

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

View File

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