<!--
SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
SPDX-License-Identifier: BSD-2-Clause
-->

# Create a temporary file, take care of removing it if needed

\[[Home][ringlet-home] | [GitLab][gitlab] | [PyPI][pypi] | [ReadTheDocs][readthedocs]\]

## Overview

The `with-tempfile` library is designed to help handle files that
should be modified atomically, so that there is no chance that
at any time, another program reading the file would get its
incomplete contents, or that an error halfway through writing
the updated contents will leave the file incomplete on disk.

It currently provides two functions, `write_text` and `append_text`.

Binary file handing is planned.

A command-line tool to overwrite or append to a file atomically is planned.

## API

### Update or create files atomically

#### write_text

The `write_text` function creates a temporary file in
the same directory as the target (so that an atomic rename will succeed),
writes out the supplied contents, and then atomically renames the temporary file to
the target one.

``` python
LOCAL_CONFIG: Final = pathlib.Path("/etc/sample/config.toml")

with_tempfile.write_text(LOCAL_CONFIG, toml.dumps(cfg))
```

#### append_text

The `append_text` function creates a temporary file in
the same directory as the target (so that an atomic rename will succeed),
copies the current contents of the target file if it exists,
appends the supplied text, and then atomically renames the temporary file to
the target one.

``` python
ENC_SINGLE: Final = "ISO-8859-15"
"""A fallback single-byte encoding that has all 256 codepoints as valid."""

ENCODINGS: Final = ("UTF-8", ENC_SINGLE)
"""The names of the encodings to try to decode a file's contents with."""

MARKER_TEXT: Final = f"""
# Autogenerated by sampletool; modifications may be lost!
"""

if CFG_SECTION_NAME not in cfg:
    new_section: Final = f"{MARKER_TEXT}{toml.dumps({CFG_SECTION_NAME: cfg_section})}"
    with_tempfile.append_text(LOCAL_CONFIG, new_section, encoding=ENCODINGS)
```

Note that this function may be dropped in a future version of the `with-tempfile`
library, since in most cases the desired contents of the new file can be fully
constructed and `write_text` may be used instead.

### Path manipulation helpers

The `with_tempfile.pathutil` module
provides the `PathLike` union type that also
includes `str`, and the `as_path` function that
will convert an object into a `pathlike.Path` object if it is not already one.

``` python
def create_default(path: pathutil.PathLike) -> None:
    """Write the default text to the specified file."""
    with_tempfile.write_text(pathutil.as_path(path), DEFAULT_CONTENTS)
```

The `read_text` function is similar to
the `Path.read_text` method from the Python standard `pathlib` library, but
it allows more than one encoding to be tried in order.
This may be useful for files that may or may not be valid UTF-8 and a reader
that might want to fall back to a single-byte encoding.

``` python
contents, encoding = pathutil.read_text(path, encoding=ENCODINGS)
with_tempfile.write_text(path, rot13(contents), encoding=encoding)
```

### Temporary file handling

The `with_tempfile.temputil` module
provides the `NamedTemporaryTextFile`
function that returns
a `TemporaryTextFile` object.
It is quite similar to `NamedTemporaryFile` in Python's standard `tempfile`
library, but the object has the new `path` and `resolved_path` properties:
both `pathllib.Path` objects.
It also has
an `unset_delete` method that
prevents the object destructor from trying to delete the temporary file if
the program has already e.g. renamed it.

``` python
with temputil.NamedTemporaryTextFile(prefix="info-", suffix=".txt", encoding="UTF-8") as tempf:
    print("this is a test", file=tempf, flush=True)
    tempf.path.rename_to("/etc/sample/info.txt")
    tempf.unset_delete()
```

There is also
a `TemporaryDirectory` class that
is similar to the one in the `tempfile` library, but also has
the `path` and `resolved_path` properties.
An important difference with the system's `TemporaryDirectory` class is that
the `__enter__()` method returns a `pathlib.Path` object instead of a string.

``` python
with temputil.TemporaryDirectory(prefix="test-") as tempd:
    tempcfg: Final = tempd / "config.txt"
    ...
```

## Contact

The `with-tempfile` library was written by [Peter Pentchev][roam].
It is developed in [a GitLab repository][gitlab]. This documentation is
hosted at [Ringlet][ringlet-home] with a copy at [ReadTheDocs][readthedocs].

[roam]: mailto:roam@ringlet.net "Peter Pentchev"
[gitlab]: https://gitlab.com/ppentchev/with-tempfile "The with-tempfile GitLab repository"
[pypi]: https://pypi.org/project/with-tempfile/ "The with-tempfile Python Package Index page"
[readthedocs]: https://with-tempfile.readthedocs.io/ "The with-tempfile ReadTheDocs page"
[ringlet-home]: https://devel.ringlet.net/devel/with-tempfile/ "The Ringlet with-tempfile homepage"
