Fix runtime AttributeError in wordnet type casts, improve type safety and API contracts - #1354
Conversation
- ancient/currency.py: narrow dict -> dict[str, float] - augment/word2vec/bpemb_wv.py: use cast instead of type: ignore - augment/word2vec/core.py: format long line - augment/wordnet.py: narrow list -> list[Synset], list -> list[list[str]] - benchmarks/word_tokenization.py: add NDArray import, narrow types, fix exception chaining, use dtype=np.int8 - chunk/__init__.py: add blank line after docstring - chunk/crfchunk.py: add cast, replace type: ignore - coref/core.py: replace _MODEL global with _MODEL_CACHE dict, narrow list[dict] -> list[dict[str, Any]], use cast - corpus/common.py: format conditions and dict assignment - corpus/core.py: move Any/Optional to runtime imports, add cast, remove double blank lines, drop type: ignore comments - corpus/wordnet.py: add cast, replace all type: ignore comments, fix custom_lemmas to return None explicitly - el/_multiel.py: narrow list[dict] -> list[dict[str, Any]], use cast - el/core.py: narrow return type, fix docstring param - generate/wangchanglm.py: use re.Pattern[str], add cast - parse/ud_goeswith.py: rename r -> root_adjustments, format expression - phayathaibert/core.py: narrow Callable types, add cast - soundex/complete_soundex.py: narrow list/tuple types in signatures - soundex/sound.py: add cast, replace type: ignore - spell/phunspell.py: add cast, replace type: ignore - spell/tltk.py: add cast, replace type: ignore Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- spell/wanchanberta_thai_grammarly.py: add # type: ignore[misc] to BertModel - spell/words_spelling_correction.py: replace global _WSC with _WSC_CACHE, use import_module for dependency checks, improve docstrings, add cast, fix numpy dtype precision - summarize/freq.py: narrow __rank and __compute_frequencies signatures - summarize/keybert.py: add cast/NDArray, fix embed return type, fix _rank_keywords signatures, add exception chaining - tag/_tag_perceptron.py: dict[str, Union[dict, list]] -> dict[str, Any] - tag/chunk.py, tag/crfchunk.py: add blank line after module docstring - tag/thai_nner.py: narrow dict -> dict[str, Any], add Any import - tag/thainer.py: narrow list -> list[tuple[str,str]], dict -> dict[str, Union[str, bool]] - tag/tltk.py: replace # type: ignore with cast - tag/wangchanberta_onnx.py: fix mutable default arg, use NDArray types, use cast, fix sp.encode -> sp.EncodeAsIds - tokenize/core.py: add cast for paragraph_tokenize return - tokenize/han_solo.py: dict[str, list] -> dict[str, list[Any]] - tokenize/newmm.py: defaultdict -> defaultdict[int, list[int]] - translate/tokenization_small100.py: multiple cast improvements, fix __getstate__/__setstate__ types, add # type: ignore[misc] - translate/word2word_translate.py: add cast for Optional[list[str]] - transliterate/ipa.py: add cast for all returns - transliterate/lookup.py: add cast for bool and str returns - transliterate/thai2rom.py: add # type: ignore[misc] to nn.Module subclasses - transliterate/thai2rom_onnx.py: NDArray types, cast, encoding fix Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- transliterate/thaig2p.py: add Any import, NDArray[Any] for generics, type: ignore[misc] on nn.Module subclasses - transliterate/thaig2p_v2.py, umt5_thaig2p.py: replace type: ignore with cast for pipeline output - transliterate/tltk.py: add cast import; use cast(str, ...) for tltk return values - transliterate/w2p.py: cast NDArray[np.float32] assignments, add docstrings, refine _sigmoid/_grucell/_gru/_encode signatures - transliterate/wunsen.py: use cast(str, ...) for thap return - ulmfit/core.py: update process_thai signature/body to Optional params with defaults, NDArray[np.float32] for document_vector, typed merge_wgts wgts param - util/abbreviation.py: cast return type of _replace - util/collate.py: Iterable[str] parameter, update docstring - util/keywords.py: Counter[str] return type, update docstring - util/numtoword.py: Optional[float] for bahttext parameter - util/pronounce.py: reformat rhyme generator expression - util/thai.py: reformat MappingProxyType, dict[str,int] return types - wangchanberta/core.py: cast tokenize return - word_vector/core.py: NDArray[np.float32] for sentence_vectorizer, np.zeros with dtype, cast for model method returns - wsd/core.py: replace _MODEL global with _MODEL_CACHE dict, rename loop vars, typed custom_dict, float() for get_score return Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…hf_hub return Co-authored-by: bact <128572+bact@users.noreply.github.com> Agent-Logs-Url: https://github.com/PyThaiNLP/pythainlp/sessions/a8b6840c-c575-4f78-b39b-cde7736e50c5
Refactor frequency computation to use Counter and simplify filtering based on min and max cut values.
…et directly Co-authored-by: bact <128572+bact@users.noreply.github.com> Agent-Logs-Url: https://github.com/PyThaiNLP/pythainlp/sessions/43afb162-b51d-4003-b3c2-1c93a6fba13b
There was a problem hiding this comment.
Pull request overview
This PR focuses on tightening and correcting type hints across PyThaiNLP, replacing broad Any/type: ignore usage with narrower typing (often via cast()), and addressing a runtime typing issue in the WordNet wrapper.
Changes:
- Fix WordNet typing/runtime usage by importing
Synset/Lemmadirectly and updating annotations/casts accordingly. - Replace multiple
# type: ignore[...]returns with explicitcast(...)and refine return/parameter types (includingnumpy.typing.NDArray). - Introduce simple global caches for some heavyweight models to reduce repeated initialization.
Reviewed changes
Copilot reviewed 54 out of 54 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pythainlp/wsd/core.py | Refines types and introduces device-based model caching for WSD scoring. |
| pythainlp/word_vector/core.py | Improves return typing (casts/NDArray) and enforces float32 vector output. |
| pythainlp/wangchanberta/core.py | Replaces type ignores with casts for tokenizer return types. |
| pythainlp/util/thai.py | Formatting plus more precise return types for Thai text utilities. |
| pythainlp/util/pronounce.py | Formatting-only change to comprehension layout. |
| pythainlp/util/numtoword.py | Broadens typing for bahttext parameter and updates docstring typing. |
| pythainlp/util/keywords.py | Narrows Counter typing and documents None return on empty input. |
| pythainlp/util/collate.py | Narrows Iterable typing to Iterable[str] and updates doc types. |
| pythainlp/util/abbreviation.py | Replaces type ignore with cast for external return type. |
| pythainlp/ulmfit/core.py | Tightens callable/rule types, NDArray return types, and float32 conversions. |
| pythainlp/transliterate/wunsen.py | Replaces type ignore with cast for transliteration return type. |
| pythainlp/transliterate/w2p.py | Refines NDArray annotations and adds casts for loaded numpy arrays. |
| pythainlp/transliterate/umt5_thaig2p.py | Replaces type ignore with cast and safer output extraction. |
| pythainlp/transliterate/tltk.py | Adds casts around tltk outputs and formats processing steps. |
| pythainlp/transliterate/thaig2p_v2.py | Replaces type ignore with cast and safer output extraction. |
| pythainlp/transliterate/thaig2p.py | Narrows NDArray typing to avoid NDArray without element type. |
| pythainlp/transliterate/thai2rom_onnx.py | Adds NDArray typing, encoding for JSON, and dtype fixes/casts for ONNX IO. |
| pythainlp/transliterate/lookup.py | Replaces type ignores with casts for lookup-derived values. |
| pythainlp/transliterate/ipa.py | Replaces type ignores with casts for epitran return types. |
| pythainlp/translate/word2word_translate.py | Replaces type ignore with cast for external translation return. |
| pythainlp/translate/tokenization_small100.py | Adds casts/typed dict state and narrows return types for tokenizer helpers. |
| pythainlp/tokenize/newmm.py | Narrows graph typing for BFS helper. |
| pythainlp/tokenize/han_solo.py | Narrows feature dict typing (list[Any]). |
| pythainlp/tokenize/core.py | Replaces type ignore with cast for paragraph tokenization return. |
| pythainlp/tag/wangchanberta_onnx.py | Tightens ONNX IO types, improves SentencePiece loading compatibility, and adds casts. |
| pythainlp/tag/tltk.py | Replaces type ignore with cast for tltk POS tagging results. |
| pythainlp/tag/thainer.py | Narrows feature dict typing for CRF feature extraction. |
| pythainlp/tag/thai_nner.py | Narrows entity container typing to dict[str, Any] across helpers. |
| pythainlp/tag/crfchunk.py | Formatting-only (blank line) in deprecated shim. |
| pythainlp/tag/chunk.py | Formatting-only (blank line) in deprecated shim. |
| pythainlp/tag/_tag_perceptron.py | Narrows JSON payload typing to Any to satisfy type-checkers. |
| pythainlp/summarize/keybert.py | Improves exception chaining and adds NDArray typing/casts for embeddings. |
| pythainlp/summarize/freq.py | Refactors frequency calculation to Counter/dict and tightens typing. |
| pythainlp/spell/words_spelling_correction.py | Improves dependency checking, adds typing/docstrings, and introduces a simple cache. |
| pythainlp/spell/tltk.py | Replaces type ignore with cast for spell candidate output. |
| pythainlp/spell/phunspell.py | Replaces type ignore with cast for correction return type. |
| pythainlp/soundex/sound.py | Replaces type ignore with cast for panphon vector output. |
| pythainlp/soundex/complete_soundex.py | Narrows helper parameter/return typings for soundex processing. |
| pythainlp/phayathaibert/core.py | Tightens callable types and replaces type ignore with cast for tokenization. |
| pythainlp/parse/ud_goeswith.py | Improves variable naming and formatting for root adjustment logic. |
| pythainlp/generate/wangchanglm.py | Narrows regex pattern typing and replaces type ignore with cast for decoding output. |
| pythainlp/el/core.py | Tightens EL return typing (list[dict[str, Any]]) and fixes docstring param line. |
| pythainlp/el/_multiel.py | Tightens return typing and replaces type ignore with cast for external output. |
| pythainlp/corpus/wordnet.py | Fixes Synset/Lemma typing imports and replaces ignores with casts in wrappers. |
| pythainlp/corpus/core.py | Replaces ignores with casts, cleans typing imports, and ensures string return for HF hub path. |
| pythainlp/corpus/common.py | Formatting-only restructuring for readability. |
| pythainlp/coref/core.py | Replaces singleton model with keyed cache and tightens return typing/casts. |
| pythainlp/chunk/crfchunk.py | Replaces type ignore with cast for tagger output typing. |
| pythainlp/chunk/init.py | Formatting-only (blank line). |
| pythainlp/benchmarks/word_tokenization.py | Adds NDArray typing, dtype fixes, and improves exception chaining. |
| pythainlp/augment/wordnet.py | Narrows list typings for synsets and synonym lists. |
| pythainlp/augment/word2vec/core.py | Formatting-only change to wrap long call for readability. |
| pythainlp/augment/word2vec/bpemb_wv.py | Replaces type ignore with cast for BPEmb encode return. |
| pythainlp/ancient/currency.py | Narrows return type to dict[str, float] and fixes a comment. |
Comments suppressed due to low confidence (1)
pythainlp/wsd/core.py:17
_TRIE/_word_cutare built from_mean_all.keys()before_mean_allis populated (the loop that fills_mean_allhappens later). This makes_TRIEempty at import time, so the defaultcustom_tokenizeris likely initialized with an empty dictionary. Build_all_word/_TRIE/_word_cutafter thefor ... zip(words, meanings)loop (or build them lazily) so they reflect the actual WSD vocabulary.
| 'calculate' | ||
| """ | ||
| return wordnet.morphy(form, pos=None) # type: ignore[no-any-return] | ||
| return cast(str, wordnet.morphy(form, pos=None)) |
There was a problem hiding this comment.
morphy() accepts a pos argument but currently ignores it by always calling wordnet.morphy(form, pos=None). This makes the pos parameter ineffective and can return incorrect base forms for callers that pass a part-of-speech. Pass through the provided pos value to wordnet.morphy.
| return cast(str, wordnet.morphy(form, pos=None)) | |
| return cast(str, wordnet.morphy(form, pos=pos)) |
There was a problem hiding this comment.
We should add test cases for this too.
At least one test case for None and another for no n-None.
There was a problem hiding this comment.
Added two test cases in testx_corpus.py: one for morphy("dogs", pos=None) and one for morphy("dogs", pos="n"), both asserting the result is "dog". Fixed in fea0717.
| @@ -20,7 +20,7 @@ | |||
| def pos_tag(words: list[str], corpus: str = "tnc") -> list[tuple[str, str]]: | |||
| if corpus != "tnc": | |||
| raise ValueError(f"tltk not support {0} corpus.") | |||
There was a problem hiding this comment.
The ValueError message uses {0} instead of interpolating the provided corpus value, so it will always render as "tltk not support 0 corpus.". Use the corpus variable (ideally with !r) in the f-string so the error is actionable.
| raise ValueError(f"tltk not support {0} corpus.") | |
| raise ValueError(f"tltk not support {corpus!r} corpus.") |
| def bahttext(number: Optional[float]) -> str: | ||
| """Converts a number to Thai text and adds | ||
| a suffix "บาท" (Baht). | ||
| The precision will be fixed at two decimal places (0.00) | ||
| to fit "สตางค์" (Satang) unit. | ||
| This function works similarly to the `BAHTTEXT` function in Microsoft Excel. | ||
|
|
||
| :param float number: number to be converted into Thai Baht currency format | ||
| :param Optional[float] number: number to be converted into Thai Baht | ||
| currency format |
There was a problem hiding this comment.
The signature/docstring now allow number: Optional[float], which implies None is a supported input. To avoid an ambiguous API contract, either keep number as float (and reject None), or explicitly document what bahttext(None) should return/raise and implement that behavior consistently.
There was a problem hiding this comment.
Changed bahttext to number: float (removes Optional), added a TypeError guard at the top of the function, and updated the test in test_util.py from assertEqual(bahttext(None), "") to assertRaises(TypeError). Fixed in fea0717.
|
@copilot look at suggestions and comments in thread #1354 (review) and fix all the issues. Run mypy, pyright, pylint, ruff, flake8 and fix all issues. Self-review and add test cases that addressed issues fixed in this PR. |
…r message, add tests Co-authored-by: bact <128572+bact@users.noreply.github.com> Agent-Logs-Url: https://github.com/PyThaiNLP/pythainlp/sessions/4404ee59-fa2b-4a99-9de3-e477cf5b2668
All three review issues are addressed in fea0717:
|
|



