Skip to content

Commit 11158b3

Browse files
committed
feat: rename --min-python to --python, add --isolated flag
- --python replaces --min-python across install.sh, install.ps1, setup.py - Default mode passes --python-preference system to uv: prefer system Python, fall back to uv-managed if no match - --isolated passes --python-preference only-managed: always use a uv-managed Python, unconditionally adds bin/ to PATH in env files - distro.toml [install] section records python and isolated keys - README updated with revised option descriptions and mode guidance
1 parent 1d193ff commit 11158b3

4 files changed

Lines changed: 63 additions & 36 deletions

File tree

README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Expand-Archive managed-python.zip
4040
```bash
4141
./install.sh \
4242
--prefix ~/.local/redmatter/python \
43-
--min-python 3.10 \
43+
--python 3.10 \
4444
--uv-env REDMATTER_UV \
4545
--python-env REDMATTER_PYTHON
4646

@@ -52,7 +52,7 @@ source ~/.local/redmatter/python/env.sh
5252
```powershell
5353
.\install.ps1 `
5454
-Prefix "$env:USERPROFILE\.local\redmatter\python" `
55-
-MinPython "3.10" `
55+
-Python "3.10" `
5656
-UvEnv "REDMATTER_UV" `
5757
-PythonEnv "REDMATTER_PYTHON"
5858
@@ -66,14 +66,14 @@ source ~/.local/redmatter/python/env.sh
6666
> **PowerShell** — evaluate `env.ps1` as a string (bypasses script execution policy):
6767
>
6868
> ```powershell
69-
> install.bat -Prefix "$env:USERPROFILE\.local\redmatter\python" -MinPython "3.10" -UvEnv "REDMATTER_UV" -PythonEnv "REDMATTER_PYTHON"
69+
> install.bat -Prefix "$env:USERPROFILE\.local\redmatter\python" -Python "3.10" -UvEnv "REDMATTER_UV" -PythonEnv "REDMATTER_PYTHON"
7070
> Invoke-Expression (Get-Content "$env:USERPROFILE\.local\redmatter\python\env.ps1" -Raw)
7171
> ```
7272
>
7373
> **CMD** — use `call` to load `env.bat` into the current session:
7474
>
7575
> ```bat
76-
> install.bat -Prefix "%USERPROFILE%\.local\redmatter\python" -MinPython "3.10" -UvEnv "REDMATTER_UV" -PythonEnv "REDMATTER_PYTHON"
76+
> install.bat -Prefix "%USERPROFILE%\.local\redmatter\python" -Python "3.10" -UvEnv "REDMATTER_UV" -PythonEnv "REDMATTER_PYTHON"
7777
> call "%USERPROFILE%\.local\redmatter\python\env.bat"
7878
> ```
7979
>
@@ -85,7 +85,7 @@ source ~/.local/redmatter/python/env.sh
8585
```bash
8686
./install.sh \
8787
--prefix ~/.local/redmatter/python \
88-
--min-python 3.10 \
88+
--python 3.10 \
8989
--uv-env REDMATTER_UV \
9090
--python-env REDMATTER_PYTHON \
9191
--quiet
@@ -99,12 +99,16 @@ source ~/.local/redmatter/python/env.sh
9999
| Flag | Required | Purpose |
100100
|------|----------|---------|
101101
| `--prefix PATH` | yes | Install location |
102-
| `--min-python X.Y` | yes | Minimum Python version for venv |
102+
| `--python X.Y` | yes | Python version for venv. Default mode: prefer matching system Python, fall back to uv-managed. Isolated mode: always uv-managed. |
103103
| `--uv-env NAME` | yes | Env var name for the uv binary path |
104104
| `--python-env NAME` | yes | Env var name for the python binary path |
105+
| `--isolated` | no | Force uv-managed Python (ignores system Python); always adds `bin/` to PATH |
105106
| `--shell-profile` | no | Append `source <prefix>/env.sh` to shell rc |
106107
| `--quiet` / `-q` | no | Suppress all output except warnings |
107108

