Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/self-evolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Historical verification snapshot: `51be33361422e55e1f2f00c33a0e0f8c56132a91`
(the post-#54 `main` revision, captured before this #55 documentation-only
update). Snapshot date: 2026-09-04.

Current repository test count at this snapshot: **196 unittest cases**.
Current repository test count at this snapshot: **198 unittest cases**.

## Verified surface

Expand Down
7 changes: 7 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ if (-not $pythonVersion) {

Write-Output "[INFO] Installing OpenKyrozen $releaseTag from its immutable GitHub release with Python $pythonVersion..."
& $uvCommand.Source tool install --python $pythonVersion --force --with fastapi --with uvicorn $releaseWheelUrl
if ($LASTEXITCODE -ne 0) {
Write-Output "[INFO] uv cache was incomplete; retrying without the existing cache..."
& $uvCommand.Source --no-cache tool install --python $pythonVersion --force --with fastapi --with uvicorn $releaseWheelUrl
}
if ($LASTEXITCODE -ne 0) {
Fail "OpenKyrozen installation failed from release $releaseTag."
}
try { & $uvCommand.Source tool update-shell *> $null } catch { }

$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
Expand Down
10 changes: 8 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,14 @@ else
fi

info "Installing OpenKyrozen $release_tag from its immutable GitHub release with Python $python_version..."
"$uv_bin" tool install --python "$python_version" --force \
--with fastapi --with uvicorn "$release_wheel_url"
install_openkyrozen() {
"$uv_bin" "$@" tool install --python "$python_version" --force \
--with fastapi --with uvicorn "$release_wheel_url"
}
if ! install_openkyrozen; then
info 'uv cache was incomplete; retrying without the existing cache...'
install_openkyrozen --no-cache
fi
"$uv_bin" tool update-shell >/dev/null 2>&1 || true

profile=''
Expand Down
16 changes: 14 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1287,14 +1287,26 @@ def _self_update() -> str:
f"{sys.version_info.major}.{sys.version_info.minor}"
if sys.version_info[:2] in {(3, 12), (3, 13)} else "3.12"
)
command = [
"uv", "tool", "install", "--python", requested_python, "--force",
"--with", "fastapi", "--with", "uvicorn", RELEASE_WHEEL_URL,
]
try:
result = subprocess.run(
["uv", "tool", "install", "--python", requested_python, "--force",
"--with", "fastapi", "--with", "uvicorn", RELEASE_WHEEL_URL],
command,
capture_output=True,
text=True,
timeout=60,
)
if result.returncode != 0:
retry = subprocess.run(
["uv", "--no-cache", *command[1:]],
capture_output=True,
text=True,
timeout=60,
)
if retry.returncode == 0:
result = retry
out = result.stdout.strip() or ""
err = result.stderr.strip() or ""
if result.returncode != 0:
Expand Down
71 changes: 71 additions & 0 deletions tests/test_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def test_posix_installer_has_valid_syntax_and_idempotent_user_path_logic(self):
self.assertIn("release_version='2.0.2'", text)
self.assertIn("releases/download", text)
self.assertIn("--with fastapi --with uvicorn", text)
self.assertIn("--no-cache", text)
self.assertNotIn("pypi.org", text)
self.assertIn("installed_version=", text)

Expand All @@ -117,6 +118,7 @@ def test_powershell_installer_is_static_safe_and_parses_when_available(self):
self.assertIn("v2.0.2", text)
self.assertIn("releases/download", text)
self.assertIn("--with fastapi --with uvicorn", text)
self.assertIn("--no-cache", text)
self.assertNotIn("pypi.org", text)
powershell = shutil.which("pwsh") or shutil.which("powershell")
if powershell is None:
Expand All @@ -129,6 +131,56 @@ def test_powershell_installer_is_static_safe_and_parses_when_available(self):
)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)

def test_posix_installer_retries_with_uncached_uv_install(self):
with tempfile.TemporaryDirectory() as home, tempfile.TemporaryDirectory() as bin_dir:
home_path = Path(home)
bin_path = Path(bin_dir)
log_path = home_path / "uv.log"
uv_path = bin_path / "uv"
uv_path.write_text(
r"""#!/bin/sh
printf '%s\n' "$*" >> "$UV_TEST_LOG"
if [ "$1" = "python" ] && [ "$2" = "find" ]; then
exit 0
fi
if [ "$1" = "tool" ] && [ "$2" = "install" ]; then
exit 1
fi
if [ "$1" = "--no-cache" ] && [ "$2" = "tool" ] && [ "$3" = "install" ]; then
mkdir -p "$HOME/.local/bin"
printf '%s\n' '#!/bin/sh' 'if [ "$1" = "--version" ]; then printf "%s\n" "OpenKyrozen 2.0.2"; fi' > "$HOME/.local/bin/kyrozen"
chmod +x "$HOME/.local/bin/kyrozen"
exit 0
fi
exit 0
""",
encoding="utf-8",
)
curl_path = bin_path / "curl"
curl_path.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
os.chmod(uv_path, 0o755)
os.chmod(curl_path, 0o755)
env = os.environ.copy()
env.update({
"HOME": str(home_path),
"PATH": f"{bin_path}:{env.get('PATH', '')}",
"SHELL": "/bin/sh",
"UV_TEST_LOG": str(log_path),
})
result = subprocess.run(
["sh", str(ROOT / "install.sh")],
cwd=ROOT, env=env, capture_output=True, text=True, timeout=30,
)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
install_calls = [
line for line in log_path.read_text(encoding="utf-8").splitlines()
if "tool install" in line
]
self.assertEqual(len(install_calls), 2)
self.assertTrue(install_calls[0].startswith("tool install "))
self.assertTrue(install_calls[1].startswith("--no-cache tool install "))
self.assertIn("Installation complete.", result.stdout)

def test_cli_and_web_help_version_flags_work_without_provider_setup(self):
with tempfile.TemporaryDirectory() as home:
env = os.environ.copy()
Expand Down Expand Up @@ -195,6 +247,25 @@ def test_update_uses_the_package_manager_instead_of_git_pull(self):
self.assertIn("uvicorn", command)
self.assertTrue(command[-1].endswith("/v2.0.2/openkyrozen-2.0.2-py3-none-any.whl"))

def test_update_retries_without_uv_cache_after_install_failure(self):
import main

failed = subprocess.CompletedProcess(
["uv", "tool", "install"], 1, stdout="", stderr="missing archive",
)
completed = subprocess.CompletedProcess(
["uv", "--no-cache", "tool", "install"], 0, stdout="upgraded", stderr="",
)
with patch("main.shutil.which", return_value="/usr/local/bin/uv"), \
patch("main.subprocess.run", side_effect=[failed, completed]) as run:
result = main._self_update()
self.assertIn("upgraded", result)
self.assertEqual(run.call_count, 2)
self.assertEqual(
run.call_args_list[1].args[0][:3],
["uv", "--no-cache", "tool"],
)

def test_docker_starts_server_in_explicit_project_mode(self):
dockerfile = (ROOT / "Dockerfile").read_text(encoding="utf-8")
self.assertIn('"server.py", "--project", "/app"', dockerfile)
Expand Down
Loading