@vercel/python 6.16.0 → 6.17.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 CHANGED
@@ -2871,8 +2871,8 @@ var import_fs6 = __toESM(require("fs"));
2871
2871
  var import_util2 = require("util");
2872
2872
  var import_path8 = require("path");
2873
2873
 
2874
- // src/runtime-version.ts
2875
- var VERCEL_RUNTIME_VERSION = "0.5.1";
2874
+ // src/package-versions.ts
2875
+ var VERCEL_RUNTIME_VERSION = "0.5.2";
2876
2876
 
2877
2877
  // src/index.ts
2878
2878
  var import_build_utils9 = require("@vercel/build-utils");
@@ -3880,6 +3880,19 @@ var PythonDependencyExternalizer = class {
3880
3880
  this.projectName = options.projectName;
3881
3881
  this.noBuildCheckFailed = options.noBuildCheckFailed;
3882
3882
  this.pythonPath = options.pythonPath;
3883
+ this.hasCustomCommand = options.hasCustomCommand;
3884
+ }
3885
+ shouldEnableRuntimeInstall() {
3886
+ if (this.hasCustomCommand) {
3887
+ return false;
3888
+ }
3889
+ const pythonOnHiveEnabled = process.env.VERCEL_PYTHON_ON_HIVE === "1" || process.env.VERCEL_PYTHON_ON_HIVE === "true";
3890
+ if (pythonOnHiveEnabled) {
3891
+ return false;
3892
+ } else if (this.totalBundleSize > LAMBDA_SIZE_THRESHOLD_BYTES && this.uvLockPath !== null) {
3893
+ return true;
3894
+ }
3895
+ return false;
3883
3896
  }
