mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: extend importBundleArchive for site-packages and fixups
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import {
|
||||
constants,
|
||||
@@ -632,6 +633,44 @@ export async function importBundleArchive(
|
||||
moveTreeRecursive(stagingModels, MODELS_DIR);
|
||||
}
|
||||
|
||||
// Move site-packages/* into venv site-packages
|
||||
const stagingSitePackages = join(stagingDir, "site-packages");
|
||||
if (existsSync(stagingSitePackages)) {
|
||||
const venvPath = process.env.PYTHON_VENV_PATH || join(AI_DIR, "venv");
|
||||
let sitePackagesDir = "";
|
||||
const libDir = join(venvPath, "lib");
|
||||
if (existsSync(libDir)) {
|
||||
const pyDirs = readdirSync(libDir).filter((d) => d.startsWith("python"));
|
||||
if (pyDirs.length > 0) {
|
||||
sitePackagesDir = join(libDir, pyDirs[0], "site-packages");
|
||||
}
|
||||
}
|
||||
if (sitePackagesDir && existsSync(sitePackagesDir)) {
|
||||
moveTreeRecursive(stagingSitePackages, sitePackagesDir);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply fixups (NCCL wheel) if present
|
||||
const stagingFixups = join(stagingDir, "fixups");
|
||||
if (existsSync(stagingFixups)) {
|
||||
const wheels = readdirSync(stagingFixups).filter((f) => f.endsWith(".whl"));
|
||||
if (wheels.length > 0) {
|
||||
const venvPython =
|
||||
(process.env.PYTHON_VENV_PATH || join(AI_DIR, "venv")) + "/bin/python3";
|
||||
for (const wheel of wheels) {
|
||||
try {
|
||||
execFileSync(venvPython, [
|
||||
"-m", "pip", "install", "--no-index",
|
||||
`--find-links=${stagingFixups}`,
|
||||
wheel.split("-")[0],
|
||||
], { stdio: "ignore", timeout: 30_000 });
|
||||
} catch {
|
||||
// Non-fatal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
markInstalled(descriptor.bundleId, descriptor.version, descriptor.models);
|
||||
|
||||
return {
|
||||
|
||||
@@ -143,6 +143,41 @@ async function buildSymlinkArchive(): Promise<string> {
|
||||
return archivePath;
|
||||
}
|
||||
|
||||
async function createBundleTarWithSitePackages(
|
||||
bundleId: string,
|
||||
version: string,
|
||||
modelFiles: Record<string, string>,
|
||||
sitePackageFiles: Record<string, string>,
|
||||
): Promise<string> {
|
||||
const tarDir = join(testRoot, `tar-src-${randomUUID()}`);
|
||||
mkdirSync(tarDir, { recursive: true });
|
||||
|
||||
writeFileSync(
|
||||
join(tarDir, "bundle.json"),
|
||||
JSON.stringify({ bundleId, version, models: Object.keys(modelFiles) }),
|
||||
);
|
||||
|
||||
const modelsSubdir = join(tarDir, "models");
|
||||
mkdirSync(modelsSubdir, { recursive: true });
|
||||
for (const [name, content] of Object.entries(modelFiles)) {
|
||||
const modelPath = join(modelsSubdir, name);
|
||||
mkdirSync(join(modelPath, ".."), { recursive: true });
|
||||
writeFileSync(modelPath, content);
|
||||
}
|
||||
|
||||
const spSubdir = join(tarDir, "site-packages");
|
||||
mkdirSync(spSubdir, { recursive: true });
|
||||
for (const [name, content] of Object.entries(sitePackageFiles)) {
|
||||
const spPath = join(spSubdir, name);
|
||||
mkdirSync(join(spPath, ".."), { recursive: true });
|
||||
writeFileSync(spPath, content);
|
||||
}
|
||||
|
||||
const tarPath = join(testRoot, `bundle-${randomUUID()}.tar.gz`);
|
||||
await tar.create({ gzip: true, file: tarPath, cwd: tarDir }, ["."]);
|
||||
return tarPath;
|
||||
}
|
||||
|
||||
function resetState(): void {
|
||||
writeFileSync(installedPath, JSON.stringify({ bundles: {} }), "utf-8");
|
||||
invalidateCache();
|
||||
@@ -265,6 +300,30 @@ describe("importBundleArchive", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("site-packages import", () => {
|
||||
beforeEach(resetState);
|
||||
|
||||
it("extracts site-packages into venv site-packages directory", async () => {
|
||||
const venvSitePackages = join(aiDir, "venv", "lib", "python3.12", "site-packages");
|
||||
mkdirSync(venvSitePackages, { recursive: true });
|
||||
process.env.PYTHON_VENV_PATH = join(aiDir, "venv");
|
||||
|
||||
const tarPath = await createBundleTarWithSitePackages(
|
||||
testBundleId,
|
||||
testVersion,
|
||||
{ "mediapipe/face.tflite": "model-data" },
|
||||
{ "fakepkg/__init__.py": "# fake package" },
|
||||
);
|
||||
|
||||
invalidateCache();
|
||||
const result = await importBundleArchive(createReadStream(tarPath));
|
||||
expect(result.bundleId).toBe(testBundleId);
|
||||
expect(existsSync(join(venvSitePackages, "fakepkg", "__init__.py"))).toBe(true);
|
||||
|
||||
delete process.env.PYTHON_VENV_PATH;
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/v1/admin/features/import", () => {
|
||||
let app: Awaited<ReturnType<typeof import("fastify")>>["default"] extends (
|
||||
...args: infer _A
|
||||
|
||||
Reference in New Issue
Block a user