toolkitx 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toolkitx/__init__.py +13 -0
- toolkitx/hello.py +2 -0
- toolkitx/lab/__init__.py +0 -0
- toolkitx/lab/translator.py +230 -0
- toolkitx/text_utils.py +160 -0
- toolkitx-0.0.1.dist-info/METADATA +66 -0
- toolkitx-0.0.1.dist-info/RECORD +10 -0
- toolkitx-0.0.1.dist-info/WHEEL +4 -0
- toolkitx-0.0.1.dist-info/entry_points.txt +2 -0
- toolkitx-0.0.1.dist-info/licenses/LICENSE +24 -0
toolkitx/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 从子模块导入,使其可以直接从 toolkitx 包导入
|
|
2
|
+
from .hello import hello
|
|
3
|
+
from .text_utils import truncate_text_smart, split_text_by_word_count
|
|
4
|
+
|
|
5
|
+
# 定义 __all__,当用户使用 from toolkitx import * 时,会导入这些符号
|
|
6
|
+
__all__ = [
|
|
7
|
+
"hello",
|
|
8
|
+
"truncate_text_smart",
|
|
9
|
+
"split_text_by_word_count"
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
# 你也可以在这里定义包级别的版本号等元数据
|
|
13
|
+
__version__ = "0.0.1" # 与 pyproject.toml 中的 version 一致
|
toolkitx/hello.py
ADDED
toolkitx/lab/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Optional, Literal
|
|
4
|
+
from diskcache import Cache
|
|
5
|
+
from transgpt.trans_baidu import BaiduTranslation
|
|
6
|
+
from transgpt.trans_tencent import TencentTranslation
|
|
7
|
+
import hashlib
|
|
8
|
+
|
|
9
|
+
# 定义支持的翻译引擎类型
|
|
10
|
+
SUPPORTED_ENGINES = Literal["baidu", "tencent"]
|
|
11
|
+
|
|
12
|
+
class TranslatorInterface(ABC):
|
|
13
|
+
"""
|
|
14
|
+
Abstract base class for a translator.
|
|
15
|
+
Defines the common interface for translation services.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def translate(self, text: str, target_lang: str, source_lang: str = 'auto') -> str:
|
|
20
|
+
"""
|
|
21
|
+
Translate text from a source language to a target language.
|
|
22
|
+
|
|
23
|
+
:param text: The text to translate.
|
|
24
|
+
:param target_lang: The target language code (e.g., 'en', 'zh').
|
|
25
|
+
:param source_lang: The source language code (e.g., 'auto', 'en', 'zh').
|
|
26
|
+
:return: The translated text.
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def clear_cache(self) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Clear the translation cache.
|
|
34
|
+
"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Translator(TranslatorInterface):
|
|
39
|
+
"""
|
|
40
|
+
A translator implementation using py-transgpt with disk-based caching.
|
|
41
|
+
Supports Baidu and Tencent translation engines.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
engine: SUPPORTED_ENGINES,
|
|
47
|
+
cache_path: str,
|
|
48
|
+
api_id: Optional[str] = None,
|
|
49
|
+
api_key: Optional[str] = None,
|
|
50
|
+
target_lang: str = 'en', # Default target language
|
|
51
|
+
source_lang: str = 'auto' # Default source language
|
|
52
|
+
):
|
|
53
|
+
"""
|
|
54
|
+
Initialize the Translator.
|
|
55
|
+
|
|
56
|
+
:param engine: The translation engine to use ('baidu' or 'tencent').
|
|
57
|
+
:param cache_path: Path to the directory for storing the translation cache.
|
|
58
|
+
:param api_id: API ID for the translation service. If None, attempts to load from ENV.
|
|
59
|
+
For Tencent, this is SecretId.
|
|
60
|
+
:param api_key: API Key for the translation service. If None, attempts to load from ENV.
|
|
61
|
+
For Tencent, this is SecretKey.
|
|
62
|
+
:param target_lang: Default target language for translations.
|
|
63
|
+
:param source_lang: Default source language for translations.
|
|
64
|
+
:raises ValueError: If the engine is not supported or API credentials are not found.
|
|
65
|
+
"""
|
|
66
|
+
if engine not in ["baidu", "tencent"]:
|
|
67
|
+
raise ValueError(f"Unsupported engine: {engine}. Supported engines are 'baidu', 'tencent'.")
|
|
68
|
+
|
|
69
|
+
self.engine_name = engine
|
|
70
|
+
self.cache = Cache(cache_path)
|
|
71
|
+
self.default_target_lang = target_lang
|
|
72
|
+
self.default_source_lang = source_lang
|
|
73
|
+
|
|
74
|
+
# Load API credentials
|
|
75
|
+
env_api_id_name = ""
|
|
76
|
+
env_api_key_name = ""
|
|
77
|
+
|
|
78
|
+
if self.engine_name == "baidu":
|
|
79
|
+
env_api_id_name = "BAIDU_API_ID"
|
|
80
|
+
env_api_key_name = "BAIDU_API_KEY"
|
|
81
|
+
_api_id = api_id or os.getenv(env_api_id_name)
|
|
82
|
+
_api_key = api_key or os.getenv(env_api_key_name)
|
|
83
|
+
if not _api_id or not _api_key:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"Baidu API ID and API Key are required. "
|
|
86
|
+
f"Provide them as arguments or set {env_api_id_name} and {env_api_key_name} environment variables."
|
|
87
|
+
)
|
|
88
|
+
self.translator_instance = BaiduTranslation(api_id=_api_id, api_key=_api_key)
|
|
89
|
+
|
|
90
|
+
elif self.engine_name == "tencent":
|
|
91
|
+
env_api_id_name = "TENCENT_API_ID"
|
|
92
|
+
env_api_key_name = "TENCENT_API_KEY"
|
|
93
|
+
_api_id = api_id or os.getenv(env_api_id_name)
|
|
94
|
+
_api_key = api_key or os.getenv(env_api_key_name)
|
|
95
|
+
if not _api_id or not _api_key:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"Tencent Secret ID and Secret Key are required. "
|
|
98
|
+
f"Provide them as arguments or set {env_api_id_name} and {env_api_key_name} environment variables."
|
|
99
|
+
)
|
|
100
|
+
self.translator_instance = TencentTranslation(api_id=_api_id, api_key=_api_key)
|
|
101
|
+
|
|
102
|
+
def _create_cache_key(self, text: str, target_lang: str, source_lang: str) -> str:
|
|
103
|
+
"""Helper to create a unique cache key."""
|
|
104
|
+
return f"{self.engine_name}:{source_lang}:{target_lang}:{hashlib.md5(text.encode("utf8")).hexdigest()}"
|
|
105
|
+
|
|
106
|
+
def translate(self, text: str, target_lang: Optional[str] = None, source_lang: Optional[str] = None) -> str:
|
|
107
|
+
"""
|
|
108
|
+
Translate text using the configured engine and cache.
|
|
109
|
+
|
|
110
|
+
:param text: The text to translate.
|
|
111
|
+
:param target_lang: The target language code. Defaults to instance's default_target_lang.
|
|
112
|
+
:param source_lang: The source language code. Defaults to instance's default_source_lang.
|
|
113
|
+
:return: The translated text.
|
|
114
|
+
"""
|
|
115
|
+
_target_lang = target_lang or self.default_target_lang
|
|
116
|
+
_source_lang = source_lang or self.default_source_lang
|
|
117
|
+
|
|
118
|
+
if not text:
|
|
119
|
+
return ""
|
|
120
|
+
|
|
121
|
+
cache_key = self._create_cache_key(text, _target_lang, _source_lang)
|
|
122
|
+
cached_translation = self.cache.get(cache_key)
|
|
123
|
+
|
|
124
|
+
if cached_translation is not None:
|
|
125
|
+
return cached_translation
|
|
126
|
+
print("no cache, cache_key:",cache_key)
|
|
127
|
+
# print(f"Cache miss. Translating: {text[:30]}...") # For debugging
|
|
128
|
+
try:
|
|
129
|
+
# py-transgpt's translate method takes (text, target_language, source_language)
|
|
130
|
+
translated_text = self.translator_instance.translate(text, _target_lang, _source_lang)
|
|
131
|
+
print("set cache:",text,translated_text)
|
|
132
|
+
if translated_text: # Ensure we don't cache None or empty if translation fails silently
|
|
133
|
+
self.cache.set(cache_key, translated_text)
|
|
134
|
+
return translated_text
|
|
135
|
+
except Exception as e:
|
|
136
|
+
# Log error or handle as needed
|
|
137
|
+
print(f"Error during translation with {self.engine_name}: {e}")
|
|
138
|
+
# Depending on requirements, you might want to re-raise or return original text/error message
|
|
139
|
+
raise # Re-raise the exception to make the caller aware
|
|
140
|
+
|
|
141
|
+
def clear_cache(self) -> None:
|
|
142
|
+
"""
|
|
143
|
+
Clear all items from the translation cache for this translator instance.
|
|
144
|
+
"""
|
|
145
|
+
self.cache.clear()
|
|
146
|
+
print(f"Cache cleared for path: {self.cache.directory}")
|
|
147
|
+
|
|
148
|
+
def close_cache(self) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Close the cache. Important to call when done if cache is not used as a context manager.
|
|
151
|
+
"""
|
|
152
|
+
self.cache.close()
|
|
153
|
+
|
|
154
|
+
# Example Usage (you can put this in a separate test file or a __main__ block)
|
|
155
|
+
if __name__ == "__main__":
|
|
156
|
+
# Ensure you have set your environment variables for BAIDU_API_ID, BAIDU_API_KEY
|
|
157
|
+
# or TENCENT_SECRET_ID, TENCENT_SECRET_KEY, or pass them directly.
|
|
158
|
+
|
|
159
|
+
# Example for Baidu (assuming ENV VARS are set)
|
|
160
|
+
try:
|
|
161
|
+
print("Testing Baidu Translator...")
|
|
162
|
+
# Create a directory for cache if it doesn't exist
|
|
163
|
+
baidu_cache_dir = "/tmp/translator_cache/baidu"
|
|
164
|
+
os.makedirs(baidu_cache_dir, exist_ok=True)
|
|
165
|
+
|
|
166
|
+
baidu_translator = Translator(engine="baidu", cache_path=baidu_cache_dir, target_lang='en', source_lang='zh')
|
|
167
|
+
|
|
168
|
+
text_to_translate_zh = "你好,世界!"
|
|
169
|
+
print(f"Original (zh): {text_to_translate_zh}")
|
|
170
|
+
|
|
171
|
+
# First translation (should be from API)
|
|
172
|
+
translated_en = baidu_translator.translate(text_to_translate_zh)
|
|
173
|
+
print(f"Translated (en): {translated_en}")
|
|
174
|
+
|
|
175
|
+
# Second translation (should be from cache)
|
|
176
|
+
translated_en_cached = baidu_translator.translate(text_to_translate_zh)
|
|
177
|
+
print(f"Translated (en) from cache: {translated_en_cached}")
|
|
178
|
+
|
|
179
|
+
assert translated_en == translated_en_cached
|
|
180
|
+
|
|
181
|
+
# Translate to French
|
|
182
|
+
translated_fr = baidu_translator.translate(text_to_translate_zh, target_lang='fr') # Baidu uses 'fra' for French
|
|
183
|
+
print(f"Translated (fr): {translated_fr}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# baidu_translator.clear_cache()
|
|
187
|
+
baidu_translator.close_cache()
|
|
188
|
+
print("Baidu Translator test finished.\n")
|
|
189
|
+
|
|
190
|
+
except ValueError as ve:
|
|
191
|
+
print(f"Skipping Baidu test: {ve}")
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print(f"Error during Baidu test: {e}")
|
|
194
|
+
|
|
195
|
+
# Example for Tencent (assuming ENV VARS are set)
|
|
196
|
+
try:
|
|
197
|
+
print("Testing Tencent Translator...")
|
|
198
|
+
tencent_cache_dir = "/tmp/translator_cache/tencent"
|
|
199
|
+
os.makedirs(tencent_cache_dir, exist_ok=True)
|
|
200
|
+
|
|
201
|
+
# You can pass api_id and api_key directly if not using ENV VARS
|
|
202
|
+
# tencent_translator = Translator(
|
|
203
|
+
# engine="tencent",
|
|
204
|
+
# cache_path=tencent_cache_dir,
|
|
205
|
+
# api_id="YOUR_TENCENT_SECRET_ID",
|
|
206
|
+
# api_key="YOUR_TENCENT_SECRET_KEY",
|
|
207
|
+
# target_lang='zh',
|
|
208
|
+
# source_lang='en'
|
|
209
|
+
# )
|
|
210
|
+
tencent_translator = Translator(engine="tencent", cache_path=tencent_cache_dir, target_lang='zh', source_lang='en')
|
|
211
|
+
|
|
212
|
+
text_to_translate_en = "Hello, world!"
|
|
213
|
+
print(f"Original (en): {text_to_translate_en}")
|
|
214
|
+
|
|
215
|
+
translated_zh = tencent_translator.translate(text_to_translate_en)
|
|
216
|
+
print(f"Translated (zh): {translated_zh}")
|
|
217
|
+
|
|
218
|
+
translated_zh_cached = tencent_translator.translate(text_to_translate_en)
|
|
219
|
+
print(f"Translated (zh) from cache: {translated_zh_cached}")
|
|
220
|
+
|
|
221
|
+
assert translated_zh == translated_zh_cached
|
|
222
|
+
|
|
223
|
+
# tencent_translator.clear_cache()
|
|
224
|
+
tencent_translator.close_cache()
|
|
225
|
+
print("Tencent Translator test finished.\n")
|
|
226
|
+
|
|
227
|
+
except ValueError as ve:
|
|
228
|
+
print(f"Skipping Tencent test: {ve}")
|
|
229
|
+
except Exception as e:
|
|
230
|
+
print(f"Error during Tencent test: {e}")
|
toolkitx/text_utils.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Define sentence terminators as a tuple for easy checking
|
|
5
|
+
SENTENCE_TERMINATORS = ('.', '!', '?')
|
|
6
|
+
|
|
7
|
+
def truncate_text_smart(text: str, limit: int = 100, mode: str = "char", suffix: str = "...", tolerance: int = 10) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Smartly truncates text based on character or word limit, with tolerance.
|
|
10
|
+
|
|
11
|
+
:param text: The original string.
|
|
12
|
+
:param limit: The target truncation length (in characters or words).
|
|
13
|
+
:param mode: Truncation mode: 'char' for character-based, 'word' for word-based.
|
|
14
|
+
:param suffix: The suffix to append after truncation.
|
|
15
|
+
:param tolerance: The allowed deviation from the limit for smart truncation.
|
|
16
|
+
:return: The truncated string.
|
|
17
|
+
:raises ValueError: If the mode is not 'char' or 'word'.
|
|
18
|
+
"""
|
|
19
|
+
if not isinstance(text, str):
|
|
20
|
+
# Or handle non-string input differently, e.g., convert to string or raise TypeError
|
|
21
|
+
return str(text) # Basic handling for non-string input
|
|
22
|
+
|
|
23
|
+
if mode == 'char':
|
|
24
|
+
# If text is already within or at the limit, no truncation needed.
|
|
25
|
+
if len(text) <= limit:
|
|
26
|
+
return text
|
|
27
|
+
|
|
28
|
+
# If the limit is too small to even hold the suffix,
|
|
29
|
+
# return the suffix truncated to the limit.
|
|
30
|
+
if limit <= len(suffix):
|
|
31
|
+
return suffix[:limit] if limit > 0 else ""
|
|
32
|
+
|
|
33
|
+
# Ideal length of the text part before the suffix.
|
|
34
|
+
ideal_text_part_len = limit - len(suffix)
|
|
35
|
+
# Maximum length of the text part we are willing to consider (within tolerance).
|
|
36
|
+
max_potential_text_len = min(len(text), limit + tolerance - len(suffix))
|
|
37
|
+
# Minimum length for the text part for smart truncation (within tolerance).
|
|
38
|
+
min_potential_text_len = max(0, limit - tolerance - len(suffix))
|
|
39
|
+
# Ensure min_potential_text_len is not greater than max_potential_text_len.
|
|
40
|
+
min_potential_text_len = min(min_potential_text_len, max_potential_text_len)
|
|
41
|
+
|
|
42
|
+
# The chunk of text to search for smart cut points.
|
|
43
|
+
# We search up to max_potential_text_len.
|
|
44
|
+
candidate_chunk_for_search = text[:max_potential_text_len]
|
|
45
|
+
|
|
46
|
+
# Attempt 1: Find a sentence boundary.
|
|
47
|
+
# Search backwards for the last sentence terminator in the candidate_chunk.
|
|
48
|
+
# The cut should result in a text part whose length is between min_potential_text_len and max_potential_text_len.
|
|
49
|
+
best_sentence_cut_len = -1
|
|
50
|
+
for i in range(len(candidate_chunk_for_search) - 1, -1, -1):
|
|
51
|
+
char_at_i = candidate_chunk_for_search[i]
|
|
52
|
+
# Check if it's a sentence terminator and the resulting part is long enough.
|
|
53
|
+
if char_at_i in SENTENCE_TERMINATORS:
|
|
54
|
+
# The length of the text part would be i + 1.
|
|
55
|
+
current_cut_len = i + 1
|
|
56
|
+
if current_cut_len >= min_potential_text_len:
|
|
57
|
+
# Check if it's a proper sentence end (e.g., followed by space or end of chunk)
|
|
58
|
+
# This check is simplified; more robust NLP might be needed for edge cases like "U.S.A."
|
|
59
|
+
is_actual_sentence_end = (i + 1 == len(candidate_chunk_for_search)) or \
|
|
60
|
+
(i + 1 < len(candidate_chunk_for_search) and candidate_chunk_for_search[i+1] == ' ')
|
|
61
|
+
if is_actual_sentence_end:
|
|
62
|
+
best_sentence_cut_len = current_cut_len
|
|
63
|
+
break # Found the latest possible sentence cut within tolerance.
|
|
64
|
+
|
|
65
|
+
if best_sentence_cut_len != -1:
|
|
66
|
+
# .rstrip() to handle cases like "Sentence. " before adding suffix.
|
|
67
|
+
return text[:best_sentence_cut_len].rstrip() + suffix
|
|
68
|
+
|
|
69
|
+
# Attempt 2: Find a word boundary.
|
|
70
|
+
# Search backwards for the last space in the candidate_chunk.
|
|
71
|
+
# The cut should result in a text part whose length is between min_potential_text_len and max_potential_text_len.
|
|
72
|
+
best_word_cut_len = -1
|
|
73
|
+
for i in range(len(candidate_chunk_for_search) - 1, -1, -1):
|
|
74
|
+
char_at_i = candidate_chunk_for_search[i]
|
|
75
|
+
if char_at_i == ' ':
|
|
76
|
+
# The length of the text part would be i (cutting before the space).
|
|
77
|
+
current_cut_len = i
|
|
78
|
+
if current_cut_len >= min_potential_text_len:
|
|
79
|
+
best_word_cut_len = current_cut_len
|
|
80
|
+
break # Found the latest possible word cut within tolerance.
|
|
81
|
+
|
|
82
|
+
if best_word_cut_len != -1:
|
|
83
|
+
# .rstrip() just in case, though text[:best_word_cut_len] should not have trailing spaces.
|
|
84
|
+
return text[:best_word_cut_len].rstrip() + suffix
|
|
85
|
+
|
|
86
|
+
# Fallback: Hard truncate to the ideal_text_part_len.
|
|
87
|
+
return text[:ideal_text_part_len] + suffix
|
|
88
|
+
|
|
89
|
+
elif mode == 'word':
|
|
90
|
+
words = text.split()
|
|
91
|
+
|
|
92
|
+
# If word count is already within or at the limit, no truncation needed.
|
|
93
|
+
if len(words) <= limit:
|
|
94
|
+
return text
|
|
95
|
+
|
|
96
|
+
best_word_count_for_cut = -1
|
|
97
|
+
|
|
98
|
+
# Attempt 1: Find a sentence boundary within word tolerance.
|
|
99
|
+
# Iterate from longest possible (limit + tolerance) down to shortest (limit - tolerance).
|
|
100
|
+
# Ensure k is at least 1.
|
|
101
|
+
start_k = min(len(words), limit + tolerance)
|
|
102
|
+
end_k = max(1, limit - tolerance)
|
|
103
|
+
|
|
104
|
+
for k in range(start_k, end_k - 1, -1):
|
|
105
|
+
if k == 0: continue # Should not happen with max(1, ...)
|
|
106
|
+
current_phrase_words = words[:k]
|
|
107
|
+
current_phrase_str = " ".join(current_phrase_words)
|
|
108
|
+
# Check if the formed phrase ends with a sentence terminator.
|
|
109
|
+
if current_phrase_str.rstrip().endswith(SENTENCE_TERMINATORS):
|
|
110
|
+
best_word_count_for_cut = k
|
|
111
|
+
break # Found the longest suitable sentence-ending phrase.
|
|
112
|
+
|
|
113
|
+
if best_word_count_for_cut != -1:
|
|
114
|
+
final_words = words[:best_word_count_for_cut]
|
|
115
|
+
result_text = " ".join(final_words)
|
|
116
|
+
# Add suffix only if actual truncation happened relative to original word count.
|
|
117
|
+
if len(words) > len(final_words):
|
|
118
|
+
result_text += suffix
|
|
119
|
+
return result_text
|
|
120
|
+
|
|
121
|
+
# Fallback: Truncate to the 'limit' number of words.
|
|
122
|
+
# This also handles cases where no sentence boundary was found in tolerance.
|
|
123
|
+
final_words = words[:limit]
|
|
124
|
+
result_text = " ".join(final_words)
|
|
125
|
+
if len(words) > len(final_words): # Add suffix only if truncated
|
|
126
|
+
result_text += suffix
|
|
127
|
+
return result_text
|
|
128
|
+
else:
|
|
129
|
+
raise ValueError("mode must be 'char' or 'word'")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def split_text_by_word_count(
|
|
134
|
+
text: str, max_words: int = 300, overlap: int = 0
|
|
135
|
+
) -> list[str]:
|
|
136
|
+
"""
|
|
137
|
+
Split a long text into overlapping chunks (trunks), each with at most `max_words` words,
|
|
138
|
+
and `overlap` words overlapping between consecutive trunks.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
text (str): The input text.
|
|
142
|
+
max_words (int): Maximum number of words per chunk.
|
|
143
|
+
overlap (int): Number of overlapping words between adjacent chunks.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
List[str]: A list of text chunks.
|
|
147
|
+
"""
|
|
148
|
+
assert 0 <= overlap < max_words, "Overlap must be >= 0 and less than max_words"
|
|
149
|
+
|
|
150
|
+
words = text.split()
|
|
151
|
+
trunks = []
|
|
152
|
+
step = max_words - overlap
|
|
153
|
+
|
|
154
|
+
for i in range(0, len(words), step):
|
|
155
|
+
trunk = " ".join(words[i : i + max_words])
|
|
156
|
+
trunks.append(trunk)
|
|
157
|
+
if i + max_words >= len(words):
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
return trunks
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: toolkitx
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A personal Python toolkit for common tasks
|
|
5
|
+
Project-URL: Homepage, https://github.com/ider-zh/toolkitx
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/ider-zh/toolkitx/issues
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Requires-Dist: diskcache>=5.6.3
|
|
10
|
+
Requires-Dist: py-transgpt>=2.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest-dotenv; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# toolkitx
|
|
17
|
+
|
|
18
|
+
A personal Python toolkit for common tasks. This package provides various utility functions to simplify common development workflows.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
* **Text Utilities** (`toolkitx.text_utils`):
|
|
23
|
+
* `truncate_text_smart`: Smartly truncates text by characters or words, with options for suffix and tolerance, attempting to preserve sentence or word boundaries.
|
|
24
|
+
* `split_text_by_word_count`: Splits long text into overlapping chunks based on word count.
|
|
25
|
+
* **Command-Line Script**:
|
|
26
|
+
* `hello`: A simple script accessible via `hello` command after installation, prints a greeting message.
|
|
27
|
+
* **Experimental Translator** (`toolkitx.lab.translator`):
|
|
28
|
+
* `Translator`: A class providing translation capabilities using Baidu or Tencent translation APIs, with disk-based caching for performance. (Requires API credentials)
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
1. Clone the repository:
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/ider-zh/toolkitx.git
|
|
35
|
+
cd toolkitx
|
|
36
|
+
```
|
|
37
|
+
2. Install the package. For development, you can install it in editable mode with development dependencies:
|
|
38
|
+
```bash
|
|
39
|
+
pip install -e ".[dev]"
|
|
40
|
+
```
|
|
41
|
+
For regular installation:
|
|
42
|
+
```bash
|
|
43
|
+
pip install .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### Text Utilities
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from toolkitx import truncate_text_smart, split_text_by_word_count
|
|
52
|
+
|
|
53
|
+
# Smart Truncation
|
|
54
|
+
text = "This is a very long sentence that needs to be truncated."
|
|
55
|
+
truncated_char = truncate_text_smart(text, limit=20, mode="char", suffix="...")
|
|
56
|
+
print(f"Char truncated: {truncated_char}")
|
|
57
|
+
|
|
58
|
+
truncated_word = truncate_text_smart(text, limit=5, mode="word", suffix="...")
|
|
59
|
+
print(f"Word truncated: {truncated_word}")
|
|
60
|
+
|
|
61
|
+
# Split Text
|
|
62
|
+
long_text = "This is a long piece of text that we want to split into several smaller chunks with some overlap between them for context."
|
|
63
|
+
chunks = split_text_by_word_count(long_text, max_words=10, overlap=2)
|
|
64
|
+
for i, chunk in enumerate(chunks):
|
|
65
|
+
print(f"Chunk {i+1}: {chunk}")
|
|
66
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
toolkitx/__init__.py,sha256=a06W3QTZABS6VQb6SiVzkBe35OzzrYMFrCVXXBLHOcM,459
|
|
2
|
+
toolkitx/hello.py,sha256=_2zorlP0W9gLGuDUPO7MSx5Hd6szTJnWPE014vR6Wfw,47
|
|
3
|
+
toolkitx/text_utils.py,sha256=MVK_Q8DGEUlOo7vz4cUOU4FKFsAoV-1pvgi-DNs-VCw,7393
|
|
4
|
+
toolkitx/lab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
toolkitx/lab/translator.py,sha256=L9yyAz6SbwbfggznaSF_7P2NetL0RqLBmlVqn5inxIU,9547
|
|
6
|
+
toolkitx-0.0.1.dist-info/METADATA,sha256=K8onr_V6_D31-S-Nbf-JMUb61aBstBLPthW-6U7auE0,2430
|
|
7
|
+
toolkitx-0.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
+
toolkitx-0.0.1.dist-info/entry_points.txt,sha256=qzg8ZI4SSi3YrHbBu7OOa6j8hRiTwKfRv2tXrDEIdSg,47
|
|
9
|
+
toolkitx-0.0.1.dist-info/licenses/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
|
|
10
|
+
toolkitx-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org>
|