@vercel/python 6.0.2 → 6.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +157 -47
- package/package.json +2 -2
- package/vc_init.py +144 -77
- package/vc_init_dev_asgi.py +3 -2
package/dist/index.js
CHANGED
|
@@ -2751,7 +2751,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
2751
2751
|
var import_fs5 = __toESM(require("fs"));
|
|
2752
2752
|
var import_util = require("util");
|
|
2753
2753
|
var import_path5 = require("path");
|
|
2754
|
-
var
|
|
2754
|
+
var import_build_utils7 = require("@vercel/build-utils");
|
|
2755
2755
|
|
|
2756
2756
|
// src/install.ts
|
|
2757
2757
|
var import_execa = __toESM(require_execa());
|
|
@@ -3004,7 +3004,12 @@ async function exportRequirementsFromUv(projectDir, uvPath, options = {}) {
|
|
|
3004
3004
|
if (!uvPath) {
|
|
3005
3005
|
throw new Error("uv is not available to export requirements");
|
|
3006
3006
|
}
|
|
3007
|
-
const args = [
|
|
3007
|
+
const args = [
|
|
3008
|
+
"export",
|
|
3009
|
+
"--no-default-groups",
|
|
3010
|
+
"--no-emit-workspace",
|
|
3011
|
+
"--no-editable"
|
|
3012
|
+
];
|
|
3008
3013
|
if (locked) {
|
|
3009
3014
|
args.push("--frozen");
|
|
3010
3015
|
}
|
|
@@ -3066,7 +3071,7 @@ async function exportRequirementsFromPipfile({
|
|
|
3066
3071
|
}
|
|
3067
3072
|
|
|
3068
3073
|
// src/index.ts
|
|
3069
|
-
var
|
|
3074
|
+
var import_build_utils8 = require("@vercel/build-utils");
|
|
3070
3075
|
|
|
3071
3076
|
// src/version.ts
|
|
3072
3077
|
var import_build_utils2 = require("@vercel/build-utils");
|
|
@@ -3199,7 +3204,7 @@ function isInstalled2({ pipPath, pythonPath }) {
|
|
|
3199
3204
|
var import_child_process = require("child_process");
|
|
3200
3205
|
var import_fs4 = require("fs");
|
|
3201
3206
|
var import_path4 = require("path");
|
|
3202
|
-
var
|
|
3207
|
+
var import_build_utils6 = require("@vercel/build-utils");
|
|
3203
3208
|
|
|
3204
3209
|
// src/entrypoint.ts
|
|
3205
3210
|
var import_fs2 = __toESM(require("fs"));
|
|
@@ -3329,6 +3334,8 @@ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint)
|
|
|
3329
3334
|
// src/utils.ts
|
|
3330
3335
|
var import_fs3 = __toESM(require("fs"));
|
|
3331
3336
|
var import_path3 = require("path");
|
|
3337
|
+
var import_execa2 = __toESM(require_execa());
|
|
3338
|
+
var import_build_utils5 = require("@vercel/build-utils");
|
|
3332
3339
|
var isInVirtualEnv = () => {
|
|
3333
3340
|
return process.env.VIRTUAL_ENV;
|
|
3334
3341
|
};
|
|
@@ -3349,6 +3356,51 @@ function useVirtualEnv(workPath, env, systemPython) {
|
|
|
3349
3356
|
}
|
|
3350
3357
|
return { pythonCmd };
|
|
3351
3358
|
}
|
|
3359
|
+
async function runPyprojectScript(workPath, scriptNames, env) {
|
|
3360
|
+
const pyprojectPath = (0, import_path3.join)(workPath, "pyproject.toml");
|
|
3361
|
+
if (!import_fs3.default.existsSync(pyprojectPath))
|
|
3362
|
+
return false;
|
|
3363
|
+
let pyproject = null;
|
|
3364
|
+
try {
|
|
3365
|
+
pyproject = await (0, import_build_utils5.readConfigFile)(pyprojectPath);
|
|
3366
|
+
} catch {
|
|
3367
|
+
console.error("Failed to parse pyproject.toml");
|
|
3368
|
+
return false;
|
|
3369
|
+
}
|
|
3370
|
+
const scripts = pyproject?.tool?.vercel?.scripts || {};
|
|
3371
|
+
const candidates = typeof scriptNames === "string" ? [scriptNames] : Array.from(scriptNames);
|
|
3372
|
+
const scriptToRun = candidates.find((name) => Boolean(scripts[name]));
|
|
3373
|
+
if (!scriptToRun)
|
|
3374
|
+
return false;
|
|
3375
|
+
const systemPython = process.platform === "win32" ? "python" : "python3";
|
|
3376
|
+
const finalEnv = { ...process.env, ...env };
|
|
3377
|
+
const { pythonCmd } = useVirtualEnv(workPath, finalEnv, systemPython);
|
|
3378
|
+
const uvPath = await getUvBinaryOrInstall(pythonCmd);
|
|
3379
|
+
const scriptCommand = scripts[scriptToRun];
|
|
3380
|
+
if (typeof scriptCommand === "string" && scriptCommand.trim()) {
|
|
3381
|
+
const uvDir = (0, import_path3.dirname)(uvPath);
|
|
3382
|
+
finalEnv.PATH = `${uvDir}${import_path3.delimiter}${finalEnv.PATH || ""}`;
|
|
3383
|
+
if (/^\s*uv(\s|$)/i.test(scriptCommand)) {
|
|
3384
|
+
console.log(`Executing: ${scriptCommand}`);
|
|
3385
|
+
await (0, import_build_utils5.execCommand)(scriptCommand, {
|
|
3386
|
+
cwd: workPath,
|
|
3387
|
+
env: finalEnv
|
|
3388
|
+
});
|
|
3389
|
+
return true;
|
|
3390
|
+
}
|
|
3391
|
+
const args = process.platform === "win32" ? ["run", "cmd", "/d", "/s", "/c", scriptCommand] : ["run", "sh", "-lc", scriptCommand];
|
|
3392
|
+
console.log(
|
|
3393
|
+
`Executing: ${uvPath} ${args.map((a) => /\s/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a).join(" ")}`
|
|
3394
|
+
);
|
|
3395
|
+
await (0, import_execa2.default)(uvPath, args, {
|
|
3396
|
+
cwd: workPath,
|
|
3397
|
+
stdio: "inherit",
|
|
3398
|
+
env: finalEnv
|
|
3399
|
+
});
|
|
3400
|
+
return true;
|
|
3401
|
+
}
|
|
3402
|
+
return false;
|
|
3403
|
+
}
|
|
3352
3404
|
|
|
3353
3405
|
// src/start-dev-server.ts
|
|
3354
3406
|
function silenceNodeWarnings() {
|
|
@@ -3393,12 +3445,12 @@ function installGlobalCleanupHandlers() {
|
|
|
3393
3445
|
try {
|
|
3394
3446
|
process.kill(info.pid, "SIGTERM");
|
|
3395
3447
|
} catch (err) {
|
|
3396
|
-
(0,
|
|
3448
|
+
(0, import_build_utils6.debug)(`Error sending SIGTERM to ${info.pid}: ${err}`);
|
|
3397
3449
|
}
|
|
3398
3450
|
try {
|
|
3399
3451
|
process.kill(info.pid, "SIGKILL");
|
|
3400
3452
|
} catch (err) {
|
|
3401
|
-
(0,
|
|
3453
|
+
(0, import_build_utils6.debug)(`Error sending SIGKILL to ${info.pid}: ${err}`);
|
|
3402
3454
|
}
|
|
3403
3455
|
PERSISTENT_SERVERS.delete(key);
|
|
3404
3456
|
}
|
|
@@ -3406,7 +3458,7 @@ function installGlobalCleanupHandlers() {
|
|
|
3406
3458
|
try {
|
|
3407
3459
|
restoreWarnings();
|
|
3408
3460
|
} catch (err) {
|
|
3409
|
-
(0,
|
|
3461
|
+
(0, import_build_utils6.debug)(`Error restoring warnings: ${err}`);
|
|
3410
3462
|
}
|
|
3411
3463
|
restoreWarnings = null;
|
|
3412
3464
|
}
|
|
@@ -3432,10 +3484,10 @@ function createDevAsgiShim(workPath, modulePath) {
|
|
|
3432
3484
|
const template = (0, import_fs4.readFileSync)(templatePath, "utf8");
|
|
3433
3485
|
const shimSource = template.replace(/__VC_DEV_MODULE_PATH__/g, modulePath);
|
|
3434
3486
|
(0, import_fs4.writeFileSync)(shimPath, shimSource, "utf8");
|
|
3435
|
-
(0,
|
|
3487
|
+
(0, import_build_utils6.debug)(`Prepared Python dev static shim at ${shimPath}`);
|
|
3436
3488
|
return ASGI_SHIM_MODULE;
|
|
3437
3489
|
} catch (err) {
|
|
3438
|
-
(0,
|
|
3490
|
+
(0, import_build_utils6.debug)(`Failed to prepare dev static shim: ${err?.message || err}`);
|
|
3439
3491
|
return null;
|
|
3440
3492
|
}
|
|
3441
3493
|
}
|
|
@@ -3448,10 +3500,10 @@ function createDevWsgiShim(workPath, modulePath) {
|
|
|
3448
3500
|
const template = (0, import_fs4.readFileSync)(templatePath, "utf8");
|
|
3449
3501
|
const shimSource = template.replace(/__VC_DEV_MODULE_PATH__/g, modulePath);
|
|
3450
3502
|
(0, import_fs4.writeFileSync)(shimPath, shimSource, "utf8");
|
|
3451
|
-
(0,
|
|
3503
|
+
(0, import_build_utils6.debug)(`Prepared Python dev WSGI shim at ${shimPath}`);
|
|
3452
3504
|
return WSGI_SHIM_MODULE;
|
|
3453
3505
|
} catch (err) {
|
|
3454
|
-
(0,
|
|
3506
|
+
(0, import_build_utils6.debug)(`Failed to prepare dev WSGI shim: ${err?.message || err}`);
|
|
3455
3507
|
return null;
|
|
3456
3508
|
}
|
|
3457
3509
|
}
|
|
@@ -3471,7 +3523,7 @@ var startDevServer = async (opts) => {
|
|
|
3471
3523
|
);
|
|
3472
3524
|
if (!entry) {
|
|
3473
3525
|
const searched = framework === "fastapi" ? FASTAPI_CANDIDATE_ENTRYPOINTS.join(", ") : FLASK_CANDIDATE_ENTRYPOINTS.join(", ");
|
|
3474
|
-
throw new
|
|
3526
|
+
throw new import_build_utils6.NowBuildError({
|
|
3475
3527
|
code: "PYTHON_ENTRYPOINT_NOT_FOUND",
|
|
3476
3528
|
message: `No ${framework} entrypoint found. Define a valid application entrypoint in one of the following locations: ${searched} or add an 'app' script in pyproject.toml.`,
|
|
3477
3529
|
link: `https://vercel.com/docs/frameworks/backend/${framework?.toLowerCase()}#exporting-the-${framework?.toLowerCase()}-application`,
|
|
@@ -3521,7 +3573,7 @@ var startDevServer = async (opts) => {
|
|
|
3521
3573
|
let pythonCmd = systemPython;
|
|
3522
3574
|
const venv = isInVirtualEnv();
|
|
3523
3575
|
if (venv) {
|
|
3524
|
-
(0,
|
|
3576
|
+
(0, import_build_utils6.debug)(`Running in virtualenv at ${venv}`);
|
|
3525
3577
|
} else {
|
|
3526
3578
|
const { pythonCmd: venvPythonCmd, venvRoot } = useVirtualEnv(
|
|
3527
3579
|
workPath,
|
|
@@ -3530,9 +3582,9 @@ var startDevServer = async (opts) => {
|
|
|
3530
3582
|
);
|
|
3531
3583
|
pythonCmd = venvPythonCmd;
|
|
3532
3584
|
if (venvRoot) {
|
|
3533
|
-
(0,
|
|
3585
|
+
(0, import_build_utils6.debug)(`Using virtualenv at ${venvRoot}`);
|
|
3534
3586
|
} else {
|
|
3535
|
-
(0,
|
|
3587
|
+
(0, import_build_utils6.debug)("No virtualenv found");
|
|
3536
3588
|
try {
|
|
3537
3589
|
const yellow = "\x1B[33m";
|
|
3538
3590
|
const reset = "\x1B[0m";
|
|
@@ -3555,7 +3607,7 @@ If you are using a virtual environment, activate it before running "vercel dev",
|
|
|
3555
3607
|
}
|
|
3556
3608
|
const moduleToRun = devShimModule || modulePath;
|
|
3557
3609
|
const argv = ["-u", "-m", moduleToRun];
|
|
3558
|
-
(0,
|
|
3610
|
+
(0, import_build_utils6.debug)(`Starting ASGI dev server: ${pythonCmd} ${argv.join(" ")}`);
|
|
3559
3611
|
const child = (0, import_child_process.spawn)(pythonCmd, argv, {
|
|
3560
3612
|
cwd: workPath,
|
|
3561
3613
|
env,
|
|
@@ -3633,7 +3685,7 @@ If you are using a virtual environment, activate it before running "vercel dev",
|
|
|
3633
3685
|
}
|
|
3634
3686
|
const moduleToRun = devShimModule || modulePath;
|
|
3635
3687
|
const argv = ["-u", "-m", moduleToRun];
|
|
3636
|
-
(0,
|
|
3688
|
+
(0, import_build_utils6.debug)(`Starting Flask dev server: ${pythonCmd} ${argv.join(" ")}`);
|
|
3637
3689
|
const child = (0, import_child_process.spawn)(pythonCmd, argv, {
|
|
3638
3690
|
cwd: workPath,
|
|
3639
3691
|
env,
|
|
@@ -3743,13 +3795,13 @@ async function downloadFilesInWorkPath({
|
|
|
3743
3795
|
files,
|
|
3744
3796
|
meta = {}
|
|
3745
3797
|
}) {
|
|
3746
|
-
(0,
|
|
3747
|
-
let downloadedFiles = await (0,
|
|
3798
|
+
(0, import_build_utils7.debug)("Downloading user files...");
|
|
3799
|
+
let downloadedFiles = await (0, import_build_utils7.download)(files, workPath, meta);
|
|
3748
3800
|
if (meta.isDev) {
|
|
3749
3801
|
const { devCacheDir = (0, import_path5.join)(workPath, ".now", "cache") } = meta;
|
|
3750
3802
|
const destCache = (0, import_path5.join)(devCacheDir, (0, import_path5.basename)(entrypoint, ".py"));
|
|
3751
|
-
await (0,
|
|
3752
|
-
downloadedFiles = await (0,
|
|
3803
|
+
await (0, import_build_utils7.download)(downloadedFiles, destCache);
|
|
3804
|
+
downloadedFiles = await (0, import_build_utils7.glob)("**", destCache);
|
|
3753
3805
|
workPath = destCache;
|
|
3754
3806
|
}
|
|
3755
3807
|
return workPath;
|
|
@@ -3777,7 +3829,50 @@ var build = async ({
|
|
|
3777
3829
|
console.log('Failed to create "setup.cfg" file');
|
|
3778
3830
|
throw err;
|
|
3779
3831
|
}
|
|
3780
|
-
|
|
3832
|
+
if (framework === "fastapi" || framework === "flask") {
|
|
3833
|
+
const {
|
|
3834
|
+
cliType,
|
|
3835
|
+
lockfileVersion,
|
|
3836
|
+
packageJsonPackageManager,
|
|
3837
|
+
turboSupportsCorepackHome
|
|
3838
|
+
} = await (0, import_build_utils7.scanParentDirs)(workPath, true);
|
|
3839
|
+
const spawnEnv = (0, import_build_utils7.getEnvForPackageManager)({
|
|
3840
|
+
cliType,
|
|
3841
|
+
lockfileVersion,
|
|
3842
|
+
packageJsonPackageManager,
|
|
3843
|
+
env: process.env,
|
|
3844
|
+
turboSupportsCorepackHome,
|
|
3845
|
+
projectCreatedAt: config?.projectSettings?.createdAt
|
|
3846
|
+
});
|
|
3847
|
+
const installCommand = config?.projectSettings?.installCommand;
|
|
3848
|
+
if (typeof installCommand === "string") {
|
|
3849
|
+
if (installCommand.trim()) {
|
|
3850
|
+
console.log(`Running "install" command: \`${installCommand}\`...`);
|
|
3851
|
+
await (0, import_build_utils7.execCommand)(installCommand, {
|
|
3852
|
+
env: spawnEnv,
|
|
3853
|
+
cwd: workPath
|
|
3854
|
+
});
|
|
3855
|
+
} else {
|
|
3856
|
+
console.log('Skipping "install" command...');
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
const projectBuildCommand = config?.projectSettings?.buildCommand ?? // fallback if provided directly on config (some callers set this)
|
|
3860
|
+
config?.buildCommand;
|
|
3861
|
+
if (projectBuildCommand) {
|
|
3862
|
+
console.log(`Running "${projectBuildCommand}"`);
|
|
3863
|
+
await (0, import_build_utils7.execCommand)(projectBuildCommand, {
|
|
3864
|
+
env: spawnEnv,
|
|
3865
|
+
cwd: workPath
|
|
3866
|
+
});
|
|
3867
|
+
} else {
|
|
3868
|
+
await runPyprojectScript(
|
|
3869
|
+
workPath,
|
|
3870
|
+
["vercel-build", "now-build", "build"],
|
|
3871
|
+
spawnEnv
|
|
3872
|
+
);
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
let fsFiles = await (0, import_build_utils7.glob)("**", workPath);
|
|
3781
3876
|
if ((framework === "fastapi" || framework === "flask") && (!fsFiles[entrypoint] || !entrypoint.endsWith(".py"))) {
|
|
3782
3877
|
const detected = await detectPythonEntrypoint(
|
|
3783
3878
|
config.framework,
|
|
@@ -3785,13 +3880,13 @@ var build = async ({
|
|
|
3785
3880
|
entrypoint
|
|
3786
3881
|
);
|
|
3787
3882
|
if (detected) {
|
|
3788
|
-
(0,
|
|
3883
|
+
(0, import_build_utils7.debug)(
|
|
3789
3884
|
`Resolved Python entrypoint to "${detected}" (configured "${entrypoint}" not found).`
|
|
3790
3885
|
);
|
|
3791
3886
|
entrypoint = detected;
|
|
3792
3887
|
} else {
|
|
3793
3888
|
const searchedList = framework === "fastapi" ? FASTAPI_CANDIDATE_ENTRYPOINTS.join(", ") : FLASK_CANDIDATE_ENTRYPOINTS.join(", ");
|
|
3794
|
-
throw new
|
|
3889
|
+
throw new import_build_utils7.NowBuildError({
|
|
3795
3890
|
code: `${framework.toUpperCase()}_ENTRYPOINT_NOT_FOUND`,
|
|
3796
3891
|
message: `No ${framework} entrypoint found. Define a valid application entrypoint in one of the following locations: ${searchedList} or add an 'app' script in pyproject.toml.`,
|
|
3797
3892
|
link: `https://vercel.com/docs/frameworks/backend/${framework}#exporting-the-${framework}-application`,
|
|
@@ -3820,20 +3915,20 @@ var build = async ({
|
|
|
3820
3915
|
if (pyprojectDir) {
|
|
3821
3916
|
let requiresPython;
|
|
3822
3917
|
try {
|
|
3823
|
-
const pyproject = await (0,
|
|
3918
|
+
const pyproject = await (0, import_build_utils8.readConfigFile)((0, import_path5.join)(pyprojectDir, "pyproject.toml"));
|
|
3824
3919
|
requiresPython = pyproject?.project?.["requires-python"];
|
|
3825
3920
|
} catch (err) {
|
|
3826
|
-
(0,
|
|
3921
|
+
(0, import_build_utils7.debug)("Failed to parse pyproject.toml", err);
|
|
3827
3922
|
}
|
|
3828
3923
|
const VERSION_REGEX = /\b\d+\.\d+\b/;
|
|
3829
3924
|
const exact = requiresPython?.trim().match(VERSION_REGEX)?.[0];
|
|
3830
3925
|
if (exact) {
|
|
3831
3926
|
declaredPythonVersion = { version: exact, source: "pyproject.toml" };
|
|
3832
|
-
(0,
|
|
3927
|
+
(0, import_build_utils7.debug)(
|
|
3833
3928
|
`Found Python version ${exact} in pyproject.toml (requires-python: "${requiresPython}")`
|
|
3834
3929
|
);
|
|
3835
3930
|
} else if (requiresPython) {
|
|
3836
|
-
(0,
|
|
3931
|
+
(0, import_build_utils7.debug)(
|
|
3837
3932
|
`Could not parse Python version from pyproject.toml requires-python: "${requiresPython}"`
|
|
3838
3933
|
);
|
|
3839
3934
|
}
|
|
@@ -3843,7 +3938,7 @@ var build = async ({
|
|
|
3843
3938
|
const json = await readFile((0, import_path5.join)(pipfileLockDir, "Pipfile.lock"), "utf8");
|
|
3844
3939
|
lock = JSON.parse(json);
|
|
3845
3940
|
} catch (err) {
|
|
3846
|
-
throw new
|
|
3941
|
+
throw new import_build_utils7.NowBuildError({
|
|
3847
3942
|
code: "INVALID_PIPFILE_LOCK",
|
|
3848
3943
|
message: "Unable to parse Pipfile.lock"
|
|
3849
3944
|
});
|
|
@@ -3851,14 +3946,14 @@ var build = async ({
|
|
|
3851
3946
|
const pyFromLock = lock?._meta?.requires?.python_version;
|
|
3852
3947
|
if (pyFromLock) {
|
|
3853
3948
|
declaredPythonVersion = { version: pyFromLock, source: "Pipfile.lock" };
|
|
3854
|
-
(0,
|
|
3949
|
+
(0, import_build_utils7.debug)(`Found Python version ${pyFromLock} in Pipfile.lock`);
|
|
3855
3950
|
}
|
|
3856
3951
|
}
|
|
3857
3952
|
const pythonVersion = getSupportedPythonVersion({
|
|
3858
3953
|
isDev: meta.isDev,
|
|
3859
3954
|
declaredPythonVersion
|
|
3860
3955
|
});
|
|
3861
|
-
fsFiles = await (0,
|
|
3956
|
+
fsFiles = await (0, import_build_utils7.glob)("**", workPath);
|
|
3862
3957
|
const requirementsTxt = (0, import_path5.join)(entryDirectory, "requirements.txt");
|
|
3863
3958
|
const vendorBaseDir = (0, import_path5.join)(
|
|
3864
3959
|
workPath,
|
|
@@ -3903,7 +3998,7 @@ var build = async ({
|
|
|
3903
3998
|
`uv is required for this project but failed to install: ${err instanceof Error ? err.message : String(err)}`
|
|
3904
3999
|
);
|
|
3905
4000
|
}
|
|
3906
|
-
(0,
|
|
4001
|
+
(0, import_build_utils7.debug)("Failed to install uv", err);
|
|
3907
4002
|
}
|
|
3908
4003
|
await installRequirement({
|
|
3909
4004
|
pythonPath: pythonVersion.pythonPath,
|
|
@@ -3915,9 +4010,21 @@ var build = async ({
|
|
|
3915
4010
|
targetDir: vendorBaseDir,
|
|
3916
4011
|
meta
|
|
3917
4012
|
});
|
|
4013
|
+
if (framework !== "flask") {
|
|
4014
|
+
await installRequirement({
|
|
4015
|
+
pythonPath: pythonVersion.pythonPath,
|
|
4016
|
+
pipPath: pythonVersion.pipPath,
|
|
4017
|
+
uvPath,
|
|
4018
|
+
dependency: "uvicorn",
|
|
4019
|
+
version: "0.38.0",
|
|
4020
|
+
workPath,
|
|
4021
|
+
targetDir: vendorBaseDir,
|
|
4022
|
+
meta
|
|
4023
|
+
});
|
|
4024
|
+
}
|
|
3918
4025
|
let installedFromProjectFiles = false;
|
|
3919
4026
|
if (uvLockDir) {
|
|
3920
|
-
(0,
|
|
4027
|
+
(0, import_build_utils7.debug)('Found "uv.lock"');
|
|
3921
4028
|
if (pyprojectDir) {
|
|
3922
4029
|
const exportedReq = await exportRequirementsFromUv(pyprojectDir, uvPath, {
|
|
3923
4030
|
locked: true
|
|
@@ -3933,10 +4040,10 @@ var build = async ({
|
|
|
3933
4040
|
});
|
|
3934
4041
|
installedFromProjectFiles = true;
|
|
3935
4042
|
} else {
|
|
3936
|
-
(0,
|
|
4043
|
+
(0, import_build_utils7.debug)('Skipping uv export because "pyproject.toml" was not found');
|
|
3937
4044
|
}
|
|
3938
4045
|
} else if (pyprojectDir) {
|
|
3939
|
-
(0,
|
|
4046
|
+
(0, import_build_utils7.debug)('Found "pyproject.toml"');
|
|
3940
4047
|
if (hasReqLocal || hasReqGlobal) {
|
|
3941
4048
|
console.log(
|
|
3942
4049
|
"Detected both pyproject.toml and requirements.txt but no lockfile; using pyproject.toml"
|
|
@@ -3956,9 +4063,9 @@ var build = async ({
|
|
|
3956
4063
|
});
|
|
3957
4064
|
installedFromProjectFiles = true;
|
|
3958
4065
|
} else if (pipfileLockDir || pipfileDir) {
|
|
3959
|
-
(0,
|
|
4066
|
+
(0, import_build_utils7.debug)(`Found ${pipfileLockDir ? '"Pipfile.lock"' : '"Pipfile"'}`);
|
|
3960
4067
|
if (hasReqLocal || hasReqGlobal) {
|
|
3961
|
-
(0,
|
|
4068
|
+
(0, import_build_utils7.debug)('Skipping Pipfile export because "requirements.txt" exists');
|
|
3962
4069
|
} else {
|
|
3963
4070
|
const exportedReq = await exportRequirementsFromPipfile({
|
|
3964
4071
|
pythonPath: pythonVersion.pythonPath,
|
|
@@ -3980,7 +4087,7 @@ var build = async ({
|
|
|
3980
4087
|
}
|
|
3981
4088
|
}
|
|
3982
4089
|
if (!installedFromProjectFiles && fsFiles[requirementsTxt]) {
|
|
3983
|
-
(0,
|
|
4090
|
+
(0, import_build_utils7.debug)('Found local "requirements.txt"');
|
|
3984
4091
|
const requirementsTxtPath = fsFiles[requirementsTxt].fsPath;
|
|
3985
4092
|
await installRequirementsFile({
|
|
3986
4093
|
pythonPath: pythonVersion.pythonPath,
|
|
@@ -3992,7 +4099,7 @@ var build = async ({
|
|
|
3992
4099
|
meta
|
|
3993
4100
|
});
|
|
3994
4101
|
} else if (!installedFromProjectFiles && fsFiles["requirements.txt"]) {
|
|
3995
|
-
(0,
|
|
4102
|
+
(0, import_build_utils7.debug)('Found global "requirements.txt"');
|
|
3996
4103
|
const requirementsTxtPath = fsFiles["requirements.txt"].fsPath;
|
|
3997
4104
|
await installRequirementsFile({
|
|
3998
4105
|
pythonPath: pythonVersion.pythonPath,
|
|
@@ -4006,12 +4113,12 @@ var build = async ({
|
|
|
4006
4113
|
}
|
|
4007
4114
|
const originalPyPath = (0, import_path5.join)(__dirname, "..", "vc_init.py");
|
|
4008
4115
|
const originalHandlerPyContents = await readFile(originalPyPath, "utf8");
|
|
4009
|
-
(0,
|
|
4116
|
+
(0, import_build_utils7.debug)("Entrypoint is", entrypoint);
|
|
4010
4117
|
const moduleName = entrypoint.replace(/\//g, ".").replace(/\.py$/i, "");
|
|
4011
4118
|
const vendorDir = resolveVendorDir();
|
|
4012
4119
|
const suffix = meta.isDev && !entrypoint.endsWith(".py") ? ".py" : "";
|
|
4013
4120
|
const entrypointWithSuffix = `${entrypoint}${suffix}`;
|
|
4014
|
-
(0,
|
|
4121
|
+
(0, import_build_utils7.debug)("Entrypoint with suffix is", entrypointWithSuffix);
|
|
4015
4122
|
const handlerPyContents = originalHandlerPyContents.replace(/__VC_HANDLER_MODULE_NAME/g, moduleName).replace(/__VC_HANDLER_ENTRYPOINT/g, entrypointWithSuffix).replace(/__VC_HANDLER_VENDOR_DIR/g, vendorDir);
|
|
4016
4123
|
const predefinedExcludes = [
|
|
4017
4124
|
".git/**",
|
|
@@ -4026,7 +4133,10 @@ var build = async ({
|
|
|
4026
4133
|
"**/__pycache__/**",
|
|
4027
4134
|
"**/.mypy_cache/**",
|
|
4028
4135
|
"**/.ruff_cache/**",
|
|
4029
|
-
"**/public/**"
|
|
4136
|
+
"**/public/**",
|
|
4137
|
+
"**/pnpm-lock.yaml",
|
|
4138
|
+
"**/yarn.lock",
|
|
4139
|
+
"**/package-lock.json"
|
|
4030
4140
|
];
|
|
4031
4141
|
const lambdaEnv = {};
|
|
4032
4142
|
lambdaEnv.PYTHONPATH = vendorDir;
|
|
@@ -4034,11 +4144,11 @@ var build = async ({
|
|
|
4034
4144
|
cwd: workPath,
|
|
4035
4145
|
ignore: config && typeof config.excludeFiles === "string" ? [...predefinedExcludes, config.excludeFiles] : predefinedExcludes
|
|
4036
4146
|
};
|
|
4037
|
-
const files = await (0,
|
|
4147
|
+
const files = await (0, import_build_utils7.glob)("**", globOptions);
|
|
4038
4148
|
try {
|
|
4039
4149
|
const cachedVendorAbs = (0, import_path5.join)(vendorBaseDir, resolveVendorDir());
|
|
4040
4150
|
if (import_fs5.default.existsSync(cachedVendorAbs)) {
|
|
4041
|
-
const vendorFiles = await (0,
|
|
4151
|
+
const vendorFiles = await (0, import_build_utils7.glob)("**", cachedVendorAbs, resolveVendorDir());
|
|
4042
4152
|
for (const [p, f] of Object.entries(vendorFiles)) {
|
|
4043
4153
|
files[p] = f;
|
|
4044
4154
|
}
|
|
@@ -4048,12 +4158,12 @@ var build = async ({
|
|
|
4048
4158
|
throw err;
|
|
4049
4159
|
}
|
|
4050
4160
|
const handlerPyFilename = "vc__handler__python";
|
|
4051
|
-
files[`${handlerPyFilename}.py`] = new
|
|
4161
|
+
files[`${handlerPyFilename}.py`] = new import_build_utils7.FileBlob({ data: handlerPyContents });
|
|
4052
4162
|
if (config.framework === "fasthtml") {
|
|
4053
4163
|
const { SESSKEY = "" } = process.env;
|
|
4054
|
-
files[".sesskey"] = new
|
|
4164
|
+
files[".sesskey"] = new import_build_utils7.FileBlob({ data: `"${SESSKEY}"` });
|
|
4055
4165
|
}
|
|
4056
|
-
const output = new
|
|
4166
|
+
const output = new import_build_utils7.Lambda({
|
|
4057
4167
|
files,
|
|
4058
4168
|
handler: `${handlerPyFilename}.vc_handler`,
|
|
4059
4169
|
runtime: pythonVersion.runtime,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/python",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.4",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"@types/jest": "27.4.1",
|
|
22
22
|
"@types/node": "14.18.33",
|
|
23
23
|
"@types/which": "3.0.0",
|
|
24
|
-
"@vercel/build-utils": "13.0.
|
|
24
|
+
"@vercel/build-utils": "13.0.1",
|
|
25
25
|
"cross-env": "7.0.3",
|
|
26
26
|
"execa": "^1.0.0",
|
|
27
27
|
"fs-extra": "11.1.1",
|
package/vc_init.py
CHANGED
|
@@ -210,12 +210,17 @@ if 'VERCEL_IPC_PATH' in os.environ:
|
|
|
210
210
|
|
|
211
211
|
|
|
212
212
|
# Import relative path https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
213
|
+
try:
|
|
214
|
+
user_mod_path = os.path.join(_here, "__VC_HANDLER_ENTRYPOINT") # absolute
|
|
215
|
+
__vc_spec = util.spec_from_file_location("__VC_HANDLER_MODULE_NAME", user_mod_path)
|
|
216
|
+
__vc_module = util.module_from_spec(__vc_spec)
|
|
217
|
+
sys.modules["__VC_HANDLER_MODULE_NAME"] = __vc_module
|
|
218
|
+
__vc_spec.loader.exec_module(__vc_module)
|
|
219
|
+
__vc_variables = dir(__vc_module)
|
|
220
|
+
except Exception:
|
|
221
|
+
_stderr(f'Error importing __VC_HANDLER_ENTRYPOINT:')
|
|
222
|
+
_stderr(traceback.format_exc())
|
|
223
|
+
exit(1)
|
|
219
224
|
|
|
220
225
|
_use_legacy_asyncio = sys.version_info < (3, 10)
|
|
221
226
|
|
|
@@ -231,6 +236,95 @@ def format_headers(headers, decode=False):
|
|
|
231
236
|
return keyToList
|
|
232
237
|
|
|
233
238
|
|
|
239
|
+
class ASGIMiddleware:
|
|
240
|
+
"""
|
|
241
|
+
ASGI middleware that preserves Vercel IPC semantics for request lifecycle:
|
|
242
|
+
- Handles /_vercel/ping
|
|
243
|
+
- Extracts x-vercel-internal-* headers and removes them from downstream app
|
|
244
|
+
- Sets request context into `storage` for logging/metrics
|
|
245
|
+
- Emits handler-started and end IPC messages
|
|
246
|
+
"""
|
|
247
|
+
def __init__(self, app):
|
|
248
|
+
self.app = app
|
|
249
|
+
|
|
250
|
+
async def __call__(self, scope, receive, send):
|
|
251
|
+
if scope.get('type') != 'http':
|
|
252
|
+
# Non-HTTP traffic is forwarded verbatim
|
|
253
|
+
await self.app(scope, receive, send)
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
if scope.get('path') == '/_vercel/ping':
|
|
257
|
+
await send({
|
|
258
|
+
'type': 'http.response.start',
|
|
259
|
+
'status': 200,
|
|
260
|
+
'headers': [],
|
|
261
|
+
})
|
|
262
|
+
await send({
|
|
263
|
+
'type': 'http.response.body',
|
|
264
|
+
'body': b'',
|
|
265
|
+
'more_body': False,
|
|
266
|
+
})
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
# Extract internal headers and set per-request context
|
|
270
|
+
headers_list = scope.get('headers', []) or []
|
|
271
|
+
new_headers = []
|
|
272
|
+
invocation_id = "0"
|
|
273
|
+
request_id = 0
|
|
274
|
+
|
|
275
|
+
def _b2s(b: bytes) -> str:
|
|
276
|
+
try:
|
|
277
|
+
return b.decode()
|
|
278
|
+
except Exception:
|
|
279
|
+
return ''
|
|
280
|
+
|
|
281
|
+
for k, v in headers_list:
|
|
282
|
+
key = _b2s(k).lower()
|
|
283
|
+
val = _b2s(v)
|
|
284
|
+
if key == 'x-vercel-internal-invocation-id':
|
|
285
|
+
invocation_id = val
|
|
286
|
+
continue
|
|
287
|
+
if key == 'x-vercel-internal-request-id':
|
|
288
|
+
request_id = int(val) if val.isdigit() else 0
|
|
289
|
+
continue
|
|
290
|
+
if key in ('x-vercel-internal-span-id', 'x-vercel-internal-trace-id'):
|
|
291
|
+
continue
|
|
292
|
+
new_headers.append((k, v))
|
|
293
|
+
|
|
294
|
+
new_scope = dict(scope)
|
|
295
|
+
new_scope['headers'] = new_headers
|
|
296
|
+
|
|
297
|
+
# Announce handler start and set context for logging/metrics
|
|
298
|
+
send_message({
|
|
299
|
+
"type": "handler-started",
|
|
300
|
+
"payload": {
|
|
301
|
+
"handlerStartedAt": int(time.time() * 1000),
|
|
302
|
+
"context": {
|
|
303
|
+
"invocationId": invocation_id,
|
|
304
|
+
"requestId": request_id,
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
token = storage.set({
|
|
310
|
+
"invocationId": invocation_id,
|
|
311
|
+
"requestId": request_id,
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
await self.app(new_scope, receive, send)
|
|
316
|
+
finally:
|
|
317
|
+
storage.reset(token)
|
|
318
|
+
send_message({
|
|
319
|
+
"type": "end",
|
|
320
|
+
"payload": {
|
|
321
|
+
"context": {
|
|
322
|
+
"invocationId": invocation_id,
|
|
323
|
+
"requestId": request_id,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
|
|
234
328
|
if 'VERCEL_IPC_PATH' in os.environ:
|
|
235
329
|
start_time = time.time()
|
|
236
330
|
|
|
@@ -424,80 +518,53 @@ if 'VERCEL_IPC_PATH' in os.environ:
|
|
|
424
518
|
if hasattr(response, 'close'):
|
|
425
519
|
response.close()
|
|
426
520
|
else:
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
'method': self.command,
|
|
456
|
-
'path': url.path,
|
|
457
|
-
'raw_path': url.path.encode(),
|
|
458
|
-
}
|
|
521
|
+
# ASGI: Run with Uvicorn so we get proper lifespan and protocol handling
|
|
522
|
+
try:
|
|
523
|
+
import uvicorn
|
|
524
|
+
except Exception:
|
|
525
|
+
_stderr('Uvicorn is required to run ASGI apps. Please ensure it is installed.')
|
|
526
|
+
exit(1)
|
|
527
|
+
|
|
528
|
+
# Prefer a callable app.asgi when available; some frameworks expose a boolean here
|
|
529
|
+
user_app_candidate = getattr(__vc_module.app, 'asgi', None)
|
|
530
|
+
user_app = user_app_candidate if callable(user_app_candidate) else __vc_module.app
|
|
531
|
+
asgi_app = ASGIMiddleware(user_app)
|
|
532
|
+
|
|
533
|
+
# Pre-bind a socket to obtain an ephemeral port for IPC announcement
|
|
534
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
535
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
536
|
+
sock.bind(('127.0.0.1', 0))
|
|
537
|
+
sock.listen(2048)
|
|
538
|
+
http_port = sock.getsockname()[1]
|
|
539
|
+
|
|
540
|
+
config = uvicorn.Config(
|
|
541
|
+
app=asgi_app,
|
|
542
|
+
fd=sock.fileno(),
|
|
543
|
+
lifespan='auto',
|
|
544
|
+
access_log=False,
|
|
545
|
+
log_config=None,
|
|
546
|
+
log_level='warning',
|
|
547
|
+
)
|
|
548
|
+
server = uvicorn.Server(config)
|
|
459
549
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
550
|
+
send_message({
|
|
551
|
+
"type": "server-started",
|
|
552
|
+
"payload": {
|
|
553
|
+
"initDuration": int((time.time() - start_time) * 1000),
|
|
554
|
+
"httpPort": http_port,
|
|
555
|
+
}
|
|
556
|
+
})
|
|
465
557
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
app_queue.put_nowait({'type': 'http.request', 'body': body, 'more_body': False})
|
|
472
|
-
|
|
473
|
-
# Prepare ASGI receive function
|
|
474
|
-
async def receive():
|
|
475
|
-
message = await app_queue.get()
|
|
476
|
-
return message
|
|
477
|
-
|
|
478
|
-
# Prepare ASGI send function
|
|
479
|
-
response_started = False
|
|
480
|
-
async def send(event):
|
|
481
|
-
nonlocal response_started
|
|
482
|
-
if event['type'] == 'http.response.start':
|
|
483
|
-
self.send_response(event['status'])
|
|
484
|
-
if 'headers' in event:
|
|
485
|
-
for name, value in event['headers']:
|
|
486
|
-
self.send_header(name.decode(), value.decode())
|
|
487
|
-
self.end_headers()
|
|
488
|
-
response_started = True
|
|
489
|
-
elif event['type'] == 'http.response.body':
|
|
490
|
-
self.wfile.write(event['body'])
|
|
491
|
-
if not event.get('more_body', False):
|
|
492
|
-
self.wfile.flush()
|
|
558
|
+
# Mark IPC as ready and flush any buffered init logs
|
|
559
|
+
_ipc_ready = True
|
|
560
|
+
for m in _init_log_buf:
|
|
561
|
+
send_message(m)
|
|
562
|
+
_init_log_buf.clear()
|
|
493
563
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
loop.run_until_complete(asgi_task)
|
|
499
|
-
else:
|
|
500
|
-
asyncio.run(asgi_instance)
|
|
564
|
+
# Run the server (blocking)
|
|
565
|
+
server.run()
|
|
566
|
+
# If the server ever returns, exit
|
|
567
|
+
sys.exit(0)
|
|
501
568
|
|
|
502
569
|
if 'Handler' in locals():
|
|
503
570
|
server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
|
package/vc_init_dev_asgi.py
CHANGED
|
@@ -39,8 +39,9 @@ if _app is None:
|
|
|
39
39
|
f"Missing 'app' in module '{USER_MODULE}'. Define `app = ...` (ASGI app)."
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
|
|
42
|
+
# Prefer a callable app.asgi when available; some frameworks expose a boolean here
|
|
43
|
+
_CAND = getattr(_app, 'asgi', None)
|
|
44
|
+
USER_ASGI_APP = _CAND if callable(_CAND) else _app
|
|
44
45
|
|
|
45
46
|
PUBLIC_DIR = 'public'
|
|
46
47
|
|