Skip to content

Commit 59fa33c

Browse files
committed
perf: Migrate logic to async
For backward compatibility, converters are added new "_async" methods, in complement of the original (sync). Sync method is marked deprecated. Youtube converted is executed in a thread context (the lib isn't natively async) and OpenAI integration use the AsyncOpenAI class, also marking sync class as deprecated.
1 parent da7bcea commit 59fa33c

30 files changed

Lines changed: 672 additions & 363 deletions

README.md

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,39 @@
99
1010
> [!IMPORTANT]
1111
> Breaking changes between 0.0.1 to 0.1.0:
12-
> * Dependencies are now organized into optional feature-groups (further details below). Use `pip install 'markitdown[all]'` to have backward-compatible behavior.
12+
>
13+
> * Dependencies are now organized into optional feature-groups (further details below). Use `pip install 'markitdown[all]'` to have backward-compatible behavior.
1314
> * convert\_stream() now requires a binary file-like object (e.g., a file opened in binary mode, or an io.BytesIO object). This is a breaking change from the previous version, where it previously also accepted text file-like objects, like io.StringIO.
1415
> * The DocumentConverter class interface has changed to read from file-like streams rather than file paths. *No temporary files are created anymore*. If you are the maintainer of a plugin, or custom DocumentConverter, you likely need to update your code. Otherwise, if only using the MarkItDown class or CLI (as in these examples), you should not need to change anything.
1516
1617
MarkItDown is a lightweight Python utility for converting various files to Markdown for use with LLMs and related text analysis pipelines. To this end, it is most comparable to [textract](https://github.com/deanmalmgren/textract), but with a focus on preserving important document structure and content as Markdown (including: headings, lists, tables, links, etc.) While the output is often reasonably presentable and human-friendly, it is meant to be consumed by text analysis tools -- and may not be the best option for high-fidelity document conversions for human consumption.
1718

1819
MarkItDown currently supports the conversion from:
1920

20-
- PDF
21-
- PowerPoint
22-
- Word
23-
- Excel
24-
- Images (EXIF metadata and OCR)
25-
- Audio (EXIF metadata and speech transcription)
26-
- HTML
27-
- Text-based formats (CSV, JSON, XML)
28-
- ZIP files (iterates over contents)
29-
- Youtube URLs
30-
- EPubs
31-
- ... and more!
21+
* PDF
22+
* PowerPoint
23+
* Word
24+
* Excel
25+
* Images (EXIF metadata and OCR)
26+
* Audio (EXIF metadata and speech transcription)
27+
* HTML
28+
* Text-based formats (CSV, JSON, XML)
29+
* ZIP files (iterates over contents)
30+
* Youtube URLs
31+
* EPubs
32+
* ... and more!
3233

3334
## Why Markdown?
3435

3536
Markdown is extremely close to plain text, with minimal markup or formatting, but still
3637
provides a way to represent important document structure. Mainstream LLMs, such as
37-
OpenAI's GPT-4o, natively "_speak_" Markdown, and often incorporate Markdown into their
38+
OpenAI's GPT-4o, natively "*speak*" Markdown, and often incorporate Markdown into their
3839
responses unprompted. This suggests that they have been trained on vast amounts of
3940
Markdown-formatted text, and understand it well. As a side benefit, Markdown conventions
4041
are also highly token-efficient.
4142

4243
## Prerequisites
44+
4345
MarkItDown requires Python 3.10 or higher. It is recommended to use a virtual environment to avoid dependency conflicts.
4446

4547
With the standard Python installation, you can create and activate a virtual environment using the following commands:
@@ -95,6 +97,7 @@ cat path-to-file.pdf | markitdown
9597
```
9698

9799
### Optional Dependencies
100+
98101
MarkItDown has optional dependencies for activating various file formats. Earlier in this document, we installed all optional dependencies with the `[all]` option. However, you can also install them individually for more control. For example:
99102

100103
```bash
@@ -144,7 +147,7 @@ More information about how to set up an Azure Document Intelligence Resource can
144147

145148
### Python API
146149

147-
Basic usage in Python:
150+
Basic usage in Python. All functions are available in async and sync versions:
148151

149152
```python
150153
from markitdown import MarkItDown
@@ -164,13 +167,13 @@ result = md.convert("test.pdf")
164167
print(result.text_content)
165168
```
166169

167-
To use Large Language Models for image descriptions, provide `llm_client` and `llm_model`:
170+
To use Large Language Models for image descriptions, provide `llm_client` (AsyncOpenAI) and `llm_model` (str), and enable the `[openai]` optional dependency group:
168171

169172
```python
170173
from markitdown import MarkItDown
171-
from openai import OpenAI
174+
from openai import AsyncOpenAI
172175

173-
client = OpenAI()
176+
client = AsyncOpenAI()
174177
md = MarkItDown(llm_client=client, llm_model="gpt-4o")
175178
result = md.convert("example.jpg")
176179
print(result.text_content)
@@ -187,7 +190,7 @@ docker run --rm -i markitdown:latest < ~/your-file.pdf > output.md
187190

188191
This project welcomes contributions and suggestions. Most contributions require you to agree to a
189192
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
190-
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
193+
the rights to use your contribution. For details, visit <https://cla.opensource.microsoft.com>.
191194

192195
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
193196
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
@@ -212,13 +215,13 @@ You can help by looking at issues or helping review PRs. Any issue or PR is welc
212215

213216
### Running Tests and Checks
214217

215-
- Navigate to the MarkItDown package:
218+
* Navigate to the MarkItDown package:
216219

217220
```sh
218221
cd packages/markitdown
219222
```
220223

221-
- Install `hatch` in your environment and run tests:
224+
* Install `hatch` in your environment and run tests:
222225

223226
```sh
224227
pip install hatch # Other ways of installing hatch: https://hatch.pypa.io/dev/install/
@@ -233,7 +236,7 @@ You can help by looking at issues or helping review PRs. Any issue or PR is welc
233236
hatch test
234237
```
235238

236-
- Run pre-commit checks before submitting a PR: `pre-commit run --all-files`
239+
* Run pre-commit checks before submitting a PR: `pre-commit run --all-files`
237240

238241
### Contributing 3rd-party Plugins
239242

packages/markitdown-mcp/src/markitdown_mcp/__main__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
@mcp.tool()
2121
async def convert_to_markdown(uri: str) -> str:
2222
"""Convert a resource described by an http:, https:, file: or data: URI to markdown"""
23-
return MarkItDown(enable_plugins=check_plugins_enabled()).convert_uri(uri).markdown
23+
return (
24+
await MarkItDown(enable_plugins=check_plugins_enabled()).convert_uri_async(uri)
25+
).markdown
2426

2527

2628
def check_plugins_enabled() -> bool:

packages/markitdown-sample-plugin/README.md

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,25 @@ class RtfConverter(DocumentConverter):
2020
):
2121
super().__init__(priority=priority)
2222

23-
def accepts(
23+
async def accepts_async(
2424
self,
2525
file_stream: BinaryIO,
2626
stream_info: StreamInfo,
2727
**kwargs: Any,
2828
) -> bool:
29-
30-
# Implement logic to check if the file stream is an RTF file
31-
# ...
32-
raise NotImplementedError()
29+
# Implement logic to check if the file stream is an RTF file
30+
# ...
31+
raise NotImplementedError()
3332

34-
35-
def convert(
33+
async def convert_async(
3634
self,
3735
file_stream: BinaryIO,
3836
stream_info: StreamInfo,
3937
**kwargs: Any,
4038
) -> DocumentConverterResult:
41-
42-
# Implement logic to convert the file stream to Markdown
43-
# ...
44-
raise NotImplementedError()
39+
# Implement logic to convert the file stream to Markdown
40+
# ...
41+
raise NotImplementedError()
4542
```
4643

4744
Next, make sure your package implements and exports the following:
@@ -98,7 +95,7 @@ In Python, plugins can be enabled as follows:
9895
from markitdown import MarkItDown
9996

10097
md = MarkItDown(enable_plugins=True)
101-
result = md.convert("path-to-file.rtf")
98+
result = await md.convert_async("path-to-file.rtf")
10299
print(result.text_content)
103100
```
104101

packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class RtfConverter(DocumentConverter):
3636
Converts an RTF file to in the simplest possible way.
3737
"""
3838

39-
def accepts(
39+
async def accepts_async(
4040
self,
4141
file_stream: BinaryIO,
4242
stream_info: StreamInfo,
@@ -54,7 +54,7 @@ def accepts(
5454

5555
return False
5656

57-
def convert(
57+
async def convert_async(
5858
self,
5959
file_stream: BinaryIO,
6060
stream_info: StreamInfo,

packages/markitdown-sample-plugin/tests/test_sample_plugin.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env python3 -m pytest
2+
import asyncio
23
import os
34

45
from markitdown import MarkItDown, StreamInfo
@@ -12,11 +13,11 @@
1213
}
1314

1415

15-
def test_converter() -> None:
16+
async def test_converter() -> None:
1617
"""Tests the RTF converter dirctly."""
1718
with open(os.path.join(TEST_FILES_DIR, "test.rtf"), "rb") as file_stream:
1819
converter = RtfConverter()
19-
result = converter.convert(
20+
result = await converter.convert_async(
2021
file_stream=file_stream,
2122
stream_info=StreamInfo(
2223
mimetype="text/rtf", extension=".rtf", filename="test.rtf"
@@ -38,6 +39,6 @@ def test_markitdown() -> None:
3839

3940
if __name__ == "__main__":
4041
"""Runs this file's tests from the command line."""
41-
test_converter()
42+
asyncio.run(test_converter())
4243
test_markitdown()
4344
print("All tests passed.")

packages/markitdown/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# MarkItDown
22

33
> [!IMPORTANT]
4-
> MarkItDown is a Python package and command-line utility for converting various files to Markdown (e.g., for indexing, text analysis, etc).
4+
> MarkItDown is a Python package and command-line utility for converting various files to Markdown (e.g., for indexing, text analysis, etc).
55
>
66
> For more information, and full documentation, see the project [README.md](https://github.com/microsoft/markitdown) on GitHub.
77

packages/markitdown/pyproject.toml

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ readme = "README.md"
1010
requires-python = ">=3.10"
1111
license = "MIT"
1212
keywords = []
13-
authors = [
14-
{ name = "Adam Fourney", email = "adamfo@microsoft.com" },
15-
]
13+
authors = [{ name = "Adam Fourney", email = "adamfo@microsoft.com" }]
1614
classifiers = [
1715
"Development Status :: 4 - Beta",
1816
"Programming Language :: Python",
@@ -25,12 +23,13 @@ classifiers = [
2523
]
2624
dependencies = [
2725
"beautifulsoup4",
28-
"requests",
2926
"markdownify",
3027
"magika~=0.6.1",
3128
"charset-normalizer",
3229
"defusedxml",
3330
"onnxruntime<=1.20.1; sys_platform == 'win32'",
31+
"aiohttp",
32+
"openai",
3433
]
3534

3635
[project.optional-dependencies]
@@ -47,7 +46,7 @@ all = [
4746
"SpeechRecognition",
4847
"youtube-transcript-api~=1.0.0",
4948
"azure-ai-documentintelligence",
50-
"azure-identity"
49+
"azure-identity",
5150
]
5251
pptx = ["python-pptx"]
5352
docx = ["mammoth", "lxml"]
@@ -75,16 +74,11 @@ features = ["all"]
7574

7675
[tool.hatch.envs.hatch-test]
7776
features = ["all"]
78-
extra-dependencies = [
79-
"openai",
80-
]
77+
parallel = true
8178

8279
[tool.hatch.envs.types]
8380
features = ["all"]
84-
extra-dependencies = [
85-
"openai",
86-
"mypy>=1.0.0",
87-
]
81+
extra-dependencies = ["mypy>=1.0.0"]
8882

8983
[tool.hatch.envs.types.scripts]
9084
check = "mypy --install-types --non-interactive --ignore-missing-imports {args:src/markitdown tests}"
@@ -93,20 +87,14 @@ check = "mypy --install-types --non-interactive --ignore-missing-imports {args:s
9387
source_pkgs = ["markitdown", "tests"]
9488
branch = true
9589
parallel = true
96-
omit = [
97-
"src/markitdown/__about__.py",
98-
]
90+
omit = ["src/markitdown/__about__.py"]
9991

10092
[tool.coverage.paths]
10193
markitdown = ["src/markitdown", "*/markitdown/src/markitdown"]
10294
tests = ["tests", "*/markitdown/tests"]
10395

10496
[tool.coverage.report]
105-
exclude_lines = [
106-
"no cov",
107-
"if __name__ == .__main__.:",
108-
"if TYPE_CHECKING:",
109-
]
97+
exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
11098

11199
[tool.hatch.build.targets.sdist]
112100
only-include = ["src/markitdown"]

0 commit comments

Comments
 (0)