Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,81 @@ basepython = "python"
deps = ["ruff"]
commands = [["ruff", "check", "pythainlp"], ["ruff", "format", "--check", "pythainlp"]]
skip_install = true

# Mypy configuration
[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false
disallow_incomplete_defs = false
check_untyped_defs = true
disallow_untyped_decorators = false
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
show_error_codes = true
show_column_numbers = true
pretty = true

# Per-module options for stricter checking on foundation modules
[[tool.mypy.overrides]]
module = [
"pythainlp.tools.*",
"pythainlp.cli.*",
]
disallow_untyped_defs = true
disallow_incomplete_defs = true

# Ignore missing imports for optional dependencies
[[tool.mypy.overrides]]
module = [
"attacut.*",
"bpemb.*",
"budoux.*",
"deepcut.*",
"emoji.*",
"epitran.*",
"esupar.*",
"fairseq.*",
"fastai.*",
"fastcoref.*",
"gensim.*",
"huggingface_hub.*",
"icu.*",
"khamyo.*",
"khanaa.*",
"multiel.*",
"nlpo3.*",
"nltk.*",
"numpy.*",
"onnxruntime.*",
"oskut.*",
"pandas.*",
"panphon.*",
"phunspell.*",
"pycrfsuite.*",
"pyicu.*",
"sacremoses.*",
"sefr_cut.*",
"sentencepiece.*",
"sentence_transformers.*",
"spacy.*",
"spacy_thai.*",
"ssg.*",
"symspellpy.*",
"thai_nner.*",
"tltk.*",
"torch.*",
"tqdm.*",
"transformers.*",
"ufal.chu_liu_edmonds.*",
"word2word.*",
"wtpsplit.*",
"wunsen.*",
"yaml.*",
]
ignore_missing_imports = true
2 changes: 1 addition & 1 deletion pythainlp/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
CLI_NAME = "thainlp"


def make_usage(command: str) -> dict:
def make_usage(command: str) -> dict[str, str]:
prog = f"{CLI_NAME} {command}"

return {"prog": prog, "usage": f"{prog} [options]"}
Expand Down
14 changes: 10 additions & 4 deletions pythainlp/cli/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,28 @@
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import argparse
import json
import os
from typing import TYPE_CHECKING

from pythainlp import cli
from pythainlp.tools import safe_print

if TYPE_CHECKING:
from collections.abc import Sequence


def _read_file(path):
def _read_file(path: str) -> list[str]:
with open(path, encoding="utf-8") as f:
lines = (r.strip() for r in f.readlines())
return list(lines)


class App:
def __init__(self, argv):
def __init__(self, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(
prog="benchmark",
description=(
Expand All @@ -45,8 +51,8 @@ def __init__(self, argv):


class WordTokenizationBenchmark:
def __init__(self, name, argv):
parser = argparse.ArgumentParser(**cli.make_usage("benchmark " + name))
def __init__(self, name: str, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(**cli.make_usage("benchmark " + name)) # type: ignore[arg-type]

parser.add_argument(
"--input-file",
Expand Down
26 changes: 16 additions & 10 deletions pythainlp/cli/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@
"""Command line for PyThaiNLP's dataset/corpus management.
"""

from __future__ import annotations

import argparse
from typing import TYPE_CHECKING

from pythainlp import corpus
from pythainlp.tools import get_pythainlp_data_path

if TYPE_CHECKING:
from collections.abc import Sequence


class App:
def __init__(self, argv):
def __init__(self, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(
prog="data",
description="Manage dataset/corpus.",
Expand Down Expand Up @@ -43,7 +49,7 @@ def __init__(self, argv):
args = parser.parse_args(argv[2:3])
getattr(self, args.subcommand)(argv)

def get(self, argv):
def get(self, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(
description="Download a dataset",
usage="thainlp data get <dataset_name>",
Expand All @@ -59,7 +65,7 @@ def get(self, argv):
else:
print("Not found.")

def rm(self, argv):
def rm(self, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(
description="Remove a dataset",
usage="thainlp data rm <dataset_name>",
Expand All @@ -75,7 +81,7 @@ def rm(self, argv):
else:
print("Not found.")

def info(self, argv):
def info(self, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(
description="Print information about a dataset",
usage="thainlp data info <dataset_name>",
Expand All @@ -92,14 +98,14 @@ def info(self, argv):
else:
print("Not found.")

def catalog(self, argv):
def catalog(self, argv: Sequence[str]) -> None:
"""Print dataset/corpus available for download."""
corpus_db = corpus.get_corpus_db(corpus.corpus_db_url())
corpus_db = corpus_db.json()
corpus_names = sorted(corpus_db.keys())
corpus_db_response = corpus.get_corpus_db(corpus.corpus_db_url())
corpus_db_dict: dict[str, dict[str, str]] = corpus_db_response.json() # type: ignore[union-attr]
corpus_names = sorted(corpus_db_dict.keys())
print("Dataset/corpus available for download:")
for name in corpus_names:
print(f"- {name} {corpus_db[name]['latest_version']}", end="")
print(f"- {name} {corpus_db_dict[name]['latest_version']}", end="")
corpus_info = corpus.get_corpus_db_detail(name)
if corpus_info:
print(f" (Local: {corpus_info['version']})")
Expand All @@ -111,6 +117,6 @@ def catalog(self, argv):
"Example: thainlp data get crfcut\n"
)

def path(self, argv):
def path(self, argv: Sequence[str]) -> None:
"""Print path of local dataset."""
print(get_pythainlp_data_path())
8 changes: 7 additions & 1 deletion pythainlp/cli/misspell.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import argparse
import os
import random
from typing import TYPE_CHECKING

from pythainlp.tools.misspell import misspell

if TYPE_CHECKING:
from collections.abc import Sequence


class App:
def __init__(self, argv):
def __init__(self, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(
prog="misspell",
description="Generate misspelled texts from a given file.",
Expand Down
8 changes: 7 additions & 1 deletion pythainlp/cli/soundex.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
It takes input text from the command line.
"""

from __future__ import annotations

import argparse
from typing import TYPE_CHECKING

from pythainlp.soundex import DEFAULT_SOUNDEX_ENGINE, soundex
from pythainlp.tools import safe_print

if TYPE_CHECKING:
from collections.abc import Sequence


class App:
def __init__(self, argv):
def __init__(self, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(
prog="soundex",
description="Convert a text to its sound-based index.",
Expand Down
19 changes: 14 additions & 5 deletions pythainlp/cli/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,25 @@
"""Command line for PyThaiNLP's taggers.
"""

from __future__ import annotations

import argparse
from typing import TYPE_CHECKING

from pythainlp import cli
from pythainlp.tag import pos_tag
from pythainlp.tools import safe_print

if TYPE_CHECKING:
from collections.abc import Callable, Sequence


class SubAppBase:
def __init__(self, name, argv):
parser = argparse.ArgumentParser(**cli.make_usage("tag " + name))
separator: str
run: Callable[[list[str]], list[tuple[str, str]]]

def __init__(self, name: str, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(**cli.make_usage("tag " + name)) # type: ignore[arg-type]
parser.add_argument(
"text",
type=str,
Expand All @@ -39,15 +48,15 @@ def __init__(self, name, argv):


class POSTaggingApp(SubAppBase):
def __init__(self, *args, **kwargs):
def __init__(self, *args: str, **kwargs: str) -> None:
self.separator = "|"
self.run = pos_tag

super().__init__(*args, **kwargs)


class App:
def __init__(self, argv):
def __init__(self, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(
prog="tag",
description="Annotate a text with linguistic information",
Expand All @@ -72,6 +81,6 @@ def __init__(self, argv):
argv = argv[3:]

if tag_type == "pos":
POSTaggingApp("Part-of-Speech tagging", argv)
POSTaggingApp("Part-of-Speech tagging", argv) # type: ignore[arg-type]
else:
print(f"Tag type not available: {tag_type}")
28 changes: 19 additions & 9 deletions pythainlp/cli/tokenize.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
# SPDX-License-Identifier: Apache-2.0
"""Command line for PyThaiNLP's tokenizers."""

from __future__ import annotations

import argparse
from typing import TYPE_CHECKING

from pythainlp import cli
from pythainlp.tokenize import (
Expand All @@ -16,15 +19,22 @@
)
from pythainlp.tools import safe_print

if TYPE_CHECKING:
from collections.abc import Callable, Sequence

DEFAULT_SENT_TOKEN_SEPARATOR = "@@" # noqa: S105
DEFAULT_SUBWORD_TOKEN_SEPARATOR = "/" # noqa: S105
DEFAULT_SYLLABLE_TOKEN_SEPARATOR = "~" # noqa: S105
DEFAULT_WORD_TOKEN_SEPARATOR = "|" # noqa: S105


class SubAppBase:
def __init__(self, name, argv):
parser = argparse.ArgumentParser(**cli.make_usage("tokenize " + name))
separator: str
algorithm: str
run: Callable[..., list[str]]

def __init__(self, name: str, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(**cli.make_usage("tokenize " + name)) # type: ignore[arg-type]
parser.add_argument(
"text",
type=str,
Expand Down Expand Up @@ -74,7 +84,7 @@ def __init__(self, name, argv):


class WordTokenizationApp(SubAppBase):
def __init__(self, *args, **kwargs):
def __init__(self, *args: str, **kwargs: str) -> None:
self.keep_whitespace = True
self.algorithm = DEFAULT_WORD_TOKENIZE_ENGINE
self.separator = DEFAULT_WORD_TOKEN_SEPARATOR
Expand All @@ -83,7 +93,7 @@ def __init__(self, *args, **kwargs):


class SentenceTokenizationApp(SubAppBase):
def __init__(self, *args, **kwargs):
def __init__(self, *args: str, **kwargs: str) -> None:
self.keep_whitespace = True
self.algorithm = DEFAULT_SENT_TOKENIZE_ENGINE
self.separator = DEFAULT_SENT_TOKEN_SEPARATOR
Expand All @@ -92,7 +102,7 @@ def __init__(self, *args, **kwargs):


class SubwordTokenizationApp(SubAppBase):
def __init__(self, *args, **kwargs):
def __init__(self, *args: str, **kwargs: str) -> None:
self.keep_whitespace = True
self.algorithm = DEFAULT_SUBWORD_TOKENIZE_ENGINE
self.separator = DEFAULT_SUBWORD_TOKEN_SEPARATOR
Expand All @@ -101,7 +111,7 @@ def __init__(self, *args, **kwargs):


class App:
def __init__(self, argv):
def __init__(self, argv: Sequence[str]) -> None:
parser = argparse.ArgumentParser(
prog="tokenize",
description="Break a text into small units (tokens).",
Expand Down Expand Up @@ -137,10 +147,10 @@ def __init__(self, argv):

argv = argv[3:]
if token_type.startswith("w"):
WordTokenizationApp("word", argv)
WordTokenizationApp("word", argv) # type: ignore[arg-type]
elif token_type.startswith("su"):
SubwordTokenizationApp("subword", argv)
SubwordTokenizationApp("subword", argv) # type: ignore[arg-type]
elif token_type.startswith("se"):
SentenceTokenizationApp("sent", argv)
SentenceTokenizationApp("sent", argv) # type: ignore[arg-type]
else:
safe_print(f"Token type not available: {token_type}")
Loading
Loading