diff --git a/ui/litellm-dashboard/scripts/gen-api-types.mjs b/ui/litellm-dashboard/scripts/gen-api-types.mjs index 974a391b747..fb5ae803e50 100644 --- a/ui/litellm-dashboard/scripts/gen-api-types.mjs +++ b/ui/litellm-dashboard/scripts/gen-api-types.mjs @@ -55,12 +55,17 @@ export function unsupportedPythonMessage(pythonCommand, version) { ); } -function assertSupportedPython(pythonCommand) { +export function probePythonVersion(pythonCommand, cwd, exec = execFileSync) { const probe = "import sys; print('.'.join(map(str, sys.version_info[:2])))"; - const stdout = execFileSync(pythonCommand[0], [...pythonCommand.slice(1), "-c", probe], { + const stdout = exec(pythonCommand[0], [...pythonCommand.slice(1), "-c", probe], { + cwd, encoding: "utf8", }); - const version = parsePythonVersion(stdout); + return parsePythonVersion(stdout); +} + +function assertSupportedPython(pythonCommand, cwd) { + const version = probePythonVersion(pythonCommand, cwd); if (!isSupportedPython(version)) { throw new Error(unsupportedPythonMessage(pythonCommand, version)); } @@ -89,7 +94,7 @@ function main() { ].join("\n"); try { - assertSupportedPython(python); + assertSupportedPython(python, repoRoot); execFileSync(python[0], [...python.slice(1), "-c", dumpSpec, specPath], { cwd: repoRoot, diff --git a/ui/litellm-dashboard/tests/gen-api-types.test.ts b/ui/litellm-dashboard/tests/gen-api-types.test.ts index f6946b87632..412a340767d 100644 --- a/ui/litellm-dashboard/tests/gen-api-types.test.ts +++ b/ui/litellm-dashboard/tests/gen-api-types.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect } from "vitest"; import { resolvePythonCommand, parsePythonVersion, + probePythonVersion, isSupportedPython, unsupportedPythonMessage, } from "../scripts/gen-api-types.mjs"; @@ -49,6 +50,32 @@ describe("parsePythonVersion", () => { }); }); +describe("probePythonVersion", () => { + it("probes the interpreter from the repo root, matching the spec-dump cwd", () => { + let seenCwd: string | undefined; + const exec = (_bin: string, _args: string[], opts: { cwd?: string }) => { + seenCwd = opts.cwd; + return "3.11\n"; + }; + const version = probePythonVersion(["python3"], repoRoot, exec); + expect(seenCwd).toBe(repoRoot); + expect(version).toEqual({ major: 3, minor: 11 }); + }); + + it("resolves a repo-root-relative interpreter against the repo root, not the dashboard cwd", () => { + let seenBin: string | undefined; + let seenCwd: string | undefined; + const exec = (bin: string, _args: string[], opts: { cwd?: string }) => { + seenBin = bin; + seenCwd = opts.cwd; + return "3.12\n"; + }; + probePythonVersion([".venv/bin/python"], repoRoot, exec); + expect(seenBin).toBe(".venv/bin/python"); + expect(seenCwd).toBe(repoRoot); + }); +}); + describe("isSupportedPython", () => { it("rejects the 3.9 interpreter that breaks on dataclass slots=True", () => { expect(isSupportedPython({ major: 3, minor: 9 })).toBe(false);