docs

A small CLI that bootstraps and manages Python projects. One binary, no Python prerequisite — pyr downloads a standalone CPython into ~/.pyr/python on first use and creates a project-local .venv from it.

pyr init myapp        # scaffold a project (downloads python on first run)
cd myapp
pyr run               # python app/main.py
pyr add httpx         # add a dep, resolve, lock, install
pyr remove httpx      # the obvious
pyr sync              # reconcile venv + lock with pyproject.toml
pyr upgrade           # update pyr itself
pyr upgrade --python  # update the managed cpython
pyr upgrade --python 3.14.7+20260901  # install one exact upstream build

Install

macOS / Linux

curl -fsSL https://pyrun.dev/install.sh | sh

The script drops pyr into ~/.pyr/bin/. Add that to your PATH — the installer prints the exact line for your shell.

WSL counts as Linux: install inside WSL and leave PYR_HOME at its WSL path. Do not share that runtime or a project .venv with native Windows Pyr; the executables and venv layouts are platform-specific.

Windows

In PowerShell:

irm https://pyrun.dev/install.ps1 | iex

This installs pyr.exe into %USERPROFILE%\.pyr\bin\. Add that directory to your user PATH if it isn't already — the installer prints the exact command. On Windows ARM64, it uses the native ARM64 ZIP when the latest release has one; older releases without that asset fall back to the x86_64 ZIP under Windows emulation and print a warning.

Git Bash counts as native Windows. Use install.ps1 from PowerShell rather than piping install.sh to Bash, then restart Git Bash to pick up the Windows user PATH. Native Pyr prefers %USERPROFILE% over Git Bash's $HOME; use an explicit Windows-native PYR_HOME to override it.

Or, manually: download the latest pyr-windows-x86_64.zip (or pyr-windows-aarch64.zip on ARM64) from releases, unzip it, and put pyr.exe somewhere on your PATH.

PYR_HOME overrides where pyr keeps its state and binary; defaults to ~/.pyr (or %USERPROFILE%\.pyr on Windows).

The released executable does not depend on Deno, fnm, Node, npm, or JSR. Its compile graph is local source plus Deno/Node built-ins; the separate website toolchain is not part of the release artifact.

How dependencies work

pyproject.toml [project].dependencies is the source of truth. You write what you want there — either by hand, or via pyr add/pyr remove. requirements.txt is a generated lock file (flat, alpha-sorted, fully pinned, with a # generated by pyr header), produced by pyr sync.

pyproject.toml         requirements.txt
[project]              # generated by pyr 0.1.0; do not edit
dependencies = [       anyio==4.3.0
    "httpx[http2]",    certifi==2024.2.2
]                      h11==0.14.0
                       h2==4.1.0
                       httpcore==1.0.5
                       httpx==0.27.0

pyr add httpx and pyr remove httpx both edit pyproject.toml then call pyr sync under the hood. pyr sync resolves the top-level deps via pip, prunes anything no longer needed, and rewrites the lock. pyr run auto-syncs if pyproject.toml is newer than the lock — so hand-edits to [project].dependencies followed by pyr run Just Work.

Commands

command what it does
pyr init [name] Scaffold a project in ./<name>/ (or cwd). Refuses existing content, $HOME, roots.
pyr run [-- args] Run app/main.py in the venv with PYTHONPATH=.. Args after -- go to python.
pyr add <pkg>... Add packages to pyproject.toml [project].dependencies, then sync.
pyr remove <pkg>... Remove packages from pyproject.toml, then sync.
pyr sync Reconcile the venv and lock with pyproject.toml. Idempotent.
pyr upgrade Update the pyr binary from the latest GitHub release.
pyr upgrade --python [VERSION] Update managed CPython, optionally pinning X.Y.Z[+BUILD].
pyr help [cmd] Top-level usage, or per-command details.

pyr <cmd> --help and pyr help <cmd> are equivalent. To pass a literal --help (or any flag starting with -) to python via pyr run, separate with --: pyr run -- --help.

Project layout

After pyr init myapp:

myapp/
  pyproject.toml         # [project] metadata; you edit dependencies here
  requirements.txt       # generated lock; pyr owns this file
  .gitignore             # python defaults
  app/
    __init__.py
    config.py            # ENV from os.getenv
    main.py              # the entrypoint pyr run executes
  .venv/                 # the project's python environment

How upgrades propagate

pyr upgrade --python checks release metadata once. If the installed CPython version and exact upstream build both match and run successfully, no archive is downloaded. Otherwise pyr downloads and extracts into a temporary directory beside the live runtime, checks the interpreter's version and venv tooling, and then replaces ~/.pyr/python. Older installs without an exact-build stamp refresh once on their next explicit Python upgrade. Download, extraction, and verification failures leave the existing tree in place; if moving the replacement fails, pyr restores the previous tree.

pyr upgrade --python X.Y.Z accepts only that interpreter version from the current upstream release and fails rather than selecting another version. A build-qualified pin such as 3.14.7+20260901 resolves that exact python-build-standalone release and asset, making it the preferred form for a reproducible runtime manifest. The selected release's SHA256SUMS entry is required and verified before extraction.

Only one Python installer may run per PYR_HOME at a time. An interrupted process can leave .python-install-lock and a .python-install-* directory there. Confirm no installer is running before removing a stale lock. If the live python directory is absent and staging contains previous, restore previous to python first. If rollback itself fails, pyr prints the retained previous directory for recovery. This is failure recovery, not a guarantee against power loss during the two renames.

The next time you run pyr run (or any command that touches the venv) in any project, pyr notices the version stamp in .venv/.pyr-python no longer matches and silently rebuilds the venv from the new managed python, reinstalling everything from requirements.txt.

pyr upgrade (no flags) updates the pyr binary itself. The downloaded zip is extracted to a temp dir, size-checked, exec'd to confirm it reports the expected version, then renamed over the running binary.

Requirements

  • macOS / Linux: any reasonably recent x86_64 or aarch64 system. tar and unzip must be on PATH (both are basically always present).
  • Windows 10 1803+: needed for the bundled tar.exe in System32 (used to extract release zips during self-upgrade).