From 61ebe6cc0b1ecb332a394a331d2f14c2df927530 Mon Sep 17 00:00:00 2001 From: "Christopher T. Johnson" Date: Sun, 4 Feb 2024 15:06:07 -0500 Subject: [PATCH] Lint picking --- lib/person.py | 27 +++++----- lib/preferences.py | 25 +++++----- lib/read.py | 120 ++++++++++++++++++--------------------------- lib/session.py | 28 ++++++----- lib/sounds.py | 22 +++++---- main.py | 9 ++-- 6 files changed, 112 insertions(+), 119 deletions(-) diff --git a/lib/person.py b/lib/person.py index 8a29a39..adf8fe5 100644 --- a/lib/person.py +++ b/lib/person.py @@ -7,6 +7,7 @@ from email import policy from email.message import EmailMessage from html.parser import HTMLParser from io import StringIO +from typing import Any, List import css_inline from PyQt6.QtCore import QResource, QSize, Qt, QUrl, pyqtSlot @@ -21,7 +22,7 @@ from ui.PeopleDialog import Ui_Dialog class blockHandler(HTMLParser): text = "" - blocks = [] + blocks: List[str] = [] active = 0 tags = [ "h1", @@ -39,7 +40,7 @@ class blockHandler(HTMLParser): ] space = ["b", "i", "em", "st", "span"] - def __init__(self): + def __init__(self) -> None: super().__init__() self.reset() self.strict = False @@ -49,7 +50,7 @@ class blockHandler(HTMLParser): self.active = 0 return - def handle_starttag(self, tag, attrs): + def handle_starttag(self, tag: str, attrs: Any) -> None: if not tag in self.tags: return self.active += 1 @@ -58,7 +59,7 @@ class blockHandler(HTMLParser): self.text += f"<{tag}>" return - def handle_endtag(self, tag): + def handle_endtag(self, tag: str) -> None: if not tag in self.tags: return self.active -= 1 @@ -71,21 +72,21 @@ class blockHandler(HTMLParser): self.active = 0 return - def handle_data(self, data): + def handle_data(self, data: str) -> None: self.text += data return - def get_block(self, block): + def get_block(self, block: int) -> str: return self.blocks[block] class MLStripper(HTMLParser): - def __init__(self): + def __init__(self) -> None: super().__init__() self.reset() return - def reset(self): + def reset(self) -> None: super().reset() self.strict = False self.convert_charrefs = True @@ -93,13 +94,13 @@ class MLStripper(HTMLParser): self.first = True return - def handle_data(self, d): + def handle_data(self, d: str) -> None: if self.first: self.text.write(d) self.first = False return - def get_data(self): + def get_data(self) -> str: return self.text.getvalue() @@ -110,7 +111,7 @@ class PersonDialog(QDialog, Ui_Dialog): person_id = 0 inliner = css_inline.CSSInliner(keep_style_tags=True, keep_link_tags=True) - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: self.person_id = kwargs.pop("person_id", 0) super(PersonDialog, self).__init__(*args, **kwargs) self.setupUi(self) @@ -128,7 +129,7 @@ class PersonDialog(QDialog, Ui_Dialog): self.bookCombo.setModel(model) self.bookCombo.setModelColumn(1) self.bookCombo.setCurrentIndex(-1) - model = QStandardItemModel() + model: QStandardItemModel = QStandardItemModel() # type: ignore[no-redef] self.sectionCombo.setPlaceholderText("Select A Section") self.sectionCombo.setModel(model) self.sectionCombo.setEnabled(False) @@ -349,7 +350,7 @@ class PersonDialog(QDialog, Ui_Dialog): return @pyqtSlot() - def checkLineEdits(self): + def checkLineEdits(self) -> None: name = self.nameEdit.text().strip() org = self.orgEdit.text().strip() button = self.buttonBox.button(QDialogButtonBox.StandardButton.Ok) diff --git a/lib/preferences.py b/lib/preferences.py index 779def1..451acba 100644 --- a/lib/preferences.py +++ b/lib/preferences.py @@ -1,41 +1,42 @@ import json import os +from typing import Any, Dict, List, Optional, Type, cast from PyQt6.QtCore import Qt, pyqtSlot from PyQt6.QtMultimedia import QMediaDevices -from PyQt6.QtWidgets import QAbstractItemView, QDialog, QListWidgetItem +from PyQt6.QtWidgets import QAbstractItemView, QDialog, QListWidgetItem, QWidget from ui.Preferences import Ui_Dialog class Preferences(QDialog, Ui_Dialog): _instance = None + preferences: Dict[str, str | List[str]] - def __new__(cls): + def __new__(cls: Type[Preferences]) -> Preferences: if cls._instance: return cls._instance cls._instance = super(Preferences, cls).__new__(cls) return cls._instance @pyqtSlot(int) - def done(self, r): + def done(self, r: int) -> None: self.hide() super().done(r) return @pyqtSlot() - def exec(self): + def exec(self) -> int: self.show() - super().exec() - return + return super().exec() @pyqtSlot() - def open(self): + def open(self) -> None: self.show() super().open() return - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(Preferences, self).__init__(*args, **kwargs) self.setupUi(self) self.hide() @@ -61,7 +62,7 @@ class Preferences(QDialog, Ui_Dialog): self.setCurrent() return - def setCurrent(self): + def setCurrent(self) -> None: if os.path.exists("preferences.json"): with open("preferences.json", "r") as f: self.preferences = json.load(f) @@ -94,12 +95,14 @@ class Preferences(QDialog, Ui_Dialog): self.phoneticsCombo.setCurrentIndex(index) return - def get(self, name: str = None): + def get( + self, name: Optional[str] = None + ) -> str | List[str] | Dict[str, str | List[str]]: if not name: return self.preferences return self.preferences[name] - def accept(self): + def accept(self) -> None: self.preferences["readerFont"] = self.readerCombo.currentFont().family() self.preferences[ "phoneticFont" diff --git a/lib/read.py b/lib/read.py index 469e5eb..d407e50 100644 --- a/lib/read.py +++ b/lib/read.py @@ -1,6 +1,6 @@ import json import re -from typing import cast +from typing import Any, Dict, List, Optional, cast import requests from PyQt6.QtCore import ( @@ -30,35 +30,38 @@ from PyQt6.QtGui import ( QTextListFormat, ) from PyQt6.QtSql import QSqlDatabase, QSqlQuery, QSqlQueryModel -from PyQt6.QtWidgets import QDialog, QPushButton +from PyQt6.QtWidgets import QDialog, QPushButton, QTextEdit, QWidget +from lib.preferences import Preferences +from lib.session import SessionDialog +from lib.sounds import SoundOff from main import query_error from ui.ReadDialog import Ui_Dialog -from lib.preferences import Preferences -from lib.sounds import SoundOff + class ReadDialog(QDialog, Ui_Dialog): playSound = pyqtSignal(str) playAlert = pyqtSignal() block: int + preferences: Dict[str, str | List[str]] paragraphs = True sessionSignal = pyqtSignal() displayedWord = pyqtSignal(int) newParagraph = pyqtSignal(int, int) - - def __init__(self, parent, session, person_id: int) -> None: + + def __init__( + self, parent: Optional[QWidget], session: SessionDialog, person_id: int + ) -> None: self.session = session super(ReadDialog, self).__init__(parent) self.person_id = person_id - self.preferences = Preferences().get() + self.preferences = cast(Dict[str, str | List[str]], Preferences().get()) self.sound = SoundOff() styleSheet = QResource(":/display.css").data().decode("utf-8") - styleSheet = styleSheet.replace( - '{readerFont}',self.preferences['readerFont'] - ) - styleSheet = styleSheet.replace( - '{phoneticFont}',self.preferences['phoneticFont'] - ) + readerFont = cast(str, self.preferences["readerFont"]) + phoneticFont = cast(str, self.preferences["phoneticFont"]) + styleSheet = styleSheet.replace("{readerFont}", readerFont) + styleSheet = styleSheet.replace("{phoneticFont}", phoneticFont) self.setupUi(self) # # Override UI @@ -88,7 +91,7 @@ class ReadDialog(QDialog, Ui_Dialog): self.prevBtn.clicked.connect(self.prevAction) self.sessionBtn.clicked.connect(self.timerAction) self.paraEdit.verticalScrollBar().valueChanged.connect(self.scrollSlot) - #self.defEdit.selectionChanged.connect(self.recursiveDef) + # self.defEdit.selectionChanged.connect(self.recursiveDef) self.returnBtn.clicked.connect(self.returnAction) # # Connect signals @@ -107,16 +110,17 @@ class ReadDialog(QDialog, Ui_Dialog): # slots # @pyqtSlot() - def timerAction(self): - if self.session.isActive(): # We are stopping + def timerAction(self) -> None: + if self.session.isActive(): # We are stopping self.sessionBtn.setText("Start") else: self.sessionBtn.setText("Stop") self.session.timerAction() self.newParagraph.emit(self.section_id, self.block) return + @pyqtSlot() - def recursiveDef(self): + def recursiveDef(self) -> None: cursor = self.defEdit.textCursor() selection = cursor.selectedText().strip() if len(selection) <= 0: @@ -125,7 +129,7 @@ class ReadDialog(QDialog, Ui_Dialog): query.prepare("SELECT * FROM words " "WHERE word = :word") query.bindValue(":word", selection) if not query.exec(): - query_error() + query_error(query) if not query.next(): response = requests.get( f"https://api.dictionaryapi.dev/api/v2/entries/en/{selection}" @@ -138,13 +142,13 @@ class ReadDialog(QDialog, Ui_Dialog): else: definition = query.value("definition") word_id = query.value("word_id") - self.setDefEdit(selection, word_id, definition) + self.setDefEdit(selection, word_id, definition) return @pyqtSlot() def sessionAction(self) -> None: self.sessionSignal.emit() - self.session.addParagraph(self.section_id, self.block) + self.newParagraph.emit(self.section_id, self.block) return @pyqtSlot() @@ -248,10 +252,7 @@ class ReadDialog(QDialog, Ui_Dialog): @pyqtSlot() def nextAction(self) -> None: - if self.stackedWidget.currentIndex() == 1: - print("Next Definition") - self.nextDefinition() - elif self.paragraphs: + if self.paragraphs: self.nextParagraph() else: self.nextSection() @@ -259,15 +260,15 @@ class ReadDialog(QDialog, Ui_Dialog): @pyqtSlot() def prevAction(self) -> None: - if self.stackedWidget.currentIndex() == 1: - print("Previous Definition") - self.prevDefinition() - elif self.paragraphs: + if self.paragraphs: self.prevParagraph() else: self.prevSection() return + # + # Called when the "define" button is pressed + # @pyqtSlot() def defineAction(self) -> None: editor = self.paraEdit @@ -277,10 +278,10 @@ class ReadDialog(QDialog, Ui_Dialog): self.showDefinition() return - def defToHtml(self, word: str, definition) -> str: + def defToHtml(self, word: str, definition: Dict[str, Any]) -> str: html = f'

