Skip to main content

Transactional file modifications – atomic, rollback on failure

Project description

safefile

Atomic, transactional file modifications – automatic rollback on failure.

Protect files and directories from being left corrupted if your script crashes. Zero dependencies, pure Python 3.7+.

Installation

pip install safefile

Quick start

from safefile import transaction

with transaction("config.yaml", "data.csv"):
    update_config("config.yaml")
    update_data("data.csv")
    # crash here → both files restored automatically

How it works

  1. On enter: each existing file/directory is backed up using the chosen strategy.
  2. On success: backups are silently discarded.
  3. On exception: all originals are restored; any new files/dirs created inside the block are deleted.

Backup strategies

copy (default)

Safe copy via shutil.copy2. Works on every OS and filesystem.

with transaction("config.yaml", strategy="copy"):
    ...

hardlink

Near-instant snapshot via os.link() — no data duplication on disk. Automatically falls back to copy if the temp directory is on a different filesystem.

with transaction("big_file.bin", strategy="hardlink"):
    ...

Directory support

Pass a directory path — the entire tree is snapshotted and restored on failure.

with transaction("configs/", "data.csv"):
    shutil.rmtree("configs/")
    rebuild_configs("configs/")
    update_data("data.csv")
    # any failure → configs/ and data.csv both restored

Hooks

Run callbacks on commit or rollback — useful for logging, alerting, or audit trails.

with transaction(
    "db.sqlite",
    on_commit=lambda: logger.info("committed"),
    on_rollback=lambda: alerts.send("rolled back"),
):
    modify_db("db.sqlite")

Savepoints

Snapshot state mid-transaction and roll back to that point without aborting the whole block.

with transaction("a.txt", "b.txt") as tx:
    modify_a("a.txt")
    sp = tx.savepoint()       # snapshot here
    try:
        risky_modify_b("b.txt")
    except Exception:
        tx.rollback_to(sp)    # b.txt reverts, a.txt stays modified
        safe_fallback_b("b.txt")
# commits with a.txt modified + safe b.txt

Multiple savepoints stack. Rolling back to an earlier one automatically discards all later ones.


Lazy backup

Only back up files that are actually going to be written. Saves I/O when protecting many files but only touching a few.

with transaction("a.txt", "b.txt", "c.txt", lazy=True) as tx:
    tx.touch("a.txt")        # backup triggered here, not at block entry
    modify_a("a.txt")
    # b.txt and c.txt are never backed up — no I/O cost

Async support

Drop-in async context manager. Backup and restore run in a thread pool so the event loop is never blocked.

from safefile import async_transaction

async def deploy():
    async with async_transaction("config.yaml", "state.json"):
        await write_config("config.yaml")
        await update_state("state.json")

All the same options (strategy, lazy, verify, on_commit, etc.) work on async_transaction.


Dry run

Test a destructive operation without touching the real files. tx.path() redirects writes to shadow copies.

with transaction("prod.cfg", dry_run=True) as tx:
    with open(tx.path("prod.cfg"), "w") as f:
        f.write(generate_config())
# prod.cfg is completely untouched; shadow copies are cleaned up

Checksum verification

After rollback, verify the restored file matches the original backup's SHA-256. Catches corrupted backups before silently leaving a broken file.

with transaction("critical.db", verify=True):
    rewrite_database("critical.db")
# on rollback: raises RuntimeError if restored file doesn't match original checksum

Streaming backup with progress

For large files, backup runs in configurable chunks and reports progress.

with transaction(
    "10gb.iso",
    chunk_size=10 * 1024 * 1024,           # 10 MB chunks
    on_progress=lambda pct: print(f"{pct}%"),
):
    replace_image("10gb.iso")

on_progress is only called when the file exceeds chunk_size. Small files use a direct copy with no overhead.


All options

transaction(
    *filepaths,
    strategy="copy",        # "copy" (default) or "hardlink"
    on_commit=None,         # callable, fired on success
    on_rollback=None,       # callable, fired on failure
    lazy=False,             # defer backup until tx.touch(path) is called
    dry_run=False,          # redirect writes to shadow copies, never touch originals
    verify=False,           # SHA-256 checksum check after rollback
    chunk_size=52428800,    # bytes threshold for chunked streaming (default 50 MB)
    on_progress=None,       # callable(int) receiving 0–100 during large-file backup
)

async_transaction accepts the same options.


License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

safefile-0.3.0.tar.gz (12.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

safefile-0.3.0-py3-none-any.whl (10.0 kB view details)

Uploaded Python 3

File details

Details for the file safefile-0.3.0.tar.gz.

File metadata

  • Download URL: safefile-0.3.0.tar.gz
  • Upload date:
  • Size: 12.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for safefile-0.3.0.tar.gz
Algorithm Hash digest
SHA256 bcd90e0adb61ff9b541f0e093bf41e9a593dd697a1e4ef8babc66ecae0f886f1
MD5 677ffd64da305ebdc8539ed7eec83b38
BLAKE2b-256 91ffcf26188e451110ba3d1ae40a14e69ac762f8ed1949939974998cc69e8940

See more details on using hashes here.

Provenance

The following attestation bundles were made for safefile-0.3.0.tar.gz:

Publisher: publish.yml on AbirHasanSupta/safefile

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file safefile-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: safefile-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 10.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for safefile-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 319c5dff45a489870dacf7b180ec85cd6704036b0d230ec1426dd27d5e4bf0ca
MD5 5b9939e777606f93a421ee9196ca18c0
BLAKE2b-256 df946bddccc8a4c8abc13519fe37c42d3ca2f57c3da02d4d54a10c4d31eabdf8

See more details on using hashes here.

Provenance

The following attestation bundles were made for safefile-0.3.0-py3-none-any.whl:

Publisher: publish.yml on AbirHasanSupta/safefile

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page