BabelDOC 0.2.20__tar.gz → 0.2.22__tar.gz

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.
Files changed (97) hide show
  1. {babeldoc-0.2.20 → babeldoc-0.2.22}/PKG-INFO +2 -1
  2. {babeldoc-0.2.20 → babeldoc-0.2.22}/README.md +1 -0
  3. babeldoc-0.2.22/babeldoc/__init__.py +1 -0
  4. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/const.py +1 -1
  5. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/il_translator.py +11 -0
  6. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/il_translator_llm_only.py +35 -15
  7. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/translator/translator.py +1 -25
  8. babeldoc-0.2.22/babeldoc/document_il/utils/atomic_integer.py +26 -0
  9. babeldoc-0.2.22/babeldoc/document_il/utils/priority_thread_pool_executor.py +259 -0
  10. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/main.py +1 -1
  11. {babeldoc-0.2.20 → babeldoc-0.2.22}/pyproject.toml +2 -2
  12. {babeldoc-0.2.20 → babeldoc-0.2.22}/uv.lock +1 -1
  13. babeldoc-0.2.20/babeldoc/__init__.py +0 -1
  14. {babeldoc-0.2.20 → babeldoc-0.2.22}/.cursorignore +0 -0
  15. {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
  16. {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
  17. {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/dependabot.yml +0 -0
  18. {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/labels.yml +0 -0
  19. {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/release-drafter.yml +0 -0
  20. {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/workflows/codeql.yml +0 -0
  21. {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/workflows/docs.yml +0 -0
  22. {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/workflows/labeler.yml +0 -0
  23. {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/workflows/publish-to-pypi.yml +0 -0
  24. {babeldoc-0.2.20 → babeldoc-0.2.22}/.github/workflows/test.yml +0 -0
  25. {babeldoc-0.2.20 → babeldoc-0.2.22}/.gitignore +0 -0
  26. {babeldoc-0.2.20 → babeldoc-0.2.22}/.pre-commit-config.yaml +0 -0
  27. {babeldoc-0.2.20 → babeldoc-0.2.22}/LICENSE +0 -0
  28. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/assets/assets.py +0 -0
  29. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/assets/embedding_assets_metadata.py +0 -0
  30. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/asynchronize/__init__.py +0 -0
  31. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/converter.py +0 -0
  32. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/__init__.py +0 -0
  33. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
  34. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/backend/__init__.py +0 -0
  35. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/backend/pdf_creater.py +0 -0
  36. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/frontend/__init__.py +0 -0
  37. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/frontend/il_creater.py +0 -0
  38. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/il_version_1.py +0 -0
  39. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/il_version_1.rnc +0 -0
  40. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/il_version_1.rng +0 -0
  41. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/il_version_1.xsd +0 -0
  42. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/__init__.py +0 -0
  43. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/add_debug_information.py +0 -0
  44. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/detect_scanned_file.py +0 -0
  45. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/layout_parser.py +0 -0
  46. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/paragraph_finder.py +0 -0
  47. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/remove_descent.py +0 -0
  48. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/styles_and_formulas.py +0 -0
  49. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/table_parser.py +0 -0
  50. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/midend/typesetting.py +0 -0
  51. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/translator/__init__.py +0 -0
  52. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/translator/cache.py +0 -0
  53. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/utils/fontmap.py +0 -0
  54. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/utils/layout_helper.py +0 -0
  55. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/utils/style_helper.py +0 -0
  56. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/document_il/xml_converter.py +0 -0
  57. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/docvision/README.md +0 -0
  58. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/docvision/__init__.py +0 -0
  59. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/docvision/doclayout.py +0 -0
  60. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/docvision/rpc_doclayout.py +0 -0
  61. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/docvision/table_detection/rapidocr.py +0 -0
  62. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/high_level.py +0 -0
  63. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/pdfinterp.py +0 -0
  64. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/progress_monitor.py +0 -0
  65. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/result_merger.py +0 -0
  66. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/split_manager.py +0 -0
  67. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/tools/generate_font_metadata.py +0 -0
  68. {babeldoc-0.2.20 → babeldoc-0.2.22}/babeldoc/translation_config.py +0 -0
  69. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/CODE_OF_CONDUCT.md +0 -0
  70. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/CONTRIBUTING.md +0 -0
  71. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/CONTRIBUTOR_REWARD.md +0 -0
  72. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
  73. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
  74. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
  75. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
  76. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
  77. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/README.md +0 -0
  78. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
  79. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
  80. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/README.md +0 -0
  81. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/deploy.sh +0 -0
  82. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-banner.png +0 -0
  83. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
  84. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
  85. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
  86. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
  87. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-big-logo.png +0 -0
  88. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-contributor_reward_example.png +0 -0
  89. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-preview.gif +0 -0
  90. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
  91. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
  92. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/images/babeldoc-small-logo.png +0 -0
  93. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/index.md +0 -0
  94. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/intro-to-pdf-object.md +0 -0
  95. {babeldoc-0.2.20 → babeldoc-0.2.22}/docs/requirements.txt +0 -0
  96. {babeldoc-0.2.20 → babeldoc-0.2.22}/mkdocs.yml +0 -0
  97. {babeldoc-0.2.20 → babeldoc-0.2.22}/tests/test_translation_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BabelDOC
3
- Version: 0.2.20
3
+ Version: 0.2.22
4
4
  Summary: Yet Another Document Translator
5
5
  Project-URL: Homepage, https://github.com/funstory-ai/BabelDOC
6
6
  Project-URL: Issues, https://github.com/funstory-ai/BabelDOC/issues
@@ -399,6 +399,7 @@ Everyone interacting in YADT and its sub-projects' codebases, issue trackers, ch
399
399
  - [pdfminer](https://github.com/pdfminer/pdfminer.six)
400
400
  - [PyMuPDF](https://github.com/pymupdf/PyMuPDF)
401
401
  - [Asynchronize](https://github.com/multimeric/Asynchronize/tree/master?tab=readme-ov-file)
402
+ - [PriorityThreadPoolExecutor](https://github.com/oleglpts/PriorityThreadPoolExecutor)
402
403
 
403
404
  <h2 id="star_hist">Star History</h2>
404
405
 
@@ -353,6 +353,7 @@ Everyone interacting in YADT and its sub-projects' codebases, issue trackers, ch
353
353
  - [pdfminer](https://github.com/pdfminer/pdfminer.six)
354
354
  - [PyMuPDF](https://github.com/pymupdf/PyMuPDF)
355
355
  - [Asynchronize](https://github.com/multimeric/Asynchronize/tree/master?tab=readme-ov-file)
356
+ - [PriorityThreadPoolExecutor](https://github.com/oleglpts/PriorityThreadPoolExecutor)
356
357
 
357
358
  <h2 id="star_hist">Star History</h2>
358
359
 
@@ -0,0 +1 @@
1
+ __version__ = "0.2.22"
@@ -3,7 +3,7 @@ import shutil
3
3
  import subprocess
4
4
  from pathlib import Path
5
5
 
6
- __version__ = "0.2.20"
6
+ __version__ = "0.2.22"
7
7
 
8
8
  CACHE_FOLDER = Path.home() / ".cache" / "babeldoc"
9
9
 
@@ -4,6 +4,7 @@ import logging
4
4
  import re
5
5
  from pathlib import Path
6
6
 
7
+ import tiktoken
7
8
  from tqdm import tqdm
8
9
 
9
10
  from babeldoc.document_il import Document
@@ -119,11 +120,20 @@ class ILTranslator:
119
120
  self,
120
121
  translate_engine: BaseTranslator,
121
122
  translation_config: TranslationConfig,
123
+ tokenizer=None,
122
124
  ):
123
125
  self.translate_engine = translate_engine
124
126
  self.translation_config = translation_config
125
127
  self.font_mapper = FontMapper(translation_config)
126
128
 
129
+ if tokenizer is None:
130
+ self.tokenizer = tiktoken.encoding_for_model("gpt-4o")
131
+ else:
132
+ self.tokenizer = tokenizer
133
+
134
+ def calc_token_count(self, text: str) -> int:
135
+ return len(self.tokenizer.encode(text))
136
+
127
137
  def translate(self, docs: Document):
128
138
  tracker = DocumentTranslateTracker()
129
139
  # count total paragraph
@@ -173,6 +183,7 @@ class ILTranslator:
173
183
  tracker.new_paragraph(),
174
184
  page_font_map,
175
185
  page_xobj_font_map,
186
+ priority=1048576 - self.calc_token_count(paragraph.unicode),
176
187
  )
177
188
 
178
189
  class TranslateInput:
@@ -1,4 +1,3 @@
1
- import concurrent.futures
2
1
  import json
3
2
  import logging
4
3
  import re
@@ -17,10 +16,11 @@ from babeldoc.document_il.midend.il_translator import ILTranslator
17
16
  from babeldoc.document_il.midend.il_translator import PageTranslateTracker
18
17
  from babeldoc.document_il.translator.translator import BaseTranslator
19
18
  from babeldoc.document_il.utils.fontmap import FontMapper
19
+ from babeldoc.document_il.utils.priority_thread_pool_executor import (
20
+ PriorityThreadPoolExecutor,
21
+ )
20
22
  from babeldoc.translation_config import TranslationConfig
21
23
 
22
- enc = tiktoken.encoding_for_model("gpt-4o")
23
-
24
24
  logger = logging.getLogger(__name__)
25
25
 
26
26
 
@@ -39,6 +39,7 @@ class ILTranslatorLLMOnly:
39
39
  self,
40
40
  translate_engine: BaseTranslator,
41
41
  translation_config: TranslationConfig,
42
+ tokenizer=None,
42
43
  ):
43
44
  self.translate_engine = translate_engine
44
45
  self.translation_config = translation_config
@@ -47,9 +48,15 @@ class ILTranslatorLLMOnly:
47
48
  translation_config.shared_context_cross_split_part
48
49
  )
49
50
 
51
+ if tokenizer is None:
52
+ self.tokenizer = tiktoken.encoding_for_model("gpt-4o")
53
+ else:
54
+ self.tokenizer = tokenizer
55
+
50
56
  self.il_translator = ILTranslator(
51
57
  translate_engine=translate_engine,
52
58
  translation_config=translation_config,
59
+ tokenizer=self.tokenizer,
53
60
  )
54
61
 
55
62
  try:
@@ -57,6 +64,9 @@ class ILTranslatorLLMOnly:
57
64
  except NotImplementedError as e:
58
65
  raise ValueError("LLM translator not supported") from e
59
66
 
67
+ def calc_token_count(self, text: str) -> int:
68
+ return len(self.tokenizer.encode(text))
69
+
60
70
  def find_title_paragraph(self, docs: Document) -> PdfParagraph | None:
61
71
  """Find the first paragraph with layout_label 'title' in the document.
62
72
 
@@ -103,10 +113,10 @@ class ILTranslatorLLMOnly:
103
113
  self.stage_name,
104
114
  total,
105
115
  ) as pbar:
106
- with concurrent.futures.ThreadPoolExecutor(
116
+ with PriorityThreadPoolExecutor(
107
117
  max_workers=self.translation_config.qps,
108
118
  ) as executor2:
109
- with concurrent.futures.ThreadPoolExecutor(
119
+ with PriorityThreadPoolExecutor(
110
120
  max_workers=self.translation_config.qps,
111
121
  ) as executor:
112
122
  for page in docs.page:
@@ -128,10 +138,10 @@ class ILTranslatorLLMOnly:
128
138
  def process_page(
129
139
  self,
130
140
  page: Page,
131
- executor: concurrent.futures.ThreadPoolExecutor,
141
+ executor: PriorityThreadPoolExecutor,
132
142
  pbar: tqdm | None = None,
133
143
  tracker: PageTranslateTracker = None,
134
- executor2: concurrent.futures.ThreadPoolExecutor | None = None,
144
+ executor2: PriorityThreadPoolExecutor | None = None,
135
145
  ):
136
146
  self.translation_config.raise_if_cancelled()
137
147
  page_font_map = {}
@@ -145,17 +155,17 @@ class ILTranslatorLLMOnly:
145
155
 
146
156
  paragraphs = []
147
157
 
148
- total_unicode_counts = 0
158
+ total_token_count = 0
149
159
  for paragraph in page.pdf_paragraph:
150
160
  if paragraph.debug_id is None or paragraph.unicode is None:
151
161
  continue
152
162
  # self.translate_paragraph(paragraph, pbar,tracker.new_paragraph(), page_font_map, page_xobj_font_map)
153
- total_unicode_counts += len(paragraph.unicode)
163
+ total_token_count += self.calc_token_count(paragraph.unicode)
154
164
  paragraphs.append(paragraph)
155
165
  if paragraph.layout_label == "title":
156
166
  self.shared_context_cross_split_part.recent_title_paragraph = paragraph
157
167
 
158
- if total_unicode_counts > 1200 or len(paragraphs) > 4:
168
+ if total_token_count > 800 or len(paragraphs) > 5:
159
169
  executor.submit(
160
170
  self.translate_paragraph,
161
171
  BatchParagraph(paragraphs, tracker),
@@ -165,9 +175,10 @@ class ILTranslatorLLMOnly:
165
175
  self.translation_config.shared_context_cross_split_part.first_paragraph,
166
176
  self.translation_config.shared_context_cross_split_part.recent_title_paragraph,
167
177
  executor2,
178
+ priority=1048576 - total_token_count,
168
179
  )
169
180
  paragraphs = []
170
- total_unicode_counts = 0
181
+ total_token_count = 0
171
182
 
172
183
  if paragraphs:
173
184
  executor.submit(
@@ -179,6 +190,7 @@ class ILTranslatorLLMOnly:
179
190
  self.translation_config.shared_context_cross_split_part.first_paragraph,
180
191
  self.translation_config.shared_context_cross_split_part.recent_title_paragraph,
181
192
  executor2,
193
+ priority=1048576 - total_token_count,
182
194
  )
183
195
 
184
196
  def translate_paragraph(
@@ -189,10 +201,11 @@ class ILTranslatorLLMOnly:
189
201
  xobj_font_map: dict[int, dict[str, PdfFont]] = None,
190
202
  title_paragraph: PdfParagraph | None = None,
191
203
  local_title_paragraph: PdfParagraph | None = None,
192
- executor=None,
204
+ executor: PriorityThreadPoolExecutor | None = None,
193
205
  ):
194
206
  """Translate a paragraph using pre and post processing functions."""
195
207
  self.translation_config.raise_if_cancelled()
208
+ should_translate_paragraph = []
196
209
  try:
197
210
  inputs = []
198
211
 
@@ -205,6 +218,7 @@ class ILTranslatorLLMOnly:
205
218
  if text is None:
206
219
  pbar.advance(1)
207
220
  continue
221
+ should_translate_paragraph.append(i)
208
222
  inputs.append((text, translate_input, paragraph, tracker))
209
223
  if not inputs:
210
224
  return
@@ -313,8 +327,8 @@ class ILTranslatorLLMOnly:
313
327
  input_unicode = inputs[id_][2].unicode
314
328
  output_unicode = translated_text
315
329
 
316
- input_token_count = len(enc.encode(input_unicode))
317
- output_token_count = len(enc.encode(output_unicode))
330
+ input_token_count = self.calc_token_count(input_unicode)
331
+ output_token_count = self.calc_token_count(output_unicode)
318
332
 
319
333
  if not (0.3 < output_token_count / input_token_count < 3):
320
334
  logger.warning(
@@ -354,11 +368,16 @@ class ILTranslatorLLMOnly:
354
368
  inputs[id_][3],
355
369
  page_font_map,
356
370
  xobj_font_map,
371
+ priority=1048576 - self.calc_token_count(paragraph.unicode),
357
372
  )
358
373
 
359
374
  except Exception as e:
360
375
  logger.warning(f"Error {e} during translation. try fallback")
361
- for i in range(len(batch_paragraph.paragraphs)):
376
+ if not should_translate_paragraph:
377
+ should_translate_paragraph = list(
378
+ range(len(batch_paragraph.paragraphs))
379
+ )
380
+ for i in should_translate_paragraph:
362
381
  paragraph = batch_paragraph.paragraphs[i]
363
382
  tracker = batch_paragraph.trackers[i]
364
383
  if paragraph.debug_id is None:
@@ -370,6 +389,7 @@ class ILTranslatorLLMOnly:
370
389
  tracker,
371
390
  page_font_map,
372
391
  xobj_font_map,
392
+ priority=1048576 - self.calc_token_count(paragraph.unicode),
373
393
  )
374
394
 
375
395
  def _clean_json_output(self, llm_output: str) -> str:
@@ -13,6 +13,7 @@ from tenacity import stop_after_attempt
13
13
  from tenacity import wait_exponential
14
14
 
15
15
  from babeldoc.document_il.translator.cache import TranslationCache
16
+ from babeldoc.document_il.utils.atomic_integer import AtomicInteger
16
17
 
17
18
  logger = logging.getLogger(__name__)
18
19
 
@@ -62,31 +63,6 @@ def set_translate_rate_limiter(max_qps):
62
63
  _translate_rate_limiter.set_max_qps(max_qps)
63
64
 
64
65
 
65
- class AtomicInteger:
66
- def __init__(self, value=0):
67
- self._value = int(value)
68
- self._lock = threading.Lock()
69
-
70
- def inc(self, d=1):
71
- with self._lock:
72
- self._value += int(d)
73
- return self._value
74
-
75
- def dec(self, d=1):
76
- return self.inc(-d)
77
-
78
- @property
79
- def value(self):
80
- with self._lock:
81
- return self._value
82
-
83
- @value.setter
84
- def value(self, v):
85
- with self._lock:
86
- self._value = int(v)
87
- return self._value
88
-
89
-
90
66
  class BaseTranslator(ABC):
91
67
  # Due to cache limitations, name should be within 20 characters.
92
68
  # cache.py: translate_engine = CharField(max_length=20)
@@ -0,0 +1,26 @@
1
+ import threading
2
+
3
+
4
+ class AtomicInteger:
5
+ def __init__(self, value=0):
6
+ self._value = int(value)
7
+ self._lock = threading.Lock()
8
+
9
+ def inc(self, d=1):
10
+ with self._lock:
11
+ self._value += int(d)
12
+ return self._value
13
+
14
+ def dec(self, d=1):
15
+ return self.inc(-d)
16
+
17
+ @property
18
+ def value(self):
19
+ with self._lock:
20
+ return self._value
21
+
22
+ @value.setter
23
+ def value(self, v):
24
+ with self._lock:
25
+ self._value = int(v)
26
+ return self._value
@@ -0,0 +1,259 @@
1
+ # thanks to:
2
+ # https://github.com/oleglpts/PriorityThreadPoolExecutor/blob/master/PriorityThreadPoolExecutor/__init__.py
3
+ # https://github.com/oleglpts/PriorityThreadPoolExecutor/issues/4
4
+
5
+ import atexit
6
+ import itertools
7
+ import logging
8
+ import queue
9
+ import random
10
+ import sys
11
+ import threading
12
+ import weakref
13
+ from concurrent.futures import _base
14
+ from concurrent.futures.thread import BrokenThreadPool
15
+ from concurrent.futures.thread import ThreadPoolExecutor
16
+ from concurrent.futures.thread import _python_exit
17
+ from concurrent.futures.thread import _threads_queues
18
+ from concurrent.futures.thread import _WorkItem
19
+ from heapq import heappop
20
+ from heapq import heappush
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ ########################################################################################################################
25
+ # Global variables #
26
+ ########################################################################################################################
27
+
28
+ NULL_ENTRY = (sys.maxsize, _WorkItem(None, None, (), {}))
29
+ _shutdown = False
30
+
31
+ ########################################################################################################################
32
+ # Before system exit procedure #
33
+ ########################################################################################################################
34
+
35
+
36
+ def python_exit():
37
+ """
38
+
39
+ Cleanup before system exit
40
+
41
+ """
42
+ global _shutdown
43
+ _shutdown = True
44
+ items = list(_threads_queues.items())
45
+ for _t, q in items:
46
+ q.put(NULL_ENTRY)
47
+ for t, _q in items:
48
+ t.join()
49
+
50
+
51
+ # change default cleanup
52
+
53
+
54
+ atexit.unregister(_python_exit)
55
+ atexit.register(python_exit)
56
+
57
+
58
+ class PriorityQueue(queue.Queue):
59
+ """Variant of Queue that retrieves open entries in priority order (lowest first).
60
+
61
+ Entries are typically tuples of the form: (priority number, data).
62
+ """
63
+
64
+ REMOVED = "<removed-task>"
65
+ DEFAULT_PRIORITY = 100
66
+
67
+ def _init(self, maxsize):
68
+ self.queue = []
69
+ self.entry_finder = {}
70
+ self.counter = itertools.count()
71
+
72
+ def _qsize(self):
73
+ return len(self.queue)
74
+
75
+ def _put(self, item):
76
+ # heappush(self.queue, item)
77
+ try:
78
+ if item[1] in self.entry_finder:
79
+ self.remove(item[1])
80
+ count = next(self.counter)
81
+ entry = [item[0], count, item[1]]
82
+ self.entry_finder[item[1]] = entry
83
+ heappush(self.queue, entry)
84
+ except TypeError: # handle item==None
85
+ self._put((self.DEFAULT_PRIORITY, None))
86
+
87
+ def remove(self, task):
88
+ """
89
+ This simply replaces the data with the REMOVED value,
90
+ which will get cleared out once _get reaches it.
91
+ """
92
+ entry = self.entry_finder.pop(task)
93
+ entry[-1] = self.REMOVED
94
+
95
+ def _get(self):
96
+ while self.queue:
97
+ entry = heappop(self.queue)
98
+ if entry[2] is not self.REMOVED:
99
+ del self.entry_finder[entry[2]]
100
+ return entry
101
+ return None
102
+
103
+
104
+ def _worker(executor_reference, work_queue, initializer, initargs):
105
+ if initializer is not None:
106
+ try:
107
+ initializer(*initargs)
108
+ except BaseException:
109
+ _base.LOGGER.critical("Exception in initializer:", exc_info=True)
110
+ executor = executor_reference()
111
+ if executor is not None:
112
+ executor._initializer_failed()
113
+ return
114
+ try:
115
+ while True:
116
+ work_item = work_queue.get(block=True)
117
+ try:
118
+ if work_item[2] is not None:
119
+ work_item[2].run()
120
+ # Delete references to object. See issue16284
121
+ del work_item
122
+
123
+ # attempt to increment idle count
124
+ executor = executor_reference()
125
+ if executor is not None:
126
+ executor._idle_semaphore.release()
127
+ del executor
128
+ continue
129
+
130
+ executor = executor_reference()
131
+ # Exit if:
132
+ # - The interpreter is shutting down OR
133
+ # - The executor that owns the worker has been collected OR
134
+ # - The executor that owns the worker has been shutdown.
135
+ if _shutdown or executor is None or executor._shutdown:
136
+ # Flag the executor as shutting down as early as possible if it
137
+ # is not gc-ed yet.
138
+ if executor is not None:
139
+ executor._shutdown = True
140
+ # Notice other workers
141
+ work_queue.put(None)
142
+ return
143
+ del executor
144
+ finally:
145
+ work_queue.task_done()
146
+ except BaseException:
147
+ _base.LOGGER.critical("Exception in worker", exc_info=True)
148
+
149
+
150
+ class PriorityThreadPoolExecutor(ThreadPoolExecutor):
151
+ """
152
+ Thread pool executor with priority queue (priorities must be different, lowest first)
153
+ """
154
+
155
+ def __init__(self, *args, **kwargs):
156
+ super().__init__(*args, **kwargs)
157
+
158
+ # change work queue type to queue.PriorityQueue
159
+ self._work_queue: PriorityQueue = PriorityQueue()
160
+
161
+ def submit(self, fn, *args, **kwargs):
162
+ """
163
+
164
+ Sending the function to the execution queue
165
+
166
+ :param fn: function being executed
167
+ :type fn: callable
168
+ :param args: function's positional arguments
169
+ :param kwargs: function's keywords arguments
170
+ :return: future instance
171
+ :rtype: _base.Future
172
+
173
+ Added keyword:
174
+
175
+ - priority (integer later sys.maxsize)
176
+
177
+ """
178
+ with self._shutdown_lock:
179
+ if self._broken:
180
+ raise BrokenThreadPool(self._broken)
181
+
182
+ if self._shutdown:
183
+ raise RuntimeError("cannot schedule new futures after shutdown")
184
+ if _shutdown:
185
+ raise RuntimeError(
186
+ "cannot schedule new futures after interpreter shutdown"
187
+ )
188
+
189
+ priority = kwargs.get("priority", random.randint(0, sys.maxsize - 1)) # noqa: S311
190
+ if "priority" in kwargs:
191
+ del kwargs["priority"]
192
+
193
+ f = _base.Future()
194
+ w = _WorkItem(f, fn, args, kwargs)
195
+
196
+ self._work_queue.put((priority, w))
197
+ self._adjust_thread_count()
198
+ return f
199
+
200
+ def _adjust_thread_count(self):
201
+ # if idle threads are available, don't spin new threads
202
+ if self._idle_semaphore.acquire(timeout=0):
203
+ return
204
+
205
+ # When the executor gets lost, the weakref callback will wake up
206
+ # the worker threads.
207
+ def weakref_cb(_, q=self._work_queue):
208
+ q.put(None)
209
+
210
+ num_threads = len(self._threads)
211
+ if num_threads < self._max_workers:
212
+ thread_name = f"{self._thread_name_prefix or self}_{num_threads:d}"
213
+ t = threading.Thread(
214
+ name=thread_name,
215
+ target=_worker,
216
+ args=(
217
+ weakref.ref(self, weakref_cb),
218
+ self._work_queue,
219
+ self._initializer,
220
+ self._initargs,
221
+ ),
222
+ )
223
+ t.start()
224
+ self._threads.add(t)
225
+ _threads_queues[t] = self._work_queue
226
+
227
+ def shutdown(self, wait=True, *, cancel_futures=False):
228
+ logger.info("Shutting down executor %s", self._thread_name_prefix or self)
229
+ if wait:
230
+ logger.info(
231
+ "Waiting for all tasks done %s", self._thread_name_prefix or self
232
+ )
233
+ self._work_queue.join()
234
+ logger.info("All tasks done %s", self._thread_name_prefix or self)
235
+
236
+ with self._shutdown_lock:
237
+ self._shutdown = True
238
+ if cancel_futures:
239
+ # Drain all work items from the queue, and then cancel their
240
+ # associated futures.
241
+ while True:
242
+ try:
243
+ work_item = self._work_queue.get_nowait()
244
+ except queue.Empty:
245
+ break
246
+ if work_item is not None:
247
+ work_item.future.cancel()
248
+
249
+ # Send a wake-up to prevent threads calling
250
+ # _work_queue.get(block=True) from permanently blocking.
251
+ self._work_queue.put(None)
252
+ if wait:
253
+ logger.info(
254
+ "Waiting for all thread done %s", self._thread_name_prefix or self
255
+ )
256
+ for t in self._threads:
257
+ self._work_queue.put(None)
258
+ t.join()
259
+ logger.info("shutdown finish %s", self._thread_name_prefix or self)
@@ -23,7 +23,7 @@ from babeldoc.translation_config import TranslationConfig
23
23
  from babeldoc.translation_config import WatermarkOutputMode
24
24
 
25
25
  logger = logging.getLogger(__name__)
26
- __version__ = "0.2.20"
26
+ __version__ = "0.2.22"
27
27
 
28
28
 
29
29
  def create_parser():
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "BabelDOC"
3
- version = "0.2.20"
3
+ version = "0.2.22"
4
4
  description = "Yet Another Document Translator"
5
5
  license = "AGPL-3.0"
6
6
  readme = "README.md"
@@ -148,7 +148,7 @@ pythonpath = [".", "src"]
148
148
  testpaths = ["tests"]
149
149
 
150
150
  [bumpver]
151
- current_version = "0.2.20"
151
+ current_version = "0.2.22"
152
152
  version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
153
153
 
154
154
  [bumpver.file_patterns]
@@ -48,7 +48,7 @@ wheels = [
48
48
 
49
49
  [[package]]
50
50
  name = "babeldoc"
51
- version = "0.2.20"
51
+ version = "0.2.22"
52
52
  source = { editable = "." }
53
53
  dependencies = [
54
54
  { name = "bitstring" },
@@ -1 +0,0 @@
1
- __version__ = "0.2.20"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes