From dd3180a04980a2921af5dd69cc055e04b010362b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:17:07 +0000 Subject: [PATCH 1/5] Initial plan From d9e6b124bfd4b3eafe5ef5d6652edcafb3ac5dfc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:29:48 +0000 Subject: [PATCH 2/5] Fix type hints in corpus core and common modules Co-authored-by: bact <128572+bact@users.noreply.github.com> --- pythainlp/corpus/common.py | 8 +++++--- pythainlp/corpus/core.py | 32 ++++++++++++++++---------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/pythainlp/corpus/common.py b/pythainlp/corpus/common.py index d9f8f33c1..275750716 100644 --- a/pythainlp/corpus/common.py +++ b/pythainlp/corpus/common.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Union + from typing import Any, Union __all__ = [ "countries", @@ -384,10 +384,12 @@ def find_synonyms(word: str) -> list[str]: # output: ['จรุก', 'วราหะ', 'วราห์', 'ศูกร', 'สุกร'] """ synonyms = thai_synonyms() # get a dictionary of {word, synonym} - list_synonym = [] + list_synonym: list[Any] = [] if word in synonyms["word"]: # find by word - list_synonym.extend(synonyms["synonym"][synonyms["word"].index(word)]) + word_list = synonyms["word"] + assert isinstance(word_list, list) + list_synonym.extend(synonyms["synonym"][word_list.index(word)]) # type: ignore[arg-type] for idx, words in enumerate(synonyms["synonym"]): # find by synonym if word in words: diff --git a/pythainlp/corpus/core.py b/pythainlp/corpus/core.py index 22ca034e6..cbf8e4d34 100644 --- a/pythainlp/corpus/core.py +++ b/pythainlp/corpus/core.py @@ -20,7 +20,7 @@ from pythainlp.tools import get_full_data_path if TYPE_CHECKING: - from typing import Optional + from typing import Any, Optional _CHECK_MODE = os.getenv("PYTHAINLP_READ_MODE") _USER_AGENT = ( @@ -38,10 +38,10 @@ def __init__(self, response: HTTPResponse) -> None: self.headers = response.headers self._content = response.read() - def json(self) -> dict[str, str]: + def json(self) -> dict[str, Any]: """Parse JSON content from response.""" try: - return json.loads(self._content.decode("utf-8")) + return json.loads(self._content.decode("utf-8")) # type: ignore[no-any-return] except (json.JSONDecodeError, UnicodeDecodeError) as err: raise ValueError(f"Failed to parse JSON response: {err}") from err @@ -73,7 +73,7 @@ def get_corpus_db(url: str) -> Optional[_ResponseWrapper]: return corpus_db -def get_corpus_db_detail(name: str, version: str = "") -> dict[str, str]: +def get_corpus_db_detail(name: str, version: str = "") -> dict[str, Any]: """Get details about a corpus, using information from local catalog. :param str name: name of corpus @@ -86,11 +86,11 @@ def get_corpus_db_detail(name: str, version: str = "") -> dict[str, str]: if not version: for corpus in local_db["_default"].values(): if corpus["name"] == name: - return corpus + return corpus # type: ignore[no-any-return] else: for corpus in local_db["_default"].values(): if corpus["name"] == name and corpus["version"] == version: - return corpus + return corpus # type: ignore[no-any-return] return {} @@ -550,14 +550,14 @@ def _check_version(cause: str) -> bool: temp = cause.replace(">", "") check = v > _version2int(temp) elif cause.startswith(">=") and "<=" not in cause and "<" in cause: - temp = cause.replace(">=", "").split("<") - check = _version2int(temp[0]) <= v < _version2int(temp[1]) + temp_parts = cause.replace(">=", "").split("<") + check = _version2int(temp_parts[0]) <= v < _version2int(temp_parts[1]) elif cause.startswith(">=") and "<=" in cause: - temp = cause.replace(">=", "").split("<=") - check = _version2int(temp[0]) <= v <= _version2int(temp[1]) + temp_parts = cause.replace(">=", "").split("<=") + check = _version2int(temp_parts[0]) <= v <= _version2int(temp_parts[1]) elif cause.startswith(">") and "<" in cause: - temp = cause.replace(">", "").split("<") - check = _version2int(temp[0]) < v < _version2int(temp[1]) + temp_parts = cause.replace(">", "").split("<") + check = _version2int(temp_parts[0]) < v < _version2int(temp_parts[1]) elif cause.startswith("<="): temp = cause.replace("<=", "") check = v <= _version2int(temp[0]) @@ -608,14 +608,14 @@ def download(name: str, force: bool = False, url: str = "", version: str = "") - print(f"Cannot download corpus catalog from: {url}") return False - corpus_db = corpus_db.json() + corpus_db_dict = corpus_db.json() # check if corpus is available - if name in corpus_db: + if name in corpus_db_dict: with open(corpus_db_path(), encoding="utf-8-sig") as f: local_db = json.load(f) - corpus = corpus_db[name] + corpus = corpus_db_dict[name] print("Corpus:", name) if not version: for v, file in corpus["versions"].items(): @@ -842,4 +842,4 @@ def get_hf_hub(repo_id: str, filename: str = "") -> str: ) else: output_path = snapshot_download(repo_id=repo_id, local_dir=root_project) - return output_path + return output_path # type: ignore[no-any-return] From 2ac3eb2b181f3eab7347e1d2e47bb4ed2ec98231 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:34:43 +0000 Subject: [PATCH 3/5] Fix type hints in transliterate, tag, and util modules Co-authored-by: bact <128572+bact@users.noreply.github.com> --- pythainlp/tag/unigram.py | 10 +++++----- pythainlp/transliterate/core.py | 14 +++++++------- pythainlp/util/spell_words.py | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pythainlp/tag/unigram.py b/pythainlp/tag/unigram.py index bd5c3a6d5..13fab6120 100644 --- a/pythainlp/tag/unigram.py +++ b/pythainlp/tag/unigram.py @@ -38,7 +38,7 @@ def _orchid_tagger() -> dict: if not _ORCHID_TAGGER: with open(_ORCHID_PATH, encoding="utf-8-sig") as fh: _ORCHID_TAGGER = json.load(fh) - return _ORCHID_TAGGER + return _ORCHID_TAGGER # type: ignore[no-any-return] def _pud_tagger() -> dict: @@ -46,7 +46,7 @@ def _pud_tagger() -> dict: if not _PUD_TAGGER: with open(_PUD_PATH, encoding="utf-8-sig") as fh: _PUD_TAGGER = json.load(fh) - return _PUD_TAGGER + return _PUD_TAGGER # type: ignore[no-any-return] def _blackboard_tagger() -> dict: @@ -57,7 +57,7 @@ def _blackboard_tagger() -> dict: raise ValueError(f"Corpus path not found for {_BLACKBOARD_NAME}") with open(path, encoding="utf-8-sig") as fh: _BLACKBOARD_TAGGER = json.load(fh) - return _BLACKBOARD_TAGGER + return _BLACKBOARD_TAGGER # type: ignore[no-any-return] def _thai_tdtb() -> dict: @@ -65,7 +65,7 @@ def _thai_tdtb() -> dict: if not _TDTB_TAGGER: with open(_TDTB_PATH, encoding="utf-8-sig") as fh: _TDTB_TAGGER = json.load(fh) - return _TDTB_TAGGER + return _TDTB_TAGGER # type: ignore[no-any-return] def _tud_tagger() -> dict: @@ -73,7 +73,7 @@ def _tud_tagger() -> dict: if not _TUD_TAGGER: with open(_TUD_PATH, encoding="utf-8-sig") as fh: _TUD_TAGGER = json.load(fh) - return _TUD_TAGGER + return _TUD_TAGGER # type: ignore[no-any-return] def _find_tag( diff --git a/pythainlp/transliterate/core.py b/pythainlp/transliterate/core.py index b54cd41d1..7c2714916 100644 --- a/pythainlp/transliterate/core.py +++ b/pythainlp/transliterate/core.py @@ -169,19 +169,19 @@ def transliterate( if engine in ("icu", "pyicu"): from pythainlp.transliterate.pyicu import transliterate elif engine == "ipa": - from pythainlp.transliterate.ipa import transliterate # type: ignore[no-redef] # noqa: I001 + from pythainlp.transliterate.ipa import transliterate # noqa: I001 elif engine == "tltk_g2p": - from pythainlp.transliterate.tltk import tltk_g2p as transliterate # type: ignore[no-redef] # noqa: I001 + from pythainlp.transliterate.tltk import tltk_g2p as transliterate # noqa: I001 elif engine == "tltk_ipa": - from pythainlp.transliterate.tltk import tltk_ipa as transliterate # type: ignore[no-redef] # noqa: I001 + from pythainlp.transliterate.tltk import tltk_ipa as transliterate # noqa: I001 elif engine == "iso_11940": - from pythainlp.transliterate.iso_11940 import transliterate # type: ignore[no-redef] # noqa: I001 + from pythainlp.transliterate.iso_11940 import transliterate # type: ignore[assignment] # noqa: I001 elif engine == "thaig2p_v2": - from pythainlp.transliterate.thaig2p_v2 import transliterate # type: ignore[no-redef] # noqa: I001 + from pythainlp.transliterate.thaig2p_v2 import transliterate # noqa: I001 elif engine == "umt5_thaig2p": - from pythainlp.translate.umt5_thaig2p import transliterate # type: ignore[no-redef] # noqa: I001 + from pythainlp.translate.umt5_thaig2p import transliterate # type: ignore[import-not-found, no-redef] # noqa: I001 else: # use default engine: "thaig2p" - from pythainlp.transliterate.thaig2p import transliterate # type: ignore[no-redef] # noqa: I001 + from pythainlp.transliterate.thaig2p import transliterate # noqa: I001 return transliterate(text) diff --git a/pythainlp/util/spell_words.py b/pythainlp/util/spell_words.py index 50c47f63f..d96e0c3a0 100644 --- a/pythainlp/util/spell_words.py +++ b/pythainlp/util/spell_words.py @@ -24,9 +24,9 @@ for i, j in zip(list(thai_tonemarks), ["เอก", "โท", "ตรี", "จัตวา"]) } -rule1 = [i.replace("-", f"([{thai_letters}](thai_tonemarks)?)") for i in _r1] -rule2 = [i.replace("–", f"([{thai_letters}])").replace(":", "") for i in _r2] -rule3 = [ +rule1: list[str] = [i.replace("-", f"([{thai_letters}](thai_tonemarks)?)") for i in _r1] +rule2: list[str] = [i.replace("–", f"([{thai_letters}])").replace(":", "") for i in _r2] +rule3: list[str] = [ i.replace("–", f"([{thai_letters}])").replace(":", f"([{thai_tonemarks}])") for i in _r2 ] From b8cc9eb1747291530fd940622d4d7f7b864f4020 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:40:17 +0000 Subject: [PATCH 4/5] Fix type hints in tag/named_entity.py and transliterate/lookup.py Co-authored-by: bact <128572+bact@users.noreply.github.com> --- pythainlp/tag/named_entity.py | 6 +++--- pythainlp/transliterate/lookup.py | 10 +++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pythainlp/tag/named_entity.py b/pythainlp/tag/named_entity.py index 98b715207..e1d6775bb 100644 --- a/pythainlp/tag/named_entity.py +++ b/pythainlp/tag/named_entity.py @@ -49,9 +49,9 @@ def load_engine(self, engine: str, corpus: str) -> None: model="pythainlp/thainer-corpus-v2-base-model" ) elif engine == "wangchanberta": - from pythainlp.wangchanberta import ThaiNameTagger as WangchanbertaThaiNameTagger # type: ignore[assignment] # noqa: I001,E501 + from pythainlp.wangchanberta import ThaiNameTagger as WangchanbertaThaiNameTagger # noqa: I001,E501 - self.engine = WangchanbertaThaiNameTagger(dataset_name=corpus) # type: ignore[call-arg] + self.engine = WangchanbertaThaiNameTagger(dataset_name=corpus) elif corpus == "thainer-v2": if engine == "phayathaibert": from pythainlp.phayathaibert.core import NamedEntityTagger @@ -101,7 +101,7 @@ def tag( >>> ner.tag("ทดสอบ นายวรรณพงษ์ ภัททิยไพบูลย์", tag=True) 'ทดสอบ นายวรรณพงษ์ ภัททิยไพบูลย์' """ - return self.engine.get_ner(text, tag=tag, pos=pos) # type: ignore[union-attr] + return self.engine.get_ner(text, tag=tag, pos=pos) # type: ignore[no-any-return] class NNER: diff --git a/pythainlp/transliterate/lookup.py b/pythainlp/transliterate/lookup.py index 52e2bc5e0..12df26cbe 100644 --- a/pythainlp/transliterate/lookup.py +++ b/pythainlp/transliterate/lookup.py @@ -36,7 +36,9 @@ def follow_rtgs(text: str) -> Optional[bool]: except IndexError: return None else: - return follow + if isinstance(follow, bool) or follow is None: + return follow + return None # fallback for str type (shouldn't happen) def _romanize(text: str, fallback_func: Callable[[str], str]) -> str: @@ -49,9 +51,11 @@ def _romanize(text: str, fallback_func: Callable[[str], str]) -> str: except IndexError: return fallback_func(text) except TypeError as e: - raise TypeError(f"`fallback_engine` is not callable. {e}") + raise TypeError(f"`fallback_engine` is not callable. {e}") from e else: - return lookup + if isinstance(lookup, str): + return lookup + return fallback_func(text) # fallback for non-str types def romanize(text: str, fallback_func: Callable[[str], str]) -> str: From d09c7f949ebdd41aff79c1e17b97eb9357d5552e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:45:42 +0000 Subject: [PATCH 5/5] Simplify type fixes per code review feedback Co-authored-by: bact <128572+bact@users.noreply.github.com> --- pythainlp/corpus/common.py | 4 ++-- pythainlp/transliterate/lookup.py | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pythainlp/corpus/common.py b/pythainlp/corpus/common.py index 275750716..e9bd468ee 100644 --- a/pythainlp/corpus/common.py +++ b/pythainlp/corpus/common.py @@ -388,8 +388,8 @@ def find_synonyms(word: str) -> list[str]: if word in synonyms["word"]: # find by word word_list = synonyms["word"] - assert isinstance(word_list, list) - list_synonym.extend(synonyms["synonym"][word_list.index(word)]) # type: ignore[arg-type] + if isinstance(word_list, list): + list_synonym.extend(synonyms["synonym"][word_list.index(word)]) # type: ignore[arg-type] for idx, words in enumerate(synonyms["synonym"]): # find by synonym if word in words: diff --git a/pythainlp/transliterate/lookup.py b/pythainlp/transliterate/lookup.py index 12df26cbe..4b0d1964e 100644 --- a/pythainlp/transliterate/lookup.py +++ b/pythainlp/transliterate/lookup.py @@ -36,9 +36,7 @@ def follow_rtgs(text: str) -> Optional[bool]: except IndexError: return None else: - if isinstance(follow, bool) or follow is None: - return follow - return None # fallback for str type (shouldn't happen) + return follow # type: ignore[return-value] def _romanize(text: str, fallback_func: Callable[[str], str]) -> str: @@ -53,9 +51,7 @@ def _romanize(text: str, fallback_func: Callable[[str], str]) -> str: except TypeError as e: raise TypeError(f"`fallback_engine` is not callable. {e}") from e else: - if isinstance(lookup, str): - return lookup - return fallback_func(text) # fallback for non-str types + return lookup # type: ignore[return-value] def romanize(text: str, fallback_func: Callable[[str], str]) -> str: