markitdown-pro 2.1.0__tar.gz → 2.1.2__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 (173) hide show
  1. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/.github/workflows/test.yml +1 -7
  2. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/.gitignore +1 -0
  3. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/PKG-INFO +1 -1
  4. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/gotenberg_converter.py +88 -26
  5. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/gpt_vision_converter.py +3 -0
  6. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/page_pdf_converter.py +1 -0
  7. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/services/azure_doc_intelligence.py +1 -1
  8. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/services/openai_services.py +10 -2
  9. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/.github/workflows/lint.yaml +0 -0
  10. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/.github/workflows/publish.yaml +0 -0
  11. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/.vscode/settings.json +0 -0
  12. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/README.md +0 -0
  13. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/__init__.py +0 -0
  14. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/__init__.py +0 -0
  15. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/common/__init__.py +0 -0
  16. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/common/config.py +0 -0
  17. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/common/isolated_worker.py +0 -0
  18. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/common/logger.py +0 -0
  19. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/common/schemas.py +0 -0
  20. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/common/utils.py +0 -0
  21. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/conversion_pipeline.py +0 -0
  22. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/__init__.py +0 -0
  23. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/base.py +0 -0
  24. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/doc_intel_converter.py +0 -0
  25. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/markitdown_converter.py +0 -0
  26. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/pymupdf_converter.py +0 -0
  27. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/speech_converter.py +0 -0
  28. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/tabular_converter.py +0 -0
  29. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/unstructured_converter.py +0 -0
  30. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/converters/youtube_converter.py +0 -0
  31. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/__init__.py +0 -0
  32. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/audio_handler.py +0 -0
  33. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/base_handler.py +0 -0
  34. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/email_handler.py +0 -0
  35. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/epub_handler.py +0 -0
  36. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/image_handler.py +0 -0
  37. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/ipynb_handler.py +0 -0
  38. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/markup_handler.py +0 -0
  39. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/office_handler.py +0 -0
  40. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/pdf_handler.py +0 -0
  41. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/pst_handler.py +0 -0
  42. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/tabular_handler.py +0 -0
  43. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/handlers/text_handler.py +0 -0
  44. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/services/__init__.py +0 -0
  45. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/markitdown_pro/services/azure_speech.py +0 -0
  46. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/pyproject.toml +0 -0
  47. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/__init__.py +0 -0
  48. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/common/__init__.py +0 -0
  49. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/common/test_config.py +0 -0
  50. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/conftest.py +0 -0
  51. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/conversion_pipeline/__init__.py +0 -0
  52. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/conversion_pipeline/test_conversion_pipeline.py +0 -0
  53. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/converters/test_azure_doc_intel_wrapper.py +0 -0
  54. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/converters/test_azure_speech_wrapper.py +0 -0
  55. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/converters/test_doc_intel_lifecycle.py +0 -0
  56. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/converters/test_gpt_vision_config.py +0 -0
  57. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/converters/test_gpt_vision_wrapper.py +0 -0
  58. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/converters/test_pymupdf_wrapper.py +0 -0
  59. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/converters/test_pymupdf_wrapper_async.py +0 -0
  60. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/converters/test_unstructured_io_wrapper.py +0 -0
  61. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/audio/the-power-of-small-steps_cn.wav +0 -0
  62. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/audio/the-power-of-small-steps_en.mp3 +0 -0
  63. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/audio/the-power-of-small-steps_en.wav +0 -0
  64. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/audio/the-power-of-small-steps_es.mp3 +0 -0
  65. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/audio/the-power-of-small-steps_es.wav +0 -0
  66. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/audio/the-power-of-small-steps_it.wav +0 -0
  67. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/audio/the-power-of-small-steps_jp.mp3 +0 -0
  68. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/audio/the-power-of-small-steps_jp.wav +0 -0
  69. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/eml/email-with-image.eml +0 -0
  70. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/eml/mime-different-plain-html.eml +0 -0
  71. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/epub/sample1.epub +0 -0
  72. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/images/flower.jpg +0 -0
  73. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/images/geometry_colors.bmp +0 -0
  74. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/images/handwriting.jpg +0 -0
  75. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/images/scanned_text.heic +0 -0
  76. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/images/scanned_text.png +0 -0
  77. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/images/text_with_image.jpg +0 -0
  78. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/markup/blog.html +0 -0
  79. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/markup/factbook.xml +0 -0
  80. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/markup/simple.json +0 -0
  81. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/markup/simple.yaml +0 -0
  82. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/msg/fake-email-multiple-attachments.msg +0 -0
  83. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/msg/fake-email.msg +0 -0
  84. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/pdf/geometry-colors.pdf +0 -0
  85. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/pdf/lorem-ipsum-only-images.pdf +0 -0
  86. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/pdf/lorem-ipsum-only-text.pdf +0 -0
  87. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/pdf/lorem-ipsum-scanned-text.pdf +0 -0
  88. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/pdf/lorem-ipsum-text-with-image.pdf +0 -0
  89. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/powerpoint/geometry-colors.pptx +0 -0
  90. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/powerpoint/lorem-ipsum-only-text.pptx +0 -0
  91. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/powerpoint/lorem-ipsum-scanned-text.pptx +0 -0
  92. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/powerpoint/lorem-ipsum-text-with-image.pptx +0 -0
  93. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/powerpoint/lorem-ipsum-text-with-scanned-text.pptx +0 -0
  94. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/powerpoint-legacy/geometry-colors.ppt +0 -0
  95. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/powerpoint-legacy/lorem-ipsum-only-text.ppt +0 -0
  96. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/powerpoint-legacy/lorem-ipsum-scanned-text.ppt +0 -0
  97. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/powerpoint-legacy/lorem-ipsum-text-with-image.ppt +0 -0
  98. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/powerpoint-legacy/lorem-ipsum-text-with-scanned-text.ppt +0 -0
  99. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/tabular/2023-half-year-analyses-by-segment.xlsx +0 -0
  100. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/tabular/stanley-cups.csv +0 -0
  101. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/tabular/stanley-cups.tsv +0 -0
  102. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/tabular/stanley-cups.xlsx +0 -0
  103. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/test_expectations.yaml +0 -0
  104. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/text/book-war-and-peace-1p.txt +0 -0
  105. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/text/fake-email.txt +0 -0
  106. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/text/logger.py +0 -0
  107. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word/geometry-colors.docx +0 -0
  108. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word/lorem-ipsum-only-text.docx +0 -0
  109. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word/lorem-ipsum-scanned-text.docx +0 -0
  110. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word/lorem-ipsum-text-with-image.docx +0 -0
  111. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word/lorem-ipsum-text-with-scanned-text.docx +0 -0
  112. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/geometry-colors.doc +0 -0
  113. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/geometry-colors.odt +0 -0
  114. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/geometry-colors.rtf +0 -0
  115. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-only-text.doc +0 -0
  116. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-only-text.odt +0 -0
  117. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-only-text.rtf +0 -0
  118. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-scanned-text.doc +0 -0
  119. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-scanned-text.odt +0 -0
  120. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-scanned-text.rtf +0 -0
  121. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-text-with-image.doc +0 -0
  122. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-text-with-image.odt +0 -0
  123. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-text-with-image.rtf +0 -0
  124. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.doc +0 -0
  125. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.odt +0 -0
  126. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.rtf +0 -0
  127. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/fixtures.py +0 -0
  128. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/__init__.py +0 -0
  129. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_email_handler.py +0 -0
  130. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_epub_handler.py +0 -0
  131. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_image_handler.py +0 -0
  132. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_ipynb_handler.py +0 -0
  133. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_markitdown_handler.py +0 -0
  134. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_markitdown_validation.py +0 -0
  135. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_markup_handler.py +0 -0
  136. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_page_pdf_converter.py +0 -0
  137. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_pdf_handler.py +0 -0
  138. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_pst_handler.py +0 -0
  139. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_tabular_handler.py +0 -0
  140. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/handlers/test_text_handler.py +0 -0
  141. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/__init__.py +0 -0
  142. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/cheat_sheet.py +0 -0
  143. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/runner.py +0 -0
  144. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_audio_handler.py +0 -0
  145. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_doc_intelligence_converter.py +0 -0
  146. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_email_handler.py +0 -0
  147. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_gotenberg_converter.py +0 -0
  148. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_gpt_vision_converter.py +0 -0
  149. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_legacy_office_formats.py +0 -0
  150. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_markitdown_converter.py +0 -0
  151. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_markup_handler.py +0 -0
  152. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_pdf_handler.py +0 -0
  153. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_pymupdf_converter.py +0 -0
  154. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_tabular_handler.py +0 -0
  155. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/integration/test_text_handler.py +0 -0
  156. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/page_count/__init__.py +0 -0
  157. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/page_count/test_pdf_page_count.py +0 -0
  158. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/__init__.py +0 -0
  159. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_audio_handler.py +0 -0
  160. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_base_converter.py +0 -0
  161. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_conversion_pipeline.py +0 -0
  162. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_doc_intel_converter.py +0 -0
  163. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_gotenberg_converter.py +0 -0
  164. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_gpt_vision_converter.py +0 -0
  165. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_image_handler.py +0 -0
  166. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_markitdown_converter.py +0 -0
  167. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_office_handler.py +0 -0
  168. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_page_pdf_converter.py +0 -0
  169. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_pdf_handler.py +0 -0
  170. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_pymupdf_converter.py +0 -0
  171. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/unit/test_speech_converter.py +0 -0
  172. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/tests/utils.py +0 -0
  173. {markitdown_pro-2.1.0 → markitdown_pro-2.1.2}/uv.lock +0 -0
@@ -1,13 +1,6 @@
1
1
  name: '🧪 Test package'
2
2
 
3
3
  on:
4
- push:
5
- branches:
6
- - main
7
- paths:
8
- - 'markitdown_pro/**'
9
- - 'tests/**'
10
- - 'pyproject.toml'
11
4
  pull_request:
12
5
  branches:
13
6
  - main
@@ -15,6 +8,7 @@ on:
15
8
  - 'markitdown_pro/**'
16
9
  - 'tests/**'
17
10
  - 'pyproject.toml'
11
+ workflow_dispatch:
18
12
 
19
13
  permissions:
20
14
  contents: read
@@ -25,3 +25,4 @@ docs/
25
25
  **/CONTINUITY.md
26
26
  **/CHANGELOG.md
27
27
  .mcp.json
28
+ .worktrees/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markitdown-pro
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: A package that converts almost any file format to Markdown.
5
5
  Author: Developer
6
6
  License-Expression: MIT
@@ -15,10 +15,14 @@ If the Gotenberg service is configured but unreachable at call time,
15
15
  converter.
16
16
  """
17
17
 
18
+ import asyncio
18
19
  import contextlib
20
+ import functools
19
21
  import os
20
22
  import tempfile
23
+ from collections.abc import Awaitable, Callable
21
24
  from pathlib import Path
25
+ from typing import TypeVar
22
26
 
23
27
  import httpx
24
28
 
@@ -28,6 +32,52 @@ from .base import Converter
28
32
  _COMPONENT = "GotenbergConverter"
29
33
  _CONVERT_ENDPOINT = "/forms/libreoffice/convert"
30
34
 
35
+ # Transient failures worth retrying. ConnectError is intentionally excluded —
36
+ # "service unreachable" is handled as a graceful skip, not a retry.
37
+ _RETRYABLE_EXC: tuple[type[BaseException], ...] = (
38
+ httpx.TimeoutException,
39
+ httpx.RemoteProtocolError,
40
+ httpx.ReadError,
41
+ httpx.WriteError,
42
+ )
43
+
44
+ T = TypeVar("T")
45
+
46
+
47
+ def _retry_transient(
48
+ attempts: int = 3, delays: tuple[float, ...] = (1.0, 3.0)
49
+ ) -> Callable[[Callable[..., Awaitable[T | None]]], Callable[..., Awaitable[T | None]]]:
50
+ """Retry an async method on transient httpx errors. Sleeps between attempts
51
+ are taken from ``delays`` (short, fixed backoff — enough to ride out a
52
+ worker restart or a brief connection hiccup)."""
53
+
54
+ def decorator(
55
+ fn: Callable[..., Awaitable[T | None]],
56
+ ) -> Callable[..., Awaitable[T | None]]:
57
+ """Wrap ``fn`` with the retry-on-transient-httpx-errors policy above."""
58
+
59
+ @functools.wraps(fn)
60
+ async def wrapper(self, *args, **kwargs) -> T | None:
61
+ """Call ``fn`` up to ``attempts`` times, sleeping per ``delays``."""
62
+ tag = args[1] if len(args) >= 2 else kwargs.get("tag", _COMPONENT)
63
+ for i in range(attempts):
64
+ try:
65
+ return await fn(self, *args, **kwargs)
66
+ except _RETRYABLE_EXC as e:
67
+ if i == attempts - 1:
68
+ raise
69
+ sleep_for = delays[min(i, len(delays) - 1)]
70
+ logger.warning(
71
+ f"{tag} | transient {type(e).__name__}: {e}; "
72
+ f"retry {i + 1}/{attempts - 1} in {sleep_for:.1f}s"
73
+ )
74
+ await asyncio.sleep(sleep_for)
75
+ return None
76
+
77
+ return wrapper
78
+
79
+ return decorator
80
+
31
81
 
32
82
  class GotenbergConverter(Converter):
33
83
  """
