docling-rs 0.15.1__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.
- docling_rs-0.15.1/PKG-INFO +248 -0
- docling_rs-0.15.1/README.md +233 -0
- docling_rs-0.15.1/docling/Cargo.toml +57 -0
- docling_rs-0.15.1/docling/README.md +597 -0
- docling_rs-0.15.1/docling/examples/convert.rs +17 -0
- docling_rs-0.15.1/docling/examples/stream.rs +38 -0
- docling_rs-0.15.1/docling/sample.html +42 -0
- docling_rs-0.15.1/docling/sample.md +13 -0
- docling_rs-0.15.1/docling/src/backend/asciidoc.rs +326 -0
- docling_rs-0.15.1/docling/src/backend/browser.rs +138 -0
- docling_rs-0.15.1/docling/src/backend/csv.rs +109 -0
- docling_rs-0.15.1/docling/src/backend/deepseek.rs +203 -0
- docling_rs-0.15.1/docling/src/backend/docling_json.rs +280 -0
- docling_rs-0.15.1/docling/src/backend/docx.rs +1371 -0
- docling_rs-0.15.1/docling/src/backend/email.rs +120 -0
- docling_rs-0.15.1/docling/src/backend/epub.rs +241 -0
- docling_rs-0.15.1/docling/src/backend/html.rs +1510 -0
- docling_rs-0.15.1/docling/src/backend/images.rs +258 -0
- docling_rs-0.15.1/docling/src/backend/jats.rs +952 -0
- docling_rs-0.15.1/docling/src/backend/latex.rs +469 -0
- docling_rs-0.15.1/docling/src/backend/markdown.rs +667 -0
- docling_rs-0.15.1/docling/src/backend/mhtml.rs +198 -0
- docling_rs-0.15.1/docling/src/backend/mod.rs +101 -0
- docling_rs-0.15.1/docling/src/backend/odf.rs +1378 -0
- docling_rs-0.15.1/docling/src/backend/omml.rs +507 -0
- docling_rs-0.15.1/docling/src/backend/ooxml.rs +229 -0
- docling_rs-0.15.1/docling/src/backend/pptx.rs +504 -0
- docling_rs-0.15.1/docling/src/backend/uspto.rs +1075 -0
- docling_rs-0.15.1/docling/src/backend/uspto_entities.rs +2178 -0
- docling_rs-0.15.1/docling/src/backend/webvtt.rs +191 -0
- docling_rs-0.15.1/docling/src/backend/xbrl.rs +110 -0
- docling_rs-0.15.1/docling/src/backend/xlsx.rs +305 -0
- docling_rs-0.15.1/docling/src/converter.rs +373 -0
- docling_rs-0.15.1/docling/src/dclx.rs +58 -0
- docling_rs-0.15.1/docling/src/error.rs +60 -0
- docling_rs-0.15.1/docling/src/format.rs +102 -0
- docling_rs-0.15.1/docling/src/lib.rs +51 -0
- docling_rs-0.15.1/docling/src/result.rs +27 -0
- docling_rs-0.15.1/docling/src/source.rs +73 -0
- docling_rs-0.15.1/docling/src/stream.rs +193 -0
- docling_rs-0.15.1/docling-asr/Cargo.toml +22 -0
- docling_rs-0.15.1/docling-asr/README.md +597 -0
- docling_rs-0.15.1/docling-asr/src/audio.rs +167 -0
- docling_rs-0.15.1/docling-asr/src/lib.rs +99 -0
- docling_rs-0.15.1/docling-asr/src/mel.rs +197 -0
- docling_rs-0.15.1/docling-asr/src/tokenizer.rs +170 -0
- docling_rs-0.15.1/docling-asr/src/whisper.rs +513 -0
- docling_rs-0.15.1/docling-core/Cargo.toml +21 -0
- docling_rs-0.15.1/docling-core/README.md +597 -0
- docling_rs-0.15.1/docling-core/src/base64.rs +85 -0
- docling_rs-0.15.1/docling-core/src/doclang.rs +1679 -0
- docling_rs-0.15.1/docling-core/src/document.rs +415 -0
- docling_rs-0.15.1/docling-core/src/json.rs +732 -0
- docling_rs-0.15.1/docling-core/src/labels.rs +43 -0
- docling_rs-0.15.1/docling-core/src/lib.rs +26 -0
- docling_rs-0.15.1/docling-core/src/markdown.rs +876 -0
- docling_rs-0.15.1/docling-pdf/Cargo.toml +27 -0
- docling_rs-0.15.1/docling-pdf/README.md +597 -0
- docling_rs-0.15.1/docling-pdf/examples/dump_chars.rs +24 -0
- docling_rs-0.15.1/docling-pdf/examples/dump_regions.rs +54 -0
- docling_rs-0.15.1/docling-pdf/examples/dump_render_modes.rs +48 -0
- docling_rs-0.15.1/docling-pdf/examples/dump_stages.rs +20 -0
- docling_rs-0.15.1/docling-pdf/examples/extract.rs +26 -0
- docling_rs-0.15.1/docling-pdf/examples/layout.rs +32 -0
- docling_rs-0.15.1/docling-pdf/examples/probe_page.rs +67 -0
- docling_rs-0.15.1/docling-pdf/examples/snapshot.rs +88 -0
- docling_rs-0.15.1/docling-pdf/examples/textparse_dump.rs +44 -0
- docling_rs-0.15.1/docling-pdf/examples/textparse_glyphs.rs +18 -0
- docling_rs-0.15.1/docling-pdf/examples/tf_otsl.rs +73 -0
- docling_rs-0.15.1/docling-pdf/examples/word_cells.rs +17 -0
- docling_rs-0.15.1/docling-pdf/src/assemble.rs +1559 -0
- docling_rs-0.15.1/docling-pdf/src/dp_lines.rs +485 -0
- docling_rs-0.15.1/docling-pdf/src/layout.rs +154 -0
- docling_rs-0.15.1/docling-pdf/src/lib.rs +814 -0
- docling_rs-0.15.1/docling-pdf/src/mets.rs +164 -0
- docling_rs-0.15.1/docling-pdf/src/ocr.rs +223 -0
- docling_rs-0.15.1/docling-pdf/src/pdfium_backend.rs +978 -0
- docling_rs-0.15.1/docling-pdf/src/resample.rs +70 -0
- docling_rs-0.15.1/docling-pdf/src/tableformer.rs +633 -0
- docling_rs-0.15.1/docling-pdf/src/textparse.rs +1456 -0
- docling_rs-0.15.1/docling-pdf/src/timing.rs +65 -0
- docling_rs-0.15.1/docling-py/Cargo.lock +2914 -0
- docling_rs-0.15.1/docling-py/Cargo.toml +31 -0
- docling_rs-0.15.1/docling-py/README.md +233 -0
- docling_rs-0.15.1/docling-py/src/lib.rs +254 -0
- docling_rs-0.15.1/docling-py/tests/test_config.py +165 -0
- docling_rs-0.15.1/docling-py/tests/test_dropin.py +60 -0
- docling_rs-0.15.1/pyproject.toml +35 -0
- docling_rs-0.15.1/python/docling_rs/__init__.py +237 -0
- docling_rs-0.15.1/python/docling_rs/models.py +130 -0
- docling_rs-0.15.1/python/docling_rs/options.py +122 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: docling-rs
|
|
3
|
+
Version: 0.15.1
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Programming Language :: Rust
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Requires-Dist: docling-core>=2.0
|
|
9
|
+
Summary: Rust port of docling (docling.rs): convert PDF/DOCX/HTML/... to Markdown or docling JSON. Python bindings.
|
|
10
|
+
License: MIT
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
13
|
+
Project-URL: Repository, https://github.com/docling-project/docling.rs
|
|
14
|
+
|
|
15
|
+
# docling-py — Python bindings (PyO3)
|
|
16
|
+
|
|
17
|
+
A **strangler-fig drop-in** for Python docling's common conversion path,
|
|
18
|
+
backed by the Rust [docling.rs](https://github.com/docling-project/docling.rs) engine:
|
|
19
|
+
same call shape, no torch, ~4× faster PDF conversion at a fraction of the
|
|
20
|
+
memory (see [`PDF_PERFORMANCE.md`](../../PDF_PERFORMANCE.md)).
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
# was: from docling.document_converter import DocumentConverter
|
|
24
|
+
from docling_rs import DocumentConverter
|
|
25
|
+
|
|
26
|
+
result = DocumentConverter().convert("document.pdf")
|
|
27
|
+
print(result.document.export_to_markdown())
|
|
28
|
+
data = result.document.export_to_dict() # docling-core JSON wire format (schema 1.10.0)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Only the document processor is Rust.** The engine parses the input and returns
|
|
32
|
+
docling-core's JSON wire format; this package validates it into a genuine
|
|
33
|
+
[`docling_core.types.doc.DoclingDocument`](https://github.com/docling-project/docling-core).
|
|
34
|
+
So `result.document` **is** the docling object — `export_to_markdown()`,
|
|
35
|
+
`export_to_dict()`, `export_to_doctags()`, the serializers, and the
|
|
36
|
+
[chunkers](https://github.com/docling-project/docling-core) are docling's own
|
|
37
|
+
Python code, unchanged. `docling-core` is a runtime dependency; nothing else from
|
|
38
|
+
docling is required for the declarative path.
|
|
39
|
+
|
|
40
|
+
> **Status: experimental.** The PyPI distribution name is `docling-rs`.
|
|
41
|
+
> Releases are cut manually (like the npm package) via the
|
|
42
|
+
> [`pypi-publish`](../../.github/workflows/pypi-publish.yml) workflow — see
|
|
43
|
+
> [Publishing](#publishing) below. The crate is intentionally outside the repo's
|
|
44
|
+
> Cargo workspace and its crates.io publish flow. For development, build and
|
|
45
|
+
> install locally as shown next.
|
|
46
|
+
|
|
47
|
+
## Try it locally
|
|
48
|
+
|
|
49
|
+
Needs a Rust toolchain (1.82+) and Python ≥ 3.9.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd crates/docling-py
|
|
53
|
+
|
|
54
|
+
# 1. Build + install into the CURRENT virtualenv (create one first):
|
|
55
|
+
python -m venv .venv && source .venv/bin/activate
|
|
56
|
+
pip install maturin
|
|
57
|
+
maturin develop --release # compiles the Rust engine, installs `docling.rs`
|
|
58
|
+
|
|
59
|
+
# 2. One-time model download (~700 MB → ~/.cache/docling.rs), pure Python —
|
|
60
|
+
# fetched from the repo's models-v1 GitHub release, like docling fetches
|
|
61
|
+
# its artifacts. Declarative formats (DOCX/HTML/XLSX/…) skip this entirely.
|
|
62
|
+
python -c "import docling_rs; docling_rs.download_models()"
|
|
63
|
+
|
|
64
|
+
# 3. Convert:
|
|
65
|
+
python - <<'PY'
|
|
66
|
+
from docling_rs import DocumentConverter
|
|
67
|
+
|
|
68
|
+
conv = DocumentConverter()
|
|
69
|
+
result = conv.convert("../../tests/data/pdf/sources/2305.03393v1-pg9.pdf")
|
|
70
|
+
print(result.status) # "success"
|
|
71
|
+
print(result.document.export_to_markdown()[:400])
|
|
72
|
+
PY
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API surface (docling-shaped)
|
|
76
|
+
|
|
77
|
+
| docling.rs | docling counterpart | notes |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `DocumentConverter(format_options=None, *, allowed_formats=None, do_ocr=True, do_table_structure=True, fetch_images=False, use_web_browser=False, artifacts_path=None)` | `DocumentConverter(allowed_formats=…, format_options=…)` | Pass `{InputFormat.PDF: PdfFormatOption(pipeline_options=PdfPipelineOptions(…))}` or the shorthand kwargs; `allowed_formats` restricts conversion; `artifacts_path` overrides the model cache dir. |
|
|
80
|
+
| `.convert(path \| DocumentStream) -> ConversionResult` | `.convert(source)` | str / `pathlib.Path` / `DocumentStream`. Releases the GIL during conversion. |
|
|
81
|
+
| `.convert_all(sources, raises_on_error=True) -> Iterator[ConversionResult]` | same | lazily converts many sources; `raises_on_error=False` yields a `failure` result instead of raising |
|
|
82
|
+
| `.initialize_pipeline(format=None)` | same | pre-loads the PDF/image ML models so the first conversion isn't slow and later PDFs reuse the warm pipeline (no-op for non-ML formats; needs the models available) |
|
|
83
|
+
| `.convert_bytes(name, data)` | `DocumentStream` | extension of `name` drives format detection |
|
|
84
|
+
| `InputFormat`, `PdfPipelineOptions`, `PdfFormatOption`, `AcceleratorOptions`, `TableFormerMode`, `DocumentStream`, `ImageRefMode` | same modules | docling-shaped config re-exported from `docling_rs` (see below) |
|
|
85
|
+
| `ConversionError` | `docling.exceptions.ConversionError` | raised on a failed conversion; caught by `convert_all(..., raises_on_error=False)` |
|
|
86
|
+
| `result.status` / `result.document` / `result.input.file` | same | `.status` is a `ConversionStatus` str-enum (`"success" / "partial_success" / "failure"`); `.document` is a genuine `docling_core` `DoclingDocument` |
|
|
87
|
+
| `document.export_to_markdown(...)` | same | docling-core's own method — all of docling's params (`image_placeholder`, `page_break_placeholder`, …) apply |
|
|
88
|
+
| `document.export_to_dict()` / `export_to_json()` / `export_to_doctags()` | same | docling-core's own serializers over the wire format |
|
|
89
|
+
| `document.save_as_markdown(p)` / `save_as_json(p)` / chunkers | same | anything `docling_core` offers on a `DoclingDocument` works, since it *is* one |
|
|
90
|
+
| `docling_rs.download_models()` | `docling-tools models download` | idempotent; `~/.cache/docling.rs` or `$DOCLING_RS_CACHE_DIR`; INT8 models fetched when hosted and preferred automatically (`DOCLING_RS_FP32=1` opts out) |
|
|
91
|
+
|
|
92
|
+
Model/env resolution order: explicit `DOCLING_*` env vars → the cache dir set
|
|
93
|
+
by `ensure_env()` (called by the constructor) → the process CWD (`models/`,
|
|
94
|
+
`.pdfium/`, matching the CLI). pdfium is Linux x64 from the release; on other
|
|
95
|
+
platforms set `PDFIUM_DYNAMIC_LIB_PATH` to a local build.
|
|
96
|
+
|
|
97
|
+
## Configuration (docling-shaped)
|
|
98
|
+
|
|
99
|
+
`docling_rs` re-exports docling-shaped config objects — same names and fields, so
|
|
100
|
+
docling code reads unchanged:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from docling_rs import DocumentConverter, InputFormat, PdfFormatOption, PdfPipelineOptions, AcceleratorOptions
|
|
104
|
+
|
|
105
|
+
opts = PdfPipelineOptions(
|
|
106
|
+
do_ocr=False, # skip OCR on scanned pages
|
|
107
|
+
do_table_structure=True, # TableFormer table recovery
|
|
108
|
+
accelerator_options=AcceleratorOptions(num_threads=4),
|
|
109
|
+
)
|
|
110
|
+
conv = DocumentConverter(format_options={InputFormat.PDF: PdfFormatOption(pipeline_options=opts)})
|
|
111
|
+
# shorthand: DocumentConverter(do_ocr=False, do_table_structure=True)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The Rust engine acts on `do_ocr`, `do_table_structure`, and
|
|
115
|
+
`accelerator_options.num_threads` (→ ONNX Runtime intra-op threads via
|
|
116
|
+
`DOCLING_RS_PDF_THREADS`). The remaining `PdfPipelineOptions` fields
|
|
117
|
+
(`images_scale`, `generate_page_images`, `table_structure_options.mode`, …) are
|
|
118
|
+
accepted for API compatibility but do not change the pipeline. `InputFormat`,
|
|
119
|
+
`DocumentStream` and `ImageRefMode` are re-exported too (the last straight from
|
|
120
|
+
`docling_core`, for `export_to_markdown(image_mode=…)`). A GPU
|
|
121
|
+
`accelerator_options.device` (`CUDA`/`MPS`) is accepted but warns and falls back
|
|
122
|
+
to CPU — the engine runs ONNX Runtime on the CPU execution provider.
|
|
123
|
+
|
|
124
|
+
## Not covered (yet)
|
|
125
|
+
|
|
126
|
+
VLM/enrichment pipelines, GPU accelerator devices (the engine is ONNX Runtime on
|
|
127
|
+
CPU), and per-format *backend* selection. Chunkers **are** available — the
|
|
128
|
+
returned object is a real `docling_core` `DoclingDocument`, so
|
|
129
|
+
`docling_core.transforms.chunker`'s `HierarchicalChunker` / `HybridChunker`
|
|
130
|
+
operate on it directly (install docling-core's own extras for those:
|
|
131
|
+
`pip install "docling-core[chunking]"`). The document carries rendered text for
|
|
132
|
+
inline formatting rather than structured `formatting` fields — see
|
|
133
|
+
`MIGRATION.md` §4 for the documented divergences.
|
|
134
|
+
|
|
135
|
+
## Publishing
|
|
136
|
+
|
|
137
|
+
Releases are **manual**, mirroring the npm package: the
|
|
138
|
+
[`pypi-publish`](../../.github/workflows/pypi-publish.yml) GitHub Actions workflow
|
|
139
|
+
(`workflow_dispatch`) builds an `abi3` wheel per platform (Linux x86-64/arm64 as
|
|
140
|
+
`manylinux_2_28`, Windows x86-64 — one wheel covers every Python ≥ 3.9) plus an
|
|
141
|
+
sdist, and uploads them to PyPI.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# From the Actions tab, or:
|
|
145
|
+
gh workflow run pypi-publish.yml # version from pyproject.toml
|
|
146
|
+
gh workflow run pypi-publish.yml -f version=0.16.0
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
No secrets: it publishes via PyPI **Trusted Publishing** (OIDC), like
|
|
150
|
+
docling-core — no API token is stored or rotated. This requires a **one-time
|
|
151
|
+
setup on PyPI by the project owner** (below); re-runs are idempotent
|
|
152
|
+
(`skip-existing`). macOS wheels are omitted (no hosted runners here); macOS users
|
|
153
|
+
install the sdist, which compiles from source. The ONNX runtime is bundled in the
|
|
154
|
+
wheel; pdfium is fetched at runtime by `download_models()`.
|
|
155
|
+
|
|
156
|
+
### First-time PyPI setup (project owner)
|
|
157
|
+
|
|
158
|
+
Trusted Publishing lets this repo's workflow upload to PyPI without any password
|
|
159
|
+
or API token — GitHub mints a short-lived OIDC token per run and PyPI verifies it
|
|
160
|
+
against a *trusted publisher* you register once. Until that publisher exists, the
|
|
161
|
+
`publish` job fails with `invalid-publisher: valid token, but no corresponding
|
|
162
|
+
publisher`.
|
|
163
|
+
|
|
164
|
+
Because the `docling-rs` project does not exist on PyPI yet, register a **pending
|
|
165
|
+
publisher** (it both authorizes the workflow and lets the first run create the
|
|
166
|
+
project):
|
|
167
|
+
|
|
168
|
+
1. Sign in to PyPI as the account that will own `docling-rs`, then open
|
|
169
|
+
**<https://pypi.org/manage/account/publishing/>**.
|
|
170
|
+
2. Under **“Add a new pending publisher”**, choose **GitHub** and fill in
|
|
171
|
+
**exactly** these values:
|
|
172
|
+
|
|
173
|
+
| Field | Value |
|
|
174
|
+
|---|---|
|
|
175
|
+
| PyPI Project Name | `docling-rs` |
|
|
176
|
+
| Owner | `docling-project` |
|
|
177
|
+
| Repository name | `docling.rs` |
|
|
178
|
+
| Workflow name | `pypi-publish.yml` |
|
|
179
|
+
| Environment name | `pypi` |
|
|
180
|
+
|
|
181
|
+
3. Click **Add**. Then re-run the **pypi publish** workflow (Actions tab → Run
|
|
182
|
+
workflow) — the `publish` job will now succeed and create the project on its
|
|
183
|
+
first upload.
|
|
184
|
+
|
|
185
|
+
Notes:
|
|
186
|
+
- The Git branch does not matter: PyPI matches on repository + workflow +
|
|
187
|
+
environment, not the branch, so publishing works from any branch once the
|
|
188
|
+
publisher is registered.
|
|
189
|
+
- **Environment name must be `pypi`** — it has to match the `environment: pypi`
|
|
190
|
+
the `publish` job runs in.
|
|
191
|
+
- After the first successful publish the project exists; the *pending* publisher
|
|
192
|
+
automatically becomes a regular trusted publisher (manage it later at
|
|
193
|
+
*Project → Manage → Publishing*). To rotate/add publishers there, use the same
|
|
194
|
+
five values above.
|
|
195
|
+
- To rehearse without touching production PyPI, register the equivalent pending
|
|
196
|
+
publisher on **TestPyPI** (<https://test.pypi.org/manage/account/publishing/>)
|
|
197
|
+
and point the workflow's upload at `https://test.pypi.org/legacy/`.
|
|
198
|
+
|
|
199
|
+
### Test the release build locally
|
|
200
|
+
|
|
201
|
+
Reproduce what CI does — build the wheel + sdist and verify both install and run
|
|
202
|
+
— before (or instead of) triggering the workflow. Needs a Rust toolchain and
|
|
203
|
+
Python ≥ 3.9.
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
cd crates/docling-py
|
|
207
|
+
python -m venv .venv && source .venv/bin/activate
|
|
208
|
+
pip install maturin
|
|
209
|
+
|
|
210
|
+
# 1. Build the same two artifacts the workflow builds.
|
|
211
|
+
maturin build --release --out dist # dist/docling_rs-<v>-cp39-abi3-<platform>.whl
|
|
212
|
+
maturin sdist --out dist # dist/docling_rs-<v>.tar.gz (vendors all crates)
|
|
213
|
+
|
|
214
|
+
# 2. Smoke-test the WHEEL in a clean env — pip pulls docling-core from the
|
|
215
|
+
# wheel's declared dependency, exactly as an end user would get it.
|
|
216
|
+
python -m venv /tmp/wheel-test
|
|
217
|
+
/tmp/wheel-test/bin/pip install dist/docling_rs-*.whl
|
|
218
|
+
/tmp/wheel-test/bin/python - <<'PY'
|
|
219
|
+
from docling_rs import DocumentConverter
|
|
220
|
+
r = DocumentConverter().convert("../../tests/data/html/sources/hyperlink_03.html")
|
|
221
|
+
assert r.status == "success"
|
|
222
|
+
assert type(r.document).__module__.startswith("docling_core") # the real DoclingDocument
|
|
223
|
+
print("wheel OK:", len(r.document.export_to_markdown()), "md chars")
|
|
224
|
+
PY
|
|
225
|
+
|
|
226
|
+
# 3. Verify the SDIST is self-contained: pip compiles the Rust engine from source
|
|
227
|
+
# (this is the exact unpack-and-build path cibuildwheel runs in the manylinux
|
|
228
|
+
# containers, so a green result here means the CI wheel build will work too).
|
|
229
|
+
python -m venv /tmp/sdist-test
|
|
230
|
+
/tmp/sdist-test/bin/pip install dist/docling_rs-*.tar.gz
|
|
231
|
+
/tmp/sdist-test/bin/python -c "import docling_rs; print('sdist build OK')"
|
|
232
|
+
|
|
233
|
+
# 4. Run the declarative-path test suite (no ML models needed).
|
|
234
|
+
pip install pytest docling-core
|
|
235
|
+
pytest tests/
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
To exercise the full manylinux wheel build (what `pypa/cibuildwheel` runs) you
|
|
239
|
+
need Docker; with a daemon available:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
pipx run cibuildwheel==2.21.3 --platform linux --output-dir wheelhouse .
|
|
243
|
+
# env: CIBW_BUILD=cp39-* CIBW_SKIP=*-musllinux* CIBW_BEFORE_ALL_LINUX="curl … rustup … -y"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
An optional final rehearsal uploads to **TestPyPI** (needs a TestPyPI token or a
|
|
247
|
+
pending publisher there): `pip install twine && twine upload --repository testpypi dist/*`.
|
|
248
|
+
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# docling-py — Python bindings (PyO3)
|
|
2
|
+
|
|
3
|
+
A **strangler-fig drop-in** for Python docling's common conversion path,
|
|
4
|
+
backed by the Rust [docling.rs](https://github.com/docling-project/docling.rs) engine:
|
|
5
|
+
same call shape, no torch, ~4× faster PDF conversion at a fraction of the
|
|
6
|
+
memory (see [`PDF_PERFORMANCE.md`](../../PDF_PERFORMANCE.md)).
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
# was: from docling.document_converter import DocumentConverter
|
|
10
|
+
from docling_rs import DocumentConverter
|
|
11
|
+
|
|
12
|
+
result = DocumentConverter().convert("document.pdf")
|
|
13
|
+
print(result.document.export_to_markdown())
|
|
14
|
+
data = result.document.export_to_dict() # docling-core JSON wire format (schema 1.10.0)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Only the document processor is Rust.** The engine parses the input and returns
|
|
18
|
+
docling-core's JSON wire format; this package validates it into a genuine
|
|
19
|
+
[`docling_core.types.doc.DoclingDocument`](https://github.com/docling-project/docling-core).
|
|
20
|
+
So `result.document` **is** the docling object — `export_to_markdown()`,
|
|
21
|
+
`export_to_dict()`, `export_to_doctags()`, the serializers, and the
|
|
22
|
+
[chunkers](https://github.com/docling-project/docling-core) are docling's own
|
|
23
|
+
Python code, unchanged. `docling-core` is a runtime dependency; nothing else from
|
|
24
|
+
docling is required for the declarative path.
|
|
25
|
+
|
|
26
|
+
> **Status: experimental.** The PyPI distribution name is `docling-rs`.
|
|
27
|
+
> Releases are cut manually (like the npm package) via the
|
|
28
|
+
> [`pypi-publish`](../../.github/workflows/pypi-publish.yml) workflow — see
|
|
29
|
+
> [Publishing](#publishing) below. The crate is intentionally outside the repo's
|
|
30
|
+
> Cargo workspace and its crates.io publish flow. For development, build and
|
|
31
|
+
> install locally as shown next.
|
|
32
|
+
|
|
33
|
+
## Try it locally
|
|
34
|
+
|
|
35
|
+
Needs a Rust toolchain (1.82+) and Python ≥ 3.9.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd crates/docling-py
|
|
39
|
+
|
|
40
|
+
# 1. Build + install into the CURRENT virtualenv (create one first):
|
|
41
|
+
python -m venv .venv && source .venv/bin/activate
|
|
42
|
+
pip install maturin
|
|
43
|
+
maturin develop --release # compiles the Rust engine, installs `docling.rs`
|
|
44
|
+
|
|
45
|
+
# 2. One-time model download (~700 MB → ~/.cache/docling.rs), pure Python —
|
|
46
|
+
# fetched from the repo's models-v1 GitHub release, like docling fetches
|
|
47
|
+
# its artifacts. Declarative formats (DOCX/HTML/XLSX/…) skip this entirely.
|
|
48
|
+
python -c "import docling_rs; docling_rs.download_models()"
|
|
49
|
+
|
|
50
|
+
# 3. Convert:
|
|
51
|
+
python - <<'PY'
|
|
52
|
+
from docling_rs import DocumentConverter
|
|
53
|
+
|
|
54
|
+
conv = DocumentConverter()
|
|
55
|
+
result = conv.convert("../../tests/data/pdf/sources/2305.03393v1-pg9.pdf")
|
|
56
|
+
print(result.status) # "success"
|
|
57
|
+
print(result.document.export_to_markdown()[:400])
|
|
58
|
+
PY
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## API surface (docling-shaped)
|
|
62
|
+
|
|
63
|
+
| docling.rs | docling counterpart | notes |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `DocumentConverter(format_options=None, *, allowed_formats=None, do_ocr=True, do_table_structure=True, fetch_images=False, use_web_browser=False, artifacts_path=None)` | `DocumentConverter(allowed_formats=…, format_options=…)` | Pass `{InputFormat.PDF: PdfFormatOption(pipeline_options=PdfPipelineOptions(…))}` or the shorthand kwargs; `allowed_formats` restricts conversion; `artifacts_path` overrides the model cache dir. |
|
|
66
|
+
| `.convert(path \| DocumentStream) -> ConversionResult` | `.convert(source)` | str / `pathlib.Path` / `DocumentStream`. Releases the GIL during conversion. |
|
|
67
|
+
| `.convert_all(sources, raises_on_error=True) -> Iterator[ConversionResult]` | same | lazily converts many sources; `raises_on_error=False` yields a `failure` result instead of raising |
|
|
68
|
+
| `.initialize_pipeline(format=None)` | same | pre-loads the PDF/image ML models so the first conversion isn't slow and later PDFs reuse the warm pipeline (no-op for non-ML formats; needs the models available) |
|
|
69
|
+
| `.convert_bytes(name, data)` | `DocumentStream` | extension of `name` drives format detection |
|
|
70
|
+
| `InputFormat`, `PdfPipelineOptions`, `PdfFormatOption`, `AcceleratorOptions`, `TableFormerMode`, `DocumentStream`, `ImageRefMode` | same modules | docling-shaped config re-exported from `docling_rs` (see below) |
|
|
71
|
+
| `ConversionError` | `docling.exceptions.ConversionError` | raised on a failed conversion; caught by `convert_all(..., raises_on_error=False)` |
|
|
72
|
+
| `result.status` / `result.document` / `result.input.file` | same | `.status` is a `ConversionStatus` str-enum (`"success" / "partial_success" / "failure"`); `.document` is a genuine `docling_core` `DoclingDocument` |
|
|
73
|
+
| `document.export_to_markdown(...)` | same | docling-core's own method — all of docling's params (`image_placeholder`, `page_break_placeholder`, …) apply |
|
|
74
|
+
| `document.export_to_dict()` / `export_to_json()` / `export_to_doctags()` | same | docling-core's own serializers over the wire format |
|
|
75
|
+
| `document.save_as_markdown(p)` / `save_as_json(p)` / chunkers | same | anything `docling_core` offers on a `DoclingDocument` works, since it *is* one |
|
|
76
|
+
| `docling_rs.download_models()` | `docling-tools models download` | idempotent; `~/.cache/docling.rs` or `$DOCLING_RS_CACHE_DIR`; INT8 models fetched when hosted and preferred automatically (`DOCLING_RS_FP32=1` opts out) |
|
|
77
|
+
|
|
78
|
+
Model/env resolution order: explicit `DOCLING_*` env vars → the cache dir set
|
|
79
|
+
by `ensure_env()` (called by the constructor) → the process CWD (`models/`,
|
|
80
|
+
`.pdfium/`, matching the CLI). pdfium is Linux x64 from the release; on other
|
|
81
|
+
platforms set `PDFIUM_DYNAMIC_LIB_PATH` to a local build.
|
|
82
|
+
|
|
83
|
+
## Configuration (docling-shaped)
|
|
84
|
+
|
|
85
|
+
`docling_rs` re-exports docling-shaped config objects — same names and fields, so
|
|
86
|
+
docling code reads unchanged:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from docling_rs import DocumentConverter, InputFormat, PdfFormatOption, PdfPipelineOptions, AcceleratorOptions
|
|
90
|
+
|
|
91
|
+
opts = PdfPipelineOptions(
|
|
92
|
+
do_ocr=False, # skip OCR on scanned pages
|
|
93
|
+
do_table_structure=True, # TableFormer table recovery
|
|
94
|
+
accelerator_options=AcceleratorOptions(num_threads=4),
|
|
95
|
+
)
|
|
96
|
+
conv = DocumentConverter(format_options={InputFormat.PDF: PdfFormatOption(pipeline_options=opts)})
|
|
97
|
+
# shorthand: DocumentConverter(do_ocr=False, do_table_structure=True)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The Rust engine acts on `do_ocr`, `do_table_structure`, and
|
|
101
|
+
`accelerator_options.num_threads` (→ ONNX Runtime intra-op threads via
|
|
102
|
+
`DOCLING_RS_PDF_THREADS`). The remaining `PdfPipelineOptions` fields
|
|
103
|
+
(`images_scale`, `generate_page_images`, `table_structure_options.mode`, …) are
|
|
104
|
+
accepted for API compatibility but do not change the pipeline. `InputFormat`,
|
|
105
|
+
`DocumentStream` and `ImageRefMode` are re-exported too (the last straight from
|
|
106
|
+
`docling_core`, for `export_to_markdown(image_mode=…)`). A GPU
|
|
107
|
+
`accelerator_options.device` (`CUDA`/`MPS`) is accepted but warns and falls back
|
|
108
|
+
to CPU — the engine runs ONNX Runtime on the CPU execution provider.
|
|
109
|
+
|
|
110
|
+
## Not covered (yet)
|
|
111
|
+
|
|
112
|
+
VLM/enrichment pipelines, GPU accelerator devices (the engine is ONNX Runtime on
|
|
113
|
+
CPU), and per-format *backend* selection. Chunkers **are** available — the
|
|
114
|
+
returned object is a real `docling_core` `DoclingDocument`, so
|
|
115
|
+
`docling_core.transforms.chunker`'s `HierarchicalChunker` / `HybridChunker`
|
|
116
|
+
operate on it directly (install docling-core's own extras for those:
|
|
117
|
+
`pip install "docling-core[chunking]"`). The document carries rendered text for
|
|
118
|
+
inline formatting rather than structured `formatting` fields — see
|
|
119
|
+
`MIGRATION.md` §4 for the documented divergences.
|
|
120
|
+
|
|
121
|
+
## Publishing
|
|
122
|
+
|
|
123
|
+
Releases are **manual**, mirroring the npm package: the
|
|
124
|
+
[`pypi-publish`](../../.github/workflows/pypi-publish.yml) GitHub Actions workflow
|
|
125
|
+
(`workflow_dispatch`) builds an `abi3` wheel per platform (Linux x86-64/arm64 as
|
|
126
|
+
`manylinux_2_28`, Windows x86-64 — one wheel covers every Python ≥ 3.9) plus an
|
|
127
|
+
sdist, and uploads them to PyPI.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# From the Actions tab, or:
|
|
131
|
+
gh workflow run pypi-publish.yml # version from pyproject.toml
|
|
132
|
+
gh workflow run pypi-publish.yml -f version=0.16.0
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
No secrets: it publishes via PyPI **Trusted Publishing** (OIDC), like
|
|
136
|
+
docling-core — no API token is stored or rotated. This requires a **one-time
|
|
137
|
+
setup on PyPI by the project owner** (below); re-runs are idempotent
|
|
138
|
+
(`skip-existing`). macOS wheels are omitted (no hosted runners here); macOS users
|
|
139
|
+
install the sdist, which compiles from source. The ONNX runtime is bundled in the
|
|
140
|
+
wheel; pdfium is fetched at runtime by `download_models()`.
|
|
141
|
+
|
|
142
|
+
### First-time PyPI setup (project owner)
|
|
143
|
+
|
|
144
|
+
Trusted Publishing lets this repo's workflow upload to PyPI without any password
|
|
145
|
+
or API token — GitHub mints a short-lived OIDC token per run and PyPI verifies it
|
|
146
|
+
against a *trusted publisher* you register once. Until that publisher exists, the
|
|
147
|
+
`publish` job fails with `invalid-publisher: valid token, but no corresponding
|
|
148
|
+
publisher`.
|
|
149
|
+
|
|
150
|
+
Because the `docling-rs` project does not exist on PyPI yet, register a **pending
|
|
151
|
+
publisher** (it both authorizes the workflow and lets the first run create the
|
|
152
|
+
project):
|
|
153
|
+
|
|
154
|
+
1. Sign in to PyPI as the account that will own `docling-rs`, then open
|
|
155
|
+
**<https://pypi.org/manage/account/publishing/>**.
|
|
156
|
+
2. Under **“Add a new pending publisher”**, choose **GitHub** and fill in
|
|
157
|
+
**exactly** these values:
|
|
158
|
+
|
|
159
|
+
| Field | Value |
|
|
160
|
+
|---|---|
|
|
161
|
+
| PyPI Project Name | `docling-rs` |
|
|
162
|
+
| Owner | `docling-project` |
|
|
163
|
+
| Repository name | `docling.rs` |
|
|
164
|
+
| Workflow name | `pypi-publish.yml` |
|
|
165
|
+
| Environment name | `pypi` |
|
|
166
|
+
|
|
167
|
+
3. Click **Add**. Then re-run the **pypi publish** workflow (Actions tab → Run
|
|
168
|
+
workflow) — the `publish` job will now succeed and create the project on its
|
|
169
|
+
first upload.
|
|
170
|
+
|
|
171
|
+
Notes:
|
|
172
|
+
- The Git branch does not matter: PyPI matches on repository + workflow +
|
|
173
|
+
environment, not the branch, so publishing works from any branch once the
|
|
174
|
+
publisher is registered.
|
|
175
|
+
- **Environment name must be `pypi`** — it has to match the `environment: pypi`
|
|
176
|
+
the `publish` job runs in.
|
|
177
|
+
- After the first successful publish the project exists; the *pending* publisher
|
|
178
|
+
automatically becomes a regular trusted publisher (manage it later at
|
|
179
|
+
*Project → Manage → Publishing*). To rotate/add publishers there, use the same
|
|
180
|
+
five values above.
|
|
181
|
+
- To rehearse without touching production PyPI, register the equivalent pending
|
|
182
|
+
publisher on **TestPyPI** (<https://test.pypi.org/manage/account/publishing/>)
|
|
183
|
+
and point the workflow's upload at `https://test.pypi.org/legacy/`.
|
|
184
|
+
|
|
185
|
+
### Test the release build locally
|
|
186
|
+
|
|
187
|
+
Reproduce what CI does — build the wheel + sdist and verify both install and run
|
|
188
|
+
— before (or instead of) triggering the workflow. Needs a Rust toolchain and
|
|
189
|
+
Python ≥ 3.9.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
cd crates/docling-py
|
|
193
|
+
python -m venv .venv && source .venv/bin/activate
|
|
194
|
+
pip install maturin
|
|
195
|
+
|
|
196
|
+
# 1. Build the same two artifacts the workflow builds.
|
|
197
|
+
maturin build --release --out dist # dist/docling_rs-<v>-cp39-abi3-<platform>.whl
|
|
198
|
+
maturin sdist --out dist # dist/docling_rs-<v>.tar.gz (vendors all crates)
|
|
199
|
+
|
|
200
|
+
# 2. Smoke-test the WHEEL in a clean env — pip pulls docling-core from the
|
|
201
|
+
# wheel's declared dependency, exactly as an end user would get it.
|
|
202
|
+
python -m venv /tmp/wheel-test
|
|
203
|
+
/tmp/wheel-test/bin/pip install dist/docling_rs-*.whl
|
|
204
|
+
/tmp/wheel-test/bin/python - <<'PY'
|
|
205
|
+
from docling_rs import DocumentConverter
|
|
206
|
+
r = DocumentConverter().convert("../../tests/data/html/sources/hyperlink_03.html")
|
|
207
|
+
assert r.status == "success"
|
|
208
|
+
assert type(r.document).__module__.startswith("docling_core") # the real DoclingDocument
|
|
209
|
+
print("wheel OK:", len(r.document.export_to_markdown()), "md chars")
|
|
210
|
+
PY
|
|
211
|
+
|
|
212
|
+
# 3. Verify the SDIST is self-contained: pip compiles the Rust engine from source
|
|
213
|
+
# (this is the exact unpack-and-build path cibuildwheel runs in the manylinux
|
|
214
|
+
# containers, so a green result here means the CI wheel build will work too).
|
|
215
|
+
python -m venv /tmp/sdist-test
|
|
216
|
+
/tmp/sdist-test/bin/pip install dist/docling_rs-*.tar.gz
|
|
217
|
+
/tmp/sdist-test/bin/python -c "import docling_rs; print('sdist build OK')"
|
|
218
|
+
|
|
219
|
+
# 4. Run the declarative-path test suite (no ML models needed).
|
|
220
|
+
pip install pytest docling-core
|
|
221
|
+
pytest tests/
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
To exercise the full manylinux wheel build (what `pypa/cibuildwheel` runs) you
|
|
225
|
+
need Docker; with a daemon available:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
pipx run cibuildwheel==2.21.3 --platform linux --output-dir wheelhouse .
|
|
229
|
+
# env: CIBW_BUILD=cp39-* CIBW_SKIP=*-musllinux* CIBW_BEFORE_ALL_LINUX="curl … rustup … -y"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
An optional final rehearsal uploads to **TestPyPI** (needs a TestPyPI token or a
|
|
233
|
+
pending publisher there): `pip install twine && twine upload --repository testpypi dist/*`.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "docling"
|
|
3
|
+
description = "DocumentConverter and format backends for docling.rs (a Rust port of docling)."
|
|
4
|
+
version = "0.30.0"
|
|
5
|
+
edition = "2021"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
repository = "https://github.com/docling-project/docling.rs"
|
|
8
|
+
homepage = "https://github.com/docling-project/docling.rs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
keywords = ["docling", "document", "pdf", "markdown", "converter"]
|
|
11
|
+
categories = ["text-processing", "parser-implementations"]
|
|
12
|
+
rust-version = "1.74.0"
|
|
13
|
+
# The 25 MB output-regression corpus under tests/ is dev-only — keep it out of
|
|
14
|
+
# the published crate (and under crates.io's size limit). benches/ reference that
|
|
15
|
+
# corpus by path, so they're dev-only too.
|
|
16
|
+
exclude = ["tests", "benches"]
|
|
17
|
+
|
|
18
|
+
[features]
|
|
19
|
+
# Optional headless-browser HTML pre-render (the `--use-web-browser` flag /
|
|
20
|
+
# `DocumentConverter::use_web_browser`). Off by default so the standard build
|
|
21
|
+
# stays pure-Rust with no browser/runtime dependency; enable with
|
|
22
|
+
# `--features web-browser`. Drives the system Chromium over the DevTools
|
|
23
|
+
# protocol purely from Rust (no Node/Playwright) via `headless_chrome`.
|
|
24
|
+
web-browser = ["dep:headless_chrome"]
|
|
25
|
+
|
|
26
|
+
[dependencies]
|
|
27
|
+
calamine = { version = "0.26", features = ["dates"] }
|
|
28
|
+
csv = "1.4.0"
|
|
29
|
+
headless_chrome = { version = "1.0", optional = true }
|
|
30
|
+
docling-asr = { path = "../docling-asr", version = "0.30.0" }
|
|
31
|
+
docling-core = { path = "../docling-core", version = "0.30.0" }
|
|
32
|
+
docling-pdf = { path = "../docling-pdf", version = "0.30.0" }
|
|
33
|
+
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "gif", "bmp", "tiff", "webp"] }
|
|
34
|
+
mail-parser = "0.11"
|
|
35
|
+
pulldown-cmark = { version = "0.13.4", default-features = false }
|
|
36
|
+
quick-xml = "0.37"
|
|
37
|
+
regex = "1.11"
|
|
38
|
+
roxmltree = "0.20"
|
|
39
|
+
scraper = "0.27.0"
|
|
40
|
+
serde_json = "1"
|
|
41
|
+
# Blocking HTTP client for fetching remote <img src> images when image fetching
|
|
42
|
+
# is enabled (DocumentConverter::fetch_images). 3.x matches the version `ort`
|
|
43
|
+
# already pulls in (so the graph keeps a single ureq); it needs Rust 1.85, which
|
|
44
|
+
# this crate already requires transitively via docling-pdf → ort.
|
|
45
|
+
ureq = "3"
|
|
46
|
+
zip = { version = "2.2", default-features = false, features = ["deflate"] }
|
|
47
|
+
|
|
48
|
+
[[example]]
|
|
49
|
+
name = "convert"
|
|
50
|
+
path = "examples/convert.rs"
|
|
51
|
+
|
|
52
|
+
[[example]]
|
|
53
|
+
name = "stream"
|
|
54
|
+
path = "examples/stream.rs"
|
|
55
|
+
|
|
56
|
+
[dev-dependencies]
|
|
57
|
+
criterion = "0.5"
|