@vercel/python 6.36.1 → 6.38.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 +280 -47
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -3231,7 +3231,7 @@ var import_path13 = require("path");
|
|
|
3231
3231
|
|
|
3232
3232
|
// src/package-versions.ts
|
|
3233
3233
|
var VERCEL_RUNTIME_VERSION = "0.13.1";
|
|
3234
|
-
var VERCEL_WORKERS_VERSION = "0.0.
|
|
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
|
}
|
|
@@ -5280,6 +5306,7 @@ var PYTHON_ENTRYPOINT_DIRS = ["", "src", "app", "api"];
|
|
|
5280
5306
|
var PYTHON_CANDIDATE_ENTRYPOINTS = getCandidateEntrypointsInDirs(
|
|
5281
5307
|
PYTHON_ENTRYPOINT_DIRS
|
|
5282
5308
|
);
|
|
5309
|
+
var PYTHON_ENTRYPOINT_DOCS_URL = "https://vercel.com/docs/functions/runtimes/python#python-entrypoints";
|
|
5283
5310
|
function getCandidateEntrypointsInDirs(dirs) {
|
|
5284
5311
|
return dirs.flatMap(
|
|
5285
5312
|
(dir) => PYTHON_ENTRYPOINT_FILENAMES.map(
|
|
@@ -5295,6 +5322,51 @@ async function fileExists(filePath) {
|
|
|
5295
5322
|
return false;
|
|
5296
5323
|
}
|
|
5297
5324
|
}
|
|
5325
|
+
function getFrameworkDisplayName(framework) {
|
|
5326
|
+
switch (framework) {
|
|
5327
|
+
case "fastapi":
|
|
5328
|
+
return "FastAPI";
|
|
5329
|
+
case "flask":
|
|
5330
|
+
return "Flask";
|
|
5331
|
+
case "django":
|
|
5332
|
+
return "Django";
|
|
5333
|
+
case "fasthtml":
|
|
5334
|
+
return "FastHTML";
|
|
5335
|
+
default:
|
|
5336
|
+
return framework;
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
function getFrameworkEntrypointDocsUrl(framework) {
|
|
5340
|
+
switch (framework) {
|
|
5341
|
+
case "fastapi":
|
|
5342
|
+
return "https://vercel.com/docs/frameworks/backend/fastapi#exporting-the-fastapi-application";
|
|
5343
|
+
case "flask":
|
|
5344
|
+
return "https://vercel.com/docs/frameworks/backend/flask#exporting-the-flask-application";
|
|
5345
|
+
case "django":
|
|
5346
|
+
return "https://vercel.com/docs/frameworks/full-stack/django#configure-the-django-entrypoint";
|
|
5347
|
+
default:
|
|
5348
|
+
return PYTHON_ENTRYPOINT_DOCS_URL;
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
5351
|
+
function getMissingExportMessage(framework, entrypoints) {
|
|
5352
|
+
const fileList = entrypoints.slice(0, 3).join(", ");
|
|
5353
|
+
const found = `Found ${fileList}`;
|
|
5354
|
+
const subject = entrypoints.length === 1 ? "it" : "none";
|
|
5355
|
+
const verb = entrypoints.length === 1 ? "does not define" : "define";
|
|
5356
|
+
const displayName = getFrameworkDisplayName(framework);
|
|
5357
|
+
switch (framework) {
|
|
5358
|
+
case "fastapi":
|
|
5359
|
+
return `${found} but ${subject} ${verb} a top-level "app" FastAPI instance.`;
|
|
5360
|
+
case "flask":
|
|
5361
|
+
return `${found} but ${subject} ${verb} a top-level "app" Flask instance.`;
|
|
5362
|
+
case "django":
|
|
5363
|
+
return `${found} but ${subject} ${verb} a top-level "application" ASGI or WSGI instance.`;
|
|
5364
|
+
case "fasthtml":
|
|
5365
|
+
return `${found} but ${subject} ${verb} a top-level "app" ${displayName} instance.`;
|
|
5366
|
+
default:
|
|
5367
|
+
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.`;
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5298
5370
|
async function checkEntrypoint(workPath, relPath) {
|
|
5299
5371
|
const absPath = (0, import_path8.join)(workPath, relPath);
|
|
5300
5372
|
if (!await fileExists(absPath))
|
|
@@ -5302,37 +5374,92 @@ async function checkEntrypoint(workPath, relPath) {
|
|
|
5302
5374
|
const content = await import_fs8.default.promises.readFile(absPath, "utf-8");
|
|
5303
5375
|
return (0, import_python_analysis5.findAppOrHandler)(content);
|
|
5304
5376
|
}
|
|
5305
|
-
async function
|
|
5377
|
+
async function findOtherPyFiles(workPath, dirs, candidateSet) {
|
|
5378
|
+
const results = [];
|
|
5379
|
+
for (const dir of dirs) {
|
|
5380
|
+
const dirPath = (0, import_path8.join)(workPath, dir);
|
|
5381
|
+
try {
|
|
5382
|
+
const entries = await import_fs8.default.promises.readdir(dirPath);
|
|
5383
|
+
for (const entry of entries) {
|
|
5384
|
+
if (!entry.endsWith(".py"))
|
|
5385
|
+
continue;
|
|
5386
|
+
const relPath = dir ? import_path8.posix.join(dir, entry) : entry;
|
|
5387
|
+
if (candidateSet.has(relPath))
|
|
5388
|
+
continue;
|
|
5389
|
+
const stat = await import_fs8.default.promises.stat((0, import_path8.join)(dirPath, entry));
|
|
5390
|
+
if (stat.isFile()) {
|
|
5391
|
+
results.push(relPath);
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
} catch {
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
5397
|
+
return results;
|
|
5398
|
+
}
|
|
5399
|
+
async function resolveModuleAttrEntrypoint(workPath, value) {
|
|
5400
|
+
const match = value.match(/([A-Za-z_][\w.]*)\s*:\s*([A-Za-z_][\w]*)/);
|
|
5401
|
+
if (!match)
|
|
5402
|
+
return null;
|
|
5403
|
+
const modulePath = match[1];
|
|
5404
|
+
const variableName = match[2];
|
|
5405
|
+
const relPath = modulePath.replace(/\./g, "/");
|
|
5406
|
+
const candidates = [`${relPath}.py`, `${relPath}/__init__.py`];
|
|
5407
|
+
for (const candidate of candidates) {
|
|
5408
|
+
if (await fileExists((0, import_path8.join)(workPath, candidate))) {
|
|
5409
|
+
return { entrypoint: candidate, variableName };
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
return null;
|
|
5413
|
+
}
|
|
5414
|
+
async function getVercelToolsEntrypoint(workPath) {
|
|
5306
5415
|
const pyprojectData = await (0, import_build_utils10.readConfigFile)((0, import_path8.join)(workPath, "pyproject.toml"));
|
|
5307
5416
|
if (!pyprojectData)
|
|
5308
5417
|
return null;
|
|
5418
|
+
const vercelEntrypoint = pyprojectData.tool?.vercel?.entrypoint;
|
|
5419
|
+
if (typeof vercelEntrypoint !== "string")
|
|
5420
|
+
return null;
|
|
5421
|
+
return resolveModuleAttrEntrypoint(workPath, vercelEntrypoint);
|
|
5422
|
+
}
|
|
5423
|
+
async function getPyprojectEntrypointWithDiagnostics(workPath) {
|
|
5424
|
+
const pyprojectData = await (0, import_build_utils10.readConfigFile)((0, import_path8.join)(workPath, "pyproject.toml"));
|
|
5425
|
+
if (!pyprojectData)
|
|
5426
|
+
return { entrypoint: null };
|
|
5309
5427
|
const scripts = pyprojectData.project?.scripts;
|
|
5310
5428
|
const appScript = scripts?.app;
|
|
5311
5429
|
if (typeof appScript !== "string")
|
|
5312
|
-
return null;
|
|
5430
|
+
return { entrypoint: null };
|
|
5313
5431
|
const match = appScript.match(/([A-Za-z_][\w.]*)\s*:\s*([A-Za-z_][\w]*)/);
|
|
5314
5432
|
if (!match)
|
|
5315
|
-
return null;
|
|
5433
|
+
return { entrypoint: null, appScript };
|
|
5316
5434
|
const modulePath = match[1];
|
|
5317
5435
|
const variableName = match[2];
|
|
5318
5436
|
const relPath = modulePath.replace(/\./g, "/");
|
|
5319
5437
|
const candidates = [`${relPath}.py`, `${relPath}/__init__.py`];
|
|
5320
5438
|
for (const candidate of candidates) {
|
|
5321
5439
|
if (await fileExists((0, import_path8.join)(workPath, candidate))) {
|
|
5322
|
-
return { entrypoint: candidate, variableName };
|
|
5440
|
+
return { entrypoint: { entrypoint: candidate, variableName } };
|
|
5323
5441
|
}
|
|
5324
5442
|
}
|
|
5325
|
-
return null;
|
|
5443
|
+
return { entrypoint: null, appScript, checkedPaths: candidates };
|
|
5326
5444
|
}
|
|
5327
5445
|
async function findValidEntrypoint(workPath, candidates) {
|
|
5446
|
+
const existingWithoutEntrypoint = [];
|
|
5328
5447
|
for (const candidate of candidates) {
|
|
5329
|
-
const
|
|
5448
|
+
const absPath = (0, import_path8.join)(workPath, candidate);
|
|
5449
|
+
if (!await fileExists(absPath))
|
|
5450
|
+
continue;
|
|
5451
|
+
const content = await import_fs8.default.promises.readFile(absPath, "utf-8");
|
|
5452
|
+
const varName = await (0, import_python_analysis5.findAppOrHandler)(content);
|
|
5330
5453
|
if (varName) {
|
|
5331
5454
|
(0, import_build_utils9.debug)(`Detected Python entrypoint: ${candidate} (variable: ${varName})`);
|
|
5332
|
-
return {
|
|
5455
|
+
return {
|
|
5456
|
+
entrypoint: { entrypoint: candidate, variableName: varName },
|
|
5457
|
+
existingWithoutEntrypoint
|
|
5458
|
+
};
|
|
5333
5459
|
}
|
|
5460
|
+
existingWithoutEntrypoint.push(candidate);
|
|
5334
5461
|
}
|
|
5335
|
-
return null;
|
|
5462
|
+
return { entrypoint: null, existingWithoutEntrypoint };
|
|
5336
5463
|
}
|
|
5337
5464
|
async function checkDjangoManage(workPath) {
|
|
5338
5465
|
const managePath = (0, import_path8.join)(workPath, "manage.py");
|
|
@@ -5356,28 +5483,56 @@ async function getSubdirectories(workPath) {
|
|
|
5356
5483
|
return [];
|
|
5357
5484
|
}
|
|
5358
5485
|
}
|
|
5359
|
-
function
|
|
5360
|
-
const
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5486
|
+
function makeDiagnosticError(framework, diagnostics2) {
|
|
5487
|
+
const code = `${framework.toUpperCase()}_ENTRYPOINT_NOT_FOUND`;
|
|
5488
|
+
const link = getFrameworkEntrypointDocsUrl(framework);
|
|
5489
|
+
const action = "Learn More";
|
|
5490
|
+
const displayName = getFrameworkDisplayName(framework);
|
|
5491
|
+
let message;
|
|
5492
|
+
if (diagnostics2.existingWithoutEntrypoint.length > 0) {
|
|
5493
|
+
message = getMissingExportMessage(
|
|
5494
|
+
framework,
|
|
5495
|
+
diagnostics2.existingWithoutEntrypoint
|
|
5496
|
+
);
|
|
5497
|
+
} else if (diagnostics2.pyprojectAppScript && diagnostics2.pyprojectCheckedPaths?.length) {
|
|
5498
|
+
const checked = diagnostics2.pyprojectCheckedPaths.join(" or ");
|
|
5499
|
+
message = `pyproject.toml [project.scripts] defines app = "${diagnostics2.pyprojectAppScript}" but ${checked} was not found.`;
|
|
5500
|
+
} else if (diagnostics2.otherPyFiles.length > 0) {
|
|
5501
|
+
const fileList = diagnostics2.otherPyFiles.slice(0, 5).join(", ");
|
|
5502
|
+
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.`;
|
|
5503
|
+
} else {
|
|
5504
|
+
const searchedList = PYTHON_CANDIDATE_ENTRYPOINTS.join(", ");
|
|
5505
|
+
message = `No ${displayName} entrypoint found. Set "tool.vercel.entrypoint" in pyproject.toml or define an entrypoint in one of: ${searchedList}.`;
|
|
5506
|
+
}
|
|
5507
|
+
return new import_build_utils8.NowBuildError({ code, message, link, action });
|
|
5367
5508
|
}
|
|
5368
5509
|
async function detectGenericPythonEntrypoint(workPath) {
|
|
5369
5510
|
try {
|
|
5370
|
-
const
|
|
5511
|
+
const result = await findValidEntrypoint(
|
|
5371
5512
|
workPath,
|
|
5372
5513
|
PYTHON_CANDIDATE_ENTRYPOINTS
|
|
5373
5514
|
);
|
|
5374
|
-
return
|
|
5515
|
+
return {
|
|
5516
|
+
detected: result.entrypoint ? { entrypoint: result.entrypoint } : null,
|
|
5517
|
+
findDiagnostics: result
|
|
5518
|
+
};
|
|
5375
5519
|
} catch {
|
|
5376
5520
|
(0, import_build_utils9.debug)("Failed to discover Python entrypoint");
|
|
5377
|
-
return
|
|
5521
|
+
return {
|
|
5522
|
+
detected: null,
|
|
5523
|
+
findDiagnostics: { entrypoint: null, existingWithoutEntrypoint: [] }
|
|
5524
|
+
};
|
|
5378
5525
|
}
|
|
5379
5526
|
}
|
|
5380
5527
|
async function detectDjangoPythonEntrypoint(workPath) {
|
|
5528
|
+
const emptyResult = {
|
|
5529
|
+
detected: null,
|
|
5530
|
+
findDiagnostics: {
|
|
5531
|
+
entrypoint: null,
|
|
5532
|
+
existingWithoutEntrypoint: []
|
|
5533
|
+
},
|
|
5534
|
+
dirs: PYTHON_ENTRYPOINT_DIRS
|
|
5535
|
+
};
|
|
5381
5536
|
try {
|
|
5382
5537
|
const subdirs = await getSubdirectories(workPath);
|
|
5383
5538
|
const rootDirs = ["", ...subdirs];
|
|
@@ -5385,21 +5540,40 @@ async function detectDjangoPythonEntrypoint(workPath) {
|
|
|
5385
5540
|
const currPath = (0, import_path8.join)(workPath, rootDir);
|
|
5386
5541
|
const isDjango = await checkDjangoManage(currPath);
|
|
5387
5542
|
if (isDjango) {
|
|
5388
|
-
return {
|
|
5543
|
+
return {
|
|
5544
|
+
detected: { baseDir: rootDir },
|
|
5545
|
+
findDiagnostics: { entrypoint: null, existingWithoutEntrypoint: [] },
|
|
5546
|
+
dirs: rootDirs
|
|
5547
|
+
};
|
|
5389
5548
|
}
|
|
5390
5549
|
}
|
|
5391
5550
|
const candidates = getCandidateEntrypointsInDirs(rootDirs);
|
|
5392
|
-
const
|
|
5393
|
-
return
|
|
5551
|
+
const result = await findValidEntrypoint(workPath, candidates);
|
|
5552
|
+
return {
|
|
5553
|
+
detected: result.entrypoint ? { entrypoint: result.entrypoint } : null,
|
|
5554
|
+
findDiagnostics: result,
|
|
5555
|
+
dirs: rootDirs
|
|
5556
|
+
};
|
|
5394
5557
|
} catch {
|
|
5395
5558
|
(0, import_build_utils9.debug)("Failed to discover Django Python entrypoint");
|
|
5396
|
-
return
|
|
5559
|
+
return emptyResult;
|
|
5397
5560
|
}
|
|
5398
5561
|
}
|
|
5399
5562
|
async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint, service) {
|
|
5400
5563
|
if (configuredEntrypoint) {
|
|
5401
5564
|
const { filePath: configEntryFile, varName: configEntryVar } = configuredEntrypoint;
|
|
5402
5565
|
const entrypoint = configEntryFile.endsWith(".py") ? configEntryFile : `${configEntryFile}.py`;
|
|
5566
|
+
const entrypointExists = await fileExists((0, import_path8.join)(workPath, entrypoint));
|
|
5567
|
+
if (!entrypointExists) {
|
|
5568
|
+
return {
|
|
5569
|
+
error: new import_build_utils8.NowBuildError({
|
|
5570
|
+
code: "PYTHON_ENTRYPOINT_NOT_FOUND",
|
|
5571
|
+
message: `Configured Python entrypoint "${entrypoint}" was not found.`,
|
|
5572
|
+
link: PYTHON_ENTRYPOINT_DOCS_URL,
|
|
5573
|
+
action: "Learn More"
|
|
5574
|
+
})
|
|
5575
|
+
};
|
|
5576
|
+
}
|
|
5403
5577
|
let varName = configEntryVar ?? await checkEntrypoint(workPath, entrypoint);
|
|
5404
5578
|
if (!varName) {
|
|
5405
5579
|
const needsDynamicApp = !!service && ((0, import_build_utils8.isScheduleTriggeredService)(service) || (0, import_build_utils8.isQueueTriggeredService)(service));
|
|
@@ -5415,7 +5589,7 @@ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint,
|
|
|
5415
5589
|
error: new import_build_utils8.NowBuildError({
|
|
5416
5590
|
code: "PYTHON_ENTRYPOINT_NOT_FOUND",
|
|
5417
5591
|
message: `Could not find a top-level "app", "application", or "handler" in "${entrypoint}".`,
|
|
5418
|
-
link:
|
|
5592
|
+
link: PYTHON_ENTRYPOINT_DOCS_URL,
|
|
5419
5593
|
action: "Learn More"
|
|
5420
5594
|
})
|
|
5421
5595
|
};
|
|
@@ -5424,11 +5598,60 @@ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint,
|
|
|
5424
5598
|
if (!framework) {
|
|
5425
5599
|
return null;
|
|
5426
5600
|
}
|
|
5427
|
-
const
|
|
5428
|
-
if (
|
|
5429
|
-
return
|
|
5430
|
-
|
|
5431
|
-
|
|
5601
|
+
const vercelEntry = await getVercelToolsEntrypoint(workPath);
|
|
5602
|
+
if (vercelEntry)
|
|
5603
|
+
return { entrypoint: vercelEntry };
|
|
5604
|
+
let findDiagnostics;
|
|
5605
|
+
let searchDirs;
|
|
5606
|
+
if (framework === "django") {
|
|
5607
|
+
const djangoResult = await detectDjangoPythonEntrypoint(workPath);
|
|
5608
|
+
findDiagnostics = djangoResult.findDiagnostics;
|
|
5609
|
+
searchDirs = djangoResult.dirs;
|
|
5610
|
+
if (djangoResult.detected) {
|
|
5611
|
+
if (djangoResult.detected.entrypoint)
|
|
5612
|
+
return djangoResult.detected;
|
|
5613
|
+
const candidateSet2 = new Set(getCandidateEntrypointsInDirs(searchDirs));
|
|
5614
|
+
const otherPyFiles2 = await findOtherPyFiles(
|
|
5615
|
+
workPath,
|
|
5616
|
+
searchDirs,
|
|
5617
|
+
candidateSet2
|
|
5618
|
+
);
|
|
5619
|
+
const pyprojectResult2 = await getPyprojectEntrypointWithDiagnostics(workPath);
|
|
5620
|
+
const diagnostics3 = {
|
|
5621
|
+
existingWithoutEntrypoint: findDiagnostics.existingWithoutEntrypoint,
|
|
5622
|
+
otherPyFiles: otherPyFiles2,
|
|
5623
|
+
pyprojectAppScript: pyprojectResult2.appScript,
|
|
5624
|
+
pyprojectCheckedPaths: pyprojectResult2.checkedPaths
|
|
5625
|
+
};
|
|
5626
|
+
return {
|
|
5627
|
+
baseDir: djangoResult.detected.baseDir,
|
|
5628
|
+
error: makeDiagnosticError("django", diagnostics3)
|
|
5629
|
+
};
|
|
5630
|
+
}
|
|
5631
|
+
} else {
|
|
5632
|
+
const genericResult = await detectGenericPythonEntrypoint(workPath);
|
|
5633
|
+
findDiagnostics = genericResult.findDiagnostics;
|
|
5634
|
+
searchDirs = PYTHON_ENTRYPOINT_DIRS;
|
|
5635
|
+
if (genericResult.detected)
|
|
5636
|
+
return genericResult.detected;
|
|
5637
|
+
}
|
|
5638
|
+
const pyprojectResult = await getPyprojectEntrypointWithDiagnostics(workPath);
|
|
5639
|
+
if (pyprojectResult.entrypoint) {
|
|
5640
|
+
return { entrypoint: pyprojectResult.entrypoint };
|
|
5641
|
+
}
|
|
5642
|
+
const candidateSet = new Set(getCandidateEntrypointsInDirs(searchDirs));
|
|
5643
|
+
const otherPyFiles = await findOtherPyFiles(
|
|
5644
|
+
workPath,
|
|
5645
|
+
searchDirs,
|
|
5646
|
+
candidateSet
|
|
5647
|
+
);
|
|
5648
|
+
const diagnostics2 = {
|
|
5649
|
+
existingWithoutEntrypoint: findDiagnostics.existingWithoutEntrypoint,
|
|
5650
|
+
otherPyFiles,
|
|
5651
|
+
pyprojectAppScript: pyprojectResult.appScript,
|
|
5652
|
+
pyprojectCheckedPaths: pyprojectResult.checkedPaths
|
|
5653
|
+
};
|
|
5654
|
+
return { error: makeDiagnosticError(framework, diagnostics2) };
|
|
5432
5655
|
}
|
|
5433
5656
|
|
|
5434
5657
|
// src/start-dev-server.ts
|
|
@@ -6779,6 +7002,7 @@ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir,
|
|
|
6779
7002
|
// src/index.ts
|
|
6780
7003
|
var import_python_analysis9 = require("@vercel/python-analysis");
|
|
6781
7004
|
var writeFile = import_fs13.default.promises.writeFile;
|
|
7005
|
+
var PYTHON_ENTRYPOINT_DOCS_URL2 = "https://vercel.com/docs/functions/runtimes/python#python-entrypoints";
|
|
6782
7006
|
var version = -1;
|
|
6783
7007
|
async function runFrameworkHook(framework, ctx) {
|
|
6784
7008
|
const hook = framework ? frameworkHooks[framework] : void 0;
|
|
@@ -6866,6 +7090,15 @@ async function downloadFilesInWorkPath({
|
|
|
6866
7090
|
(0, import_build_utils16.debug)("Downloading user files...");
|
|
6867
7091
|
let downloadedFiles = await (0, import_build_utils16.download)(files, workPath, meta);
|
|
6868
7092
|
if (meta.isDev && entrypoint) {
|
|
7093
|
+
const normalizedEntrypoint = entrypoint.endsWith(".py") ? entrypoint : `${entrypoint}.py`;
|
|
7094
|
+
if (!hasProp(downloadedFiles, entrypoint) && !hasProp(downloadedFiles, normalizedEntrypoint)) {
|
|
7095
|
+
throw new import_build_utils16.NowBuildError({
|
|
7096
|
+
code: "PYTHON_ENTRYPOINT_NOT_FOUND",
|
|
7097
|
+
message: `Configured Python entrypoint "${normalizedEntrypoint}" was not found.`,
|
|
7098
|
+
link: PYTHON_ENTRYPOINT_DOCS_URL2,
|
|
7099
|
+
action: "Learn More"
|
|
7100
|
+
});
|
|
7101
|
+
}
|
|
6869
7102
|
const { devCacheDir = (0, import_path13.join)(workPath, ".now", "cache") } = meta;
|
|
6870
7103
|
const cacheKey = (0, import_path13.basename)(entrypoint).replace(/\./g, "_");
|
|
6871
7104
|
const destCache = (0, import_path13.join)(devCacheDir, cacheKey);
|
|
@@ -6971,6 +7204,7 @@ var build = async ({
|
|
|
6971
7204
|
const venvPath = service?.name ? (0, import_path13.join)(workPath, ".vercel", "python", "services", service.name, ".venv") : (0, import_path13.join)(workPath, ".vercel", "python", ".venv");
|
|
6972
7205
|
const hasCachedVenv = import_fs13.default.existsSync((0, import_path13.join)(venvPath, "pyvenv.cfg"));
|
|
6973
7206
|
const hasCachedUv = import_fs13.default.existsSync(uvCacheDir);
|
|
7207
|
+
const restoredCache = hasCachedVenv && hasCachedUv ? "both" : hasCachedVenv ? "venv" : hasCachedUv ? "uv" : "none";
|
|
6974
7208
|
if (hasCachedVenv || hasCachedUv) {
|
|
6975
7209
|
(0, import_build_utils16.debug)(
|
|
6976
7210
|
`Build cache detected: venv=${hasCachedVenv}, uv-cache=${hasCachedUv}`
|
|
@@ -7017,7 +7251,9 @@ var build = async ({
|
|
|
7017
7251
|
let uvProjectDir = null;
|
|
7018
7252
|
let projectName;
|
|
7019
7253
|
await builderSpan.child(import_build_utils16.BUILDER_INSTALLER_STEP, {
|
|
7020
|
-
installCommand: projectInstallCommand || void 0
|
|
7254
|
+
installCommand: projectInstallCommand || void 0,
|
|
7255
|
+
runtime: "python",
|
|
7256
|
+
"python.cache.restored": restoredCache
|
|
7021
7257
|
}).trace(async () => {
|
|
7022
7258
|
if (projectInstallCommand) {
|
|
7023
7259
|
await import_fs13.default.promises.rm(venvPath, { recursive: true, force: true });
|
|
@@ -7328,9 +7564,6 @@ var prepareCache = async ({
|
|
|
7328
7564
|
repoRootPath,
|
|
7329
7565
|
workPath
|
|
7330
7566
|
}) => {
|
|
7331
|
-
if (process.env.VERCEL_PYTHON_PREPARE_CACHE !== "1") {
|
|
7332
|
-
return {};
|
|
7333
|
-
}
|
|
7334
7567
|
const root = repoRootPath || workPath;
|
|
7335
7568
|
const ignore = ["**/*.pyc", "**/__pycache__/**"];
|
|
7336
7569
|
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.38.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"directory": "packages/python"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@vercel/python-analysis": "0.11.
|
|
18
|
+
"@vercel/python-analysis": "0.11.1"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@renovatebot/pep440": "4.2.1",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"smol-toml": "1.5.2",
|
|
37
37
|
"vitest": "2.1.4",
|
|
38
38
|
"which": "3.0.0",
|
|
39
|
-
"@vercel/build-utils": "13.20.0",
|
|
40
39
|
"@vercel/python-runtime": "0.13.1",
|
|
41
|
-
"@vercel/
|
|
40
|
+
"@vercel/build-utils": "13.21.0",
|
|
41
|
+
"@vercel/error-utils": "2.1.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "node ../../utils/build-builder.mjs",
|