Skip to main content

Crash-safe atomic JSON / text / JSONL file writes for Linux (stdlib only).

Project description

atomic-json-io

Crash-safe, concurrency-safe atomic file writes for JSON, plain text, and JSONL on Linux. Pure standard library — zero runtime dependencies.

A reader of the target file always sees either the complete previous content or the complete new content, never a half-written (torn) file. Multiple threads or processes can write to the same target path at the same time without corrupting the file or racing on a shared temporary file.

Install

From PyPI:

pip install atomic-json-io

Or from source (GitHub):

pip install git+https://github.com/JohnLinotte/atomic-json-io.git

Requires Python 3.10+ and Linux.

Usage

Write JSON

from pathlib import Path
from atomic_json_io import write_json_atomic

write_json_atomic(Path("config.json"), {"name": "atomic", "version": 1})

Write text

from pathlib import Path
from atomic_json_io import write_text_atomic

write_text_atomic(Path("notes.md"), "# Title\n\nSome text")

Write JSONL

from pathlib import Path
from atomic_json_io import write_jsonl_atomic

write_jsonl_atomic(
    Path("events.jsonl"),
    [{"id": 1, "event": "open"}, {"id": 2, "event": "close"}],
)

All three helpers create parent directories automatically and add a trailing newline.

Why this is atomic on Linux

The write does not modify the target file in place. Instead it:

  1. Serializes the payload to a brand-new temporary file in the same directory as the target.
  2. Calls file.flush() then os.fsync(fd) so the bytes (and the file's data) are durably on disk before the swap.
  3. Calls os.replace(tmp, target) to move the temporary file onto the final path.

On Linux, os.replace is implemented with the rename(2) syscall, which the POSIX standard guarantees to be atomic: at any instant the target name resolves to either the old inode or the new inode, never to a partially written file. A concurrent reader therefore opens one complete version or the other.

The fsync step matters for crash safety specifically: without it, a power loss right after os.replace could leave the directory entry pointing at a file whose data blocks were never written. Flushing and fsyncing before the rename closes that window.

The cross-filesystem pitfall

rename(2) — and therefore os.replace — is only atomic when the source and the target are on the same filesystem. That is exactly why the temporary file is created in the same directory as the target rather than in /tmp or some other location. A naive implementation that writes to /tmp and then "moves" the file onto a target on a different mount would fall back to a copy-then-delete under the hood, which is not atomic and can expose a torn file. Keeping the temporary file as a sibling of the target guarantees a same-filesystem rename.

How concurrency is handled

Every write generates its own temporary filename containing a UUID nonce, combined with the process id and the thread id:

<target>.<pid>.<tid>.<nonce>.tmp

Because each writer owns a distinct temporary file, two simultaneous writers never share or clobber each other's in-progress data. They each fsync their own temp file and then race only on the final os.replace; the last rename to complete wins, and the file is always one valid, complete payload. There is no shared-temp-file race that could make one writer's os.replace fail with FileNotFoundError because another writer already renamed the shared temp away.

This holds across threads (thread id + nonce) and across processes (pid + nonce).

Scope

Linux-only. There is no Windows portability claim — os.replace semantics over an existing target differ across platforms, and the fsync-before-rename durability argument relies on POSIX rename behavior.

License

MIT — see LICENSE.

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

atomic_json_io-0.1.0.tar.gz (7.2 kB view details)

Uploaded Source

Built Distribution

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

atomic_json_io-0.1.0-py3-none-any.whl (6.3 kB view details)

Uploaded Python 3

File details

Details for the file atomic_json_io-0.1.0.tar.gz.

File metadata

  • Download URL: atomic_json_io-0.1.0.tar.gz
  • Upload date:
  • Size: 7.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for atomic_json_io-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c8be77f199410ec7ecb4e93850ad64e76eff4a5d35a3abb67b6472cdd063aec6
MD5 93d27854f747a95fe0538842adad0ab0
BLAKE2b-256 576bd518f763dd9a60351d3f4aae92b2797da576a60aad3777e572d42ebd0b0e

See more details on using hashes here.

File details

Details for the file atomic_json_io-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: atomic_json_io-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 6.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for atomic_json_io-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 44c0fd7473a2c1546ce7e224e715f8e38fd602be8c904c2cfe9a976c8eb4a950
MD5 6102963877dfdf4da49ac121b910c8f6
BLAKE2b-256 8bbb9d0fecbb2c711d21e2dcd77d5c6a3c7abd83bbdb4bb258187353760e958b

See more details on using hashes here.

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