Skip to content

Commit 26e7d41

Browse files
authored
Merge pull request #1244 from PyThaiNLP/copilot/check-distributed-operations-support
Document distributed operations support for PySpark environments
2 parents d6ce016 + bb5cf24 commit 26e7d41

5 files changed

Lines changed: 387 additions & 2 deletions

File tree

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,22 @@ For dependency details, look at the `[project.optional-dependencies]` section in
128128
- The data directory can be changed by specifying the environment variable `PYTHAINLP_DATA_DIR`.
129129
- See the data catalog (`db.json`) at <https://github.com/PyThaiNLP/pythainlp-corpus>
130130

131+
### Using PyThaiNLP in Distributed Environments
132+
133+
When using PyThaiNLP in distributed computing environments (e.g., Apache Spark), set the `PYTHAINLP_DATA_DIR` environment variable inside the function that will be distributed to worker nodes:
134+
135+
```python
136+
def tokenize_thai(text):
137+
import os
138+
os.environ['PYTHAINLP_DATA_DIR'] = './pythainlp-data'
139+
from pythainlp.tokenize import word_tokenize
140+
return word_tokenize(text)
141+
142+
rdd.map(tokenize_thai)
143+
```
144+
145+
This ensures each worker uses a local writable directory. See `examples/distributed_pyspark.py` for more examples.
146+
131147
## Command-Line Interface
132148

133149
Some of PyThaiNLP functionalities can be used via command line with the `thainlp` command.

docs/notes/installation.rst

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,67 @@ Option 2 (advanced):
6969

7070
- Building from source takes longer and requires technical knowledge, but produces a wheel optimized for your system.
7171

72+
Using PyThaiNLP in distributed environments
73+
-------------------------------------------
74+
75+
PyThaiNLP can be used in distributed computing environments such as Apache Spark. When using PyThaiNLP in these environments, you need to configure the data directory for each worker node.
76+
77+
Key considerations
78+
~~~~~~~~~~~~~~~~~~
79+
80+
1. **Set environment variables inside distributed functions**: Environment variables must be set inside the function that will be distributed to executor nodes, not in the driver program.
81+
82+
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.
83+
84+
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.
85+
86+
Example usage with Apache Spark
87+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
88+
89+
Basic example using PySpark RDD::
90+
91+
from pyspark import SparkContext
92+
93+
sc = SparkContext("local[*]", "PyThaiNLP Example")
94+
thai_texts = ["สวัสดีครับ", "ภาษาไทย"]
95+
rdd = sc.parallelize(thai_texts)
96+
97+
def tokenize_thai(text):
98+
import os
99+
os.environ['PYTHAINLP_DATA_DIR'] = './pythainlp-data'
100+
from pythainlp.tokenize import word_tokenize
101+
return word_tokenize(text)
102+
103+
tokenized_rdd = rdd.map(tokenize_thai)
104+
results = tokenized_rdd.collect()
105+
106+
Example using PySpark DataFrame API::
107+
108+
from pyspark.sql import SparkSession
109+
from pyspark.sql.functions import udf
110+
from pyspark.sql.types import ArrayType, StringType
111+
112+
spark = SparkSession.builder.appName("PyThaiNLP").getOrCreate()
113+
df = spark.createDataFrame([(1, "สวัสดีครับ")], ["id", "text"])
114+
115+
@udf(returnType=ArrayType(StringType()))
116+
def tokenize_udf(text):
117+
import os
118+
os.environ['PYTHAINLP_DATA_DIR'] = './pythainlp-data'
119+
from pythainlp.tokenize import word_tokenize
120+
return word_tokenize(text)
121+
122+
result_df = df.withColumn("tokens", tokenize_udf(df.text))
123+
124+
For more comprehensive examples including error handling, production best practices, and advanced features, see the file ``examples/distributed_pyspark.py`` in the PyThaiNLP repository.
125+
126+
Thread safety considerations
127+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
128+
129+
PyThaiNLP's core tokenization engines are thread-safe, which is important for distributed computing environments where multiple threads may process data concurrently. For detailed information about thread safety guarantees and best practices, see :doc:`threadsafe`.
130+
131+
Note that while the code itself is thread-safe, you still need to configure the data directory appropriately for distributed environments as described above.
132+
72133
Runtime configurations
73134
----------------------
74135

@@ -89,8 +150,22 @@ FAQ
89150

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

92-
A: See the discussion in `PermissionError: [Errno 13] Permission denied: /home/pythainlp-data <https://github.com/PyThaiNLP/pythainlp/issues/475>`_.
153+
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::
154+
155+
def tokenize_thai(text):
156+
import os
157+
os.environ['PYTHAINLP_DATA_DIR'] = './pythainlp-data'
158+
from pythainlp.tokenize import word_tokenize
159+
return word_tokenize(text)
160+
161+
rdd.map(tokenize_thai)
162+
163+
This ensures that each executor node uses a local data directory instead of the default home directory, which may not be writable on executor nodes.
164+
165+
For detailed examples including PySpark DataFrame API and production best practices, see ``examples/distributed_pyspark.py``.
166+
167+
For more discussion, see `PermissionError: [Errno 13] Permission denied: /home/pythainlp-data <https://github.com/PyThaiNLP/pythainlp/issues/475>`_.
93168

