@vercel/python 6.37.0 → 6.39.0
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 +273 -52
- package/package.json +7 -8
package/dist/index.js
CHANGED
|
@@ -3230,8 +3230,8 @@ var import_fs13 = __toESM(require("fs"));
|
|
|
3230
3230
|
var import_path13 = require("path");
|
|
3231
3231
|
|
|
3232
3232
|
// src/package-versions.ts
|
|
3233
|
-
var VERCEL_RUNTIME_VERSION = "0.13.
|
|
3234
|
-
var VERCEL_WORKERS_VERSION = "0.0.
|
|
3233
|
+
var VERCEL_RUNTIME_VERSION = "0.13.2";
|
|
3234
|
+
var VERCEL_WORKERS_VERSION = "0.0.22";
|
|
3235
3235
|
|
|
3236
3236
|
// src/index.ts
|
|
3237
3237
|
var import_build_utils16 = require("@vercel/build-utils");
|
|
@@ -4328,6 +4328,9 @@ var LAMBDA_SIZE_THRESHOLD_BYTES = 245 * 1024 * 1024;
|
|
|
4328
4328
|
var LAMBDA_EPHEMERAL_STORAGE_BYTES = 500 * 1024 * 1024;
|
|
4329
4329
|
var HIVE_LAMBDA_SIZE_BYTES = 1 * 1024 * 1024 * 1024;
|
|
4330
4330
|
var FUNCTIONS_BETA_CTA = "Run `vercel deploy --functions-beta` to use extended function limits (up to 1 GB), or reduce your dependency footprint.";
|
|
4331
|
+
var BUNDLING_DOCS_LINK = "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled";
|
|
4332
|
+
var FUNCTIONS_BETA_DOCS_LINK = "https://vercel.com/docs/functions/runtimes/python#extended-size-limits-with-functions-beta";
|
|
4333
|
+
var FUNCTIONS_BETA_EXCEEDED_CTA = "Your deployment is already using extended function limits (`--functions-beta`).\nReduce your dependency footprint to under 1 GB or split your application\ninto smaller functions.";
|
|
4331
4334
|
function shouldShowFunctionsBetaHint() {
|
|
4332
4335
|
const v = process.env.VERCEL_FUNCTIONS_BETA_HINT;
|
|
4333
4336
|
return v === "1" || v === "true";
|
|
@@ -4390,10 +4393,13 @@ var PythonDependencyExternalizer = class {
|
|
|
4390
4393
|
|
|
4391
4394
|
` + FUNCTIONS_BETA_CTA : `Total bundle size (${totalBundleSizeMB} MB) exceeds the size limit (${limitMB} MB).
|
|
4392
4395
|
|
|
4393
|
-
When using a custom install command, Vercel cannot automatically
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4396
|
+
When using a custom install command, Vercel cannot automatically
|
|
4397
|
+
optimize dependency bundling. To reduce the size of your
|
|
4398
|
+
dependencies, you can:
|
|
4399
|
+
1. Remove unused dependencies from your project.
|
|
4400
|
+
2. Remove the custom install command to allow Vercel to manage
|
|
4401
|
+
and optimize dependencies automatically.`,
|
|
4402
|
+
link: shouldShowFunctionsBetaHint() ? FUNCTIONS_BETA_DOCS_LINK : BUNDLING_DOCS_LINK,
|
|
4397
4403
|
action: "Learn More"
|
|
4398
4404
|
});
|
|
4399
4405
|
}
|
|
@@ -4401,7 +4407,12 @@ When using a custom install command, Vercel cannot automatically optimize depend
|
|
|
4401
4407
|
const limitMB = (HIVE_LAMBDA_SIZE_BYTES / (1024 * 1024)).toFixed(0);
|
|
4402
4408
|
throw new import_build_utils5.NowBuildError({
|
|
4403
4409
|
code: "LAMBDA_SIZE_EXCEEDED",
|
|
4404
|
-
message: `Total bundle size (${totalBundleSizeMB} MB) exceeds the extended function size limit (${limitMB} MB).
|
|
4410
|
+
message: shouldShowFunctionsBetaHint() ? `Total bundle size (${totalBundleSizeMB} MB) exceeds the extended function size limit (${limitMB} MB).
|
|
4411
|
+
|
|
4412
|
+
` + FUNCTIONS_BETA_EXCEEDED_CTA : `Total bundle size (${totalBundleSizeMB} MB) exceeds the extended function size limit (${limitMB} MB).
|
|
4413
|
+
|
|
4414
|
+
Consider removing unused dependencies or splitting your
|
|
4415
|
+
application into smaller functions.`,
|
|
4405
4416
|
link: "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled",
|
|
4406
4417
|
action: "Learn More"
|
|
4407
4418
|
});
|
|
@@ -4440,8 +4451,13 @@ When using a custom install command, Vercel cannot automatically optimize depend
|
|
|
4440
4451
|
code: "LAMBDA_SIZE_EXCEEDED",
|
|
4441
4452
|
message: shouldShowFunctionsBetaHint() ? `Total bundle size (${totalBundleSizeMB} MB) exceeds the ephemeral storage limit (${ephemeralLimitMB} MB).
|
|
4442
4453
|
|
|
4443
|
-
` + FUNCTIONS_BETA_CTA : `Total bundle size (${totalBundleSizeMB} MB) exceeds Lambda ephemeral storage limit (${ephemeralLimitMB} MB).
|
|
4444
|
-
|
|
4454
|
+
` + FUNCTIONS_BETA_CTA : `Total bundle size (${totalBundleSizeMB} MB) exceeds Lambda ephemeral storage limit (${ephemeralLimitMB} MB).
|
|
4455
|
+
|
|
4456
|
+
Even with runtime dependency installation, all packages must fit
|
|
4457
|
+
within the ${ephemeralLimitMB} MB ephemeral storage available to Lambda
|
|
4458
|
+
functions. Consider removing unused dependencies or splitting
|
|
4459
|
+
your application into smaller functions.`,
|
|
4460
|
+
link: shouldShowFunctionsBetaHint() ? FUNCTIONS_BETA_DOCS_LINK : BUNDLING_DOCS_LINK,
|
|
4445
4461
|
action: "Learn More"
|
|
4446
4462
|
});
|
|
4447
4463
|
}
|
|
@@ -4513,12 +4529,17 @@ ${error.fileContent}`
|
|
|
4513
4529
|
if (externalizablePublic.length === 0) {
|
|
4514
4530
|
throw new import_build_utils5.NowBuildError({
|
|
4515
4531
|
code: "RUNTIME_DEPENDENCY_INSTALLATION_FAILED",
|
|
4516
|
-
message: `Bundle size exceeds the Lambda limit and requires runtime
|
|
4517
|
-
|
|
4532
|
+
message: `Bundle size exceeds the Lambda limit and requires runtime
|
|
4533
|
+
dependency installation, but no public packages have compatible
|
|
4534
|
+
pre-built wheels for the Lambda platform.
|
|
4535
|
+
|
|
4536
|
+
Runtime dependency installation requires packages to have binary
|
|
4537
|
+
wheels.
|
|
4518
4538
|
|
|
4519
4539
|
To fix this, either:
|
|
4520
|
-
|
|
4521
|
-
|
|
4540
|
+
1. Regenerate your lock file with: uv lock --upgrade, or
|
|
4541
|
+
2. Switch the problematic packages to ones that have pre-built
|
|
4542
|
+
wheels available.`
|
|
4522
4543
|
});
|
|
4523
4544
|
}
|
|
4524
4545
|
const packageSizes = await calculatePerPackageSizes(this.venvPath);
|
|
@@ -4636,8 +4657,13 @@ To fix this, either:
|
|
|
4636
4657
|
code: "LAMBDA_SIZE_EXCEEDED",
|
|
4637
4658
|
message: shouldShowFunctionsBetaHint() ? `Total bundle size (${finalSizeMB} MB) exceeds the size limit (${limitMB} MB).
|
|
4638
4659
|
|
|
4639
|
-
` + FUNCTIONS_BETA_CTA : `Total bundle size (${finalSizeMB} MB) exceeds Lambda limit (${limitMB} MB) even after
|
|
4640
|
-
|
|
4660
|
+
` + FUNCTIONS_BETA_CTA : `Total bundle size (${finalSizeMB} MB) exceeds Lambda limit (${limitMB} MB) even after
|
|
4661
|
+
deferring public packages to runtime installation.
|
|
4662
|
+
|
|
4663
|
+
This usually means your private packages or source code are too
|
|
4664
|
+
large. Consider reducing the size of private dependencies or
|
|
4665
|
+
splitting your application.`,
|
|
4666
|
+
link: shouldShowFunctionsBetaHint() ? FUNCTIONS_BETA_DOCS_LINK : BUNDLING_DOCS_LINK,
|
|
4641
4667
|
action: "Learn More"
|
|
4642
4668
|
});
|
|
4643
4669
|
}
|
|
@@ -4943,7 +4969,8 @@ async function generateProjectManifest({
|
|
|
4943
4969
|
pythonPackage,
|
|
4944
4970
|
pythonVersion,
|
|
4945
4971
|
uvLockPath,
|
|
4946
|
-
framework
|
|
4972
|
+
framework,
|
|
4973
|
+
serviceType
|
|
4947
4974
|
}) {
|
|
4948
4975
|
const resolved = pythonVersionString(pythonVersion) ?? "";
|
|
4949
4976
|
const constraint = pythonPackage.requiresPython?.[0];
|
|
@@ -5097,6 +5124,7 @@ async function generateProjectManifest({
|
|
|
5097
5124
|
version: import_build_utils6.MANIFEST_VERSION,
|
|
5098
5125
|
runtime: "python",
|
|
5099
5126
|
...framework ? { framework } : {},
|
|
5127
|
+
...serviceType ? { serviceType } : {},
|
|
5100
5128
|
runtimeVersion: {
|
|
5101
5129
|
...requested ? { requested } : {},
|
|
5102
5130
|
...constraint?.source ? { requestedSource: constraint.source } : {},
|
|
@@ -5131,7 +5159,7 @@ function buildCronRouteTable(crons) {
|
|
|
5131
5159
|
}
|
|
5132
5160
|
async function getServiceCrons(opts) {
|
|
5133
5161
|
const { service, entrypoint, rawEntrypoint, handlerFunction } = opts;
|
|
5134
|
-
const isScheduledService = service
|
|
5162
|
+
const isScheduledService = !!service && (0, import_build_utils7.isScheduleTriggeredService)(service);
|
|
5135
5163
|
if (!isScheduledService || !service.name || typeof service.schedule !== "string") {
|
|
5136
5164
|
return void 0;
|
|
5137
5165
|
}
|
|
@@ -5280,6 +5308,7 @@ var PYTHON_ENTRYPOINT_DIRS = ["", "src", "app", "api"];
|
|
|
5280
5308
|
var PYTHON_CANDIDATE_ENTRYPOINTS = getCandidateEntrypointsInDirs(
|
|
5281
5309
|
PYTHON_ENTRYPOINT_DIRS
|
|
5282
5310
|
);
|
|
5311
|
+
var PYTHON_ENTRYPOINT_DOCS_URL = "https://vercel.com/docs/functions/runtimes/python#python-entrypoints";
|
|
5283
5312
|
function getCandidateEntrypointsInDirs(dirs) {
|
|
5284
5313
|
return dirs.flatMap(
|
|
5285
5314
|
(dir) => PYTHON_ENTRYPOINT_FILENAMES.map(
|
|
@@ -5295,6 +5324,51 @@ async function fileExists(filePath) {
|
|
|
5295
5324
|
return false;
|
|
5296
5325
|
}
|
|
5297
5326
|
}
|
|
5327
|
+
function getFrameworkDisplayName(framework) {
|
|
5328
|
+
switch (framework) {
|
|
5329
|
+
case "fastapi":
|
|
5330
|
+
return "FastAPI";
|
|
5331
|
+
case "flask":
|
|
5332
|
+
return "Flask";
|
|
5333
|
+
case "django":
|
|
5334
|
+
return "Django";
|
|
5335
|
+
case "fasthtml":
|
|
5336
|
+
return "FastHTML";
|
|
5337
|
+
default:
|
|
5338
|
+
return framework;
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
function getFrameworkEntrypointDocsUrl(framework) {
|
|
5342
|
+
switch (framework) {
|
|
5343
|
+
case "fastapi":
|
|
5344
|
+
return "https://vercel.com/docs/frameworks/backend/fastapi#exporting-the-fastapi-application";
|
|
5345
|
+
case "flask":
|
|
5346
|
+
return "https://vercel.com/docs/frameworks/backend/flask#exporting-the-flask-application";
|
|
5347
|
+
case "django":
|
|
5348
|
+
return "https://vercel.com/docs/frameworks/full-stack/django#configure-the-django-entrypoint";
|
|
5349
|
+
default:
|
|
5350
|
+
return PYTHON_ENTRYPOINT_DOCS_URL;
|
|
5351
|
+
}
|
|
5352
|
+
}
|
|
5353
|
+
function getMissingExportMessage(framework, entrypoints) {
|
|
5354
|
+
const fileList = entrypoints.slice(0, 3).join(", ");
|
|
5355
|
+
const found = `Found ${fileList}`;
|
|
5356
|
+
const subject = entrypoints.length === 1 ? "it" : "none";
|
|
5357
|
+
const verb = entrypoints.length === 1 ? "does not define" : "define";
|
|
5358
|
+
const displayName = getFrameworkDisplayName(framework);
|
|
5359
|
+
switch (framework) {
|
|
5360
|
+
case "fastapi":
|
|
5361
|
+
return `${found} but ${subject} ${verb} a top-level "app" FastAPI instance.`;
|
|
5362
|
+
case "flask":
|
|
5363
|
+
return `${found} but ${subject} ${verb} a top-level "app" Flask instance.`;
|
|
5364
|
+
case "django":
|
|
5365
|
+
return `${found} but ${subject} ${verb} a top-level "application" ASGI or WSGI instance.`;
|
|
5366
|
+
case "fasthtml":
|
|
5367
|
+
return `${found} but ${subject} ${verb} a top-level "app" ${displayName} instance.`;
|
|
5368
|
+
default:
|
|
5369
|
+
return entrypoints.length === 1 ? `Found ${fileList} but it does not export a top-level "app", "application", or "handler" variable.` : `Found ${fileList} but none export a top-level "app", "application", or "handler" variable.`;
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5298
5372
|
async function checkEntrypoint(workPath, relPath) {
|
|
5299
5373
|
const absPath = (0, import_path8.join)(workPath, relPath);
|
|
5300
5374
|
if (!await fileExists(absPath))
|
|
@@ -5302,6 +5376,28 @@ async function checkEntrypoint(workPath, relPath) {
|
|
|
5302
5376
|
const content = await import_fs8.default.promises.readFile(absPath, "utf-8");
|
|
5303
5377
|
return (0, import_python_analysis5.findAppOrHandler)(content);
|
|
5304
5378
|
}
|
|
5379
|
+
async function findOtherPyFiles(workPath, dirs, candidateSet) {
|
|
5380
|
+
const results = [];
|
|
5381
|
+
for (const dir of dirs) {
|
|
5382
|
+
const dirPath = (0, import_path8.join)(workPath, dir);
|
|
5383
|
+
try {
|
|
5384
|
+
const entries = await import_fs8.default.promises.readdir(dirPath);
|
|
5385
|
+
for (const entry of entries) {
|
|
5386
|
+
if (!entry.endsWith(".py"))
|
|
5387
|
+
continue;
|
|
5388
|
+
const relPath = dir ? import_path8.posix.join(dir, entry) : entry;
|
|
5389
|
+
if (candidateSet.has(relPath))
|
|
5390
|
+
continue;
|
|
5391
|
+
const stat = await import_fs8.default.promises.stat((0, import_path8.join)(dirPath, entry));
|
|
5392
|
+
if (stat.isFile()) {
|
|
5393
|
+
results.push(relPath);
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
} catch {
|
|
5397
|
+
}
|
|
5398
|
+
}
|
|
5399
|
+
return results;
|
|
5400
|
+
}
|
|
5305
5401
|
async function resolveModuleAttrEntrypoint(workPath, value) {
|
|
5306
5402
|
const match = value.match(/([A-Za-z_][\w.]*)\s*:\s*([A-Za-z_][\w]*)/);
|
|
5307
5403
|
if (!match)
|
|
@@ -5326,25 +5422,46 @@ async function getVercelToolsEntrypoint(workPath) {
|
|
|
5326
5422
|
return null;
|
|
5327
5423
|
return resolveModuleAttrEntrypoint(workPath, vercelEntrypoint);
|
|
5328
5424
|
}
|
|
5329
|
-
async function
|
|
5425
|
+
async function getPyprojectEntrypointWithDiagnostics(workPath) {
|
|
5330
5426
|
const pyprojectData = await (0, import_build_utils10.readConfigFile)((0, import_path8.join)(workPath, "pyproject.toml"));
|
|
5331
5427
|
if (!pyprojectData)
|
|
5332
|
-
return null;
|
|
5428
|
+
return { entrypoint: null };
|
|
5333
5429
|
const scripts = pyprojectData.project?.scripts;
|
|
5334
5430
|
const appScript = scripts?.app;
|
|
5335
5431
|
if (typeof appScript !== "string")
|
|
5336
|
-
return null;
|
|
5337
|
-
|
|
5432
|
+
return { entrypoint: null };
|
|
5433
|
+
const match = appScript.match(/([A-Za-z_][\w.]*)\s*:\s*([A-Za-z_][\w]*)/);
|
|
5434
|
+
if (!match)
|
|
5435
|
+
return { entrypoint: null, appScript };
|
|
5436
|
+
const modulePath = match[1];
|
|
5437
|
+
const variableName = match[2];
|
|
5438
|
+
const relPath = modulePath.replace(/\./g, "/");
|
|
5439
|
+
const candidates = [`${relPath}.py`, `${relPath}/__init__.py`];
|
|
5440
|
+
for (const candidate of candidates) {
|
|
5441
|
+
if (await fileExists((0, import_path8.join)(workPath, candidate))) {
|
|
5442
|
+
return { entrypoint: { entrypoint: candidate, variableName } };
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
return { entrypoint: null, appScript, checkedPaths: candidates };
|
|
5338
5446
|
}
|
|
5339
5447
|
async function findValidEntrypoint(workPath, candidates) {
|
|
5448
|
+
const existingWithoutEntrypoint = [];
|
|
5340
5449
|
for (const candidate of candidates) {
|
|
5341
|
-
const
|
|
5450
|
+
const absPath = (0, import_path8.join)(workPath, candidate);
|
|
5451
|
+
if (!await fileExists(absPath))
|
|
5452
|
+
continue;
|
|
5453
|
+
const content = await import_fs8.default.promises.readFile(absPath, "utf-8");
|
|
5454
|
+
const varName = await (0, import_python_analysis5.findAppOrHandler)(content);
|
|
5342
5455
|
if (varName) {
|
|
5343
5456
|
(0, import_build_utils9.debug)(`Detected Python entrypoint: ${candidate} (variable: ${varName})`);
|
|
5344
|
-
return {
|
|
5457
|
+
return {
|
|
5458
|
+
entrypoint: { entrypoint: candidate, variableName: varName },
|
|
5459
|
+
existingWithoutEntrypoint
|
|
5460
|
+
};
|
|
5345
5461
|
}
|
|
5462
|
+
existingWithoutEntrypoint.push(candidate);
|
|
5346
5463
|
}
|
|
5347
|
-
return null;
|
|
5464
|
+
return { entrypoint: null, existingWithoutEntrypoint };
|
|
5348
5465
|
}
|
|
5349
5466
|
async function checkDjangoManage(workPath) {
|
|
5350
5467
|
const managePath = (0, import_path8.join)(workPath, "manage.py");
|
|
@@ -5368,28 +5485,56 @@ async function getSubdirectories(workPath) {
|
|
|
5368
5485
|
return [];
|
|
5369
5486
|
}
|
|
5370
5487
|
}
|
|
5371
|
-
function
|
|
5372
|
-
const
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5488
|
+
function makeDiagnosticError(framework, diagnostics2) {
|
|
5489
|
+
const code = `${framework.toUpperCase()}_ENTRYPOINT_NOT_FOUND`;
|
|
5490
|
+
const link = getFrameworkEntrypointDocsUrl(framework);
|
|
5491
|
+
const action = "Learn More";
|
|
5492
|
+
const displayName = getFrameworkDisplayName(framework);
|
|
5493
|
+
let message;
|
|
5494
|
+
if (diagnostics2.existingWithoutEntrypoint.length > 0) {
|
|
5495
|
+
message = getMissingExportMessage(
|
|
5496
|
+
framework,
|
|
5497
|
+
diagnostics2.existingWithoutEntrypoint
|
|
5498
|
+
);
|
|
5499
|
+
} else if (diagnostics2.pyprojectAppScript && diagnostics2.pyprojectCheckedPaths?.length) {
|
|
5500
|
+
const checked = diagnostics2.pyprojectCheckedPaths.join(" or ");
|
|
5501
|
+
message = `pyproject.toml [project.scripts] defines app = "${diagnostics2.pyprojectAppScript}" but ${checked} was not found.`;
|
|
5502
|
+
} else if (diagnostics2.otherPyFiles.length > 0) {
|
|
5503
|
+
const fileList = diagnostics2.otherPyFiles.slice(0, 5).join(", ");
|
|
5504
|
+
message = `No ${displayName} entrypoint found in standard locations. Found Python files: ${fileList}. Rename one to app.py or set "tool.vercel.entrypoint" in pyproject.toml.`;
|
|
5505
|
+
} else {
|
|
5506
|
+
const searchedList = PYTHON_CANDIDATE_ENTRYPOINTS.join(", ");
|
|
5507
|
+
message = `No ${displayName} entrypoint found. Set "tool.vercel.entrypoint" in pyproject.toml or define an entrypoint in one of: ${searchedList}.`;
|
|
5508
|
+
}
|
|
5509
|
+
return new import_build_utils8.NowBuildError({ code, message, link, action });
|
|
5379
5510
|
}
|
|
5380
5511
|
async function detectGenericPythonEntrypoint(workPath) {
|
|
5381
5512
|
try {
|
|
5382
|
-
const
|
|
5513
|
+
const result = await findValidEntrypoint(
|
|
5383
5514
|
workPath,
|
|
5384
5515
|
PYTHON_CANDIDATE_ENTRYPOINTS
|
|
5385
5516
|
);
|
|
5386
|
-
return
|
|
5517
|
+
return {
|
|
5518
|
+
detected: result.entrypoint ? { entrypoint: result.entrypoint } : null,
|
|
5519
|
+
findDiagnostics: result
|
|
5520
|
+
};
|
|
5387
5521
|
} catch {
|
|
5388
5522
|
(0, import_build_utils9.debug)("Failed to discover Python entrypoint");
|
|
5389
|
-
return
|
|
5523
|
+
return {
|
|
5524
|
+
detected: null,
|
|
5525
|
+
findDiagnostics: { entrypoint: null, existingWithoutEntrypoint: [] }
|
|
5526
|
+
};
|
|
5390
5527
|
}
|
|
5391
5528
|
}
|
|
5392
5529
|
async function detectDjangoPythonEntrypoint(workPath) {
|
|
5530
|
+
const emptyResult = {
|
|
5531
|
+
detected: null,
|
|
5532
|
+
findDiagnostics: {
|
|
5533
|
+
entrypoint: null,
|
|
5534
|
+
existingWithoutEntrypoint: []
|
|
5535
|
+
},
|
|
5536
|
+
dirs: PYTHON_ENTRYPOINT_DIRS
|
|
5537
|
+
};
|
|
5393
5538
|
try {
|
|
5394
5539
|
const subdirs = await getSubdirectories(workPath);
|
|
5395
5540
|
const rootDirs = ["", ...subdirs];
|
|
@@ -5397,21 +5542,40 @@ async function detectDjangoPythonEntrypoint(workPath) {
|
|
|
5397
5542
|
const currPath = (0, import_path8.join)(workPath, rootDir);
|
|
5398
5543
|
const isDjango = await checkDjangoManage(currPath);
|
|
5399
5544
|
if (isDjango) {
|
|
5400
|
-
return {
|
|
5545
|
+
return {
|
|
5546
|
+
detected: { baseDir: rootDir },
|
|
5547
|
+
findDiagnostics: { entrypoint: null, existingWithoutEntrypoint: [] },
|
|
5548
|
+
dirs: rootDirs
|
|
5549
|
+
};
|
|
5401
5550
|
}
|
|
5402
5551
|
}
|
|
5403
5552
|
const candidates = getCandidateEntrypointsInDirs(rootDirs);
|
|
5404
|
-
const
|
|
5405
|
-
return
|
|
5553
|
+
const result = await findValidEntrypoint(workPath, candidates);
|
|
5554
|
+
return {
|
|
5555
|
+
detected: result.entrypoint ? { entrypoint: result.entrypoint } : null,
|
|
5556
|
+
findDiagnostics: result,
|
|
5557
|
+
dirs: rootDirs
|
|
5558
|
+
};
|
|
5406
5559
|
} catch {
|
|
5407
5560
|
(0, import_build_utils9.debug)("Failed to discover Django Python entrypoint");
|
|
5408
|
-
return
|
|
5561
|
+
return emptyResult;
|
|
5409
5562
|
}
|
|
5410
5563
|
}
|
|
5411
5564
|
async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint, service) {
|
|
5412
5565
|
if (configuredEntrypoint) {
|
|
5413
5566
|
const { filePath: configEntryFile, varName: configEntryVar } = configuredEntrypoint;
|
|
5414
5567
|
const entrypoint = configEntryFile.endsWith(".py") ? configEntryFile : `${configEntryFile}.py`;
|
|
5568
|
+
const entrypointExists = await fileExists((0, import_path8.join)(workPath, entrypoint));
|
|
5569
|
+
if (!entrypointExists) {
|
|
5570
|
+
return {
|
|
5571
|
+
error: new import_build_utils8.NowBuildError({
|
|
5572
|
+
code: "PYTHON_ENTRYPOINT_NOT_FOUND",
|
|
5573
|
+
message: `Configured Python entrypoint "${entrypoint}" was not found.`,
|
|
5574
|
+
link: PYTHON_ENTRYPOINT_DOCS_URL,
|
|
5575
|
+
action: "Learn More"
|
|
5576
|
+
})
|
|
5577
|
+
};
|
|
5578
|
+
}
|
|
5415
5579
|
let varName = configEntryVar ?? await checkEntrypoint(workPath, entrypoint);
|
|
5416
5580
|
if (!varName) {
|
|
5417
5581
|
const needsDynamicApp = !!service && ((0, import_build_utils8.isScheduleTriggeredService)(service) || (0, import_build_utils8.isQueueTriggeredService)(service));
|
|
@@ -5427,7 +5591,7 @@ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint,
|
|
|
5427
5591
|
error: new import_build_utils8.NowBuildError({
|
|
5428
5592
|
code: "PYTHON_ENTRYPOINT_NOT_FOUND",
|
|
5429
5593
|
message: `Could not find a top-level "app", "application", or "handler" in "${entrypoint}".`,
|
|
5430
|
-
link:
|
|
5594
|
+
link: PYTHON_ENTRYPOINT_DOCS_URL,
|
|
5431
5595
|
action: "Learn More"
|
|
5432
5596
|
})
|
|
5433
5597
|
};
|
|
@@ -5439,11 +5603,57 @@ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint,
|
|
|
5439
5603
|
const vercelEntry = await getVercelToolsEntrypoint(workPath);
|
|
5440
5604
|
if (vercelEntry)
|
|
5441
5605
|
return { entrypoint: vercelEntry };
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5606
|
+
let findDiagnostics;
|
|
5607
|
+
let searchDirs;
|
|
5608
|
+
if (framework === "django") {
|
|
5609
|
+
const djangoResult = await detectDjangoPythonEntrypoint(workPath);
|
|
5610
|
+
findDiagnostics = djangoResult.findDiagnostics;
|
|
5611
|
+
searchDirs = djangoResult.dirs;
|
|
5612
|
+
if (djangoResult.detected) {
|
|
5613
|
+
if (djangoResult.detected.entrypoint)
|
|
5614
|
+
return djangoResult.detected;
|
|
5615
|
+
const candidateSet2 = new Set(getCandidateEntrypointsInDirs(searchDirs));
|
|
5616
|
+
const otherPyFiles2 = await findOtherPyFiles(
|
|
5617
|
+
workPath,
|
|
5618
|
+
searchDirs,
|
|
5619
|
+
candidateSet2
|
|
5620
|
+
);
|
|
5621
|
+
const pyprojectResult2 = await getPyprojectEntrypointWithDiagnostics(workPath);
|
|
5622
|
+
const diagnostics3 = {
|
|
5623
|
+
existingWithoutEntrypoint: findDiagnostics.existingWithoutEntrypoint,
|
|
5624
|
+
otherPyFiles: otherPyFiles2,
|
|
5625
|
+
pyprojectAppScript: pyprojectResult2.appScript,
|
|
5626
|
+
pyprojectCheckedPaths: pyprojectResult2.checkedPaths
|
|
5627
|
+
};
|
|
5628
|
+
return {
|
|
5629
|
+
baseDir: djangoResult.detected.baseDir,
|
|
5630
|
+
error: makeDiagnosticError("django", diagnostics3)
|
|
5631
|
+
};
|
|
5632
|
+
}
|
|
5633
|
+
} else {
|
|
5634
|
+
const genericResult = await detectGenericPythonEntrypoint(workPath);
|
|
5635
|
+
findDiagnostics = genericResult.findDiagnostics;
|
|
5636
|
+
searchDirs = PYTHON_ENTRYPOINT_DIRS;
|
|
5637
|
+
if (genericResult.detected)
|
|
5638
|
+
return genericResult.detected;
|
|
5639
|
+
}
|
|
5640
|
+
const pyprojectResult = await getPyprojectEntrypointWithDiagnostics(workPath);
|
|
5641
|
+
if (pyprojectResult.entrypoint) {
|
|
5642
|
+
return { entrypoint: pyprojectResult.entrypoint };
|
|
5643
|
+
}
|
|
5644
|
+
const candidateSet = new Set(getCandidateEntrypointsInDirs(searchDirs));
|
|
5645
|
+
const otherPyFiles = await findOtherPyFiles(
|
|
5646
|
+
workPath,
|
|
5647
|
+
searchDirs,
|
|
5648
|
+
candidateSet
|
|
5649
|
+
);
|
|
5650
|
+
const diagnostics2 = {
|
|
5651
|
+
existingWithoutEntrypoint: findDiagnostics.existingWithoutEntrypoint,
|
|
5652
|
+
otherPyFiles,
|
|
5653
|
+
pyprojectAppScript: pyprojectResult.appScript,
|
|
5654
|
+
pyprojectCheckedPaths: pyprojectResult.checkedPaths
|
|
5655
|
+
};
|
|
5656
|
+
return { error: makeDiagnosticError(framework, diagnostics2) };
|
|
5447
5657
|
}
|
|
5448
5658
|
|
|
5449
5659
|
// src/start-dev-server.ts
|
|
@@ -5997,7 +6207,7 @@ var startDevServer = async (opts) => {
|
|
|
5997
6207
|
filePath: entrypoint,
|
|
5998
6208
|
// Schedule-triggered services create their own "app" wrapper dynamically.
|
|
5999
6209
|
// Other services use handlerFunction as the entrypoint variable name.
|
|
6000
|
-
varName: service
|
|
6210
|
+
varName: service && (0, import_build_utils11.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
|
|
6001
6211
|
} : void 0,
|
|
6002
6212
|
service
|
|
6003
6213
|
);
|
|
@@ -6794,6 +7004,7 @@ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir,
|
|
|
6794
7004
|
// src/index.ts
|
|
6795
7005
|
var import_python_analysis9 = require("@vercel/python-analysis");
|
|
6796
7006
|
var writeFile = import_fs13.default.promises.writeFile;
|
|
7007
|
+
var PYTHON_ENTRYPOINT_DOCS_URL2 = "https://vercel.com/docs/functions/runtimes/python#python-entrypoints";
|
|
6797
7008
|
var version = -1;
|
|
6798
7009
|
async function runFrameworkHook(framework, ctx) {
|
|
6799
7010
|
const hook = framework ? frameworkHooks[framework] : void 0;
|
|
@@ -6881,6 +7092,15 @@ async function downloadFilesInWorkPath({
|
|
|
6881
7092
|
(0, import_build_utils16.debug)("Downloading user files...");
|
|
6882
7093
|
let downloadedFiles = await (0, import_build_utils16.download)(files, workPath, meta);
|
|
6883
7094
|
if (meta.isDev && entrypoint) {
|
|
7095
|
+
const normalizedEntrypoint = entrypoint.endsWith(".py") ? entrypoint : `${entrypoint}.py`;
|
|
7096
|
+
if (!hasProp(downloadedFiles, entrypoint) && !hasProp(downloadedFiles, normalizedEntrypoint)) {
|
|
7097
|
+
throw new import_build_utils16.NowBuildError({
|
|
7098
|
+
code: "PYTHON_ENTRYPOINT_NOT_FOUND",
|
|
7099
|
+
message: `Configured Python entrypoint "${normalizedEntrypoint}" was not found.`,
|
|
7100
|
+
link: PYTHON_ENTRYPOINT_DOCS_URL2,
|
|
7101
|
+
action: "Learn More"
|
|
7102
|
+
});
|
|
7103
|
+
}
|
|
6884
7104
|
const { devCacheDir = (0, import_path13.join)(workPath, ".now", "cache") } = meta;
|
|
6885
7105
|
const cacheKey = (0, import_path13.basename)(entrypoint).replace(/\./g, "_");
|
|
6886
7106
|
const destCache = (0, import_path13.join)(devCacheDir, cacheKey);
|
|
@@ -6932,7 +7152,7 @@ var build = async ({
|
|
|
6932
7152
|
filePath: entrypoint,
|
|
6933
7153
|
// For schedule-triggered jobs, the WSGI variable is always 'app' (created dynamically).
|
|
6934
7154
|
// For other services, handlerFunction is used as the entrypoint variable name.
|
|
6935
|
-
varName: service
|
|
7155
|
+
varName: service && (0, import_build_utils16.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
|
|
6936
7156
|
} : void 0,
|
|
6937
7157
|
service
|
|
6938
7158
|
) ?? void 0;
|
|
@@ -6986,6 +7206,7 @@ var build = async ({
|
|
|
6986
7206
|
const venvPath = service?.name ? (0, import_path13.join)(workPath, ".vercel", "python", "services", service.name, ".venv") : (0, import_path13.join)(workPath, ".vercel", "python", ".venv");
|
|
6987
7207
|
const hasCachedVenv = import_fs13.default.existsSync((0, import_path13.join)(venvPath, "pyvenv.cfg"));
|
|
6988
7208
|
const hasCachedUv = import_fs13.default.existsSync(uvCacheDir);
|
|
7209
|
+
const restoredCache = hasCachedVenv && hasCachedUv ? "both" : hasCachedVenv ? "venv" : hasCachedUv ? "uv" : "none";
|
|
6989
7210
|
if (hasCachedVenv || hasCachedUv) {
|
|
6990
7211
|
(0, import_build_utils16.debug)(
|
|
6991
7212
|
`Build cache detected: venv=${hasCachedVenv}, uv-cache=${hasCachedUv}`
|
|
@@ -7032,7 +7253,9 @@ var build = async ({
|
|
|
7032
7253
|
let uvProjectDir = null;
|
|
7033
7254
|
let projectName;
|
|
7034
7255
|
await builderSpan.child(import_build_utils16.BUILDER_INSTALLER_STEP, {
|
|
7035
|
-
installCommand: projectInstallCommand || void 0
|
|
7256
|
+
installCommand: projectInstallCommand || void 0,
|
|
7257
|
+
runtime: "python",
|
|
7258
|
+
"python.cache.restored": restoredCache
|
|
7036
7259
|
}).trace(async () => {
|
|
7037
7260
|
if (projectInstallCommand) {
|
|
7038
7261
|
await import_fs13.default.promises.rm(venvPath, { recursive: true, force: true });
|
|
@@ -7313,7 +7536,8 @@ from vercel_runtime.vc_init import vc_handler
|
|
|
7313
7536
|
pythonPackage,
|
|
7314
7537
|
pythonVersion,
|
|
7315
7538
|
uvLockPath,
|
|
7316
|
-
framework
|
|
7539
|
+
framework,
|
|
7540
|
+
serviceType: service ? (0, import_build_utils16.getReportedServiceType)(service) : void 0
|
|
7317
7541
|
});
|
|
7318
7542
|
} catch (err) {
|
|
7319
7543
|
(0, import_build_utils16.debug)(
|
|
@@ -7343,9 +7567,6 @@ var prepareCache = async ({
|
|
|
7343
7567
|
repoRootPath,
|
|
7344
7568
|
workPath
|
|
7345
7569
|
}) => {
|
|
7346
|
-
if (process.env.VERCEL_PYTHON_PREPARE_CACHE !== "1") {
|
|
7347
|
-
return {};
|
|
7348
|
-
}
|
|
7349
7570
|
const root = repoRootPath || workPath;
|
|
7350
7571
|
const ignore = ["**/*.pyc", "**/__pycache__/**"];
|
|
7351
7572
|
const uvCacheDir = getUvCacheDir(workPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/python",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.39.0",
|
|
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,6 @@
|
|
|
21
21
|
"@renovatebot/pep440": "4.2.1",
|
|
22
22
|
"@types/execa": "^0.9.0",
|
|
23
23
|
"@types/fs-extra": "11.0.2",
|
|
24
|
-
"@types/jest": "27.4.1",
|
|
25
24
|
"@types/node": "20.11.0",
|
|
26
25
|
"@types/which": "3.0.0",
|
|
27
26
|
"cross-env": "7.0.3",
|
|
@@ -30,23 +29,23 @@
|
|
|
30
29
|
"fs-extra": "11.1.1",
|
|
31
30
|
"get-port": "5.1.1",
|
|
32
31
|
"is-port-reachable": "3.1.0",
|
|
33
|
-
"jest-junit": "16.0.0",
|
|
34
32
|
"minimatch": "10.1.1",
|
|
35
33
|
"pip-requirements-js": "1.0.2",
|
|
36
34
|
"smol-toml": "1.5.2",
|
|
37
35
|
"vitest": "2.1.4",
|
|
38
36
|
"which": "3.0.0",
|
|
39
|
-
"@vercel/
|
|
40
|
-
"@vercel/
|
|
41
|
-
"@vercel/
|
|
37
|
+
"@vercel/error-utils": "2.1.0",
|
|
38
|
+
"@vercel/python-runtime": "0.13.2",
|
|
39
|
+
"@vercel/build-utils": "13.22.0"
|
|
42
40
|
},
|
|
43
41
|
"scripts": {
|
|
44
42
|
"build": "node ../../utils/build-builder.mjs",
|
|
45
43
|
"type-check": "tsc --noEmit",
|
|
46
|
-
"test": "cross-env VERCEL_FORCE_PYTHON_STREAMING=1 NODE_OPTIONS=--experimental-vm-modules
|
|
44
|
+
"test": "cross-env VERCEL_FORCE_PYTHON_STREAMING=1 NODE_OPTIONS=--experimental-vm-modules vitest run --config ../../vitest.config.mts",
|
|
47
45
|
"test-unit": "vitest run --config ../../vitest.config.mts test/unit.test.ts",
|
|
48
46
|
"test-e2e": "pnpm test test/integration-*",
|
|
49
47
|
"vitest-run": "vitest -c ../../vitest.config.mts",
|
|
50
|
-
"vitest-unit": "glob --absolute 'test/unit.test.ts'"
|
|
48
|
+
"vitest-unit": "glob --absolute 'test/unit.test.ts'",
|
|
49
|
+
"vitest-e2e": "glob --absolute 'test/integration-*'"
|
|
51
50
|
}
|
|
52
51
|
}
|