Skip to content

Commit 3608277

Browse files
committed
Add new "TemporaryPathPlus" class.
1 parent 65d4470 commit 3608277

File tree

2 files changed

+88
-3
lines changed

2 files changed

+88
-3
lines changed

domdf_python_tools/paths.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@
4949
import shutil
5050
import stat
5151
import sys
52+
import tempfile
5253
from collections import deque
53-
from typing import IO, Any, Callable, Iterable, Iterator, List, Optional, TypeVar, Union
54+
from typing import IO, Any, Callable, ContextManager, Iterable, Iterator, List, Optional, TypeVar, Union
5455

5556
# this package
57+
from domdf_python_tools.compat import nullcontext
5658
from domdf_python_tools.typing import JsonLibrary, PathLike
5759

5860
__all__ = [
@@ -75,6 +77,7 @@
7577
"traverse_to_file",
7678
"matchglob",
7779
"unwanted_dirs",
80+
"TemporaryPathPlus",
7881
]
7982

8083
newline_default = object()
@@ -449,6 +452,8 @@ def write_lines(
449452
data: Iterable[str],
450453
encoding: Optional[str] = "UTF-8",
451454
errors: Optional[str] = None,
455+
*,
456+
trailing_whitespace: bool = False
452457
) -> None:
453458
"""
454459
Write the given list of lines to the file without trailing whitespace.
@@ -458,9 +463,19 @@ def write_lines(
458463
:param data:
459464
:param encoding: The encoding to write to the file in.
460465
:param errors:
466+
:param trailing_whitespace: If :py:obj:`True` trailing whitespace is preserved.
467+
468+
.. versionchanged:: 2.4.0 Added the ``trailing_whitespace`` option.
461469
"""
462470

463-
return self.write_clean('\n'.join(data), encoding=encoding, errors=errors)
471+
if trailing_whitespace:
472+
data = list(data)
473+
if data[-1].strip():
474+
data.append('')
475+
476+
self.write_text('\n'.join(data), encoding=encoding, errors=errors)
477+
else:
478+
self.write_clean('\n'.join(data), encoding=encoding, errors=errors)
464479

465480
def read_text(
466481
self,
@@ -925,3 +940,52 @@ def matchglob(filename: PathLike, pattern: str):
925940
continue
926941
else:
927942
return False
943+
944+
945+
class TemporaryPathPlus(tempfile.TemporaryDirectory):
946+
"""
947+
Securely creates a temporary directory using the same rules as :func:`tempfile.mkdtemp`.
948+
The resulting object can be used as a context manager.
949+
On completion of the context or destruction of the object
950+
the newly created temporary directory and all its contents are removed from the filesystem.
951+
952+
Unlike :func:`tempfile.TemporaryDirectory` this class is based around a :class:`~.PathPlus` object.
953+
954+
.. versionadded:: 2.4.0
955+
"""
956+
957+
name: PathPlus # type: ignore
958+
"""
959+
The temporary directory itself.
960+
961+
This will be assigned to the target of the :keyword:`as` clause if the :class:`~.TemporaryPathPlus`
962+
is used as a context manager.
963+
"""
964+
965+
def __init__(
966+
self,
967+
suffix: Optional[str] = None,
968+
prefix: Optional[str] = None,
969+
dir: Optional[PathLike] = None, # noqa: A002 # pylint: disable=redefined-builtin
970+
) -> None:
971+
972+
super().__init__(suffix, prefix, dir)
973+
self.name = PathPlus(self.name)
974+
975+
def cleanup(self) -> None:
976+
"""
977+
Cleanup the temporary directory by removing it and its contents.
978+
979+
If the :class:`~.TemporaryPathPlus` is used as a context manager
980+
this is called when leaving the :keyword:`with` block.
981+
"""
982+
983+
context: ContextManager
984+
985+
if sys.platform == "win32":
986+
context = contextlib.suppress(PermissionError, NotADirectoryError)
987+
else:
988+
context = nullcontext()
989+
990+
with context:
991+
super().cleanup()

tests/test_paths.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,15 @@
2222

2323
# this package
2424
from domdf_python_tools import paths
25-
from domdf_python_tools.paths import PathPlus, clean_writer, copytree, in_directory, matchglob, traverse_to_file
25+
from domdf_python_tools.paths import (
26+
PathPlus,
27+
TemporaryPathPlus,
28+
clean_writer,
29+
copytree,
30+
in_directory,
31+
matchglob,
32+
traverse_to_file
33+
)
2634
from domdf_python_tools.testing import not_pypy, not_windows
2735

2836

@@ -861,3 +869,16 @@ def test_abspath_dotted(tmp_pathplus: PathPlus):
861869
assert link.is_symlink()
862870
assert link.resolve() == file
863871
assert link.abspath() == link
872+
873+
874+
def test_temporarypathplus():
875+
with TemporaryPathPlus() as tmpdir:
876+
assert isinstance(tmpdir, PathPlus)
877+
assert tmpdir.exists()
878+
assert tmpdir.is_dir()
879+
880+
t = TemporaryPathPlus()
881+
assert isinstance(t.name, PathPlus)
882+
assert t.name.exists()
883+
assert t.name.is_dir()
884+
t.cleanup()

0 commit comments

Comments
 (0)