@vercel/python 5.0.7 → 5.0.8

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
@@ -3224,6 +3224,47 @@ function isFastapiEntrypoint(file) {
3224
3224
  return false;
3225
3225
  }
3226
3226
  }
3227
+ var FLASK_ENTRYPOINT_FILENAMES = ["app", "index", "server", "main"];
3228
+ var FLASK_ENTRYPOINT_DIRS = ["", "src", "app"];
3229
+ var FLASK_CONTENT_REGEX = /(from\s+flask\s+import\s+Flask|import\s+flask|Flask\s*\()/;
3230
+ var FLASK_CANDIDATE_ENTRYPOINTS = FLASK_ENTRYPOINT_FILENAMES.flatMap(
3231
+ (filename) => FLASK_ENTRYPOINT_DIRS.map(
3232
+ (dir) => import_path2.posix.join(dir, `${filename}.py`)
3233
+ )
3234
+ );
3235
+ function isFlaskEntrypoint(file) {
3236
+ try {
3237
+ const fsPath = file.fsPath;
3238
+ if (!fsPath)
3239
+ return false;
3240
+ const contents = import_fs2.default.readFileSync(fsPath, "utf8");
3241
+ return FLASK_CONTENT_REGEX.test(contents);
3242
+ } catch {
3243
+ return false;
3244
+ }
3245
+ }
3246
+ async function detectFlaskEntrypoint(workPath, configuredEntrypoint) {
3247
+ const entry = configuredEntrypoint.endsWith(".py") ? configuredEntrypoint : `${configuredEntrypoint}.py`;
3248
+ try {
3249
+ const fsFiles = await (0, import_build_utils3.glob)("**", workPath);
3250
+ if (fsFiles[entry])
3251
+ return entry;
3252
+ const candidates = FLASK_CANDIDATE_ENTRYPOINTS.filter(
3253
+ (c) => !!fsFiles[c]
3254
+ );
3255
+ if (candidates.length > 0) {
3256
+ const flaskEntrypoint = candidates.find(
3257
+ (c) => isFlaskEntrypoint(fsFiles[c])
3258
+ ) || candidates[0];
3259
+ (0, import_build_utils3.debug)(`Detected Flask entrypoint: ${flaskEntrypoint}`);
3260
+ return flaskEntrypoint;
3261
+ }
3262
+ return null;
3263
+ } catch {
3264
+ (0, import_build_utils3.debug)("Failed to discover entrypoint for Flask");
3265
+ return null;
3266
+ }
3267
+ }
3227
3268
  async function detectFastapiEntrypoint(workPath, configuredEntrypoint) {
3228
3269
  const entry = configuredEntrypoint.endsWith(".py") ? configuredEntrypoint : `${configuredEntrypoint}.py`;
3229
3270
  try {
@@ -3645,6 +3686,20 @@ var build = async ({
3645
3686
  message: `No FastAPI entrypoint found. Searched for: ${searchedList}`
3646
3687
  });
3647
3688
  }
3689
+ } else if (!fsFiles[entrypoint] && config?.framework === "flask") {
3690
+ const detected = await detectFlaskEntrypoint(workPath, entrypoint);
3691
+ if (detected) {
3692
+ (0, import_build_utils5.debug)(
3693
+ `Resolved Python entrypoint to "${detected}" (configured "${entrypoint}" not found).`
3694
+ );
3695
+ entrypoint = detected;
3696
+ } else {
3697
+ const searchedList = FLASK_CANDIDATE_ENTRYPOINTS.join(", ");
3698
+ throw new import_build_utils5.NowBuildError({
3699
+ code: "FLASK_ENTRYPOINT_NOT_FOUND",
3700
+ message: `No Flask entrypoint found. Searched for: ${searchedList}`
3701
+ });
3702
+ }
3648
3703
  }
3649
3704
  const entryDirectory = (0, import_path5.dirname)(entrypoint);
3650
3705
  const hasReqLocal = !!fsFiles[(0, import_path5.join)(entryDirectory, "requirements.txt")];
@@ -3915,6 +3970,12 @@ var shouldServe = (opts) => {
3915
3970
  return false;
3916
3971
  }
3917
3972
  return true;
3973
+ } else if (framework === "flask") {
3974
+ const requestPath = opts.requestPath.replace(/\/$/, "");
3975
+ if (requestPath.startsWith("api") && opts.hasMatched) {
3976
+ return false;
3977
+ }
3978
+ return true;
3918
3979
  }
3919
3980
  return defaultShouldServe(opts);
3920
3981
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "5.0.7",
3
+ "version": "5.0.8",
4
4
  "main": "./dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -20,7 +20,7 @@
20
20
  "@types/jest": "27.4.1",
21
21
  "@types/node": "14.18.33",
22
22
  "@types/which": "3.0.0",
23
- "@vercel/build-utils": "12.1.0",
23
+ "@vercel/build-utils": "12.1.2",
24
24
  "cross-env": "7.0.3",
25
25
  "execa": "^1.0.0",
26
26
  "fs-extra": "11.1.1",
package/vc_init.py CHANGED
@@ -434,6 +434,8 @@ if 'VERCEL_IPC_PATH' in os.environ:
434
434
 
435
435
  # Event to signal that the response has been fully sent
436
436
  response_done = threading.Event()
437
+ # Event to signal the ASGI app has fully completed (incl. background tasks)
438
+ app_done = threading.Event()
437
439
 
438
440
  # Propagate request context to background thread for logging & metrics
439
441
  request_context = storage.get()
@@ -488,6 +490,8 @@ if 'VERCEL_IPC_PATH' in os.environ:
488
490
  # Run ASGI app (includes background tasks)
489
491
  asgi_instance = app(scope, receive, send)
490
492
  await asgi_instance
493
+ # Mark app completion when the ASGI callable returns
494
+ app_done.set()
491
495
 
492
496
  asyncio.run(runner())
493
497
  except Exception:
@@ -504,10 +508,9 @@ if 'VERCEL_IPC_PATH' in os.environ:
504
508
  pass
505
509
  finally:
506
510
  # Always unblock the waiting thread to avoid hangs
507
- try:
508
- response_done.set()
509
- except Exception:
510
- pass
511
+ response_done.set()
512
+ # Ensure app completion is always signaled
513
+ app_done.set()
511
514
  if token is not None:
512
515
  storage.reset(token)
513
516
 
@@ -517,6 +520,8 @@ if 'VERCEL_IPC_PATH' in os.environ:
517
520
 
518
521
  # Wait until final body chunk has been flushed to client
519
522
  response_done.wait()
523
+ # Also wait until the ASGI app finishes (includes background tasks)
524
+ app_done.wait()
520
525
 
521
526
  if 'Handler' in locals():
522
527
  server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)