@vercel/python 5.0.10 → 6.0.1
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 +113 -87
- package/package.json +2 -2
- package/vc_init.py +165 -79
package/dist/index.js
CHANGED
|
@@ -2651,7 +2651,7 @@ ${stderr}${stdout}`;
|
|
|
2651
2651
|
var require_lib = __commonJS({
|
|
2652
2652
|
"../../node_modules/.pnpm/which@3.0.0/node_modules/which/lib/index.js"(exports, module2) {
|
|
2653
2653
|
var isexe = require_isexe();
|
|
2654
|
-
var { join:
|
|
2654
|
+
var { join: join6, delimiter, sep, posix } = require("path");
|
|
2655
2655
|
var isWindows = process.platform === "win32";
|
|
2656
2656
|
var rSlash = new RegExp(`[${posix.sep}${sep === posix.sep ? "" : sep}]`.replace(/(\\)/g, "\\$1"));
|
|
2657
2657
|
var rRel = new RegExp(`^\\.${rSlash.source}`);
|
|
@@ -2680,7 +2680,7 @@ var require_lib = __commonJS({
|
|
|
2680
2680
|
var getPathPart = (raw, cmd) => {
|
|
2681
2681
|
const pathPart = /^".*"$/.test(raw) ? raw.slice(1, -1) : raw;
|
|
2682
2682
|
const prefix = !pathPart && rRel.test(cmd) ? cmd.slice(0, 2) : "";
|
|
2683
|
-
return prefix +
|
|
2683
|
+
return prefix + join6(pathPart, cmd);
|
|
2684
2684
|
};
|
|
2685
2685
|
var which3 = async (cmd, opt = {}) => {
|
|
2686
2686
|
const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
|
|
@@ -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_utils6 = require("@vercel/build-utils");
|
|
2755
2755
|
|
|
2756
2756
|
// src/install.ts
|
|
2757
2757
|
var import_execa = __toESM(require_execa());
|
|
@@ -3066,7 +3066,7 @@ async function exportRequirementsFromPipfile({
|
|
|
3066
3066
|
}
|
|
3067
3067
|
|
|
3068
3068
|
// src/index.ts
|
|
3069
|
-
var
|
|
3069
|
+
var import_build_utils7 = require("@vercel/build-utils");
|
|
3070
3070
|
|
|
3071
3071
|
// src/version.ts
|
|
3072
3072
|
var import_build_utils2 = require("@vercel/build-utils");
|
|
@@ -3199,14 +3199,15 @@ function isInstalled2({ pipPath, pythonPath }) {
|
|
|
3199
3199
|
var import_child_process = require("child_process");
|
|
3200
3200
|
var import_fs4 = require("fs");
|
|
3201
3201
|
var import_path4 = require("path");
|
|
3202
|
-
var
|
|
3202
|
+
var import_build_utils5 = require("@vercel/build-utils");
|
|
3203
3203
|
|
|
3204
3204
|
// src/entrypoint.ts
|
|
3205
3205
|
var import_fs2 = __toESM(require("fs"));
|
|
3206
3206
|
var import_path2 = require("path");
|
|
3207
3207
|
var import_build_utils3 = require("@vercel/build-utils");
|
|
3208
|
+
var import_build_utils4 = require("@vercel/build-utils");
|
|
3208
3209
|
var FASTAPI_ENTRYPOINT_FILENAMES = ["app", "index", "server", "main"];
|
|
3209
|
-
var FASTAPI_ENTRYPOINT_DIRS = ["", "src", "app"];
|
|
3210
|
+
var FASTAPI_ENTRYPOINT_DIRS = ["", "src", "app", "api"];
|
|
3210
3211
|
var FASTAPI_CONTENT_REGEX = /(from\s+fastapi\s+import\s+FastAPI|import\s+fastapi|FastAPI\s*\()/;
|
|
3211
3212
|
var FASTAPI_CANDIDATE_ENTRYPOINTS = FASTAPI_ENTRYPOINT_FILENAMES.flatMap(
|
|
3212
3213
|
(filename) => FASTAPI_ENTRYPOINT_DIRS.map(
|
|
@@ -3225,7 +3226,7 @@ function isFastapiEntrypoint(file) {
|
|
|
3225
3226
|
}
|
|
3226
3227
|
}
|
|
3227
3228
|
var FLASK_ENTRYPOINT_FILENAMES = ["app", "index", "server", "main"];
|
|
3228
|
-
var FLASK_ENTRYPOINT_DIRS = ["", "src", "app"];
|
|
3229
|
+
var FLASK_ENTRYPOINT_DIRS = ["", "src", "app", "api"];
|
|
3229
3230
|
var FLASK_CONTENT_REGEX = /(from\s+flask\s+import\s+Flask|import\s+flask|Flask\s*\()/;
|
|
3230
3231
|
var FLASK_CANDIDATE_ENTRYPOINTS = FLASK_ENTRYPOINT_FILENAMES.flatMap(
|
|
3231
3232
|
(filename) => FLASK_ENTRYPOINT_DIRS.map(
|
|
@@ -3287,6 +3288,43 @@ async function detectFastapiEntrypoint(workPath, configuredEntrypoint) {
|
|
|
3287
3288
|
return null;
|
|
3288
3289
|
}
|
|
3289
3290
|
}
|
|
3291
|
+
async function getPyprojectEntrypoint(workPath) {
|
|
3292
|
+
const pyprojectData = await (0, import_build_utils4.readConfigFile)((0, import_path2.join)(workPath, "pyproject.toml"));
|
|
3293
|
+
if (!pyprojectData)
|
|
3294
|
+
return null;
|
|
3295
|
+
const scripts = pyprojectData.project?.scripts;
|
|
3296
|
+
const appScript = scripts?.app;
|
|
3297
|
+
if (typeof appScript !== "string")
|
|
3298
|
+
return null;
|
|
3299
|
+
const match = appScript.match(/([A-Za-z_][\w.]*)\s*:\s*([A-Za-z_][\w]*)/);
|
|
3300
|
+
if (!match)
|
|
3301
|
+
return null;
|
|
3302
|
+
const modulePath = match[1];
|
|
3303
|
+
const relPath = modulePath.replace(/\./g, "/");
|
|
3304
|
+
try {
|
|
3305
|
+
const fsFiles = await (0, import_build_utils3.glob)("**", workPath);
|
|
3306
|
+
const candidates = [`${relPath}.py`, `${relPath}/__init__.py`];
|
|
3307
|
+
for (const candidate of candidates) {
|
|
3308
|
+
if (fsFiles[candidate])
|
|
3309
|
+
return candidate;
|
|
3310
|
+
}
|
|
3311
|
+
return null;
|
|
3312
|
+
} catch {
|
|
3313
|
+
(0, import_build_utils3.debug)("Failed to discover Python entrypoint from pyproject.toml");
|
|
3314
|
+
return null;
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint) {
|
|
3318
|
+
let entrypoint = null;
|
|
3319
|
+
if (framework === "fastapi") {
|
|
3320
|
+
entrypoint = await detectFastapiEntrypoint(workPath, configuredEntrypoint);
|
|
3321
|
+
} else if (framework === "flask") {
|
|
3322
|
+
entrypoint = await detectFlaskEntrypoint(workPath, configuredEntrypoint);
|
|
3323
|
+
}
|
|
3324
|
+
if (entrypoint)
|
|
3325
|
+
return entrypoint;
|
|
3326
|
+
return await getPyprojectEntrypoint(workPath);
|
|
3327
|
+
}
|
|
3290
3328
|
|
|
3291
3329
|
// src/utils.ts
|
|
3292
3330
|
var import_fs3 = __toESM(require("fs"));
|
|
@@ -3355,12 +3393,12 @@ function installGlobalCleanupHandlers() {
|
|
|
3355
3393
|
try {
|
|
3356
3394
|
process.kill(info.pid, "SIGTERM");
|
|
3357
3395
|
} catch (err) {
|
|
3358
|
-
(0,
|
|
3396
|
+
(0, import_build_utils5.debug)(`Error sending SIGTERM to ${info.pid}: ${err}`);
|
|
3359
3397
|
}
|
|
3360
3398
|
try {
|
|
3361
3399
|
process.kill(info.pid, "SIGKILL");
|
|
3362
3400
|
} catch (err) {
|
|
3363
|
-
(0,
|
|
3401
|
+
(0, import_build_utils5.debug)(`Error sending SIGKILL to ${info.pid}: ${err}`);
|
|
3364
3402
|
}
|
|
3365
3403
|
PERSISTENT_SERVERS.delete(key);
|
|
3366
3404
|
}
|
|
@@ -3368,7 +3406,7 @@ function installGlobalCleanupHandlers() {
|
|
|
3368
3406
|
try {
|
|
3369
3407
|
restoreWarnings();
|
|
3370
3408
|
} catch (err) {
|
|
3371
|
-
(0,
|
|
3409
|
+
(0, import_build_utils5.debug)(`Error restoring warnings: ${err}`);
|
|
3372
3410
|
}
|
|
3373
3411
|
restoreWarnings = null;
|
|
3374
3412
|
}
|
|
@@ -3394,10 +3432,10 @@ function createDevAsgiShim(workPath, modulePath) {
|
|
|
3394
3432
|
const template = (0, import_fs4.readFileSync)(templatePath, "utf8");
|
|
3395
3433
|
const shimSource = template.replace(/__VC_DEV_MODULE_PATH__/g, modulePath);
|
|
3396
3434
|
(0, import_fs4.writeFileSync)(shimPath, shimSource, "utf8");
|
|
3397
|
-
(0,
|
|
3435
|
+
(0, import_build_utils5.debug)(`Prepared Python dev static shim at ${shimPath}`);
|
|
3398
3436
|
return ASGI_SHIM_MODULE;
|
|
3399
3437
|
} catch (err) {
|
|
3400
|
-
(0,
|
|
3438
|
+
(0, import_build_utils5.debug)(`Failed to prepare dev static shim: ${err?.message || err}`);
|
|
3401
3439
|
return null;
|
|
3402
3440
|
}
|
|
3403
3441
|
}
|
|
@@ -3410,10 +3448,10 @@ function createDevWsgiShim(workPath, modulePath) {
|
|
|
3410
3448
|
const template = (0, import_fs4.readFileSync)(templatePath, "utf8");
|
|
3411
3449
|
const shimSource = template.replace(/__VC_DEV_MODULE_PATH__/g, modulePath);
|
|
3412
3450
|
(0, import_fs4.writeFileSync)(shimPath, shimSource, "utf8");
|
|
3413
|
-
(0,
|
|
3451
|
+
(0, import_build_utils5.debug)(`Prepared Python dev WSGI shim at ${shimPath}`);
|
|
3414
3452
|
return WSGI_SHIM_MODULE;
|
|
3415
3453
|
} catch (err) {
|
|
3416
|
-
(0,
|
|
3454
|
+
(0, import_build_utils5.debug)(`Failed to prepare dev WSGI shim: ${err?.message || err}`);
|
|
3417
3455
|
return null;
|
|
3418
3456
|
}
|
|
3419
3457
|
}
|
|
@@ -3426,26 +3464,19 @@ var startDevServer = async (opts) => {
|
|
|
3426
3464
|
if (!restoreWarnings)
|
|
3427
3465
|
restoreWarnings = silenceNodeWarnings();
|
|
3428
3466
|
installGlobalCleanupHandlers();
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
const detectedFlask = await detectFlaskEntrypoint(workPath, rawEntrypoint);
|
|
3443
|
-
if (!detectedFlask) {
|
|
3444
|
-
throw new Error(
|
|
3445
|
-
`No Flask entrypoint found. Searched for: ${FLASK_CANDIDATE_ENTRYPOINTS.join(", ")}`
|
|
3446
|
-
);
|
|
3447
|
-
}
|
|
3448
|
-
entry = detectedFlask;
|
|
3467
|
+
const entry = await detectPythonEntrypoint(
|
|
3468
|
+
framework,
|
|
3469
|
+
workPath,
|
|
3470
|
+
rawEntrypoint
|
|
3471
|
+
);
|
|
3472
|
+
if (!entry) {
|
|
3473
|
+
const searched = framework === "fastapi" ? FASTAPI_CANDIDATE_ENTRYPOINTS.join(", ") : FLASK_CANDIDATE_ENTRYPOINTS.join(", ");
|
|
3474
|
+
throw new import_build_utils5.NowBuildError({
|
|
3475
|
+
code: "PYTHON_ENTRYPOINT_NOT_FOUND",
|
|
3476
|
+
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
|
+
link: `https://vercel.com/docs/frameworks/backend/${framework?.toLowerCase()}#exporting-the-${framework?.toLowerCase()}-application`,
|
|
3478
|
+
action: "Learn More"
|
|
3479
|
+
});
|
|
3449
3480
|
}
|
|
3450
3481
|
const modulePath = entry.replace(/\.py$/i, "").replace(/[\\/]/g, ".");
|
|
3451
3482
|
const env = { ...process.env, ...meta.env || {} };
|
|
@@ -3490,7 +3521,7 @@ var startDevServer = async (opts) => {
|
|
|
3490
3521
|
let pythonCmd = systemPython;
|
|
3491
3522
|
const venv = isInVirtualEnv();
|
|
3492
3523
|
if (venv) {
|
|
3493
|
-
(0,
|
|
3524
|
+
(0, import_build_utils5.debug)(`Running in virtualenv at ${venv}`);
|
|
3494
3525
|
} else {
|
|
3495
3526
|
const { pythonCmd: venvPythonCmd, venvRoot } = useVirtualEnv(
|
|
3496
3527
|
workPath,
|
|
@@ -3499,9 +3530,9 @@ var startDevServer = async (opts) => {
|
|
|
3499
3530
|
);
|
|
3500
3531
|
pythonCmd = venvPythonCmd;
|
|
3501
3532
|
if (venvRoot) {
|
|
3502
|
-
(0,
|
|
3533
|
+
(0, import_build_utils5.debug)(`Using virtualenv at ${venvRoot}`);
|
|
3503
3534
|
} else {
|
|
3504
|
-
(0,
|
|
3535
|
+
(0, import_build_utils5.debug)("No virtualenv found");
|
|
3505
3536
|
try {
|
|
3506
3537
|
const yellow = "\x1B[33m";
|
|
3507
3538
|
const reset = "\x1B[0m";
|
|
@@ -3524,7 +3555,7 @@ If you are using a virtual environment, activate it before running "vercel dev",
|
|
|
3524
3555
|
}
|
|
3525
3556
|
const moduleToRun = devShimModule || modulePath;
|
|
3526
3557
|
const argv = ["-u", "-m", moduleToRun];
|
|
3527
|
-
(0,
|
|
3558
|
+
(0, import_build_utils5.debug)(`Starting ASGI dev server: ${pythonCmd} ${argv.join(" ")}`);
|
|
3528
3559
|
const child = (0, import_child_process.spawn)(pythonCmd, argv, {
|
|
3529
3560
|
cwd: workPath,
|
|
3530
3561
|
env,
|
|
@@ -3602,7 +3633,7 @@ If you are using a virtual environment, activate it before running "vercel dev",
|
|
|
3602
3633
|
}
|
|
3603
3634
|
const moduleToRun = devShimModule || modulePath;
|
|
3604
3635
|
const argv = ["-u", "-m", moduleToRun];
|
|
3605
|
-
(0,
|
|
3636
|
+
(0, import_build_utils5.debug)(`Starting Flask dev server: ${pythonCmd} ${argv.join(" ")}`);
|
|
3606
3637
|
const child = (0, import_child_process.spawn)(pythonCmd, argv, {
|
|
3607
3638
|
cwd: workPath,
|
|
3608
3639
|
env,
|
|
@@ -3712,13 +3743,13 @@ async function downloadFilesInWorkPath({
|
|
|
3712
3743
|
files,
|
|
3713
3744
|
meta = {}
|
|
3714
3745
|
}) {
|
|
3715
|
-
(0,
|
|
3716
|
-
let downloadedFiles = await (0,
|
|
3746
|
+
(0, import_build_utils6.debug)("Downloading user files...");
|
|
3747
|
+
let downloadedFiles = await (0, import_build_utils6.download)(files, workPath, meta);
|
|
3717
3748
|
if (meta.isDev) {
|
|
3718
3749
|
const { devCacheDir = (0, import_path5.join)(workPath, ".now", "cache") } = meta;
|
|
3719
3750
|
const destCache = (0, import_path5.join)(devCacheDir, (0, import_path5.basename)(entrypoint, ".py"));
|
|
3720
|
-
await (0,
|
|
3721
|
-
downloadedFiles = await (0,
|
|
3751
|
+
await (0, import_build_utils6.download)(downloadedFiles, destCache);
|
|
3752
|
+
downloadedFiles = await (0, import_build_utils6.glob)("**", destCache);
|
|
3722
3753
|
workPath = destCache;
|
|
3723
3754
|
}
|
|
3724
3755
|
return workPath;
|
|
@@ -3730,6 +3761,7 @@ var build = async ({
|
|
|
3730
3761
|
meta = {},
|
|
3731
3762
|
config
|
|
3732
3763
|
}) => {
|
|
3764
|
+
const framework = config?.framework;
|
|
3733
3765
|
workPath = await downloadFilesInWorkPath({
|
|
3734
3766
|
workPath,
|
|
3735
3767
|
files: originalFiles,
|
|
@@ -3745,33 +3777,25 @@ var build = async ({
|
|
|
3745
3777
|
console.log('Failed to create "setup.cfg" file');
|
|
3746
3778
|
throw err;
|
|
3747
3779
|
}
|
|
3748
|
-
let fsFiles = await (0,
|
|
3749
|
-
if (!fsFiles[entrypoint]
|
|
3750
|
-
const detected = await
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
entrypoint = detected;
|
|
3756
|
-
} else {
|
|
3757
|
-
const searchedList = FASTAPI_CANDIDATE_ENTRYPOINTS.join(", ");
|
|
3758
|
-
throw new import_build_utils5.NowBuildError({
|
|
3759
|
-
code: "FASTAPI_ENTRYPOINT_NOT_FOUND",
|
|
3760
|
-
message: `No FastAPI entrypoint found. Searched for: ${searchedList}`
|
|
3761
|
-
});
|
|
3762
|
-
}
|
|
3763
|
-
} else if (!fsFiles[entrypoint] && config?.framework === "flask") {
|
|
3764
|
-
const detected = await detectFlaskEntrypoint(workPath, entrypoint);
|
|
3780
|
+
let fsFiles = await (0, import_build_utils6.glob)("**", workPath);
|
|
3781
|
+
if ((framework === "fastapi" || framework === "flask") && (!fsFiles[entrypoint] || !entrypoint.endsWith(".py"))) {
|
|
3782
|
+
const detected = await detectPythonEntrypoint(
|
|
3783
|
+
config.framework,
|
|
3784
|
+
workPath,
|
|
3785
|
+
entrypoint
|
|
3786
|
+
);
|
|
3765
3787
|
if (detected) {
|
|
3766
|
-
(0,
|
|
3788
|
+
(0, import_build_utils6.debug)(
|
|
3767
3789
|
`Resolved Python entrypoint to "${detected}" (configured "${entrypoint}" not found).`
|
|
3768
3790
|
);
|
|
3769
3791
|
entrypoint = detected;
|
|
3770
3792
|
} else {
|
|
3771
|
-
const searchedList = FLASK_CANDIDATE_ENTRYPOINTS.join(", ");
|
|
3772
|
-
throw new
|
|
3773
|
-
code:
|
|
3774
|
-
message: `No
|
|
3793
|
+
const searchedList = framework === "fastapi" ? FASTAPI_CANDIDATE_ENTRYPOINTS.join(", ") : FLASK_CANDIDATE_ENTRYPOINTS.join(", ");
|
|
3794
|
+
throw new import_build_utils6.NowBuildError({
|
|
3795
|
+
code: `${framework.toUpperCase()}_ENTRYPOINT_NOT_FOUND`,
|
|
3796
|
+
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
|
+
link: `https://vercel.com/docs/frameworks/backend/${framework}#exporting-the-${framework}-application`,
|
|
3798
|
+
action: "Learn More"
|
|
3775
3799
|
});
|
|
3776
3800
|
}
|
|
3777
3801
|
}
|
|
@@ -3796,20 +3820,20 @@ var build = async ({
|
|
|
3796
3820
|
if (pyprojectDir) {
|
|
3797
3821
|
let requiresPython;
|
|
3798
3822
|
try {
|
|
3799
|
-
const pyproject = await (0,
|
|
3823
|
+
const pyproject = await (0, import_build_utils7.readConfigFile)((0, import_path5.join)(pyprojectDir, "pyproject.toml"));
|
|
3800
3824
|
requiresPython = pyproject?.project?.["requires-python"];
|
|
3801
3825
|
} catch (err) {
|
|
3802
|
-
(0,
|
|
3826
|
+
(0, import_build_utils6.debug)("Failed to parse pyproject.toml", err);
|
|
3803
3827
|
}
|
|
3804
3828
|
const VERSION_REGEX = /\b\d+\.\d+\b/;
|
|
3805
3829
|
const exact = requiresPython?.trim().match(VERSION_REGEX)?.[0];
|
|
3806
3830
|
if (exact) {
|
|
3807
3831
|
declaredPythonVersion = { version: exact, source: "pyproject.toml" };
|
|
3808
|
-
(0,
|
|
3832
|
+
(0, import_build_utils6.debug)(
|
|
3809
3833
|
`Found Python version ${exact} in pyproject.toml (requires-python: "${requiresPython}")`
|
|
3810
3834
|
);
|
|
3811
3835
|
} else if (requiresPython) {
|
|
3812
|
-
(0,
|
|
3836
|
+
(0, import_build_utils6.debug)(
|
|
3813
3837
|
`Could not parse Python version from pyproject.toml requires-python: "${requiresPython}"`
|
|
3814
3838
|
);
|
|
3815
3839
|
}
|
|
@@ -3819,7 +3843,7 @@ var build = async ({
|
|
|
3819
3843
|
const json = await readFile((0, import_path5.join)(pipfileLockDir, "Pipfile.lock"), "utf8");
|
|
3820
3844
|
lock = JSON.parse(json);
|
|
3821
3845
|
} catch (err) {
|
|
3822
|
-
throw new
|
|
3846
|
+
throw new import_build_utils6.NowBuildError({
|
|
3823
3847
|
code: "INVALID_PIPFILE_LOCK",
|
|
3824
3848
|
message: "Unable to parse Pipfile.lock"
|
|
3825
3849
|
});
|
|
@@ -3827,14 +3851,14 @@ var build = async ({
|
|
|
3827
3851
|
const pyFromLock = lock?._meta?.requires?.python_version;
|
|
3828
3852
|
if (pyFromLock) {
|
|
3829
3853
|
declaredPythonVersion = { version: pyFromLock, source: "Pipfile.lock" };
|
|
3830
|
-
(0,
|
|
3854
|
+
(0, import_build_utils6.debug)(`Found Python version ${pyFromLock} in Pipfile.lock`);
|
|
3831
3855
|
}
|
|
3832
3856
|
}
|
|
3833
3857
|
const pythonVersion = getSupportedPythonVersion({
|
|
3834
3858
|
isDev: meta.isDev,
|
|
3835
3859
|
declaredPythonVersion
|
|
3836
3860
|
});
|
|
3837
|
-
fsFiles = await (0,
|
|
3861
|
+
fsFiles = await (0, import_build_utils6.glob)("**", workPath);
|
|
3838
3862
|
const requirementsTxt = (0, import_path5.join)(entryDirectory, "requirements.txt");
|
|
3839
3863
|
const vendorBaseDir = (0, import_path5.join)(
|
|
3840
3864
|
workPath,
|
|
@@ -3879,7 +3903,7 @@ var build = async ({
|
|
|
3879
3903
|
`uv is required for this project but failed to install: ${err instanceof Error ? err.message : String(err)}`
|
|
3880
3904
|
);
|
|
3881
3905
|
}
|
|
3882
|
-
(0,
|
|
3906
|
+
(0, import_build_utils6.debug)("Failed to install uv", err);
|
|
3883
3907
|
}
|
|
3884
3908
|
await installRequirement({
|
|
3885
3909
|
pythonPath: pythonVersion.pythonPath,
|
|
@@ -3893,7 +3917,7 @@ var build = async ({
|
|
|
3893
3917
|
});
|
|
3894
3918
|
let installedFromProjectFiles = false;
|
|
3895
3919
|
if (uvLockDir) {
|
|
3896
|
-
(0,
|
|
3920
|
+
(0, import_build_utils6.debug)('Found "uv.lock"');
|
|
3897
3921
|
if (pyprojectDir) {
|
|
3898
3922
|
const exportedReq = await exportRequirementsFromUv(pyprojectDir, uvPath, {
|
|
3899
3923
|
locked: true
|
|
@@ -3909,10 +3933,10 @@ var build = async ({
|
|
|
3909
3933
|
});
|
|
3910
3934
|
installedFromProjectFiles = true;
|
|
3911
3935
|
} else {
|
|
3912
|
-
(0,
|
|
3936
|
+
(0, import_build_utils6.debug)('Skipping uv export because "pyproject.toml" was not found');
|
|
3913
3937
|
}
|
|
3914
3938
|
} else if (pyprojectDir) {
|
|
3915
|
-
(0,
|
|
3939
|
+
(0, import_build_utils6.debug)('Found "pyproject.toml"');
|
|
3916
3940
|
if (hasReqLocal || hasReqGlobal) {
|
|
3917
3941
|
console.log(
|
|
3918
3942
|
"Detected both pyproject.toml and requirements.txt but no lockfile; using pyproject.toml"
|
|
@@ -3932,9 +3956,9 @@ var build = async ({
|
|
|
3932
3956
|
});
|
|
3933
3957
|
installedFromProjectFiles = true;
|
|
3934
3958
|
} else if (pipfileLockDir || pipfileDir) {
|
|
3935
|
-
(0,
|
|
3959
|
+
(0, import_build_utils6.debug)(`Found ${pipfileLockDir ? '"Pipfile.lock"' : '"Pipfile"'}`);
|
|
3936
3960
|
if (hasReqLocal || hasReqGlobal) {
|
|
3937
|
-
(0,
|
|
3961
|
+
(0, import_build_utils6.debug)('Skipping Pipfile export because "requirements.txt" exists');
|
|
3938
3962
|
} else {
|
|
3939
3963
|
const exportedReq = await exportRequirementsFromPipfile({
|
|
3940
3964
|
pythonPath: pythonVersion.pythonPath,
|
|
@@ -3956,7 +3980,7 @@ var build = async ({
|
|
|
3956
3980
|
}
|
|
3957
3981
|
}
|
|
3958
3982
|
if (!installedFromProjectFiles && fsFiles[requirementsTxt]) {
|
|
3959
|
-
(0,
|
|
3983
|
+
(0, import_build_utils6.debug)('Found local "requirements.txt"');
|
|
3960
3984
|
const requirementsTxtPath = fsFiles[requirementsTxt].fsPath;
|
|
3961
3985
|
await installRequirementsFile({
|
|
3962
3986
|
pythonPath: pythonVersion.pythonPath,
|
|
@@ -3968,7 +3992,7 @@ var build = async ({
|
|
|
3968
3992
|
meta
|
|
3969
3993
|
});
|
|
3970
3994
|
} else if (!installedFromProjectFiles && fsFiles["requirements.txt"]) {
|
|
3971
|
-
(0,
|
|
3995
|
+
(0, import_build_utils6.debug)('Found global "requirements.txt"');
|
|
3972
3996
|
const requirementsTxtPath = fsFiles["requirements.txt"].fsPath;
|
|
3973
3997
|
await installRequirementsFile({
|
|
3974
3998
|
pythonPath: pythonVersion.pythonPath,
|
|
@@ -3982,12 +4006,12 @@ var build = async ({
|
|
|
3982
4006
|
}
|
|
3983
4007
|
const originalPyPath = (0, import_path5.join)(__dirname, "..", "vc_init.py");
|
|
3984
4008
|
const originalHandlerPyContents = await readFile(originalPyPath, "utf8");
|
|
3985
|
-
(0,
|
|
4009
|
+
(0, import_build_utils6.debug)("Entrypoint is", entrypoint);
|
|
3986
4010
|
const moduleName = entrypoint.replace(/\//g, ".").replace(/\.py$/i, "");
|
|
3987
4011
|
const vendorDir = resolveVendorDir();
|
|
3988
4012
|
const suffix = meta.isDev && !entrypoint.endsWith(".py") ? ".py" : "";
|
|
3989
4013
|
const entrypointWithSuffix = `${entrypoint}${suffix}`;
|
|
3990
|
-
(0,
|
|
4014
|
+
(0, import_build_utils6.debug)("Entrypoint with suffix is", entrypointWithSuffix);
|
|
3991
4015
|
const handlerPyContents = originalHandlerPyContents.replace(/__VC_HANDLER_MODULE_NAME/g, moduleName).replace(/__VC_HANDLER_ENTRYPOINT/g, entrypointWithSuffix).replace(/__VC_HANDLER_VENDOR_DIR/g, vendorDir);
|
|
3992
4016
|
const predefinedExcludes = [
|
|
3993
4017
|
".git/**",
|
|
@@ -4000,6 +4024,8 @@ var build = async ({
|
|
|
4000
4024
|
"**/.venv/**",
|
|
4001
4025
|
"**/venv/**",
|
|
4002
4026
|
"**/__pycache__/**",
|
|
4027
|
+
"**/.mypy_cache/**",
|
|
4028
|
+
"**/.ruff_cache/**",
|
|
4003
4029
|
"**/public/**"
|
|
4004
4030
|
];
|
|
4005
4031
|
const lambdaEnv = {};
|
|
@@ -4008,11 +4034,11 @@ var build = async ({
|
|
|
4008
4034
|
cwd: workPath,
|
|
4009
4035
|
ignore: config && typeof config.excludeFiles === "string" ? [...predefinedExcludes, config.excludeFiles] : predefinedExcludes
|
|
4010
4036
|
};
|
|
4011
|
-
const files = await (0,
|
|
4037
|
+
const files = await (0, import_build_utils6.glob)("**", globOptions);
|
|
4012
4038
|
try {
|
|
4013
4039
|
const cachedVendorAbs = (0, import_path5.join)(vendorBaseDir, resolveVendorDir());
|
|
4014
4040
|
if (import_fs5.default.existsSync(cachedVendorAbs)) {
|
|
4015
|
-
const vendorFiles = await (0,
|
|
4041
|
+
const vendorFiles = await (0, import_build_utils6.glob)("**", cachedVendorAbs, resolveVendorDir());
|
|
4016
4042
|
for (const [p, f] of Object.entries(vendorFiles)) {
|
|
4017
4043
|
files[p] = f;
|
|
4018
4044
|
}
|
|
@@ -4022,12 +4048,12 @@ var build = async ({
|
|
|
4022
4048
|
throw err;
|
|
4023
4049
|
}
|
|
4024
4050
|
const handlerPyFilename = "vc__handler__python";
|
|
4025
|
-
files[`${handlerPyFilename}.py`] = new
|
|
4051
|
+
files[`${handlerPyFilename}.py`] = new import_build_utils6.FileBlob({ data: handlerPyContents });
|
|
4026
4052
|
if (config.framework === "fasthtml") {
|
|
4027
4053
|
const { SESSKEY = "" } = process.env;
|
|
4028
|
-
files[".sesskey"] = new
|
|
4054
|
+
files[".sesskey"] = new import_build_utils6.FileBlob({ data: `"${SESSKEY}"` });
|
|
4029
4055
|
}
|
|
4030
|
-
const output = new
|
|
4056
|
+
const output = new import_build_utils6.Lambda({
|
|
4031
4057
|
files,
|
|
4032
4058
|
handler: `${handlerPyFilename}.vc_handler`,
|
|
4033
4059
|
runtime: pythonVersion.runtime,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/python",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
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": "12.
|
|
24
|
+
"@vercel/build-utils": "12.2.4",
|
|
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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import sys
|
|
2
3
|
import os
|
|
3
4
|
import site
|
|
@@ -5,9 +6,19 @@ import importlib
|
|
|
5
6
|
import base64
|
|
6
7
|
import json
|
|
7
8
|
import inspect
|
|
9
|
+
import asyncio
|
|
10
|
+
import http
|
|
11
|
+
import time
|
|
8
12
|
from importlib import util
|
|
9
|
-
from http.server import BaseHTTPRequestHandler
|
|
13
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
10
14
|
import socket
|
|
15
|
+
import functools
|
|
16
|
+
import logging
|
|
17
|
+
import builtins
|
|
18
|
+
from typing import Callable, Literal, TextIO
|
|
19
|
+
import contextvars
|
|
20
|
+
import contextlib
|
|
21
|
+
|
|
11
22
|
|
|
12
23
|
_here = os.path.dirname(__file__)
|
|
13
24
|
_vendor_rel = '__VC_HANDLER_VENDOR_DIR'
|
|
@@ -30,6 +41,153 @@ if os.path.isdir(_vendor):
|
|
|
30
41
|
|
|
31
42
|
importlib.invalidate_caches()
|
|
32
43
|
|
|
44
|
+
|
|
45
|
+
def setup_logging(send_message: Callable[[dict], None], storage: contextvars.ContextVar[dict | None]):
|
|
46
|
+
# Override logging.Handler to send logs to the platform when a request context is available.
|
|
47
|
+
class VCLogHandler(logging.Handler):
|
|
48
|
+
def emit(self, record: logging.LogRecord):
|
|
49
|
+
try:
|
|
50
|
+
message = record.getMessage()
|
|
51
|
+
except Exception:
|
|
52
|
+
message = repr(getattr(record, "msg", ""))
|
|
53
|
+
|
|
54
|
+
if record.levelno >= logging.CRITICAL:
|
|
55
|
+
level = "fatal"
|
|
56
|
+
elif record.levelno >= logging.ERROR:
|
|
57
|
+
level = "error"
|
|
58
|
+
elif record.levelno >= logging.WARNING:
|
|
59
|
+
level = "warn"
|
|
60
|
+
elif record.levelno >= logging.INFO:
|
|
61
|
+
level = "info"
|
|
62
|
+
else:
|
|
63
|
+
level = "debug"
|
|
64
|
+
|
|
65
|
+
context = storage.get()
|
|
66
|
+
if context is not None:
|
|
67
|
+
send_message({
|
|
68
|
+
"type": "log",
|
|
69
|
+
"payload": {
|
|
70
|
+
"context": {
|
|
71
|
+
"invocationId": context['invocationId'],
|
|
72
|
+
"requestId": context['requestId'],
|
|
73
|
+
},
|
|
74
|
+
"message": base64.b64encode(message.encode()).decode(),
|
|
75
|
+
"level": level,
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
else:
|
|
79
|
+
# If IPC is not ready, enqueue the message to be sent later.
|
|
80
|
+
enqueue_or_send_message({
|
|
81
|
+
"type": "log",
|
|
82
|
+
"payload": {
|
|
83
|
+
"context": {"invocationId": "0", "requestId": 0},
|
|
84
|
+
"message": base64.b64encode(message.encode()).decode(),
|
|
85
|
+
"level": level,
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
# Override sys.stdout and sys.stderr to map logs to the correct request
|
|
90
|
+
class StreamWrapper:
|
|
91
|
+
def __init__(self, stream: TextIO, stream_name: Literal["stdout", "stderr"]):
|
|
92
|
+
self.stream = stream
|
|
93
|
+
self.stream_name = stream_name
|
|
94
|
+
|
|
95
|
+
def write(self, message: str):
|
|
96
|
+
context = storage.get()
|
|
97
|
+
if context is not None:
|
|
98
|
+
send_message({
|
|
99
|
+
"type": "log",
|
|
100
|
+
"payload": {
|
|
101
|
+
"context": {
|
|
102
|
+
"invocationId": context['invocationId'],
|
|
103
|
+
"requestId": context['requestId'],
|
|
104
|
+
},
|
|
105
|
+
"message": base64.b64encode(message.encode()).decode(),
|
|
106
|
+
"stream": self.stream_name,
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
else:
|
|
110
|
+
enqueue_or_send_message({
|
|
111
|
+
"type": "log",
|
|
112
|
+
"payload": {
|
|
113
|
+
"context": {"invocationId": "0", "requestId": 0},
|
|
114
|
+
"message": base64.b64encode(message.encode()).decode(),
|
|
115
|
+
"stream": self.stream_name,
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
def __getattr__(self, name):
|
|
120
|
+
return getattr(self.stream, name)
|
|
121
|
+
|
|
122
|
+
sys.stdout = StreamWrapper(sys.stdout, "stdout")
|
|
123
|
+
sys.stderr = StreamWrapper(sys.stderr, "stderr")
|
|
124
|
+
|
|
125
|
+
logging.basicConfig(level=logging.INFO, handlers=[VCLogHandler()], force=True)
|
|
126
|
+
|
|
127
|
+
# Ensure built-in print funnels through stdout wrapper so prints are
|
|
128
|
+
# attributed to the current request context.
|
|
129
|
+
def print_wrapper(func: Callable[..., None]) -> Callable[..., None]:
|
|
130
|
+
@functools.wraps(func)
|
|
131
|
+
def wrapper(*args, sep=' ', end='\n', file=None, flush=False):
|
|
132
|
+
if file is None:
|
|
133
|
+
file = sys.stdout
|
|
134
|
+
if file in (sys.stdout, sys.stderr):
|
|
135
|
+
file.write(sep.join(map(str, args)) + end)
|
|
136
|
+
if flush:
|
|
137
|
+
file.flush()
|
|
138
|
+
else:
|
|
139
|
+
# User specified a different file, use original print behavior
|
|
140
|
+
func(*args, sep=sep, end=end, file=file, flush=flush)
|
|
141
|
+
return wrapper
|
|
142
|
+
|
|
143
|
+
builtins.print = print_wrapper(builtins.print)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# If running in the platform (IPC present), logging must be setup before importing user code so that
|
|
147
|
+
# logs happening outside the request context are emitted correctly.
|
|
148
|
+
ipc_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
149
|
+
storage: contextvars.ContextVar[dict | None] = contextvars.ContextVar('storage', default=None)
|
|
150
|
+
send_message = lambda m: None
|
|
151
|
+
_original_stderr = sys.stderr
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# Buffer for pre-handshake logs (to avoid blocking IPC on startup)
|
|
155
|
+
_ipc_ready = False
|
|
156
|
+
_init_log_buf: list[dict] = []
|
|
157
|
+
_INIT_LOG_BUF_MAX_BYTES = 1_000_000
|
|
158
|
+
_init_log_buf_bytes = 0
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def enqueue_or_send_message(msg: dict):
|
|
162
|
+
global _init_log_buf_bytes
|
|
163
|
+
if _ipc_ready:
|
|
164
|
+
send_message(msg)
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
enc_len = len(json.dumps(msg))
|
|
168
|
+
|
|
169
|
+
if _init_log_buf_bytes + enc_len <= _INIT_LOG_BUF_MAX_BYTES:
|
|
170
|
+
_init_log_buf.append(msg)
|
|
171
|
+
_init_log_buf_bytes += enc_len
|
|
172
|
+
else:
|
|
173
|
+
# Fallback so message is not lost if buffer is full
|
|
174
|
+
with contextlib.suppress(Exception):
|
|
175
|
+
payload = msg.get("payload", {})
|
|
176
|
+
decoded = base64.b64decode(payload.get("message", "")).decode(errors="ignore")
|
|
177
|
+
_original_stderr.write(decoded + "\n")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if 'VERCEL_IPC_PATH' in os.environ:
|
|
181
|
+
with contextlib.suppress(Exception):
|
|
182
|
+
ipc_sock.connect(os.getenv("VERCEL_IPC_PATH", ""))
|
|
183
|
+
|
|
184
|
+
def send_message(message: dict):
|
|
185
|
+
with contextlib.suppress(Exception):
|
|
186
|
+
ipc_sock.sendall((json.dumps(message) + '\0').encode())
|
|
187
|
+
|
|
188
|
+
setup_logging(send_message, storage)
|
|
189
|
+
|
|
190
|
+
|
|
33
191
|
# Import relative path https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
|
|
34
192
|
user_mod_path = os.path.join(_here, "__VC_HANDLER_ENTRYPOINT") # absolute
|
|
35
193
|
__vc_spec = util.spec_from_file_location("__VC_HANDLER_MODULE_NAME", user_mod_path)
|
|
@@ -51,21 +209,9 @@ def format_headers(headers, decode=False):
|
|
|
51
209
|
keyToList[key].append(value)
|
|
52
210
|
return keyToList
|
|
53
211
|
|
|
54
|
-
if 'VERCEL_IPC_PATH' in os.environ:
|
|
55
|
-
from http.server import ThreadingHTTPServer
|
|
56
|
-
import http
|
|
57
|
-
import time
|
|
58
|
-
import contextvars
|
|
59
|
-
import functools
|
|
60
|
-
import builtins
|
|
61
|
-
import logging
|
|
62
212
|
|
|
213
|
+
if 'VERCEL_IPC_PATH' in os.environ:
|
|
63
214
|
start_time = time.time()
|
|
64
|
-
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
65
|
-
sock.connect(os.getenv("VERCEL_IPC_PATH", ""))
|
|
66
|
-
|
|
67
|
-
send_message = lambda message: sock.sendall((json.dumps(message) + '\0').encode())
|
|
68
|
-
storage = contextvars.ContextVar('storage', default=None)
|
|
69
215
|
|
|
70
216
|
# Override urlopen from urllib3 (& requests) to send Request Metrics
|
|
71
217
|
try:
|
|
@@ -110,71 +256,6 @@ if 'VERCEL_IPC_PATH' in os.environ:
|
|
|
110
256
|
except:
|
|
111
257
|
pass
|
|
112
258
|
|
|
113
|
-
# Override sys.stdout and sys.stderr to map logs to the correct request
|
|
114
|
-
class StreamWrapper:
|
|
115
|
-
def __init__(self, stream, stream_name):
|
|
116
|
-
self.stream = stream
|
|
117
|
-
self.stream_name = stream_name
|
|
118
|
-
|
|
119
|
-
def write(self, message):
|
|
120
|
-
context = storage.get()
|
|
121
|
-
if context is not None:
|
|
122
|
-
send_message({
|
|
123
|
-
"type": "log",
|
|
124
|
-
"payload": {
|
|
125
|
-
"context": {
|
|
126
|
-
"invocationId": context['invocationId'],
|
|
127
|
-
"requestId": context['requestId'],
|
|
128
|
-
},
|
|
129
|
-
"message": base64.b64encode(message.encode()).decode(),
|
|
130
|
-
"stream": self.stream_name,
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
else:
|
|
134
|
-
self.stream.write(message)
|
|
135
|
-
|
|
136
|
-
def __getattr__(self, name):
|
|
137
|
-
return getattr(self.stream, name)
|
|
138
|
-
|
|
139
|
-
sys.stdout = StreamWrapper(sys.stdout, "stdout")
|
|
140
|
-
sys.stderr = StreamWrapper(sys.stderr, "stderr")
|
|
141
|
-
|
|
142
|
-
# Override the global print to log to stdout
|
|
143
|
-
def print_wrapper(func):
|
|
144
|
-
@functools.wraps(func)
|
|
145
|
-
def wrapper(*args, **kwargs):
|
|
146
|
-
sys.stdout.write(' '.join(map(str, args)) + '\n')
|
|
147
|
-
return wrapper
|
|
148
|
-
builtins.print = print_wrapper(builtins.print)
|
|
149
|
-
|
|
150
|
-
# Override logging to maps logs to the correct request
|
|
151
|
-
def logging_wrapper(func, level="info"):
|
|
152
|
-
@functools.wraps(func)
|
|
153
|
-
def wrapper(*args, **kwargs):
|
|
154
|
-
context = storage.get()
|
|
155
|
-
if context is not None:
|
|
156
|
-
send_message({
|
|
157
|
-
"type": "log",
|
|
158
|
-
"payload": {
|
|
159
|
-
"context": {
|
|
160
|
-
"invocationId": context['invocationId'],
|
|
161
|
-
"requestId": context['requestId'],
|
|
162
|
-
},
|
|
163
|
-
"message": base64.b64encode(f"{args[0]}".encode()).decode(),
|
|
164
|
-
"level": level,
|
|
165
|
-
}
|
|
166
|
-
})
|
|
167
|
-
else:
|
|
168
|
-
func(*args, **kwargs)
|
|
169
|
-
return wrapper
|
|
170
|
-
|
|
171
|
-
logging.basicConfig(level=logging.INFO)
|
|
172
|
-
logging.debug = logging_wrapper(logging.debug)
|
|
173
|
-
logging.info = logging_wrapper(logging.info)
|
|
174
|
-
logging.warning = logging_wrapper(logging.warning, "warn")
|
|
175
|
-
logging.error = logging_wrapper(logging.error, "error")
|
|
176
|
-
logging.critical = logging_wrapper(logging.critical, "error")
|
|
177
|
-
|
|
178
259
|
class BaseHandler(BaseHTTPRequestHandler):
|
|
179
260
|
# Re-implementation of BaseHTTPRequestHandler's log_message method to
|
|
180
261
|
# log to stdout instead of stderr.
|
|
@@ -406,6 +487,11 @@ if 'VERCEL_IPC_PATH' in os.environ:
|
|
|
406
487
|
"httpPort": server.server_address[1],
|
|
407
488
|
}
|
|
408
489
|
})
|
|
490
|
+
# Mark IPC as ready and flush any buffered init logs
|
|
491
|
+
_ipc_ready = True
|
|
492
|
+
for m in _init_log_buf:
|
|
493
|
+
send_message(m)
|
|
494
|
+
_init_log_buf.clear()
|
|
409
495
|
server.serve_forever()
|
|
410
496
|
|
|
411
497
|
print('Missing variable `handler` or `app` in file "__VC_HANDLER_ENTRYPOINT".')
|