Metadata-Version: 2.4
Name: multipart-rs
Version: 0.0.1
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: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Rust
Requires-Dist: python-multipart>=0.0.26 ; extra == 'bench'
Requires-Dist: pytest>=9.0.3 ; extra == 'dev'
Requires-Dist: pyyaml>=6.0.3 ; extra == 'dev'
Requires-Dist: ruff>=0.15.10 ; extra == 'dev'
Provides-Extra: bench
Provides-Extra: dev
Summary: A fast multipart/form-data parser powered by Rust
License-Expression: Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# multipart-rs

A drop-in replacement for [python-multipart](https://github.com/Kludex/python-multipart) with a Rust-powered core via [PyO3](https://pyo3.rs/).

Swap `python-multipart` for `multipart-rs` with **zero code changes** in Starlette / FastAPI applications.

**Supports Python 3.10 – 3.14** on Linux, macOS, and Windows.

## Performance

Benchmarked on Apple M4 Pro, Python 3.13 (median of 10 runs):

| Benchmark | python-multipart | multipart-rs | Speedup |
|---|--:|--:|--:|
| Small form (~2 KB) | 50.6 µs | 4.3 µs | **11.7x** |
| 100 small files (~100 KB) | 3.14 ms | 172.3 µs | **18.2x** |
| Mixed fields+files (~5 MB) | 833.9 µs | 185.5 µs | **4.5x** |
| Single large file (50 MB) | 6.54 ms | 1.58 ms | **4.1x** |
| Querystring small (~500 B) | 25.0 µs | 5.4 µs | **4.7x** |
| Querystring large (~1 MB) | 1.42 ms | 365.5 µs | **3.9x** |

**Geometric mean: 5.1x faster**

## Installation

```bash
pip install multipart-rs
```

## Usage

multipart-rs exposes the exact same API as python-multipart v0.0.26. Replace the import and everything works:

```python
# Before
from python_multipart.multipart import MultipartParser, QuerystringParser

# After
from multipart_rs import MultipartParser, QuerystringParser
```

### With Starlette / FastAPI

```python
# settings.py or conftest.py — patch before Starlette imports the parser
import multipart_rs
import sys
sys.modules["multipart"] = multipart_rs
sys.modules["multipart.multipart"] = multipart_rs
```

### Low-level parser

```python
from multipart_rs import MultipartParser

def on_part_data(data, start, end):
    print(f"Got {end - start} bytes of part data")

parser = MultipartParser(
    boundary=b"----formdata",
    callbacks={
        "on_part_begin": lambda: print("part begin"),
        "on_part_data": on_part_data,
        "on_part_end": lambda: print("part end"),
        "on_end": lambda: print("done"),
    },
)
parser.write(raw_body)
parser.finalize()
```

### High-level form parsing

```python
from multipart_rs import parse_form

fields = []
files = []

parse_form(
    headers={"Content-Type": b"multipart/form-data; boundary=----formdata"},
    input_stream=stream,
    on_field=lambda field: fields.append(field),
    on_file=lambda file: files.append(file),
)
```

## API

All of the following are importable from `multipart_rs`:

| Export | Description |
|---|---|
| `MultipartParser` | Streaming multipart/form-data parser |
| `QuerystringParser` | Streaming querystring parser |
| `OctetStreamParser` | Streaming octet-stream parser |
| `FormParser` | High-level parser (auto-selects based on Content-Type) |
| `Field` | Parsed form field |
| `File` | Parsed file upload |
| `parse_options_header` | RFC 2231 header parser |
| `create_form_parser` | Create a FormParser from headers |
| `parse_form` | One-shot form parsing |
| `Base64Decoder` | Base64 content-transfer-encoding decoder |
| `QuotedPrintableDecoder` | Quoted-printable decoder |

### Exceptions

```
FormParserError(ValueError)
├── ParseError(FormParserError)
│   ├── MultipartParseError
│   ├── QuerystringParseError
│   └── DecodeError
└── FileError(FormParserError, OSError)
```

## Development

```bash
# Setup
uv venv .venv && source .venv/bin/activate
uv pip install maturin

# Build and install in dev mode
maturin develop --release

# Run tests
pytest tests/ -v

# Run benchmarks (requires python-multipart for comparison)
uv pip install python-multipart
python benchmarks/bench.py          # side-by-side comparison
python benchmarks/bench.py rust     # multipart-rs only
```

## Architecture

The performance-critical parsing logic runs in Rust. The Python layer handles orchestration, file I/O, and API compatibility.

```
src/                              # Rust core (PyO3)
├── multipart.rs                  # Multipart parser state machine
├── querystring.rs                # Querystring parser state machine
├── octet_stream.rs               # Octet-stream parser
└── parse_options_header.rs       # RFC 2231 header parsing

python/multipart_rs/              # Python compatibility layer
├── __init__.py                   # Public API re-exports
├── _compat.py                    # Field, File, FormParser
├── decoders.py                   # Base64/QuotedPrintable decoders
└── exceptions.py                 # Exception hierarchy
```

The Rust parser processes the entire buffer in a single call and returns a list of events `(event_type, start, end)`. The Python wrapper dispatches callbacks using the original buffer with zero-copy slicing.

## License

Apache-2.0

