Commit 953307b177

953307b17732f8988ae099932eb2d7f4ec70404e

parent: 232e808bd8

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-22 23:52 UTC

Fix the lint failures and stop ruff drifting

CI has been red since 2026-08-02 with no change to this repo's code. The
workflow installs whatever ruff shipped most recently and there is no ruff
config, so every release that adds a rule turns the build red on its own. Last
green run was May.

Sixteen findings. Ten were worth fixing: import sorting, an implicit string
concat now parenthesised, typing.Optional replaced with X | None under deferred
annotations, and BINDINGS annotated ClassVar as Textual's own stubs declare it.

Two were the linter fighting the code and are now ignored with the reason
recorded next to them: blind excepts that exist to keep a duration parser and
the TUI from dying on the cases they absorb, and compose()'s nested with blocks,
where the nesting is the widget tree.

ruff is pinned in the workflow so a future release cannot turn this red again
without someone choosing to move.

Layout: unified · split

.github/workflows/pylint.yml +5
@@ -19,8 +19,13 @@ jobs:
1919 run: |
2020 python -m pip install --upgrade pip
2121 pip install -r requirements.txt
22 # Pinned. Unpinned, this installs whatever ruff shipped this week, and a
23 # release that adds a rule turns the build red with no change to the code —
24 # which is what happened between May and August 2026.
2225 - name: Install Ruff
2326 uses: astral-sh/ruff-action@v3.2.2
27 with:
28 version: "0.16.4"
2429 - name: Ruff Actions
2530 run: |
2631 ruff check --fix
nba/box_score.py +1 −1
@@ -4,8 +4,8 @@ Fetches and formats live box scores for individual games.
44
55import json
66
7from tabulate import tabulate
87from nba_api.live.nba.endpoints.boxscore import BoxScore
8from tabulate import tabulate
99
1010BOLD = "\033[1m"
1111END = "\033[0m"
nba/bracket.py +8 −5
@@ -2,10 +2,11 @@
22Fetches and formats the NBA playoff bracket.
33"""
44
5from __future__ import annotations
6
57import json
68import re
79from collections import defaultdict
8from typing import Optional
910
1011from nba_api.stats.endpoints.commonplayoffseries import CommonPlayoffSeries
1112from nba_api.stats.endpoints.leaguegamelog import LeagueGameLog
@@ -59,8 +60,10 @@ def get_bracket_table(data: dict) -> str:
5960 return "\n".join(
6061 [
6162 _center(f"{BOLD}NBA Playoff Bracket{END}", 74),
62 f"{GREEN}* Advanced{END} {RED}x Eliminated{END} "
63 f"{CYAN}> Series lead{END} {YELLOW}! Can clinch next win{END}",
63 (
64 f"{GREEN}* Advanced{END} {RED}x Eliminated{END} "
65 f"{CYAN}> Series lead{END} {YELLOW}! Can clinch next win{END}"
66 ),
6467 "",
6568 _render_conference("Western", summaries),
6669 "",
@@ -205,11 +208,11 @@ def _conference_round(summaries: dict, conference: str, round_number: int) -> li
205208 )
206209
207210
208def _slot(items: list, idx: int) -> Optional[dict]:
211def _slot(items: list, idx: int) -> dict | None:
209212 return items[idx] if idx < len(items) else None
210213
211214
212def _series_box(summary: Optional[dict], width: int) -> str:
215def _series_box(summary: dict | None, width: int) -> str:
213216 if summary is None:
214217 return _blank(width, "TBD")
215218
nba/cli.py +1
@@ -5,6 +5,7 @@ It imports the required modules and sets up a parser with basic options for demo
55"""
66
77import argparse
8
89from nba import fetch_data, scores, standings
910
1011
nba/fetch_data.py +1
@@ -3,6 +3,7 @@ Fetches data for use in other modules.
33"""
44
55import json
6
67from nba_api.live.nba.endpoints import scoreboard
78from nba_api.stats.endpoints import leaguestandings
89
nba/leaders.py +1 −1
@@ -4,8 +4,8 @@ Fetches and formats NBA statistical leaders.
44
55import json
66
7from tabulate import tabulate
87from nba_api.stats.endpoints.leagueleaders import LeagueLeaders
8from tabulate import tabulate
99
1010BOLD = "\033[1m"
1111END = "\033[0m"
nba/playoff.py +1 −1
@@ -4,8 +4,8 @@ Fetches and formats the NBA playoff picture.
44
55import json
66
7from tabulate import tabulate
87from nba_api.stats.endpoints.playoffpicture import PlayoffPicture
8from tabulate import tabulate
99
1010BOLD = "\033[1m"
1111END = "\033[0m"
nba/tui/app.py +5 −3
@@ -6,17 +6,19 @@ from __future__ import annotations
66
77import asyncio
88from pathlib import Path
9from typing import ClassVar
910
1011from rich.text import Text
1112from textual.app import App, ComposeResult
13from textual.binding import BindingType
1214from textual.containers import Horizontal
1315from textual.widgets import Footer, Header, Static, TabbedContent, TabPane
1416
15from nba import fetch_data
17from nba import box_score as box_score_mod
1618from nba import bracket as bracket_mod
19from nba import fetch_data
1720from nba import leaders as leaders_mod
1821from nba import playoff as playoff_mod
19from nba import box_score as box_score_mod
2022from nba.scores import get_scoreboard_table
2123from nba.standings import get_east_standings_table, get_west_standings_table
2224from nba.tui.widgets import CountdownBar, ScoresWidget
@@ -27,7 +29,7 @@ class NBAApp(App):
2729
2830 CSS_PATH = Path(__file__).parent / "styles.tcss"
2931
30 BINDINGS = [
32 BINDINGS: ClassVar[list[BindingType]] = [
3133 ("q", "quit", "Quit"),
3234 ("s", "show_scores", "Scores"),
3335 ("t", "show_standings", "Standings"),
pyproject.toml +20
@@ -39,3 +39,23 @@ include = ["nba*"]
3939
4040[tool.setuptools.package-data]
4141"nba.tui" = ["*.tcss"]
42
43[tool.ruff.lint]
44# ruff's defaults widen with each release, and the workflow installs whatever is
45# newest — which is how this repo went from green in May to sixteen findings in
46# August without a line of its own code changing. The rules below are the two
47# that fight the code rather than improve it; everything else ruff flags is
48# treated as worth fixing.
49ignore = [
50 # Blind `except Exception`. Deliberate here: a duration parser that falls back
51 # to the raw string, and TUI handlers that render "Error loading …" instead of
52 # taking the whole app down. Narrowing them would make the app crash on the
53 # cases they exist to absorb.
54 "BLE001",
55]
56
57[tool.ruff.lint.per-file-ignores]
58# Textual's compose() nests `with` blocks because the nesting *is* the widget
59# tree. Collapsing them into a single `with A, B:` would flatten a hierarchy
60# that is meant to be read as one.
61"nba/tui/app.py" = ["SIM117"]