@vercel/python 6.55.3 → 6.56.1

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 +363 -70
  2. package/package.json +8 -11
package/dist/index.js CHANGED
@@ -5032,12 +5032,29 @@ var import_fs20 = __toESM(require("fs"));
5032
5032
  var import_path21 = require("path");
5033
5033
 
5034
5034
  // src/package-versions.ts
5035
- var VERCEL_RUNTIME_VERSION = "0.18.0";
5035
+ var VERCEL_RUNTIME_VERSION = "0.19.0";
5036
5036
  var VERCEL_WORKERS_VERSION = "0.0.25";
5037
5037
 
5038
5038
  // src/conditional-vendoring.ts
5039
5039
  var import_python_analysis = require("@vercel/python-analysis");
5040
5040
  var UPSTREAM_DEPENDENCY_ADAPTERS = /* @__PURE__ */ new Map([
5041
+ [
5042
+ "apscheduler",
5043
+ {
5044
+ bundled: "vercel-apscheduler-bundle",
5045
+ unbundled: "vercel-apscheduler",
5046
+ envOverride: "VERCEL_PYTHON_APSCHEDULER_DEPENDENCY",
5047
+ preferUnbundledWhenPresent: ["vercel-queue"],
5048
+ integration: {
5049
+ module: "vercel.integrations.apscheduler",
5050
+ installer: "install_vercel_apscheduler_integration",
5051
+ // APScheduler jobs are declared while the subscriber module imports.
5052
+ // Install first so the adapter can capture those definitions.
5053
+ installBeforeImport: true,
5054
+ subscriberProbe: "is_scheduler_subscriber"
5055
+ }
5056
+ }
5057
+ ],
5041
5058
  [
5042
5059
  "celery",
5043
5060
  {
@@ -5069,31 +5086,26 @@ var UPSTREAM_DEPENDENCY_ADAPTERS = /* @__PURE__ */ new Map([
5069
5086
  }
5070
5087
  ]
5071
5088
  ]);
5072
- async function getQueueIntegrations({
5073
- pythonPackage
5089
+ async function getQueueAdapterBootstrap({
5090
+ pythonPackage,
5091
+ env,
5092
+ legacyWorkersProject,
5093
+ hasDeclaredSubscribers
5074
5094
  }) {
5075
- const dependencies = await getDirectDependencyNames(pythonPackage);
5076
- if (!dependencies)
5077
- return [];
5078
5095
  const integrations = [];
5079
- for (const [upstream, adapter] of UPSTREAM_DEPENDENCY_ADAPTERS) {
5080
- if (dependencies.has(upstream)) {
5081
- integrations.push(adapter.integration);
5082
- }
5096
+ const injectedPackages = [];
5097
+ const bootstrap = { integrations, injectedPackages };
5098
+ if (legacyWorkersProject || !hasDeclaredSubscribers) {
5099
+ return bootstrap;
5083
5100
  }
5084
- return integrations;
5085
- }
5086
- async function getConditionalInjectedPackages({
5087
- pythonPackage,
5088
- env
5089
- }) {
5090
5101
  const dependencies = await getDirectDependencyNames(pythonPackage);
5091
- if (!dependencies)
5092
- return [];
5093
- const injectedPackages = [];
5102
+ if (!dependencies) {
5103
+ return bootstrap;
5104
+ }
5094
5105
  for (const [upstream, adapter] of UPSTREAM_DEPENDENCY_ADAPTERS) {
5095
5106
  if (!dependencies.has(upstream))
5096
5107
  continue;
5108
+ integrations.push(adapter.integration);
5097
5109
  if (dependencies.has(adapter.bundled) || dependencies.has(adapter.unbundled)) {
5098
5110
  continue;
5099
5111
  }
@@ -5107,7 +5119,7 @@ async function getConditionalInjectedPackages({
5107
5119
  allowLocalSource: false
5108
5120
  });
5109
5121
  }
5110
- return injectedPackages;
5122
+ return bootstrap;
5111
5123
  }
5112
5124
  async function getDirectDependencyNames(pythonPackage) {
5113
5125
  const dependencies = pythonPackage?.manifest?.data?.project?.dependencies;
@@ -5158,6 +5170,30 @@ async function hasExplicitVercelSdkDependency(pythonPackage) {
5158
5170
  );
5159
5171
  return names?.has("vercel") ?? false;
5160
5172
  }
5173
+ async function getLocalSdkSourcePaths({
5174
+ pythonPackage,
5175
+ env
5176
+ }) {
5177
+ const sdkRoot = env.VERCEL_SDK_PYTHON;
5178
+ if (!sdkRoot) {
5179
+ return [];
5180
+ }
5181
+ const names = await getDeclaredDependencyNames(
5182
+ pythonPackage?.manifest?.data?.project?.dependencies
5183
+ );
5184
+ const hasVercelPackage = [...names ?? []].some(
5185
+ (name) => name === "vercel" || name.startsWith("vercel-")
5186
+ );
5187
+ if (!hasVercelPackage) {
5188
+ return [];
5189
+ }
5190
+ const entries = await import_fs.default.promises.readdir((0, import_path.join)(sdkRoot, "src"), {
5191
+ withFileTypes: true
5192
+ });
5193
+ return entries.filter(
5194
+ (entry) => entry.isDirectory() && (entry.name === "vercel" || entry.name.startsWith("vercel-")) && (names?.has("vercel") || names?.has(entry.name))
5195
+ ).map((entry) => entry.name).sort().map((name) => (0, import_path.join)(sdkRoot, "src", name));
5196
+ }
5161
5197
  async function getInstalledVercelSdkVersion({
5162
5198
  uv,
5163
5199
  venvPath,
@@ -7979,6 +8015,7 @@ var import_build_utils14 = require("@vercel/build-utils");
7979
8015
  var import_path10 = require("path");
7980
8016
  var import_fs10 = __toESM(require("fs"));
7981
8017
  var MODULE_ATTR_RE = /^([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*):([A-Za-z_][\w]*)$/;
8018
+ var MODULE_RE = /^[A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*$/;
7982
8019
  function parseModuleEntrypoint(value) {
7983
8020
  const match = MODULE_ATTR_RE.exec(value);
7984
8021
  if (!match) {
@@ -7990,11 +8027,21 @@ function parseModuleEntrypoint(value) {
7990
8027
  filePath: `${match[1].replace(/\./g, "/")}.py`
7991
8028
  };
7992
8029
  }
8030
+ function parseBareModuleEntrypoint(value) {
8031
+ if (!MODULE_RE.test(value)) {
8032
+ return null;
8033
+ }
8034
+ return {
8035
+ moduleName: value,
8036
+ filePath: `${value.replace(/\./g, "/")}.py`
8037
+ };
8038
+ }
7993
8039
  function getModuleEntrypointName({
7994
8040
  moduleName,
7995
8041
  variableName
7996
8042
  }) {
7997
- return `${moduleName.replace(/\./g, "-")}_${variableName}`;
8043
+ const base = moduleName.replace(/\./g, "-");
8044
+ return variableName === void 0 ? base : `${base}_${variableName}`;
7998
8045
  }
7999
8046
  function safePathSegment(value) {
8000
8047
  return [...value].map((char) => {
@@ -8108,6 +8155,7 @@ function workflowError(message) {
8108
8155
 
8109
8156
  // src/subscribers.ts
8110
8157
  var SUBSCRIBER_OUTPUT_DIR = "_py_subscribers";
8158
+ var SUBSCRIBER_ID_ENV = "VERCEL_PYTHON_SUBSCRIBER_ID";
8111
8159
  var TRIGGER_NUMBER_FIELDS = [
8112
8160
  {
8113
8161
  field: "max_attempts",
@@ -8166,6 +8214,13 @@ var LEGACY_SUBSCRIBER_FIELD_NAMES = /* @__PURE__ */ new Set([
8166
8214
  "topics",
8167
8215
  ...LEGACY_TRIGGER_NUMBER_FIELDS.map(({ field }) => field)
8168
8216
  ]);
8217
+ var APSCHEDULER_PREVIEW_FIELD_NAMES = /* @__PURE__ */ new Set(["enabled", "idle_timeout"]);
8218
+ var APSCHEDULER_DURATION_UNITS = {
8219
+ s: 1,
8220
+ m: 60,
8221
+ h: 60 * 60,
8222
+ d: 24 * 60 * 60
8223
+ };
8169
8224
  function getSubscriberOutputPath(subscriberName) {
8170
8225
  return `${SUBSCRIBER_OUTPUT_DIR}/${safePathSegment(subscriberName)}`;
8171
8226
  }
@@ -8178,6 +8233,15 @@ function getGeneratedQueueHandlerPath(outputPath) {
8178
8233
  function generatedPythonPathToModule(filePath) {
8179
8234
  return filePath.replace(/\.py$/, "").split(/[\\/]+/).join(".");
8180
8235
  }
8236
+ async function hasPyprojectSubscribers(workPath) {
8237
+ const pyprojectPath = (0, import_path12.join)(workPath, "pyproject.toml");
8238
+ if (!import_fs12.default.existsSync(pyprojectPath)) {
8239
+ return false;
8240
+ }
8241
+ const pyproject = await (0, import_build_utils14.readConfigFile)(pyprojectPath);
8242
+ const subscribers = pyproject?.tool?.vercel?.subscribers;
8243
+ return Array.isArray(subscribers) && subscribers.length > 0;
8244
+ }
8181
8245
  async function getPyprojectSubscribers(workPath, { legacySchema = false } = {}) {
8182
8246
  const pyprojectPath = (0, import_path12.join)(workPath, "pyproject.toml");
8183
8247
  if (!import_fs12.default.existsSync(pyprojectPath)) {
@@ -8207,6 +8271,63 @@ async function getPyprojectSubscribers(workPath, { legacySchema = false } = {})
8207
8271
  }
8208
8272
  return parsedSubscribers;
8209
8273
  }
8274
+ async function getApschedulerPreviewConfig(workPath) {
8275
+ const pyprojectPath = (0, import_path12.join)(workPath, "pyproject.toml");
8276
+ if (!import_fs12.default.existsSync(pyprojectPath)) {
8277
+ return void 0;
8278
+ }
8279
+ const pyproject = await (0, import_build_utils14.readConfigFile)(pyprojectPath);
8280
+ const apscheduler = pyproject?.tool?.vercel?.apscheduler;
8281
+ if (apscheduler === void 0) {
8282
+ return void 0;
8283
+ }
8284
+ if (!apscheduler || typeof apscheduler !== "object" || Array.isArray(apscheduler)) {
8285
+ throw apschedulerError('"tool.vercel.apscheduler" must be a table');
8286
+ }
8287
+ const previews = apscheduler.previews;
8288
+ if (previews === void 0) {
8289
+ return void 0;
8290
+ }
8291
+ if (!previews || typeof previews !== "object" || Array.isArray(previews)) {
8292
+ throw apschedulerError(
8293
+ '"tool.vercel.apscheduler.previews" must be a table'
8294
+ );
8295
+ }
8296
+ const rawPreviews = previews;
8297
+ for (const key of Object.keys(rawPreviews)) {
8298
+ if (!APSCHEDULER_PREVIEW_FIELD_NAMES.has(key)) {
8299
+ throw apschedulerError(
8300
+ `"tool.vercel.apscheduler.previews" has unrecognized field "${key}"`
8301
+ );
8302
+ }
8303
+ }
8304
+ if (rawPreviews.enabled !== void 0 && typeof rawPreviews.enabled !== "boolean") {
8305
+ throw apschedulerError(
8306
+ '"tool.vercel.apscheduler.previews.enabled" must be a boolean'
8307
+ );
8308
+ }
8309
+ if (rawPreviews.enabled !== true) {
8310
+ return void 0;
8311
+ }
8312
+ if (typeof rawPreviews.idle_timeout !== "string") {
8313
+ throw apschedulerError(
8314
+ '"tool.vercel.apscheduler.previews.idle_timeout" must be a duration such as "30m"'
8315
+ );
8316
+ }
8317
+ const match = /^([1-9]\d*)([smhd])$/.exec(rawPreviews.idle_timeout);
8318
+ if (!match) {
8319
+ throw apschedulerError(
8320
+ '"tool.vercel.apscheduler.previews.idle_timeout" must be a positive duration using s, m, h, or d (for example "30m")'
8321
+ );
8322
+ }
8323
+ const idleTimeoutSeconds = Number(match[1]) * APSCHEDULER_DURATION_UNITS[match[2]];
8324
+ if (!Number.isSafeInteger(idleTimeoutSeconds)) {
8325
+ throw apschedulerError(
8326
+ '"tool.vercel.apscheduler.previews.idle_timeout" is too large'
8327
+ );
8328
+ }
8329
+ return { idleTimeoutSeconds };
8330
+ }
8210
8331
  async function resolveQueueSubscribers({
8211
8332
  declarations,
8212
8333
  uv,
@@ -8228,10 +8349,12 @@ async function resolveQueueSubscribers({
8228
8349
  const hint = kind === "workflow" ? "; ensure the entrypoint instantiates vercel.workflow.Workflows" : "";
8229
8350
  const unmatchedTopicPatterns = getUnmatchedQueueTopicPatterns(
8230
8351
  declaration,
8231
- introspected
8352
+ introspected.subscriptions
8232
8353
  );
8233
8354
  if (unmatchedTopicPatterns.length > 0) {
8234
- const introspectedTopics = introspected.map(({ topic }) => topic);
8355
+ const introspectedTopics = introspected.subscriptions.map(
8356
+ ({ topic }) => topic
8357
+ );
8235
8358
  throw subscriberError(
8236
8359
  `${kind} "${declaration.name}" declared topics [${unmatchedTopicPatterns.join(
8237
8360
  ", "
@@ -8240,9 +8363,9 @@ async function resolveQueueSubscribers({
8240
8363
  )}]${hint}`
8241
8364
  );
8242
8365
  }
8243
- const subscriptions = kind === "workflow" ? introspected.filter(
8366
+ const subscriptions = kind === "workflow" ? introspected.subscriptions.filter(
8244
8367
  (subscription) => isWorkflowQueueTopic(subscription.topic)
8245
- ) : filterQueueSubscriptions(declaration, introspected);
8368
+ ) : filterQueueSubscriptions(declaration, introspected.subscriptions);
8246
8369
  if (subscriptions.length === 0) {
8247
8370
  if (kind === "workflow") {
8248
8371
  throw subscriberError(
@@ -8254,7 +8377,7 @@ async function resolveQueueSubscribers({
8254
8377
  `${kind} "${declaration.name}" declared topics [${declared}] but no introspected queue subscriptions matched${hint}`
8255
8378
  );
8256
8379
  }
8257
- result.push({ ...declaration, subscriptions });
8380
+ result.push({ ...declaration, subscriptions, owners: introspected.owners });
8258
8381
  }
8259
8382
  return result;
8260
8383
  }
@@ -8307,8 +8430,10 @@ function createIntegrationInstallLines(integrations, { serving, beforeImport })
8307
8430
  function createQueueHandlerModule(declaration, integrations) {
8308
8431
  return [
8309
8432
  "import importlib",
8433
+ "import os",
8310
8434
  "import vercel.queue",
8311
8435
  "",
8436
+ `os.environ[${JSON.stringify(SUBSCRIBER_ID_ENV)}] = ${JSON.stringify(declaration.name)}`,
8312
8437
  ...createIntegrationInstallLines(integrations, {
8313
8438
  serving: true,
8314
8439
  beforeImport: true
@@ -8322,14 +8447,22 @@ function createQueueHandlerModule(declaration, integrations) {
8322
8447
  ""
8323
8448
  ].join("\n");
8324
8449
  }
8325
- async function parseSubscriberEntrypoint(workPath, label, entrypointValue) {
8450
+ async function parseSubscriberEntrypoint(workPath, label, entrypointValue, { requireVariable }) {
8326
8451
  if (typeof entrypointValue !== "string") {
8327
8452
  throw subscriberError(`${label} must define string field "entrypoint"`);
8328
8453
  }
8329
- const entrypoint = parseModuleEntrypoint(entrypointValue);
8454
+ if (!requireVariable && /\.py$/i.test(entrypointValue)) {
8455
+ const suggestion = parseBareModuleEntrypoint(
8456
+ entrypointValue.slice(0, -3).replace(/[\\/]+/g, ".").replace(/(^|\.)__init__$/, "")
8457
+ );
8458
+ throw subscriberError(
8459
+ `${label} has invalid entrypoint "${entrypointValue}". Use an import path, not a file path${suggestion ? `: "${suggestion.moduleName}"` : ""}`
8460
+ );
8461
+ }
8462
+ const entrypoint = parseModuleEntrypoint(entrypointValue) ?? (requireVariable ? null : parseBareModuleEntrypoint(entrypointValue));
8330
8463
  if (!entrypoint) {
8331
8464
  throw subscriberError(
8332
- `${label} has invalid entrypoint "${entrypointValue}". Use "module:object"`
8465
+ `${label} has invalid entrypoint "${entrypointValue}". Use ${requireVariable ? '"module:object"' : '"module:object" or a module path like "pkg.module"'}`
8333
8466
  );
8334
8467
  }
8335
8468
  const name = getModuleEntrypointName(entrypoint);
@@ -8342,11 +8475,12 @@ async function parseSubscriberEntrypoint(workPath, label, entrypointValue) {
8342
8475
  `subscriber "${name}" has entrypoint "${entrypointValue}" but file "${entrypoint.filePath}" does not exist`
8343
8476
  );
8344
8477
  }
8478
+ const { variableName } = entrypoint;
8345
8479
  return {
8346
8480
  name,
8347
8481
  entrypoint: existingEntrypoint,
8348
8482
  moduleName: entrypoint.moduleName,
8349
- variableName: entrypoint.variableName
8483
+ ...variableName === void 0 ? {} : { variableName }
8350
8484
  };
8351
8485
  }
8352
8486
  async function parseSubscriber(workPath, index, config) {
@@ -8362,7 +8496,8 @@ async function parseSubscriber(workPath, index, config) {
8362
8496
  const base = await parseSubscriberEntrypoint(
8363
8497
  workPath,
8364
8498
  label,
8365
- config.entrypoint
8499
+ config.entrypoint,
8500
+ { requireVariable: false }
8366
8501
  );
8367
8502
  return {
8368
8503
  ...base,
@@ -8382,7 +8517,8 @@ async function parseLegacySubscriber(workPath, index, config) {
8382
8517
  const base = await parseSubscriberEntrypoint(
8383
8518
  workPath,
8384
8519
  label,
8385
- config.entrypoint
8520
+ config.entrypoint,
8521
+ { requireVariable: true }
8386
8522
  );
8387
8523
  return {
8388
8524
  ...base,
@@ -8446,14 +8582,26 @@ function parseTopicPatterns(name, value) {
8446
8582
  }
8447
8583
  return value;
8448
8584
  }
8449
- function createQueueIntrospectionScript(moduleName, integrations) {
8585
+ function createQueueIntrospectionScript(declaration, integrations) {
8586
+ const { variableName } = declaration;
8587
+ const probeLines = variableName === void 0 ? [] : integrations.filter((integration) => integration.subscriberProbe).flatMap(({ module: module2, subscriberProbe }) => [
8588
+ "try:",
8589
+ ` from ${module2} import ${subscriberProbe}`,
8590
+ "except ImportError:",
8591
+ " pass",
8592
+ "else:",
8593
+ ` if ${subscriberProbe}(${JSON.stringify(declaration.moduleName)}, ${JSON.stringify(variableName)}):`,
8594
+ ` owners.append(${JSON.stringify(module2)})`
8595
+ ]);
8450
8596
  return [
8451
- "import importlib, json, sys",
8597
+ "import importlib, json, os, sys",
8598
+ `os.environ[${JSON.stringify(SUBSCRIBER_ID_ENV)}] = ${JSON.stringify(declaration.name)}`,
8599
+ 'os.environ["VERCEL_APSCHEDULER_DISCOVERY"] = "1"',
8452
8600
  ...createIntegrationInstallLines(integrations, {
8453
8601
  serving: false,
8454
8602
  beforeImport: true
8455
8603
  }),
8456
- `importlib.import_module(${JSON.stringify(moduleName)})`,
8604
+ `importlib.import_module(${JSON.stringify(declaration.moduleName)})`,
8457
8605
  ...createIntegrationInstallLines(integrations, {
8458
8606
  serving: false,
8459
8607
  beforeImport: false
@@ -8470,7 +8618,9 @@ function createQueueIntrospectionScript(moduleName, integrations) {
8470
8618
  " }.items() if v is not None}",
8471
8619
  " for s in get_subscriptions()",
8472
8620
  "]",
8473
- "json.dump(subs, sys.stdout)"
8621
+ "owners = []",
8622
+ ...probeLines,
8623
+ "json.dump({'subscriptions': subs, 'owners': owners}, sys.stdout)"
8474
8624
  ].join("\n");
8475
8625
  }
8476
8626
  async function introspectQueueSubscriptions({
@@ -8481,10 +8631,7 @@ async function introspectQueueSubscriptions({
8481
8631
  kind,
8482
8632
  integrations
8483
8633
  }) {
8484
- const script3 = createQueueIntrospectionScript(
8485
- declaration.moduleName,
8486
- integrations
8487
- );
8634
+ const script3 = createQueueIntrospectionScript(declaration, integrations);
8488
8635
  try {
8489
8636
  const { stdout } = await uv.run({
8490
8637
  venvPath,
@@ -8516,6 +8663,8 @@ async function introspectQueueSubscriptions({
8516
8663
  }
8517
8664
  async function introspectDevQueueSubscriptions({
8518
8665
  moduleName,
8666
+ subscriberName,
8667
+ variableName,
8519
8668
  pythonBin,
8520
8669
  cwd,
8521
8670
  env,
@@ -8524,7 +8673,13 @@ async function introspectDevQueueSubscriptions({
8524
8673
  try {
8525
8674
  const { stdout } = await (0, import_execa6.default)(
8526
8675
  pythonBin,
8527
- ["-c", createQueueIntrospectionScript(moduleName, integrations)],
8676
+ [
8677
+ "-c",
8678
+ createQueueIntrospectionScript(
8679
+ { moduleName, name: subscriberName, variableName },
8680
+ integrations
8681
+ )
8682
+ ],
8528
8683
  // Match the deployed-function environment (see
8529
8684
  // introspectQueueSubscriptions): SDKs may need VERCEL=1,
8530
8685
  // VERCEL_REGION, and VERCEL_DEPLOYMENT_ID to register subscriptions.
@@ -8538,12 +8693,12 @@ async function introspectDevQueueSubscriptions({
8538
8693
  }
8539
8694
  }
8540
8695
  );
8541
- const subscriptions = parseIntrospectedSubscriptions(
8696
+ const introspected = parseIntrospectedSubscriptions(
8542
8697
  "subscriber",
8543
8698
  moduleName,
8544
8699
  stdout
8545
8700
  );
8546
- return subscriptions.map((subscription) => {
8701
+ const subscriptions = introspected.subscriptions.map((subscription) => {
8547
8702
  const { retryAfterSeconds, initialDelaySeconds, maxDeliveries } = subscription.triggerDefaults;
8548
8703
  return {
8549
8704
  topic: subscription.topic,
@@ -8553,6 +8708,7 @@ async function introspectDevQueueSubscriptions({
8553
8708
  ...maxDeliveries === void 0 ? {} : { maxDeliveries }
8554
8709
  };
8555
8710
  });
8711
+ return { subscriptions, owners: introspected.owners };
8556
8712
  } catch (err) {
8557
8713
  (0, import_build_utils14.debug)(
8558
8714
  `Failed to introspect dev queue subscriptions for module "${moduleName}": ${err instanceof Error ? err.message : String(err)}`
@@ -8569,14 +8725,28 @@ function parseIntrospectedSubscriptions(kind, subscriberName, stdout) {
8569
8725
  `${kind} "${subscriberName}" queue introspection did not return valid JSON`
8570
8726
  );
8571
8727
  }
8572
- if (!Array.isArray(parsed)) {
8728
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
8573
8729
  throw subscriberError(
8574
- `${kind} "${subscriberName}" queue introspection must return an array`
8730
+ `${kind} "${subscriberName}" queue introspection must return an object`
8575
8731
  );
8576
8732
  }
8577
- return parsed.map(
8578
- (subscription, index) => parseIntrospectedSubscription(kind, subscriberName, index, subscription)
8579
- );
8733
+ const { subscriptions, owners = [] } = parsed;
8734
+ if (!Array.isArray(subscriptions)) {
8735
+ throw subscriberError(
8736
+ `${kind} "${subscriberName}" queue introspection must return a "subscriptions" array`
8737
+ );
8738
+ }
8739
+ if (!Array.isArray(owners) || owners.some((o) => typeof o !== "string")) {
8740
+ throw subscriberError(
8741
+ `${kind} "${subscriberName}" queue introspection "owners" must be an array of strings`
8742
+ );
8743
+ }
8744
+ return {
8745
+ subscriptions: subscriptions.map(
8746
+ (subscription, index) => parseIntrospectedSubscription(kind, subscriberName, index, subscription)
8747
+ ),
8748
+ owners
8749
+ };
8580
8750
  }
8581
8751
  function parseIntrospectedSubscription(kind, subscriberName, index, value) {
8582
8752
  const label = `${kind} "${subscriberName}" introspected subscription #${index + 1}`;
@@ -8620,12 +8790,27 @@ function getQueueWildcardPrefix(pattern) {
8620
8790
  }
8621
8791
  return void 0;
8622
8792
  }
8793
+ var APSCHEDULER_INTEGRATION_MODULE = "vercel.integrations.apscheduler";
8794
+ function apschedulerSubscriberIdentities(subscribers) {
8795
+ return subscribers.filter(
8796
+ (subscriber) => subscriber.owners.includes(APSCHEDULER_INTEGRATION_MODULE)
8797
+ ).map((subscriber) => ({
8798
+ id: subscriber.name,
8799
+ entrypoint: `${subscriber.moduleName}:${subscriber.variableName}`
8800
+ }));
8801
+ }
8623
8802
  function subscriberError(message) {
8624
8803
  return new import_build_utils14.NowBuildError({
8625
8804
  code: "PYTHON_INVALID_SUBSCRIBER_CONFIG",
8626
8805
  message
8627
8806
  });
8628
8807
  }
8808
+ function apschedulerError(message) {
8809
+ return new import_build_utils14.NowBuildError({
8810
+ code: "PYTHON_INVALID_APSCHEDULER_CONFIG",
8811
+ message
8812
+ });
8813
+ }
8629
8814
 
8630
8815
  // src/start-dev-server.ts
8631
8816
  var DEV_SERVER_STARTUP_TIMEOUT = 5 * 6e4;
@@ -9087,6 +9272,7 @@ var startDevServer = async (opts) => {
9087
9272
  const isPyprojectEntrypoint = entrypoint === "pyproject.toml";
9088
9273
  let resolved;
9089
9274
  const handlerFunction = typeof config?.handlerFunction === "string" ? config.handlerFunction : void 0;
9275
+ const isPyQueueSidecar = config?.pythonQueueSidecar === "subscriber" || config?.pythonQueueSidecar === "workflow";
9090
9276
  let detected;
9091
9277
  if (isPyprojectEntrypoint) {
9092
9278
  const declaredEntrypoint = await getVercelToolsEntrypoint(
@@ -9102,7 +9288,7 @@ var startDevServer = async (opts) => {
9102
9288
  filePath: entrypoint,
9103
9289
  // Schedule-triggered services create their own "app" wrapper dynamically.
9104
9290
  // Other services use handlerFunction as the entrypoint variable name.
9105
- varName: service && (0, import_build_utils15.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
9291
+ varName: service && (0, import_build_utils15.isScheduleTriggeredService)(service) ? void 0 : handlerFunction ?? (isPyQueueSidecar ? "app" : void 0)
9106
9292
  } : void 0,
9107
9293
  service,
9108
9294
  opts.repoRootPath
@@ -9241,7 +9427,13 @@ If you are using a virtual environment, activate it before running "vercel dev",
9241
9427
  entrypointDir: workPath,
9242
9428
  rootDir: workPath
9243
9429
  });
9244
- queueIntegrations = legacyProject ? [] : await getQueueIntegrations({ pythonPackage });
9430
+ const queueAdapterBootstrap = await getQueueAdapterBootstrap({
9431
+ pythonPackage,
9432
+ env,
9433
+ legacyWorkersProject: legacyProject,
9434
+ hasDeclaredSubscribers: await hasPyprojectSubscribers(workPath)
9435
+ });
9436
+ queueIntegrations = queueAdapterBootstrap.integrations;
9245
9437
  if (queueIntegrations.length > 0) {
9246
9438
  env.VERCEL_QUEUE_INTEGRATIONS = queueIntegrations.map(
9247
9439
  ({ module: module2, installer, servingActivator }) => `${module2}:${installer}` + (servingActivator ? `:${servingActivator}` : "")
@@ -9249,11 +9441,7 @@ If you are using a virtual environment, activate it before running "vercel dev",
9249
9441
  } else {
9250
9442
  delete env.VERCEL_QUEUE_INTEGRATIONS;
9251
9443
  }
9252
- const conditionalInjectedPackages = legacyProject ? [] : await getConditionalInjectedPackages({
9253
- pythonPackage,
9254
- env
9255
- });
9256
- for (const injectedPackage of conditionalInjectedPackages) {
9444
+ for (const injectedPackage of queueAdapterBootstrap.injectedPackages) {
9257
9445
  await installInjectedDevPackage(
9258
9446
  {
9259
9447
  name: injectedPackage.name,
@@ -9268,6 +9456,48 @@ If you are using a virtual environment, activate it before running "vercel dev",
9268
9456
  `Skipping conditional dev package injection: ${err instanceof Error ? err.message : String(err)}`
9269
9457
  );
9270
9458
  }
9459
+ const apschedulerSubscribers = [];
9460
+ if (!legacyProject) {
9461
+ const runtimeDir = (0, import_path13.join)(workPath, ".vercel", "python");
9462
+ const declaredSubscribers = await getPyprojectSubscribers(workPath);
9463
+ const introspectionEnv = {
9464
+ ...env,
9465
+ PYTHONPATH: [runtimeDir, env.PYTHONPATH].filter(Boolean).join(import_path13.delimiter)
9466
+ };
9467
+ delete introspectionEnv.VERCEL_APSCHEDULER_SUBSCRIBERS;
9468
+ for (const declaration of declaredSubscribers) {
9469
+ const introspected = await introspectDevQueueSubscriptions({
9470
+ moduleName: declaration.moduleName,
9471
+ subscriberName: declaration.name,
9472
+ variableName: declaration.variableName || "app",
9473
+ pythonBin: spawnCommand,
9474
+ cwd: workPath,
9475
+ env: introspectionEnv,
9476
+ integrations: queueIntegrations
9477
+ });
9478
+ apschedulerSubscribers.push(
9479
+ ...apschedulerSubscriberIdentities([
9480
+ { ...declaration, owners: introspected?.owners ?? [] }
9481
+ ])
9482
+ );
9483
+ }
9484
+ }
9485
+ if (apschedulerSubscribers.length > 0) {
9486
+ env.VERCEL_APSCHEDULER_SUBSCRIBERS = JSON.stringify(
9487
+ apschedulerSubscribers
9488
+ );
9489
+ const previewConfig = await getApschedulerPreviewConfig(workPath);
9490
+ if (previewConfig) {
9491
+ env.VERCEL_APSCHEDULER_PREVIEW_IDLE_TIMEOUT_SECONDS = String(
9492
+ previewConfig.idleTimeoutSeconds
9493
+ );
9494
+ } else {
9495
+ delete env.VERCEL_APSCHEDULER_PREVIEW_IDLE_TIMEOUT_SECONDS;
9496
+ }
9497
+ } else {
9498
+ delete env.VERCEL_APSCHEDULER_SUBSCRIBERS;
9499
+ delete env.VERCEL_APSCHEDULER_PREVIEW_IDLE_TIMEOUT_SECONDS;
9500
+ }
9271
9501
  if (legacyProject) {
9272
9502
  await installInjectedDevPackage(
9273
9503
  {
@@ -9292,19 +9522,29 @@ If you are using a virtual environment, activate it before running "vercel dev",
9292
9522
  }
9293
9523
  if (useQueueServing) {
9294
9524
  env.VERCEL_DEV_QUEUE_SERVING = "1";
9525
+ const subscriberName = getModuleEntrypointName({
9526
+ moduleName: modulePath,
9527
+ variableName: variableName || "app"
9528
+ });
9529
+ env.VERCEL_PYTHON_SUBSCRIBER_ID = subscriberName;
9295
9530
  const runtimeDir = (0, import_path13.join)(workPath, ".vercel", "python");
9296
- queueSubscriptions = await introspectDevQueueSubscriptions({
9531
+ const introspectionEnv = {
9532
+ ...env,
9533
+ PYTHONPATH: [runtimeDir, env.PYTHONPATH].filter(Boolean).join(import_path13.delimiter)
9534
+ };
9535
+ delete introspectionEnv.VERCEL_APSCHEDULER_SUBSCRIBERS;
9536
+ queueSubscriptions = (await introspectDevQueueSubscriptions({
9297
9537
  moduleName: modulePath,
9538
+ subscriberName,
9539
+ variableName: variableName || "app",
9298
9540
  pythonBin: spawnCommand,
9299
9541
  cwd: workPath,
9300
- env: {
9301
- ...env,
9302
- PYTHONPATH: [runtimeDir, env.PYTHONPATH].filter(Boolean).join(import_path13.delimiter)
9303
- },
9542
+ env: introspectionEnv,
9304
9543
  integrations: queueIntegrations
9305
- });
9544
+ }))?.subscriptions;
9306
9545
  } else {
9307
9546
  delete env.VERCEL_DEV_QUEUE_SERVING;
9547
+ delete env.VERCEL_PYTHON_SUBSCRIBER_ID;
9308
9548
  await installInjectedDevPackage(
9309
9549
  {
9310
9550
  name: "vercel-workers",
@@ -10793,7 +11033,7 @@ async function getDevSidecars({
10793
11033
  use: build2.use,
10794
11034
  src: subscriber.entrypoint,
10795
11035
  config: {
10796
- handlerFunction: subscriber.variableName,
11036
+ ...subscriber.variableName === void 0 ? {} : { handlerFunction: subscriber.variableName },
10797
11037
  pythonQueueSidecar: "subscriber"
10798
11038
  }
10799
11039
  },
@@ -11420,11 +11660,14 @@ var build = async ({
11420
11660
  projectDir: (0, import_path21.join)(workPath, entryDirectory),
11421
11661
  pipPlatformArgs
11422
11662
  });
11423
- const conditionalInjectedPackages = usedUvManagedInstall && !legacyWorkersProject ? await getConditionalInjectedPackages({
11663
+ const queueAdapterBootstrap = await getQueueAdapterBootstrap({
11424
11664
  pythonPackage,
11425
- env: baseEnv
11426
- }) : [];
11427
- for (const injectedPackage of conditionalInjectedPackages) {
11665
+ env: baseEnv,
11666
+ legacyWorkersProject,
11667
+ hasDeclaredSubscribers: await hasPyprojectSubscribers(workPath)
11668
+ });
11669
+ const queueAdapterInjectedPackages = usedUvManagedInstall ? queueAdapterBootstrap.injectedPackages : [];
11670
+ for (const injectedPackage of queueAdapterInjectedPackages) {
11428
11671
  await installInjectedPackage({
11429
11672
  ...injectedPackage,
11430
11673
  uv,
@@ -11433,6 +11676,32 @@ var build = async ({
11433
11676
  pipPlatformArgs
11434
11677
  });
11435
11678
  }
11679
+ const localSdkSourcePaths = await getLocalSdkSourcePaths({
11680
+ pythonPackage,
11681
+ env: baseEnv
11682
+ });
11683
+ if (localSdkSourcePaths.length > 0) {
11684
+ (0, import_build_utils24.debug)(
11685
+ `Installing vercel SDK override from ${baseEnv.VERCEL_SDK_PYTHON}: ` + localSdkSourcePaths.join(", ")
11686
+ );
11687
+ await uv.pip({
11688
+ venvPath,
11689
+ projectDir: (0, import_path21.join)(workPath, entryDirectory),
11690
+ args: [
11691
+ "install",
11692
+ "--link-mode",
11693
+ "copy",
11694
+ // Without --no-sources, uv resolves the checkout's workspace
11695
+ // dependencies as editable .pth installs pointing at the (build
11696
+ // only) checkout mount — invisible to bundling and dangling at
11697
+ // runtime. Explicit paths stay real installs; the remaining
11698
+ // workspace siblings resolve from PyPI.
11699
+ "--no-sources",
11700
+ ...pipPlatformArgs,
11701
+ ...localSdkSourcePaths
11702
+ ]
11703
+ });
11704
+ }
11436
11705
  if (workflows.length > 0) {
11437
11706
  workflowMode = await resolveWorkflowServingMode({
11438
11707
  pythonPackage,
@@ -11465,7 +11734,7 @@ var build = async ({
11465
11734
  if (quirksResult.buildEnv) {
11466
11735
  Object.assign(pythonEnv, quirksResult.buildEnv);
11467
11736
  }
11468
- const queueIntegrations = legacyWorkersProject ? [] : await getQueueIntegrations({ pythonPackage });
11737
+ const queueIntegrations = queueAdapterBootstrap.integrations;
11469
11738
  const writeGeneratedQueueHandler = async (outputPath, declaration) => {
11470
11739
  const generatedPath = getGeneratedQueueHandlerPath(outputPath);
11471
11740
  await import_fs20.default.promises.mkdir((0, import_path21.dirname)((0, import_path21.join)(workPath, generatedPath)), {
@@ -11635,6 +11904,18 @@ var build = async ({
11635
11904
  ({ module: module2, installer, servingActivator }) => `${module2}:${installer}` + (servingActivator ? `:${servingActivator}` : "")
11636
11905
  ).join(",");
11637
11906
  }
11907
+ const apschedulerSubscribers = apschedulerSubscriberIdentities(subscribers);
11908
+ if (apschedulerSubscribers.length > 0) {
11909
+ lambdaEnv.VERCEL_APSCHEDULER_SUBSCRIBERS = JSON.stringify(
11910
+ apschedulerSubscribers
11911
+ );
11912
+ const previewConfig = await getApschedulerPreviewConfig(workPath);
11913
+ if (previewConfig) {
11914
+ lambdaEnv.VERCEL_APSCHEDULER_PREVIEW_IDLE_TIMEOUT_SECONDS = String(
11915
+ previewConfig.idleTimeoutSeconds
11916
+ );
11917
+ }
11918
+ }
11638
11919
  const globOptions = {
11639
11920
  cwd: workPath,
11640
11921
  ignore: config && typeof config.excludeFiles === "string" ? [...predefinedExcludes, config.excludeFiles] : predefinedExcludes
@@ -11971,7 +12252,9 @@ var build = async ({
11971
12252
  runtime: pythonVersion.runtime,
11972
12253
  ...lambdaOptions,
11973
12254
  architecture: target.architecture,
11974
- environment: lambdaEnv,
12255
+ environment: {
12256
+ ...lambdaEnv
12257
+ },
11975
12258
  supportsResponseStreaming: true
11976
12259
  });
11977
12260
  }
@@ -12009,7 +12292,12 @@ var build = async ({
12009
12292
  handler: `${handlerPyFilename}.vc_handler`,
12010
12293
  runtime: pythonVersion.runtime,
12011
12294
  architecture: target.architecture,
12012
- environment: lambdaEnv,
12295
+ environment: {
12296
+ ...lambdaEnv,
12297
+ // The runtime activates publish-side integrations before importing the
12298
+ // generated subscriber module, so the identity must already be present.
12299
+ VERCEL_PYTHON_SUBSCRIBER_ID: subscriber.name
12300
+ },
12013
12301
  experimentalTriggers,
12014
12302
  supportsResponseStreaming: true
12015
12303
  });
@@ -12021,6 +12309,11 @@ var build = async ({
12021
12309
  const consumer = getSubscriberConsumerName(declaration.name);
12022
12310
  const legacy = declaration.legacy;
12023
12311
  (0, import_assert.default)(legacy, "legacy subscriber declarations must carry legacy config");
12312
+ const { variableName } = declaration;
12313
+ (0, import_assert.default)(
12314
+ variableName,
12315
+ "legacy subscriber declarations must name an entrypoint object"
12316
+ );
12024
12317
  const experimentalTriggers = legacy.topics.map((topic) => ({
12025
12318
  type: "queue/v2beta",
12026
12319
  topic,
@@ -12035,7 +12328,7 @@ var build = async ({
12035
12328
  moduleName: declaration.moduleName,
12036
12329
  entrypoint: declaration.entrypoint,
12037
12330
  vendorDir,
12038
- variableName: declaration.variableName
12331
+ variableName
12039
12332
  })
12040
12333
  })
12041
12334
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "6.55.3",
3
+ "version": "6.56.1",
4
4
  "main": "./dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -14,7 +14,7 @@
14
14
  "directory": "packages/python"
15
15
  },
16
16
  "dependencies": {
17
- "@vercel/python-analysis": "0.13.1"
17
+ "@vercel/python-analysis": "0.13.2"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@renovatebot/pep440": "4.2.1",
@@ -33,20 +33,17 @@
33
33
  "pip-requirements-js": "1.0.2",
34
34
  "semver": "6.3.1",
35
35
  "smol-toml": "1.5.2",
36
- "vitest": "2.1.4",
36
+ "vitest": "4.1.10",
37
37
  "which": "3.0.0",
38
- "@vercel/error-utils": "2.2.1",
39
- "@vercel/build-utils": "14.0.2",
40
- "@vercel/python-runtime": "0.18.0"
38
+ "@vercel/build-utils": "14.0.5",
39
+ "@vercel/python-runtime": "0.19.0",
40
+ "@vercel/error-utils": "2.2.1"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "node ../../utils/build-builder.mjs",
44
44
  "type-check": "tsc --noEmit",
45
45
  "test": "cross-env VERCEL_FORCE_PYTHON_STREAMING=1 NODE_OPTIONS=--experimental-vm-modules vitest run --config ../../vitest.config.mts",
46
- "test-unit": "vitest run --config ../../vitest.config.mts test/unit",
47
- "test-e2e": "pnpm test test/integration-*",
48
- "vitest-run": "vitest -c ../../vitest.config.mts",
49
- "vitest-unit": "pnpm test test/unit",
50
- "vitest-e2e": "glob --absolute 'test/integration-*'"
46
+ "test-unit": "cross-env VERCEL_FORCE_PYTHON_STREAMING=1 NODE_OPTIONS=--experimental-vm-modules vitest run --config ../../vitest.config.mts test/unit",
47
+ "test-e2e": "cross-env VERCEL_FORCE_PYTHON_STREAMING=1 NODE_OPTIONS=--experimental-vm-modules vitest run --config ../../vitest.config.mts test/integration-"
51
48
  }
52
49
  }