Skip to main content

A safer file opener

Project description

Travis (.org) Codecov GitHub release (latest SemVer including pre-releases) PyPI - Python Version GitHub top language CodeFactor Grade PyPI - License GitHub code size in bytes

No more partial writes or corruption! Wraps file streams, sockets or any callable.

Install safer from the command line with pip: pip install safer.

Tested on Python 3.4 and 3.8 - Python 2.7 version is here <https://github.com/rec/safer/tree/v2.0.5>`_.

See the Medium article here.


safer helps prevent programmer error from corrupting files, socket connections, or generalized streams

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/

  • 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.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!)


EXAMPLES

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.

EXAMPLE

sock = socket.socket(*args)

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

# safer
try:
    with safer.writer(sock) as s:
        write_header(s)
        write_body(s)  # Exception is thrown here
        write_footer(s)
 except:
    write_error(sock)  # Nothing has been written

safer.open()

Writes a whole file or nothing. It’s a drop-in replacement for built-in open() except that safer.open() leaves the original file unchanged on failure.

EXAMPLE

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

# safer
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.


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.

EXAMPLE

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

# safer
with safer.printer(file) as print:
    for item in items:
        print(item)
    # Either the whole file is written, or nothing

API

safer.writer(stream=None, is_binary=None, close_on_exit=False, temp_file=False, chunk_size=1048576, delete_failures=True)

(safer/bin/safer.py, 163-262)

Write safely to file streams, sockets and callables.

safer.writer yields an in-memory stream that you can write to, but which is only written to the original stream if the context finishes without raising an exception.

Because the actual writing happens when the context exits, it’s possible to block indefinitely if the underlying socket, stream or callable does.

ARGUMENTS
stream:

A file stream, a socket, or a callable that will receive data. If stream is None, output is written to stdout If stream is a string, this file is opened for writing.

is_binary:

Is stream a binary stream?

If is_binary is None, deduce whether it’s a binary file from the stream, or assume it’s text otherwise.

close_on_exit: If True, the underlying stream is closed when the writer

closes

temp_file:

If not false, use a disk file and os.rename() at the end, otherwise cache the writes in memory. If it’s a string, use this as the name of the temporary file, otherwise select one in the same directory as the target file, or in the system tempfile for streams that aren’t files.

chunk_size:

Transfer data from the temporary file to the underlying stream in chunks of this byte size

delete_failures:

If set to false, any temporary files created are not deleted if there is an exception

safer.open(name, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None, make_parents=False, delete_failures=True, temp_file=False)

(safer/bin/safer.py, 264-383)

A drop-in replacement for open() which returns a stream which only overwrites the original file when close() is called, and only if there was no failure.

If a stream fp return from safer.open() is used as a context manager and an exception is raised, the property fp.safer_failed is set to True.

In the method fp.close(), if fp.safer_failed is not set, then the cached results replace the original file, successfully completing the write.

If fp.safer_failed is true, then if delete_failures is true, the temporary file is deleted.

If the mode argument contains either 'a' (append), or '+' (update), then the original file will be copied to the temporary file before writing starts.

Note that if the temp_file argument is set, safer uses an extra temporary file which is renamed over the file only after the stream closes without failing. This uses as much disk space as the old and new files put together.

ARGUMENTS
make_parents:

If true, create the parent directory of the file if it doesn’t exist

delete_failures:

If set to false, any temporary files created are not deleted if there is an exception

temp_file:

If true, use a disk file and os.rename() at the end, otherwise cache the writes in memory. If it’s a string, use this as the name of the temporary file, otherwise select one in the same directory as the target file, or in the system tempfile for streams that aren’t files.

The remaining arguments are the same as for built-in open().

safer.closer(stream, is_binary=None, close_on_exit=True, **kwds)

(safer/bin/safer.py, 385-393)

Like safer.writer() but with close_on_exit=True by default

ARGUMENTS

Same as for safer.writer()

safer.printer(name, mode='w', *args, **kwargs)

(safer/bin/safer.py, 395-413)

A context manager that yields a function that prints to the opened file, only writing to the original file at the exit of the context, and only if there was no exception thrown

ARGUMENTS

Same as for safer.open()

(automatically generated by doks on 2020-10-11T10:27:38.477406)

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-4.3.0.tar.gz (16.3 kB view details)

Uploaded Source

Built Distribution

safer-4.3.0-py3-none-any.whl (16.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: safer-4.3.0.tar.gz
  • Upload date:
  • Size: 16.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/49.2.0 requests-toolbelt/0.9.1 tqdm/4.48.0 CPython/3.6.6

File hashes

Hashes for safer-4.3.0.tar.gz
Algorithm Hash digest
SHA256 2d90ef3a01870c59bd1b29f5ffc515a380d736602b6d222e89a7585c3db0f34a
MD5 8598e1b578df2c68b81905c1a6ae655a
BLAKE2b-256 13e4b2730c523c9e097a5e6faa7cfbc3374e24a2cd42b9a1d8de77413dfd1593

See more details on using hashes here.

File details

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

File metadata

  • Download URL: safer-4.3.0-py3-none-any.whl
  • Upload date:
  • Size: 16.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/49.2.0 requests-toolbelt/0.9.1 tqdm/4.48.0 CPython/3.6.6

File hashes

Hashes for safer-4.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 407ae0c40295687514315e86c1c2606740925603ead4302616a3f1e7d13c0cf5
MD5 a8482f899aed932b75e3e9859daae781
BLAKE2b-256 1cc85eb9f68baebdecc3839b0e961dac6ae376c5e306f96ca1a9a374a64c8b61

See more details on using hashes here.

Supported by

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