Waypoint
GitHub
Python · one decorator

Resume any Python loop where it stopped.

For data engineers running hours-long backfills and API scrapes that keep dying at item 8,000 of 250,000. Rerun the same script and it continues at 8,001.

Why it exists

Long jobs die. Restarting from zero is the tax.

A backfill hits an OOM. A spot instance gets reclaimed. An API times out at record 8,000. The usual fix is boilerplate: track an index by hand, write it to a file after each item, load it back at the top, wrap the body in try/except so the write actually happens. It is easy to get subtly wrong and it buries the one thing the script was for. Waypoint does that part for you.

01

One decorator, body untouched

Add @checkpoint above a function with a for loop. Waypoint rewrites only the loop's iterator to resume from a saved index. Your loop body stays exactly as written.

02

Never redoes finished work

The index advances only after an iteration fully completes. Killed mid-item, that one item is retried on the next run, not skipped. Completed items are never re-run.

03

Survives a hard kill

Progress is a tiny JSON file written temp-file-then-rename. A SIGKILL or power loss mid-write leaves the old file or the new one, never a corrupt half-written one.

Install & use

Two lines to make a loop resumable.

Install

# from the repo (v1.0.0) git clone https://github.com/ctkrug/waypoint.git cd waypoint pip install -e .

Decorate

from waypoint import checkpoint @checkpoint def backfill(records): for record in records: upload(record) # runs once per record, # even across restarts

Iterating something lazy like a generator or a database cursor? Wrap it with waypoint.seq(...) to materialize it once so it becomes resumable. Inspect or clear saved progress from the terminal with python -m waypoint status and python -m waypoint clear <key>.

Questions

How it behaves, precisely.

How do I resume a Python loop after it crashes?

Put @checkpoint on the function whose for loop does the work, then rerun the exact same script with the same arguments. Waypoint saves the loop's position to .waypoint/<key>.json after each completed iteration, so the second run reads that file and starts at the next unfinished item. There is no flag to pass and no restore code to write. When the loop finishes normally the checkpoint file is deleted, so a later run starts clean.

How does Waypoint know which run to resume?

The checkpoint key is the function's qualified name plus a hash of its call arguments. Calling the same function with the same input resumes the same checkpoint; calling it with different input gets its own file, so two jobs never cross-contaminate. If your arguments do not hash stably (an open file handle, or a set of strings under hash randomization), pass an explicit key="job-42" to pin it.

Does it work with generators, or only lists?

A loop can only be resumed if its source can be indexed and replayed, so Waypoint resumes loops over a list, tuple, or range directly. A plain generator cannot be sliced from an arbitrary point, so decorating a loop over one raises a clear NotResumableError instead of silently failing. Wrap the generator with waypoint.seq(...) to materialize it once and make it resumable.

Is my progress file safe if the process is killed mid-write?

Yes. Every write goes to a temp file in the same directory, is flushed and fsynced, then atomically renamed into place. A hard kill or power loss during a write leaves either the complete old file or the complete new one, never a truncated one. A checkpoint that somehow does become unreadable fails loudly with an actionable message rather than resuming from a guessed position.

What does it cost per iteration?

One small JSON write with an fsync per completed item. That is negligible next to loop bodies doing real work like an API call or a file upload, which is the case Waypoint is built for. If your loop body is sub-millisecond pure Python, the checkpoint write will dominate, so batch such loops into chunks before decorating.