109+
> [!NOTE]
110+
> **Choosing a mode:** Use the default on developer machines where a system Python already exists. Use `--isolated` in CI, containers, or shared servers where you need a fully reproducible environment independent of whatever Python is (or isn't) installed on the host.
111+
108112
## Usage
109113

110114
```bash
@@ -152,10 +156,11 @@ uv_version = "0.10.6"
152156

153157
[install]
154158
prefix = "/home/user/.local/redmatter/python"
155-
min_python = "3.10"
159+
python = "3.10"
156160
uv_env = "REDMATTER_UV"
157161
python_env = "REDMATTER_PYTHON"
158162
shell_profile = false
163+
isolated = false
159164
```
160165

161166
## Idempotency

install.ps1

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@
1010
1111
.EXAMPLE
1212
.\install.ps1 -Prefix "$env:USERPROFILE\.local\redmatter\python" `
13-
-MinPython "3.10" -UvEnv "REDMATTER_UV" -PythonEnv "REDMATTER_PYTHON"
13+
-Python "3.10" -UvEnv "REDMATTER_UV" -PythonEnv "REDMATTER_PYTHON"
1414
#>
1515
param(
1616
[Parameter(Mandatory=$true)] [string]$Prefix,
17-
[Parameter(Mandatory=$true)] [string]$MinPython,
17+
[Parameter(Mandatory=$true)] [string]$Python,
1818
[Parameter(Mandatory=$true)] [string]$UvEnv,
1919
[Parameter(Mandatory=$true)] [string]$PythonEnv,
2020
[switch]$ShellProfile,
21-
[switch]$Quiet
21+
[switch]$Quiet,
22+
[switch]$Isolated
2223
)
2324

2425
function Write-Msg($msg) { if (-not $Quiet) { Write-Host $msg } }
@@ -88,16 +89,17 @@ if ($currentVer -eq $UvVersion) {
8889
if (Test-Path $VenvPy) {
8990
Write-Msg " ✓ venv already exists"
9091
} else {
91-
Write-Msg " → Creating Python $MinPython venv"
92-
$venvArgs = @("venv", "--python", $MinPython)
92+
Write-Msg " → Creating Python $Python venv"
93+
$pythonPref = if ($Isolated) { "only-managed" } else { "system" }
94+
$venvArgs = @("venv", "--python", $Python, "--python-preference", $pythonPref)
9395
if ($Quiet) { $venvArgs += "--quiet" }
9496
& $UvExe @venvArgs (Join-Path $Prefix "venv")
9597
if ($LASTEXITCODE -ne 0) {
96-
Write-Error "Failed to create Python $MinPython venv — see uv error above"
98+
Write-Error "Failed to create Python $Python venv — see uv error above"
9799
exit $LASTEXITCODE
98100
}
99101
if (-not (Test-Path $VenvPy)) {
100-
Write-Error "Failed to create Python $MinPython venv — python.exe not found at $VenvPy"
102+
Write-Error "Failed to create Python $Python venv — python.exe not found at $VenvPy"
101103
exit 1
102104
}
103105
Write-Msg " ✓ venv created"
@@ -106,8 +108,9 @@ if (Test-Path $VenvPy) {
106108
Write-Msg ""
107109

108110
# Hand off to setup.py
109-
$setupArgs = @("--prefix", $Prefix, "--min-python", $MinPython, "--uv-env", $UvEnv, "--python-env", $PythonEnv)
111+
$setupArgs = @("--prefix", $Prefix, "--python", $Python, "--uv-env", $UvEnv, "--python-env", $PythonEnv)
110112
if ($ShellProfile) { $setupArgs += "--shell-profile" }
113+
if ($Isolated) { $setupArgs += "--isolated" }
111114
if ($Quiet) { $setupArgs += "--quiet" }
112115
& $VenvPy (Join-Path $ScriptDir "setup.py") @setupArgs
113116
exit $LASTEXITCODE

install.sh

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# install.sh — Bootstrap uv + Python venv, then hand off to setup.py
33
#
44
# Usage:
5-
# ./install.sh --prefix PATH --min-python X.Y --uv-env NAME --python-env NAME [--shell-profile]
5+
# ./install.sh --prefix PATH --python X.Y --uv-env NAME --python-env NAME [--isolated] [--shell-profile]
66
#
77
# Bootstrap phase (this script): download uv, create venv.
88
# Configuration phase (setup.py): env.sh, env.ps1, bin/ wrappers, shell profile.
@@ -92,14 +92,16 @@ _bootstrap_uv() {
9292
}
9393

9494
_bootstrap_venv() {
95-
local prefix="$1" min_python="$2"
95+
local prefix="$1" min_python="$2" isolated="${3:-}"
9696

9797
if [[ -x "${prefix}/venv/bin/python" ]]; then
9898
_msg " ✓ venv already exists"; return
9999
fi
100100

101101
_msg " → Creating Python $min_python venv"
102-
"${prefix}/uv" venv --python "$min_python" ${quiet:+--quiet} "${prefix}/venv" \
102+
local pref_args="--python-preference system"
103+
[[ "$isolated" == "1" ]] && pref_args="--python-preference only-managed"
104+
"${prefix}/uv" venv --python "$min_python" $pref_args ${quiet:+--quiet} "${prefix}/venv" \
103105
|| { printf "ERROR: Failed to create Python %s venv — see uv error above\n" "$min_python" >&2; exit 1; }
104106
_msg " ✓ venv created"
105107
}
@@ -114,33 +116,34 @@ main() {
114116
uv_version="$(grep '^uv_version' "${script_dir}/distro.toml" \
115117
| sed -E 's/^[^=]+=\s*"?([^"#]+)"?.*/\1/' | tr -d '[:space:]')"
116118

117-
# Extract --prefix, --min-python, and --quiet for bootstrap (all flags forwarded to setup.py)
118-
local prefix="" min_python="" quiet=""
119+
# Extract --prefix, --python, --isolated, and --quiet for bootstrap (all flags forwarded to setup.py)
120+
local prefix="" min_python="" isolated="" quiet=""
119121
local i j
120122
for (( i=1; i<=$#; i++ )); do
121123
case "${!i}" in
122124
--prefix)
123125
j=$((i+1))
124126
if (( j > $# )); then printf "ERROR: --prefix requires a value\n" >&2; exit 1; fi
125127
prefix="${!j}"; prefix="${prefix/#\~/$HOME}" ;;
126-
--min-python)
128+
--python)
127129
j=$((i+1))
128-
if (( j > $# )); then printf "ERROR: --min-python requires a value\n" >&2; exit 1; fi
130+
if (( j > $# )); then printf "ERROR: --python requires a value\n" >&2; exit 1; fi
129131
min_python="${!j}" ;;
132+
--isolated) isolated=1 ;;
130133
--quiet|-q) quiet=1 ;;
131134
esac
132135
done
133136

134137
[[ -z "$prefix" ]] && { printf "ERROR: --prefix is required\n" >&2; exit 1; }
135-
[[ -z "$min_python" ]] && { printf "ERROR: --min-python is required\n" >&2; exit 1; }
138+
[[ -z "$min_python" ]] && { printf "ERROR: --python is required\n" >&2; exit 1; }
136139

137140
_msg ""
138141
_msg "managed-python bootstrap"
139142
_msg " prefix $prefix"
140143
_msg ""
141144

142145
_bootstrap_uv "$prefix" "$uv_version" "${script_dir}/distro.toml"
143-
_bootstrap_venv "$prefix" "$min_python"
146+
_bootstrap_venv "$prefix" "$min_python" "$isolated"
144147

145148
_msg ""
146149
exec "${prefix}/venv/bin/python" "${script_dir}/setup.py" "$@"

setup.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def _create_bin(prefix: Path) -> None:
140140

141141
# ── env.sh ────────────────────────────────────────────────────────────────────
142142

143-
def _write_env_sh(prefix: Path, uv_env: str, python_env: str, distro_version: str) -> None:
143+
def _write_env_sh(prefix: Path, uv_env: str, python_env: str, distro_version: str, *, isolated: bool = False) -> None:
144144
if _IS_WINDOWS:
145145
_info("Skipping env.sh on Windows — use env.ps1 instead")
146146
return
@@ -150,7 +150,11 @@ def _write_env_sh(prefix: Path, uv_env: str, python_env: str, distro_version: st
150150
venv_py = prefix / "venv" / "bin" / "python"
151151
bin_dir = prefix / "bin"
152152

153-
add_to_path, notes = _path_decision(bin_dir)
153+
if isolated:
154+
add_to_path = True
155+
notes = ["--isolated: always adding bin/ to PATH"]
156+
else:
157+
add_to_path, notes = _path_decision(bin_dir)
154158

155159
lines: list[str] = [
156160
f"# managed-python v{distro_version} \u2014 generated by setup.py",
@@ -176,7 +180,7 @@ def _write_env_sh(prefix: Path, uv_env: str, python_env: str, distro_version: st
176180

177181
# ── env.ps1 ───────────────────────────────────────────────────────────────────
178182

179-
def _write_env_ps1(prefix: Path, uv_env: str, python_env: str, distro_version: str) -> None:
183+
def _write_env_ps1(prefix: Path, uv_env: str, python_env: str, distro_version: str, *, isolated: bool = False) -> None:
180184
_step("Writing env.ps1")
181185

182186
uv_exe = prefix / ("uv.exe" if _IS_WINDOWS else "uv")
@@ -185,7 +189,11 @@ def _write_env_ps1(prefix: Path, uv_env: str, python_env: str, distro_version: s
185189
)
186190
bin_dir = prefix / "bin"
187191

188-
add_to_path, notes = _path_decision(bin_dir)
192+
if isolated:
193+
add_to_path = True
194+
notes = ["--isolated: always adding bin/ to PATH"]
195+
else:
196+
add_to_path, notes = _path_decision(bin_dir)
189197

190198
lines: list[str] = [
191199
f"# managed-python v{distro_version} -- generated by setup.py",
@@ -210,15 +218,19 @@ def _write_env_ps1(prefix: Path, uv_env: str, python_env: str, distro_version: s
210218

211219
# ── env.bat ───────────────────────────────────────────────────────────────────
212220

213-
def _write_env_bat(prefix: Path, uv_env: str, python_env: str, distro_version: str) -> None:
221+
def _write_env_bat(prefix: Path, uv_env: str, python_env: str, distro_version: str, *, isolated: bool = False) -> None:
214222
if not _IS_WINDOWS:
215223
return
216224

217225
uv_exe = prefix / "uv.exe"
218226
venv_py = prefix / "venv" / "Scripts" / "python.exe"
219227
bin_dir = prefix / "bin"
220228

221-
add_to_path, notes = _path_decision(bin_dir)
229+
if isolated:
230+
add_to_path = True
231+
notes = ["--isolated: always adding bin/ to PATH"]
232+
else:
233+
add_to_path, notes = _path_decision(bin_dir)
222234

223235
lines: list[str] = [
224236
f"@echo off",
@@ -250,6 +262,7 @@ def _write_installed_distro_toml(
250262
uv_env: str,
251263
python_env: str,
252264
shell_profile: bool,
265+
isolated: bool = False,
253266
) -> None:
254267
"""
255268
Write distro.toml to the prefix, appending an [install] section that
@@ -259,10 +272,11 @@ def _write_installed_distro_toml(
259272
install_section = (
260273
f"\n\n[install]\n"
261274
f'prefix = "{prefix.as_posix()}"\n'
262-
f'min_python = "{min_python}"\n'
275+
f'python = "{min_python}"\n'
263276
f'uv_env = "{uv_env}"\n'
264277
f'python_env = "{python_env}"\n'
265278
f"shell_profile = {'true' if shell_profile else 'false'}\n"
279+
f"isolated = {'true' if isolated else 'false'}\n"
266280
)
267281
(prefix / "distro.toml").write_text(source + install_section, encoding="utf-8")
268282

@@ -311,10 +325,11 @@ def _parse_args() -> argparse.Namespace:
311325
description="Post-bootstrap configuration for managed-python (stdlib, no deps).",
312326
)
313327
p.add_argument("--prefix", required=True, help="Install prefix")
314-
p.add_argument("--min-python", required=True, dest="min_python")
328+
p.add_argument("--python", required=True, dest="python_version")
315329
p.add_argument("--uv-env", required=True, dest="uv_env", help="Env var name for uv path")
316330
p.add_argument("--python-env", required=True, dest="python_env", help="Env var name for python path")
317331
p.add_argument("--shell-profile", action="store_true", dest="shell_profile")
332+
p.add_argument("--isolated", action="store_true", dest="isolated")
318333
p.add_argument("--quiet", "-q", action="store_true", dest="quiet",
319334
help="Suppress all output except warnings")
320335
return p.parse_args()
@@ -330,11 +345,12 @@ def main() -> None:
330345
distro_version = _toml_get(script_dir / "distro.toml", "version")
331346

332347
_create_bin(prefix)
333-
_write_env_sh(prefix, args.uv_env, args.python_env, distro_version)
334-
_write_env_ps1(prefix, args.uv_env, args.python_env, distro_version)
335-
_write_env_bat(prefix, args.uv_env, args.python_env, distro_version)
348+
_write_env_sh(prefix, args.uv_env, args.python_env, distro_version, isolated=args.isolated)
349+
_write_env_ps1(prefix, args.uv_env, args.python_env, distro_version, isolated=args.isolated)
350+
_write_env_bat(prefix, args.uv_env, args.python_env, distro_version, isolated=args.isolated)
336351
_write_installed_distro_toml(
337-
script_dir, prefix, args.min_python, args.uv_env, args.python_env, args.shell_profile
352+
script_dir, prefix, args.python_version, args.uv_env, args.python_env, args.shell_profile,
353+
isolated=args.isolated,
338354
)
339355

340356
if args.shell_profile:

0 commit comments

Comments
 (0)