Metadata-Version: 2.4
Name: mag-parse
Version: 0.3.1
Summary: One parse() for every document - PDFs via mag-pdf, everything else via mag-file-handler
Author-email: Magure <aman.p@magureinc.com>
Maintainer-email: Magure <aman.p@magureinc.com>
License: Apache-2.0
Project-URL: Homepage, https://github.com/magurelabs/magoneai-file-handler
Project-URL: Source, https://github.com/magurelabs/magoneai-file-handler
Project-URL: Issues, https://github.com/magurelabs/magoneai-file-handler/issues
Keywords: pdf,docx,xlsx,ocr,extraction,markdown,parsing
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Processing
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: pdf
Requires-Dist: mag-pdf<1,>=0.3; extra == "pdf"
Provides-Extra: tables
Requires-Dist: mag-pdf[tables]<1,>=0.3; extra == "tables"
Provides-Extra: office
Requires-Dist: mag-file-handler<1,>=0.3; extra == "office"
Provides-Extra: http
Requires-Dist: fastapi<1,>=0.110; extra == "http"
Requires-Dist: uvicorn[standard]<1,>=0.27; extra == "http"
Requires-Dist: python-multipart>=0.0.9; extra == "http"
Provides-Extra: http-test
Requires-Dist: mag-parse[http]; extra == "http-test"
Requires-Dist: httpx>=0.27; extra == "http-test"
Requires-Dist: pytest>=7; extra == "http-test"
Requires-Dist: pytest-timeout>=2.1; extra == "http-test"
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Requires-Dist: pytest-timeout>=2.1; extra == "test"
Provides-Extra: all
Requires-Dist: mag-parse[office,pdf]; extra == "all"
Provides-Extra: everything
Requires-Dist: mag-parse[http,office,tables]; extra == "everything"
Dynamic: license-file

# mag-parse

