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 buildInstall
macOS / Linux
curl -fsSL https://pyrun.dev/install.sh | shThe 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 | iexThis 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.0pyr 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 environmentHow 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.
tarandunzipmust be on PATH (both are basically always present). - Windows 10 1803+: needed for the bundled
tar.exeinSystem32(used to extract release zips during self-upgrade).