What do these changes do
AttributeErrorinpythainlp/corpus/wordnet.pycaused by accessingwordnet.Synset/wordnet.Lemmaon aWordNetCorpusReaderinstance insidecast()calls.morphy()to correctly pass itsposargument through to the underlying NLTK call (it was silently ignoring the argument).bahttext()to rejectNoneinput with aTypeError, clarifying the API contract.ValueErrormessage inpythainlp/tag/tltk.pyto correctly display the unsupported corpus name.morphy()with bothpos=Noneand a non-Nonepos value.bahttext(None)test to expect aTypeError.What was wrong
wordnet(fromfrom nltk.corpus import wordnet) is aWordNetCorpusReaderinstance, not a module. Accessingwordnet.Synsetorwordnet.Lemmainsidecast()calls evaluates the attribute lookup at runtime, raisingAttributeError: 'WordNetCorpusReader' object has no attribute 'Synset'.morphy(form, pos=pos)was implemented asmorphy(form, pos=None), silently ignoring the caller-supplied part-of-speech and potentially returning wrong base forms.bahttext()acceptedOptional[float], implyingNonewas a valid input, but the intended contract is to only accept numeric values.ValueErrorintltk.pos_tagused{0}as a placeholder instead of{corpus!r}, always rendering as"tltk not support 0 corpus.".How this fixes it
SynsetandLemmadirectly fromnltk.corpus.reader.wordnet(where they are actual classes) and replace allwordnet.Synset/wordnet.Lemmareferences with these names.posthrough inmorphy():return cast(str, wordnet.morphy(form, pos=pos)).bahttext()signature tonumber: floatand add an explicitisinstanceguard that raisesTypeErrorfor non-numeric input.tltk.pos_tagtof"tltk not support {corpus!r} corpus.".morphy("dogs", pos=None)andmorphy("dogs", pos="n")assertions totestx_corpus.py.assertEqual(bahttext(None), "")withassertRaises(TypeError)intest_util.py.Your checklist for this pull request
📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.