# Python Development Windows Native Setup Adding Python development on top of a Windows 11 machine already configured per [[General_Development_Windows_Native_Setup]]. Most of the stack carries over — Windows Terminal, PowerShell 7, Starship, Git, GitHub CLI, VS Code, Claude Code, and the PSReadLine setup all work identically. This guide only covers the Python-specific additions. It's the Windows-native counterpart to [[Python_Development_Mac_Tahoe_Setup]] and [[Python_Development_Ubuntu_Setup]], and everything here installs the same way on **ARM64 (Snapdragon, Surface Pro 11, Dev Kit 2023) and x64 (Intel/AMD)** — with one important ARM64 caveat for `uv python install` covered below. > [!tip] AI assistance is optional > The reference to Claude Code above assumes you completed Part 7 of the General guide. If you skipped it (which is fine) or use a different AI tool (Copilot, Cursor, Continue, etc.), this guide's instructions still work — the language toolchain doesn't depend on any AI being present. > [!info] Coexistence > C, Python, Ruby, Go, and Rust can all live on the same Windows system without interfering. Each language's toolchain installs to its own prefix: `uv` under `%LOCALAPPDATA%\uv\` and `%USERPROFILE%\.local\bin\`, Rust under `%USERPROFILE%\.rustup\` and `%USERPROFILE%\.cargo\`, Go under `%USERPROFILE%\go\`, Ruby under its own MSYS2-managed tree, MSVC under Visual Studio's prefix. No conflicts. --- ## Why `uv`? `uv` is Astral's unified Python toolchain. It replaces **pyenv-win + venv + pip + pip-tools + pipx** with a single fast binary and has essentially become the default for new Python projects in 2026. Written in Rust, 10–100× faster than pip for most operations. | Use case | Pre-2024 tool(s) | With uv | |----------|------------------|---------| | Install a Python version | pyenv-win | `uv python install 3.13` | | Create a virtualenv | `python -m venv` | `uv venv` (auto on `uv init`) | | Install dependencies | pip + requirements.txt | `uv add <pkg>` → `pyproject.toml` + lockfile | | Global CLI tools | pipx | `uv tool install <pkg>` | | Lock dependencies | pip-tools | built-in (`uv.lock`) | | Run scripts | `python foo.py` | `uv run foo.py` | > [!info] Why not the python.org installer or the Microsoft Store Python? > Both will work, but they each have rough edges on Windows. The python.org installer doesn't manage multiple versions cleanly — you end up juggling PATH order between 3.12 and 3.13. The Microsoft Store Python is sandboxed in a way that confuses pip and some IDEs. `uv` sidesteps all of this by managing its own Python builds in a private prefix — nothing you do touches a system interpreter, and switching versions per-project is a single line in `.python-version`. --- ## Install uv `uv` ships native binaries for both Windows ARM64 and x64. The official PowerShell installer detects your architecture automatically: ```powershell powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" ``` Or, equivalently, via winget: ```powershell winget install --id=astral-sh.uv -e ``` Both methods install to `%USERPROFILE%\.local\bin\uv.exe` (already on your PATH from the General guide, which prepends `$HOME\.local\bin`). Open a new PowerShell window or reload your profile with `. $PROFILE`, then verify: ```powershell uv --version ``` ### Install Python versions `uv` manages Python installations independently of anything Windows already had. Multiple versions coexist: ```powershell uv python install 3.13 uv python install 3.12 uv python install 3.11 uv python list ``` These install to `%LOCALAPPDATA%\uv\python\` — nothing touches the python.org installer's directories or the Microsoft Store Python. The downloads are Astral's `python-build-standalone` builds. > [!warning] ARM64: force a native aarch64 build > On **Windows ARM64 hosts**, `uv python install 3.13` historically resolves to an **x86_64 CPython** binary, which then runs under Windows' x64 emulation. This works but adds startup overhead and prevents linking against native ARM64 wheels. To force a native aarch64 install: > > ```powershell > uv python install 3.13 --python-platform aarch64-pc-windows-msvc > ``` > > Confirm the architecture after install: > > ```powershell > uv run python -c "import platform; print(platform.machine())" > # Should print: ARM64 (not AMD64 — that would mean you got the x64 build under emulation) > ``` > > See <https://pydevtools.com/handbook/how-to/how-to-use-uv-on-windows-arm64/> for background. On x64 hosts no flag is needed — the default download is correct. ### Global Python CLI tools `uv tool` is the `pipx` replacement. Install commonly-used CLIs globally: ```powershell uv tool install ruff uv tool install ipython uv tool install mypy ``` These land in `%USERPROFILE%\.local\bin\` (already on your PATH from the General guide's `$PROFILE` block) and are callable by name anywhere. --- ## VS Code extensions for Python ```powershell # Core Python support — language server, debugger, formatter integration code --install-extension ms-python.python code --install-extension ms-python.vscode-pylance code --install-extension ms-python.debugpy # Ruff (linter + formatter, matches the CLI you installed with uv) code --install-extension charliermarsh.ruff # Jupyter notebooks (optional) code --install-extension ms-toolsai.jupyter code --install-extension ms-toolsai.jupyter-renderers ``` --- ## VS Code settings Append these language-specific settings to your existing `settings.json` at `%APPDATA%\Code\User\settings.json`. The general settings from [[General_Development_Windows_Native_Setup]] stay in place; these merge with them: ```json { // ── Python ──────────────────────────────────────────────── "python.defaultInterpreterPath": ".venv\\Scripts\\python.exe", "python.terminal.activateEnvInCurrentTerminal": true, "python.terminal.activateEnvironment": true, "python.analysis.typeCheckingMode": "basic", "python.analysis.autoImportCompletions": true, "python.analysis.inlayHints.functionReturnTypes": true, "python.analysis.inlayHints.variableTypes": false, "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnSave": true, "editor.tabSize": 4, "editor.rulers": [88, 120], "editor.codeActionsOnSave": { "source.fixAll.ruff": "explicit", "source.organizeImports.ruff": "explicit" } }, "ruff.nativeServer": "on", "ruff.lineLength": 88, // Hide Python clutter from the file explorer "files.exclude": { "**/__pycache__": true, "**/*.pyc": true, "**/.pytest_cache": true, "**/.ruff_cache": true, "**/.mypy_cache": true }, "search.exclude": { "**/.venv": true, "**/uv.lock": true } } ``` > [!info] `python.defaultInterpreterPath` > Pointing at `.venv\Scripts\python.exe` (note the Windows-style path — `Scripts\python.exe`, **not** `bin/python` like on macOS/Linux) means any project made with `uv init` "just works" — VS Code finds the venv, activates it in new terminals, and uses it for Pylance type-checking. No manual interpreter selection per-project. > [!info] Why double backslashes in the JSON? > JSON treats `\` as an escape character, so a literal backslash in a Windows path has to be written `\\`. Forward slashes also work (`.venv/Scripts/python.exe`) and many people prefer them for readability; either form is correct. --- ## Full demo: A calculator program This walks through building, testing, debugging, and publishing a four-function calculator from scratch. Parallel implementations exist in C, Ruby, Go, and Rust. ### Create the project ```powershell cd $HOME\projects uv init calc-python --python 3.13 cd calc-python ``` This creates: ``` calc-python\ ├── .python-version ├── .gitignore ├── README.md ├── main.py └── pyproject.toml ``` Remove the auto-generated `main.py` — we'll replace it with a proper project layout. ```powershell Remove-Item main.py New-Item -ItemType Directory -Path src\calc, tests -Force | Out-Null ``` ### `src\calc\__init__.py` — the core logic ```python """Four-function calculator.""" from __future__ import annotations def add(a: float, b: float) -> float: return a + b def sub(a: float, b: float) -> float: return a - b def mul(a: float, b: float) -> float: return a * b def div(a: float, b: float) -> float: if b == 0: raise ZeroDivisionError("division by zero") return a / b def calculate(a: float, op: str, b: float) -> float: """Dispatch to the appropriate operation based on op.""" match op: case "+": return add(a, b) case "-": return sub(a, b) case "*" | "x": return mul(a, b) case "/": return div(a, b) case _: raise ValueError(f"unknown operator {op!r}") ``` ### `src\calc\__main__.py` — the CLI entry point ```python """Command-line entry point for the calculator.""" from __future__ import annotations import sys from calc import calculate def usage(prog: str) -> None: print(f"Usage: {prog} <number> <op> <number>", file=sys.stderr) print(" op: + - * /", file=sys.stderr) print(f"Example: {prog} 6 + 7", file=sys.stderr) def main(argv: list[str] | None = None) -> int: argv = argv if argv is not None else sys.argv if len(argv) != 4: usage(argv[0]) return 1 try: a = float(argv[1]) b = float(argv[3]) except ValueError as e: print(f"Error: {e}", file=sys.stderr) return 1 try: result = calculate(a, argv[2], b) except (ValueError, ZeroDivisionError) as e: print(f"Error: {e}", file=sys.stderr) return 1 # Print as integer if it came out whole print(int(result) if result == int(result) else result) return 0 if __name__ == "__main__": raise SystemExit(main()) ``` ### `tests\test_calc.py` — unit tests with pytest ```python """Tests for the four-function calculator.""" from __future__ import annotations import pytest from calc import calculate class TestCalculate: def test_add(self): assert calculate(6, "+", 7) == 13 def test_sub(self): assert calculate(10, "-", 3) == 7 def test_mul_star(self): assert calculate(4, "*", 5) == 20 def test_mul_x(self): assert calculate(4, "x", 5) == 20 def test_div(self): assert calculate(20, "/", 4) == 5 def test_div_by_zero(self): with pytest.raises(ZeroDivisionError): calculate(5, "/", 0) def test_unknown_operator(self): with pytest.raises(ValueError, match="unknown operator"): calculate(1, "?", 2) @pytest.mark.parametrize( "a, op, b, expected", [ (1, "+", 1, 2), (0, "+", 0, 0), (-1, "+", 1, 0), (1.5, "+", 2.5, 4.0), (10, "-", 20, -10), (3, "*", 4, 12), (1, "/", 3, pytest.approx(0.3333, rel=1e-3)), ], ) def test_operations_parametrized(a, op, b, expected): assert calculate(a, op, b) == expected ``` ### Update `pyproject.toml` Replace the generated `pyproject.toml` with: ```toml [project] name = "calc-python" version = "0.1.0" description = "Four-function command-line calculator" requires-python = ">=3.13" dependencies = [] [project.scripts] calc = "calc.__main__:main" [dependency-groups] dev = [ "pytest>=8.0", "pytest-cov>=5.0", "mypy>=1.10", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/calc"] [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["src"] addopts = "-v --cov=calc --cov-report=term-missing" [tool.ruff] line-length = 88 target-version = "py313" [tool.ruff.lint] select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"] ``` > [!info] Forward slashes in `pyproject.toml` > Even on Windows, `packages = ["src/calc"]` uses forward slashes — this is a TOML/Python convention, not a filesystem path. Hatchling translates it correctly on every platform. ### Install dev dependencies ```powershell uv sync --all-groups ``` This creates `.venv\`, installs `pytest`, `pytest-cov`, `mypy`, and the project itself in editable mode. > [!tip] Activating the venv (rarely needed) > `uv run` runs commands inside the project's venv without activation, which is the modern workflow and what this guide uses throughout. If you ever want to activate it explicitly in PowerShell (e.g., to run `python` interactively): > > ```powershell > . .venv\Scripts\Activate.ps1 > ``` > > Note the leading dot — that *sources* the script into your current session. Without it, the script runs in a child process and the activation evaporates the moment it exits. To deactivate: `deactivate`. ### Run everything ```powershell # Run the CLI uv run calc 6 + 7 uv run calc 10 - 3 uv run calc 4 x 5 uv run calc 20 / 4 # Or run the module directly uv run python -m calc 6 + 7 # Run tests uv run pytest # Type check uv run mypy src/ # Lint and format (using the global ruff) ruff check src/ tests/ ruff format src/ tests/ # Or fix issues automatically ruff check --fix src/ tests/ ``` > [!info] `*` is not a wildcard in PowerShell command arguments > On Linux/Mac shells, `uv run calc 4 * 5` would have the shell expand `*` to filenames in the current directory before the program ever sees it. PowerShell **doesn't** do glob expansion on argv by default — programs receive the literal `*`. The calculator's `4 * 5` example works in PowerShell exactly as written. (If you ever notice argv expansion happening, it's because the program you're calling is doing its own globbing.) ### Debug in VS Code Create `.vscode\launch.json`: ```json { "version": "0.2.0", "configurations": [ { "name": "Debug calc CLI", "type": "debugpy", "request": "launch", "module": "calc", "args": ["6", "+", "7"], "console": "integratedTerminal", "env": { "PYTHONPATH": "${workspaceFolder}/src" } }, { "name": "Debug pytest", "type": "debugpy", "request": "launch", "module": "pytest", "args": ["-v"], "console": "integratedTerminal", "env": { "PYTHONPATH": "${workspaceFolder}/src" } } ] } ``` > [!info] `${workspaceFolder}` and forward slashes > Even on Windows, VS Code's `launch.json` accepts forward slashes inside `${workspaceFolder}/src` — VS Code normalizes them. Use them for portability across machines. ### Publish to GitHub Same workflow as any project (see [[General_Development_Windows_Native_Setup#Publishing a repo to GitHub]]): ```powershell git init git add . git commit -m "Initial commit: four-function calculator" gh repo create calc-python --private --source=. --remote=origin --push ``` ### `CLAUDE.md` for this project ```markdown # Project: calc-python ## Purpose Four-function command-line calculator in Python — pedagogical example. ## Conventions - Python 3.13 via uv (.python-version pins it) - src/ layout with package `calc` - Tests in tests/ with pytest - Format and lint with Ruff (config in pyproject.toml) - Type hints on all public functions ## Commands - Install deps: `uv sync --all-groups` - Run: `uv run calc <a> <op> <b>` - Test: `uv run pytest` - Test with coverage: `uv run pytest --cov` - Type check: `uv run mypy src/` - Format: `ruff format src/ tests/` - Lint: `ruff check --fix src/ tests/` ## Style - 4-space indent, 88-column line length (Black/Ruff default) - PEP 8 naming: snake_case functions/variables, CamelCase classes - Type hints required on public APIs - `from __future__ import annotations` at top of every module ``` --- ## Full demo: A calculator GUI Same calculator, wrapped in a graphical interface. This uses **Tkinter**, Python's standard GUI toolkit. ### Tkinter on Windows uv's `python-build-standalone` interpreters bundle Tk on both architectures, so **if you installed Python via `uv python install` (above), `import tkinter` just works** — nothing extra to install. > [!warning] python.org's ARM64 installer does NOT bundle Tk > If you instead installed Python from python.org's **ARM64** installer rather than uv, you'll hit `ModuleNotFoundError: No module named '_tkinter'` the moment you import `tkinter`. python.org's ARM64 builds intentionally ship without Tk. Three ways out: > > 1. **Recommended — switch to uv-managed Python** for this project: `uv python install 3.13 --python-platform aarch64-pc-windows-msvc` and re-run `uv sync`. uv's bundled Python includes Tk. > 2. **Use the x64 python.org build**, which does include Tk. It runs under Windows' transparent x64 emulation on ARM64 — slower, but functional for a GUI demo. > 3. **Install Tk via conda-forge** (`conda install -c conda-forge tk python`) if you already use Miniforge/Mambaforge. > > The x64 python.org installer does include Tk, so on **x64 hosts** all three install paths (uv, python.org, Microsoft Store) work for Tkinter. The ARM64-specific gotcha only affects python.org's ARM64 build. > [!info] Why Tkinter? > It ships with Python (via uv on Windows), zero extra dependencies, works on all platforms, and renders acceptably with the native Windows theme. For a pedagogical calculator it's the right default. Alternatives (PySide6, Flet) are noted at the end. ### Add the GUI module Create `src\calc\gui.py`: ```python """Tkinter GUI for the four-function calculator.""" from __future__ import annotations import tkinter as tk from tkinter import ttk from calc import calculate class CalculatorApp: """A simple four-function calculator GUI.""" BUTTON_GRID = [ ["7", "8", "9", "/"], ["4", "5", "6", "*"], ["1", "2", "3", "-"], ["0", ".", "=", "+"], ] def __init__(self, root: tk.Tk) -> None: self.root = root self.root.title("Calculator") self.root.resizable(False, False) # Internal state self.current = "" self.stored: float | None = None self.pending_op: str | None = None self._build_ui() self._bind_keys() def _build_ui(self) -> None: style = ttk.Style() style.configure("Calc.TButton", font=("Segoe UI", 16), padding=10) style.configure("CalcOp.TButton", font=("Segoe UI", 16, "bold"), padding=10) # Display self.display_var = tk.StringVar(value="0") display = ttk.Entry( self.root, textvariable=self.display_var, justify="right", font=("Cascadia Mono", 24), state="readonly", ) display.grid(row=0, column=0, columnspan=4, sticky="ew", padx=8, pady=8) # Button grid for row_idx, row in enumerate(self.BUTTON_GRID, start=1): for col_idx, label in enumerate(row): style_name = ( "CalcOp.TButton" if label in {"+", "-", "*", "/", "="} else "Calc.TButton" ) btn = ttk.Button( self.root, text=label, style=style_name, command=lambda lbl=label: self._on_press(lbl), ) btn.grid(row=row_idx, column=col_idx, sticky="nsew", padx=2, pady=2) # Clear button spans the bottom clear_btn = ttk.Button(self.root, text="Clear", command=self._clear) clear_btn.grid(row=5, column=0, columnspan=4, sticky="ew", padx=8, pady=8) # Make columns expand evenly for col in range(4): self.root.columnconfigure(col, weight=1, minsize=60) def _bind_keys(self) -> None: for key in "0123456789.": self.root.bind(key, lambda e, k=key: self._on_press(k)) for key in ["+", "-", "*", "/"]: self.root.bind(key, lambda e, k=key: self._on_press(k)) self.root.bind("<Return>", lambda e: self._on_press("=")) self.root.bind("=", lambda e: self._on_press("=")) self.root.bind("<Escape>", lambda e: self._clear()) self.root.bind("<BackSpace>", lambda e: self._backspace()) def _on_press(self, label: str) -> None: if label.isdigit() or label == ".": self.current += label self.display_var.set(self.current or "0") elif label in {"+", "-", "*", "/"}: self._apply_pending() self.pending_op = label self.current = "" elif label == "=": self._apply_pending() self.pending_op = None def _apply_pending(self) -> None: if not self.current: return try: value = float(self.current) except ValueError: self.display_var.set("Error") self._reset_state() return if self.stored is None or self.pending_op is None: self.stored = value else: try: self.stored = calculate(self.stored, self.pending_op, value) except (ZeroDivisionError, ValueError): self.display_var.set("Error") self._reset_state() return # Display whole numbers as integers display_val = int(self.stored) if self.stored == int(self.stored) else self.stored self.display_var.set(str(display_val)) self.current = "" def _reset_state(self) -> None: self.current = "" self.stored = None self.pending_op = None def _clear(self) -> None: self._reset_state() self.display_var.set("0") def _backspace(self) -> None: self.current = self.current[:-1] self.display_var.set(self.current or "0") def main() -> None: root = tk.Tk() CalculatorApp(root) root.mainloop() if __name__ == "__main__": main() ``` > [!info] Segoe UI and Cascadia Mono > The fonts are swapped from the Linux version's `DejaVu` family to Windows defaults — **Segoe UI** for the button labels (the Windows system font, present on every Windows install) and **Cascadia Mono** for the display (Microsoft's modern monospace, shipped with Windows Terminal and present on Windows 11 by default). Tk will fall back gracefully on systems missing these fonts. ### Add a GUI entry point to `pyproject.toml` ```toml [project.scripts] calc = "calc.__main__:main" calc-gui = "calc.gui:main" ``` Re-sync to pick up the new script: ```powershell uv sync --all-groups ``` ### Tests for the GUI state machine GUI event handling is hard to test directly, but the state machine (`_apply_pending`, `_on_press`) is pure logic and easily unit-tested. Create `tests\test_gui.py`: ```python """Tests for the calculator GUI's state machine.""" from __future__ import annotations import tkinter as tk import pytest from calc.gui import CalculatorApp @pytest.fixture def app(): """Create a CalculatorApp with a real but invisible Tk root.""" root = tk.Tk() root.withdraw() app = CalculatorApp(root) yield app root.destroy() class TestCalculatorState: def test_initial_display(self, app): assert app.display_var.get() == "0" def test_single_digit(self, app): app._on_press("5") assert app.display_var.get() == "5" def test_multi_digit(self, app): for ch in "123": app._on_press(ch) assert app.display_var.get() == "123" def test_simple_addition(self, app): app._on_press("6") app._on_press("+") app._on_press("7") app._on_press("=") assert app.display_var.get() == "13" def test_chained_operations(self, app): # 2 + 3 * 4 evaluated left-to-right → (2+3)*4 = 20 for ch in ["2", "+", "3", "*", "4", "="]: app._on_press(ch) assert app.display_var.get() == "20" def test_division(self, app): for ch in ["2", "0", "/", "4", "="]: app._on_press(ch) assert app.display_var.get() == "5" def test_division_by_zero_shows_error(self, app): for ch in ["5", "/", "0", "="]: app._on_press(ch) assert app.display_var.get() == "Error" def test_clear_resets_state(self, app): for ch in ["5", "+", "3"]: app._on_press(ch) app._clear() assert app.display_var.get() == "0" assert app.current == "" assert app.stored is None assert app.pending_op is None def test_decimal_input(self, app): for ch in "1.5": app._on_press(ch) app._on_press("+") for ch in "2.5": app._on_press(ch) app._on_press("=") assert app.display_var.get() == "4" ``` ### Run the GUI ```powershell uv run calc-gui ``` A small calculator window opens. Click buttons or use the keyboard — digits, `+`, `-`, `*`, `/`, `Enter` (for `=`), `Esc` (to clear), and `Backspace` (to delete the last digit). ### Run all tests (CLI + GUI) ```powershell uv run pytest ``` The `CalculatorApp` tests use a hidden Tk root (`root.withdraw()`) so no windows pop up during test runs. > [!info] Headless testing on Windows > Unlike Linux (where you need `xvfb` for headless GUI tests), Windows' graphics stack provides a default display context for every desktop session, including over RDP. Tk tests run cleanly in any normal user session. For unattended CI on a headless Windows runner without an interactive session, GitHub Actions' `windows-latest` image already has Tk working under the runner's session — no special configuration needed. The `root.withdraw()` call keeps test windows from flashing on screen. --- ## Alternative GUI libraries If Tkinter's default look bothers you, here are two drop-in alternatives: ### PySide6 (Qt for Python, native-looking widgets) ```powershell uv add pyside6 ``` Good for polished applications. Ships with Qt Designer for drag-and-drop UI layout. Larger install (~200 MB). Wheels available for both Windows ARM64 and x64. ### Flet (Flutter-powered, modern declarative UI) ```powershell uv add flet ``` Write UIs in pure Python with Flutter widgets. Produces web, desktop, and mobile targets from the same code. For pedagogical work, Tkinter's zero-dependency "it just works" wins. --- ## Starship prompt — Python auto-detection The general Starship config you set up already includes the `[python]` module. When you `cd` into this project, the prompt shows: ``` ~\projects\calc-python main 3.13.2 (calc-python) ❯ ``` Reading left-to-right: directory, git branch, Python version, active venv name. --- ## direnv — auto-activate this project's environment direnv can load this project's `.venv` automatically when you `cd` in, so a bare `python`, `pytest`, or `ruff` uses it without the `uv run` prefix (see [[General_Development_Windows_Native_Setup]] for the PowerShell hook). ```bash # from the project root, after `uv sync` has created .venv\ cat > .envrc <<'EOF' export VIRTUAL_ENV="$PWD/.venv" PATH_add "$VIRTUAL_ENV/Scripts" EOF direnv allow ``` > [!warning] Two things to get right on Windows > 1. **`.envrc` is bash.** direnv runs it through bash and applies the result to PowerShell, so write bash syntax and keep Git for Windows (Git Bash) on `PATH`. Forward slashes are fine — but note the venv lives in `.venv/Scripts` on Windows, not `.venv/bin`. > 2. **Create the venv first.** `uv init` doesn't create `.venv\` — only `uv sync` (or the first `uv run`) does. Run `uv sync` before `direnv allow`, or `python` won't resolve to the venv. `uv run` already uses `.venv` regardless of direnv; this just extends that to tools you invoke directly. --- ## Troubleshooting > [!warning] `python: command not found` after installing via uv > `uv` doesn't put a `python` shim on your PATH globally — it uses `uv run python` inside projects. For a global `python` command, either add a function to `$PROFILE`: > > ```powershell > function python { uv run python @args } > ``` > > or use `uv python install 3.13 --default`, which installs a shim at `%USERPROFILE%\.local\bin\python.exe`. > [!warning] `ModuleNotFoundError: No module named 'tkinter'` on ARM64 > You're using a Python that didn't ship with Tk — most likely the python.org ARM64 installer. Switch to uv-managed Python: `uv python install 3.13 --python-platform aarch64-pc-windows-msvc`, then `uv sync` to rebuild the venv. See the Tkinter section above for the alternatives. > [!warning] `uv python install 3.13` gives me x86_64 on an ARM64 machine > This is the known ARM64 default-resolution issue. Force the native build with `uv python install 3.13 --python-platform aarch64-pc-windows-msvc`. Confirm with `uv run python -c "import platform; print(platform.machine())"` — it should print `ARM64`. > [!warning] VS Code doesn't find the `.venv\` > Open the workspace from a terminal (`code .` from Windows Terminal), not from File Explorer — VS Code inherits the shell's environment. Or manually select the interpreter: `Ctrl+Shift+P` → "Python: Select Interpreter" → browse to `.venv\Scripts\python.exe`. > [!warning] `ModuleNotFoundError: No module named 'calc'` when running tests > The `src\` layout requires the package path to be set. The provided `pyproject.toml` includes `pythonpath = ["src"]` under `[tool.pytest.ini_options]` — make sure this line is present. > [!warning] `Activate.ps1` won't run — "running scripts is disabled on this system" > PowerShell's default execution policy blocks unsigned local scripts. Either use `uv run` (which doesn't need activation), or relax the policy for your user only: > > ```powershell > Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned > ``` > > This allows local scripts and signed remote scripts; it's the standard developer setting on Windows. > [!warning] `pip install` inside an activated venv suddenly hangs or is slow > Don't use `pip` directly — `uv pip install` is a drop-in replacement that's 10–100× faster and uses the same wheel index. Better still, use `uv add` to update `pyproject.toml` and the lockfile in one step. > [!warning] Long path errors on `uv sync` (`The filename or extension is too long`) > Windows' historical 260-character path limit can bite deep dependency trees. Enable long paths: > > ```powershell > # Elevated PowerShell, one time per machine > New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" ` > -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force > ``` > > Plus enable `git config --global core.longpaths true`. Reboot after. > [!warning] `Defender` slows down `uv sync` on first install > Windows Defender real-time scanning hits hard when uv unpacks hundreds of wheels. If you trust the path, add an exclusion in Windows Security → Virus & threat protection → Manage settings → Exclusions: `%LOCALAPPDATA%\uv\` and your `%USERPROFILE%\projects\` folder. Typical speedup: 2–4×. --- ## Summary — the one-shot Python addition > [!warning] This is a checklist, not a script > Copy and paste one block at a time. One step in the middle is a **manual file-paste step** (append the Python settings block to `%APPDATA%\Code\User\settings.json`) — do that before the verify step at the end will reflect your editor config. ```powershell # Python toolchain (architecture-aware installer) powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" # ...or, equivalently: # winget install --id=astral-sh.uv -e # Reload PowerShell profile (or open a new tab) . $PROFILE # Install Python versions # On x64: uv python install 3.13 3.12 # On ARM64, force the native aarch64 build: # uv python install 3.13 --python-platform aarch64-pc-windows-msvc # uv python install 3.12 --python-platform aarch64-pc-windows-msvc # Global CLI tools uv tool install ruff uv tool install ipython uv tool install mypy # (Tk libraries ship with uv-managed Python on both architectures — nothing to install) # VS Code extensions code --install-extension ms-python.python code --install-extension ms-python.vscode-pylance code --install-extension ms-python.debugpy code --install-extension charliermarsh.ruff code --install-extension ms-toolsai.jupyter # Paste the Python block into %APPDATA%\Code\User\settings.json # Verify uv --version; uv python list; ruff --version ``` About three minutes on top of the general setup. --- ## Related notes - [[General_Development_Windows_Native_Setup]] - [[Python_Development_Ubuntu_Setup]] — the Linux counterpart - [[Python_Development_Mac_Tahoe_Setup]] — the macOS counterpart - [[C_Development_Windows_Native_Setup]] - [[Ruby_Development_Windows_Native_Setup]] - [[Go_Development_Windows_Native_Setup]] - [[Rust_Development_Windows_Native_Setup]] - [uv Quick Reference](https://docs.astral.sh/uv/) - [uv on Windows ARM64 (PyDevTools)](https://pydevtools.com/handbook/how-to/how-to-use-uv-on-windows-arm64/) - [pytest Patterns](https://docs.pytest.org/en/stable/how-to/index.html)