@vercel/python 6.37.0 → 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.
Files changed (2) hide show
  1. package/dist/index.js +264 -46
  2. package/package.json +1 -1
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.20";
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 optimize dependency bundling. To reduce the size of your dependencies, you can:
4394
- 1. Remove unused dependencies from your project
4395
- 2. Remove the custom install command to allow Vercel to manage and optimize dependencies automatically`,
4396
- link: "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled",
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). Consider removing unused dependencies or splitting your application into smaller functions.`,
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). Even with runtime dependency installation, all packages must fit within the ${ephemeralLimitMB} MB ephemeral storage available to Lambda functions. Consider removing unused dependencies or splitting your application into smaller functions.`,
4444
- link: "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled",
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 dependency installation, but no public packages have compatible pre-built wheels for the Lambda platform.
4517
- Runtime dependency installation requires packages to have binary wheels.
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
- 1. Regenerate your lock file with: uv lock --upgrade, or
4521
- 2. Switch the problematic packages to ones that have pre-built wheels available`
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 deferring public packages to runtime installation. This usually means your private packages or source code are too large. Consider reducing the size of private dependencies or splitting your application.`,
4640
- link: "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled",
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,6 +5374,28 @@ 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
  }
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
+ }
5305
5399
  async function resolveModuleAttrEntrypoint(workPath, value) {
5306
5400
  const match = value.match(/([A-Za-z_][\w.]*)\s*:\s*([A-Za-z_][\w]*)/);
5307
5401
  if (!match)
@@ -5326,25 +5420,46 @@ async function getVercelToolsEntrypoint(workPath) {
5326
5420
  return null;
5327
5421
  return resolveModuleAttrEntrypoint(workPath, vercelEntrypoint);
5328
5422
  }
5329
- async function getPyprojectScriptsEntrypoint(workPath) {
5423
+ async function getPyprojectEntrypointWithDiagnostics(workPath) {
5330
5424
  const pyprojectData = await (0, import_build_utils10.readConfigFile)((0, import_path8.join)(workPath, "pyproject.toml"));
5331
5425
  if (!pyprojectData)
5332
- return null;
5426
+ return { entrypoint: null };
5333
5427
  const scripts = pyprojectData.project?.scripts;
5334
5428
  const appScript = scripts?.app;
5335
5429
  if (typeof appScript !== "string")
5336
- return null;
5337
- return resolveModuleAttrEntrypoint(workPath, appScript);
5430
+ return { entrypoint: null };
5431
+ const match = appScript.match(/([A-Za-z_][\w.]*)\s*:\s*([A-Za-z_][\w]*)/);
5432
+ if (!match)
5433
+ return { entrypoint: null, appScript };
5434
+ const modulePath = match[1];
5435
+ const variableName = match[2];
5436
+ const relPath = modulePath.replace(/\./g, "/");
5437
+ const candidates = [`${relPath}.py`, `${relPath}/__init__.py`];
5438
+ for (const candidate of candidates) {
5439
+ if (await fileExists((0, import_path8.join)(workPath, candidate))) {
5440
+ return { entrypoint: { entrypoint: candidate, variableName } };
5441
+ }
5442
+ }
5443
+ return { entrypoint: null, appScript, checkedPaths: candidates };
5338
5444
  }
5339
5445
  async function findValidEntrypoint(workPath, candidates) {
5446
+ const existingWithoutEntrypoint = [];
5340
5447
  for (const candidate of candidates) {
5341
- const varName = await checkEntrypoint(workPath, candidate);
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);
5342
5453
  if (varName) {
5343
5454
  (0, import_build_utils9.debug)(`Detected Python entrypoint: ${candidate} (variable: ${varName})`);
5344
- return { entrypoint: candidate, variableName: varName };
5455
+ return {
5456
+ entrypoint: { entrypoint: candidate, variableName: varName },
5457
+ existingWithoutEntrypoint
5458
+ };
5345
5459
  }
5460
+ existingWithoutEntrypoint.push(candidate);
5346
5461
  }
5347
- return null;
5462
+ return { entrypoint: null, existingWithoutEntrypoint };
5348
5463
  }
5349
5464
  async function checkDjangoManage(workPath) {
5350
5465
  const managePath = (0, import_path8.join)(workPath, "manage.py");
@@ -5368,28 +5483,56 @@ async function getSubdirectories(workPath) {
5368
5483
  return [];
5369
5484
  }
5370
5485
  }
5371
- function makeDetectError(framework) {
5372
- const searchedList = PYTHON_CANDIDATE_ENTRYPOINTS.join(", ");
5373
- return new import_build_utils8.NowBuildError({
5374
- code: `${framework.toUpperCase()}_ENTRYPOINT_NOT_FOUND`,
5375
- message: `No ${framework} entrypoint found. Set \`tool.vercel.entrypoint\` in pyproject.toml or define an entrypoint in one of: ${searchedList}.`,
5376
- link: `https://vercel.com/docs/frameworks/backend/${framework}#exporting-the-${framework}-application`,
5377
- action: "Learn More"
5378
- });
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 });
5379
5508
  }
