Skip to main content

🧿 A safer writer for files and streams 🧿

Project description

🧿 safer: A safer writer 🧿

Avoid partial writes or corruption!

safer wraps file streams, sockets, or a callable, and offers a drop-in replacement for regular old open().

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.

How to use

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

Tested on Python 3.4 - 3.11. An old Python 2.7 version is here.

See the Medium article here

The details

safer helps prevent programmer error from corrupting files, socket connections, or generalized streams by writing a whole file or nothing.

It does not prevent concurrent modification of files from other threads or processes: if you need atomic file writing, see https://pypi.org/project/atomicwrites/

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 writes a whole response or nothing

  • safer.open() is a drop-in replacement for built-in open that writes a whole file or nothing

  • safer.closer() returns a stream like from safer.write() 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.

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.rename if the operation completes successfully. This functionality does not work on Windows. (In fact, it's unclear if any of this works on Windows, but that certainly won't. Windows developer solicted!)

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 you write all or nothing:

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)  # Nothing has been written

Example: safer.open() and json

safer.open() is a a drop-in replacement for built-in open() except that 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)
    # Either the whole file is written, or nothing

API Documentation

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

safer-5.3.0.tar.gz (8.2 kB view details)

Uploaded Source

Built Distribution

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

safer-5.3.0-py3-none-any.whl (10.4 kB view details)

Uploaded Python 3

File details

Details for the file safer-5.3.0.tar.gz.

File metadata

  • Download URL: safer-5.3.0.tar.gz
  • Upload date:
  • Size: 8.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","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}

File hashes

Hashes for safer-5.3.0.tar.gz
Algorithm Hash digest
SHA256 2c7b5ab57d183bbeb03966561293fb4e88d28cafecbdbd3ec8ef629d41888613
MD5 98b213d225f6ce7ff2cfcbe9a0dfc154
BLAKE2b-256 eddccaa6d8c0a1f8b89f508212fec510c763ce8022d0b3c1dd71d9508fb39c79

See more details on using hashes here.

File details

Details for the file safer-5.3.0-py3-none-any.whl.

File metadata

  • Download URL: safer-5.3.0-py3-none-any.whl
  • Upload date:
  • Size: 10.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","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}

File hashes

Hashes for safer-5.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 83b26a0b285cde0a1b46e8bd95a52f0e3761a0a0d21704f658a017b3a1014fa3
MD5 68ff09c49df478a0adec47b0e0101bec
BLAKE2b-256 a6fe0441c8c1e6e849b7003174e502f5a88ae4660d1c37afdc7e788668a7840d

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