3884
3897
  /**
3885
3898
  * Analyze the bundle: mirror all vendor files, calculate total size,
@@ -3899,11 +3912,24 @@ var PythonDependencyExternalizer = class {
3899
3912
  this.analyzed = true;
3900
3913
  const totalBundleSizeMB = (this.totalBundleSize / (1024 * 1024)).toFixed(2);
3901
3914
  (0, import_build_utils5.debug)(`Total bundle size: ${totalBundleSizeMB} MB`);
3902
- const overLambdaLimit = shouldEnableRuntimeInstall({
3903
- totalBundleSize: this.totalBundleSize,
3904
- uvLockPath: this.uvLockPath
3905
- });
3906
- return { overLambdaLimit, allVendorFiles: this.allVendorFiles };
3915
+ const runtimeInstallEnabled = this.shouldEnableRuntimeInstall();
3916
+ const pythonOnHiveEnabled = process.env.VERCEL_PYTHON_ON_HIVE === "1" || process.env.VERCEL_PYTHON_ON_HIVE === "true";
3917
+ if (this.totalBundleSize > LAMBDA_SIZE_THRESHOLD_BYTES && this.hasCustomCommand && !pythonOnHiveEnabled) {
3918
+ const limitMB = (LAMBDA_SIZE_THRESHOLD_BYTES / (1024 * 1024)).toFixed(0);
3919
+ throw new import_build_utils5.NowBuildError({
3920
+ code: "LAMBDA_SIZE_EXCEEDED",
3921
+ message: `Total bundle size (${totalBundleSizeMB} MB) exceeds the Lambda size limit (${limitMB} MB).
3922
+
3923
+ Runtime dependency installation is not available for projects that use a custom build or install command, because custom commands may install dependencies that are not tracked in uv.lock.
3924
+
3925
+ To resolve this, either:
3926
+ 1. Remove the custom build/install command and let Vercel manage dependencies automatically
3927
+ 2. Reduce your dependency footprint to fit within the ${limitMB} MB limit`,
3928
+ link: "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled",
3929
+ action: "Learn More"
3930
+ });
3931
+ }
3932
+ return { runtimeInstallEnabled, allVendorFiles: this.allVendorFiles };
3907
3933
  }
3908
3934
  /**
3909
3935
  * Generate the optimally-packed Lambda bundle.
@@ -3931,7 +3957,9 @@ var PythonDependencyExternalizer = class {
3931
3957
  const ephemeralLimitMB = (LAMBDA_EPHEMERAL_STORAGE_BYTES / (1024 * 1024)).toFixed(0);
3932
3958
  throw new import_build_utils5.NowBuildError({
3933
3959
  code: "LAMBDA_SIZE_EXCEEDED",
3934
- message: `Total dependency 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.`
3960
+ message: `Total dependency 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.`,
3961
+ link: "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled",
3962
+ action: "Learn More"
3935
3963
  });
3936
3964
  }
3937
3965
  if (this.noBuildCheckFailed) {
@@ -4097,23 +4125,13 @@ ${error.fileContent}`
4097
4125
  const limitMB = (LAMBDA_SIZE_THRESHOLD_BYTES / (1024 * 1024)).toFixed(0);
4098
4126
  throw new import_build_utils5.NowBuildError({
4099
4127
  code: "LAMBDA_SIZE_EXCEEDED",
4100
- message: `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.`
4128
+ message: `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.`,
4129
+ link: "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled",
4130
+ action: "Learn More"
4101
4131
  });
4102
4132
  }
4103
4133
  }
4104
4134
  };
4105
- function shouldEnableRuntimeInstall({
4106
- totalBundleSize,
4107
- uvLockPath
4108
- }) {
4109
- const pythonOnHiveEnabled = process.env.VERCEL_PYTHON_ON_HIVE === "1" || process.env.VERCEL_PYTHON_ON_HIVE === "true";
4110
- if (pythonOnHiveEnabled) {
4111
- return false;
4112
- } else if (totalBundleSize > LAMBDA_SIZE_THRESHOLD_BYTES && uvLockPath !== null) {
4113
- return true;
4114
- }
4115
- return false;
4116
- }
4117
4135
  async function mirrorPackagesIntoVendor({
4118
4136
  venvPath,
4119
4137
  vendorDirName,
@@ -4246,11 +4264,16 @@ var PYTHON_ENTRYPOINT_FILENAMES = [
4246
4264
  "asgi"
4247
4265
  ];
4248
4266
  var PYTHON_ENTRYPOINT_DIRS = ["", "src", "app", "api"];
4249
- var PYTHON_CANDIDATE_ENTRYPOINTS = PYTHON_ENTRYPOINT_FILENAMES.flatMap(
4250
- (filename) => PYTHON_ENTRYPOINT_DIRS.map(
4251
- (dir) => import_path6.posix.join(dir, `${filename}.py`)
4252
- )
4267
+ var PYTHON_CANDIDATE_ENTRYPOINTS = getCandidateEntrypointsInDirs(
4268
+ PYTHON_ENTRYPOINT_DIRS
4253
4269
  );
4270
+ function getCandidateEntrypointsInDirs(dirs) {
4271
+ return dirs.flatMap(
4272
+ (dir) => PYTHON_ENTRYPOINT_FILENAMES.map(
4273
+ (filename) => import_path6.posix.join(dir, `${filename}.py`)
4274
+ )
4275
+ );
4276
+ }
4254
4277
  async function getPyprojectEntrypoint(workPath) {
4255
4278
  const pyprojectData = await (0, import_build_utils7.readConfigFile)((0, import_path6.join)(workPath, "pyproject.toml"));
4256
4279
  if (!pyprojectData)
@@ -4277,6 +4300,18 @@ async function getPyprojectEntrypoint(workPath) {
4277
4300
  return null;
4278
4301
  }
4279
4302
  }
4303
+ async function findValidEntrypoint(fsFiles, candidates) {
4304
+ for (const candidate of candidates) {
4305
+ if (fsFiles[candidate]) {
4306
+ const isValid = await (0, import_build_utils6.isPythonEntrypoint)(fsFiles[candidate]);
4307
+ if (isValid) {
4308
+ (0, import_build_utils6.debug)(`Detected Python entrypoint: ${candidate}`);
4309
+ return candidate;
4310
+ }
4311
+ }
4312
+ }
4313
+ return null;
4314
+ }
4280
4315
  async function detectGenericPythonEntrypoint(workPath, configuredEntrypoint) {
4281
4316
  const entry = configuredEntrypoint.endsWith(".py") ? configuredEntrypoint : `${configuredEntrypoint}.py`;
4282
4317
  try {
@@ -4291,24 +4326,54 @@ async function detectGenericPythonEntrypoint(workPath, configuredEntrypoint) {
4291
4326
  const candidates = PYTHON_CANDIDATE_ENTRYPOINTS.filter(
4292
4327
  (c) => !!fsFiles[c]
4293
4328
  );
4294
- for (const candidate of candidates) {
4295
- const isValid = await (0, import_build_utils6.isPythonEntrypoint)(fsFiles[candidate]);
4329
+ return findValidEntrypoint(fsFiles, candidates);
4330
+ } catch {
4331
+ (0, import_build_utils6.debug)("Failed to discover Python entrypoint");
4332
+ return null;
4333
+ }
4334
+ }
4335
+ async function detectDjangoPythonEntrypoint(workPath, configuredEntrypoint) {
4336
+ const entry = configuredEntrypoint.endsWith(".py") ? configuredEntrypoint : `${configuredEntrypoint}.py`;
4337
+ try {
4338
+ const fsFiles = await (0, import_build_utils6.glob)("**", workPath);
4339
+ if (fsFiles[entry]) {
4340
+ const isValid = await (0, import_build_utils6.isPythonEntrypoint)(fsFiles[entry]);
4296
4341
  if (isValid) {
4297
- (0, import_build_utils6.debug)(`Detected Python entrypoint: ${candidate}`);
4298
- return candidate;
4342
+ (0, import_build_utils6.debug)(`Using configured Python entrypoint: ${entry}`);
4343
+ return entry;
4299
4344
  }
4300
4345
  }
4301
- return null;
4346
+ const rootGlobs = await (0, import_build_utils6.glob)("*", {
4347
+ cwd: workPath,
4348
+ includeDirectories: true
4349
+ });
4350
+ const rootDirs = [
4351
+ "",
4352
+ ...Object.keys(rootGlobs).filter(
4353
+ (name) => !name.startsWith(".") && rootGlobs[name].mode != null && (0, import_build_utils6.isDirectory)(rootGlobs[name].mode)
4354
+ )
4355
+ ];
4356
+ for (const rootDir of rootDirs) {
4357
+ const currPath = (0, import_path6.join)(workPath, rootDir);
4358
+ const wsgiEntry = await (0, import_build_utils6.getDjangoEntrypoint)(currPath);
4359
+ if (wsgiEntry) {
4360
+ const fullWsgiEntry = import_path6.posix.join(rootDir, wsgiEntry);
4361
+ if (fsFiles[fullWsgiEntry]) {
4362
+ (0, import_build_utils6.debug)(`Using Django WSGI entrypoint: ${fullWsgiEntry}`);
4363
+ return fullWsgiEntry;
4364
+ }
4365
+ }
4366
+ }
4367
+ const baseCandidates = getCandidateEntrypointsInDirs(rootDirs);
4368
+ const candidates = baseCandidates.filter((c) => !!fsFiles[c]);
4369
+ return findValidEntrypoint(fsFiles, candidates);
4302
4370
  } catch {
4303
- (0, import_build_utils6.debug)("Failed to discover Python entrypoint");
4371
+ (0, import_build_utils6.debug)("Failed to discover Django Python entrypoint");
4304
4372
  return null;
4305
4373
  }
4306
4374
  }
4307
- async function detectPythonEntrypoint(_framework, workPath, configuredEntrypoint) {
4308
- const entrypoint = await detectGenericPythonEntrypoint(
4309
- workPath,
4310
- configuredEntrypoint
4311
- );
4375
+ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint) {
4376
+ const entrypoint = framework === "django" ? await detectDjangoPythonEntrypoint(workPath, configuredEntrypoint) : await detectGenericPythonEntrypoint(workPath, configuredEntrypoint);
4312
4377
  if (entrypoint)
4313
4378
  return entrypoint;
4314
4379
  return await getPyprojectEntrypoint(workPath);
@@ -4561,17 +4626,25 @@ function installGlobalCleanupHandlers() {
4561
4626
  killAll();
4562
4627
  });
4563
4628
  }
4564
- function createDevShim(workPath, modulePath) {
4629
+ function createDevShim(workPath, entry, modulePath) {
4565
4630
  try {
4566
4631
  const vercelPythonDir = (0, import_path7.join)(workPath, ".vercel", "python");
4567
4632
  (0, import_fs5.mkdirSync)(vercelPythonDir, { recursive: true });
4633
+ let qualifiedModule = modulePath;
4634
+ let extraPythonPath;
4635
+ if ((0, import_fs5.existsSync)((0, import_path7.join)(workPath, "__init__.py"))) {
4636
+ const pkgName = (0, import_path7.basename)(workPath);
4637
+ qualifiedModule = `${pkgName}.${modulePath}`;
4638
+ extraPythonPath = (0, import_path7.dirname)(workPath);
4639
+ }
4640
+ const entryAbs = (0, import_path7.join)(workPath, entry);
4568
4641
  const shimPath = (0, import_path7.join)(vercelPythonDir, `${DEV_SHIM_MODULE}.py`);
4569
4642
  const templatePath = (0, import_path7.join)(__dirname, "..", `${DEV_SHIM_MODULE}.py`);
4570
4643
  const template = (0, import_fs5.readFileSync)(templatePath, "utf8");
4571
- const shimSource = template.replace(/__VC_DEV_MODULE_PATH__/g, modulePath);
4644
+ const shimSource = template.replace(/__VC_DEV_MODULE_NAME__/g, qualifiedModule).replace(/__VC_DEV_ENTRY_ABS__/g, entryAbs);
4572
4645
  (0, import_fs5.writeFileSync)(shimPath, shimSource, "utf8");
4573
4646
  (0, import_build_utils8.debug)(`Prepared Python dev shim at ${shimPath}`);
4574
- return DEV_SHIM_MODULE;
4647
+ return { module: DEV_SHIM_MODULE, extraPythonPath };
4575
4648
  } catch (err) {
4576
4649
  (0, import_build_utils8.debug)(`Failed to prepare dev shim: ${err?.message || err}`);
4577
4650
  return null;
@@ -4735,22 +4808,33 @@ If you are using a virtual environment, activate it before running "vercel dev",
4735
4808
  }
4736
4809
  const port = typeof meta.port === "number" ? meta.port : await (0, import_get_port.default)();
4737
4810
  env.PORT = `${port}`;
4738
- const devShimModule = createDevShim(workPath, modulePath);
4739
- if (devShimModule) {
4811
+ const devShim = createDevShim(workPath, entry, modulePath);
4812
+ if (devShim) {
4740
4813
  const vercelPythonDir = (0, import_path7.join)(workPath, ".vercel", "python");
4814
+ const pathParts = [vercelPythonDir];
4815
+ if (devShim.extraPythonPath) {
4816
+ pathParts.push(devShim.extraPythonPath);
4817
+ }
4741
4818
  const existingPythonPath = env.PYTHONPATH || "";
4742
- env.PYTHONPATH = existingPythonPath ? `${vercelPythonDir}:${existingPythonPath}` : vercelPythonDir;
4819
+ if (existingPythonPath) {
4820
+ pathParts.push(existingPythonPath);
4821
+ }
4822
+ env.PYTHONPATH = pathParts.join(import_path7.delimiter);
4743
4823
  }
4744
- const moduleToRun = devShimModule || modulePath;
4824
+ const moduleToRun = devShim?.module || modulePath;
4745
4825
  const pythonArgs = ["-u", "-m", moduleToRun];
4746
4826
  const argv = [...spawnArgsPrefix, ...pythonArgs];
4747
4827
  (0, import_build_utils8.debug)(
4748
4828
  `Starting Python dev server (${framework}): ${spawnCommand} ${argv.join(" ")} [PORT=${port}]`
4749
4829
  );
4830
+ if (process.stdout.columns) {
4831
+ env.COLUMNS = `${process.stdout.columns}`;
4832
+ }
4750
4833
  const child = (0, import_child_process2.spawn)(spawnCommand, argv, {
4751
4834
  cwd: workPath,
4752
4835
  env,
4753
- stdio: ["inherit", "pipe", "pipe"]
4836
+ stdio: ["ignore", "pipe", "pipe"],
4837
+ detached: true
4754
4838
  });
4755
4839
  childProcess = child;
4756
4840
  stdoutLogListener = createLogListener(onStdout, process.stdout);
@@ -4825,6 +4909,7 @@ var build = async ({
4825
4909
  const framework = config?.framework;
4826
4910
  let spawnEnv;
4827
4911
  let projectInstallCommand;
4912
+ let hasCustomCommand = false;
4828
4913
  (0, import_build_utils9.debug)(`workPath: ${workPath}`);
4829
4914
  workPath = await downloadFilesInWorkPath({
4830
4915
  workPath,
@@ -4873,12 +4958,16 @@ var build = async ({
4873
4958
  env: spawnEnv,
4874
4959
  cwd: workPath
4875
4960
  });
4961
+ hasCustomCommand = true;
4876
4962
  } else {
4877
- await runPyprojectScript(
4963
+ const ranBuildScript = await runPyprojectScript(
4878
4964
  workPath,
4879
4965
  ["vercel-build", "now-build", "build"],
4880
4966
  spawnEnv
4881
4967
  );
4968
+ if (ranBuildScript) {
4969
+ hasCustomCommand = true;
4970
+ }
4882
4971
  }
4883
4972
  }
4884
4973
  let fsFiles = await (0, import_build_utils9.glob)("**", workPath);
@@ -5011,6 +5100,7 @@ ${pipfileLockContent}`
5011
5100
  cwd: workPath
5012
5101
  });
5013
5102
  assumeDepsInstalled = true;
5103
+ hasCustomCommand = true;
5014
5104
  } else {
5015
5105
  assumeDepsInstalled = await runPyprojectScript(
5016
5106
  workPath,
@@ -5019,6 +5109,9 @@ ${pipfileLockContent}`
5019
5109
  /* useUserVirtualEnv */
5020
5110
  false
5021
5111
  );
5112
+ if (assumeDepsInstalled) {
5113
+ hasCustomCommand = true;
5114
+ }
5022
5115
  }
5023
5116
  let uv;
5024
5117
  try {
@@ -5159,10 +5252,11 @@ from vercel_runtime.vc_init import vc_handler
5159
5252
  uvProjectDir,
5160
5253
  projectName,
5161
5254
  noBuildCheckFailed,
5162
- pythonPath: pythonVersion.pythonPath
5255
+ pythonPath: pythonVersion.pythonPath,
5256
+ hasCustomCommand
5163
5257
  });
5164
- const { overLambdaLimit, allVendorFiles } = await depExternalizer.analyze(files);
5165
- if (overLambdaLimit) {
5258
+ const { runtimeInstallEnabled, allVendorFiles } = await depExternalizer.analyze(files);
5259
+ if (runtimeInstallEnabled) {
5166
5260
  await depExternalizer.generateBundle(files);
5167
5261
  } else {
5168
5262
  for (const [p, f] of Object.entries(allVendorFiles)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "6.16.0",
3
+ "version": "6.17.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.7.0"
18
+ "@vercel/python-analysis": "0.8.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@renovatebot/pep440": "4.2.1",
@@ -35,9 +35,9 @@
35
35
  "which": "3.0.0",
36
36
  "get-port": "5.1.1",
37
37
  "is-port-reachable": "3.1.0",
38
- "@vercel/build-utils": "13.5.0",
39
- "@vercel/python-runtime": "0.5.1",
40
- "@vercel/error-utils": "2.0.3"
38
+ "@vercel/build-utils": "13.6.0",
39
+ "@vercel/error-utils": "2.0.3",
40
+ "@vercel/python-runtime": "0.5.2"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "node ../../utils/build-builder.mjs",
package/vc_init_dev.py CHANGED
@@ -5,7 +5,7 @@ import sys
5
5
  import os
6
6
  import inspect
7
7
  from os import path as _p
8
- from importlib import import_module
8
+ from importlib import util as _importlib_util
9
9
  import mimetypes
10
10
 
11
11
 
@@ -72,8 +72,20 @@ def _strip_service_route_prefix(path_value):
72
72
 
73
73
 
74
74
  # ASGI/WSGI app detection
75
- USER_MODULE = "__VC_DEV_MODULE_PATH__"
76
- _mod = import_module(USER_MODULE)
75
+ _MODULE_NAME = "__VC_DEV_MODULE_NAME__"
76
+ _ENTRY_ABS = "__VC_DEV_ENTRY_ABS__"
77
+
78
+ # Import user module by file path, matching vc_init.py's approach.
79
+ # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
80
+ _spec = _importlib_util.spec_from_file_location(_MODULE_NAME, _ENTRY_ABS)
81
+ if _spec is None or _spec.loader is None:
82
+ raise RuntimeError(
83
+ f"Could not load module spec for '{_MODULE_NAME}' at {_ENTRY_ABS}"
84
+ )
85
+ _mod = _importlib_util.module_from_spec(_spec)
86
+ sys.modules[_MODULE_NAME] = _mod
87
+ _spec.loader.exec_module(_mod)
88
+
77
89
  _user_app_name = (
78
90
  "app"
79
91
  if hasattr(_mod, "app")
@@ -83,7 +95,7 @@ _user_app_name = (
83
95
  )
84
96
  if _user_app_name is None:
85
97
  raise RuntimeError(
86
- f"Missing 'app' or 'application' in module '{USER_MODULE}'. "
98
+ f"Missing 'app' or 'application' in module '{_MODULE_NAME}'. "
87
99
  f"Define `app = ...` or `application = ...` in your entrypoint."
88
100
  )
89
101
 
@@ -137,7 +149,7 @@ def _detect_app_type(app_obj):
137
149
 
138
150
  print(
139
151
  _color(
140
- f"Could not determine the application interface for '{USER_MODULE}:{_user_app_name}'\n"
152
+ f"Could not determine the application interface for '{_MODULE_NAME}:{_user_app_name}'\n"
141
153
  f"Expected either:\n"
142
154
  f" - An ASGI app: async callable(scope, receive, send)\n"
143
155
  f" - A WSGI app: callable(environ, start_response)",