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:
19 run: | 19 run: |
20 python -m pip install --upgrade pip 20 python -m pip install --upgrade pip
21 pip install -r requirements.txt 21 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.
22 - name: Install Ruff 25 - name: Install Ruff
23 uses: astral-sh/ruff-action@v3.2.2 26 uses: astral-sh/ruff-action@v3.2.2
27 with:
28 version: "0.16.4"
24 - name: Ruff Actions 29 - name: Ruff Actions
25 run: | 30 run: |
26 ruff check --fix 31 ruff check --fix
nba/box_score.py +1 −1
@@ -4,8 +4,8 @@ Fetches and formats live box scores for individual games.
4 4
5import json 5import json
6 6
7from tabulate import tabulate
8from nba_api.live.nba.endpoints.boxscore import BoxScore 7from nba_api.live.nba.endpoints.boxscore import BoxScore
8from tabulate import tabulate
9 9
10BOLD = "\033[1m" 10BOLD = "\033[1m"
11END = "\033[0m" 11END = "\033[0m"
nba/bracket.py +8 −5
@@ -2,10 +2,11 @@
2Fetches and formats the NBA playoff bracket. 2Fetches and formats the NBA playoff bracket.
3""" 3"""
4 4
5from __future__ import annotations
6
5import json 7import json
6import re 8import re
7from collections import defaultdict 9from collections import defaultdict
8from typing import Optional
9 10
10from nba_api.stats.endpoints.commonplayoffseries import CommonPlayoffSeries 11from nba_api.stats.endpoints.commonplayoffseries import CommonPlayoffSeries
11from nba_api.stats.endpoints.leaguegamelog import LeagueGameLog 12from nba_api.stats.endpoints.leaguegamelog import LeagueGameLog
@@ -59,8 +60,10 @@ def get_bracket_table(data: dict) -> str:
59 return "\n".join( 60 return "\n".join(
60 [ 61 [
61 _center(f"{BOLD}NBA Playoff Bracket{END}", 74), 62 _center(f"{BOLD}NBA Playoff Bracket{END}", 74),
62 f"{GREEN}* Advanced{END} {RED}x Eliminated{END} " 63 (
63 f"{CYAN}> Series lead{END} {YELLOW}! Can clinch next win{END}", 64 f"{GREEN}* Advanced{END} {RED}x Eliminated{END} "
65 f"{CYAN}> Series lead{END} {YELLOW}! Can clinch next win{END}"
66 ),
64 "", 67 "",
65 _render_conference("Western", summaries), 68 _render_conference("Western", summaries),
66 "", 69 "",
@@ -205,11 +208,11 @@ def _conference_round(summaries: dict, conference: str, round_number: int) -> li
205 ) 208 )
206 209
207 210
208def _slot(items: list, idx: int) -> Optional[dict]: 211def _slot(items: list, idx: int) -> dict | None:
209 return items[idx] if idx < len(items) else None 212 return items[idx] if idx < len(items) else None
210 213
211 214
212def _series_box(summary: Optional[dict], width: int) -> str: 215def _series_box(summary: dict | None, width: int) -> str:
213 if summary is None: 216 if summary is None:
214 return _blank(width, "TBD") 217 return _blank(width, "TBD")
215 218
nba/cli.py +1
@@ -5,6 +5,7 @@ It imports the required modules and sets up a parser with basic options for demo
5""" 5"""
6 6
7import argparse 7import argparse
8
8from nba import fetch_data, scores, standings 9from nba import fetch_data, scores, standings
9 10
10 11
nba/fetch_data.py +1
@@ -3,6 +3,7 @@ Fetches data for use in other modules.
3""" 3"""
4 4
5import json 5import json
6
6from nba_api.live.nba.endpoints import scoreboard 7from nba_api.live.nba.endpoints import scoreboard
7from nba_api.stats.endpoints import leaguestandings 8from nba_api.stats.endpoints import leaguestandings
8 9
nba/leaders.py +1 −1
@@ -4,8 +4,8 @@ Fetches and formats NBA statistical leaders.
4 4
5import json 5import json
6 6
7from tabulate import tabulate
8from nba_api.stats.endpoints.leagueleaders import LeagueLeaders 7from nba_api.stats.endpoints.leagueleaders import LeagueLeaders
8from tabulate import tabulate
9 9
10BOLD = "\033[1m" 10BOLD = "\033[1m"
11END = "\033[0m" 11END = "\033[0m"
nba/playoff.py +1 −1
@@ -4,8 +4,8 @@ Fetches and formats the NBA playoff picture.
4 4
5import json 5import json
6 6
7from tabulate import tabulate
8from nba_api.stats.endpoints.playoffpicture import PlayoffPicture 7from nba_api.stats.endpoints.playoffpicture import PlayoffPicture
8from tabulate import tabulate
9 9
10BOLD = "\033[1m" 10BOLD = "\033[1m"
11END = "\033[0m" 11END = "\033[0m"
nba/tui/app.py +5 −3
@@ -6,17 +6,19 @@ from __future__ import annotations
6 6
7import asyncio 7import asyncio
8from pathlib import Path 8from pathlib import Path
9from typing import ClassVar
9 10
10from rich.text import Text 11from rich.text import Text
11from textual.app import App, ComposeResult 12from textual.app import App, ComposeResult
13from textual.binding import BindingType
12from textual.containers import Horizontal 14from textual.containers import Horizontal
13from textual.widgets import Footer, Header, Static, TabbedContent, TabPane 15from textual.widgets import Footer, Header, Static, TabbedContent, TabPane
14 16
15from nba import fetch_data 17from nba import box_score as box_score_mod
16from nba import bracket as bracket_mod 18from nba import bracket as bracket_mod
19from nba import fetch_data
17from nba import leaders as leaders_mod 20from nba import leaders as leaders_mod
18from nba import playoff as playoff_mod 21from nba import playoff as playoff_mod
19from nba import box_score as box_score_mod
20from nba.scores import get_scoreboard_table 22from nba.scores import get_scoreboard_table
21from nba.standings import get_east_standings_table, get_west_standings_table 23from nba.standings import get_east_standings_table, get_west_standings_table
22from nba.tui.widgets import CountdownBar, ScoresWidget 24from nba.tui.widgets import CountdownBar, ScoresWidget
@@ -27,7 +29,7 @@ class NBAApp(App):
27 29
28 CSS_PATH = Path(__file__).parent / "styles.tcss" 30 CSS_PATH = Path(__file__).parent / "styles.tcss"
29 31
30 BINDINGS = [ 32 BINDINGS: ClassVar[list[BindingType]] = [
31 ("q", "quit", "Quit"), 33 ("q", "quit", "Quit"),
32 ("s", "show_scores", "Scores"), 34 ("s", "show_scores", "Scores"),
33 ("t", "show_standings", "Standings"), 35 ("t", "show_standings", "Standings"),
pyproject.toml +20
@@ -39,3 +39,23 @@ include = ["nba*"]
39 39
40[tool.setuptools.package-data] 40[tool.setuptools.package-data]
41"nba.tui" = ["*.tcss"] 41"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"]