diff --git a/pyproject.toml b/pyproject.toml index c80a9c2..f22370b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,28 +55,35 @@ subtle_gui = ["assets/*"] line-length = 120 [tool.ruff.lint] -preview = false - # Rules: https://docs.astral.sh/ruff/rules select = [ "A", # flake8-builtins (A) "ANN", # flake8-annotations (ANN) "B", # flake8-bugbear (B) + "C4", # flake8-comprehensions (C4) "D", # pydocstyle (D) "E", # pycodestyle (E) "F", # Pyflakes (F) "FA", # flake8-future-annotations (FA) + "G", # flake8-logging-format (G) + "I", # isort (I) + "ISC", # flake8-implicit-str-concat (ISC) + "LOG", # flake8-logging (LOG) "PERF", # Perflint (PERF) + "PIE", # flake8-pie (PIE) "PLC", # Pylint Convention (PLC) "PLE", # Pylint Error (PLE) "PLR17", # Refactor (PLR) - Only PLR17xx "PLW", # Pylint Warning (PLW) + "PT", # flake8-pytest-style (PT) "Q", # flake8-quotes (Q) "RET", # flake8-return (RET) "RUF", # Ruff-specific rules (RUF) + "SIM", # flake8-simplify (SIM) "SLF", # flake8-self (SLF) "SLOT", # flake8-slots (SLOT) "TC", # flake8-type-checking (TC) + "TID", # flake8-tidy-imports (TID) "UP", # pyupgrade (UP) "W", # pycodestyle (W) ] @@ -95,7 +102,7 @@ ignore = [ "RUF001", # ambiguous-unicode-character-string "RUF015", # unnecessary-iterable-allocation-for-first-element "RUF067", # non-empty-init-module - "RUF076", # pytest-fixture-autouse + "SIM108", # if-else-block-instead-of-if-exp "UP015", # redundant-open-modes "UP030", # format-literals ] @@ -103,6 +110,10 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "tests/*" = ["ANN", "D101", "D102", "D105", "PLC2701", "RUF069", "SLF", "TC"] +[tool.ruff.lint.isort] +lines-between-types = 1 +forced-separate = ["tests"] + [tool.ruff.format] quote-style = "double" diff --git a/subtle_gui/__init__.py b/subtle_gui/__init__.py index 16ab784..c24c5ed 100644 --- a/subtle_gui/__init__.py +++ b/subtle_gui/__init__.py @@ -28,11 +28,11 @@ from typing import TYPE_CHECKING +from PyQt6.QtWidgets import QApplication + from subtle_gui.config import Config from subtle_gui.shared import SharedData -from PyQt6.QtWidgets import QApplication - if TYPE_CHECKING: # pragma: no cover from subtle_gui.guimain import GuiMain @@ -40,14 +40,14 @@ # ============ # fmt: off -__package__ = "subtle_gui" -__copyright__ = "Copyright (C) Veronica Berglyd Olsen" -__license__ = "GPLv3" -__author__ = "Veronica Berglyd Olsen" +__package__ = "subtle_gui" +__copyright__ = "Copyright (C) Veronica Berglyd Olsen" +__license__ = "GPLv3" +__author__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen" -__email__ = "code@vkbo.net" -__version__ = "26.1.1" -__date__ = "2026-06-24" +__email__ = "code@vkbo.net" +__version__ = "26.1.1" +__date__ = "2026-06-24" # fmt: on logger = logging.getLogger(__name__) @@ -114,7 +114,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None: ) # Defaults - logLevel = logging.WARN + logLevel = logging.WARNING fmtColor = FORCE_COLOR fmtLong = False diff --git a/subtle_gui/common.py b/subtle_gui/common.py index af106d9..3c7c3c0 100644 --- a/subtle_gui/common.py +++ b/subtle_gui/common.py @@ -161,10 +161,9 @@ def decodeTS(value: str | None, default: int = 0, fmt: T_Subs = "SRT") -> int: return 3600000 * int(value[0:2]) + 60000 * int(value[3:5]) + int(value[6:8] + value[9:12]) except Exception: pass - elif fmt == "SSA" and len(value) == 10: - if value[1] == ":" and value[4] == ":" and value[7] in ":.,": - try: - return 3600000 * int(value[0]) + 60000 * int(value[2:4]) + 10 * int(value[5:7] + value[8:10]) - except Exception: - pass + elif fmt == "SSA" and len(value) == 10 and value[1] == ":" and value[4] == ":" and value[7] in ":.,": + try: + return 3600000 * int(value[0]) + 60000 * int(value[2:4]) + 10 * int(value[5:7] + value[8:10]) + except Exception: + pass return default diff --git a/subtle_gui/config.py b/subtle_gui/config.py index c4c2144..186ef3e 100644 --- a/subtle_gui/config.py +++ b/subtle_gui/config.py @@ -30,12 +30,12 @@ from pathlib import Path from typing import Literal -from subtle_gui.common import jsonEncode - from PyQt6.QtCore import PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QSize, QStandardPaths, QSysInfo from PyQt6.QtGui import QFont from PyQt6.QtWidgets import QApplication +from subtle_gui.common import jsonEncode + logger = logging.getLogger(__name__) diff --git a/subtle_gui/core/media.py b/subtle_gui/core/media.py index fa5bb05..82c5cff 100644 --- a/subtle_gui/core/media.py +++ b/subtle_gui/core/media.py @@ -25,6 +25,8 @@ from typing import TYPE_CHECKING +from PyQt6.QtCore import QObject, pyqtSignal + from subtle_gui import SHARED from subtle_gui.common import decodeTS from subtle_gui.constants import MediaType @@ -33,8 +35,6 @@ from subtle_gui.formats.srtsubs import SRTSubs from subtle_gui.formats.ssasubs import SSASubs -from PyQt6.QtCore import QObject, pyqtSignal - if TYPE_CHECKING: from collections.abc import Iterable from pathlib import Path diff --git a/subtle_gui/core/mkvextract.py b/subtle_gui/core/mkvextract.py index c94f6c4..75becf2 100644 --- a/subtle_gui/core/mkvextract.py +++ b/subtle_gui/core/mkvextract.py @@ -25,10 +25,10 @@ from typing import TYPE_CHECKING -from subtle_gui.common import checkInt - from PyQt6.QtCore import QObject, QProcess, pyqtSignal, pyqtSlot +from subtle_gui.common import checkInt + if TYPE_CHECKING: from pathlib import Path diff --git a/subtle_gui/core/spellcheck.py b/subtle_gui/core/spellcheck.py index 658a56d..fda260f 100644 --- a/subtle_gui/core/spellcheck.py +++ b/subtle_gui/core/spellcheck.py @@ -27,10 +27,10 @@ from pathlib import Path from typing import TYPE_CHECKING -from subtle_gui import CONFIG - from PyQt6.QtCore import QLocale +from subtle_gui import CONFIG + if TYPE_CHECKING: from collections.abc import Iterator diff --git a/subtle_gui/formats/pgssubs.py b/subtle_gui/formats/pgssubs.py index f7c6832..a8d9cf1 100644 --- a/subtle_gui/formats/pgssubs.py +++ b/subtle_gui/formats/pgssubs.py @@ -26,12 +26,12 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING -from subtle_gui.common import formatTS -from subtle_gui.formats.base import FrameBase, SubtitlesBase - from PyQt6.QtCore import QMargins, QPoint, QRect, QSize from PyQt6.QtGui import QColor, QImage, QPainter, qRgba +from subtle_gui.common import formatTS +from subtle_gui.formats.base import FrameBase, SubtitlesBase + if TYPE_CHECKING: from collections.abc import Iterable from pathlib import Path diff --git a/subtle_gui/gui/filetree.py b/subtle_gui/gui/filetree.py index bbaaab2..813c3c4 100644 --- a/subtle_gui/gui/filetree.py +++ b/subtle_gui/gui/filetree.py @@ -25,12 +25,12 @@ from pathlib import Path -from subtle_gui import CONFIG, SHARED - from PyQt6.QtCore import QModelIndex, pyqtSlot from PyQt6.QtGui import QFileSystemModel from PyQt6.QtWidgets import QTreeView, QWidget +from subtle_gui import CONFIG, SHARED + logger = logging.getLogger(__name__) @@ -71,10 +71,9 @@ def saveSettings(self) -> None: @pyqtSlot(QModelIndex) def _itemDoubleClicked(self, index: QModelIndex) -> None: """Process item double click in the file tree.""" - if (path := Path(self._model.filePath(index))).is_file(): - if path != self._current: - SHARED.media.loadMediaFile(path) - self._current = path + if (path := Path(self._model.filePath(index))).is_file() and path != self._current: + SHARED.media.loadMediaFile(path) + self._current = path @pyqtSlot(str) def _directoryLoaded(self, path: str) -> None: diff --git a/subtle_gui/gui/highlighter.py b/subtle_gui/gui/highlighter.py index abb214a..014804d 100644 --- a/subtle_gui/gui/highlighter.py +++ b/subtle_gui/gui/highlighter.py @@ -24,11 +24,11 @@ import logging import re -from subtle_gui import SHARED - from PyQt6.QtGui import QColor, QSyntaxHighlighter, QTextBlockUserData, QTextCharFormat, QTextDocument from PyQt6.QtWidgets import QApplication +from subtle_gui import SHARED + logger = logging.getLogger(__name__) SPELL_RX = re.compile(r"\b[^\s\-–—\/<>]+\b", re.UNICODE) diff --git a/subtle_gui/gui/imageviewer.py b/subtle_gui/gui/imageviewer.py index cad9b84..7486d45 100644 --- a/subtle_gui/gui/imageviewer.py +++ b/subtle_gui/gui/imageviewer.py @@ -23,12 +23,12 @@ import logging -from subtle_gui.formats.base import FrameBase - from PyQt6.QtCore import QRect, QRectF, Qt, pyqtSlot from PyQt6.QtGui import QPixmap, QResizeEvent from PyQt6.QtWidgets import QGraphicsScene, QGraphicsView, QHBoxLayout, QWidget +from subtle_gui.formats.base import FrameBase + logger = logging.getLogger(__name__) diff --git a/subtle_gui/gui/mediaview.py b/subtle_gui/gui/mediaview.py index 3d8b322..bc67215 100644 --- a/subtle_gui/gui/mediaview.py +++ b/subtle_gui/gui/mediaview.py @@ -26,12 +26,6 @@ from typing import TYPE_CHECKING -from subtle_gui import CONFIG, SHARED -from subtle_gui.common import formatTS -from subtle_gui.constants import GuiLabels, MediaType, trConst -from subtle_gui.core.mediafile import EXTRACTABLE, SUBTITLE_FILE -from subtle_gui.core.mkvextract import MkvExtract - from PyQt6.QtCore import QModelIndex, pyqtSlot from PyQt6.QtWidgets import ( QHBoxLayout, @@ -44,6 +38,12 @@ QWidget, ) +from subtle_gui import CONFIG, SHARED +from subtle_gui.common import formatTS +from subtle_gui.constants import GuiLabels, MediaType, trConst +from subtle_gui.core.mediafile import EXTRACTABLE, SUBTITLE_FILE +from subtle_gui.core.mkvextract import MkvExtract + if TYPE_CHECKING: from pathlib import Path @@ -183,12 +183,15 @@ def _itemDoubleClicked(self, index: QModelIndex) -> None: """Process track double click in the media view.""" if (file := SHARED.media.mediaFile) and (item := self.tracksView.itemFromIndex(index)): idx = item.text(self.C_TRACK) - if idx not in self._extracted: - if (track := SHARED.media.getTrack(idx)) and track.trackType == MediaType.SUBS: - path = file.dumpFile(idx) - track.setTrackFile(path) - self._runTrackExtraction(file.filePath, [(idx, path)]) - self._emitTrack = idx + if ( + idx not in self._extracted + and (track := SHARED.media.getTrack(idx)) + and track.trackType == MediaType.SUBS + ): + path = file.dumpFile(idx) + track.setTrackFile(path) + self._runTrackExtraction(file.filePath, [(idx, path)]) + self._emitTrack = idx if idx in self._extracted and not self._emitTrack: SHARED.media.setCurrentTrack(idx) diff --git a/subtle_gui/gui/subsview.py b/subtle_gui/gui/subsview.py index c749f09..eccbeb0 100644 --- a/subtle_gui/gui/subsview.py +++ b/subtle_gui/gui/subsview.py @@ -25,15 +25,15 @@ from pathlib import Path +from PyQt6.QtCore import QModelIndex, Qt, pyqtSignal, pyqtSlot +from PyQt6.QtWidgets import QAbstractItemView, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget + from subtle_gui import CONFIG, SHARED from subtle_gui.common import formatTS from subtle_gui.constants import MediaType from subtle_gui.formats.base import FrameBase from subtle_gui.formats.srtsubs import SRTSubs -from PyQt6.QtCore import QModelIndex, Qt, pyqtSignal, pyqtSlot -from PyQt6.QtWidgets import QAbstractItemView, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget - logger = logging.getLogger(__name__) @@ -157,15 +157,18 @@ def selectNearby(self, step: int) -> None: @pyqtSlot(QModelIndex) def _itemClicked(self, index: QModelIndex) -> None: """Process item click in the subtitles list.""" - if (track := SHARED.media.currentTrack) and (item := self.subEntries.itemFromIndex(index)): - if frame := SHARED.media.currentTrack.getFrame(item.data(self.C_DATA, self.D_INDEX)): - if frame.imageBased: - image = frame.getImage() - if (ocrTool := SHARED.ocr) and not (text := frame.text): - text = ocrTool.processImage(frame.index, image, [track.language]) - frame.setText(text) - self.updateText(frame) - self.subsFrameUpdated.emit(frame) + if ( + (track := SHARED.media.currentTrack) + and (item := self.subEntries.itemFromIndex(index)) + and (frame := SHARED.media.currentTrack.getFrame(item.data(self.C_DATA, self.D_INDEX))) + ): + if frame.imageBased: + image = frame.getImage() + if (ocrTool := SHARED.ocr) and not (text := frame.text): + text = ocrTool.processImage(frame.index, image, [track.language]) + frame.setText(text) + self.updateText(frame) + self.subsFrameUpdated.emit(frame) ## # Internal Functions diff --git a/subtle_gui/gui/texteditor.py b/subtle_gui/gui/texteditor.py index 78249c6..b08e19d 100644 --- a/subtle_gui/gui/texteditor.py +++ b/subtle_gui/gui/texteditor.py @@ -23,14 +23,14 @@ import logging -from subtle_gui import CONFIG, SHARED -from subtle_gui.formats.base import FrameBase -from subtle_gui.gui.highlighter import GuiDocHighlighter, TextBlockData - from PyQt6.QtCore import QPoint, Qt, pyqtSignal, pyqtSlot from PyQt6.QtGui import QShortcut, QTextBlock, QTextBlockFormat, QTextCharFormat, QTextCursor from PyQt6.QtWidgets import QComboBox, QMenu, QTextEdit, QToolBar, QToolButton, QVBoxLayout, QWidget +from subtle_gui import CONFIG, SHARED +from subtle_gui.formats.base import FrameBase +from subtle_gui.gui.highlighter import GuiDocHighlighter, TextBlockData + logger = logging.getLogger(__name__) diff --git a/subtle_gui/gui/toolspanel.py b/subtle_gui/gui/toolspanel.py index 5462b1d..15f1fd9 100644 --- a/subtle_gui/gui/toolspanel.py +++ b/subtle_gui/gui/toolspanel.py @@ -25,9 +25,6 @@ from pathlib import Path -from subtle_gui import SHARED -from subtle_gui.constants import MediaType - from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt6.QtWidgets import ( QCheckBox, @@ -43,6 +40,9 @@ QWidget, ) +from subtle_gui import SHARED +from subtle_gui.constants import MediaType + logger = logging.getLogger(__name__) @@ -198,9 +198,8 @@ def _clickedSaveSrt(self) -> None: @pyqtSlot() def _clickedLoadSubs(self) -> None: """Process load subs button click.""" - if items := self.subsList.selectedItems(): - if (path := Path(items[0].data(self.D_SUBS_PATH))).is_file(): - self.requestSubsLoad.emit(path) + if (items := self.subsList.selectedItems()) and (path := Path(items[0].data(self.D_SUBS_PATH))).is_file(): + self.requestSubsLoad.emit(path) @pyqtSlot() def _updateTrackInfo(self) -> None: diff --git a/subtle_gui/guimain.py b/subtle_gui/guimain.py index 3493e1c..2acb0c7 100644 --- a/subtle_gui/guimain.py +++ b/subtle_gui/guimain.py @@ -26,6 +26,10 @@ from typing import TYPE_CHECKING +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QIcon +from PyQt6.QtWidgets import QApplication, QMainWindow, QSplitter + from subtle_gui import CONFIG, SHARED from subtle_gui.core.icons import GuiIcons from subtle_gui.core.media import MediaData @@ -38,10 +42,6 @@ from subtle_gui.gui.toolspanel import GuiToolsPanel from subtle_gui.ocr.tesseract import TesseractOCR -from PyQt6.QtCore import Qt -from PyQt6.QtGui import QIcon -from PyQt6.QtWidgets import QApplication, QMainWindow, QSplitter - if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent diff --git a/subtle_gui/shared.py b/subtle_gui/shared.py index 49ef281..3d3494b 100644 --- a/subtle_gui/shared.py +++ b/subtle_gui/shared.py @@ -50,28 +50,28 @@ def __init__(self) -> None: @property def media(self) -> MediaData: - """Return the media data object.""" + """The media data object.""" if self._media is not None: return self._media raise RuntimeError("Shared data object not yet initialised.") @property def ocr(self) -> OCRBase: - """Return the ocr object.""" + """The OCR object.""" if self._ocr is not None: return self._ocr raise RuntimeError("Shared data object not yet initialised.") @property def spelling(self) -> SpellEnchant: - """Return the spell checker object.""" + """The spell checker object.""" if self._spell is not None: return self._spell raise RuntimeError("Shared data object not yet initialised.") @property def icons(self) -> GuiIcons: - """Return the spell checker object.""" + """The GUI icons object.""" if self._icons is not None: return self._icons raise RuntimeError("Shared data object not yet initialised.") diff --git a/uv.lock b/uv.lock index 1194cd3..6090825 100644 --- a/uv.lock +++ b/uv.lock @@ -234,15 +234,15 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.410" +version = "1.1.411" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/53/e4d8ea1391bd4355231be6f91bf239479aa0014260ed3fb5526eeb12a1f2/pyright-1.1.410.tar.gz", hash = "sha256:07a073b8ba6749826773c1269773efa11b93440d9a6aa60419d9a3172d6dc488", size = 4062013, upload-time = "2026-06-01T17:35:48.894Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/ab/265f7dc69d28113ebba19092e57b075f41543b2ed048429c5f56e2b88eac/pyright-1.1.411.tar.gz", hash = "sha256:d885a0551f2e763b089a02702174e7f4ba77548cddabc972ab86d1f7f1b0f998", size = 4112861, upload-time = "2026-06-25T02:14:06.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/33/288b5868fa00846dacf249633719d747893e54aebd196b9968ac1878a5d3/pyright-1.1.410-py3-none-any.whl", hash = "sha256:5e961bed37cacf96b3f7cd7b1da39b350a9239aa2e69138d0e88f728cfaf296c", size = 6082448, upload-time = "2026-06-01T17:35:46.387Z" }, + { url = "https://files.pythonhosted.org/packages/0a/49/385be530a6a5b78d1cbcd5c2e38debc8959a2fc6bdb716f4e581002979fc/pyright-1.1.411-py3-none-any.whl", hash = "sha256:dc7c72a8e2700c55baa127554040e067041ea53ccfd50bf96308cc4291c7d5d9", size = 6181526, upload-time = "2026-06-25T02:14:04.691Z" }, ] [[package]] @@ -289,27 +289,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.19" +version = "0.15.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/e6/15800dfde183a1a106594016c912b4c12d050a301989d1aca6cb63759fe8/ruff-0.15.19.tar.gz", hash = "sha256:edc27f7172a93b32b102687009d6a588508815072141543ae603a8b9b0823063", size = 4772071, upload-time = "2026-06-24T01:10:46.942Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/4c/9ded7626c39a0440c575bf69e2bf500d443388272c842662c59852ee7fcd/ruff-0.15.19-py3-none-linux_armv6l.whl", hash = "sha256:922d1eb283161564759bd49f507e91dc6112c15da8bd5b84ed714e086243cf86", size = 10950859, upload-time = "2026-06-24T01:10:38.491Z" }, - { url = "https://files.pythonhosted.org/packages/fb/ef/c211505ece1d00ef493d58e54e3b6383c946a21e9874774eb531f2512cf3/ruff-0.15.19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4d190d8f62a0b94aba8f721116538a9ee29b1e74d26650846ba9b99f0ae21c40", size = 11294529, upload-time = "2026-06-24T01:10:36.481Z" }, - { url = "https://files.pythonhosted.org/packages/fe/93/78d462e7d39968e58094dc57be7d09ffb14ce37da5b68ed70338a35a1f21/ruff-0.15.19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5a2c86ba6870dd415a9d9eb8be94d7924ebec6a26ffc7958ec7ca29d4bff967d", size = 10641416, upload-time = "2026-06-24T01:10:48.923Z" }, - { url = "https://files.pythonhosted.org/packages/76/c4/5cb66cfd1f865d5cca908b86c93ac785e7f572193d3c7426079ca6643e24/ruff-0.15.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b432bc087264aea70fd25ac198918b70bd9e2aa0db4297b0bb91bbfbbc63ce", size = 11015582, upload-time = "2026-06-24T01:10:30.089Z" }, - { url = "https://files.pythonhosted.org/packages/51/9f/8ecfaec10cf5eecd28fbc00ff4fb867db90a1be54bf3d39ebf93f893cd52/ruff-0.15.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8530a09d03b3a8c994f8b559a7dcdabc690bcd3f78ef276c38c83166798ebf56", size = 10744059, upload-time = "2026-06-24T01:10:32.48Z" }, - { url = "https://files.pythonhosted.org/packages/35/6b/983249d04562bc2d590edd75f32455cdb473affb3ba4bc8d883e939c697d/ruff-0.15.19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87bf21fb3875fe69f0eacc825411657e2e85589cce633c35c0adf1113649c62b", size = 11568461, upload-time = "2026-06-24T01:10:17.435Z" }, - { url = "https://files.pythonhosted.org/packages/eb/39/bc7794f127b18f492a3b4ee82bba5a900c985ff13b72b46f46e3c171ba34/ruff-0.15.19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9b229cb3ef56ecc2c1c8ebeca64b7a7740ccaef40a9eb097e78dde5a8560b83", size = 12429690, upload-time = "2026-06-24T01:10:40.638Z" }, - { url = "https://files.pythonhosted.org/packages/0a/3b/0de6859e698ed11c8a49e765196c8d333599b6a546c0715df39b6ba1aa2e/ruff-0.15.19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c754515be7b76afe6e7e62df7776709571bcfc1631183828afcf3bafa869e3", size = 11693067, upload-time = "2026-06-24T01:10:25.681Z" }, - { url = "https://files.pythonhosted.org/packages/89/3d/0b1f30f84bee9ae6ae8d349c2ba8b6f4b040966744efdd3acc804ae7c024/ruff-0.15.19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a498f82e0f4d8904c4e0aea5139cdfac1f39d19a3c51d491292f63a36e83b2e", size = 11616911, upload-time = "2026-06-24T01:10:44.809Z" }, - { url = "https://files.pythonhosted.org/packages/4d/eb/c90bd3dfc12eed9032c2c1bfe05105b93a1b2c8bce555db6308315b853ce/ruff-0.15.19-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:d48caa34488fb521fd0ef4aea2b0e8fe758298df044138f0d67b687a6a0d07ed", size = 11649343, upload-time = "2026-06-24T01:10:23.472Z" }, - { url = "https://files.pythonhosted.org/packages/82/91/01caa13602a2f12fae5edbe8caf78b3c1e6db1293132aee6959eecce095c/ruff-0.15.19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4171b6613effa9363cd46dd4f75bd1827b6d1b946b5e278ed0c600d305379445", size = 10977610, upload-time = "2026-06-24T01:10:50.892Z" }, - { url = "https://files.pythonhosted.org/packages/3c/51/acb817922feab9ecbb3201377d4dbe7a25f1395e46545820061973f03468/ruff-0.15.19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:27c15b2a241dd4d995557949a094fe78b8ad99122a38ccae1595849bcc947b3f", size = 10744900, upload-time = "2026-06-24T01:10:42.726Z" }, - { url = "https://files.pythonhosted.org/packages/84/bc/5c8ca46b8a7a3f2b16cfbec88721d772b1c93912904e8f8c2e49470fea63/ruff-0.15.19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ed03b7862d68f0a8771d50ee129980cbf1b113f96e250b73954bc292f689e0bb", size = 11293560, upload-time = "2026-06-24T01:10:21.262Z" }, - { url = "https://files.pythonhosted.org/packages/81/e0/4a888cbe4d5523b3f77a2b1fa043f46cfeba1b32eac35dcfadee0578fa8a/ruff-0.15.19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:08143f0685ae278b30727ea72e90c61e5bd9c31b91aac4f5bb989538f73d24b8", size = 11696533, upload-time = "2026-06-24T01:10:53.046Z" }, - { url = "https://files.pythonhosted.org/packages/98/43/c34b2fcd79262a85161764a97aaca89c3e4f574340ab61430cefa2bdd2c1/ruff-0.15.19-py3-none-win32.whl", hash = "sha256:8f47f0f92952af2557212bb10cf3e695cd4cf28b2c6e42cdb18ec6c9ebfa19da", size = 10986299, upload-time = "2026-06-24T01:10:55.185Z" }, - { url = "https://files.pythonhosted.org/packages/22/e8/15fd23e02b2442b56b2026b455977bc3057aa34b26e6323d1e99e8531a9f/ruff-0.15.19-py3-none-win_amd64.whl", hash = "sha256:efeca47ee3f9d4a7162655a3b8e6ee4a878646044233978d4d2c1ff8cdd914f0", size = 12123473, upload-time = "2026-06-24T01:10:27.74Z" }, - { url = "https://files.pythonhosted.org/packages/30/66/9a73695e31eaee04f35d8475998bf8ab354465f9c638936d76111603dcc5/ruff-0.15.19-py3-none-win_arm64.whl", hash = "sha256:6c6b607466e47349332eb1d9be52fb1467423fc07c217341af41cd0f3f0573be", size = 11376779, upload-time = "2026-06-24T01:10:34.465Z" }, + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, ] [[package]]