Metadata-Version: 2.5
Name: python-multipart
Version: 0.0.33
Summary: A streaming multipart parser for Python
Project-URL: Homepage, https://github.com/Kludex/python-multipart
Project-URL: Documentation, https://kludex.github.io/python-multipart/
Project-URL: Changelog, https://github.com/Kludex/python-multipart/blob/master/CHANGELOG.md
Project-URL: Source, https://github.com/Kludex/python-multipart
Author-email: Andrew Dunham <andrew@du.nham.ca>
Maintainer-email: Marcelo Trylesinski <marcelotryle@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE.txt
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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 :: Python :: 3.15
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# Python-Multipart

[![CI](https://github.com/Kludex/python-multipart/actions/workflows/main.yml/badge.svg)](https://github.com/Kludex/python-multipart/actions/workflows/main.yml)
[![PyPI](https://img.shields.io/pypi/v/python-multipart.svg)](https://pypi.org/project/python-multipart)
[![Python versions](https://img.shields.io/pypi/pyversions/python-multipart.svg)](https://pypi.org/project/python-multipart)
[![Discord](https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/RxKUF5JuHs)

Python-Multipart reads the data that a web browser sends when someone submits a form or uploads a file.

```console
pip install python-multipart
```

It powers file uploads in [Starlette](https://starlette.dev) and [FastAPI](https://fastapi.tiangolo.com).

## What problem does it solve?

### A form sends data

When you fill in a form on a website and click "Submit", your browser sends the data to a server.
The server is a program, maybe written in Python, that has to read that data and make sense of it.

### Files need a special format

A form with only text, like a username, is sent as one short line: `username=alice`.

A form with a file is different. A file can contain anything, even text that looks like `username=alice`.
So the browser uses a format called `multipart/form-data`. It splits the request into **parts**, one per form field, separated by a **boundary**: a marker string the browser picks.

This is what the server receives when you send a username and a file called `cat.txt`:

```text
--boundary
Content-Disposition: form-data; name="username"

alice
--boundary
Content-Disposition: form-data; name="avatar"; filename="cat.txt"
Content-Type: text/plain

meow
--boundary--
```

Each part has some headers (the field name, the file name) and then the content.
Every line ends with `\r\n` (a carriage return and a newline), which is why the examples below write `\r\n` explicitly.
A **parser** is the code that turns this text back into fields and files. Python-Multipart is that parser.

### Big files do not fit in memory

An upload can be a 5 GB video. If the server reads the whole request before it starts working, it needs 5 GB of memory, per upload.

Python-Multipart is a **streaming** parser. You give it the request a small piece (a **chunk**) at a time, and it tells you what it found as soon as it finds it. The full request never has to be in memory.

## Usage

### Parse a whole form

```python
from io import BytesIO

from python_multipart import parse_form

body = (
    b"--boundary\r\n"
    b'Content-Disposition: form-data; name="username"\r\n'
    b"\r\n"
    b"alice\r\n"
    b"--boundary\r\n"
    b'Content-Disposition: form-data; name="avatar"; filename="cat.txt"\r\n'
    b"Content-Type: text/plain\r\n"
    b"\r\n"
    b"meow\r\n"
    b"--boundary--\r\n"
)
headers = {"Content-Type": "multipart/form-data; boundary=boundary"}


def on_field(field):
    print("Field:", field.field_name, "=", field.value)


def on_file(file):
    file.file_object.seek(0)
    print("File:", file.field_name, file.file_name, file.file_object.read())


parse_form(headers, BytesIO(body), on_field, on_file)
```

Output:

```text
Field: b'username' = b'alice'
File: b'avatar' b'cat.txt' b'meow'
```

`parse_form` reads the body in chunks and calls your functions:

- `on_field` runs for every text field.
- `on_file` runs for every uploaded file.

Small files stay in memory. Once a file grows past 1 MiB, it is moved to a temporary file on disk, so a large upload does not fill up your memory.

> **Real servers do not use `BytesIO`.** In a real application, the body comes from the network. Any object with a `read()` method that returns `bytes` works.

### Handle each chunk yourself

`parse_form` still saves every file somewhere. When you want full control, for example to send an upload straight to cloud storage, use `MultipartParser`:

```python
from python_multipart import MultipartParser

chunks = [
    b"--boundary\r\n",
    b'Content-Disposition: form-data; name="avatar"; filename="cat.txt"\r\n\r\n',
    b"me",
    b"ow\r\n--boundary--\r\n",
]


def on_part_data(data, start, end):
    print("Got a piece of the file:", data[start:end])


parser = MultipartParser("boundary", callbacks={"on_part_data": on_part_data})
for chunk in chunks:
    parser.write(chunk)
parser.finalize()
```

Output:

```text
Got a piece of the file: b'me'
Got a piece of the file: b'ow'
```

The file arrived in two chunks, and you saw each one as it arrived. Nothing was stored.

Instead of copying data, the parser passes you a buffer plus `start` and `end` positions that mark the part you care about.

## Learn more

- [Documentation](https://kludex.github.io/python-multipart/)
- [API reference](https://kludex.github.io/python-multipart/api/)
- [Changelog](https://github.com/Kludex/python-multipart/blob/main/CHANGELOG.md)

## License

Apache 2.0. See [LICENSE.txt](LICENSE.txt).
