Metadata-Version: 2.5
Name: pyhaul
Version: 0.7.1
Summary: Resumable, cursor-based, CDN-safe HTTP downloads for Python
Project-URL: Homepage, https://github.com/chad-loder/pyhaul
Project-URL: Documentation, https://chad-loder.github.io/pyhaul/
Project-URL: Issues, https://github.com/chad-loder/pyhaul/issues
Project-URL: Source, https://github.com/chad-loder/pyhaul
Project-URL: Changelog, https://github.com/chad-loder/pyhaul/blob/main/CHANGELOG.md
Author: pyhaul contributors
License-Expression: MIT
License-File: LICENSE
Keywords: aiohttp,download,http,partial,range,resume
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Networking
Classifier: Typing :: Typed
Requires-Python: >=3.12
Provides-Extra: aiohttp
Requires-Dist: aiohttp>=3.10; extra == 'aiohttp'
Provides-Extra: httpx
Requires-Dist: httpx>=0.27; extra == 'httpx'
Provides-Extra: niquests
Requires-Dist: niquests>=3.14; extra == 'niquests'
Provides-Extra: requests
Requires-Dist: requests>=2.32; extra == 'requests'
Provides-Extra: urllib3
Requires-Dist: urllib3>=2.0; extra == 'urllib3'
Provides-Extra: wreq
Requires-Dist: wreq>=0.11; extra == 'wreq'
Description-Content-Type: text/markdown

# pyhaul

[![CI](https://github.com/chad-loder/pyhaul/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/chad-loder/pyhaul/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/chad-loder/pyhaul/graph/badge.svg)](https://codecov.io/gh/chad-loder/pyhaul)
[![PyPI](https://img.shields.io/pypi/v/pyhaul.svg)](https://pypi.org/project/pyhaul/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/chad-loder/pyhaul/blob/main/LICENSE)
[![Docs](https://img.shields.io/badge/docs-properdocs-blue.svg)](https://chad-loder.github.io/pyhaul/)

Resumable HTTP downloads for Python. **Bring your own client:** pyhaul borrows your existing
session and handles byte-range negotiation, crash-safe checkpointing, and validation.

[![httpx](https://img.shields.io/badge/httpx-async%2Bsync-6B46C1.svg)](https://www.python-httpx.org/)
[![niquests](https://img.shields.io/badge/niquests-async%2Bsync-6B46C1.svg)](https://niquests.readthedocs.io/)
[![aiohttp](https://img.shields.io/badge/aiohttp-async-2563EB.svg)](https://docs.aiohttp.org/)
[![requests](https://img.shields.io/badge/requests-sync-059669.svg)](https://requests.readthedocs.io/)
[![urllib3](https://img.shields.io/badge/urllib3-sync-059669.svg)](https://urllib3.readthedocs.io/)

Install [`pyhaul`](https://pypi.org/project/pyhaul/) from PyPI — pick the extra matching your
HTTP client:

```bash
pip install pyhaul[httpx]   # or: niquests, requests, urllib3, aiohttp, wreq
```

```python
import httpx
from pathlib import Path
from pyhaul import haul, PartialHaulError

dest = Path("big.zip")
with httpx.Client() as client:
    for _ in range(10):
        try:
            result = haul("https://example.com/big.zip", client, dest=dest)
            break
        except PartialHaulError:
            pass  # only retryable error; others propagate

print(f"done: {dest.stat().st_size:,} bytes")
```

---

## What is it?

A small, pure-Python library that makes HTTP downloads **resumable**.
Call `haul()` with a URL, your existing HTTP client, and a destination
path. **pyhaul** handles byte-range negotiation, ETag validation,
crash-safe checkpointing, and atomic file completion. Supports both
sync and async across multiple HTTP client libraries.

Each call to `haul()` upholds these guarantees:

- **One `haul()` makes one request**. You are responsible for
  retry loops, but retry just means call `haul()` again.
- **The destination file will not exist until download is complete.**
  Incomplete data lives in a temporary `.part` file; on completion
  it is atomically moved into place.
- **Interrupted downloads resume when possible.** Checkpoint state
  lives on disk, not in memory. Kill the process, lose the network,
  get a 503 — the next `haul()` picks up from the last durable byte.
- **If the remote resource changes, retry will not corrupt.** ETag
  mismatch detection prevents gluing mismatched halves together.
- **Your HTTP client is borrowed, not owned.** `pyhaul` sets
  per-request headers and returns your session untouched.
- **Transport errors pass through unwrapped.** `httpx.ReadTimeout`
  stays `httpx.ReadTimeout`. You catch the types you already know.

No hard dependency on any HTTP library. Pick one (or several) as extras.

## Documentation

**[Full documentation](https://chad-loder.github.io/pyhaul/)** — Quick start, guides, and API reference.

- [Quick Start](https://chad-loder.github.io/pyhaul/latest/quickstart/) — install, first download, async usage
- [Why pyhaul Exists](https://chad-loder.github.io/pyhaul/latest/explanation/why/) — silent failure modes in HTTP resume, comparison with curl/wget/aria2c
- [Design & Architecture](https://chad-loder.github.io/pyhaul/latest/explanation/design/) — transport adapters, checkpoint state, download lifecycle
- [API Reference](https://chad-loder.github.io/pyhaul/latest/reference/api/) — `haul()`, `haul_async()`, `HaulState`, exceptions
- [Control File Spec](https://chad-loder.github.io/pyhaul/latest/reference/spec/) — checkpoint format for implementers

