tmpkit 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tmpkit/__init__.py +24 -0
- tmpkit/_async.py +293 -0
- tmpkit/_atomic.py +289 -0
- tmpkit/_config.py +32 -0
- tmpkit/_decorators.py +271 -0
- tmpkit/_registry.py +159 -0
- tmpkit/_sync.py +565 -0
- tmpkit/_types.py +42 -0
- tmpkit/py.typed +0 -0
- tmpkit-1.0.0.dist-info/METADATA +506 -0
- tmpkit-1.0.0.dist-info/RECORD +13 -0
- tmpkit-1.0.0.dist-info/WHEEL +4 -0
- tmpkit-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tmpkit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Ergonomic tempfile & tempdir context managers with auto-cleanup, atomic writes, async support, and zero dependencies.
|
|
5
|
+
Project-URL: Homepage, https://github.com/MathiasPaulenko/tmpkit
|
|
6
|
+
Project-URL: Repository, https://github.com/MathiasPaulenko/tmpkit
|
|
7
|
+
Project-URL: Issues, https://github.com/MathiasPaulenko/tmpkit/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/MathiasPaulenko/tmpkit/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Mathias Paulenko
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: async,atomic,cleanup,context manager,filesystem,temp,tempfile,temporary,tmp
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: System :: Filesystems
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy>=1.11.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# tmpkit
|
|
35
|
+
|
|
36
|
+
Ergonomic tempfile & tempdir context managers with auto-cleanup, atomic writes, async support, and zero dependencies.
|
|
37
|
+
|
|
38
|
+
[](https://github.com/MathiasPaulenko/tmpkit/actions/workflows/ci.yml)
|
|
39
|
+
[](https://pypi.org/project/tmpkit/)
|
|
40
|
+
[](https://pypi.org/project/tmpkit/)
|
|
41
|
+
[](https://github.com/MathiasPaulenko/tmpkit/blob/main/LICENSE)
|
|
42
|
+
[](https://codecov.io/gh/MathiasPaulenko/tmpkit)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Table of Contents
|
|
47
|
+
|
|
48
|
+
- [Why tmpkit?](#why-tmpkit)
|
|
49
|
+
- [Installation](#installation)
|
|
50
|
+
- [Quick Start](#quick-start)
|
|
51
|
+
- [Features](#features)
|
|
52
|
+
- [API Reference](#api-reference)
|
|
53
|
+
- [`temp_file()`](#temp_file)
|
|
54
|
+
- [`temp_dir()`](#temp_dir)
|
|
55
|
+
- [`atomic_write()`](#atomic_write)
|
|
56
|
+
- [`@temp_dir()` Decorator](#temp_dir-decorator)
|
|
57
|
+
- [`@temp_file()` Decorator](#temp_file-decorator)
|
|
58
|
+
- [`temp_registry`](#temp_registry)
|
|
59
|
+
- [Async API](#async-api)
|
|
60
|
+
- [Environment Variables](#environment-variables)
|
|
61
|
+
- [Keep Control: Precedence](#keep-control-precedence)
|
|
62
|
+
- [Cleanup Hooks](#cleanup-hooks)
|
|
63
|
+
- [Comparison](#comparison)
|
|
64
|
+
- [Contributing](#contributing)
|
|
65
|
+
- [Changelog](#changelog)
|
|
66
|
+
- [Acknowledgements](#acknowledgements)
|
|
67
|
+
- [License](#license)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Why tmpkit?
|
|
72
|
+
|
|
73
|
+
Python's `tempfile` gives you the pieces but forces you to write cleanup boilerplate every time. tmpkit wraps it in ergonomic context managers that **guarantee cleanup** — with features nobody else offers.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# stdlib — verbose, easy to forget cleanup
|
|
77
|
+
import os, tempfile, shutil
|
|
78
|
+
tmpdir = tempfile.mkdtemp()
|
|
79
|
+
try:
|
|
80
|
+
with open(os.path.join(tmpdir, "data.csv"), "w") as f:
|
|
81
|
+
f.write(data)
|
|
82
|
+
finally:
|
|
83
|
+
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
84
|
+
|
|
85
|
+
# tmpkit — one line, always cleans up
|
|
86
|
+
from tmpkit import temp_file
|
|
87
|
+
with temp_file(suffix=".csv") as f:
|
|
88
|
+
f.write(data)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Installation
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install tmpkit
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Requirements:** Python >= 3.11. Zero runtime dependencies.
|
|
100
|
+
|
|
101
|
+
For development:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install -e ".[dev]"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
This installs `pytest`, `pytest-asyncio`, `pytest-cov`, `ruff`, `mypy`, and `build`.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Quick Start
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from tmpkit import temp_file, temp_dir, atomic_write, async_temp_file
|
|
115
|
+
|
|
116
|
+
# Temp file
|
|
117
|
+
with temp_file(suffix=".csv", prefix="myapp_") as f:
|
|
118
|
+
f.write(data)
|
|
119
|
+
# deleted on exit
|
|
120
|
+
|
|
121
|
+
# Temp dir with auto-chdir
|
|
122
|
+
with temp_dir(cwd=True) as d:
|
|
123
|
+
(d / "output.txt").write_text("hello")
|
|
124
|
+
# cwd restored, dir removed on exit
|
|
125
|
+
|
|
126
|
+
# Keep on error — the killer debugging feature
|
|
127
|
+
with temp_file(keep_on_error=True) as f:
|
|
128
|
+
f.write(data)
|
|
129
|
+
risky_operation(f) # if this raises, file stays
|
|
130
|
+
# if no exception, file is deleted
|
|
131
|
+
|
|
132
|
+
# Atomic write
|
|
133
|
+
with atomic_write("config.json") as f:
|
|
134
|
+
f.write(data)
|
|
135
|
+
# on success: atomically renamed to config.json
|
|
136
|
+
# on error: config.json untouched, temp cleaned up
|
|
137
|
+
|
|
138
|
+
# Promote temp to permanent location
|
|
139
|
+
with temp_file(dest="output.csv") as f:
|
|
140
|
+
f.write(data)
|
|
141
|
+
# on success: moved to output.csv
|
|
142
|
+
|
|
143
|
+
# Async
|
|
144
|
+
async def main() -> None:
|
|
145
|
+
async with async_temp_file(suffix=".json") as f:
|
|
146
|
+
await f.write(data)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Features
|
|
152
|
+
|
|
153
|
+
- **`keep_on_error=True`** — keep temp only on exception, delete on success. The #1 most requested tempfile feature. Nobody else has it.
|
|
154
|
+
- **`atomic_write()`** — temp file + atomic rename. The most reimplemented pattern, now built-in.
|
|
155
|
+
- **`dest=` parameter** — promote temp to a permanent location on success.
|
|
156
|
+
- **`DEBUG=1` env var** — keep all temps for debugging, no code changes.
|
|
157
|
+
- **`keep=True` per-call** — keep a specific temp without global DEBUG.
|
|
158
|
+
- **`.keep()` method** — decide at runtime whether to keep.
|
|
159
|
+
- **`cwd=True`** — auto-chdir into temp dir, restore on exit.
|
|
160
|
+
- **`content=`** — pre-populate file with content.
|
|
161
|
+
- **`cleanup_hook=`** — custom hook called before standard cleanup.
|
|
162
|
+
- **`temp_registry`** — track all temps globally, cleanup on demand.
|
|
163
|
+
- **`@temp_dir()` / `@temp_file()` decorators** — inject temps into functions and test classes.
|
|
164
|
+
- **Close without delete** — file survives `close()`, deleted on context exit (Windows subprocess friendly).
|
|
165
|
+
- **`.path` attribute** — `Path` object, no more `Path(f.name)` boilerplate.
|
|
166
|
+
- **Async support** — `async with temp_file() as f:` with async I/O methods.
|
|
167
|
+
- **Windows-safe by default** — no `O_TEMPORARY` lock, `ignore_cleanup_errors=True`.
|
|
168
|
+
- **Zero dependencies** — stdlib only.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## API Reference
|
|
173
|
+
|
|
174
|
+
### `temp_file()`
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from tmpkit import temp_file
|
|
178
|
+
|
|
179
|
+
with temp_file(
|
|
180
|
+
suffix: str | None = None, # e.g. ".csv"
|
|
181
|
+
prefix: str | None = None, # e.g. "myapp_"
|
|
182
|
+
dir: str | Path | None = None, # parent directory
|
|
183
|
+
mode: str = "w+b", # open mode
|
|
184
|
+
content: str | bytes | None = None, # pre-populate
|
|
185
|
+
dest: str | Path | None = None, # move here on success
|
|
186
|
+
keep: bool = False, # always keep
|
|
187
|
+
keep_on_error: bool = False, # keep only on exception
|
|
188
|
+
ignore_cleanup_errors: bool = True,
|
|
189
|
+
cleanup_hook: Callable[[Path], None] | None = None,
|
|
190
|
+
) as f:
|
|
191
|
+
f.write(data) # file-like I/O
|
|
192
|
+
f.read()
|
|
193
|
+
f.seek(0)
|
|
194
|
+
f.path # Path object
|
|
195
|
+
f.keep() # runtime decision to keep
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Returns:** A file-like object with `.path` (Path), `.keep()`, and all standard file methods (`read`, `write`, `seek`, `tell`, `flush`, `close`).
|
|
199
|
+
|
|
200
|
+
**`dest=` behavior:**
|
|
201
|
+
|
|
202
|
+
- On success: temp is moved to `dest` via `os.replace()` (same filesystem) or `shutil.move()` (cross-filesystem).
|
|
203
|
+
- On error: temp is deleted, `dest` is untouched.
|
|
204
|
+
- If `dest` already exists, it is overwritten.
|
|
205
|
+
|
|
206
|
+
### `temp_dir()`
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from tmpkit import temp_dir
|
|
210
|
+
|
|
211
|
+
with temp_dir(
|
|
212
|
+
suffix: str | None = None,
|
|
213
|
+
prefix: str | None = None,
|
|
214
|
+
dir: str | Path | None = None,
|
|
215
|
+
cwd: bool = False, # auto-chdir into temp dir
|
|
216
|
+
keep: bool = False,
|
|
217
|
+
keep_on_error: bool = False,
|
|
218
|
+
ignore_cleanup_errors: bool = True,
|
|
219
|
+
cleanup_hook: Callable[[Path], None] | None = None,
|
|
220
|
+
) as d:
|
|
221
|
+
(d / "file.txt").write_text("hello")
|
|
222
|
+
d # Path object
|
|
223
|
+
|
|
224
|
+
# To call .keep(), use the context manager object directly:
|
|
225
|
+
td = temp_dir()
|
|
226
|
+
with td as d:
|
|
227
|
+
(d / "file.txt").write_text("hello")
|
|
228
|
+
td.keep() # runtime decision to keep
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Returns:** A `Path` object (the temp directory path) with `/` operator support. To call `.keep()`, use the context manager object directly (see example above).
|
|
232
|
+
|
|
233
|
+
**`cwd=True`:** Changes the working directory to the temp dir on `__enter__`, restores the original on `__exit__`.
|
|
234
|
+
|
|
235
|
+
### `atomic_write()`
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from tmpkit import atomic_write
|
|
239
|
+
|
|
240
|
+
with atomic_write(
|
|
241
|
+
dest: str | Path, # final destination
|
|
242
|
+
mode: str = "w", # "w" (text) or "wb" (binary)
|
|
243
|
+
encoding: str | None = None,
|
|
244
|
+
newline: str | None = None,
|
|
245
|
+
prefix: str | None = None,
|
|
246
|
+
suffix: str = ".tmp",
|
|
247
|
+
fsync: bool = True, # fsync before rename
|
|
248
|
+
keep_on_error: bool = False,
|
|
249
|
+
ignore_cleanup_errors: bool = True,
|
|
250
|
+
) as f:
|
|
251
|
+
f.write(data)
|
|
252
|
+
# on success: atomically renamed to dest
|
|
253
|
+
# on error: dest untouched, temp deleted
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Writes to a temp file in `dest`'s parent directory, then atomically replaces `dest` via `os.replace()` on success. On error, the temp is cleaned up and `dest` is left untouched.
|
|
257
|
+
|
|
258
|
+
### `@temp_dir()` Decorator
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from tmpkit import temp_dir_decorator
|
|
262
|
+
|
|
263
|
+
# On a function — temp dir injected as first arg
|
|
264
|
+
@temp_dir_decorator()
|
|
265
|
+
def process(tmp: Path, data: str) -> None:
|
|
266
|
+
(tmp / "output.txt").write_text(data)
|
|
267
|
+
|
|
268
|
+
# cwd=True by default for the decorator
|
|
269
|
+
@temp_dir_decorator(prefix="test_")
|
|
270
|
+
def my_func(tmp: Path) -> str:
|
|
271
|
+
return str(tmp)
|
|
272
|
+
|
|
273
|
+
# On a test class — each test_ method gets a fresh temp dir
|
|
274
|
+
@temp_dir_decorator()
|
|
275
|
+
class TestMyApp:
|
|
276
|
+
def test_writes_file(self) -> None:
|
|
277
|
+
assert self.tmpdir.exists()
|
|
278
|
+
(self.tmpdir / "data.txt").write_text("test")
|
|
279
|
+
|
|
280
|
+
async def test_async(self) -> None:
|
|
281
|
+
assert self.tmpdir.exists()
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Decorator defaults:** `cwd=True` (unlike the context manager where `cwd=False` by default).
|
|
285
|
+
|
|
286
|
+
**Class decoration:** Each method starting with `test_` is wrapped. The temp dir is available as `self.tmpdir`. Works with both sync and async test methods.
|
|
287
|
+
|
|
288
|
+
### `@temp_file()` Decorator
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
from tmpkit import temp_file_decorator
|
|
292
|
+
|
|
293
|
+
@temp_file_decorator(mode="w+")
|
|
294
|
+
def process(f, data: str) -> str:
|
|
295
|
+
f.write(data)
|
|
296
|
+
f.seek(0)
|
|
297
|
+
return f.read()
|
|
298
|
+
|
|
299
|
+
result = process("hello world")
|
|
300
|
+
|
|
301
|
+
# Async functions supported
|
|
302
|
+
@temp_file_decorator(suffix=".json")
|
|
303
|
+
async def process_async(f, data: str) -> None:
|
|
304
|
+
await f.write(data)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
The temp file object is injected as the **first positional argument**.
|
|
308
|
+
|
|
309
|
+
### `temp_registry`
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
from tmpkit import temp_registry
|
|
313
|
+
|
|
314
|
+
# Enable tracking
|
|
315
|
+
temp_registry.enable()
|
|
316
|
+
|
|
317
|
+
# Or via env var: TMPKIT_REGISTRY=1
|
|
318
|
+
|
|
319
|
+
with temp_file() as f:
|
|
320
|
+
assert len(temp_registry.active) == 1
|
|
321
|
+
assert temp_registry.active[0].path == f.path
|
|
322
|
+
# After exit:
|
|
323
|
+
assert len(temp_registry.active) == 0
|
|
324
|
+
assert len(temp_registry.cleaned) == 1
|
|
325
|
+
|
|
326
|
+
# Inspect all records
|
|
327
|
+
for record in temp_registry.all:
|
|
328
|
+
print(f"{record.kind} at {record.path} (cleaned={record.cleaned}, kept={record.kept})")
|
|
329
|
+
|
|
330
|
+
# Emergency cleanup
|
|
331
|
+
count = temp_registry.cleanup_all() # deletes all active temps
|
|
332
|
+
|
|
333
|
+
# Mark all as kept
|
|
334
|
+
temp_registry.keep_all()
|
|
335
|
+
|
|
336
|
+
# Clear cleaned records from history
|
|
337
|
+
temp_registry.clear_history()
|
|
338
|
+
|
|
339
|
+
# Disable
|
|
340
|
+
temp_registry.disable()
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**`TempRecord` fields:**
|
|
344
|
+
|
|
345
|
+
| Field | Type | Description |
|
|
346
|
+
| --- | --- | --- |
|
|
347
|
+
| `path` | `Path` | Filesystem path |
|
|
348
|
+
| `kind` | `"file"` or `"dir"` | Resource type |
|
|
349
|
+
| `created_at` | `float` | Epoch timestamp |
|
|
350
|
+
| `cleaned` | `bool` | Whether it was cleaned up |
|
|
351
|
+
| `kept` | `bool` | Whether it was kept |
|
|
352
|
+
|
|
353
|
+
**Thread-safe:** All operations are protected by `threading.Lock`.
|
|
354
|
+
|
|
355
|
+
### Async API
|
|
356
|
+
|
|
357
|
+
All sync APIs have async counterparts with identical parameters:
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
from tmpkit import async_temp_file, async_temp_dir, async_atomic_write
|
|
361
|
+
|
|
362
|
+
# Async temp file
|
|
363
|
+
async with async_temp_file(suffix=".csv") as f:
|
|
364
|
+
await f.write(data)
|
|
365
|
+
await f.seek(0)
|
|
366
|
+
content = await f.read()
|
|
367
|
+
|
|
368
|
+
# Async temp dir
|
|
369
|
+
async with async_temp_dir(cwd=True) as d:
|
|
370
|
+
...
|
|
371
|
+
|
|
372
|
+
# Async atomic write
|
|
373
|
+
async with async_atomic_write("config.json") as f:
|
|
374
|
+
await f.write(data)
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Async file objects support `await f.read()`, `await f.write()`, `await f.seek()`, `await f.tell()`, `await f.flush()`, `await f.close()`.
|
|
378
|
+
|
|
379
|
+
### Environment Variables
|
|
380
|
+
|
|
381
|
+
| Variable | Value | Effect |
|
|
382
|
+
| --- | --- | --- |
|
|
383
|
+
| `TMPKIT_DEBUG` | `1` | Keep all temps (overrides `keep=False`) |
|
|
384
|
+
| `DEBUG` | `1` | Same as `TMPKIT_DEBUG=1` (fallback) |
|
|
385
|
+
| `TMPKIT_REGISTRY` | `1` | Enable `temp_registry` at import time |
|
|
386
|
+
|
|
387
|
+
`TMPKIT_DEBUG` takes precedence over `DEBUG`.
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Keep Control: Precedence
|
|
392
|
+
|
|
393
|
+
When multiple keep signals are present, precedence is:
|
|
394
|
+
|
|
395
|
+
1. **`.keep()` method** — highest priority, always keeps.
|
|
396
|
+
2. **`keep=True` parameter** — always keeps.
|
|
397
|
+
3. **`DEBUG=1` / `TMPKIT_DEBUG=1` env var** — keeps all temps globally.
|
|
398
|
+
4. **`keep_on_error=True` + exception** — keeps only on error.
|
|
399
|
+
5. **`dest=` move** — if none of the above trigger, temp is moved to dest on success.
|
|
400
|
+
6. **Standard cleanup** — temp is deleted.
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
# .keep() wins over everything
|
|
404
|
+
with temp_file(keep=False) as f:
|
|
405
|
+
f.write(data)
|
|
406
|
+
f.keep() # file is kept despite keep=False
|
|
407
|
+
|
|
408
|
+
# DEBUG=1 overrides keep=False
|
|
409
|
+
# $ TMPKIT_DEBUG=1 python my_script.py
|
|
410
|
+
with temp_file() as f: # file is kept
|
|
411
|
+
f.write(data)
|
|
412
|
+
|
|
413
|
+
# keep_on_error keeps only on exception
|
|
414
|
+
with temp_file(keep_on_error=True) as f:
|
|
415
|
+
f.write(data)
|
|
416
|
+
raise RuntimeError("oops") # file is kept
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Cleanup Hooks
|
|
422
|
+
|
|
423
|
+
The `cleanup_hook` parameter lets you run custom logic before standard cleanup:
|
|
424
|
+
|
|
425
|
+
```python
|
|
426
|
+
def my_hook(path: Path) -> None:
|
|
427
|
+
print(f"Cleaning up {path}")
|
|
428
|
+
# e.g. log, collect metrics, copy to backup, etc.
|
|
429
|
+
|
|
430
|
+
with temp_file(cleanup_hook=my_hook) as f:
|
|
431
|
+
f.write(data)
|
|
432
|
+
# hook is called, then standard cleanup runs
|
|
433
|
+
|
|
434
|
+
# Hook is called even on exceptions
|
|
435
|
+
with temp_file(cleanup_hook=my_hook) as f:
|
|
436
|
+
raise RuntimeError("oops")
|
|
437
|
+
# hook is still called, then temp is deleted
|
|
438
|
+
|
|
439
|
+
# Hook errors are swallowed if ignore_cleanup_errors=True (default)
|
|
440
|
+
def bad_hook(path: Path) -> None:
|
|
441
|
+
raise OSError("hook failed")
|
|
442
|
+
|
|
443
|
+
with temp_file(cleanup_hook=bad_hook) as f: # no error raised
|
|
444
|
+
f.write(data)
|
|
445
|
+
|
|
446
|
+
# Hook is NOT called when temp is kept
|
|
447
|
+
with temp_file(keep=True, cleanup_hook=my_hook) as f:
|
|
448
|
+
f.write(data)
|
|
449
|
+
# hook is NOT called
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Comparison
|
|
455
|
+
|
|
456
|
+
| Feature | tmpkit | stdlib `tempfile` | `temporary` | `tdir` | `temppathlib` | `ephemdir` |
|
|
457
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
458
|
+
| `keep_on_error` | Yes | No | No | No | No | No |
|
|
459
|
+
| `atomic_write()` | Yes | No | No | No | No | No |
|
|
460
|
+
| `dest=` promote | Yes | No | No | No | No | No |
|
|
461
|
+
| `cleanup_hook` | Yes | No | No | No | No | No |
|
|
462
|
+
| `temp_registry` | Yes | No | No | No | No | No |
|
|
463
|
+
| Decorators | Yes | No | No | No | No | No |
|
|
464
|
+
| `DEBUG=1` env var | Yes | No | No | No | No | No |
|
|
465
|
+
| `keep=True` per-call | Yes | No | No | No | No | No |
|
|
466
|
+
| `.keep()` method | Yes | No | No | No | No | No |
|
|
467
|
+
| Close without delete | Yes | No | No | No | No | No |
|
|
468
|
+
| `.path` attribute | Yes | No | No | No | Yes | No |
|
|
469
|
+
| `cwd=True` | Yes | No | Yes | Yes | No | No |
|
|
470
|
+
| `content=` | Yes | No | Yes | No | No | No |
|
|
471
|
+
| Async support | Yes | No | No | No | No | No |
|
|
472
|
+
| Windows-safe by default | Yes | No | No | No | No | No |
|
|
473
|
+
| `ignore_cleanup_errors` | Yes (default) | Yes (opt-in) | No | No | No | No |
|
|
474
|
+
| Returns `Path` | Yes | No | Yes | No | Yes | No |
|
|
475
|
+
| Zero deps | Yes | Yes | No | Yes | Yes | Yes |
|
|
476
|
+
| Python >=3.11 | Yes | Yes | No | Yes | No | Yes |
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Contributing
|
|
481
|
+
|
|
482
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code style, testing, and pull request guidelines.
|
|
483
|
+
|
|
484
|
+
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing.
|
|
485
|
+
|
|
486
|
+
To report a security vulnerability, see [SECURITY.md](SECURITY.md).
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## Changelog
|
|
491
|
+
|
|
492
|
+
See [CHANGELOG.md](CHANGELOG.md) for a full list of changes.
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## Acknowledgements
|
|
497
|
+
|
|
498
|
+
- Python's [`tempfile`](https://docs.python.org/3/library/tempfile.html) module — the foundation tmpkit builds upon.
|
|
499
|
+
- [`contextlib`](https://docs.python.org/3/library/contextlib.html) — inspiration for the context manager patterns.
|
|
500
|
+
- Every developer who has written `try/finally/shutil.rmtree` boilerplate — you deserved better.
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## License
|
|
505
|
+
|
|
506
|
+
[MIT](LICENSE) — Copyright (c) 2025 Mathias Paulenko
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
tmpkit/__init__.py,sha256=ZhWHe5nff6A-80cvecYAS8rcZQrXmpTzhXrIwMHL4YU,795
|
|
2
|
+
tmpkit/_async.py,sha256=n-nEOxQvibobnxJeq7HAlJrNce9pqHb2ETM4J6g3WFk,9256
|
|
3
|
+
tmpkit/_atomic.py,sha256=xPcGSv1A_74uCX5eLXsVFSQiX47iOQr5jZtEHZZT6fU,9028
|
|
4
|
+
tmpkit/_config.py,sha256=-SVxmwvRgf8U4Qx4rx8l7CBH8rQ0Z5hXxn7UqLdeVbU,908
|
|
5
|
+
tmpkit/_decorators.py,sha256=GSdJeQkVL5PDH7GEGdMyA82dUXX92uAdY2q2d7LfIkY,8616
|
|
6
|
+
tmpkit/_registry.py,sha256=ibzwODy0bLMbFbl3MpAroNIwTo0tmLpEYAtT7wteB_Q,4960
|
|
7
|
+
tmpkit/_sync.py,sha256=scn5nwwOYzcTSsv4sILHz6ImYHZUlS_jCQ5PXIA08k8,18878
|
|
8
|
+
tmpkit/_types.py,sha256=BWnRLP6mOgyFUwHL16p-B62Md5pVIeBH0CjTCozxz9Y,1360
|
|
9
|
+
tmpkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
tmpkit-1.0.0.dist-info/METADATA,sha256=CRMmzVbnm21HpiRBnMkwBFpnDAMzPeMqzfR40SCbvpc,16050
|
|
11
|
+
tmpkit-1.0.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
12
|
+
tmpkit-1.0.0.dist-info/licenses/LICENSE,sha256=LLr8AP65kT83seFEcknE3-76LL7oikrP87U33tZKYro,1073
|
|
13
|
+
tmpkit-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mathias Paulenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|