5380
5509
  async function detectGenericPythonEntrypoint(workPath) {
5381
5510
  try {
5382
- const found = await findValidEntrypoint(
5511
+ const result = await findValidEntrypoint(
5383
5512
  workPath,
5384
5513
  PYTHON_CANDIDATE_ENTRYPOINTS
5385
5514
  );
5386
- return found ? { entrypoint: found } : null;
5515
+ return {
5516
+ detected: result.entrypoint ? { entrypoint: result.entrypoint } : null,
5517
+ findDiagnostics: result
5518
+ };
5387
5519
  } catch {
5388
5520
  (0, import_build_utils9.debug)("Failed to discover Python entrypoint");
5389
- return null;
5521
+ return {
5522
+ detected: null,
5523
+ findDiagnostics: { entrypoint: null, existingWithoutEntrypoint: [] }
5524
+ };
5390
5525
  }
5391
5526
  }
5392
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
+ };
5393
5536
  try {
5394
5537
  const subdirs = await getSubdirectories(workPath);
5395
5538
  const rootDirs = ["", ...subdirs];
@@ -5397,21 +5540,40 @@ async function detectDjangoPythonEntrypoint(workPath) {
5397
5540
  const currPath = (0, import_path8.join)(workPath, rootDir);
5398
5541
  const isDjango = await checkDjangoManage(currPath);
5399
5542
  if (isDjango) {
5400
- return { baseDir: rootDir, error: makeDetectError("django") };
5543
+ return {
5544
+ detected: { baseDir: rootDir },
5545
+ findDiagnostics: { entrypoint: null, existingWithoutEntrypoint: [] },
5546
+ dirs: rootDirs
5547
+ };
5401
5548
  }
5402
5549
  }
5403
5550
  const candidates = getCandidateEntrypointsInDirs(rootDirs);
5404
- const found = await findValidEntrypoint(workPath, candidates);
5405
- return found ? { entrypoint: found } : null;
5551
+ const result = await findValidEntrypoint(workPath, candidates);
5552
+ return {
5553
+ detected: result.entrypoint ? { entrypoint: result.entrypoint } : null,
5554
+ findDiagnostics: result,
5555
+ dirs: rootDirs
5556
+ };
5406
5557
  } catch {
5407
5558
  (0, import_build_utils9.debug)("Failed to discover Django Python entrypoint");
5408
- return null;
5559
+ return emptyResult;
5409
5560
  }
5410
5561
  }
5411
5562
  async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint, service) {
5412
5563
  if (configuredEntrypoint) {
5413
5564
  const { filePath: configEntryFile, varName: configEntryVar } = configuredEntrypoint;
5414
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
+ }
5415
5577
  let varName = configEntryVar ?? await checkEntrypoint(workPath, entrypoint);
5416
5578
  if (!varName) {
5417
5579
  const needsDynamicApp = !!service && ((0, import_build_utils8.isScheduleTriggeredService)(service) || (0, import_build_utils8.isQueueTriggeredService)(service));
@@ -5427,7 +5589,7 @@ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint,
5427
5589
  error: new import_build_utils8.NowBuildError({
5428
5590
  code: "PYTHON_ENTRYPOINT_NOT_FOUND",
5429
5591
  message: `Could not find a top-level "app", "application", or "handler" in "${entrypoint}".`,
5430
- link: "https://vercel.com/docs/functions/serverless-functions/runtimes/python",
5592
+ link: PYTHON_ENTRYPOINT_DOCS_URL,
5431
5593
  action: "Learn More"
5432
5594
  })
5433
5595
  };
@@ -5439,11 +5601,57 @@ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint,
5439
5601
  const vercelEntry = await getVercelToolsEntrypoint(workPath);
5440
5602
  if (vercelEntry)
5441
5603
  return { entrypoint: vercelEntry };
5442
- const result = framework === "django" ? await detectDjangoPythonEntrypoint(workPath) : await detectGenericPythonEntrypoint(workPath);
5443
- if (result)
5444
- return result;
5445
- const scriptsEntry = await getPyprojectScriptsEntrypoint(workPath);
5446
- return scriptsEntry ? { entrypoint: scriptsEntry } : { error: makeDetectError(framework) };
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) };
5447
5655
  }
