Files
esl-reader/lib/words.py
2024-05-07 11:26:15 -04:00

118 lines
3.3 KiB
Python

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] = {}
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]
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)
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)
return
@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']}")