@@ -81,7 +131,11 @@ class GotenbergConverter(Converter):
81
131
 
82
132
  logger.info(f"{tag} | converting to PDF via Gotenberg ({self.gotenberg_url})")
83
133
 
84
- pdf_bytes = await self._convert_to_pdf(file_path, tag)
134
+ try:
135
+ pdf_bytes = await self._convert_to_pdf(file_path, tag)
136
+ except _RETRYABLE_EXC as e:
137
+ logger.error(f"{tag} | Gotenberg error after retries: {type(e).__name__}: {e}")
138
+ return None
85
139
  if not pdf_bytes:
86
140
  return None
87
141
 
@@ -90,7 +144,7 @@ class GotenbergConverter(Converter):
90
144
  try:
91
145
  fd, tmp_pdf = tempfile.mkstemp(suffix=".pdf", prefix="mkdpro_")
92
146
  os.close(fd)
93
- Path(tmp_pdf).write_bytes(pdf_bytes)
147
+ await asyncio.to_thread(Path(tmp_pdf).write_bytes, pdf_bytes)
94
148
 
95
149
  logger.info(f"{tag} | PDF received ({len(pdf_bytes)} bytes), delegating to PDFHandler")
96
150
 
@@ -114,34 +168,42 @@ class GotenbergConverter(Converter):
114
168
  with contextlib.suppress(Exception):
