Skip to main content

🧿 safer: A safer writer 🧿

Avoid partial writes or corruption!

safer wraps file streams, sockets, or a callable, and offers an open-like API for named files.

Quick summary

A tiny example

import safer

with safer.open(filename, 'w') as fp:
    fp.write('one')
    print('two', file=fp)
    raise ValueError
    # filename was not written.

A note on AI use

Version 6.0 has exactly the same API as before, and the great majority of the code is still the handwritten code from before, but I recently used a coding assistant to find a lot of possible errors and edge cases and fix them.

I believe it should do exactly what it did before, but not fail in some unusual but certainly not impossible cases. Please let me know with an issue report if any problems crop up.

How to use

Use pip to install safer from the command line: pip install safer.

Requires Python 3.10 or later. An old Python 2.7 version is here.

See the Medium article here

The details

safer helps prevent programmer error from partially overwriting named files. For sockets, callbacks, and other streams, it defers the first write until the context succeeds. A final stream write can still block, fail, or be partially accepted, so use an application-level framing and acknowledgement protocol when delivery must be all-or-nothing.

safer does not lock files or coordinate concurrent writers. A disk-buffered writer replaces the target when it closes successfully, so concurrent writers have last-successful-close-wins semantics. Append and update modes copy a snapshot of the target and can overwrite another writer's changes. x mode cannot be used with temp_file=True, because exclusive creation cannot be preserved across delayed replacement.

os.replace() provides atomic visibility where the platform supports it, but safer does not fsync the replacement file or its directory. A successful close is therefore not a power-loss durability guarantee.

When replacing an existing file, safer preserves its mode bits. It does not preserve ownership, ACLs, extended attributes, or other platform-specific metadata.

It also has a useful dry_run setting to let you test your code without actually overwriting the target file.

  • safer.writer() wraps an existing writer, socket or stream and defers its write until successful context exit

  • safer.open() is an open-like API for named files that delays replacement until successful context exit

  • safer.closer() returns a stream like from safer.writer() that also closes the underlying stream or callable when it closes.

  • safer.dump() is like a safer json.dump() which can be used for any serialization protocol, including Yaml and Toml, and also allows you to write to file streams or any other callable.

  • safer.printer() is safer.open() except that it yields a a function that prints to the stream.

By default, safer buffers the written data in memory in a io.StringIO or io.BytesIO.

Text files use the platform default encoding unless you pass encoding, just like built-in open(). Specify an encoding such as utf-8 when output must be portable across hosts with different locales.

For very large files, safer.open() has a temp_file argument which writes the data to a temporary file on disk, which is moved over using os.replace if the operation completes successfully. Windows is supported, although replacement can fail while another process holds the target open.

Example: safer.writer()

safer.writer() wraps an existing stream - a writer, socket, or callback - in a temporary stream which is only copied to the target stream at close(), and only if no exception was raised.

Suppose sock = socket.socket(*args).

The old, dangerous way goes like this.

try:
    write_header(sock)
    write_body(sock)   # Exception is thrown here
    write_footer(sock)
 except Exception:
    write_error(sock)  # Oops, the header was already written

With safer, no write is attempted when the body raises:

try:
    with safer.writer(sock) as s:
        write_header(s)
        write_body(s)  # Exception is thrown here
        write_footer(s)
 except Exception:
    write_error(sock)  # No write was attempted by safer

Example: safer.open() and json

safer.open() accepts named paths, not file descriptors. When used as a context, it leaves the original file unchanged on failure.

It's easy to write broken JSON if something within it doesn't serialize.

with open(filename, 'w') as fp:
    json.dump(data, fp)
    # If an exception is raised, the file is empty or partly written

safer prevents this:

with safer.open(filename, 'w') as fp:
    json.dump(data, fp)
    # If an exception is raised, the file is unchanged.

safer.open(filename) returns a file stream fp like open(filename) would, except that fp writes to memory stream or a temporary file in the same directory.

If fp is used as a context manager and an exception is raised, then the property fp.safer_failed on the stream is automatically set to True.

And when fp.close() is called, the cached data is stored in filename - unless fp.safer_failed is true.

Example: safer.printer()

safer.printer() is similar to safer.open() except it yields a function that prints to the open file - it's very convenient for printing text.

Like safer.open(), if an exception is raised within its context manager, the original file is left unchanged.

Before.

with open(file, 'w') as fp:
    for item in items:
        print(item, file=fp)
    # Prints lines until the first exception

With safer

with safer.printer(file) as print:
    for item in items:
        print(item)
    # The file is replaced only after every line is printed

API Documentation

Metadata

Release files for safer 6.0.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for safer 6.0.4
File Size Uploaded
safer-6.0.4.tar.gz 55.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for safer 6.0.4
File Interpreter ABI Platform
safer-6.0.4-py3-none-any.whl Python 3 none any Details

Total release size: 67.8 kB

Release files / safer-6.0.4.tar.gz

Download URL safer-6.0.4.tar.gz
Size 55.4 kB
Tags Source
SHA-256 checksum
How to use checksums
bc1e540efd30c352288d1acf40648e0be826775dddbb4b6ef2b198ff2e1b482f
BLAKE2b-256 checksum
How to use checksums
97650d10d0aeef29d78190b560d4f3620d3e53266363d04cef566d3306b5b721
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / safer-6.0.4-py3-none-any.whl

Download URL safer-6.0.4-py3-none-any.whl
Size 12.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6fe2a66f7d450424d60218baf49385503dd698cecd5cede099434d621f235ad5
BLAKE2b-256 checksum
How to use checksums
8035f7356a7287b33de075e6d0e1aafd7e6aa5341198798a04530a99da1b7b5c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

6.1.0

2 release files

This release

6.0.4 This release

2 release files

6.0.0

1 release file

5.4.0

2 release files

5.3.0

2 release files

5.2.0

2 release files

5.1.0

2 release files

5.0.0

2 release files

4.12.3

2 release files

4.12.1

2 release files

4.12.0

2 release files

4.11.0

2 release files

4.9.0

2 release files

4.8.0

2 release files

4.7.0

2 release files

4.6.1

2 release files

4.6.0

2 release files

4.5.0

2 release files

4.4.1

2 release files

4.4.0

2 release files

4.3.0

2 release files

4.2.1

2 release files

4.2.0

2 release files

4.1.2

2 release files

4.1.1

2 release files

4.1.0

2 release files

4.0.0

2 release files

3.1.2

2 release files

3.1.1

2 release files

3.0.0

2 release files

2.0.5

2 release files

2.0.4

2 release files

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

0.9.11

2 release files

0.9.10

1 release file

0.9.8

2 release files

0.9.7

2 release files

0.9.6

2 release files

0.9.5

2 release files

0.9.4

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page