docline 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docline/__init__.py +1 -0
- docline/__main__.py +16 -0
- docline/_tools/__init__.py +9 -0
- docline/_tools/docling_worker.py +404 -0
- docline/app.py +1048 -0
- docline/app_models.py +201 -0
- docline/cli.py +595 -0
- docline/config.py +57 -0
- docline/dependencies.py +86 -0
- docline/elt/__init__.py +1 -0
- docline/elt/config.py +202 -0
- docline/elt/execute.py +608 -0
- docline/elt/manifest_models.py +93 -0
- docline/elt/models.py +82 -0
- docline/elt/orchestrate.py +47 -0
- docline/elt/paths.py +74 -0
- docline/elt/source_keys.py +79 -0
- docline/fetch/__init__.py +0 -0
- docline/fetch/crawl.py +557 -0
- docline/fetch/html_extract.py +290 -0
- docline/fetch/html_normalize.py +92 -0
- docline/fetch/http.py +159 -0
- docline/fetch/models.py +43 -0
- docline/fetch/sitemap.py +272 -0
- docline/fetch/staging.py +166 -0
- docline/fetch/url_canonical.py +127 -0
- docline/fetch/url_policy.py +92 -0
- docline/mcp/__init__.py +1 -0
- docline/mcp/exceptions.py +7 -0
- docline/mcp/server.py +113 -0
- docline/paths.py +145 -0
- docline/process/__init__.py +5 -0
- docline/process/assemble.py +162 -0
- docline/process/ast_lint.py +65 -0
- docline/process/batch_dispatch.py +243 -0
- docline/process/canonical_url.py +149 -0
- docline/process/correction.py +62 -0
- docline/process/cross_doc_links.py +179 -0
- docline/process/docfx_includes.py +126 -0
- docline/process/docfx_normalize.py +144 -0
- docline/process/docfx_tabs.py +105 -0
- docline/process/fidelity_scorer.py +870 -0
- docline/process/hashing.py +22 -0
- docline/process/heading_validation.py +241 -0
- docline/process/identity.py +37 -0
- docline/process/manifest.py +84 -0
- docline/process/metadata.py +86 -0
- docline/process/ocr_cap.py +99 -0
- docline/process/output.py +25 -0
- docline/process/output_contract.py +449 -0
- docline/process/page_range.py +199 -0
- docline/process/pdf_batch.py +568 -0
- docline/process/pdf_triage.py +1114 -0
- docline/process/prompts.py +57 -0
- docline/process/quality_metrics.py +213 -0
- docline/process/quarantine.py +66 -0
- docline/process/segment.py +211 -0
- docline/process/toc_parser.py +122 -0
- docline/process/transcripts.py +134 -0
- docline/progress.py +279 -0
- docline/quarantine_viewer.py +125 -0
- docline/readers/__init__.py +1 -0
- docline/readers/documents.py +54 -0
- docline/readers/docx.py +692 -0
- docline/readers/github.py +207 -0
- docline/readers/limits.py +106 -0
- docline/readers/mistral.py +201 -0
- docline/readers/openapi/__init__.py +11 -0
- docline/readers/openapi/convert.py +339 -0
- docline/readers/openapi/detect.py +124 -0
- docline/readers/openapi/errors.py +29 -0
- docline/readers/openapi/loader.py +176 -0
- docline/readers/openapi/reader.py +313 -0
- docline/readers/openapi/render.py +418 -0
- docline/readers/openapi/resolve.py +192 -0
- docline/readers/pdf.py +940 -0
- docline/readers/pdf_splitter.py +139 -0
- docline/readers/picture_sink.py +113 -0
- docline/readers/text.py +47 -0
- docline/readers/transcripts.py +215 -0
- docline/router.py +46 -0
- docline/runtime/__init__.py +30 -0
- docline/runtime/ocr_budget.py +168 -0
- docline/runtime/resource_probe.py +369 -0
- docline/schema/__init__.py +0 -0
- docline/schema/export.py +43 -0
- docline/schema/library.py +200 -0
- docline/schema/models.py +71 -0
- docline/types.py +36 -0
- docline-0.1.0.dist-info/METADATA +277 -0
- docline-0.1.0.dist-info/RECORD +95 -0
- docline-0.1.0.dist-info/WHEEL +5 -0
- docline-0.1.0.dist-info/entry_points.txt +2 -0
- docline-0.1.0.dist-info/licenses/LICENSE +21 -0
- docline-0.1.0.dist-info/top_level.txt +1 -0
docline/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""docline — document ingestion and normalization pipeline."""
|
docline/__main__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Package entrypoint for ``python -m docline``."""
|
|
2
|
+
|
|
3
|
+
from docline.cli import main as cli_main
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main() -> int:
|
|
7
|
+
"""Run the package entrypoint.
|
|
8
|
+
|
|
9
|
+
Returns:
|
|
10
|
+
Process exit code.
|
|
11
|
+
"""
|
|
12
|
+
return cli_main()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Internal subprocess CLIs invoked by the docline batch processor.
|
|
2
|
+
|
|
3
|
+
These tools are not part of the public docline CLI surface; they exist
|
|
4
|
+
so the batch processor can run docling (which loads heavy PyTorch
|
|
5
|
+
models and can OOM with hard-to-catch C-level errors) in an isolated
|
|
6
|
+
child process. If a child crashes, the OS reaps it cleanly and the
|
|
7
|
+
parent records a non-zero exit code, downgrading that chunk to the
|
|
8
|
+
heuristic engine.
|
|
9
|
+
"""
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""Subprocess CLI that runs docling on a single PDF or a batch of PDFs.
|
|
2
|
+
|
|
3
|
+
Invoked by :mod:`docline.process.pdf_batch` and
|
|
4
|
+
:mod:`docline.process.pdf_triage`.
|
|
5
|
+
|
|
6
|
+
Single-chunk mode (legacy)::
|
|
7
|
+
|
|
8
|
+
python -m docline._tools.docling_worker INPUT_PDF OUTPUT_MD [--no-ocr] [--ocr-scale=N]
|
|
9
|
+
|
|
10
|
+
Batched mode (030-F T3)::
|
|
11
|
+
|
|
12
|
+
python -m docline._tools.docling_worker --batch MANIFEST_JSON
|
|
13
|
+
|
|
14
|
+
In batched mode the worker imports docling and loads its layout model
|
|
15
|
+
ONCE, then iterates the manifest writing one envelope per chunk to its
|
|
16
|
+
output path. This amortizes the ~5-10s docling cold-start cost across
|
|
17
|
+
N chunks instead of paying it per invocation. The manifest format is::
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
"chunks": [
|
|
21
|
+
{"input": "/abs/path/chunk-1.pdf", "output": "/abs/path/chunk-1.md"},
|
|
22
|
+
{"input": "/abs/path/chunk-2.pdf", "output": "/abs/path/chunk-2.md"}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Each chunk may include an optional ``"do_ocr"`` boolean (default ``true``);
|
|
27
|
+
set it ``false`` to skip OCR for ranges whose pages already carry an
|
|
28
|
+
extractable text layer (034-F). The single-chunk CLI exposes the same
|
|
29
|
+
control via the optional ``--no-ocr`` flag. Each chunk may also include an
|
|
30
|
+
optional ``"ocr_scale"`` float (040-F) overriding the page render scale
|
|
31
|
+
(``images_scale``); a lower value cuts OCR peak memory. The single-chunk CLI
|
|
32
|
+
exposes this via ``--ocr-scale=N``.
|
|
33
|
+
|
|
34
|
+
Per-chunk failures during batched processing write an error envelope to
|
|
35
|
+
that chunk's output path (with the legacy schema_version=1 plus an
|
|
36
|
+
``error`` field) and the worker continues with the next chunk. The
|
|
37
|
+
process exit code is 0 if AT LEAST ONE chunk succeeded; non-zero only
|
|
38
|
+
if docling import or model load itself failed (so the parent decides
|
|
39
|
+
whether to retry single-chunk or fall back to heuristic for everything).
|
|
40
|
+
|
|
41
|
+
Writes a JSON envelope (see schema below) describing the docling-rendered
|
|
42
|
+
Markdown to the output path(s) and exits 0 on success. On failure (missing
|
|
43
|
+
input, docling import error, docling runtime error, or any other
|
|
44
|
+
exception), writes a structured JSON diagnostic line to stderr and
|
|
45
|
+
exits non-zero so the parent batch processor can route the chunk to
|
|
46
|
+
the heuristic fallback.
|
|
47
|
+
|
|
48
|
+
The exit codes are:
|
|
49
|
+
|
|
50
|
+
* ``0`` — success (at least one chunk succeeded in batched mode)
|
|
51
|
+
* ``2`` — bad CLI arguments or malformed manifest
|
|
52
|
+
* ``3`` — input PDF does not exist (single-chunk mode only;
|
|
53
|
+
per-chunk in batched mode writes an error envelope instead)
|
|
54
|
+
* ``4`` — docling extras not installed
|
|
55
|
+
* ``5`` — docling raised an exception during conversion
|
|
56
|
+
(single-chunk mode; or import / model load failure in batched mode)
|
|
57
|
+
* ``6`` — any other unexpected error (including envelope serialization
|
|
58
|
+
or all-chunks-failed in batched mode)
|
|
59
|
+
|
|
60
|
+
Output envelope (schema_version 1)
|
|
61
|
+
----------------------------------
|
|
62
|
+
|
|
63
|
+
The output file is a JSON object with the following shape::
|
|
64
|
+
|
|
65
|
+
{
|
|
66
|
+
"schema_version": 1,
|
|
67
|
+
"pages": ["page 1 markdown", "page 2 markdown", ...],
|
|
68
|
+
"page_count": N,
|
|
69
|
+
"text": "page 1 markdown\\n\\npage 2 markdown\\n\\n..."
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
In batched mode, a chunk that failed mid-loop has the additional
|
|
73
|
+
``error`` field set to ``repr(exception)`` and ``pages``/``page_count``
|
|
74
|
+
empty/zero so consumers can detect the failure and route to heuristic
|
|
75
|
+
fallback for that chunk only.
|
|
76
|
+
|
|
77
|
+
Fields:
|
|
78
|
+
|
|
79
|
+
* ``schema_version`` — integer; bumped on any breaking envelope change.
|
|
80
|
+
* ``pages`` — list of per-page markdown strings, one entry per source
|
|
81
|
+
PDF page.
|
|
82
|
+
* ``page_count`` — integer; redundancy check, equals ``len(pages)``.
|
|
83
|
+
* ``text`` — convenience field; equals ``"\\n\\n".join(pages)``.
|
|
84
|
+
* ``error`` — present only when this chunk failed in batched mode;
|
|
85
|
+
consumers check ``"error" in envelope`` to detect.
|
|
86
|
+
|
|
87
|
+
Running each chunk in its own subprocess gives the OS a chance to
|
|
88
|
+
reclaim torch tensor working set between calls (PyTorch's CPU
|
|
89
|
+
allocator does not return memory to the OS reliably) and contains
|
|
90
|
+
C-level crashes (``c10::Error``, SIGABRT, etc.) that would otherwise
|
|
91
|
+
abort the parent. Batched mode trades that per-chunk isolation for
|
|
92
|
+
shared model-load cost — use it for chunk groups where the per-chunk
|
|
93
|
+
isolation is less critical than the model-load amortization.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
from __future__ import annotations
|
|
97
|
+
|
|
98
|
+
import json
|
|
99
|
+
import sys
|
|
100
|
+
from pathlib import Path
|
|
101
|
+
from typing import Any
|
|
102
|
+
|
|
103
|
+
ENVELOPE_SCHEMA_VERSION = 1
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _emit_diagnostic(stage: str, error: str, exception: str | None = None) -> None:
|
|
107
|
+
"""Write a one-line JSON diagnostic to stderr."""
|
|
108
|
+
|
|
109
|
+
payload: dict[str, str] = {"stage": stage, "error": error}
|
|
110
|
+
if exception:
|
|
111
|
+
payload["exception"] = exception
|
|
112
|
+
sys.stderr.write(json.dumps(payload) + "\n")
|
|
113
|
+
sys.stderr.flush()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _build_envelope(pages: list[str]) -> dict[str, object]:
|
|
117
|
+
"""Build the JSON envelope from a per-page markdown list.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
pages: Per-page markdown strings as returned by
|
|
121
|
+
:func:`docline.readers.pdf._read_pdf_docling_pages`.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
A dict with ``schema_version``, ``pages``, ``page_count``,
|
|
125
|
+
and ``text`` keys. Safe to pass to :func:`json.dumps`.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
"schema_version": ENVELOPE_SCHEMA_VERSION,
|
|
130
|
+
"pages": pages,
|
|
131
|
+
"page_count": len(pages),
|
|
132
|
+
"text": "\n\n".join(pages),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _build_error_envelope(exception: Exception) -> dict[str, object]:
|
|
137
|
+
"""Build an error envelope for a failed chunk in batched mode.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
exception: The exception raised while processing the chunk.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
An envelope with empty ``pages`` and an ``error`` field
|
|
144
|
+
containing ``repr(exception)`` for diagnostic surfacing.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
"schema_version": ENVELOPE_SCHEMA_VERSION,
|
|
149
|
+
"pages": [],
|
|
150
|
+
"page_count": 0,
|
|
151
|
+
"text": "",
|
|
152
|
+
"error": repr(exception),
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _write_envelope(output_path: Path, envelope: dict[str, object]) -> None:
|
|
157
|
+
"""Serialize and write an envelope to ``output_path``."""
|
|
158
|
+
|
|
159
|
+
body = json.dumps(envelope, ensure_ascii=False)
|
|
160
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
161
|
+
output_path.write_text(body, encoding="utf-8")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _run_single(
|
|
165
|
+
input_path: Path,
|
|
166
|
+
output_path: Path,
|
|
167
|
+
*,
|
|
168
|
+
do_ocr: bool = True,
|
|
169
|
+
ocr_scale: float | None = None,
|
|
170
|
+
) -> int:
|
|
171
|
+
"""Single-chunk mode entry point.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
input_path: Source PDF path.
|
|
175
|
+
output_path: Envelope output path.
|
|
176
|
+
do_ocr: When ``False`` the docling OCR engine is skipped (034-F).
|
|
177
|
+
ocr_scale: Optional page render-scale override (040-F); ``None`` keeps
|
|
178
|
+
the docling default.
|
|
179
|
+
|
|
180
|
+
Returns the process exit code per the table in the module docstring.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
if not input_path.exists():
|
|
184
|
+
_emit_diagnostic("input", f"input PDF not found: {input_path}")
|
|
185
|
+
return 3
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
from docline.dependencies import DependencyUnavailableError
|
|
189
|
+
from docline.readers.pdf import _read_pdf_docling_pages
|
|
190
|
+
except ImportError as err:
|
|
191
|
+
_emit_diagnostic("import", "could not import docline.readers.pdf", str(err))
|
|
192
|
+
return 6
|
|
193
|
+
|
|
194
|
+
# Forward do_ocr / ocr_scale only when they diverge from the reader defaults
|
|
195
|
+
# so the existing default-path call contract remains unchanged.
|
|
196
|
+
reader_kwargs: dict[str, Any] = {}
|
|
197
|
+
if not do_ocr:
|
|
198
|
+
reader_kwargs["do_ocr"] = False
|
|
199
|
+
if ocr_scale is not None:
|
|
200
|
+
reader_kwargs["ocr_scale"] = ocr_scale
|
|
201
|
+
try:
|
|
202
|
+
pages = _read_pdf_docling_pages(input_path, **reader_kwargs)
|
|
203
|
+
except DependencyUnavailableError as err:
|
|
204
|
+
_emit_diagnostic("docling-extras", "docling extras not installed", str(err))
|
|
205
|
+
return 4
|
|
206
|
+
except Exception as err: # noqa: BLE001 — surface to parent via exit code
|
|
207
|
+
_emit_diagnostic("docling-runtime", "docling raised during conversion", repr(err))
|
|
208
|
+
return 5
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
envelope = _build_envelope(pages)
|
|
212
|
+
except (TypeError, ValueError) as err:
|
|
213
|
+
_emit_diagnostic("envelope", "could not serialize envelope to JSON", repr(err))
|
|
214
|
+
return 6
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
_write_envelope(output_path, envelope)
|
|
218
|
+
except OSError as err:
|
|
219
|
+
_emit_diagnostic("output", f"could not write {output_path}", str(err))
|
|
220
|
+
return 6
|
|
221
|
+
except (TypeError, ValueError) as err:
|
|
222
|
+
_emit_diagnostic("envelope", "could not serialize envelope to JSON", repr(err))
|
|
223
|
+
return 6
|
|
224
|
+
|
|
225
|
+
return 0
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _run_batched(manifest_path: Path) -> int:
|
|
229
|
+
"""Batched mode entry point: shared docling model load across N chunks.
|
|
230
|
+
|
|
231
|
+
Returns the process exit code per the table in the module docstring.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
if not manifest_path.exists():
|
|
235
|
+
_emit_diagnostic("batch-manifest", f"manifest file not found: {manifest_path}")
|
|
236
|
+
return 2
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
manifest_raw = manifest_path.read_text(encoding="utf-8")
|
|
240
|
+
manifest: Any = json.loads(manifest_raw)
|
|
241
|
+
except (OSError, json.JSONDecodeError) as err:
|
|
242
|
+
_emit_diagnostic("batch-manifest", "could not read or parse manifest", str(err))
|
|
243
|
+
return 2
|
|
244
|
+
|
|
245
|
+
if (
|
|
246
|
+
not isinstance(manifest, dict)
|
|
247
|
+
or not isinstance(manifest.get("chunks"), list)
|
|
248
|
+
or not manifest["chunks"]
|
|
249
|
+
):
|
|
250
|
+
_emit_diagnostic("batch-manifest", "manifest must have non-empty 'chunks' list")
|
|
251
|
+
return 2
|
|
252
|
+
|
|
253
|
+
chunks: list[dict[str, Any]] = manifest["chunks"]
|
|
254
|
+
for i, entry in enumerate(chunks):
|
|
255
|
+
if (
|
|
256
|
+
not isinstance(entry, dict)
|
|
257
|
+
or not isinstance(entry.get("input"), str)
|
|
258
|
+
or not isinstance(entry.get("output"), str)
|
|
259
|
+
):
|
|
260
|
+
_emit_diagnostic(
|
|
261
|
+
"batch-manifest",
|
|
262
|
+
f"chunk[{i}] must have string 'input' and 'output' fields",
|
|
263
|
+
)
|
|
264
|
+
return 2
|
|
265
|
+
|
|
266
|
+
# Single import / model load — this is the perf win.
|
|
267
|
+
try:
|
|
268
|
+
from docline.dependencies import DependencyUnavailableError
|
|
269
|
+
from docline.readers.pdf import _read_pdf_docling_pages
|
|
270
|
+
except ImportError as err:
|
|
271
|
+
_emit_diagnostic("import", "could not import docline.readers.pdf", str(err))
|
|
272
|
+
return 6
|
|
273
|
+
|
|
274
|
+
success_count = 0
|
|
275
|
+
failure_count = 0
|
|
276
|
+
|
|
277
|
+
for i, entry in enumerate(chunks):
|
|
278
|
+
input_path = Path(entry["input"])
|
|
279
|
+
output_path = Path(entry["output"])
|
|
280
|
+
chunk_do_ocr = bool(entry.get("do_ocr", True))
|
|
281
|
+
chunk_ocr_scale = entry.get("ocr_scale")
|
|
282
|
+
|
|
283
|
+
if not input_path.exists():
|
|
284
|
+
err_env = _build_error_envelope(FileNotFoundError(f"input PDF not found: {input_path}"))
|
|
285
|
+
try:
|
|
286
|
+
_write_envelope(output_path, err_env)
|
|
287
|
+
except OSError as werr:
|
|
288
|
+
_emit_diagnostic(
|
|
289
|
+
"batch-output",
|
|
290
|
+
f"could not write error envelope for chunk[{i}] to {output_path}",
|
|
291
|
+
str(werr),
|
|
292
|
+
)
|
|
293
|
+
# Cannot record this failure to consumers; treat as critical.
|
|
294
|
+
return 6
|
|
295
|
+
failure_count += 1
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
reader_kwargs: dict[str, Any] = {}
|
|
300
|
+
if not chunk_do_ocr:
|
|
301
|
+
reader_kwargs["do_ocr"] = False
|
|
302
|
+
if chunk_ocr_scale is not None:
|
|
303
|
+
scale_val = float(chunk_ocr_scale)
|
|
304
|
+
if scale_val <= 0:
|
|
305
|
+
raise ValueError(f"ocr_scale must be > 0, got {scale_val}")
|
|
306
|
+
reader_kwargs["ocr_scale"] = scale_val
|
|
307
|
+
pages = _read_pdf_docling_pages(input_path, **reader_kwargs)
|
|
308
|
+
except DependencyUnavailableError as err:
|
|
309
|
+
# Extras missing affects ALL chunks identically; abort.
|
|
310
|
+
_emit_diagnostic("docling-extras", "docling extras not installed", str(err))
|
|
311
|
+
return 4
|
|
312
|
+
except Exception as err: # noqa: BLE001 — per-chunk isolation
|
|
313
|
+
err_env = _build_error_envelope(err)
|
|
314
|
+
try:
|
|
315
|
+
_write_envelope(output_path, err_env)
|
|
316
|
+
except OSError as werr:
|
|
317
|
+
_emit_diagnostic(
|
|
318
|
+
"batch-output",
|
|
319
|
+
f"could not write error envelope for chunk[{i}] to {output_path}",
|
|
320
|
+
str(werr),
|
|
321
|
+
)
|
|
322
|
+
return 6
|
|
323
|
+
failure_count += 1
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
envelope = _build_envelope(pages)
|
|
328
|
+
_write_envelope(output_path, envelope)
|
|
329
|
+
except (OSError, TypeError, ValueError) as err:
|
|
330
|
+
err_env = _build_error_envelope(err)
|
|
331
|
+
try:
|
|
332
|
+
_write_envelope(output_path, err_env)
|
|
333
|
+
except OSError as werr:
|
|
334
|
+
_emit_diagnostic(
|
|
335
|
+
"batch-output",
|
|
336
|
+
f"could not write error envelope for chunk[{i}] to {output_path}",
|
|
337
|
+
str(werr),
|
|
338
|
+
)
|
|
339
|
+
return 6
|
|
340
|
+
failure_count += 1
|
|
341
|
+
continue
|
|
342
|
+
|
|
343
|
+
success_count += 1
|
|
344
|
+
|
|
345
|
+
if success_count == 0:
|
|
346
|
+
_emit_diagnostic(
|
|
347
|
+
"batch-runtime",
|
|
348
|
+
f"all {failure_count} chunks failed in batched mode",
|
|
349
|
+
)
|
|
350
|
+
return 6
|
|
351
|
+
return 0
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def main(argv: list[str] | None = None) -> int:
|
|
355
|
+
"""Entry point for ``python -m docline._tools.docling_worker``.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
argv: Command-line arguments (without the program name).
|
|
359
|
+
Defaults to ``sys.argv[1:]``.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Process exit code per the table in the module docstring.
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
args = sys.argv[1:] if argv is None else argv
|
|
366
|
+
|
|
367
|
+
if len(args) == 2 and args[0] == "--batch":
|
|
368
|
+
return _run_batched(Path(args[1]))
|
|
369
|
+
|
|
370
|
+
# Single-chunk mode: optional --no-ocr and --ocr-scale=<float> flags.
|
|
371
|
+
do_ocr = True
|
|
372
|
+
ocr_scale: float | None = None
|
|
373
|
+
positional: list[str] = []
|
|
374
|
+
for arg in args:
|
|
375
|
+
if arg == "--no-ocr":
|
|
376
|
+
do_ocr = False
|
|
377
|
+
elif arg.startswith("--ocr-scale="):
|
|
378
|
+
raw = arg.split("=", 1)[1]
|
|
379
|
+
try:
|
|
380
|
+
ocr_scale = float(raw)
|
|
381
|
+
except ValueError:
|
|
382
|
+
_emit_diagnostic("cli", f"invalid --ocr-scale value: {raw}")
|
|
383
|
+
return 2
|
|
384
|
+
if ocr_scale <= 0:
|
|
385
|
+
_emit_diagnostic("cli", f"--ocr-scale must be > 0, got {ocr_scale}")
|
|
386
|
+
return 2
|
|
387
|
+
elif arg.startswith("--"):
|
|
388
|
+
_emit_diagnostic("cli", f"unknown flag: {arg}")
|
|
389
|
+
return 2
|
|
390
|
+
else:
|
|
391
|
+
positional.append(arg)
|
|
392
|
+
|
|
393
|
+
if len(positional) != 2:
|
|
394
|
+
_emit_diagnostic(
|
|
395
|
+
"cli",
|
|
396
|
+
"expected: INPUT_PDF OUTPUT_MD [--no-ocr] [--ocr-scale=N] | --batch MANIFEST_JSON",
|
|
397
|
+
)
|
|
398
|
+
return 2
|
|
399
|
+
|
|
400
|
+
return _run_single(Path(positional[0]), Path(positional[1]), do_ocr=do_ocr, ocr_scale=ocr_scale)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
if __name__ == "__main__":
|
|
404
|
+
raise SystemExit(main())
|