115
169
  os.unlink(tmp_pdf)
116
170
 
171
+ @_retry_transient(attempts=3, delays=(1.0, 3.0))
117
172
  async def _convert_to_pdf(self, file_path: str, tag: str) -> bytes | None:
118
- """Send file to Gotenberg and return PDF bytes."""
119
- try:
120
- async with httpx.AsyncClient(timeout=120) as client:
121
- with open(file_path, "rb") as f:
122
- response = await client.post(
123
- self._convert_url,
124
- files={"files": (Path(file_path).name, f)},
125
- data={
126
- "losslessImageCompression": "true",
127
- "quality": "100",
128
- },
129
- )
130
-
131
- if response.status_code == 200:
132
- return response.content
133
-
134
- logger.warning(
135
- f"{tag} | Gotenberg returned {response.status_code}: {response.text[:200]}"
136
- )
137
- return None
173
+ """Send file to Gotenberg and return PDF bytes.
138
174
 
175
+ The HTTP call runs inside ``asyncio.to_thread`` with a sync
176
+ ``httpx.Client`` + open file handle so httpx streams the multipart
177
+ body from disk instead of holding the entire upload in memory.
178
+ Keeps peak memory bounded under concurrent large-file conversions.
179
+ """
180
+ try:
181
+ return await asyncio.to_thread(self._post_sync, file_path, tag)
139
182
  except httpx.ConnectError:
140
183
  logger.info(f"{tag} | Gotenberg not reachable at {self.gotenberg_url}, skipping")
141
184
  return None
142
- except httpx.TimeoutException:
143
- logger.warning(f"{tag} | Gotenberg timed out after 120s")
144
- return None
185
+ except _RETRYABLE_EXC:
186
+ # Let the retry decorator handle transient errors. If retries are
187
+ # exhausted it re-raises, and the caller in ``convert()`` logs.
188
+ raise
145
189
  except Exception as e:
146
- logger.warning(f"{tag} | Gotenberg error: {e}")
190
+ logger.error(f"{tag} | Gotenberg error: {e}")
147
191
  return None
