# uv

# uv: Python Environment & Package Manager

A single Rust binary that replaces `pip`, `venv`, `pyenv`, `pipx`, and `poetry`. It guarantees the right Python version and packages are present before running your code, so you stop managing virtual environments by hand.

**Use it when:** you want edit-and-run Python scripts (scrapers, automation, data jobs) without venv setup, manual activation, or a compile step.

---

## Mental Model

uv sits between you and Python. Three modes, each storing its environment differently:

| Mode | When to use | Where the env lives |
|------|-------------|---------------------|
| **Inline script (PEP 723)** | One `.py` file, ad-hoc scripts | Ephemeral, in the global cache |
| **Project** | Multi-file app, reproducible builds | Visible `.venv/` in the project folder |
| **Tool install** | Installing a CLI globally (e.g. `ruff`) | Isolated, like `pipx` |

For a scraper / automation script, use **inline script** mode.

---

## Install

```bash
# Linux / macOS (recommended, no existing Python needed)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

Installs the `uv` binary to:
- Linux / macOS: `~/.local/bin`
- Windows: `%USERPROFILE%\.local\bin`

> Remember this path. cron and other minimal-environment runners do not include it in `PATH`.

Alternatives: `pipx install uv`, `pip install uv`, `brew install uv`.
Update later with `uv self update`.

---

## Inline Scripts (PEP 723)

Declare dependencies in a comment header at the top of the file. No `requirements.txt`, no project, no manual venv.

```python
# scraper.py
# /// script
# requires-python = ">=3.12"
# dependencies = ["httpx", "polars", "PyMySQL"]
# ///

import httpx, polars as pl, pymysql
# your code here
```

Run it:

```bash
uv run scraper.py
```

First run resolves, downloads, and builds a cached environment, then executes. Subsequent runs reuse the cache and start almost instantly. uv rebuilds the env only when the dependency list or Python version changes.

Add a dependency without hand-editing the header:

```bash
uv add --script scraper.py requests
```

Make the file directly executable:

```python
#!/usr/bin/env -S uv run --script
```
```bash
chmod +x scraper.py
./scraper.py
```

---

## Where the Cached venv Lives

Inline-script environments live in uv's **global cache**, not next to the file.

| OS | Default cache path |
|----|--------------------|
| Linux | `$XDG_CACHE_HOME/uv` or `~/.cache/uv` |
| macOS | `~/Library/Caches/uv` |
| Windows | `%LOCALAPPDATA%\uv\cache` |

Find it on any machine:

```bash
uv cache dir
```

- Folder names are hashes of the Python version, dependency versions, and script name. Not human-readable.
- The cache is **deduplicated**: each package version is downloaded and built once, then shared across all scripts and projects on the machine.
- Override the location with the `UV_CACHE_DIR` environment variable.

Housekeeping:

```bash
uv cache clean    # wipe entire cache
uv cache prune    # remove stale entries only
du -sh "$(uv cache dir)"   # check size (Linux/macOS)
```

For **project** mode, the env is instead a normal `.venv/` in the project root, which you can inspect or delete directly.

---

## Reproducibility (Pin Versions)

Unpinned dependencies will silently upgrade when a new release drops. For anything unattended, lock the versions:

```bash
uv lock --script scraper.py   # creates scraper.py.lock
```

Once locked, `uv run`, `uv add --script`, and `uv tree --script` reuse the pinned versions.

For a collection of related scripts you want to rebuild on another machine, use a **project** instead: a folder with `pyproject.toml` + `uv.lock`, then `uv sync` on the new machine reproduces the exact Python version and dependencies.

---

## Running in Cron

It works. Two things bite people, both about the environment, not uv.

### 1. PATH

cron uses a stripped-down `PATH` (typically `/usr/bin:/bin`) that excludes `~/.local/bin`, so a bare `uv` fails with "command not found". Use the absolute path (find it with `which uv`).

### 2. HOME / cache

uv needs `HOME` set to locate `~/.cache/uv`. User crontabs usually set this correctly. System crontabs, containers, and service accounts may not, so set it explicitly.

```cron
# crontab -e
HOME=/home/youruser
UV_CACHE_DIR=/home/youruser/.cache/uv

*/15 * * * * /home/youruser/.local/bin/uv run /home/youruser/scrapers/scraper.py >> /home/youruser/logs/scraper.log 2>&1
```

Always redirect stdout and stderr to a log file, or failures vanish silently.

### Cron checklist

- [ ] Pin dependencies (`uv lock --script`) so a 3am package update cannot break the job.
- [ ] Warm the cache once by hand; the first run does the slow install work.
- [ ] Test the exact command in a minimal shell before scheduling:
  ```bash
  env -i HOME=/home/youruser /home/youruser/.local/bin/uv run scraper.py
  ```

---

## Command Cheat Sheet

| Command | Does |
|---------|------|
| `uv run script.py` | Run a script (project or inline) |
| `uv add --script script.py pkg` | Add a dependency to an inline script |
| `uv lock --script script.py` | Pin versions for an inline script |
| `uv init` | Create a new project |
| `uv add pkg` | Add a dependency to a project |
| `uv sync` | Rebuild a project env from the lockfile |
| `uv python install 3.12` | Install a specific Python version |
| `uv tool install ruff` | Install a CLI tool globally (pipx-style) |
| `uv cache dir` | Show the cache location |
| `uv cache clean` | Wipe the cache |
| `uv self update` | Update uv itself |

---

## Gotchas

- **MariaDB driver:** avoid the official `mariadb` PyPI package; it builds a C extension and needs MariaDB Connector/C installed system-wide. Use **PyMySQL** (pure Python, no compile) or **SQLAlchemy + PyMySQL** instead.
- **Unpinned scripts upgrade silently.** Lock anything that must not break unattended.
- **cron PATH/HOME** as above.
- **Cache and Python env should be on the same filesystem** for fast hard-linking; otherwise uv falls back to slow copies.

---

## Sources

- uv official docs: <https://docs.astral.sh/uv> (storage, cache, running scripts, CLI reference)
- PEP 723 (inline script metadata): <https://peps.python.org/pep-0723/>
- `uv-cache` man page (platform cache paths)

_Last reviewed: June 2026. Verify install URLs and cache behavior against the current docs if uv has had a major version bump._