94169
Q: How do I enable read-only mode for PyThaiNLP?
95170

96-
A: Set the environment variable `PYTHAINLP_READ_MODE` to ``1``.
171+
A: Set the environment variable ``PYTHAINLP_READ_MODE`` to ``1``.

docs/threadsafe.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,9 @@ Related files
183183
- Core implementation: ``pythainlp/tokenize/core.py``
184184
- Engine implementations: ``pythainlp/tokenize/*.py``
185185
- Tests: ``tests/core/test_tokenize_thread_safety.py``
186+
187+
See also
188+
--------
189+
190+
- :doc:`notes/installation` - For using PyThaiNLP in distributed computing environments
191+
like Apache Spark, including configuration of data directories for distributed operations

examples/distributed_pyspark.py

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# SPDX-FileCopyrightText: 2026 PyThaiNLP Project
2+
# SPDX-FileType: SOURCE
3+
# SPDX-License-Identifier: Apache-2.0
4+
"""
5+
Example: Using PyThaiNLP in PySpark Distributed Environment
6+
7+
This example demonstrates how to use PyThaiNLP in a distributed environment
8+
like Apache Spark. The key is to set the PYTHAINLP_DATA_DIR environment
9+
variable inside the function that will be distributed to executor nodes.
10+
11+
PyThaiNLP's core tokenization engines are thread-safe, making them suitable
12+
for distributed computing environments where multiple threads may process
13+
data concurrently. For detailed information about thread safety, see:
14+
https://github.com/PyThaiNLP/pythainlp/blob/dev/docs/threadsafe.rst
15+
16+
For information about data directory configuration in distributed environments, see:
17+
https://github.com/PyThaiNLP/pythainlp/issues/475
18+
"""
19+
20+
21+
# Example 1: Basic PySpark setup with PyThaiNLP
22+
def example_basic_spark():
23+
"""
24+
Basic example showing how to tokenize Thai text in PySpark.
25+
"""
26+
from pyspark import SparkContext
27+
28+
sc = SparkContext("local[*]", "PyThaiNLP Example")
29+
30+
# Sample Thai text data
31+
thai_texts = [
32+
"สวัสดีครับ ผมชื่อจอห์น",
33+
"ภาษาไทยเป็นภาษาที่สวยงาม",
34+
"PyThaiNLP ช่วยประมวลผลภาษาไทย",
35+
]
36+
37+
rdd = sc.parallelize(thai_texts)
38+
39+
def tokenize_thai(text):
40+
"""
41+
Tokenize Thai text using PyThaiNLP.
42+
43+
IMPORTANT: Import and configure environment variables INSIDE the function
44+
that will be distributed to executor nodes.
45+
"""
46+
import os
47+
48+
# Set PYTHAINLP_DATA_DIR before importing pythainlp
49+
# Use './pythainlp-data' to store data in current working directory
50+
os.environ["PYTHAINLP_DATA_DIR"] = "./pythainlp-data"
51+
52+
# Now import pythainlp modules
53+
from pythainlp.tokenize import word_tokenize
54+
55+
# Perform tokenization
56+
return word_tokenize(text)
57+
58+
# Apply tokenization to all texts in parallel
59+
tokenized_rdd = rdd.map(tokenize_thai)
60+
61+
# Collect results
62+
results = tokenized_rdd.collect()
63+
for text, tokens in zip(thai_texts, results):
64+
print(f"Text: {text}")
65+
print(f"Tokens: {tokens}\n")
66+
67+
sc.stop()
68+
69+
70+
# Example 2: Using DataFrame API
71+
def example_dataframe_api():
72+
"""
73+
Example using PySpark DataFrame API with PyThaiNLP.
74+
"""
75+
from pyspark.sql import SparkSession
76+
from pyspark.sql.functions import udf
77+
from pyspark.sql.types import ArrayType, StringType
78+
79+
spark = SparkSession.builder.appName("PyThaiNLP DataFrame Example").getOrCreate()
80+
81+
# Create sample DataFrame
82+
data = [
83+
(1, "สวัสดีครับ ผมชื่อจอห์น"),
84+
(2, "ภาษาไทยเป็นภาษาที่สวยงาม"),
85+
(3, "PyThaiNLP ช่วยประมวลผลภาษาไทย"),
86+
]
87+
df = spark.createDataFrame(data, ["id", "text"])
88+
89+
# Define UDF for tokenization
90+
@udf(returnType=ArrayType(StringType()))
91+
def tokenize_udf(text):
92+
"""
93+
UDF for tokenizing Thai text.
94+
95+
IMPORTANT: Set environment variable and import inside the UDF.
96+
"""
97+
import os
98+
99+
os.environ["PYTHAINLP_DATA_DIR"] = "./pythainlp-data"
100+
101+
from pythainlp.tokenize import word_tokenize
102+
103+
return word_tokenize(text)
104+
105+
# Apply tokenization
106+
result_df = df.withColumn("tokens", tokenize_udf(df.text))
107+
108+
# Show results
109+
result_df.show(truncate=False)
110+
111+
spark.stop()
112+
113+
114+
# Example 3: Advanced configuration with multiple PyThaiNLP features
115+
def example_advanced():
116+
"""
117+
Advanced example using multiple PyThaiNLP features in PySpark.
118+
"""
119+
from pyspark import SparkContext
120+
121+
sc = SparkContext("local[*]", "PyThaiNLP Advanced Example")
122+
123+
thai_texts = [
124+
"สวัสดีครับ ผมชื่อจอห์น",
125+
"ภาษาไทยเป็นภาษาที่สวยงาม",
126+
]
127+
128+
rdd = sc.parallelize(thai_texts)
129+
130+
def process_thai_text(text):
131+
"""
132+
Process Thai text with multiple PyThaiNLP features.
133+
"""
134+
import os
135+
136+
# Configure data directory
137+
os.environ["PYTHAINLP_DATA_DIR"] = "./pythainlp-data"
138+
139+
# Import required modules
140+
from pythainlp.tag import pos_tag
141+
from pythainlp.tokenize import word_tokenize
142+
from pythainlp.util import normalize
143+
144+
# Normalize text
145+
normalized = normalize(text)
146+
147+
# Tokenize
148+
tokens = word_tokenize(normalized)
149+
150+
# POS tagging
151+
tagged = pos_tag(tokens)
152+
153+
return {
154+
"original": text,
155+
"normalized": normalized,
156+
"tokens": tokens,
157+
"pos_tags": tagged,
158+
}
159+
160+
# Process all texts
161+
results = rdd.map(process_thai_text).collect()
162+
163+
for result in results:
164+
print(f"Original: {result['original']}")
165+
print(f"Normalized: {result['normalized']}")
166+
print(f"Tokens: {result['tokens']}")
167+
print(f"POS Tags: {result['pos_tags']}\n")
168+
169+
sc.stop()
170+
171+
172+
# Example 4: Best practices for production environments
173+
def example_production_best_practices():
174+
"""
175+
Production-ready example with error handling and logging.
176+
"""
177+
from pyspark.sql import SparkSession
178+
179+
spark = (
180+
SparkSession.builder.appName("PyThaiNLP Production Example")
181+
.config("spark.python.worker.reuse", "true") # Reuse Python workers
182+
.getOrCreate()
183+
)
184+
185+
data = [
186+
(1, "สวัสดีครับ ผมชื่อจอห์น"),
187+
(2, "ภาษาไทยเป็นภาษาที่สวยงาม"),
188+
(3, None), # Test handling of None
189+
]
190+
df = spark.createDataFrame(data, ["id", "text"])
191+
192+
def safe_tokenize(text):
193+
"""
194+
Tokenize with error handling for production use.
195+
"""
196+
import os
197+
198+
try:
199+
# Set environment variables
200+
os.environ["PYTHAINLP_DATA_DIR"] = "./pythainlp-data"
201+
202+
# Import modules
203+
from pythainlp.tokenize import word_tokenize
204+
205+
# Handle None or empty strings
206+
if not text:
207+
return []
208+
209+
return word_tokenize(text)
210+
211+
except Exception as e:
212+
# Log error (in production, use proper logging)
213+
print(f"Error tokenizing text: {text}, Error: {str(e)}")
214+
return []
215+
216+
# Register UDF
217+
from pyspark.sql.functions import udf
218+
from pyspark.sql.types import ArrayType, StringType
219+
220+
tokenize_udf = udf(safe_tokenize, ArrayType(StringType()))
221+
222+
# Apply tokenization
223+
result_df = df.withColumn("tokens", tokenize_udf(df.text))
224+
result_df.show(truncate=False)
225+
226+
spark.stop()
227+
228+
229+
if __name__ == "__main__":
230+
print("=" * 70)
231+
print("PyThaiNLP in PySpark - Examples")
232+
print("=" * 70)
233+
print("\nNote: These examples require Apache Spark to be installed:")
234+
print(" pip install pyspark")
235+
print("\nChoose an example to run:")
236+
print("1. Basic Spark example")
237+
print("2. DataFrame API example")
238+
print("3. Advanced features example")
239+
print("4. Production best practices example")
240+
print("\nTo run a specific example, uncomment the corresponding line below:")
241+
print("=" * 70)
242+
243+
# Uncomment one of these to run:
244+
# example_basic_spark()
245+
# example_dataframe_api()
246+
# example_advanced()
247+
# example_production_best_practices()
248+
249+
print("\n✓ Examples loaded successfully!")
250+
print("Uncomment an example function call to run it.")

0 commit comments

Comments
 (0)