One `parse()` for every document. PDFs go to [`mag-pdf`](https://pypi.org/project/mag-pdf/);
everything else goes to [`mag-file-handler`](https://pypi.org/project/mag-file-handler/).

```python
from mag_parse import parse

r = parse("invoice.pdf")                      # quality="fast" (default)
r = parse("report.pdf", quality="accurate")   # table structure
r = parse("deck.pptx")                        # quality does not apply
r = parse(pdf_bytes, filename="report.pdf")   # bytes work too

r.markdown        # always populated
r.text            # always populated
r.format          # "pdf" | "docx" | "xlsx" | "eml" | ...
r.engine          # which engine actually ran
r.ok, r.error, r.warnings
```

The facade itself has **zero required dependencies**. Both engines are extras,
and that is not tidiness — see [Install](#install).

## Routing

Decided by **magic bytes, never the extension**. A `.docx` renamed to `.pdf`
does not reach the PDF engine, and a PDF with no extension does.

```
parse(file, quality=...)
        │
   sniff %PDF
        │
   ┌────┴─────┐
 PDF        not PDF
   │            │
mag-pdf   mag-file-handler
   │            │
   └────┬───────┘
        ▼
    one Result
```

## `quality` is a PDF-only knob

| | What runs | Install |
|---|---|---|
| `"fast"` (default) | LiteParse behind an OCR gate; OCRs only pages that need it | `mag-parse[pdf]` |
| `"accurate"` | DocLayout-YOLO + TableFormer for real table structure | `mag-parse[tables]` |

For every other format `quality` is **accepted and ignored**, so a caller never
has to branch on file type. `Result.quality` is `""` there — the knob did not
default, it does not exist.

> `"accurate"` selects mag-pdf's `profile="tables"`, leaving `table_mode` at
> its default. Those are two different fast/accurate axes: the profile picks
> the *pipeline*, `table_mode` picks TableFormer's *weights*. The heavier
> weights measured 27.6 s against 16.1 s for one additional table row, so they
> are not what "accurate" buys — the table pipeline is.

## Markdown, and how honest it is

Both `.markdown` and `.text` are always populated, on every path including
failure. They are not equally structured, and `structured_markdown` says which
you got:

| Source | `.markdown` | `structured` |
|---|---|---|
| PDF | real GFM — pipe tables, headings | `True` |
| docx / xlsx / pptx / html / eml | real GFM — headings and pipe tables | `True` |
| md / txt / csv | the text, which is already faithful | `False` |

Measured on generated fixtures with known content: every format above recovers
100% of its title, prose, table cells and numeric values, and seven of the
eight produce pipe tables a chunker can find. Plain text is the exception, and
correctly so — there is no table in it to preserve.

Read **`result.structured`**, not the raw `structured_markdown` field. The
property additionally requires that an extraction succeeded and that the
markdown really does contain a heading or a table; the raw field is set by the
engine adapter and reported `True` even for an error result carrying no
markdown at all.

## Errors

A **bad document is a Result**, never an exception — check `.ok` / `.error`.
Only two things raise, and both are the caller's to fix:

| Exception | Means |
|---|---|
| `UnknownQuality` | `quality=` was not `"fast"` or `"accurate"`. Raised *before* any I/O, so a typo cannot run the wrong pipeline on a large file first. |
| `EngineNotAvailable` | The extra for this file type is not installed. Names the exact `pip install`. |

`Result.status` is `"ok"`, `"empty"` or `"error"`. **`empty` is a failure** — an
extraction that returns nothing while reporting success is the silent failure
both engines guard against, and the facade does not launder it.

## HTTP server

```bash
pip install 'mag-parse[http,pdf,office]'
mag-parse-http --host 0.0.0.0 --port 8080
```

```bash
curl -sf -F file=@invoice.pdf -F quality=accurate localhost:8080/v1/parse
curl -sf -F file=@notes.docx localhost:8080/v1/parse -H 'Accept: text/markdown'
curl -s localhost:8080/readyz
```

| Endpoint | What it is for |
|---|---|
| `POST /v1/parse` | multipart: `file`, `quality=fast\|accurate`, `password?` |
| `GET /livez` | process alive - restart only if this fails |
| `GET /readyz` | engines warm - stop routing if this fails |
| `GET /metrics` | Prometheus |
| `GET /version` | version, lanes, active limits |

A bad document is `200` with `ok: false`; a bad server is `4xx`/`5xx` as
`application/problem+json`. `X-Request-Id` is echoed if you send one, minted if
you do not.

Set `MAGPARSE_FAIL_ON_EMPTY=true` to turn an extraction that produced no
content into a `422` rather than a `200` nobody notices.

## Install

```bash
pip install 'mag-parse[pdf]'        # PDFs; one dependency, Tesseract bundled
pip install 'mag-parse[office]'     # docx, xlsx, pptx, eml, html, md, txt, csv
pip install 'mag-parse[all]'        # both of the above
pip install 'mag-parse[tables]'     # adds quality="accurate" (~2 GB: torch)
pip install 'mag-parse[all,http]'   # + the HTTP server (`mag-parse-http`)
```

`serve` carries no engine on purpose - it is composed with whichever of the
above you need, so a PDF-only sidecar does not inherit the office stack's
platform limits.

The engines are extras rather than dependencies because **`extractous`** (under
`mag-file-handler`) publishes no Linux ARM64 wheel and declares
`requires-python <3.14`, while **`liteparse`** (under `mag-pdf`) has neither
limit. Making the office side required would impose both constraints on
PDF-only users:

| Platform | `[pdf]` | `[office]` |
|---|---|---|
| Linux x86-64 | ✅ | ✅ |
| Linux ARM64 | ✅ | ❌ no wheel |
| macOS ARM64 / x86-64 | ✅ | ✅ |
| Windows x86-64 | ✅ | ✅ |
| Python 3.14 | ✅ | ❌ `<3.14` |

Python 3.10+. Apache-2.0.
