@vercel/python 6.47.3 → 6.49.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 +313 -147
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -4366,6 +4366,7 @@ __export(src_exports, {
4366
4366
  detectEntrypoint: () => detectEntrypoint,
4367
4367
  diagnostics: () => diagnostics,
4368
4368
  downloadFilesInWorkPath: () => downloadFilesInWorkPath,
4369
+ getDevSidecars: () => getDevSidecars,
4369
4370
  installRequirement: () => installRequirement,
4370
4371
  installRequirementsFile: () => installRequirementsFile,
4371
4372
  prepareCache: () => prepareCache,
@@ -5649,33 +5650,26 @@ var import_execa4 = __toESM(require_execa());
5649
5650
  var import_build_utils6 = require("@vercel/build-utils");
5650
5651
  var import_fs5 = __toESM(require("fs"));
5651
5652
  var import_path6 = require("path");
5652
-
5653
- // src/large-functions.ts
5654
- function isLargeFunctionsEnabled() {
5655
- const value = process.env.VERCEL_SUPPORT_LARGE_FUNCTIONS;
5656
- return value === "1" || value === "true";
5657
- }
5658
-
5659
- // src/compileall.ts
5660
- function isCompileAllEnabled() {
5661
- if (!isLargeFunctionsEnabled())
5662
- return false;
5653
+ var COMPILEALL_TIMEOUT_MS = 5 * 60 * 1e3;
5654
+ function isCompileAllFlagEnabled() {
5663
5655
  const val = process.env.VERCEL_PYTHON_COMPILEALL;
5664
5656
  if (val === void 0 || val === "")
5665
5657
  return false;
5666
5658
  const lower = val.toLowerCase();
5667
5659
  return lower === "1" || lower === "true";
5668
5660
  }
5669
- function shouldUseCompileAll({
5661
+ function shouldCompileAll({
5670
5662
  isDev,
5671
5663
  hasCustomCommand,
5672
- hasCustomBuildCommand
5664
+ hasPreDeployCommand
5673
5665
  }) {
5674
5666
  if (isDev)
5675
5667
  return false;
5676
- if (hasCustomCommand || hasCustomBuildCommand)
5668
+ if (hasCustomCommand)
5677
5669
  return false;
5678
- return isCompileAllEnabled();
5670
+ if (hasPreDeployCommand)
5671
+ return false;
5672
+ return isCompileAllFlagEnabled();
5679
5673
  }
5680
5674
  async function runCompileAll({
5681
5675
  pythonBin,
@@ -5698,7 +5692,10 @@ async function runCompileAll({
5698
5692
  ...filesOrDirectories
5699
5693
  ];
5700
5694
  try {
5701
- await (0, import_execa4.default)(pythonBin, args, { env: env || process.env });
5695
+ await (0, import_execa4.default)(pythonBin, args, {
5696
+ env: env || process.env,
5697
+ timeout: COMPILEALL_TIMEOUT_MS
5698
+ });
5702
5699
  } catch (err) {
5703
5700
  (0, import_build_utils6.debug)(`compileall error details: ${JSON.stringify(err)}`);
5704
5701
  }
@@ -5774,6 +5771,12 @@ async function collectAppBytecodeFiles({
5774
5771
  return { files, totalSize, perItemSizes };
5775
5772
  }
5776
5773
 
5774
+ // src/large-functions.ts
5775
+ function isLargeFunctionsEnabled() {
5776
+ const value = process.env.VERCEL_SUPPORT_LARGE_FUNCTIONS;
5777
+ return value === "1" || value === "true";
5778
+ }
5779
+
5777
5780
  // src/dependency-externalizer.ts
5778
5781
  var readFile = (0, import_util.promisify)(import_fs6.default.readFile);
5779
5782
  var STRIP_BASENAMES = /* @__PURE__ */ new Set([
@@ -5794,6 +5797,8 @@ function shouldStripVendorFile(filePath) {
5794
5797
  return false;
5795
5798
  }
5796
5799
  var LAMBDA_SIZE_THRESHOLD_BYTES = 225 * 1024 * 1024;
5800
+ var BYTECODE_FILL_MARGIN_BYTES = 5 * 1024 * 1024;
5801
+ var BYTECODE_FILL_CEILING_BYTES = LAMBDA_SIZE_THRESHOLD_BYTES - BYTECODE_FILL_MARGIN_BYTES;
5797
5802
  var LAMBDA_EPHEMERAL_STORAGE_BYTES = 500 * 1024 * 1024;
5798
5803
  var MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE = 5 * 1024 * 1024 * 1024;
5799
5804
  var BUNDLING_DOCS_LINK = "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled";
@@ -6510,10 +6515,12 @@ async function getPackagesReachableOnPlatform(lockFile, projectName, pythonMajor
6510
6515
  }
6511
6516
  return visited;
6512
6517
  }
6513
- async function calculateBundleSize(files) {
6518
+ async function calculateBundleSize(files, filter) {
6514
6519
  let knownSize = 0;
6515
6520
  const statPromises = [];
6516
6521
  for (const filePath of Object.keys(files)) {
6522
+ if (filter && !filter(filePath))
6523
+ continue;
6517
6524
  const file = files[filePath];
6518
6525
  if ("fsPath" in file && file.fsPath) {
6519
6526
  const fsRef = file;
@@ -6521,7 +6528,10 @@ async function calculateBundleSize(files) {
6521
6528
  knownSize += fsRef.size;
6522
6529
  } else {
6523
6530
  statPromises.push(
6524
- import_fs6.default.promises.stat(fsRef.fsPath).then((stats) => stats.size).catch((err) => {
6531
+ import_fs6.default.promises.stat(fsRef.fsPath).then((stats) => {
6532
+ fsRef.size = stats.size;
6533
+ return stats.size;
6534
+ }).catch((err) => {
6525
6535
  console.warn(
6526
6536
  `Warning: Failed to stat file ${fsRef.fsPath}, size will not be included in bundle calculation: ${err}`
6527
6537
  );
@@ -6541,6 +6551,12 @@ async function calculateBundleSize(files) {
6541
6551
  }
6542
6552
  return totalSize;
6543
6553
  }
6554
+ var PYC_TO_PY_RATIO = 1.2;
6555
+ var BYTECODE_COVERAGE_FLOOR = 0.5;
6556
+ async function estimateBytecodeSize(files) {
6557
+ const pyBytes = await calculateBundleSize(files, (p) => p.endsWith(".py"));
6558
+ return PYC_TO_PY_RATIO * pyBytes;
6559
+ }
6544
6560
  function lambdaKnapsack(packages, capacity) {
6545
6561
  if (capacity <= 0) {
6546
6562
  return [];
@@ -6870,7 +6886,7 @@ function getMissingExportMessage(framework, entrypoints) {
6870
6886
  }
6871
6887
  }
6872
6888
  function entrypointToModule(entrypoint) {
6873
- return entrypoint.replace(/\\/g, "/").replace(/\.py$/i, "").replace(/\//g, ".");
6889
+ return entrypoint.replace(/\\/g, "/").replace(/\.py$/i, "").replace(/\/__init__$/i, "").replace(/\//g, ".");
6874
6890
  }
6875
6891
  async function checkEntrypoint(workPath, relPath) {
6876
6892
  const absPath = (0, import_path8.join)(workPath, relPath);
@@ -6946,14 +6962,37 @@ async function resolveModuleAttrEntrypoint(workPath, value) {
6946
6962
  }
6947
6963
  return null;
6948
6964
  }
6949
- async function getVercelToolsEntrypoint(workPath) {
6950
- const pyprojectData = await (0, import_build_utils11.readConfigFile)((0, import_path8.join)(workPath, "pyproject.toml"));
6965
+ async function getVercelToolsEntrypoint(workPath, repoRootPath) {
6966
+ const pyprojectPath = (0, import_path8.join)(workPath, "pyproject.toml");
6967
+ const pyprojectData = await (0, import_build_utils11.readConfigFile)(pyprojectPath);
6951
6968
  if (!pyprojectData)
6952
6969
  return null;
6953
6970
  const vercelEntrypoint = pyprojectData.tool?.vercel?.entrypoint;
6954
- if (typeof vercelEntrypoint !== "string")
6971
+ if (vercelEntrypoint === void 0)
6955
6972
  return null;
6956
- return resolveModuleAttrEntrypoint(workPath, vercelEntrypoint);
6973
+ const relPyprojectPath = (0, import_path8.relative)(repoRootPath ?? workPath, pyprojectPath);
6974
+ const displayPath = !relPyprojectPath || relPyprojectPath.startsWith("..") ? pyprojectPath : relPyprojectPath;
6975
+ if (typeof vercelEntrypoint !== "string") {
6976
+ throw new import_build_utils9.NowBuildError({
6977
+ code: "PYTHON_INVALID_ENTRYPOINT",
6978
+ message: `"tool.vercel.entrypoint" in "${displayPath}" must be a string in "module:object" format (e.g. "main:app").`,
6979
+ link: PYTHON_ENTRYPOINT_DOCS_URL,
6980
+ action: "Learn More"
6981
+ });
6982
+ }
6983
+ const resolved = await resolveModuleAttrEntrypoint(
6984
+ workPath,
6985
+ vercelEntrypoint
6986
+ );
6987
+ if (!resolved) {
6988
+ throw new import_build_utils9.NowBuildError({
6989
+ code: "PYTHON_ENTRYPOINT_NOT_FOUND",
6990
+ message: `"tool.vercel.entrypoint" in "${displayPath}" is "${vercelEntrypoint}" but no matching module file was found. Use "module:object" format (e.g. "main:app") and ensure the module file exists, or remove the setting to use automatic entrypoint detection.`,
6991
+ link: PYTHON_ENTRYPOINT_DOCS_URL,
6992
+ action: "Learn More"
6993
+ });
6994
+ }
6995
+ return resolved;
6957
6996
  }
6958
6997
  async function getPyprojectEntrypointWithDiagnostics(workPath) {
6959
6998
  const pyprojectData = await (0, import_build_utils11.readConfigFile)((0, import_path8.join)(workPath, "pyproject.toml"));
@@ -7107,7 +7146,7 @@ async function detectDjangoPythonEntrypoint(workPath) {
7107
7146
  return emptyResult;
7108
7147
  }
7109
7148
  }
7110
- async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint, service) {
7149
+ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint, service, repoRootPath) {
7111
7150
  if (configuredEntrypoint) {
7112
7151
  const { filePath: configEntryFile, varName: configEntryVar } = configuredEntrypoint;
7113
7152
  const entrypoint = configEntryFile.endsWith(".py") ? configEntryFile : `${configEntryFile}.py`;
@@ -7146,7 +7185,7 @@ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint,
7146
7185
  if (!framework) {
7147
7186
  return null;
7148
7187
  }
7149
- const vercelEntry = await getVercelToolsEntrypoint(workPath);
7188
+ const vercelEntry = await getVercelToolsEntrypoint(workPath, repoRootPath);
7150
7189
  if (vercelEntry)
7151
7190
  return { entrypoint: vercelEntry };
7152
7191
  let findDiagnostics;
@@ -7186,10 +7225,18 @@ var detectEntrypoint = async ({
7186
7225
  }) => {
7187
7226
  if (!(0, import_build_utils9.isPythonFramework)(framework))
7188
7227
  return null;
7189
- const detected = await detectPythonEntrypoint(
7190
- framework,
7191
- workPath
7192
- );
7228
+ let detected;
7229
+ try {
7230
+ detected = await detectPythonEntrypoint(
7231
+ framework,
7232
+ workPath
7233
+ );
7234
+ } catch (err) {
7235
+ (0, import_build_utils10.debug)(
7236
+ `Python entrypoint detection failed for ${workPath}: ${err instanceof Error ? err.message : String(err)}`
7237
+ );
7238
+ return null;
7239
+ }
7193
7240
  if (!detected?.entrypoint)
7194
7241
  return null;
7195
7242
  const { entrypoint, variableName } = detected.entrypoint;
@@ -7415,6 +7462,23 @@ async function getReachableHost(port) {
7415
7462
  ]);
7416
7463
  return results.find(Boolean) || false;
7417
7464
  }
7465
+ async function dedupePendingOperation(operations, key, operation) {
7466
+ const existing = operations.get(key);
7467
+ if (existing) {
7468
+ return existing;
7469
+ }
7470
+ const pending = operation();
7471
+ operations.set(key, pending);
7472
+ try {
7473
+ return await pending;
7474
+ } finally {
7475
+ if (operations.get(key) === pending) {
7476
+ operations.delete(key);
7477
+ }
7478
+ }
7479
+ }
7480
+ var PENDING_MANAGED_VENV_CREATIONS = /* @__PURE__ */ new Map();
7481
+ var PENDING_DEPENDENCY_SYNCS = /* @__PURE__ */ new Map();
7418
7482
  async function syncDependencies({
7419
7483
  workPath,
7420
7484
  uvPath,
@@ -7558,16 +7622,29 @@ async function runSync({
7558
7622
  });
7559
7623
  }
7560
7624
  var PENDING_INSTALLS = /* @__PURE__ */ new Map();
7625
+ var COMPLETED_INSTALLS = /* @__PURE__ */ new Set();
7626
+ function hasInstalledDistribution(targetDir, packageName) {
7627
+ const prefix = `${packageName.replace("-", "_")}-`;
7628
+ try {
7629
+ return (0, import_fs10.readdirSync)(targetDir).some(
7630
+ (entry) => entry.startsWith(prefix) && entry.endsWith(".dist-info")
7631
+ );
7632
+ } catch {
7633
+ return false;
7634
+ }
7635
+ }
7561
7636
  async function installInjectedDevPackage(pkg, opts) {
7562
7637
  const targetDir = (0, import_path10.join)(opts.workPath, ".vercel", "python");
7563
- const key = `${targetDir}:${pkg.name}`;
7564
- let pending = PENDING_INSTALLS.get(key);
7565
- if (!pending) {
7566
- pending = doInstallInjectedDevPackage(pkg, { ...opts, targetDir });
7567
- PENDING_INSTALLS.set(key, pending);
7568
- pending.finally(() => PENDING_INSTALLS.delete(key));
7638
+ const source = pkg.envOverride || pkg.pinnedVersion;
7639
+ const key = `${targetDir}:${pkg.name}:${source}`;
7640
+ if (COMPLETED_INSTALLS.has(key) && hasInstalledDistribution(targetDir, pkg.name)) {
7641
+ return;
7569
7642
  }
7570
- await pending;
7643
+ COMPLETED_INSTALLS.delete(key);
7644
+ await dedupePendingOperation(PENDING_INSTALLS, key, async () => {
7645
+ await doInstallInjectedDevPackage(pkg, { ...opts, targetDir });
7646
+ COMPLETED_INSTALLS.add(key);
7647
+ });
7571
7648
  }
7572
7649
  async function doInstallInjectedDevPackage(pkg, opts) {
7573
7650
  const { targetDir, workPath, uvPath, pythonBin, env, onStdout, onStderr } = opts;
@@ -7707,18 +7784,26 @@ function createDevShim(workPath, entry, modulePath, serviceName, framework, vari
7707
7784
  }
7708
7785
  }
7709
7786
  async function getMultiServicePythonRunner(workPath, env, systemPython, uvPath) {
7787
+ const venvPath = (0, import_path10.join)(workPath, ".venv");
7788
+ const pendingCreation = PENDING_MANAGED_VENV_CREATIONS.get(venvPath);
7789
+ if (pendingCreation) {
7790
+ await pendingCreation;
7791
+ }
7710
7792
  const { pythonCmd, venvRoot } = useVirtualEnv(workPath, env, systemPython);
7711
7793
  if (venvRoot) {
7712
7794
  (0, import_build_utils13.debug)(`Using existing virtualenv at ${venvRoot} for multi-service dev`);
7713
7795
  return { command: pythonCmd, args: [] };
7714
7796
  }
7715
- const venvPath = (0, import_path10.join)(workPath, ".venv");
7716
- await ensureVenv({
7717
- pythonVersion: { pythonPath: systemPython },
7797
+ await dedupePendingOperation(
7798
+ PENDING_MANAGED_VENV_CREATIONS,
7718
7799
  venvPath,
7719
- uvPath,
7720
- quiet: true
7721
- });
7800
+ () => ensureVenv({
7801
+ pythonVersion: { pythonPath: systemPython },
7802
+ venvPath,
7803
+ uvPath,
7804
+ quiet: true
7805
+ })
7806
+ );
7722
7807
  (0, import_build_utils13.debug)(`Created virtualenv at ${venvPath} for multi-service dev`);
7723
7808
  const pythonBin = getVenvPythonBin(venvPath);
7724
7809
  const binDir = getVenvBinDir(venvPath);
@@ -7779,14 +7864,15 @@ var startDevServer = async (opts) => {
7779
7864
  // Other services use handlerFunction as the entrypoint variable name.
7780
7865
  varName: service && (0, import_build_utils13.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
7781
7866
  } : void 0,
7782
- service
7867
+ service,
7868
+ opts.repoRootPath
7783
7869
  );
7870
+ let hookResult;
7784
7871
  if (detected?.entrypoint) {
7785
7872
  resolved = detected.entrypoint;
7786
7873
  } else {
7787
- const hookResult = await runFrameworkHook(framework, {
7874
+ hookResult = await runFrameworkHook(framework, {
7788
7875
  pythonEnv: env,
7789
- projectDir: (0, import_path10.join)(workPath, detected?.baseDir ?? ""),
7790
7876
  workPath,
7791
7877
  entrypoint,
7792
7878
  detected: detected ?? void 0
@@ -7893,7 +7979,11 @@ If you are using a virtual environment, activate it before running "vercel dev",
7893
7979
  } else {
7894
7980
  console.log(syncMessage);
7895
7981
  }
7896
- await syncDependencies(devOpts);
7982
+ await dedupePendingOperation(
7983
+ PENDING_DEPENDENCY_SYNCS,
7984
+ workPath,
7985
+ () => syncDependencies(devOpts)
7986
+ );
7897
7987
  }
7898
7988
  await installInjectedDevPackage(
7899
7989
  {
@@ -7945,6 +8035,9 @@ If you are using a virtual environment, activate it before running "vercel dev",
7945
8035
  if (devShim.extraPythonPath) {
7946
8036
  pathParts.push(devShim.extraPythonPath);
7947
8037
  }
8038
+ if (hookResult?.extraPythonPath) {
8039
+ pathParts.push(hookResult.extraPythonPath);
8040
+ }
7948
8041
  const existingPythonPath = env.PYTHONPATH || "";
7949
8042
  if (existingPythonPath) {
7950
8043
  pathParts.push(existingPythonPath);
@@ -8493,7 +8586,7 @@ async function getDjangoSettings(projectDir, env) {
8493
8586
  djangoVersion: parsed.django_version ?? null
8494
8587
  };
8495
8588
  }
8496
- async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir, settingsModule, djangoSettings, djangoVersion) {
8589
+ async function runDjangoCollectStatic(venvPath, workPath, djangoPath, env, outputStaticDir, settingsModule, djangoSettings, djangoVersion) {
8497
8590
  const pythonPath = getVenvPythonBin(venvPath);
8498
8591
  const storages = djangoSettings["STORAGES"];
8499
8592
  const useLegacySetting = !djangoVersion || djangoVersion[0] < 5 || djangoVersion[0] === 5 && djangoVersion[1] < 1;
@@ -8505,7 +8598,7 @@ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir,
8505
8598
  const installedApps = djangoSettings["INSTALLED_APPS"] ?? [];
8506
8599
  const staticfilesDirs = djangoSettings["STATICFILES_DIRS"] ?? [];
8507
8600
  const staticSourceDirs = [
8508
- ...installedApps.map((app) => (0, import_path13.join)(workPath, ...app.split("."), "static")),
8601
+ ...installedApps.map((app) => (0, import_path13.join)(djangoPath, ...app.split("."), "static")),
8509
8602
  // TODO: Deal with optional prefixes in STATICFILES_DIRS.
8510
8603
  ...staticfilesDirs.map((d) => Array.isArray(d) ? d[1] : d)
8511
8604
  ].filter((d) => import_fs13.default.existsSync(d));
@@ -8515,11 +8608,11 @@ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir,
8515
8608
  );
8516
8609
  await (0, import_execa7.default)(pythonPath, ["manage.py", "collectstatic", "--noinput"], {
8517
8610
  env: { ...env, DJANGO_SETTINGS_MODULE: settingsModule },
8518
- cwd: workPath
8611
+ cwd: djangoPath
8519
8612
  });
8520
8613
  return {
8521
8614
  staticSourceDirs,
8522
- staticRoot: staticRoot ? (0, import_path13.resolve)(workPath, staticRoot) : null,
8615
+ staticRoot: staticRoot ? (0, import_path13.resolve)(djangoPath, staticRoot) : null,
8523
8616
  cdnOutputDir: null,
8524
8617
  manifestRelPath: null
8525
8618
  };
@@ -8533,7 +8626,7 @@ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir,
8533
8626
  const staticUrlPath = staticUrl.replace(/^\/|\/$/g, "") || "static";
8534
8627
  const staticOutputDir = (0, import_path13.join)(outputStaticDir, staticUrlPath);
8535
8628
  await import_fs13.default.promises.mkdir(staticOutputDir, { recursive: true });
8536
- const shimPath = (0, import_path13.join)(workPath, "_vercel_collectstatic_settings.py");
8629
+ const shimPath = (0, import_path13.join)(djangoPath, "_vercel_collectstatic_settings.py");
8537
8630
  const shimLines = [
8538
8631
  `from ${settingsModule} import *`,
8539
8632
  `STATIC_ROOT = ${JSON.stringify(staticOutputDir)}`
@@ -8549,7 +8642,7 @@ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir,
8549
8642
  ...env,
8550
8643
  DJANGO_SETTINGS_MODULE: "_vercel_collectstatic_settings"
8551
8644
  },
8552
- cwd: workPath
8645
+ cwd: djangoPath
8553
8646
  });
8554
8647
  } finally {
8555
8648
  await import_fs13.default.promises.unlink(shimPath).catch(() => {
@@ -8562,7 +8655,7 @@ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir,
8562
8655
  let manifestRelPath = null;
8563
8656
  if (MANIFEST_STORAGE_BACKENDS.includes(storageBackend) && staticRoot) {
8564
8657
  const manifestSrc = (0, import_path13.join)(staticOutputDir, "staticfiles.json");
8565
- const resolvedStaticRoot = (0, import_path13.resolve)(workPath, staticRoot);
8658
+ const resolvedStaticRoot = (0, import_path13.resolve)(djangoPath, staticRoot);
8566
8659
  const manifestDest = (0, import_path13.join)(resolvedStaticRoot, "staticfiles.json");
8567
8660
  await import_fs13.default.promises.mkdir(resolvedStaticRoot, { recursive: true });
8568
8661
  await import_fs13.default.promises.copyFile(manifestSrc, manifestDest);
@@ -8571,7 +8664,7 @@ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir,
8571
8664
  }
8572
8665
  return {
8573
8666
  staticSourceDirs,
8574
- staticRoot: staticRoot ? (0, import_path13.resolve)(workPath, staticRoot) : null,
8667
+ staticRoot: staticRoot ? (0, import_path13.resolve)(djangoPath, staticRoot) : null,
8575
8668
  cdnOutputDir: outputStaticDir,
8576
8669
  manifestRelPath
8577
8670
  };
@@ -8584,8 +8677,8 @@ var import_python_analysis9 = require("@vercel/python-analysis");
8584
8677
  var import_path14 = require("path");
8585
8678
  var import_fs14 = __toESM(require("fs"));
8586
8679
  var import_build_utils18 = require("@vercel/build-utils");
8587
- var SUBSCRIBER_NAME_RE = /^[A-Za-z]([A-Za-z0-9_-]*[A-Za-z0-9])?$/;
8588
8680
  var MODULE_ATTR_RE = /^([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*):([A-Za-z_][\w]*)$/;
8681
+ var SUBSCRIBER_OUTPUT_DIR = "_py_subscribers";
8589
8682
  var TRIGGER_NUMBER_FIELDS = [
8590
8683
  {
8591
8684
  field: "max_deliveries",
@@ -8625,6 +8718,12 @@ function safePathSegment(value) {
8625
8718
  return /[A-Za-z0-9-]/.test(char) ? char : `_${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`;
8626
8719
  }).join("");
8627
8720
  }
8721
+ function getSubscriberOutputPath(subscriberName) {
8722
+ return `${SUBSCRIBER_OUTPUT_DIR}/${safePathSegment(subscriberName)}`;
8723
+ }
8724
+ function getSubscriberConsumerName(subscriberName) {
8725
+ return (0, import_build_utils18.sanitizeConsumerName)(getSubscriberOutputPath(subscriberName));
8726
+ }
8628
8727
  async function getPyprojectSubscribers(workPath) {
8629
8728
  const pyprojectPath = (0, import_path14.join)(workPath, "pyproject.toml");
8630
8729
  if (!import_fs14.default.existsSync(pyprojectPath)) {
@@ -8635,37 +8734,38 @@ async function getPyprojectSubscribers(workPath) {
8635
8734
  if (!subscribers) {
8636
8735
  return [];
8637
8736
  }
8638
- if (typeof subscribers !== "object" || Array.isArray(subscribers)) {
8639
- throw subscriberError('"tool.vercel.subscribers" must be an object');
8737
+ if (!Array.isArray(subscribers)) {
8738
+ throw subscriberError('"tool.vercel.subscribers" must be an array');
8640
8739
  }
8641
- return Promise.all(
8642
- Object.entries(subscribers).map(
8643
- ([name, config]) => parseSubscriber(workPath, name, config)
8644
- )
8740
+ const parsedSubscribers = await Promise.all(
8741
+ subscribers.map((config, index) => parseSubscriber(workPath, index, config))
8645
8742
  );
8646
- }
8647
- async function parseSubscriber(workPath, name, config) {
8648
- if (!SUBSCRIBER_NAME_RE.test(name)) {
8649
- throw subscriberError(
8650
- `subscriber name "${name}" is invalid. Names must start with a letter, end with an alphanumeric character, and contain only alphanumeric characters, hyphens, and underscores`
8651
- );
8743
+ const seenNames = /* @__PURE__ */ new Set();
8744
+ for (const subscriber of parsedSubscribers) {
8745
+ if (seenNames.has(subscriber.name)) {
8746
+ throw subscriberError(
8747
+ `subscriber "${subscriber.name}" is declared more than once`
8748
+ );
8749
+ }
8750
+ seenNames.add(subscriber.name);
8652
8751
  }
8752
+ return parsedSubscribers;
8753
+ }
8754
+ async function parseSubscriber(workPath, index, config) {
8755
+ const label = `subscriber #${index + 1}`;
8653
8756
  if (!config || typeof config !== "object" || Array.isArray(config)) {
8654
- throw subscriberError(`subscriber "${name}" must be an object`);
8757
+ throw subscriberError(`${label} must be an object`);
8655
8758
  }
8656
8759
  for (const key of Object.keys(config)) {
8657
8760
  if (!SUBSCRIBER_FIELD_NAMES.has(key)) {
8658
- throw subscriberError(
8659
- `subscriber "${name}" has unrecognized field "${key}"`
8660
- );
8761
+ throw subscriberError(`${label} has unrecognized field "${key}"`);
8661
8762
  }
8662
8763
  }
8663
8764
  if (typeof config.entrypoint !== "string") {
8664
- throw subscriberError(
8665
- `subscriber "${name}" must define string field "entrypoint"`
8666
- );
8765
+ throw subscriberError(`${label} must define string field "entrypoint"`);
8667
8766
  }
8668
- const entrypoint = parseEntrypoint(name, config.entrypoint);
8767
+ const entrypoint = parseEntrypoint(label, config.entrypoint);
8768
+ const name = getSubscriberName(entrypoint);
8669
8769
  const existingEntrypoint = await resolveExistingEntrypoint(
8670
8770
  workPath,
8671
8771
  entrypoint.filePath
@@ -8684,6 +8784,12 @@ async function parseSubscriber(workPath, name, config) {
8684
8784
  triggerDefaults: parseTriggerDefaults(name, config)
8685
8785
  };
8686
8786
  }
8787
+ function getSubscriberName({
8788
+ moduleName,
8789
+ variableName
8790
+ }) {
8791
+ return `${moduleName.replace(/\./g, "-")}_${variableName}`;
8792
+ }
8687
8793
  function parseEntrypoint(name, value) {
8688
8794
  const match = MODULE_ATTR_RE.exec(value);
8689
8795
  if (!match) {
@@ -8752,6 +8858,40 @@ function subscriberError(message) {
8752
8858
  var writeFile = import_fs15.default.promises.writeFile;
8753
8859
  var PYTHON_ENTRYPOINT_DOCS_URL2 = "https://vercel.com/docs/functions/runtimes/python#python-entrypoints";
8754
8860
  var version = -1;
8861
+ function getDevSubscriberTopics(subscriber) {
8862
+ const { retryAfterSeconds, initialDelaySeconds } = subscriber.triggerDefaults;
8863
+ return subscriber.topics.map((topic) => ({
8864
+ topic,
8865
+ ...retryAfterSeconds === void 0 ? {} : { retryAfterSeconds },
8866
+ ...initialDelaySeconds === void 0 ? {} : { initialDelaySeconds }
8867
+ }));
8868
+ }
8869
+ async function getDevSidecars({
8870
+ workPath,
8871
+ build: build2
8872
+ }) {
8873
+ const framework = build2.config?.framework;
8874
+ if (build2.config?.middleware === true || typeof framework !== "string" || !(0, import_build_utils19.isPythonFramework)(framework)) {
8875
+ return [];
8876
+ }
8877
+ const subscribers = await getPyprojectSubscribers(workPath);
8878
+ return subscribers.map((subscriber) => ({
8879
+ type: "subscriber",
8880
+ name: subscriber.name,
8881
+ consumer: getSubscriberConsumerName(subscriber.name),
8882
+ workspace: ".",
8883
+ framework,
8884
+ runtime: "python",
8885
+ builder: {
8886
+ use: build2.use,
8887
+ src: subscriber.entrypoint,
8888
+ config: {
8889
+ handlerFunction: subscriber.variableName
8890
+ }
8891
+ },
8892
+ topics: getDevSubscriberTopics(subscriber)
8893
+ }));
8894
+ }
8755
8895
  function addFiles(target, source) {
8756
8896
  for (const [p, f] of Object.entries(source)) {
8757
8897
  target[p] = f;
@@ -8807,18 +8947,22 @@ async function runFrameworkHook(framework, ctx) {
8807
8947
  var frameworkHooks = {
8808
8948
  django: async ({
8809
8949
  pythonEnv,
8810
- projectDir,
8811
8950
  workPath,
8812
8951
  venvPath,
8813
8952
  detected
8814
8953
  }) => {
8815
- if (detected?.baseDir === void 0) {
8816
- (0, import_build_utils19.debug)("Django hook: no manage.py detected, skipping");
8817
- return;
8954
+ let baseDir = detected?.baseDir;
8955
+ if (baseDir === void 0) {
8956
+ if (!import_fs15.default.existsSync((0, import_path15.join)(workPath, "manage.py"))) {
8957
+ (0, import_build_utils19.debug)("Django hook: no manage.py detected, skipping");
8958
+ return;
8959
+ }
8960
+ baseDir = "";
8818
8961
  }
8962
+ const djangoPath = (0, import_path15.join)(workPath, baseDir);
8819
8963
  let settingsResult;
8820
8964
  try {
8821
- settingsResult = await getDjangoSettings(projectDir, pythonEnv);
8965
+ settingsResult = await getDjangoSettings(djangoPath, pythonEnv);
8822
8966
  } catch (err) {
8823
8967
  let detail;
8824
8968
  if (err?.code === "ENOENT") {
@@ -8829,7 +8973,7 @@ Hint: activate a venv or run with \`uv run vercel dev\``;
8829
8973
  }
8830
8974
  throw new import_build_utils19.NowBuildError({
8831
8975
  code: "DJANGO_SETTINGS_FAILED",
8832
- message: `Failed to read Django application settings from ${projectDir}/manage.py:
8976
+ message: `Failed to read Django application settings from ${djangoPath}/manage.py:
8833
8977
  ${detail}`
8834
8978
  });
8835
8979
  }
@@ -8839,7 +8983,6 @@ ${detail}`
8839
8983
  console.log(`Django ${djangoVersion.join(".")} detected`);
8840
8984
  }
8841
8985
  let resolvedEntrypoint;
8842
- const baseDir = detected?.baseDir ?? "";
8843
8986
  const asgiApp = djangoSettings["ASGI_APPLICATION"];
8844
8987
  if (typeof asgiApp === "string") {
8845
8988
  const parts = asgiApp.split(".");
@@ -8867,6 +9010,7 @@ ${detail}`
8867
9010
  djangoStatic = await runDjangoCollectStatic(
8868
9011
  venvPath,
8869
9012
  workPath,
9013
+ djangoPath,
8870
9014
  pythonEnv,
8871
9015
  outputStaticDir,
8872
9016
  settingsModule,
@@ -8874,7 +9018,11 @@ ${detail}`
8874
9018
  djangoVersion
8875
9019
  );
8876
9020
  }
8877
- return { entrypoint: resolvedEntrypoint, djangoStatic };
9021
+ return {
9022
+ entrypoint: resolvedEntrypoint,
9023
+ djangoStatic,
9024
+ extraPythonPath: baseDir ? (0, import_path15.join)(workPath, baseDir) : void 0
9025
+ };
8878
9026
  }
8879
9027
  };
8880
9028
  function createRuntimeTrampoline({
@@ -9041,7 +9189,6 @@ var build = async ({
9041
9189
  let spawnEnv;
9042
9190
  let projectInstallCommand;
9043
9191
  let hasCustomCommand = false;
9044
- let hasCustomBuildCommand = false;
9045
9192
  const target = getTargetPlatform(meta.isDev ?? false);
9046
9193
  (0, import_build_utils19.debug)(`workPath: ${workPath}`);
9047
9194
  workPath = await downloadFilesInWorkPath({
@@ -9074,7 +9221,8 @@ var build = async ({
9074
9221
  // For other services, handlerFunction is used as the entrypoint variable name.
9075
9222
  varName: service && (0, import_build_utils19.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
9076
9223
  } : void 0,
9077
- service
9224
+ service,
9225
+ repoRootPath
9078
9226
  ) ?? void 0;
9079
9227
  if (detected?.error && detected?.baseDir === void 0) {
9080
9228
  throw detected?.error;
@@ -9247,22 +9395,17 @@ var build = async ({
9247
9395
  env: pythonEnv,
9248
9396
  cwd: workPath
9249
9397
  });
9250
- hasCustomBuildCommand = true;
9251
9398
  } else {
9252
- const ranBuildScript = await runPyprojectScript(
9399
+ await runPyprojectScript(
9253
9400
  workPath,
9254
9401
  ["vercel-build", "now-build", "build"],
9255
9402
  pythonEnv
9256
9403
  );
9257
- if (ranBuildScript) {
9258
- hasCustomBuildCommand = true;
9259
- }
9260
9404
  }
9261
9405
  });
9262
9406
  }
9263
9407
  const hookResult = await runFrameworkHook(framework, {
9264
9408
  pythonEnv,
9265
- projectDir: (0, import_path15.join)(workPath, entryDirectory),
9266
9409
  workPath,
9267
9410
  venvPath,
9268
9411
  entrypoint,
@@ -9361,10 +9504,12 @@ var build = async ({
9361
9504
  variableName,
9362
9505
  extraEnv: extraTrampolineEnv
9363
9506
  });
9364
- const automaticCompileAllEnabled = shouldUseCompileAll({
9507
+ const compileAllEnabled = shouldCompileAll({
9365
9508
  isDev: meta.isDev,
9366
9509
  hasCustomCommand,
9367
- hasCustomBuildCommand
9510
+ // A pre-deploy command can rewrite source after the build, which would make
9511
+ // unchecked-hash precompiled bytecode stale; skip precompilation to avoid serving it.
9512
+ hasPreDeployCommand: typeof preDeployCommand === "string"
9368
9513
  });
9369
9514
  const predefinedExcludes = [
9370
9515
  ".git/**",
@@ -9433,55 +9578,63 @@ var build = async ({
9433
9578
  });
9434
9579
  }
9435
9580
  });
9436
- const runCompileAllAndFillBytecode = async () => {
9437
- await builderSpan.child("vc.builder.python.compileall").trace(async (compileSpan) => {
9438
- const sitePackageDirs = (await getVenvSitePackagesDirs(venvPath)).filter((d) => import_fs15.default.existsSync(d));
9439
- const pythonBin = getVenvPythonBin(venvPath);
9440
- console.log("Compiling Python application bytecode...");
9441
- await runCompileAll({
9442
- pythonBin,
9443
- filesOrDirectories: [workPath],
9444
- env: pythonEnv,
9445
- excludeRegex: getCompileAllAppExcludeRegex(workPath)
9446
- });
9447
- console.log("Compiling Python dependency bytecode...");
9448
- await runCompileAll({
9449
- pythonBin,
9450
- filesOrDirectories: sitePackageDirs,
9451
- env: pythonEnv
9452
- });
9453
- compileSpan.setAttributes({
9454
- "python.compileall.enabled": "true",
9455
- "python.compileall.sitePackageDirectoryCount": String(
9456
- sitePackageDirs.length
9457
- )
9581
+ const runCompileAllAndFillBytecode = async (capacityBytes) => {
9582
+ try {
9583
+ await builderSpan.child("vc.builder.python.compileall").trace(async (compileSpan) => {
9584
+ const sitePackageDirs = (await getVenvSitePackagesDirs(venvPath)).filter((d) => import_fs15.default.existsSync(d));
9585
+ const pythonBin = getVenvPythonBin(venvPath);
9586
+ console.log("Compiling Python bytecode...");
9587
+ await runCompileAll({
9588
+ pythonBin,
9589
+ filesOrDirectories: [workPath],
9590
+ env: pythonEnv,
9591
+ excludeRegex: getCompileAllAppExcludeRegex(workPath)
9592
+ });
9593
+ await runCompileAll({
9594
+ pythonBin,
9595
+ filesOrDirectories: sitePackageDirs,
9596
+ env: pythonEnv
9597
+ });
9598
+ compileSpan.setAttributes({
9599
+ "python.compileall.enabled": "true",
9600
+ "python.compileall.sitePackageDirectoryCount": String(
9601
+ sitePackageDirs.length
9602
+ )
9603
+ });
9458
9604
  });
9459
- });
9460
- const currentSize = await calculateBundleSize(files);
9461
- let remainingCapacity = MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE - currentSize;
9462
- if (pythonVersion.major != null && pythonVersion.minor != null) {
9463
- const appBytecodeInfo = await collectAppBytecodeFiles({
9464
- workPath,
9605
+ const currentSize = await calculateBundleSize(files);
9606
+ let remainingCapacity = capacityBytes - currentSize;
9607
+ if (pythonVersion.major != null && pythonVersion.minor != null) {
9608
+ const appBytecodeInfo = await collectAppBytecodeFiles({
9609
+ workPath,
9610
+ files,
9611
+ pythonMajor: pythonVersion.major,
9612
+ pythonMinor: pythonVersion.minor
9613
+ });
9614
+ remainingCapacity = addBytecodeWithinCapacity(
9615
+ files,
9616
+ appBytecodeInfo,
9617
+ remainingCapacity
9618
+ );
9619
+ }
9620
+ const vendorBytecodeInfo = await depExternalizer.collectBytecodeFiles(
9621
+ {
9622
+ vendorDirName: vendorDir
9623
+ }
9624
+ );
9625
+ await addVendorBytecodeWithinCapacity({
9465
9626
  files,
9466
- pythonMajor: pythonVersion.major,
9467
- pythonMinor: pythonVersion.minor
9627
+ depExternalizer,
9628
+ vendorDir,
9629
+ bytecodeInfo: vendorBytecodeInfo,
9630
+ capacity: remainingCapacity
9468
9631
  });
9469
- remainingCapacity = addBytecodeWithinCapacity(
9470
- files,
9471
- appBytecodeInfo,
9472
- remainingCapacity
9632
+ } catch (err) {
9633
+ console.log(
9634
+ "Bytecode precompilation failed; continuing without precompiled bytecode."
9473
9635
  );
9636
+ (0, import_build_utils19.debug)(`bytecode precompilation error details: ${err}`);
9474
9637
  }
9475
- const vendorBytecodeInfo = await depExternalizer.collectBytecodeFiles({
9476
- vendorDirName: vendorDir
9477
- });
9478
- await addVendorBytecodeWithinCapacity({
9479
- files,
9480
- depExternalizer,
9481
- vendorDir,
9482
- bytecodeInfo: vendorBytecodeInfo,
9483
- capacity: remainingCapacity
9484
- });
9485
9638
  };
9486
9639
  const announceLargeFunction = () => console.log(
9487
9640
  `Function "${entrypoint}" exceeds the standard size limit; enabling large functions (beta).`
@@ -9490,17 +9643,29 @@ var build = async ({
9490
9643
  const { fellBackToFullBundle } = await depExternalizer.generateBundle(files);
9491
9644
  if (fellBackToFullBundle) {
9492
9645
  announceLargeFunction();
9493
- if (automaticCompileAllEnabled) {
9494
- await runCompileAllAndFillBytecode();
9646
+ if (compileAllEnabled) {
9647
+ await runCompileAllAndFillBytecode(
9648
+ MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE
9649
+ );
9495
9650
  }
9496
9651
  }
9497
9652
  } else {
9498
9653
  addFiles(files, depAnalysis.allVendorFiles);
9499
- if (isLargeFunctionsEnabled() && depAnalysis.totalBundleSize > LAMBDA_SIZE_THRESHOLD_BYTES) {
9500
- announceLargeFunction();
9501
- }
9502
- if (automaticCompileAllEnabled && depAnalysis.totalBundleSize > LAMBDA_SIZE_THRESHOLD_BYTES) {
9503
- await runCompileAllAndFillBytecode();
9654
+ if (depAnalysis.totalBundleSize > LAMBDA_SIZE_THRESHOLD_BYTES) {
9655
+ if (isLargeFunctionsEnabled()) {
9656
+ announceLargeFunction();
9657
+ }
9658
+ if (compileAllEnabled) {
9659
+ await runCompileAllAndFillBytecode(
9660
+ MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE
9661
+ );
9662
+ }
9663
+ } else if (compileAllEnabled) {
9664
+ const capacity = BYTECODE_FILL_CEILING_BYTES - depAnalysis.totalBundleSize;
9665
+ const estimate = await estimateBytecodeSize(files);
9666
+ if (capacity >= BYTECODE_COVERAGE_FLOOR * estimate) {
9667
+ await runCompileAllAndFillBytecode(BYTECODE_FILL_CEILING_BYTES);
9668
+ }
9504
9669
  }
9505
9670
  }
9506
9671
  });
@@ -9523,9 +9688,8 @@ var build = async ({
9523
9688
  });
9524
9689
  const subscriberLambdas = {};
9525
9690
  for (const subscriber of subscribers) {
9526
- const safeName = safePathSegment(subscriber.name);
9527
- const outputPath = `_py_subscribers/${safeName}`;
9528
- const consumer = (0, import_build_utils19.sanitizeConsumerName)(outputPath);
9691
+ const outputPath = getSubscriberOutputPath(subscriber.name);
9692
+ const consumer = getSubscriberConsumerName(subscriber.name);
9529
9693
  const experimentalTriggers = subscriber.topics.map(
9530
9694
  (topic) => ({
9531
9695
  type: "queue/v2beta",
@@ -9552,6 +9716,7 @@ var build = async ({
9552
9716
  environment: {
9553
9717
  ...lambdaEnv,
9554
9718
  VERCEL_HAS_WORKER_SERVICES: "1",
9719
+ // Compatibility marker consumed by the current Python runtime.
9555
9720
  VERCEL_SERVICE_TYPE: "worker"
9556
9721
  },
9557
9722
  experimentalTriggers,
@@ -9654,6 +9819,7 @@ function hasProp(obj, key) {
9654
9819
  detectEntrypoint,
9655
9820
  diagnostics,
9656
9821
  downloadFilesInWorkPath,
9822
+ getDevSidecars,
9657
9823
  installRequirement,
9658
9824
  installRequirementsFile,
9659
9825
  prepareCache,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "6.47.3",
3
+ "version": "6.49.0",
4
4
  "main": "./dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -36,8 +36,8 @@
36
36
  "smol-toml": "1.5.2",
37
37
  "vitest": "2.1.4",
38
38
  "which": "3.0.0",
39
- "@vercel/build-utils": "13.32.1",
40
39
  "@vercel/error-utils": "2.2.0",
40
+ "@vercel/build-utils": "13.32.3",
41
41
  "@vercel/python-runtime": "0.16.0"
42
42
  },
43
43
  "scripts": {