{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Manage temporary files\n",
    "\n",
    "**The question.** You need a scratch file or directory — for intermediate output, test fixtures, caching, or the 'write to temp then rename' trick for atomic file updates. You want it cleaned up when you're done, even if an exception slips out.\n",
    "\n",
    "The answer: `tempfile.TemporaryDirectory()` or `tempfile.NamedTemporaryFile()`, both used with `with`. The `with` block guarantees cleanup; the system temp directory gets chosen for you; naming is collision-free.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# A temporary directory with guaranteed cleanup — the most flexible pattern.\n",
    "# Hands you back a string path; you build any number of files inside.\n",
    "import tempfile\n",
    "from pathlib import Path\n",
    "\n",
    "with tempfile.TemporaryDirectory(prefix='demo_') as tmpdir:\n",
    "    tmp = Path(tmpdir)\n",
    "    (tmp / 'data.txt').write_text('hello', encoding='utf-8')\n",
    "    (tmp / 'config.txt').write_text('setting=1', encoding='utf-8')\n",
    "\n",
    "    print(f'tmpdir: {tmp}')\n",
    "    print(f'files: {sorted(p.name for p in tmp.iterdir())}')\n",
    "\n",
    "# Outside the with block: directory and everything in it are gone.\n",
    "print(f'exists after: {Path(tmpdir).exists()}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Variant: `NamedTemporaryFile` for a single file\n",
    "\n",
    "When you need exactly one file and want its name immediately, `NamedTemporaryFile` is more direct. Remember to `flush()` before reading back via path (in-process file handles share a buffer; out-of-process readers don't).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tempfile\n",
    "from pathlib import Path\n",
    "\n",
    "with tempfile.NamedTemporaryFile(\n",
    "    mode='w', suffix='.txt', encoding='utf-8', delete=True\n",
    ") as f:\n",
    "    f.write('temporary data\\n')\n",
    "    f.flush()                                         # so Path().read_text() below sees it\n",
    "    print(f'name: {f.name}')\n",
    "    print(f'round-tripped: {Path(f.name).read_text(encoding=\"utf-8\")!r}')\n",
    "\n",
    "print(f'exists after with: {Path(f.name).exists()}')    # False\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Variant: atomic-write with `delete=False` + rename\n",
    "\n",
    "The standard pattern for 'overwrite this file, but don't leave it half-written if the process dies'. Write to a temporary file in the same directory, then rename atomically over the target. Same-directory rename is atomic on all major filesystems.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tempfile\n",
    "from pathlib import Path\n",
    "\n",
    "def safe_write(target: str | Path, content: str) -> None:\n",
    "    '''Atomic overwrite: write to a temp file, rename over the target.'''\n",
    "    target = Path(target)\n",
    "    target.parent.mkdir(parents=True, exist_ok=True)\n",
    "    with tempfile.NamedTemporaryFile(\n",
    "        mode='w', dir=target.parent, suffix='.tmp',\n",
    "        delete=False, encoding='utf-8',\n",
    "    ) as f:\n",
    "        f.write(content)\n",
    "        tmp = Path(f.name)\n",
    "    tmp.replace(target)        # atomic on same filesystem\n",
    "\n",
    "\n",
    "safe_write('/tmp/important.txt', 'critical information\\n')\n",
    "print(Path('/tmp/important.txt').read_text(encoding='utf-8'))\n",
    "Path('/tmp/important.txt').unlink()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Why this works\n",
    "\n",
    "`TemporaryDirectory` creates a new, unique directory in the system temp location (`/tmp` on Linux/macOS, `%TEMP%` on Windows) and yields its path. On `with`-exit it recursively deletes the directory — success, exception, early return, all the same. That's the behaviour you want for a scratch area: no leaks, no stale files between test runs, no collisions with another process because the name is unique.\n",
    "\n",
    "The path is a `str`, so wrap it in `Path(tmpdir)` if you want the pathlib API for joining and reading. Inside the block, write as many files as you like; they all disappear together on exit.\n",
    "\n",
    "For a single short-lived file (write, read back, done), `NamedTemporaryFile` is more direct — see the extra cells. For the atomic-write pattern (write to temp, rename to target), `delete=False` + explicit rename is the canonical approach.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Trade-offs\n",
    "\n",
    "Use `TemporaryDirectory` when you need more than one file, or when the code under test might create files itself and you just need a clean slate. Use `NamedTemporaryFile` (see extras) when you need exactly one file and want its name immediately.\n",
    "\n",
    "Reach for `mkdtemp()` (no `with`) when the lifetime is longer than a single function — you'll clean up manually with `shutil.rmtree` inside your own `try`/`finally`. Reach for `delete=False` on `NamedTemporaryFile` when you need the file to survive the `with` block so you can rename it, hand the path to a subprocess, etc.\n",
    "\n",
    "Tests especially benefit from `tempfile`. Each test gets its own scratch directory, passes, and cleans up after itself — no lingering artifacts between runs, no race between tests running in parallel.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Related reading\n",
    "\n",
    "- [Process large files](https://agilearn.co.uk/guides/file-handling/recipes/process-large-files) — the atomic-write pattern for outputs bigger than you want to hold twice in memory.\n",
    "- [Avoid common file-handling mistakes](https://agilearn.co.uk/guides/file-handling/recipes/avoid-common-file-handling-mistakes) — including the 'forget to clean up' trap this recipe sidesteps.\n",
    "- [pathlib quick reference](https://agilearn.co.uk/guides/file-handling/reference/pathlib-quick-reference) — the `Path` API for joining and reading the temp file's contents.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}