Skip to content

Commit d8185be

Browse files
Copilotbact
andcommitted
Allow explicit download() to bypass PYTHAINLP_OFFLINE; add CLI hint to all error messages
Co-authored-by: bact <128572+bact@users.noreply.github.com>
1 parent 4539f31 commit d8185be

17 files changed

Lines changed: 123 additions & 59 deletions

File tree

pythainlp/augment/word2vec/ltw2v.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ def tokenizer(self, text: str) -> list[str]:
3333
def load_w2v(self) -> None: # insert substitute
3434
"""Load LTW2V's word2vec model"""
3535
if not self.ltw2v_wv:
36-
raise ValueError(
37-
"LTW2V word2vec model not found. "
38-
"Please download it first using pythainlp.corpus.download('ltw2v_wv')"
36+
raise FileNotFoundError(
37+
"corpus-not-found name='ltw2v_wv'\n"
38+
" Corpus 'ltw2v_wv' not found.\n"
39+
" Python: pythainlp.corpus.download('ltw2v_wv')\n"
40+
" CLI: thainlp data get ltw2v_wv"
3941
)
4042
self.aug: Word2VecAug = Word2VecAug(
4143
self.ltw2v_wv, self.tokenizer, type="binary"

pythainlp/augment/word2vec/thai2fit.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ def tokenizer(self, text: str) -> list[str]:
3434
def load_w2v(self) -> None:
3535
"""Load Thai2Fit's word2vec model"""
3636
if not self.thai2fit_wv:
37-
raise ValueError(
38-
"Thai2Fit word2vec model not found. "
39-
"Please download it first using pythainlp.corpus.download('thai2fit_wv')"
37+
raise FileNotFoundError(
38+
"corpus-not-found name='thai2fit_wv'\n"
39+
" Corpus 'thai2fit_wv' not found.\n"
40+
" Python: pythainlp.corpus.download('thai2fit_wv')\n"
41+
" CLI: thainlp data get thai2fit_wv"
4042
)
4143
self.aug: Word2VecAug = Word2VecAug(
4244
self.thai2fit_wv, self.tokenizer, type="binary"

pythainlp/corpus/core.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -333,10 +333,12 @@ def get_corpus_path(name: str, version: str = "") -> Optional[str]:
333333
# Corpus not in local catalog; download it unless in offline mode
334334
if is_offline_mode():
335335
raise FileNotFoundError(
336-
f"Corpus '{name}' not found locally. "
337-
f"PYTHAINLP_OFFLINE is set; automatic downloading is disabled. "
338-
f"To download, unset PYTHAINLP_OFFLINE and run: "
339-
f"pythainlp.corpus.download('{name}')"
336+
f"corpus-not-found name={name!r}\n"
337+
f" Corpus '{name}' not found locally.\n"
338+
f" PYTHAINLP_OFFLINE is set; automatic downloading is disabled.\n"
339+
f" To download, unset PYTHAINLP_OFFLINE, then run:\n"
340+
f" Python: pythainlp.corpus.download('{name}')\n"
341+
f" CLI: thainlp data get {name}"
340342
)
341343
if not download(name, version=version):
342344
return None
@@ -354,10 +356,12 @@ def get_corpus_path(name: str, version: str = "") -> Optional[str]:
354356
# File is registered in catalog but missing from disk
355357
if is_offline_mode():
356358
raise FileNotFoundError(
357-
f"Corpus '{name}' expected at '{path}' but file not found. "
358-
f"PYTHAINLP_OFFLINE is set; automatic re-downloading is disabled. "
359-
f"To re-download, unset PYTHAINLP_OFFLINE and run: "
360-
f"pythainlp.corpus.download('{name}', force=True)"
359+
f"corpus-not-found name={name!r} expected-path={path!r}\n"
360+
f" Corpus '{name}' expected at '{path}' but file not found.\n"
361+
f" PYTHAINLP_OFFLINE is set; automatic re-downloading is disabled.\n"
362+
f" To re-download, unset PYTHAINLP_OFFLINE, then run:\n"
363+
f" Python: pythainlp.corpus.download('{name}', force=True)\n"
364+
f" CLI: thainlp data get {name}"
361365
)
362366
if not download(name, version=version, force=True):
363367
return None
@@ -625,6 +629,12 @@ def download(
625629
The available corpus names can be seen in this file:
626630
https://pythainlp.org/pythainlp-corpus/db.json
627631
632+
This function always performs the download regardless of the
633+
``PYTHAINLP_OFFLINE`` environment variable, because an explicit call
634+
to ``download()`` is a deliberate user action.
635+
``PYTHAINLP_OFFLINE`` only blocks the *automatic* download triggered
636+
by :func:`pythainlp.corpus.get_corpus_path`.
637+
628638
:param str name: corpus name
629639
:param bool force: force downloading
630640
:param str url: URL of the corpus catalog
@@ -651,12 +661,7 @@ def download(
651661
if _CHECK_MODE == "1":
652662
print("PyThaiNLP is read-only mode. It can't download.")
653663
return False
654-
if is_offline_mode():
655-
print(
656-
"PYTHAINLP_OFFLINE is set. Cannot download. "
657-
"To enable downloading, unset PYTHAINLP_OFFLINE."
658-
)
659-
return False
664+
660665
if not url:
661666
url = corpus_db_url()
662667

@@ -833,8 +838,10 @@ def get_path_folder_corpus(name: str, version: str, *path: str) -> str:
833838
corpus_path = get_corpus_path(name, version)
834839
if not corpus_path:
835840
raise FileNotFoundError(
836-
f"Corpus '{name}' (version {version}) not found. "
837-
f"To download: pythainlp.corpus.download('{name}')"
841+
f"corpus-not-found name={name!r} version={version!r}\n"
842+
f" Corpus '{name}' (version {version}) not found.\n"
843+
f" Python: pythainlp.corpus.download('{name}')\n"
844+
f" CLI: thainlp data get {name}"
838845
)
839846
return os.path.join(corpus_path, *path)
840847

pythainlp/generate/thai2fit.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,13 @@
5959

6060
# Validate that corpus files are available
6161
if thwiki["itos_fname"] is None or thwiki["wgts_fname"] is None:
62-
raise RuntimeError(
63-
"Thai2fit model files not found. "
64-
"Please download the corpus first:\n"
65-
" pythainlp.corpus.download('wiki_lm_lstm')\n"
66-
" pythainlp.corpus.download('wiki_itos_lstm')"
62+
raise FileNotFoundError(
63+
"corpus-not-found names=['wiki_lm_lstm', 'wiki_itos_lstm']\n"
64+
" Thai2fit model files not found.\n"
65+
" Python: pythainlp.corpus.download('wiki_lm_lstm')\n"
66+
" CLI: thainlp data get wiki_lm_lstm\n"
67+
" Python: pythainlp.corpus.download('wiki_itos_lstm')\n"
68+
" CLI: thainlp data get wiki_itos_lstm"
6769
)
6870

6971
# Security Note: This loads a pickle file from PyThaiNLP's trusted corpus.

pythainlp/spell/symspellpy.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ def _get_sym_spell() -> SymSpell:
6868
bigram_path = get_corpus_path(_BIGRAM_CORPUS_NAME)
6969
if not bigram_path:
7070
raise FileNotFoundError(
71-
f"Corpus '{_BIGRAM_CORPUS_NAME}' not found. "
72-
f"To download: pythainlp.corpus.download('{_BIGRAM_CORPUS_NAME}')"
71+
f"corpus-not-found name={_BIGRAM_CORPUS_NAME!r}\n"
72+
f" Corpus '{_BIGRAM_CORPUS_NAME}' not found.\n"
73+
f" Python: pythainlp.corpus.download('{_BIGRAM_CORPUS_NAME}')\n"
74+
f" CLI: thainlp data get {_BIGRAM_CORPUS_NAME}"
7375
)
7476
_sym_spell.load_bigram_dictionary(
7577
bigram_path,

pythainlp/tag/perceptron.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ def _blackboard_tagger() -> PerceptronTagger:
5151
if not _BLACKBOARD_TAGGER:
5252
path = get_corpus_path(_BLACKBOARD_NAME)
5353
if not path:
54-
raise ValueError(f"Corpus path not found for {_BLACKBOARD_NAME}")
54+
raise FileNotFoundError(
55+
f"corpus-not-found name={_BLACKBOARD_NAME!r}\n"
56+
f" Corpus '{_BLACKBOARD_NAME}' not found.\n"
57+
f" Python: pythainlp.corpus.download('{_BLACKBOARD_NAME}')\n"
58+
f" CLI: thainlp data get {_BLACKBOARD_NAME}"
59+
)
5560
_BLACKBOARD_TAGGER = PerceptronTagger(path=path)
5661
return _BLACKBOARD_TAGGER
5762

pythainlp/tag/thai_nner.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,10 @@ def __init__(self, path_model: Optional[str] = None) -> None:
122122
path_model = get_corpus_path("thai_nner", "1.0")
123123
if not path_model:
124124
raise FileNotFoundError(
125-
"Corpus 'thai_nner' not found. "
126-
"To download: pythainlp.corpus.download('thai_nner')"
125+
"corpus-not-found name='thai_nner'\n"
126+
" Corpus 'thai_nner' not found.\n"
127+
" Python: pythainlp.corpus.download('thai_nner')\n"
128+
" CLI: thainlp data get thai_nner"
127129
)
128130

129131
# Import inside __init__ (not at module level) to allow:

pythainlp/tag/thainer.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,22 @@ def __init__(self, version: str = "1.4") -> None:
109109
if version == "1.4":
110110
model_path = get_corpus_path("thainer-1.4", version="1.4")
111111
if not model_path:
112-
raise RuntimeError(
113-
"ThaiNER 1.4 model not found. "
114-
"Please download the corpus first:\n"
115-
" pythainlp.corpus.download('thainer-1.4')"
112+
raise FileNotFoundError(
113+
"corpus-not-found name='thainer-1.4'\n"
114+
" Corpus 'thainer-1.4' not found.\n"
115+
" Python: pythainlp.corpus.download('thainer-1.4')\n"
116+
" CLI: thainlp data get thainer-1.4"
116117
)
117118
self.crf.open(model_path)
118119
self.pos_tag_name: str = "orchid_ud"
119120
elif version == "1.5":
120121
model_path = get_corpus_path("thainer", version="1.5")
121122
if not model_path:
122-
raise RuntimeError(
123-
"ThaiNER 1.5 model not found. "
124-
"Please download the corpus first:\n"
125-
" pythainlp.corpus.download('thainer')"
123+
raise FileNotFoundError(
124+
"corpus-not-found name='thainer'\n"
125+
" Corpus 'thainer' not found.\n"
126+
" Python: pythainlp.corpus.download('thainer')\n"
127+
" CLI: thainlp data get thainer"
126128
)
127129
self.crf.open(model_path)
128130
self.pos_tag_name = "blackboard"

pythainlp/tag/unigram.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ def _blackboard_tagger() -> dict[str, str]:
5454
if not _BLACKBOARD_TAGGER:
5555
path = get_corpus_path(_BLACKBOARD_NAME)
5656
if not path:
57-
raise ValueError(f"Corpus path not found for {_BLACKBOARD_NAME}")
57+
raise FileNotFoundError(
58+
f"corpus-not-found name={_BLACKBOARD_NAME!r}\n"
59+
f" Corpus '{_BLACKBOARD_NAME}' not found.\n"
60+
f" Python: pythainlp.corpus.download('{_BLACKBOARD_NAME}')\n"
61+
f" CLI: thainlp data get {_BLACKBOARD_NAME}"
62+
)
5863
with open(path, encoding="utf-8-sig") as fh:
5964
_BLACKBOARD_TAGGER = json.load(fh)
6065
return _BLACKBOARD_TAGGER

pythainlp/tools/path.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,14 @@ def is_offline_mode() -> bool:
3434
3535
When offline mode is active, :func:`pythainlp.corpus.get_corpus_path`
3636
raises :exc:`FileNotFoundError` for any corpus that is not already
37-
cached locally, and :func:`pythainlp.corpus.download` refuses to
38-
fetch anything from the network.
37+
cached locally, instead of triggering an automatic download.
38+
39+
.. note::
40+
:func:`pythainlp.corpus.download` always executes regardless of
41+
this setting, because an explicit call to ``download()`` or
42+
``thainlp data get`` is a deliberate user action.
43+
``PYTHAINLP_OFFLINE`` only prevents *automatic* downloads
44+
initiated by :func:`~pythainlp.corpus.get_corpus_path`.
3945
4046
:return: ``True`` if PyThaiNLP is in offline mode, ``False`` otherwise.
4147
:rtype: bool

0 commit comments

Comments
 (0)