{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# How do I use `string.Template` for safe substitution?\n",
    "\n",
    "You need to drop user-supplied values into a string — an email body, a notification, a generated message — and you want the substitution to be safe even if the user data is wrong, missing, or hostile. f-strings and `str.format()` give you full Python expression power, which is exactly what you don't want when the template comes from outside your code.\n",
    "\n",
    "`string.Template` is the deliberately-limited tool for this job: `$name`-style placeholders, no expressions, no attribute access, no method calls. You hand it a dict of values and it fills them in."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from string import Template\n",
    "\n",
    "# A template — note this could safely be loaded from a file or database.\n",
    "email = Template(\n",
    "    \"Hi $name,\\n\\n\"\n",
    "    \"Your order #$order_id is ready for collection at $store.\\n\\n\"\n",
    "    \"Thanks,\\nThe team\"\n",
    ")\n",
    "\n",
    "# safe_substitute() leaves any missing placeholders as $name, rather than\n",
    "# raising. That's almost always what you want for templates that come\n",
    "# from outside your code.\n",
    "message = email.safe_substitute(\n",
    "    name=\"Alice\",\n",
    "    order_id=\"A1729\",\n",
    "    store=\"Manchester Piccadilly\",\n",
    ")\n",
    "print(message)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Two extras: choosing between `substitute` and `safe_substitute`, and a sketch of why templates are safer than f-strings for user-supplied templates."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from string import Template\n",
    "\n",
    "t = Template(\"Dear $title $surname, your order #$order_id is ready.\")\n",
    "\n",
    "# substitute() is strict — KeyError if anything is missing\n",
    "print(t.substitute(title=\"Dr\", surname=\"Singh\", order_id=\"A1729\"))\n",
    "\n",
    "try:\n",
    "    t.substitute(title=\"Dr\", surname=\"Singh\")  # missing order_id\n",
    "except KeyError as exc:\n",
    "    print(f\"strict mode raised: {exc}\")\n",
    "\n",
    "# safe_substitute() leaves the placeholder in place — useful when\n",
    "# you're building a message in stages, or when missing values should\n",
    "# show through rather than crash.\n",
    "print(t.safe_substitute(title=\"Dr\", surname=\"Singh\"))\n",
    "# Dear Dr Singh, your order #$order_id is ready.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Why templates beat f-strings for *user-supplied* templates\n",
    "from string import Template\n",
    "\n",
    "# Imagine this template came from a database row or a settings file —\n",
    "# i.e. somewhere a user could edit it.\n",
    "user_supplied = \"Hi $name, your balance is $balance.\"\n",
    "\n",
    "# string.Template only does name substitution. There's no way for the\n",
    "# template author to read attributes, call methods, or evaluate expressions.\n",
    "print(Template(user_supplied).substitute(name=\"Alice\", balance=\"£42\"))\n",
    "\n",
    "# An f-string version would require eval() or a similar mechanism, which\n",
    "# would let the template author run arbitrary Python — including reading\n",
    "# secrets out of the surrounding scope. Don't do this:\n",
    "#\n",
    "#     eval(f\"f'{user_supplied}'\")\n",
    "#\n",
    "# Use string.Template, or a real templating library like Jinja2 with\n",
    "# autoescape and a sandboxed environment.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Why it works\n",
    "\n",
    "`string.Template` solves a narrower problem than f-strings or `str.format()`, and that narrowness is the whole point.\n",
    "\n",
    "The template syntax is just `$name` (or `${name}` when you need to butt the placeholder up against following characters). There's no `{obj.attr}`, no `{obj.method()}`, no format spec, no expressions. The substitution mechanism takes a dict (or kwargs) of name → value and replaces each placeholder with the corresponding value's `str()`. That's it.\n",
    "\n",
    "`substitute()` is the strict variant: any placeholder without a value, and any value without a placeholder, raises `KeyError`. Use it when the template and the data come from the same trust domain — typically your own code — and a mismatch is a programming error you want to find loudly.\n",
    "\n",
    "`safe_substitute()` is the forgiving variant: missing placeholders are left in place as the literal `$name` text. Use it when the template is data — loaded from a config file, a database, a CMS — and you want a bad template to render imperfectly rather than crash the whole request.\n",
    "\n",
    "The reason this matters for security is that f-strings and `str.format()` give the template author access to Python's expression syntax. `f\"{user.password}\"` or `\"{0.__class__.__init__.__globals__}\".format(obj)` can leak information from the surrounding scope. `string.Template` literally cannot do either — there's no syntax for it."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Trade-offs\n",
    "\n",
    "`Template` is the right choice when the template comes from outside your trust boundary — user-editable settings, content authored by non-developers, anything loaded from a database. It's deliberately featureless; that's its security model.\n",
    "\n",
    "It is the *wrong* choice when you control the template and want the expressiveness of full Python. For interpolating into a SQL string you're about to execute, an HTML page you're rendering, or a one-off log message, an f-string is shorter, faster, and easier to read. f-strings also win on performance — `Template.substitute` does work at runtime that an f-string does at compile time.\n",
    "\n",
    "For real templating — loops, conditionals, includes, autoescaping — use Jinja2. `string.Template` is for simple value substitution; the moment you want `{% if %}` or `{% for %}`, you've outgrown it.\n",
    "\n",
    "If you're tempted to subclass `Template` to change the delimiter (say, to `%name%` for legacy compatibility), it works, but think hard about whether the legacy format is worth supporting at all."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Related\n",
    "\n",
    "- [How to avoid common string mistakes](https://agilearn.co.uk/guides/string-processing/recipes/avoid-common-string-mistakes) — including the comparison between f-strings, `format`, and templates.\n",
    "- [String formatting reference](https://agilearn.co.uk/guides/string-processing/reference/string-formatting-reference) — the full f-string and `format()` mini-language for the cases where templates aren't the right tool.\n",
    "- [How to clean and normalise text](https://agilearn.co.uk/guides/string-processing/recipes/clean-and-normalise-text) — useful before substituting user-supplied values into a template."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}