Metadata-Version: 2.4
Name: neverlose
Version: 1.0.1
Summary: 🧙‍♂️ Crash-proof any Python function with one decorator. Resume from where you stopped — automatically.
Author-email: NeverLose IO <hello@neverlose.io>
License-Expression: MIT
Project-URL: Homepage, https://github.com/neverlose-io/neverlose
Project-URL: Repository, https://github.com/neverlose-io/neverlose
Project-URL: Issues, https://github.com/neverlose-io/neverlose/issues
Project-URL: Changelog, https://github.com/neverlose-io/neverlose/blob/main/CHANGELOG.md
Keywords: resume,checkpoint,crash-recovery,fault-tolerance,decorator,pickle,checkpointing,recovery
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
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 :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Recovery Tools
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

<div align="center">

# 🧙‍♂️ NeverLose

### Crash-proof any Python function with one decorator.

**Resume from where you stopped — automatically.**

[![CI](https://github.com/neverlose-io/neverlose/actions/workflows/ci.yml/badge.svg)](https://github.com/neverlose-io/neverlose/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/neverlose.svg)](https://pypi.org/project/neverlose/)
[![Python](https://img.shields.io/pypi/pyversions/neverlose.svg)](https://pypi.org/project/neverlose/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Downloads](https://img.shields.io/pypi/dm/neverlose.svg)](https://pypi.org/project/neverlose/)
[![Stars](https://img.shields.io/github/stars/neverlose-io/neverlose?style=social)](https://github.com/neverlose-io/neverlose)

**Zero dependencies · Python 3.9 – 3.13 · One line to use**

</div>

---

## 📑 Table of Contents

- [The Problem](#-the-problem)
- [The Solution](#-the-solution)
- [Install](#-install)
- [Features](#-features)
- [Usage](#-usage)
- [API](#%EF%B8%8F-api)
- [FAQ](#-faq)
- [Roadmap](#%EF%B8%8F-roadmap)
- [Contributing](#-contributing)
- [License & Support](#-license--support)

<details>
<summary><b>🧠 How does it work internally? (click to expand)</b></summary>

1. **Fingerprint** — BLAKE2b over (source + signature + `key`). Change any → new checkpoint file.
2. **Decorator** — wraps your function, loads existing state into a thread-local store.
3. **`checkpoint(value)`** — atomic write via `tempfile` + `fsync` + `os.replace`.
4. **Signals** — intercepts `SIGINT`/`SIGTERM`, flushes the last checkpoint, exits with `os._exit(130)`.
5. **Resume** — same fingerprint finds the checkpoint and restores your value on next run.

</details>

---

## 😩 The Problem

Every Python developer has felt this:

- 🧠 **Train a model for 12 hours** → power outage at hour 11 → *everything gone*.
- 🕷️ **Scrape a million pages** → accidental `Ctrl+C` → *start from zero*.
- 📦 **Process 2TB of data** → `MemoryError` at hour 6 → *begin again*.
- 🔬 **Run a 48-hour simulation** → cluster kills the job → *rerun from scratch*.

The usual answer: save checkpoints manually. You write `try/except` + `pickle.dump`, manage state files, handle corrupt saves… then forget one edge case, and it all falls apart anyway.

**There has to be a better way.**

---

## ✨ The Solution

```python
from neverlose import resurrect, checkpoint

@resurrect
def train():
    for epoch in range(1000):
        loss = do_epoch(epoch)
        checkpoint({"epoch": epoch, "loss": loss})

train()
```

That's it. Run it. Stop it. Crash it. Re-run the same file → it resumes automatically from the last epoch.

No try/except. No pickle.dump. No dependencies.

---

## 🚀 Install

```bash
pip install neverlose
```

Python 3.9 → 3.13. Zero dependencies (standard library only).

---

🎯 Features

Feature What it means for you
🔁 Automatic resume Re-run the same file → picks up where it left off
💾 Atomic writes Power loss mid-save = no corrupt checkpoints, ever
🛑 Signal handling Catches Ctrl+C and SIGTERM, saves before exiting
🧬 Fingerprinting Detects code changes → avoids stale resumes
📦 Zero dependencies Only Python stdlib — works in air-gapped environments
🐍 Pure Python No C extensions, no compiled wheels

---

📖 Usage

🧠 Machine Learning Training

```python
from neverlose import resurrect, checkpoint

@resurrect(key="gpt-small-v3")
def train_model():
    model = build_model()
    for epoch in range(100):
        for batch in dataloader:
            loss = train_step(model, batch)
        checkpoint({"epoch": epoch, "loss": loss})

train_model()
```

Change key= when hyperparameters change → forces a fresh start.

<details>
<summary><b>🕷️ Web Scraping — click for code</b></summary>

```python
from neverlose import resurrect, checkpoint

@resurrect(key="news-2026")
def scrape_all():
    state = scrape_all.resume_state() or {"done": []}
    done = set(state["done"])
    for url in url_list:
        if url in done:
            continue
        save(fetch(url), url)
        done.add(url)
        checkpoint({"done": list(done)})

scrape_all()
```

Ctrl+C mid-scrape → re-run → continues from the last URL.

</details>

<details>
<summary><b>📦 Data Processing Pipeline — click for code</b></summary>

```python
from pathlib import Path
from neverlose import resurrect, checkpoint

@resurrect
def process(path):
    files = list(Path(path).glob("*.parquet"))
    state = process.resume_state() or {"i": 0}
    for i, f in enumerate(files[state["i"] + 1:], start=state["i"] + 1):
        transform(f)
        checkpoint({"i": i, "total": len(files)})

process("/data/raw")
```

</details>

---

## ⚙️ API

```python
@resurrect(key="", keep=True, verbose=True)
def my_function():
    ...
```

Param Type Default Description
key str "" Extra fingerprint ingredient — change to force a fresh start
keep bool True Keep checkpoint file after success, or delete it
verbose bool True Print resume/save messages to stderr

```python
from neverlose import checkpoint, resume_state, reset

checkpoint({"step": 42})       # manual save inside a decorated function
resume_state(my_function)      # → last saved value, or None
my_function.reset()            # → delete checkpoint file
```

Environment variable: NEVERLOSE_DIR (default: ~/.neverlose)

---

❓ FAQ

<details>
<summary><b>Why not just use <code>pickle</code> directly?</b></summary>

pickle alone doesn't give you: (1) atomic writes — a crash mid-save can corrupt the file; (2) signal handling — Ctrl+C loses the last value; (3) fingerprinting — it doesn't know when your code changed. NeverLose wraps all three, plus a one-line decorator.

</details>

<details>
<summary><b>Does it work with <code>async def</code>?</b></summary>

Not in v1.0. v1.1 will support it. ⭐ if you want it sooner.

</details>

<details>
<summary><b>Is it thread-safe?</b></summary>

Yes — saves are guarded by threading.RLock. Each thread has its own store via threading.local.

</details>

<details>
<summary><b>Does it support <code>multiprocessing</code>?</b></summary>

Partially — writes are atomic, but each process has its own fingerprint. First-class support lands in v1.2.

</details>

<details>
<summary><b>How fast is it?</b></summary>

Small checkpoints: < 1ms on a modern SSD. Negligible compared to any real workload.

</details>

<details>
<summary><b>What if the checkpoint file is lost?</b></summary>

The function starts fresh — safely. No crash, no error.

</details>

<details>
<summary><b>Where are checkpoints stored?</b></summary>

By default, ~/.neverlose/ — keyed by BLAKE2b fingerprints. Override with NEVERLOSE_DIR=/path.

</details>

---

## ⚖️ Comparison

<details>
<summary><b>Click to see the full comparison table</b></summary>

| Tool | Auto-resume | Zero deps | Any function | Atomic saves | Signal-safe |
|---|---|---|---|---|---|
| `pickle` (manual) | ❌ | ✅ | ❌ | ❌ | ❌ |
| `joblib.Memory` | ❌ | ❌ | ⚠️ | ❌ | ❌ |
| `dill` | ❌ | ❌ | ⚠️ | ❌ | ❌ |
| `PyTorch Lightning` | ✅ (PyTorch only) | ❌ | ❌ | ✅ | ✅ |
| `TensorFlow Checkpoint` | ✅ (TF only) | ❌ | ❌ | ⚠️ | ⚠️ |
| **NeverLose** | ✅ | ✅ | ✅ | ✅ | ✅ |

</details>

---

## 🗺️ Roadmap

- [x] **v1.0** — Sync functions, atomic writes, signal handling
- [ ] **v1.1** — `async def` support ← *⭐ to ship it faster*
- [ ] **v1.2** — Multiprocessing-safe checkpoints
- [ ] **v1.3** — Cloud backends: S3 · GCS · Azure Blob
- [ ] **v2.0** — Distributed checkpoint registry

---

## 🤝 Contributing

Every PR is welcome — from typo fixes to full features.

```bash
git clone https://github.com/neverlose-io/neverlose.git
cd neverlose
pip install -e ".[dev]"
pytest -v
```

See CONTRIBUTING.md and CODE_OF_CONDUCT.md.

---

## 📜 License

MIT © 2026 NeverLose IO — see [LICENSE](LICENSE).

---

## 💖 License & Support

NeverLose is **free and open source**, built with zero funding and no corporate backing.

<div align="center">

### Bitcoin (BTC)

<code>bc1qyw4mj8zjcutzpsd6skq87qv03t52804t6jkwte</code>

### Ethereum (ETH) / USDT (ERC-20)

<code>0x009E3Cf6F51141DBF2EfEc33BC07c966651D0Da8</code>

> ⚠️ **Always verify the address before sending.** Crypto transactions are irreversible.

</div>

---

## ⭐ Star History

[![Star History](https://api.star-history.com/svg?repos=neverlose-io/neverlose&type=Date)](https://star-history.com/#neverlose-io/neverlose&Date)

---

<div align="center">

## If NeverLose saves you even one hour — give it a ⭐

It takes 2 seconds and helps other developers find it.

[![Star](https://img.shields.io/github/stars/neverlose-io/neverlose?style=for-the-badge&logo=github&label=Star%20NeverLose)](https://github.com/neverlose-io/neverlose)
[![Fork](https://img.shields.io/github/forks/neverlose-io/neverlose?style=for-the-badge&logo=github&label=Fork)](https://github.com/neverlose-io/neverlose/fork)
[![Sponsor](https://img.shields.io/badge/Sponsor-%E2%9D%A4-red?style=for-the-badge&logo=github)](https://github.com/sponsors/neverlose-io)

**[⬆ back to top](#-neverlose)**

</div>