192
+
193
+ def _post_sync(self, file_path: str, tag: str) -> bytes | None:
194
+ """Blocking multipart upload to Gotenberg. Called via ``to_thread``."""
195
+ with httpx.Client(timeout=120) as client, open(file_path, "rb") as f:
196
+ response = client.post(
197
+ self._convert_url,
198
+ files={"files": (Path(file_path).name, f)},
199
+ data={
200
+ "losslessImageCompression": "true",
201
+ "quality": "100",
202
+ },
203
+ )
204
+
205
+ if response.status_code == 200:
206
+ return response.content
207
+
208
+ logger.error(f"{tag} | Gotenberg returned {response.status_code}: {response.text[:200]}")
209
+ return None
@@ -11,6 +11,8 @@ Returns Markdown when successful, or `None` if conversion produced
11
11
  insufficient content (as determined by `ensure_minimum_content` in the service).
12
12
  """
13
13
 
14
+ from pathlib import Path
15
+
14
16
  from ..common import config
15
17
  from ..common.utils import is_pdf
16
18
  from ..services.openai_services import GPTVision
@@ -115,6 +117,7 @@ class GPTVisionConverter(Converter):
115
117
  max_retries=_retries,
116
118
  base_delay=base_delay,
117
119
  max_delay=max_delay,
120
+ context_label=Path(file_path).name,
118
121
  )
119
122
 
120
123
  async def aclose(self) -> None:
@@ -158,6 +158,7 @@ class PagePDFConverter:
158
158
  png_path = await asyncio.to_thread(self._render_page_to_png, file_path, page_idx)
159
159
  content = await self.gpt_vision.gpt_vision.process_image(
160
160
  file_or_url=png_path,
161
+ context_label=f"{Path(file_path).name} page {page_idx + 1}",
161
162
  )
162
163
  if content:
163
164
  logger.info(f"{tag} | succeeded ({len(content)} chars)")
@@ -114,7 +114,7 @@ class DocumentIntelligenceService:
114
114
  return None
115
115
 
116
116
  try:
117
- data = file_path.read_bytes()
117
+ data = await asyncio.to_thread(file_path.read_bytes)
118
118
 
119
119
  poller = await self._client.begin_analyze_document(
120
120
  model_id="prebuilt-layout",
@@ -240,6 +240,7 @@ class GPTVision:
240
240
  base_delay: float = 0.5,
241
241
  max_delay: float = 20.0,
242
242
  retry_http_statuses: tuple[int, ...] = _RETRY_HTTP_STATUSES,
243
+ context_label: str | None = None,
243
244
  ):
244
245
  """
245
246
  Call self.lang_client.ainvoke with:
@@ -275,8 +276,9 @@ class GPTVision:
275
276
  raise last_err
276
277
  cap = min(max_delay, base_delay * (2 ** (attempt - 1)))
277
278
  sleep_for = random.uniform(0, cap)
279
+ label = context_label or file_or_url
278
280
  logger.warning(
279
- f"GPTVision: ainvoke transient error {file_or_url}: attempt {attempt}/{max_retries}: "
281
+ f"GPTVision: ainvoke transient error {label}: attempt {attempt}/{max_retries}: "
280
282
  f"{type(last_err).__name__}: {last_err}. Retrying in {sleep_for:.2f}s"
281
283
  )
282
284
  await asyncio.sleep(sleep_for)
@@ -331,6 +333,7 @@ class GPTVision:
331
333
  max_retries: int = 6,
332
334
  base_delay: float = 0.5,
333
335
  max_delay: float = 20.0,
336
+ context_label: str | None = None,
334
337
  ) -> str | None:
335
338
  """
336
339
  OCR a single image (local path or URL) with the vision model.
@@ -373,6 +376,7 @@ class GPTVision:
373
376
  max_retries=max_retries,
374
377
  base_delay=base_delay,
375
378
  max_delay=max_delay,
379
+ context_label=context_label,
376
380
  )
377
381
  finally:
378
382
  with suppress(Exception):
@@ -469,7 +473,11 @@ class GPTVision:
469
473
  # Per-page hard timeout so one slow page cannot stall the run
470
474
  try:
471
475
  partial_md = await asyncio.wait_for(
472
- self.process_image(png_path), timeout=self.page_timeout_s
476
+ self.process_image(
477
+ png_path,
478
+ context_label=f"{file_stem} page {page_index + 1}/{num_pages}",
479
+ ),
480
+ timeout=self.page_timeout_s,
473
481
  )
474
482
  except TimeoutError:
475
483
  logger.error(
File without changes
File without changes