import importlib import json import pkgutil from types import ModuleType from typing import Any, Iterable, TypedDict, cast from PyQt6.QtCore import Qt, pyqtSlot from PyQt6.QtSql import QSqlQuery from PyQt6.QtWidgets import QScrollArea from trycast import trycast import plugins from lib.definition import Definition, Line from lib.sounds import SoundOff from lib.utils import query_error def find_plugins(ns_pkg: ModuleType) -> Iterable[pkgutil.ModuleInfo]: return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".") discovered_plugins = { # finder, name, ispkg importlib.import_module(name).registration[ "source" ]: importlib.import_module(name) for _, name, _ in find_plugins(plugins) } API = "https://api.dictionaryapi.dev/api/v2/entries/en/{word}" class WordType(TypedDict): word: str source: str definition: str class Word: """All processing of a dictionary word.""" _words: dict[str, WordType] = {} _valid = False def __init__(self, word: str) -> None: # # Have we already retrieved this word? # try: self.current = Word._words[word] return except KeyError: pass query = QSqlQuery() query.prepare("SELECT * FROM words " "WHERE word = :word") query.bindValue(":word", word) if not query.exec(): query_error(query) if query.next(): Word._words[word] = { "word": word, "source": query.value("source"), "definition": json.loads(query.value("definition")), } self.current = Word._words[word] self._valid = True return # # The code should look at our settings to see if we have an API # key for MW to decide on the source to use. # source = "mw" self._words[word] = discovered_plugins[source].fetch(word) if self._words[word] is None: self._valid = False return self.current = Word._words[word] query.prepare( "INSERT INTO words " "(word, source, definition) " "VALUES (:word, :source, :definition)" ) query.bindValue(":word", self.current["word"]) query.bindValue(":source", self.current["source"]) query.bindValue(":definition", json.dumps(self.current["definition"])) if not query.exec(): query_error(query) self._valid = True return def isValid(self) -> bool: return self._valid @pyqtSlot() def playSound(self) -> None: url = discovered_plugins[self.current["source"]].getFirstSound( self.current["definition"] ) if url.isValid(): snd = SoundOff() snd.playSound(url) return def playPRS(self) -> None: return def getWord(self) -> str: return self.current["word"] def get_html(self) -> str | None: src = self.current["source"] try: return cast(str, discovered_plugins[src].getHtml(self.current)) except KeyError: raise Exception(f"Unknown source: {src}") def get_def(self) -> list[Line]: src = self.current["source"] try: lines = discovered_plugins[src].getDef(self.current["definition"]) lines = trycast(list[Line], lines) assert lines is not None return lines except KeyError: raise Exception(f"Unknown source: {self.current['source']}")