@vercel/python 6.55.3 → 6.56.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 -70
  2. package/package.json +5 -8
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;
@@ -7979,6 +7991,7 @@ var import_build_utils14 = require("@vercel/build-utils");
7979
7991
  var import_path10 = require("path");
7980
7992
  var import_fs10 = __toESM(require("fs"));
7981
7993
  var MODULE_ATTR_RE = /^([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*):([A-Za-z_][\w]*)$/;
7994
+ var MODULE_RE = /^[A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*$/;
7982
7995
  function parseModuleEntrypoint(value) {
7983
7996
  const match = MODULE_ATTR_RE.exec(value);
7984
7997
  if (!match) {
@@ -7990,11 +8003,21 @@ function parseModuleEntrypoint(value) {
7990
8003
  filePath: `${match[1].replace(/\./g, "/")}.py`
7991
8004
  };
7992
8005
  }
8006
+ function parseBareModuleEntrypoint(value) {
8007
+ if (!MODULE_RE.test(value)) {
8008
+ return null;
8009
+ }
8010
+ return {
8011
+ moduleName: value,
8012
+ filePath: `${value.replace(/\./g, "/")}.py`
8013
+ };
8014
+ }
7993
8015
  function getModuleEntrypointName({
7994
8016
  moduleName,
7995
8017
  variableName
7996
8018
  }) {
7997
- return `${moduleName.replace(/\./g, "-")}_${variableName}`;
8019
+ const base = moduleName.replace(/\./g, "-");
8020
+ return variableName === void 0 ? base : `${base}_${variableName}`;
7998
8021
  }
7999
8022
  function safePathSegment(value) {
8000
8023
  return [...value].map((char) => {
@@ -8108,6 +8131,7 @@ function workflowError(message) {
8108
8131
 
8109
8132
  // src/subscribers.ts
8110
8133
  var SUBSCRIBER_OUTPUT_DIR = "_py_subscribers";
8134
+ var SUBSCRIBER_ID_ENV = "VERCEL_PYTHON_SUBSCRIBER_ID";
8111
8135
  var TRIGGER_NUMBER_FIELDS = [
8112
8136
  {
8113
8137
  field: "max_attempts",
@@ -8166,6 +8190,13 @@ var LEGACY_SUBSCRIBER_FIELD_NAMES = /* @__PURE__ */ new Set([
8166
8190
  "topics",
8167
8191
  ...LEGACY_TRIGGER_NUMBER_FIELDS.map(({ field }) => field)
8168
8192
  ]);
8193
+ var APSCHEDULER_PREVIEW_FIELD_NAMES = /* @__PURE__ */ new Set(["enabled", "idle_timeout"]);
8194
+ var APSCHEDULER_DURATION_UNITS = {
8195
+ s: 1,
8196
+ m: 60,
8197
+ h: 60 * 60,
8198
+ d: 24 * 60 * 60
8199
+ };
8169
8200
  function getSubscriberOutputPath(subscriberName) {
8170
8201
  return `${SUBSCRIBER_OUTPUT_DIR}/${safePathSegment(subscriberName)}`;
8171
8202
  }
@@ -8178,6 +8209,15 @@ function getGeneratedQueueHandlerPath(outputPath) {
8178
8209
  function generatedPythonPathToModule(filePath) {
8179
8210
  return filePath.replace(/\.py$/, "").split(/[\\/]+/).join(".");
8180
8211
  }
8212
+ async function hasPyprojectSubscribers(workPath) {
8213
+ const pyprojectPath = (0, import_path12.join)(workPath, "pyproject.toml");
8214
+ if (!import_fs12.default.existsSync(pyprojectPath)) {
8215
+ return false;
8216
+ }
8217
+ const pyproject = await (0, import_build_utils14.readConfigFile)(pyprojectPath);
8218
+ const subscribers = pyproject?.tool?.vercel?.subscribers;
8219
+ return Array.isArray(subscribers) && subscribers.length > 0;
8220
+ }
8181
8221
  async function getPyprojectSubscribers(workPath, { legacySchema = false } = {}) {
8182
8222
  const pyprojectPath = (0, import_path12.join)(workPath, "pyproject.toml");
8183
8223
  if (!import_fs12.default.existsSync(pyprojectPath)) {
@@ -8207,6 +8247,63 @@ async function getPyprojectSubscribers(workPath, { legacySchema = false } = {})
8207
8247
  }
8208
8248
  return parsedSubscribers;
8209
8249
  }
8250
+ async function getApschedulerPreviewConfig(workPath) {
8251
+ const pyprojectPath = (0, import_path12.join)(workPath, "pyproject.toml");
8252
+ if (!import_fs12.default.existsSync(pyprojectPath)) {
8253
+ return void 0;
8254
+ }
8255
+ const pyproject = await (0, import_build_utils14.readConfigFile)(pyprojectPath);
8256
+ const apscheduler = pyproject?.tool?.vercel?.apscheduler;
8257
+ if (apscheduler === void 0) {
8258
+ return void 0;
8259
+ }
8260
+ if (!apscheduler || typeof apscheduler !== "object" || Array.isArray(apscheduler)) {
8261
+ throw apschedulerError('"tool.vercel.apscheduler" must be a table');
8262
+ }
8263
+ const previews = apscheduler.previews;
8264
+ if (previews === void 0) {
8265
+ return void 0;
8266
+ }
8267
+ if (!previews || typeof previews !== "object" || Array.isArray(previews)) {
8268
+ throw apschedulerError(
8269
+ '"tool.vercel.apscheduler.previews" must be a table'
8270
+ );
8271
+ }
8272
+ const rawPreviews = previews;
8273
+ for (const key of Object.keys(rawPreviews)) {
8274
+ if (!APSCHEDULER_PREVIEW_FIELD_NAMES.has(key)) {
8275
+ throw apschedulerError(
8276
+ `"tool.vercel.apscheduler.previews" has unrecognized field "${key}"`
8277
+ );
8278
+ }
8279
+ }
8280
+ if (rawPreviews.enabled !== void 0 && typeof rawPreviews.enabled !== "boolean") {
8281
+ throw apschedulerError(
8282
+ '"tool.vercel.apscheduler.previews.enabled" must be a boolean'
8283
+ );
8284
+ }
8285
+ if (rawPreviews.enabled !== true) {
8286
+ return void 0;
8287
+ }
8288
+ if (typeof rawPreviews.idle_timeout !== "string") {
8289
+ throw apschedulerError(
8290
+ '"tool.vercel.apscheduler.previews.idle_timeout" must be a duration such as "30m"'
8291
+ );
8292
+ }
8293
+ const match = /^([1-9]\d*)([smhd])$/.exec(rawPreviews.idle_timeout);
8294
+ if (!match) {
8295
+ throw apschedulerError(
8296
+ '"tool.vercel.apscheduler.previews.idle_timeout" must be a positive duration using s, m, h, or d (for example "30m")'
8297
+ );
8298
+ }
8299
+ const idleTimeoutSeconds = Number(match[1]) * APSCHEDULER_DURATION_UNITS[match[2]];
8300
+ if (!Number.isSafeInteger(idleTimeoutSeconds)) {
8301
+ throw apschedulerError(
8302
+ '"tool.vercel.apscheduler.previews.idle_timeout" is too large'
8303
+ );
8304
+ }
8305
+ return { idleTimeoutSeconds };
8306
+ }
8210
8307
  async function resolveQueueSubscribers({
8211
8308
  declarations,
8212
8309
  uv,
@@ -8228,10 +8325,12 @@ async function resolveQueueSubscribers({
8228
8325
  const hint = kind === "workflow" ? "; ensure the entrypoint instantiates vercel.workflow.Workflows" : "";
8229
8326
  const unmatchedTopicPatterns = getUnmatchedQueueTopicPatterns(
8230
8327
  declaration,
8231
- introspected
8328
+ introspected.subscriptions
8232
8329
  );
8233
8330
  if (unmatchedTopicPatterns.length > 0) {
8234
- const introspectedTopics = introspected.map(({ topic }) => topic);
8331
+ const introspectedTopics = introspected.subscriptions.map(
8332
+ ({ topic }) => topic
8333
+ );
8235
8334
  throw subscriberError(
8236
8335
  `${kind} "${declaration.name}" declared topics [${unmatchedTopicPatterns.join(
8237
8336
  ", "
@@ -8240,9 +8339,9 @@ async function resolveQueueSubscribers({
8240
8339
  )}]${hint}`
8241
8340
  );
8242
8341
  }
8243
- const subscriptions = kind === "workflow" ? introspected.filter(
8342
+ const subscriptions = kind === "workflow" ? introspected.subscriptions.filter(
8244
8343
  (subscription) => isWorkflowQueueTopic(subscription.topic)
8245
- ) : filterQueueSubscriptions(declaration, introspected);
8344
+ ) : filterQueueSubscriptions(declaration, introspected.subscriptions);
8246
8345
  if (subscriptions.length === 0) {
8247
8346
  if (kind === "workflow") {
8248
8347
  throw subscriberError(
@@ -8254,7 +8353,7 @@ async function resolveQueueSubscribers({
8254
8353
  `${kind} "${declaration.name}" declared topics [${declared}] but no introspected queue subscriptions matched${hint}`
8255
8354
  );
8256
8355
  }
8257
- result.push({ ...declaration, subscriptions });
8356
+ result.push({ ...declaration, subscriptions, owners: introspected.owners });
8258
8357
  }
8259
8358
  return result;
8260
8359
  }
@@ -8307,8 +8406,10 @@ function createIntegrationInstallLines(integrations, { serving, beforeImport })
8307
8406
  function createQueueHandlerModule(declaration, integrations) {
8308
8407
  return [
8309
8408
  "import importlib",
8409
+ "import os",
8310
8410
  "import vercel.queue",
8311
8411
  "",
8412
+ `os.environ[${JSON.stringify(SUBSCRIBER_ID_ENV)}] = ${JSON.stringify(declaration.name)}`,
8312
8413
  ...createIntegrationInstallLines(integrations, {
8313
8414
  serving: true,
8314
8415
  beforeImport: true
@@ -8322,14 +8423,22 @@ function createQueueHandlerModule(declaration, integrations) {
8322
8423
  ""
8323
8424
  ].join("\n");
8324
8425
  }
8325
- async function parseSubscriberEntrypoint(workPath, label, entrypointValue) {
8426
+ async function parseSubscriberEntrypoint(workPath, label, entrypointValue, { requireVariable }) {
8326
8427
  if (typeof entrypointValue !== "string") {
8327
8428
  throw subscriberError(`${label} must define string field "entrypoint"`);
8328
8429
  }
8329
- const entrypoint = parseModuleEntrypoint(entrypointValue);
8430
+ if (!requireVariable && /\.py$/i.test(entrypointValue)) {
8431
+ const suggestion = parseBareModuleEntrypoint(
8432
+ entrypointValue.slice(0, -3).replace(/[\\/]+/g, ".").replace(/(^|\.)__init__$/, "")
8433
+ );
8434
+ throw subscriberError(
8435
+ `${label} has invalid entrypoint "${entrypointValue}". Use an import path, not a file path${suggestion ? `: "${suggestion.moduleName}"` : ""}`
8436
+ );
8437
+ }
8438
+ const entrypoint = parseModuleEntrypoint(entrypointValue) ?? (requireVariable ? null : parseBareModuleEntrypoint(entrypointValue));
8330
8439
  if (!entrypoint) {
8331
8440
  throw subscriberError(
8332
- `${label} has invalid entrypoint "${entrypointValue}". Use "module:object"`
8441
+ `${label} has invalid entrypoint "${entrypointValue}". Use ${requireVariable ? '"module:object"' : '"module:object" or a module path like "pkg.module"'}`
8333
8442
  );
8334
8443
  }
8335
8444
  const name = getModuleEntrypointName(entrypoint);
@@ -8342,11 +8451,12 @@ async function parseSubscriberEntrypoint(workPath, label, entrypointValue) {
8342
8451
  `subscriber "${name}" has entrypoint "${entrypointValue}" but file "${entrypoint.filePath}" does not exist`
8343
8452
  );
8344
8453
  }
8454
+ const { variableName } = entrypoint;
8345
8455
  return {
8346
8456
  name,
8347
8457
  entrypoint: existingEntrypoint,
8348
8458
  moduleName: entrypoint.moduleName,
8349
- variableName: entrypoint.variableName
8459
+ ...variableName === void 0 ? {} : { variableName }
8350
8460
  };
8351
8461
  }
8352
8462
  async function parseSubscriber(workPath, index, config) {
@@ -8362,7 +8472,8 @@ async function parseSubscriber(workPath, index, config) {
8362
8472
  const base = await parseSubscriberEntrypoint(
8363
8473
  workPath,
8364
8474
  label,
8365
- config.entrypoint
8475
+ config.entrypoint,
8476
+ { requireVariable: false }
8366
8477
  );
8367
8478
  return {
8368
8479
  ...base,
@@ -8382,7 +8493,8 @@ async function parseLegacySubscriber(workPath, index, config) {
8382
8493
  const base = await parseSubscriberEntrypoint(
8383
8494
  workPath,
8384
8495
  label,
8385
- config.entrypoint
8496
+ config.entrypoint,
8497
+ { requireVariable: true }
8386
8498
  );
8387
8499
  return {
8388
8500
  ...base,
@@ -8446,14 +8558,26 @@ function parseTopicPatterns(name, value) {
8446
8558
  }
8447
8559
  return value;
8448
8560
  }
8449
- function createQueueIntrospectionScript(moduleName, integrations) {
8561
+ function createQueueIntrospectionScript(declaration, integrations) {
8562
+ const { variableName } = declaration;
8563
+ const probeLines = variableName === void 0 ? [] : integrations.filter((integration) => integration.subscriberProbe).flatMap(({ module: module2, subscriberProbe }) => [
8564
+ "try:",
8565
+ ` from ${module2} import ${subscriberProbe}`,
8566
+ "except ImportError:",
8567
+ " pass",
8568
+ "else:",
8569
+ ` if ${subscriberProbe}(${JSON.stringify(declaration.moduleName)}, ${JSON.stringify(variableName)}):`,
8570
+ ` owners.append(${JSON.stringify(module2)})`
8571
+ ]);
8450
8572
  return [
8451
- "import importlib, json, sys",
8573
+ "import importlib, json, os, sys",
8574
+ `os.environ[${JSON.stringify(SUBSCRIBER_ID_ENV)}] = ${JSON.stringify(declaration.name)}`,
8575
+ 'os.environ["VERCEL_APSCHEDULER_DISCOVERY"] = "1"',
8452
8576
  ...createIntegrationInstallLines(integrations, {
8453
8577
  serving: false,
8454
8578
  beforeImport: true
8455
8579
  }),
8456
- `importlib.import_module(${JSON.stringify(moduleName)})`,
8580
+ `importlib.import_module(${JSON.stringify(declaration.moduleName)})`,
8457
8581
  ...createIntegrationInstallLines(integrations, {
8458
8582
  serving: false,
8459
8583
  beforeImport: false
@@ -8470,7 +8594,9 @@ function createQueueIntrospectionScript(moduleName, integrations) {
8470
8594
  " }.items() if v is not None}",
8471
8595
  " for s in get_subscriptions()",
8472
8596
  "]",
8473
- "json.dump(subs, sys.stdout)"
8597
+ "owners = []",
8598
+ ...probeLines,
8599
+ "json.dump({'subscriptions': subs, 'owners': owners}, sys.stdout)"
8474
8600
  ].join("\n");
8475
8601
  }
8476
8602
  async function introspectQueueSubscriptions({
@@ -8481,10 +8607,7 @@ async function introspectQueueSubscriptions({
8481
8607
  kind,
8482
8608
  integrations
8483
8609
  }) {
8484
- const script3 = createQueueIntrospectionScript(
8485
- declaration.moduleName,
8486
- integrations
8487
- );
8610
+ const script3 = createQueueIntrospectionScript(declaration, integrations);
8488
8611
  try {
8489
8612
  const { stdout } = await uv.run({
8490
8613
  venvPath,
@@ -8516,6 +8639,8 @@ async function introspectQueueSubscriptions({
8516
8639
  }
8517
8640
  async function introspectDevQueueSubscriptions({
8518
8641
  moduleName,
8642
+ subscriberName,
8643
+ variableName,
8519
8644
  pythonBin,
8520
8645
  cwd,
8521
8646
  env,
@@ -8524,7 +8649,13 @@ async function introspectDevQueueSubscriptions({
8524
8649
  try {
8525
8650
  const { stdout } = await (0, import_execa6.default)(
8526
8651
  pythonBin,
8527
- ["-c", createQueueIntrospectionScript(moduleName, integrations)],
8652
+ [
8653
+ "-c",
8654
+ createQueueIntrospectionScript(
8655
+ { moduleName, name: subscriberName, variableName },
8656
+ integrations
8657
+ )
8658
+ ],
8528
8659
  // Match the deployed-function environment (see
8529
8660
  // introspectQueueSubscriptions): SDKs may need VERCEL=1,
8530
8661
  // VERCEL_REGION, and VERCEL_DEPLOYMENT_ID to register subscriptions.
@@ -8538,12 +8669,12 @@ async function introspectDevQueueSubscriptions({
8538
8669
  }
8539
8670
  }
8540
8671
  );
8541
- const subscriptions = parseIntrospectedSubscriptions(
8672
+ const introspected = parseIntrospectedSubscriptions(
8542
8673
  "subscriber",
8543
8674
  moduleName,
8544
8675
  stdout
8545
8676
  );
8546
- return subscriptions.map((subscription) => {
8677
+ const subscriptions = introspected.subscriptions.map((subscription) => {
8547
8678
  const { retryAfterSeconds, initialDelaySeconds, maxDeliveries } = subscription.triggerDefaults;
8548
8679
  return {
8549
8680
  topic: subscription.topic,
@@ -8553,6 +8684,7 @@ async function introspectDevQueueSubscriptions({
8553
8684
  ...maxDeliveries === void 0 ? {} : { maxDeliveries }
8554
8685
  };
8555
8686
  });
8687
+ return { subscriptions, owners: introspected.owners };
8556
8688
  } catch (err) {
8557
8689
  (0, import_build_utils14.debug)(
8558
8690
  `Failed to introspect dev queue subscriptions for module "${moduleName}": ${err instanceof Error ? err.message : String(err)}`
@@ -8569,14 +8701,28 @@ function parseIntrospectedSubscriptions(kind, subscriberName, stdout) {
8569
8701
  `${kind} "${subscriberName}" queue introspection did not return valid JSON`
8570
8702
  );
8571
8703
  }
8572
- if (!Array.isArray(parsed)) {
8704
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
8573
8705
  throw subscriberError(
8574
- `${kind} "${subscriberName}" queue introspection must return an array`
8706
+ `${kind} "${subscriberName}" queue introspection must return an object`
8575
8707
  );
8576
8708
  }
8577
- return parsed.map(
8578
- (subscription, index) => parseIntrospectedSubscription(kind, subscriberName, index, subscription)
8579
- );
8709
+ const { subscriptions, owners = [] } = parsed;
8710
+ if (!Array.isArray(subscriptions)) {
8711
+ throw subscriberError(
8712
+ `${kind} "${subscriberName}" queue introspection must return a "subscriptions" array`
8713
+ );
8714
+ }
8715
+ if (!Array.isArray(owners) || owners.some((o) => typeof o !== "string")) {
8716
+ throw subscriberError(
8717
+ `${kind} "${subscriberName}" queue introspection "owners" must be an array of strings`
8718
+ );
8719
+ }
8720
+ return {
8721
+ subscriptions: subscriptions.map(
8722
+ (subscription, index) => parseIntrospectedSubscription(kind, subscriberName, index, subscription)
8723
+ ),
8724
+ owners
8725
+ };
8580
8726
  }
8581
8727
  function parseIntrospectedSubscription(kind, subscriberName, index, value) {
8582
8728
  const label = `${kind} "${subscriberName}" introspected subscription #${index + 1}`;
@@ -8620,12 +8766,27 @@ function getQueueWildcardPrefix(pattern) {
8620
8766
  }
8621
8767
  return void 0;
8622
8768
  }
8769
+ var APSCHEDULER_INTEGRATION_MODULE = "vercel.integrations.apscheduler";
8770
+ function apschedulerSubscriberIdentities(subscribers) {
8771
+ return subscribers.filter(
8772
+ (subscriber) => subscriber.owners.includes(APSCHEDULER_INTEGRATION_MODULE)
8773
+ ).map((subscriber) => ({
8774
+ id: subscriber.name,
8775
+ entrypoint: `${subscriber.moduleName}:${subscriber.variableName}`
8776
+ }));
8777
+ }
8623
8778
  function subscriberError(message) {
8624
8779
  return new import_build_utils14.NowBuildError({
8625
8780
  code: "PYTHON_INVALID_SUBSCRIBER_CONFIG",
8626
8781
  message
8627
8782
  });
8628
8783
  }
8784
+ function apschedulerError(message) {
8785
+ return new import_build_utils14.NowBuildError({
8786
+ code: "PYTHON_INVALID_APSCHEDULER_CONFIG",
8787
+ message
8788
+ });
8789
+ }
8629
8790
 
8630
8791
  // src/start-dev-server.ts
8631
8792
  var DEV_SERVER_STARTUP_TIMEOUT = 5 * 6e4;
@@ -9087,6 +9248,7 @@ var startDevServer = async (opts) => {
9087
9248
  const isPyprojectEntrypoint = entrypoint === "pyproject.toml";
9088
9249
  let resolved;
9089
9250
  const handlerFunction = typeof config?.handlerFunction === "string" ? config.handlerFunction : void 0;
9251
+ const isPyQueueSidecar = config?.pythonQueueSidecar === "subscriber" || config?.pythonQueueSidecar === "workflow";
9090
9252
  let detected;
9091
9253
  if (isPyprojectEntrypoint) {
9092
9254
  const declaredEntrypoint = await getVercelToolsEntrypoint(
@@ -9102,7 +9264,7 @@ var startDevServer = async (opts) => {
9102
9264
  filePath: entrypoint,
9103
9265
  // Schedule-triggered services create their own "app" wrapper dynamically.
9104
9266
  // Other services use handlerFunction as the entrypoint variable name.
9105
- varName: service && (0, import_build_utils15.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
9267
+ varName: service && (0, import_build_utils15.isScheduleTriggeredService)(service) ? void 0 : handlerFunction ?? (isPyQueueSidecar ? "app" : void 0)
9106
9268
  } : void 0,
9107
9269
  service,
9108
9270
  opts.repoRootPath
@@ -9241,7 +9403,13 @@ If you are using a virtual environment, activate it before running "vercel dev",
9241
9403
  entrypointDir: workPath,
9242
9404
  rootDir: workPath
9243
9405
  });
9244
- queueIntegrations = legacyProject ? [] : await getQueueIntegrations({ pythonPackage });
9406
+ const queueAdapterBootstrap = await getQueueAdapterBootstrap({
9407
+ pythonPackage,
9408
+ env,
9409
+ legacyWorkersProject: legacyProject,
9410
+ hasDeclaredSubscribers: await hasPyprojectSubscribers(workPath)
9411
+ });
9412
+ queueIntegrations = queueAdapterBootstrap.integrations;
9245
9413
  if (queueIntegrations.length > 0) {
9246
9414
  env.VERCEL_QUEUE_INTEGRATIONS = queueIntegrations.map(
9247
9415
  ({ module: module2, installer, servingActivator }) => `${module2}:${installer}` + (servingActivator ? `:${servingActivator}` : "")
@@ -9249,11 +9417,7 @@ If you are using a virtual environment, activate it before running "vercel dev",
9249
9417
  } else {
9250
9418
  delete env.VERCEL_QUEUE_INTEGRATIONS;
9251
9419
  }
9252
- const conditionalInjectedPackages = legacyProject ? [] : await getConditionalInjectedPackages({
9253
- pythonPackage,
9254
- env
9255
- });
9256
- for (const injectedPackage of conditionalInjectedPackages) {
9420
+ for (const injectedPackage of queueAdapterBootstrap.injectedPackages) {
9257
9421
  await installInjectedDevPackage(
9258
9422
  {
9259
9423
  name: injectedPackage.name,
@@ -9268,6 +9432,48 @@ If you are using a virtual environment, activate it before running "vercel dev",
9268
9432
  `Skipping conditional dev package injection: ${err instanceof Error ? err.message : String(err)}`
9269
9433
  );
9270
9434
  }
9435
+ const apschedulerSubscribers = [];
9436
+ if (!legacyProject) {
9437
+ const runtimeDir = (0, import_path13.join)(workPath, ".vercel", "python");
9438
+ const declaredSubscribers = await getPyprojectSubscribers(workPath);
9439
+ const introspectionEnv = {
9440
+ ...env,
9441
+ PYTHONPATH: [runtimeDir, env.PYTHONPATH].filter(Boolean).join(import_path13.delimiter)
9442
+ };
9443
+ delete introspectionEnv.VERCEL_APSCHEDULER_SUBSCRIBERS;
9444
+ for (const declaration of declaredSubscribers) {
9445
+ const introspected = await introspectDevQueueSubscriptions({
9446
+ moduleName: declaration.moduleName,
9447
+ subscriberName: declaration.name,
9448
+ variableName: declaration.variableName || "app",
9449
+ pythonBin: spawnCommand,
9450
+ cwd: workPath,
9451
+ env: introspectionEnv,
9452
+ integrations: queueIntegrations
9453
+ });
9454
+ apschedulerSubscribers.push(
9455
+ ...apschedulerSubscriberIdentities([
9456
+ { ...declaration, owners: introspected?.owners ?? [] }
9457
+ ])
9458
+ );
9459
+ }
9460
+ }
9461
+ if (apschedulerSubscribers.length > 0) {
9462
+ env.VERCEL_APSCHEDULER_SUBSCRIBERS = JSON.stringify(
9463
+ apschedulerSubscribers
9464
+ );
9465
+ const previewConfig = await getApschedulerPreviewConfig(workPath);
9466
+ if (previewConfig) {
9467
+ env.VERCEL_APSCHEDULER_PREVIEW_IDLE_TIMEOUT_SECONDS = String(
9468
+ previewConfig.idleTimeoutSeconds
9469
+ );
9470
+ } else {
9471
+ delete env.VERCEL_APSCHEDULER_PREVIEW_IDLE_TIMEOUT_SECONDS;
9472
+ }
9473
+ } else {
9474
+ delete env.VERCEL_APSCHEDULER_SUBSCRIBERS;
9475
+ delete env.VERCEL_APSCHEDULER_PREVIEW_IDLE_TIMEOUT_SECONDS;
9476
+ }
9271
9477
  if (legacyProject) {
9272
9478
  await installInjectedDevPackage(
9273
9479
  {
@@ -9292,19 +9498,29 @@ If you are using a virtual environment, activate it before running "vercel dev",
9292
9498
  }
9293
9499
  if (useQueueServing) {
9294
9500
  env.VERCEL_DEV_QUEUE_SERVING = "1";
9501
+ const subscriberName = getModuleEntrypointName({
9502
+ moduleName: modulePath,
9503
+ variableName: variableName || "app"
9504
+ });
9505
+ env.VERCEL_PYTHON_SUBSCRIBER_ID = subscriberName;
9295
9506
  const runtimeDir = (0, import_path13.join)(workPath, ".vercel", "python");
9296
- queueSubscriptions = await introspectDevQueueSubscriptions({
9507
+ const introspectionEnv = {
9508
+ ...env,
9509
+ PYTHONPATH: [runtimeDir, env.PYTHONPATH].filter(Boolean).join(import_path13.delimiter)
9510
+ };
9511
+ delete introspectionEnv.VERCEL_APSCHEDULER_SUBSCRIBERS;
9512
+ queueSubscriptions = (await introspectDevQueueSubscriptions({
9297
9513
  moduleName: modulePath,
9514
+ subscriberName,
9515
+ variableName: variableName || "app",
9298
9516
  pythonBin: spawnCommand,
9299
9517
  cwd: workPath,
9300
- env: {
9301
- ...env,
9302
- PYTHONPATH: [runtimeDir, env.PYTHONPATH].filter(Boolean).join(import_path13.delimiter)
9303
- },
9518
+ env: introspectionEnv,
9304
9519
  integrations: queueIntegrations
9305
- });
9520
+ }))?.subscriptions;
9306
9521
  } else {
9307
9522
  delete env.VERCEL_DEV_QUEUE_SERVING;
9523
+ delete env.VERCEL_PYTHON_SUBSCRIBER_ID;
9308
9524
  await installInjectedDevPackage(
9309
9525
  {
9310
9526
  name: "vercel-workers",
@@ -10793,7 +11009,7 @@ async function getDevSidecars({
10793
11009
  use: build2.use,
10794
11010
  src: subscriber.entrypoint,
10795
11011
  config: {
10796
- handlerFunction: subscriber.variableName,
11012
+ ...subscriber.variableName === void 0 ? {} : { handlerFunction: subscriber.variableName },
10797
11013
  pythonQueueSidecar: "subscriber"
10798
11014
  }
10799
11015
  },
@@ -11420,11 +11636,14 @@ var build = async ({
11420
11636
  projectDir: (0, import_path21.join)(workPath, entryDirectory),
11421
11637
  pipPlatformArgs
11422
11638
  });
11423
- const conditionalInjectedPackages = usedUvManagedInstall && !legacyWorkersProject ? await getConditionalInjectedPackages({
11639
+ const queueAdapterBootstrap = await getQueueAdapterBootstrap({
11424
11640
  pythonPackage,
11425
- env: baseEnv
11426
- }) : [];
11427
- for (const injectedPackage of conditionalInjectedPackages) {
11641
+ env: baseEnv,
11642
+ legacyWorkersProject,
11643
+ hasDeclaredSubscribers: await hasPyprojectSubscribers(workPath)
11644
+ });
11645
+ const queueAdapterInjectedPackages = usedUvManagedInstall ? queueAdapterBootstrap.injectedPackages : [];
11646
+ for (const injectedPackage of queueAdapterInjectedPackages) {
11428
11647
  await installInjectedPackage({
11429
11648
  ...injectedPackage,
11430
11649
  uv,
@@ -11465,7 +11684,7 @@ var build = async ({
11465
11684
  if (quirksResult.buildEnv) {
11466
11685
  Object.assign(pythonEnv, quirksResult.buildEnv);
11467
11686
  }
11468
- const queueIntegrations = legacyWorkersProject ? [] : await getQueueIntegrations({ pythonPackage });
11687
+ const queueIntegrations = queueAdapterBootstrap.integrations;
11469
11688
  const writeGeneratedQueueHandler = async (outputPath, declaration) => {
11470
11689
  const generatedPath = getGeneratedQueueHandlerPath(outputPath);
11471
11690
  await import_fs20.default.promises.mkdir((0, import_path21.dirname)((0, import_path21.join)(workPath, generatedPath)), {
@@ -11635,6 +11854,18 @@ var build = async ({
11635
11854
  ({ module: module2, installer, servingActivator }) => `${module2}:${installer}` + (servingActivator ? `:${servingActivator}` : "")
11636
11855
  ).join(",");
11637
11856
  }
11857
+ const apschedulerSubscribers = apschedulerSubscriberIdentities(subscribers);
11858
+ if (apschedulerSubscribers.length > 0) {
11859
+ lambdaEnv.VERCEL_APSCHEDULER_SUBSCRIBERS = JSON.stringify(
11860
+ apschedulerSubscribers
11861
+ );
11862
+ const previewConfig = await getApschedulerPreviewConfig(workPath);
11863
+ if (previewConfig) {
11864
+ lambdaEnv.VERCEL_APSCHEDULER_PREVIEW_IDLE_TIMEOUT_SECONDS = String(
11865
+ previewConfig.idleTimeoutSeconds
11866
+ );
11867
+ }
11868
+ }
11638
11869
  const globOptions = {
11639
11870
  cwd: workPath,
11640
11871
  ignore: config && typeof config.excludeFiles === "string" ? [...predefinedExcludes, config.excludeFiles] : predefinedExcludes
@@ -11971,7 +12202,9 @@ var build = async ({
11971
12202
  runtime: pythonVersion.runtime,
11972
12203
  ...lambdaOptions,
11973
12204
  architecture: target.architecture,
11974
- environment: lambdaEnv,
12205
+ environment: {
12206
+ ...lambdaEnv
12207
+ },
11975
12208
  supportsResponseStreaming: true
11976
12209
  });
11977
12210
  }
@@ -12009,7 +12242,12 @@ var build = async ({
12009
12242
  handler: `${handlerPyFilename}.vc_handler`,
12010
12243
  runtime: pythonVersion.runtime,
12011
12244
  architecture: target.architecture,
12012
- environment: lambdaEnv,
12245
+ environment: {
12246
+ ...lambdaEnv,
12247
+ // The runtime activates publish-side integrations before importing the
12248
+ // generated subscriber module, so the identity must already be present.
12249
+ VERCEL_PYTHON_SUBSCRIBER_ID: subscriber.name
12250
+ },
12013
12251
  experimentalTriggers,
12014
12252
  supportsResponseStreaming: true
12015
12253
  });
@@ -12021,6 +12259,11 @@ var build = async ({
12021
12259
  const consumer = getSubscriberConsumerName(declaration.name);
12022
12260
  const legacy = declaration.legacy;
12023
12261
  (0, import_assert.default)(legacy, "legacy subscriber declarations must carry legacy config");
12262
+ const { variableName } = declaration;
12263
+ (0, import_assert.default)(
12264
+ variableName,
12265
+ "legacy subscriber declarations must name an entrypoint object"
12266
+ );
12024
12267
  const experimentalTriggers = legacy.topics.map((topic) => ({
12025
12268
  type: "queue/v2beta",
12026
12269
  topic,
@@ -12035,7 +12278,7 @@ var build = async ({
12035
12278
  moduleName: declaration.moduleName,
12036
12279
  entrypoint: declaration.entrypoint,
12037
12280
  vendorDir,
12038
- variableName: declaration.variableName
12281
+ variableName
12039
12282
  })
12040
12283
  })
12041
12284
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "6.55.3",
3
+ "version": "6.56.0",
4
4
  "main": "./dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -35,18 +35,15 @@
35
35
  "smol-toml": "1.5.2",
36
36
  "vitest": "2.1.4",
37
37
  "which": "3.0.0",
38
+ "@vercel/build-utils": "14.0.3",
38
39
  "@vercel/error-utils": "2.2.1",
39
- "@vercel/build-utils": "14.0.2",
40
- "@vercel/python-runtime": "0.18.0"
40
+ "@vercel/python-runtime": "0.19.0"
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
  }