Skip to content

Commit 4539f31

Browse files
Copilotbact
andcommitted
Add is_offline_mode(), PYTHAINLP_DATA env var, fix PYTHAINLP_DATA_DIR deprecation
Co-authored-by: bact <128572+bact@users.noreply.github.com>
1 parent 0de2e35 commit 4539f31

10 files changed

Lines changed: 211 additions & 64 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ See PR for prompt and details.
4343
- `get_corpus_path()` now respects `PYTHAINLP_OFFLINE` env var (same semantics
4444
as `HF_HUB_OFFLINE`): when set, raises `FileNotFoundError` if the corpus is
4545
not already cached locally; when unset, auto-downloads as before #1306
46+
- Added `pythainlp.is_offline_mode()` helper function (mirrors
47+
`huggingface_hub.is_offline_mode()`) #1306
48+
- `PYTHAINLP_DATA` is now the preferred env var for the data directory
49+
(same pattern as `NLTK_DATA`); `PYTHAINLP_DATA_DIR` is deprecated
50+
and will emit a `DeprecationWarning` #1306
4651
- Callers raise `FileNotFoundError` with download instructions when a corpus
4752
path cannot be resolved (e.g. download failed) #1306
4853
- Improved documentation; code cleanup; more tests

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,27 @@ please inspect the `[project.optional-dependencies]` section of
7777
PyThaiNLP downloads data (see the data catalog `db.json` at
7878
[pythainlp-corpus](https://github.com/PyThaiNLP/pythainlp-corpus))
7979
to `~/pythainlp-data` by default.
80-
Set the `PYTHAINLP_DATA_DIR` environment variable to override this location.
80+
Set the `PYTHAINLP_DATA` environment variable to override this location.
81+
(`PYTHAINLP_DATA_DIR` is still accepted but deprecated.)
8182

8283
When using PyThaiNLP in distributed computing environments
83-
(e.g., Apache Spark), set the `PYTHAINLP_DATA_DIR` environment variable
84+
(e.g., Apache Spark), set the `PYTHAINLP_DATA` environment variable
8485
inside the function that will be distributed to worker nodes.
8586
See details in
8687
[the documentation](https://pythainlp.org/dev-docs/notes/installation.html).
8788

89+
### Offline mode
90+
91+
Set `PYTHAINLP_OFFLINE=1` to disable automatic corpus downloads.
92+
When this variable is set and a corpus is not already cached locally,
93+
a `FileNotFoundError` is raised instead of attempting a network download.
94+
Use `pythainlp.is_offline_mode()` to check the current state programmatically.
95+
96+
```python
97+
import pythainlp
98+
print(pythainlp.is_offline_mode()) # True if PYTHAINLP_OFFLINE=1
99+
```
100+
88101
## Testing
89102

90103
We test core functionalities on all officially supported Python versions.

README_TH.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,11 @@ pip install "pythainlp[extra1,extra2,...]"
109109
PyThaiNLP ดาวน์โหลดข้อมูล (ดูแค็ตตาล็อกข้อมูล `db.json` ที่
110110
[pythainlp-corpus](https://github.com/PyThaiNLP/pythainlp-corpus))
111111
ไปที่ `~/pythainlp-data` ตามค่าเริ่มต้น
112-
ตั้งค่า environment variable `PYTHAINLP_DATA_DIR` เพื่อเปลี่ยนตำแหน่งนี้
112+
ตั้งค่า environment variable `PYTHAINLP_DATA` เพื่อเปลี่ยนตำแหน่งนี้
113+
(`PYTHAINLP_DATA_DIR` ยังคงใช้ได้แต่เลิกใช้แล้ว)
113114

114115
เมื่อใช้ PyThaiNLP ในสภาพแวดล้อมการคำนวณแบบกระจาย
115-
(เช่น Apache Spark) ให้ตั้งค่า environment variable `PYTHAINLP_DATA_DIR`
116+
(เช่น Apache Spark) ให้ตั้งค่า environment variable `PYTHAINLP_DATA`
116117
ภายในฟังก์ชันที่จะถูกกระจายไปยัง worker nodes
117118
ดูรายละเอียดใน[เอกสาร](https://pythainlp.org/dev-docs/notes/installation.html)
118119

docs/notes/installation.rst

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ Key considerations
8989

9090
2. **Use a writable local directory**: The default data directory (``~/pythainlp-data``) may not be writable on executor nodes. Use a local directory like ``./pythainlp-data`` instead.
9191

92-
3. **Set ``PYTHAINLP_DATA_DIR`` before data access**: Always set the ``PYTHAINLP_DATA_DIR`` environment variable before the first call that reads or writes PyThaiNLP data on each worker.
92+
3. **Set ``PYTHAINLP_DATA`` before data access**: Always set the ``PYTHAINLP_DATA`` environment variable before the first call that reads or writes PyThaiNLP data on each worker.
93+
(``PYTHAINLP_DATA_DIR`` is also accepted for backward compatibility but is deprecated.)
9394

9495
Example usage with Apache Spark
9596
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -104,7 +105,7 @@ Basic example using PySpark RDD::
104105

105106
def tokenize_thai(text):
106107
import os
107-
os.environ['PYTHAINLP_DATA_DIR'] = './pythainlp-data'
108+
os.environ['PYTHAINLP_DATA'] = './pythainlp-data'
108109
from pythainlp.tokenize import word_tokenize
109110
return word_tokenize(text)
110111

@@ -123,7 +124,7 @@ Example using PySpark DataFrame API::
123124
@udf(returnType=ArrayType(StringType()))
124125
def tokenize_udf(text):
125126
import os
126-
os.environ['PYTHAINLP_DATA_DIR'] = './pythainlp-data'
127+
os.environ['PYTHAINLP_DATA'] = './pythainlp-data'
127128
from pythainlp.tokenize import word_tokenize
128129
return word_tokenize(text)
129130

@@ -141,13 +142,30 @@ Note that while the code itself is thread-safe, you still need to configure the
141142
Runtime configurations
142143
----------------------
143144

144-
.. envvar:: PYTHAINLP_DATA_DIR
145+
.. envvar:: PYTHAINLP_DATA
145146

146147
Specifies the location where downloaded data and the corpus database are stored. If the directory does not exist, PyThaiNLP will create it.
147148

148149
By default this is a directory named ``pythainlp-data`` in the user's home directory.
149150

150-
Run ``thainlp data path`` at the command line to display the current `PYTHAINLP_DATA_DIR`.
151+
Run ``thainlp data path`` at the command line to display the current data directory.
152+
153+
.. envvar:: PYTHAINLP_DATA_DIR
154+
155+
.. deprecated::
156+
Use :envvar:`PYTHAINLP_DATA` instead. Setting ``PYTHAINLP_DATA_DIR`` triggers a
157+
:class:`DeprecationWarning` at runtime. If both ``PYTHAINLP_DATA`` and ``PYTHAINLP_DATA_DIR``
158+
are set simultaneously, PyThaiNLP raises :exc:`ValueError`.
159+
160+
.. envvar:: PYTHAINLP_OFFLINE
161+
162+
When set to a truthy value (``1``, ``true``, ``yes``, ``on``), PyThaiNLP operates in
163+
*offline mode*: corpus downloads are disabled, and :func:`pythainlp.corpus.get_corpus_path`
164+
raises :exc:`FileNotFoundError` for any corpus that is not already cached locally.
165+
166+
Use :func:`pythainlp.is_offline_mode` to check the current state programmatically.
167+
168+
This follows the same convention as ``HF_HUB_OFFLINE`` in `huggingface_hub`.
151169

152170
.. envvar:: PYTHAINLP_READ_MODE
153171

@@ -158,11 +176,11 @@ Installation FAQ
158176

159177
Q: How do I set environment variables on each executor node in a distributed environment?
160178

161-
A: When using PyThaiNLP in distributed computing environments like Apache Spark, you need to set the ``PYTHAINLP_DATA_DIR`` environment variable inside the function that will be distributed to executor nodes. For example::
179+
A: When using PyThaiNLP in distributed computing environments like Apache Spark, you need to set the ``PYTHAINLP_DATA`` environment variable inside the function that will be distributed to executor nodes. For example::
162180

163181
def tokenize_thai(text):
164182
import os
165-
os.environ['PYTHAINLP_DATA_DIR'] = './pythainlp-data'
183+
os.environ['PYTHAINLP_DATA'] = './pythainlp-data'
166184
from pythainlp.tokenize import word_tokenize
167185
return word_tokenize(text)
168186

pythainlp/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
__all__: list[str] = [
5656
"collate",
5757
"correct",
58+
"is_offline_mode",
5859
"pos_tag",
5960
"romanize",
6061
"spell",
@@ -76,5 +77,6 @@
7677
subword_tokenize,
7778
word_tokenize,
7879
)
80+
from pythainlp.tools.path import is_offline_mode
7981
from pythainlp.transliterate import romanize, transliterate
8082
from pythainlp.util import collate, thai_strftime

pythainlp/cli/data.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def __init__(self, argv: Sequence[str]) -> None:
3333
"Current data path:\n\n"
3434
f"{get_pythainlp_data_path()}\n\n"
3535
"To change PyThaiNLP data path, set the operating system's\n"
36-
"PYTHAINLP_DATA_DIR environment variable.\n\n"
36+
"PYTHAINLP_DATA environment variable.\n"
37+
"(PYTHAINLP_DATA_DIR is also accepted but deprecated.)\n\n"
3738
"For more information about corpora that PyThaiNLP use, see:\n"
3839
"https://github.com/PyThaiNLP/pythainlp-corpus/\n\n"
3940
"--"

pythainlp/corpus/core.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from pythainlp import __version__
1919
from pythainlp.corpus import corpus_db_path, corpus_db_url, corpus_path
2020
from pythainlp.tools import get_full_data_path
21+
from pythainlp.tools.path import is_offline_mode
2122

2223
if TYPE_CHECKING:
2324
from typing import Any, Optional
@@ -30,19 +31,6 @@
3031
)
3132

3233

33-
def _is_offline() -> bool:
34-
"""Return True if ``PYTHAINLP_OFFLINE`` env var is set to a truthy value.
35-
36-
Truthy values: any non-empty string other than ``"0"``, ``"false"``,
37-
``"no"``, and ``"off"`` (case-insensitive).
38-
39-
This follows the same convention as ``HF_HUB_OFFLINE`` in
40-
`huggingface_hub`.
41-
"""
42-
val = os.getenv("PYTHAINLP_OFFLINE", "")
43-
return val.strip().lower() not in ("", "0", "false", "no", "off")
44-
45-
4634
class _ResponseWrapper:
4735
"""Wrapper to provide requests.Response-like interface for urllib response."""
4836

@@ -343,7 +331,7 @@ def get_corpus_path(name: str, version: str = "") -> Optional[str]:
343331
corpus_db_detail = get_corpus_db_detail(name, version=version)
344332
if not corpus_db_detail:
345333
# Corpus not in local catalog; download it unless in offline mode
346-
if _is_offline():
334+
if is_offline_mode():
347335
raise FileNotFoundError(
348336
f"Corpus '{name}' not found locally. "
349337
f"PYTHAINLP_OFFLINE is set; automatic downloading is disabled. "
@@ -364,7 +352,7 @@ def get_corpus_path(name: str, version: str = "") -> Optional[str]:
364352
return path
365353

366354
# File is registered in catalog but missing from disk
367-
if _is_offline():
355+
if is_offline_mode():
368356
raise FileNotFoundError(
369357
f"Corpus '{name}' expected at '{path}' but file not found. "
370358
f"PYTHAINLP_OFFLINE is set; automatic re-downloading is disabled. "
@@ -663,7 +651,7 @@ def download(
663651
if _CHECK_MODE == "1":
664652
print("PyThaiNLP is read-only mode. It can't download.")
665653
return False
666-
if _is_offline():
654+
if is_offline_mode():
667655
print(
668656
"PYTHAINLP_OFFLINE is set. Cannot download. "
669657
"To enable downloading, unset PYTHAINLP_OFFLINE."

pythainlp/tools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"get_full_data_path",
77
"get_pythainlp_data_path",
88
"get_pythainlp_path",
9+
"is_offline_mode",
910
"safe_print",
1011
"warn_deprecation",
1112
]
@@ -16,4 +17,5 @@
1617
get_full_data_path,
1718
get_pythainlp_data_path,
1819
get_pythainlp_path,
20+
is_offline_mode,
1921
)

pythainlp/tools/path.py

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,41 @@
2121
PYTHAINLP_DEFAULT_DATA_DIR: str = "pythainlp-data"
2222

2323

24+
def is_offline_mode() -> bool:
25+
"""Return whether PyThaiNLP is operating in offline mode.
26+
27+
Offline mode is activated by setting the ``PYTHAINLP_OFFLINE``
28+
environment variable to a truthy value (e.g. ``"1"``).
29+
Falsy values (``""``, ``"0"``, ``"false"``, ``"no"``, ``"off"``)
30+
keep online mode active.
31+
32+
This follows the same convention as ``HF_HUB_OFFLINE`` in
33+
`huggingface_hub`.
34+
35+
When offline mode is active, :func:`pythainlp.corpus.get_corpus_path`
36+
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.
39+
40+
:return: ``True`` if PyThaiNLP is in offline mode, ``False`` otherwise.
41+
:rtype: bool
42+
43+
:Example:
44+
::
45+
46+
import os
47+
from pythainlp import is_offline_mode
48+
49+
os.environ["PYTHAINLP_OFFLINE"] = "1"
50+
print(is_offline_mode()) # True
51+
52+
os.environ["PYTHAINLP_OFFLINE"] = "0"
53+
print(is_offline_mode()) # False
54+
"""
55+
val = os.getenv("PYTHAINLP_OFFLINE", "")
56+
return val.strip().lower() not in ("", "0", "false", "no", "off")
57+
58+
2459
def get_full_data_path(path: str) -> str:
2560
"""This function joins path of :mod:`pythainlp` data directory and the
2661
given path, and returns the full path.
@@ -40,11 +75,24 @@ def get_full_data_path(path: str) -> str:
4075

4176

4277
def get_pythainlp_data_path() -> str:
43-
"""Returns the full path where PyThaiNLP keeps its (downloaded) data.
44-
If the directory does not yet exist, it will be created.
45-
The path can be specified through the environment variable
46-
:envvar:`PYTHAINLP_DATA_DIR`. By default, `~/pythainlp-data`
47-
will be used.
78+
"""Return the full path where PyThaiNLP keeps its (downloaded) data.
79+
80+
The directory is created if it does not yet exist.
81+
82+
The path is resolved in the following order:
83+
84+
1. ``PYTHAINLP_DATA`` environment variable (preferred).
85+
2. ``PYTHAINLP_DATA_DIR`` environment variable
86+
(deprecated; shows a warning).
87+
3. If **both** variables are set, the function raises
88+
:exc:`ValueError` because the conflict must be resolved
89+
explicitly.
90+
4. If neither is set, ``~/pythainlp-data`` is used.
91+
92+
.. deprecated::
93+
``PYTHAINLP_DATA_DIR`` is deprecated.
94+
Use ``PYTHAINLP_DATA`` instead (follows the same pattern as
95+
``NLTK_DATA``).
4896
4997
:return: full path of directory for :mod:`pythainlp` downloaded data
5098
:rtype: str
@@ -57,10 +105,27 @@ def get_pythainlp_data_path() -> str:
57105
get_pythainlp_data_path()
58106
# output: '/root/pythainlp-data'
59107
"""
60-
pythainlp_data_dir = os.getenv(
61-
"PYTHAINLP_DATA_DIR", os.path.join("~", PYTHAINLP_DEFAULT_DATA_DIR)
62-
)
63-
path = os.path.expanduser(pythainlp_data_dir)
108+
import warnings
109+
110+
data_dir = os.getenv("PYTHAINLP_DATA")
111+
data_dir_legacy = os.getenv("PYTHAINLP_DATA_DIR")
112+
113+
if data_dir and data_dir_legacy:
114+
raise ValueError(
115+
"Both PYTHAINLP_DATA and PYTHAINLP_DATA_DIR are set. "
116+
"Please use PYTHAINLP_DATA only and unset PYTHAINLP_DATA_DIR."
117+
)
118+
119+
if data_dir_legacy and not data_dir:
120+
warnings.warn(
121+
"PYTHAINLP_DATA_DIR is deprecated; use PYTHAINLP_DATA instead.",
122+
DeprecationWarning,
123+
stacklevel=2,
124+
)
125+
data_dir = data_dir_legacy
126+
127+
resolved = data_dir or os.path.join("~", PYTHAINLP_DEFAULT_DATA_DIR)
128+
path = os.path.expanduser(resolved)
64129
os.makedirs(path, exist_ok=True)
65130
return path
66131

0 commit comments

Comments
 (0)