{word}

' + "\n" try: - words = [] + words: List[str] = [] for phonetic in definition["phonetics"]: if phonetic["text"] in words: continue @@ -389,7 +390,7 @@ class ReadDialog(QDialog, Ui_Dialog): # # Event handlers # - def keyReleaseEvent(self, event: QKeyEvent) -> None: + def keyReleaseEvent(self, event: Optional[QKeyEvent]) -> None: self.nextBtn.setText("Next Para") self.prevBtn.setText("Prev Para") self.defineBtn.setText("Show Def") @@ -397,7 +398,7 @@ class ReadDialog(QDialog, Ui_Dialog): super().keyReleaseEvent(event) return - def keyPressEvent(self, event: QKeyEvent) -> None: + def keyPressEvent(self, event: Optional[QKeyEvent]) -> None: self.nextBtn.setText("Next Sect") self.prevBtn.setText("Prev Sect") self.defineBtn.setText("Add Def") @@ -447,7 +448,7 @@ class ReadDialog(QDialog, Ui_Dialog): self.savePosition() return - def addWord(self,editor) -> None: + def addWord(self, editor: QTextEdit) -> None: # # Find the word # @@ -470,7 +471,9 @@ class ReadDialog(QDialog, Ui_Dialog): # # Find the block # - textBlock = editor.document().findBlock(cursor.position()) + document = editor.document() + assert document is not None + textBlock = document.findBlock(cursor.position()) blockNum = textBlock.blockNumber() start = start - textBlock.position() end = end - textBlock.position() @@ -490,7 +493,7 @@ class ReadDialog(QDialog, Ui_Dialog): f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}" ) if response.status_code != 200: - print(f"{word}: {response.content}") + print(f"{word}: {response.content.decode('utf8')}") self.playAlert.emit() return definitions = json.loads(response.content.decode("utf-8")) @@ -551,6 +554,10 @@ class ReadDialog(QDialog, Ui_Dialog): return # XXX - rename + # + # Create a definition for the word under the cursor on the current + # panel. + # def display_definition(self, idx: int) -> bool: if idx == 0: editor = self.paraEdit @@ -559,14 +566,11 @@ class ReadDialog(QDialog, Ui_Dialog): cursor = editor.textCursor() cursor.select(QTextCursor.SelectionType.WordUnderCursor) word = cursor.selectedText() - fmt = cursor.charFormat() - if not fmt.fontUnderline(): - self.addWord(editor) + # fmt = cursor.charFormat() + # if not fmt.fontUnderline(): + # self.addWord(editor) query = QSqlQuery() - query.prepare( - "SELECT w.* FROM words w " - "WHERE word = :word" - ) + query.prepare("SELECT w.* FROM words w " "WHERE word = :word") query.bindValue(":word", word) if not query.exec(): query_error(query) @@ -577,7 +581,9 @@ class ReadDialog(QDialog, Ui_Dialog): self.setDefEdit(word, query.value("word_id"), definition) return True - def setDefEdit(self, word, word_id, definition): + def setDefEdit( + self, word: str, word_id: int, definition: Dict[str, str] + ) -> None: if "phonetics" in definition: self.phonetics = definition["phonetics"] else: @@ -615,34 +621,6 @@ class ReadDialog(QDialog, Ui_Dialog): self.update() return - def nextDefinition(self) -> None: - cursor = self.paraEdit.textCursor() - formats = self.paraEdit.document().allFormats() - found = None - for f in formats: - wc = QTextCursor(cursor) - wc.setPosition(f.start) - wc.movePosition( - QTextCursor.MoveOperation.Right, - QTextCursor.MoveMode.KeepAnchor, - f.length, - ) - word = wc.selectedText() - cf = wc.charFormat() - if f.start <= position: - continue - if not cf.fontUnderline(): - continue - if not found: - found = f - elif f.start < found.start: - found = f - if found: - cursor.setPosition(found.start) - self.paraEdit.setTextCursor(cursor) - self.display_definition(0) - return - def scrollTo(self, position: int) -> None: cursor = self.paraEdit.textCursor() cursor.setPosition(position) diff --git a/lib/session.py b/lib/session.py index 3923f55..33170ec 100644 --- a/lib/session.py +++ b/lib/session.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from typing import Optional, cast from PyQt6.QtCore import QModelIndex, Qt, QTime, QTimer, pyqtSignal, pyqtSlot from PyQt6.QtGui import ( @@ -11,7 +12,7 @@ from PyQt6.QtGui import ( QTextDocument, ) from PyQt6.QtSql import QSqlQuery -from PyQt6.QtWidgets import QDialog +from PyQt6.QtWidgets import QCheckBox, QDialog, QListView, QMessageBox from main import query_error from ui.SessionDialog import Ui_Dialog @@ -25,7 +26,7 @@ class SessionDialog(QDialog, Ui_Dialog): timer = QTimer() startTime = datetime.now() - totalTime = 0 # seconds + totalTime = timedelta(seconds=0) sessionStart = None sessionEnd = None blocks = QStandardItemModel() @@ -49,8 +50,11 @@ class SessionDialog(QDialog, Ui_Dialog): flag = index.data(SessionDialog.WordImportantRole) flag = 1 - flag model = index.model() + assert model is not None + model = cast(QStandardItemModel, model) model.setData(index, flag, SessionDialog.WordImportantRole) item = model.itemFromIndex(index) + assert item is not None if flag: item.setForeground(Qt.GlobalColor.red) else: @@ -62,13 +66,13 @@ class SessionDialog(QDialog, Ui_Dialog): @pyqtSlot() def resetForm(self) -> None: self.nameLbl.setText("") - self.totalTime = timedelta() + self.totalTime = timedelta(seconds=0) self.wordView.model().clear() self.textBrowser.document().clear() return @pyqtSlot(int) - def setPerson(self, person_id) -> None: + def setPerson(self, person_id: int) -> None: self.resetForm() self.person_id = person_id query = QSqlQuery() @@ -79,12 +83,12 @@ class SessionDialog(QDialog, Ui_Dialog): if not query.next(): raise Exception(f"Bad person_id: {person_id}") self.nameLbl.setText(query.value("name")) - self.totalTime = timedelta() + self.totalTime = timedelta(seconds=0) self.wordView.model().clear() return @pyqtSlot() - def rejected(self) -> None: + def rejected(self): # type: ignore[no-untyped-def] msg = QMessageBox() msg.setText("There is unsaved data.") msg.setInformativeText("Do you want to save the session?") @@ -100,7 +104,7 @@ class SessionDialog(QDialog, Ui_Dialog): super().reject() return self.accept() - self.done() + self.done(QDialog.DialogCode.Accepted) return @pyqtSlot() @@ -164,7 +168,8 @@ class SessionDialog(QDialog, Ui_Dialog): @pyqtSlot() def tickAction(self) -> None: - delta = self.totalTime + (datetime.now() - self.startTime) + td = datetime.now() - self.startTime + delta = self.totalTime + td seconds = delta.seconds % 60 minutes = int(delta.seconds / 60) % 60 hours = int(delta.seconds / 3600) @@ -211,7 +216,7 @@ class SessionDialog(QDialog, Ui_Dialog): return @pyqtSlot(int, int) - def addBlock(self, section_id, block) -> None: + def addBlock(self, section_id: int, block: int) -> None: if not self.activeBox.isChecked(): return new_block = QStandardItem() @@ -265,5 +270,6 @@ class SessionDialog(QDialog, Ui_Dialog): # # End Slots # - def isActive(self): - return self.activeBox.isChecked() + def isActive(self) -> bool: + active: bool = self.activeBox.isChecked() + return active diff --git a/lib/sounds.py b/lib/sounds.py index c363c1f..87af0a2 100644 --- a/lib/sounds.py +++ b/lib/sounds.py @@ -1,3 +1,5 @@ +from typing import Optional, Type, cast + from PyQt6.QtCore import QObject, Qt, QUrl, pyqtSlot from PyQt6.QtMultimedia import ( QAudioDevice, @@ -13,13 +15,13 @@ from PyQt6.QtMultimedia import ( class SoundOff(QObject): _instance = None - def __new__(cls): + def __new__(cls: Type[SoundOff]) -> SoundOff: if cls._instance: return cls._instance cls._instance = super(SoundOff, cls).__new__(cls) return cls._instance - def __init__(self): + def __init__(self) -> None: super().__init__() # # Setup devices @@ -61,36 +63,38 @@ class SoundOff(QObject): self.virtualPlayer.playbackStateChanged.connect(self.playbackState) @pyqtSlot() - def alert(self): + def alert(self) -> None: self.alertEffect.play() return @pyqtSlot(QMediaPlayer.Error, str) - def mediaError(self, error, string): + def mediaError(self, error: QMediaPlayer.Error, string: str) -> None: print(error) print(str) return @pyqtSlot(QMediaPlayer.MediaStatus) - def mediaStatus(self, status): + def mediaStatus(self, status: QMediaPlayer.MediaStatus) -> None: if status == QMediaPlayer.MediaStatus.LoadedMedia: - self.sender().play() + player: Optional[QMediaPlayer] = cast(QMediaPlayer, self.sender()) + assert player is not None + player.play() return @pyqtSlot(QMediaPlayer.PlaybackState) - def playbackState(self, state): + def playbackState(self, state: QMediaPlayer.PlaybackState) -> None: return # # Communications slots # @pyqtSlot() - def soundAlert(self): + def soundAlert(self) -> None: self.alertEffect.play() return @pyqtSlot(str) - def playSound(self, url): + def playSound(self, url: str) -> None: src = QUrl(url) if not self.localPlayer.audioOutput(): self.localPlayer.setAudioOutput(self.localOutput) diff --git a/main.py b/main.py index f062455..1daeb6b 100755 --- a/main.py +++ b/main.py @@ -16,7 +16,7 @@ import os import re import sys from datetime import datetime, timedelta -from typing import cast +from typing import Optional from PyQt6.QtCore import ( QModelIndex, @@ -98,7 +98,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): return @pyqtSlot() - def editPreferences(self): + def editPreferences(self) -> None: dlg = Preferences() dlg.exec() return @@ -134,12 +134,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): @pyqtSlot() @pyqtSlot(QModelIndex) - def editPerson(self, index=None) -> None: + def editPerson(self, index: Optional[QModelIndex] = None) -> None: if not index: indexes = self.peopleView.selectedIndexes() if len(indexes) < 1: return index = indexes[0] + assert index is not None dlg = PersonDialog(person_id=index.siblingAtColumn(0).data()) dlg.exec() return @@ -324,7 +325,7 @@ if __name__ == "__main__": uiName = "ui/" + fileName[:-3] + ".ui" rccName = "ui/" + fileName[:-3] + ".qrc" if not os.path.isfile(uiName) and not os.path.isfile(rccName): - outOfDate.append(filenName) + outOfDate.append(fileName) continue if os.path.isfile(uiName) and os.path.getmtime( uiName