Coverage for bookbuilderpy/temp.py: 97%
60 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-17 23:15 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-17 23:15 +0000
1"""A set of utilities interactions with the file system."""
2import os
3from contextlib import AbstractContextManager
4from shutil import rmtree
5from tempfile import mkdtemp, mkstemp
7from bookbuilderpy.path import Path
8from bookbuilderpy.strings import enforce_non_empty_str_without_ws
11class TempDir(Path, AbstractContextManager):
12 """
13 A scoped temporary directory to be used in a 'with' block.
15 The directory and everything in it will be deleted upon exiting the
16 'with' block.
17 """
19 #: is the directory open?
20 __is_open: bool
22 def __new__(cls, value):
23 """
24 Construct the object.
26 :param value: the string value
27 """
28 ret = super().__new__(cls, value)
29 ret.enforce_dir()
30 ret.__is_open = True
31 return ret
33 @staticmethod
34 def create(directory: str | None = None) -> "TempDir":
35 """
36 Create the temporary directory.
38 :param directory: an optional root directory
39 :raises TypeError: if `directory` is not `None` but also no `str`
40 """
41 if directory is not None:
42 root_dir = Path.path(directory)
43 root_dir.enforce_dir()
44 else:
45 root_dir = None
46 return TempDir(mkdtemp(dir=root_dir))
48 def __enter__(self) -> "TempDir":
49 """Nothing, just exists for `with`."""
50 if not self.__is_open:
51 raise ValueError(f"Temporary directory '{self}' already closed.")
52 return self
54 def __exit__(self, exception_type, exception_value, traceback) -> bool:
55 """
56 Delete the temporary directory and everything in it.
58 :param exception_type: ignored
59 :param exception_value: ignored
60 :param traceback: ignored
61 :returns: `True` to suppress an exception, `False` to rethrow it
62 """
63 opn = self.__is_open
64 self.__is_open = False
65 if opn:
66 rmtree(self, ignore_errors=True, onerror=None)
67 return exception_type is None
70class TempFile(Path, AbstractContextManager):
71 """
72 A scoped temporary file to be used in a 'with' block.
74 This file will be deleted upon exiting the 'with' block.
75 """
77 #: is the directory open?
78 __is_open: bool
80 def __new__(cls, value):
81 """
82 Construct the object.
84 :param value: the string value
85 """
86 ret = super().__new__(cls, value)
87 ret.enforce_file()
88 ret.__is_open = True
89 return ret
91 @staticmethod
92 def create(directory: str | None = None,
93 prefix: str | None = None,
94 suffix: str | None = None) -> "TempFile":
95 """
96 Create a temporary file.
98 :param directory: a root directory or `TempDir` instance
99 :param prefix: an optional prefix
100 :param suffix: an optional suffix, e.g., `.txt`
101 :raises TypeError: if any of the parameters does not fulfill the type
102 contract
103 """
104 if prefix is not None:
105 prefix = enforce_non_empty_str_without_ws(prefix)
107 if suffix is not None:
108 suffix = enforce_non_empty_str_without_ws(suffix)
110 if directory is not None:
111 base_dir = Path.path(directory)
112 base_dir.enforce_dir()
113 else:
114 base_dir = None
116 (handle, path) = mkstemp(suffix=suffix, prefix=prefix, dir=base_dir)
117 os.close(handle)
118 return TempFile(path)
120 def __enter__(self) -> "TempFile":
121 """Nothing, just exists for `with`."""
122 if not self.__is_open:
123 raise ValueError(f"Temporary file '{self}' already deleted.")
124 return self
126 def __exit__(self, exception_type, exception_value, traceback) -> bool:
127 """
128 Delete the temporary file.
130 :param exception_type: ignored
131 :param exception_value: ignored
132 :param traceback: ignored
133 :returns: `True` to suppress an exception, `False` to rethrow it
134 """
135 opn = self.__is_open
136 self.__is_open = False
137 if opn:
138 os.remove(self)
139 return exception_type is None