5448
5656
 
5449
5657
  // src/start-dev-server.ts
@@ -6794,6 +7002,7 @@ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir,
6794
7002
  // src/index.ts
6795
7003
  var import_python_analysis9 = require("@vercel/python-analysis");
6796
7004
  var writeFile = import_fs13.default.promises.writeFile;
7005
+ var PYTHON_ENTRYPOINT_DOCS_URL2 = "https://vercel.com/docs/functions/runtimes/python#python-entrypoints";
6797
7006
  var version = -1;
6798
7007
  async function runFrameworkHook(framework, ctx) {
6799
7008
  const hook = framework ? frameworkHooks[framework] : void 0;
@@ -6881,6 +7090,15 @@ async function downloadFilesInWorkPath({
6881
7090
  (0, import_build_utils16.debug)("Downloading user files...");
6882
7091
  let downloadedFiles = await (0, import_build_utils16.download)(files, workPath, meta);
6883
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
+ }
6884
7102
  const { devCacheDir = (0, import_path13.join)(workPath, ".now", "cache") } = meta;
6885
7103
  const cacheKey = (0, import_path13.basename)(entrypoint).replace(/\./g, "_");
6886
7104
  const destCache = (0, import_path13.join)(devCacheDir, cacheKey);
@@ -6986,6 +7204,7 @@ var build = async ({
6986
7204
  const venvPath = service?.name ? (0, import_path13.join)(workPath, ".vercel", "python", "services", service.name, ".venv") : (0, import_path13.join)(workPath, ".vercel", "python", ".venv");
6987
7205
  const hasCachedVenv = import_fs13.default.existsSync((0, import_path13.join)(venvPath, "pyvenv.cfg"));
6988
7206
  const hasCachedUv = import_fs13.default.existsSync(uvCacheDir);
7207
+ const restoredCache = hasCachedVenv && hasCachedUv ? "both" : hasCachedVenv ? "venv" : hasCachedUv ? "uv" : "none";
6989
7208
  if (hasCachedVenv || hasCachedUv) {
6990
7209
  (0, import_build_utils16.debug)(
6991
7210
  `Build cache detected: venv=${hasCachedVenv}, uv-cache=${hasCachedUv}`
@@ -7032,7 +7251,9 @@ var build = async ({
7032
7251
  let uvProjectDir = null;
7033
7252
  let projectName;
7034
7253
  await builderSpan.child(import_build_utils16.BUILDER_INSTALLER_STEP, {
7035
- installCommand: projectInstallCommand || void 0
7254
+ installCommand: projectInstallCommand || void 0,
7255
+ runtime: "python",
7256
+ "python.cache.restored": restoredCache
7036
7257
  }).trace(async () => {
7037
7258
  if (projectInstallCommand) {
7038
7259
  await import_fs13.default.promises.rm(venvPath, { recursive: true, force: true });
@@ -7343,9 +7564,6 @@ var prepareCache = async ({
7343
7564
  repoRootPath,
7344
7565
  workPath
7345
7566
  }) => {
7346
- if (process.env.VERCEL_PYTHON_PREPARE_CACHE !== "1") {
7347
- return {};
7348
- }
7349
7567
  const root = repoRootPath || workPath;
7350
7568
  const ignore = ["**/*.pyc", "**/__pycache__/**"];
7351
7569
  const uvCacheDir = getUvCacheDir(workPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "6.37.0",
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",