{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Installing third-party packages\n",
    "\n",
    "The Python standard library is broad, but you'll quickly want\n",
    "third-party code — `requests` for HTTP, `pandas` for data, `pytest`\n",
    "for testing. This tutorial covers how `pip` finds, downloads, and\n",
    "installs packages, and how to keep your project's dependencies\n",
    "reproducible.\n",
    "\n",
    "**Time commitment:** 15 minutes\n",
    "\n",
    "**Prerequisites:**\n",
    "\n",
    "- You can open a terminal and run `python --version`\n",
    "\n",
    "## Learning objectives\n",
    "\n",
    "By the end of this tutorial, you will be able to:\n",
    "\n",
    "- Use `pip install` to add a third-party package to your environment\n",
    "- Pin versions and use a `requirements.txt`\n",
    "- List, inspect, and remove installed packages"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "!!! note \"Browser note\"\n",
    "    The commands below run in your terminal, not in this page's browser kernel.\n",
    "    Pyodide doesn't expose a shell, so the bash blocks here are for reference —\n",
    "    copy them into a real terminal to follow along."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Where third-party packages come from\n",
    "\n",
    "The default source is the **Python Package Index** — PyPI, at\n",
    "[pypi.org](https://pypi.org). When you run `pip install requests`,\n",
    "`pip` queries PyPI for the package, downloads a built artefact (a\n",
    "*wheel* — see the [Concepts](https://agilearn.co.uk/guides/packages-and-packaging/concepts/why-wheels-exist) section), and\n",
    "installs it into your current Python environment.\n",
    "\n",
    "PyPI is open: anyone can publish a package. That's both its strength\n",
    "(a vast ecosystem) and a hazard worth knowing about — see [The PyPI\n",
    "ecosystem and trust](../concepts/the-pypi-ecosystem-and-trust.md)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `pip install`\n",
    "\n",
    "```bash\n",
    "pip install requests\n",
    "```\n",
    "\n",
    "That's it. `pip` resolves the package's dependencies, fetches them\n",
    "all, and installs them. After it finishes, `import requests` works\n",
    "from any Python script using the same environment.\n",
    "\n",
    "On many systems you'll need to call it as `python -m pip install\n",
    "requests` (or `python3 -m pip ...`) — this guarantees you're using\n",
    "the `pip` that belongs to the Python you have in mind, rather than\n",
    "whichever `pip` the shell happens to find first."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pinning a version\n",
    "\n",
    "By default, `pip install requests` installs the latest release. You\n",
    "can pin to a specific version with `==`, or use comparison operators\n",
    "for ranges:\n",
    "\n",
    "```bash\n",
    "pip install \"requests==2.31.0\"           # exact\n",
    "pip install \"requests>=2.30,<3\"          # any 2.30 or later, but not 3.x\n",
    "pip install \"requests~=2.31.0\"           # 2.31.x — compatible release\n",
    "```\n",
    "\n",
    "The `~=` (compatible release) operator is convenient: `~=2.31.0`\n",
    "means \"at least 2.31.0, but less than 2.32\" — patch updates only.\n",
    "Pinning is covered in detail in the\n",
    "[Pin and lock dependencies](https://agilearn.co.uk/guides/packages-and-packaging/recipes/pin-and-lock-dependencies)\n",
    "recipe."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## requirements.txt\n",
    "\n",
    "For a project with more than a handful of dependencies, a plain text\n",
    "file is the lowest-friction way to record them. By convention it's\n",
    "called `requirements.txt`:\n",
    "\n",
    "```text\n",
    "requests==2.31.0\n",
    "pandas>=2.0\n",
    "rich\n",
    "```\n",
    "\n",
    "Then anyone (including you, on a fresh machine) can install\n",
    "everything with one command:\n",
    "\n",
    "```bash\n",
    "pip install -r requirements.txt\n",
    "```\n",
    "\n",
    "This is good enough for many projects. For applications where you\n",
    "want full reproducibility — exact versions of every transitive\n",
    "dependency — see the lock-file approach in [Pin and lock\n",
    "dependencies](../recipes/pin-and-lock-dependencies.md)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Inspecting what's installed\n",
    "\n",
    "```bash\n",
    "pip list                # everything in the current environment\n",
    "pip show requests       # metadata for one package\n",
    "pip freeze              # output formatted as a requirements.txt\n",
    "```\n",
    "\n",
    "`pip freeze` is what people often use to bootstrap their first\n",
    "`requirements.txt`, but it includes packages you didn't explicitly\n",
    "ask for (the dependencies of your dependencies), which can make the\n",
    "file harder to read. For a clean separation, write `requirements.txt`\n",
    "by hand and use `pip freeze` only as a sanity check."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# We can ask Python where a module came from. This works in Pyodide\n",
    "# because the `requests`-like behaviour is provided by built-in modules.\n",
    "import json\n",
    "print(\"module name:\", json.__name__)\n",
    "print(\"module file:\", json.__file__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That's how you'd verify a third-party install too — import it, and\n",
    "check `__file__` to see which directory it came from.\n",
    "Site-packages — the directory `pip install` writes to — is where most\n",
    "third-party packages live; the standard library lives somewhere else\n",
    "entirely."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Removing a package\n",
    "\n",
    "```bash\n",
    "pip uninstall requests\n",
    "```\n",
    "\n",
    "`pip uninstall` removes a package but **does not** remove its\n",
    "dependencies. If you installed `requests` and it pulled in\n",
    "`urllib3`, `charset-normalizer`, etc., those stay behind. For\n",
    "proper cleanup, the cleanest answer is to recreate the\n",
    "environment from scratch — which is exactly what virtual\n",
    "environments make easy."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Recap and next steps\n",
    "\n",
    "- PyPI is the default source; `pip install <name>` is the entry point.\n",
    "- Pin versions for reproducibility; capture them in `requirements.txt`.\n",
    "- `pip list`, `pip show`, `pip freeze` for inspection; `pip uninstall`\n",
    "  for removal (but it doesn't tidy up dependencies).\n",
    "\n",
    "Before you run another `pip install`, you'll want a place to put it\n",
    "that doesn't pollute your system Python — that's\n",
    "[Virtual environments](https://agilearn.co.uk/guides/packages-and-packaging/learn/04-virtual-environments)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}