mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 21:07:03 +01:00
`requires-python` and the CI pin already said 3.14, but ruff, mypy and pylint were still grading `meta/` against 3.10 -- three tools judging an interpreter nothing runs any more, and ruff withholding the pyupgrade fixes that only become safe at the newer target. `pin_ci_python.py` rewrites these three knobs. What the stale target was hiding: `datetime.timezone.utc` where `datetime.UTC` is now correct (UP017), and ruff's stable formatter applying PEP 758 -- `except (A, B):` becomes `except A, B:`. That makes the source 3.14-only at the syntax level, which is what `requires-python = ">=3.14"` already promised; kuhy chose it deliberately over holding ruff a release back (2026-08-29). Also drops a comment in `wsg_grabber/models.py` that the bump makes false: it explained why the file used ``str, Enum`` instead of ``StrEnum`` under mypy at 3.10, sitting directly above a ``class Verdict(StrEnum)`` that had already stopped obeying it. ruff, ruff-format, mypy --strict and pylint all clean at the new target. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SfnJmxxsZ534AKk16rC2Gr
206 lines
4.4 KiB
Python
206 lines
4.4 KiB
Python
"""Plain-data records shared across the package.
|
|
|
|
Everything crossing a module boundary is a frozen dataclass or an enum, so the
|
|
pure planning code can be driven from hand-built literals and the GUI never has
|
|
to be constructed to test a decision.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from enum import StrEnum
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
class Verdict(StrEnum):
|
|
"""What the user decided about a video.
|
|
|
|
The member is ``SKIP`` rather than ``PASS`` only because ruff's S105 reads a
|
|
name of ``PASS`` as a hardcoded password and this repo forbids ``noqa``. The
|
|
stored value stays ``"pass"``, which is the word the UI shows.
|
|
"""
|
|
|
|
KEEP = "keep"
|
|
SKIP = "pass"
|
|
|
|
|
|
class TaskKind(StrEnum):
|
|
"""What the background worker should do next."""
|
|
|
|
DOWNLOAD = "download"
|
|
SCAN = "scan"
|
|
IDLE = "idle"
|
|
|
|
|
|
class Emptiness(StrEnum):
|
|
"""Why the reviewer has nothing to show."""
|
|
|
|
NOT_EMPTY = "not_empty"
|
|
WAITING = "waiting"
|
|
EXHAUSTED = "exhausted"
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class Size:
|
|
"""A pixel size."""
|
|
|
|
width: int
|
|
height: int
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ThreadRef:
|
|
"""A thread as advertised by ``threads.json``."""
|
|
|
|
thread_no: int
|
|
api_last_modified: int
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RemoteFile:
|
|
"""An attachment described by a post, before anything is downloaded.
|
|
|
|
``md5`` is the API's own ``base64(md5(file_bytes))`` and is the identity of
|
|
the file everywhere in this package.
|
|
"""
|
|
|
|
md5: str
|
|
tim: int
|
|
ext: str
|
|
orig_name: str
|
|
fsize: int
|
|
width: int
|
|
height: int
|
|
thread_no: int
|
|
post_no: int
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ReviewItem:
|
|
"""A downloaded video waiting for a verdict."""
|
|
|
|
md5: str
|
|
path: Path
|
|
orig_name: str
|
|
fsize: int
|
|
width: int
|
|
height: int
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class FileMove:
|
|
"""A planned relocation. Applying it is the only filesystem write."""
|
|
|
|
md5: str
|
|
src: Path
|
|
dst: Path
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ReviewedItem:
|
|
"""A verdict recorded in the index, and therefore reversible.
|
|
|
|
``reviewed_name`` is the name the file was given in keep/ or trash/, which
|
|
differs from ``item.path.name`` whenever a collision forced a rename. Undo
|
|
needs it to find the file again, which is why it is persisted rather than
|
|
held in memory.
|
|
"""
|
|
|
|
item: ReviewItem
|
|
choice: Verdict
|
|
reviewed_name: str
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ResumePlan:
|
|
"""How to continue a partial download."""
|
|
|
|
offset: int
|
|
discard: bool
|
|
|
|
|
|
class Outcome(StrEnum):
|
|
"""How a download ended."""
|
|
|
|
COMPLETED = "completed"
|
|
NOT_FOUND = "not_found"
|
|
TRANSIENT = "transient"
|
|
CHECKSUM_MISMATCH = "checksum_mismatch"
|
|
ABORTED = "aborted"
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class JsonResponse:
|
|
"""The result of a conditional GET against the API."""
|
|
|
|
payload: object | None
|
|
last_modified: str | None
|
|
not_modified: bool
|
|
not_found: bool
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class DownloadResult:
|
|
"""The result of streaming one file to disk."""
|
|
|
|
outcome: Outcome
|
|
bytes_done: int
|
|
retry_after: float | None = None
|
|
message: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ReviewCommand:
|
|
"""Everything the GUI must render or do, already decided.
|
|
|
|
The window applies this verbatim; it never formats a string or picks a file.
|
|
"""
|
|
|
|
play: Path | None
|
|
stop: bool
|
|
status: str
|
|
title: str
|
|
filename: str
|
|
verdicts_enabled: bool
|
|
undo_enabled: bool
|
|
quit_app: bool
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class FileReady:
|
|
"""A newly downloaded video is available for review."""
|
|
|
|
item: ReviewItem
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class Indexed:
|
|
"""The catalogue scan learned about more files."""
|
|
|
|
known: int
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class Downloaded:
|
|
"""Running count of files fetched this session."""
|
|
|
|
count: int
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ScanFinished:
|
|
"""The board has been walked and every known file downloaded."""
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class WorkerFailed:
|
|
"""The background worker died; the message is shown in the status bar."""
|
|
|
|
message: str
|
|
|
|
|
|
DownloadEvent = FileReady | Indexed | Downloaded | ScanFinished | WorkerFailed
|