fs-transaction 🛡️
Stop writing corrupted files.
fs-transaction provides Python context managers for atomic, crash-safe file operations. It ensures your file writes are "all-or-nothing," preventing data corruption during crashes, power failures, or concurrent access.
💥 The Problem
In standard Python, writing to a file is risky:
# ⚠️ DANGEROUS CODE
with open("database.json", "w") as f:
f.write(start_processing())
# <--- CRASH HERE (Power loss, Exception, OOM)
f.write(finish_processing())
# RESULT: "database.json" is now half-written and corrupted forever.
✅ The Solution
fs-transaction uses the Write-Replace Pattern:
- It writes data to a temporary file in the same directory.
- It calls
os.fsync()for crash safety. - It performs an atomic
os.replace()to swap the new file with the old one.
If the script crashes at any point before step 3, your original file remains 100% untouched and valid.
🆚 Why use fs-transaction?
| Feature | Standard open() |
fs-transaction |
|---|---|---|
| Atomic Writes | ❌ No (Partial writes possible) | ✅ Yes (All or nothing) |
| Crash Safety | ❌ Low (Data corruption risk) | ✅ High (fsync + atomic replace) |
| Concurrency | ❌ Manual locking required | ✅ Thread-safe implementation |
| Cleanup | ❌ Manual try/finally blocks |
✅ Automatic temp file cleanup |
| Rollback | ❌ Not possible | ✅ Automatic on failure |
| Overwrite Safety | ❌ Destroys original | ✅ Backup + restore on failure |
| Ease of Use | 🟡 Native | 🟢 Drop-in replacement |
📦 Installation
pip install fs-transaction
🚀 Usage
Quick Start: AtomicWrite
Use AtomicWrite as a drop-in replacement for open() when you need crash-safe single-file writes:
from fs_transaction import AtomicWrite
import json
data = {"status": "processing", "items": [1, 2, 3]}
# Even if this block raises an Exception, 'config.json' will NOT be corrupted.
with AtomicWrite("config.json", mode="w") as f:
f.write(json.dumps(data, indent=2))
print("File written safely and atomically!")
Batch Operations: Transaction
Use Transaction to group multiple file operations into a single atomic unit:
from fs_transaction import Transaction
with Transaction() as t:
t.write("config.json", json.dumps(new_config), overwrite=True)
t.move("data/old.csv", "archive/old.csv")
t.copy("templates/default.yaml", "config/app.yaml")
t.delete("cache/stale.tmp")
# ✅ All 4 operations succeed together, or none of them happen.
Overwriting Existing Files
By default, operations raise FileExistsError if the destination exists. Use overwrite=True to safely replace:
# AtomicWrite overwrites by default (like open())
with AtomicWrite("existing_file.txt") as f:
f.write("Updated content")
# Transaction methods require explicit overwrite=True
with Transaction() as t:
t.write("existing_file.txt", "Updated", overwrite=True)
t.copy("src.txt", "existing_dst.txt", overwrite=True)
t.move("new.txt", "existing.txt", overwrite=True)
Dry-Run Mode
Validate all operations without executing them:
with Transaction(dry_run=True) as t:
t.move("important.txt", "archive/important.txt")
t.write("report.txt", generate_report())
# ✅ Validation passed, but no files were changed.
Deleting Files Safely
with Transaction() as t:
t.delete("obsolete_config.json")
t.delete("old_data.csv")
# Files are backed up before deletion.
# If something fails, they are restored automatically.
🔧 API Reference
AtomicWrite(path, mode='w', overwrite=True, fsync=True, **kwargs)
Context manager for atomic single-file writes.
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
str | Path |
— | Target file path |
mode |
str |
'w' |
File open mode ('w', 'wb', etc.) |
overwrite |
bool |
True |
Replace existing files |
fsync |
bool |
True |
Call os.fsync() for crash safety |
**kwargs |
— | — | Passed to open() |
Transaction(dry_run=False, fsync=True)
Context manager for batching multiple file operations atomically.
| Parameter | Type | Default | Description |
|---|---|---|---|
dry_run |
bool |
False |
Validate without executing |
fsync |
bool |
True |
Call os.fsync() for crash safety |
Methods:
| Method | Description |
|---|---|
write(dst, content, mode='w', overwrite=False) |
Atomic file write |
move(src, dst, overwrite=False) |
Move/rename a file |
copy(src, dst, overwrite=False) |
Copy a file |
delete(path) |
Delete a file (with backup for rollback) |
commit() |
Manually commit (auto-called on __exit__) |
🏗️ Architecture
┌─────────────────────────────────────────────────┐
│ Transaction │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ │
│ │FileMove │ │FileCopy │ │FileWrite│ │FileDel │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └───┬────┘ │
│ │ │ │ │ │
│ ┌────▼───────────▼───────────▼───────────▼────┐ │
│ │ 1. Validate All │ │
│ │ 2. Execute All (with backups) │ │
│ │ 3. Cleanup / Rollback │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
🛡️ Safety Guarantees
- Atomicity: Operations are committed as a single unit. Partial state is impossible.
- Crash Safety:
os.fsync()ensures data hits disk before the atomic rename. - Backup-Restore: Original files are backed up before overwrite. On failure, they are restored.
- Thread Safety: Internal locking prevents concurrent commits.
- Automatic Cleanup: Temp files are always cleaned up, even on exceptions.
🤝 Contributing
Contributions, issues, and feature requests are welcome!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
See CONTRIBUTING.md for detailed guidelines.
📄 License
This project is licensed under the GNU General Public License v3.0 — see the LICENSE file for details.
📋 Changelog
See CHANGELOG.md for a list of changes.
Metadata
Release files for fs-transaction 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fs_transaction-0.2.0.tar.gz | 26.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fs_transaction-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 49.4 kB
Release files / fs_transaction-0.2.0.tar.gz
| Download URL | fs_transaction-0.2.0.tar.gz |
|---|---|
| Size | 26.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dbc5a745b37190ef8b792a32993195b80f65038606659c18f4c16c3544d9de78
|
|
BLAKE2b-256 checksum How to use checksums |
e5c1d7b29e7cee746be650825ddd08c4ac3df8751b6c3a8cea972475a99b8d84
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 10, 2026.
Transparency logRelease files / fs_transaction-0.2.0-py3-none-any.whl
| Download URL | fs_transaction-0.2.0-py3-none-any.whl |
|---|---|
| Size | 22.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c1c33050cefc9bc5811068aa0407e398f319b162a38c902a42c4f5313df87485
|
|
BLAKE2b-256 checksum How to use checksums |
495f94a5b2a9de6635957d9681377164ba8f395d2882d7971efb1703b35c1473
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 10, 2026.
Transparency log