@vercel/python 8.0.0 → 9.2.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 +18 -7
- package/package.json +4 -4
- package/templates/vc_fastapi_static.py +58 -19
package/dist/index.js
CHANGED
|
@@ -5032,7 +5032,7 @@ 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.
|
|
5035
|
+
var VERCEL_RUNTIME_VERSION = "0.22.1";
|
|
5036
5036
|
var VERCEL_WORKERS_VERSION = "0.0.25";
|
|
5037
5037
|
|
|
5038
5038
|
// src/conditional-vendoring.ts
|
|
@@ -10311,15 +10311,23 @@ var import_execa9 = __toESM(require_execa());
|
|
|
10311
10311
|
var import_build_utils20 = require("@vercel/build-utils");
|
|
10312
10312
|
var scriptPath3 = (0, import_path17.join)(__dirname, "..", "templates", "vc_fastapi_static.py");
|
|
10313
10313
|
var STATIC_COLLECTION_WARNING = "Warning: FastAPI static file collection failed. Static files will not be served from the CDN.";
|
|
10314
|
-
async function getFastAPIStaticDiscovery(venvPath, entrypointAbs, variableName, env, workPath) {
|
|
10314
|
+
async function getFastAPIStaticDiscovery(venvPath, entrypointAbs, variableName, env, workPath, suppressMiddlewareMounts) {
|
|
10315
10315
|
const pythonPath = getVenvPythonBin(venvPath);
|
|
10316
10316
|
const outputDir = (0, import_path17.join)(workPath, ".vercel", "python");
|
|
10317
10317
|
const outputPath = (0, import_path17.join)(outputDir, "vc_fastapi_static_output.json");
|
|
10318
|
+
const moduleName = entrypointToModule((0, import_path17.relative)(workPath, entrypointAbs));
|
|
10318
10319
|
await import_fs17.default.promises.mkdir(outputDir, { recursive: true });
|
|
10319
10320
|
try {
|
|
10320
10321
|
const { stderr } = await (0, import_execa9.default)(
|
|
10321
10322
|
pythonPath,
|
|
10322
|
-
[
|
|
10323
|
+
[
|
|
10324
|
+
scriptPath3,
|
|
10325
|
+
variableName,
|
|
10326
|
+
outputPath,
|
|
10327
|
+
workPath,
|
|
10328
|
+
moduleName,
|
|
10329
|
+
suppressMiddlewareMounts ? "1" : "0"
|
|
10330
|
+
],
|
|
10323
10331
|
{ env, cwd: workPath }
|
|
10324
10332
|
);
|
|
10325
10333
|
if (stderr) {
|
|
@@ -10383,13 +10391,14 @@ function shadowSrc(bodies) {
|
|
|
10383
10391
|
return `^/((?:${bodies.join("|")})/?)$`;
|
|
10384
10392
|
}
|
|
10385
10393
|
var SHADOW_SRC_WRAPPER_LENGTH = shadowSrc([]).length;
|
|
10386
|
-
async function runFastAPICollectStatic(venvPath, workPath, env, outputStaticDir, entrypointAbs, variableName) {
|
|
10394
|
+
async function runFastAPICollectStatic(venvPath, workPath, env, outputStaticDir, entrypointAbs, variableName, suppressMiddlewareMounts = true) {
|
|
10387
10395
|
const { mounts, shadowRoutes } = await getFastAPIStaticDiscovery(
|
|
10388
10396
|
venvPath,
|
|
10389
10397
|
entrypointAbs,
|
|
10390
10398
|
variableName,
|
|
10391
10399
|
env,
|
|
10392
|
-
workPath
|
|
10400
|
+
workPath,
|
|
10401
|
+
suppressMiddlewareMounts
|
|
10393
10402
|
);
|
|
10394
10403
|
if (mounts.length === 0) {
|
|
10395
10404
|
(0, import_build_utils20.debug)("FastAPI: no StaticFiles mounts found, skipping");
|
|
@@ -10505,7 +10514,7 @@ function fastapiFallbackRoutes(discovery) {
|
|
|
10505
10514
|
});
|
|
10506
10515
|
}
|
|
10507
10516
|
function getFastAPICdnBundle(collectedMounts, workPath, fastapiConfig) {
|
|
10508
|
-
if (fastapiConfig?.static?.exclude
|
|
10517
|
+
if (fastapiConfig?.static?.exclude !== true)
|
|
10509
10518
|
return { excludePatterns: [], directoryStubs: {}, redirects: [] };
|
|
10510
10519
|
const excludePatterns = [];
|
|
10511
10520
|
const directoryStubs = {};
|
|
@@ -11462,7 +11471,9 @@ ${detail}`
|
|
|
11462
11471
|
pythonEnv,
|
|
11463
11472
|
outputStaticDir,
|
|
11464
11473
|
entrypointAbs,
|
|
11465
|
-
variableName
|
|
11474
|
+
variableName,
|
|
11475
|
+
// Suppress middleware mounts unless the user explicitly set cdn = true.
|
|
11476
|
+
staticCdn !== true
|
|
11466
11477
|
);
|
|
11467
11478
|
if (!fastapiStatic)
|
|
11468
11479
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/python",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@vercel/python-analysis": "0.14.0"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@vercel/build-utils": "14.
|
|
20
|
+
"@vercel/build-utils": "14.5.1"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@renovatebot/pep440": "4.2.1",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"smol-toml": "1.5.2",
|
|
39
39
|
"vitest": "4.1.10",
|
|
40
40
|
"which": "3.0.0",
|
|
41
|
+
"@vercel/build-utils": "14.5.1",
|
|
41
42
|
"@vercel/error-utils": "2.2.1",
|
|
42
|
-
"@vercel/
|
|
43
|
-
"@vercel/python-runtime": "0.21.1"
|
|
43
|
+
"@vercel/python-runtime": "0.22.1"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "node ../../utils/build-builder.mjs",
|
|
@@ -25,12 +25,18 @@ Writes a JSON object to the output file:
|
|
|
25
25
|
* app.mount(): routes declared BEFORE the mount win (evaluated in order).
|
|
26
26
|
* app.frontend(): all normal routes win (the build is low-priority).
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Mounts are suppressed (omitted from output) when the app or a mounted sub-app
|
|
29
|
+
has user-defined middleware, because CDN-served files bypass the middleware
|
|
30
|
+
stack. Suppression can be disabled by passing "0" as the sixth argument (set
|
|
31
|
+
when [tool.vercel.fastapi.static] cdn = true in pyproject.toml).
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
python <this_script> <variable_name> <output_path> <project_root> <module_name> <suppress_middleware_mounts>
|
|
29
35
|
"""
|
|
30
36
|
|
|
31
37
|
from __future__ import annotations
|
|
32
38
|
|
|
33
|
-
import importlib
|
|
39
|
+
import importlib
|
|
34
40
|
import json
|
|
35
41
|
import os
|
|
36
42
|
import re
|
|
@@ -398,7 +404,12 @@ def _subtree_shadow_route(prefix: str, mounts: list[StaticMount]) -> ShadowRoute
|
|
|
398
404
|
|
|
399
405
|
|
|
400
406
|
def _collect_mount(
|
|
401
|
-
route: Mount,
|
|
407
|
+
route: Mount,
|
|
408
|
+
prefix: str,
|
|
409
|
+
prior: Precedence,
|
|
410
|
+
*,
|
|
411
|
+
has_middleware: bool,
|
|
412
|
+
suppress_middleware_mounts: bool,
|
|
402
413
|
) -> tuple[list[StaticMount], list[ShadowRoute]]:
|
|
403
414
|
"""Discover one app.mount(): a StaticFiles mount serves from the CDN, a
|
|
404
415
|
Router/sub-app recurses, and a raw ASGI app owns its subtree opaquely.
|
|
@@ -407,11 +418,14 @@ def _collect_mount(
|
|
|
407
418
|
if prior.eclipses(url_prefix):
|
|
408
419
|
return [], []
|
|
409
420
|
|
|
421
|
+
if suppress_middleware_mounts:
|
|
422
|
+
has_middleware = has_middleware or bool(getattr(route.app, "user_middleware", []))
|
|
423
|
+
|
|
410
424
|
mounts: list[StaticMount] = []
|
|
411
425
|
shadow_routes: list[ShadowRoute] = []
|
|
412
426
|
|
|
413
427
|
static = StaticMount.from_route(route, prefix, frontend=False)
|
|
414
|
-
if static:
|
|
428
|
+
if static and not has_middleware:
|
|
415
429
|
mounts.append(static)
|
|
416
430
|
shadow_routes += prior.shadow_bodies(static)
|
|
417
431
|
shadow_routes += _divergent_url_shadows(static, html=route.app.html)
|
|
@@ -427,7 +441,13 @@ def _collect_mount(
|
|
|
427
441
|
# Recurse for the sub-app's own StaticFiles mounts, then shadow the
|
|
428
442
|
# rest of its subtree to the Lambda.
|
|
429
443
|
sub_prefix = prefix + route.path.rstrip("/")
|
|
430
|
-
sub_mounts, sub_shadow = collect(
|
|
444
|
+
sub_mounts, sub_shadow = collect(
|
|
445
|
+
sub_router,
|
|
446
|
+
sub_prefix,
|
|
447
|
+
prior,
|
|
448
|
+
has_middleware=has_middleware,
|
|
449
|
+
suppress_middleware_mounts=suppress_middleware_mounts,
|
|
450
|
+
)
|
|
431
451
|
mounts.extend(sub_mounts)
|
|
432
452
|
shadow_routes += sub_shadow
|
|
433
453
|
shadow_routes.append(_subtree_shadow_route(sub_prefix, sub_mounts))
|
|
@@ -445,6 +465,9 @@ def collect(
|
|
|
445
465
|
router: Router,
|
|
446
466
|
prefix: str = "",
|
|
447
467
|
prior: Precedence | None = None,
|
|
468
|
+
*,
|
|
469
|
+
has_middleware: bool,
|
|
470
|
+
suppress_middleware_mounts: bool,
|
|
448
471
|
) -> tuple[list[StaticMount], list[ShadowRoute]]:
|
|
449
472
|
"""Walk the route table for (static mounts to copy, shadow routes).
|
|
450
473
|
|
|
@@ -463,7 +486,11 @@ def collect(
|
|
|
463
486
|
return frozenset(m) if m else None
|
|
464
487
|
|
|
465
488
|
def collect_mount(mount_route: Mount) -> None:
|
|
466
|
-
sub_mounts, sub_shadow = _collect_mount(
|
|
489
|
+
sub_mounts, sub_shadow = _collect_mount(
|
|
490
|
+
mount_route, prefix, prior,
|
|
491
|
+
has_middleware=has_middleware,
|
|
492
|
+
suppress_middleware_mounts=suppress_middleware_mounts,
|
|
493
|
+
)
|
|
467
494
|
mounts.extend(sub_mounts)
|
|
468
495
|
shadow_routes.extend(sub_shadow)
|
|
469
496
|
|
|
@@ -499,7 +526,7 @@ def collect(
|
|
|
499
526
|
|
|
500
527
|
# app.include_router() with a frontend build.
|
|
501
528
|
for ctx in _effective_low_priority_routes(route):
|
|
502
|
-
if _frontend_has_dependencies(ctx):
|
|
529
|
+
if suppress_middleware_mounts and (_frontend_has_dependencies(ctx) or has_middleware):
|
|
503
530
|
continue
|
|
504
531
|
for r in ctx.original_route.routes:
|
|
505
532
|
if m := StaticMount.from_route(
|
|
@@ -509,7 +536,7 @@ def collect(
|
|
|
509
536
|
|
|
510
537
|
# app.frontend() builds.
|
|
511
538
|
for group in _frontend_groups(router):
|
|
512
|
-
if _frontend_has_dependencies(group):
|
|
539
|
+
if suppress_middleware_mounts and (_frontend_has_dependencies(group) or has_middleware):
|
|
513
540
|
continue
|
|
514
541
|
for route in group.routes:
|
|
515
542
|
if m := StaticMount.from_route(route, prefix, frontend=True):
|
|
@@ -585,17 +612,21 @@ def write_output(output_path: str, discovery: Output) -> None:
|
|
|
585
612
|
json.dump(discovery.to_dict(), f)
|
|
586
613
|
|
|
587
614
|
|
|
588
|
-
def discover(
|
|
589
|
-
"""Import the entrypoint and walk its app's router
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
615
|
+
def discover(variable_name: str, project_root: str, module_name: str, suppress_middleware_mounts: bool) -> Output:
|
|
616
|
+
"""Import the entrypoint as its real module and walk its app's router.
|
|
617
|
+
|
|
618
|
+
The project root is put on sys.path so importlib.import_module loads the
|
|
619
|
+
entrypoint by its dotted name (as the deployed runtime does), which is what
|
|
620
|
+
makes relative and first-party absolute imports resolve. Returns empty on
|
|
621
|
+
any failure.
|
|
622
|
+
"""
|
|
623
|
+
if project_root and project_root not in sys.path:
|
|
624
|
+
sys.path.insert(0, project_root)
|
|
593
625
|
|
|
594
|
-
mod = importlib.util.module_from_spec(spec)
|
|
595
626
|
try:
|
|
596
|
-
|
|
627
|
+
mod = importlib.import_module(module_name)
|
|
597
628
|
except Exception as exc:
|
|
598
|
-
print(f"vc_fastapi_static:
|
|
629
|
+
print(f"vc_fastapi_static: import failed: {exc}", file=sys.stderr)
|
|
599
630
|
return Output()
|
|
600
631
|
|
|
601
632
|
app = getattr(mod, variable_name, None)
|
|
@@ -603,13 +634,21 @@ def discover(entrypoint_abs: str, variable_name: str) -> Output:
|
|
|
603
634
|
if router is None:
|
|
604
635
|
return Output()
|
|
605
636
|
|
|
606
|
-
|
|
637
|
+
# If the top-level app has middleware, no mounts can be promoted —
|
|
638
|
+
# CDN-served files would bypass the middleware stack entirely.
|
|
639
|
+
if suppress_middleware_mounts and getattr(app, "user_middleware", []):
|
|
640
|
+
return Output()
|
|
641
|
+
|
|
642
|
+
mounts, shadow_routes = collect(
|
|
643
|
+
router, has_middleware=False, suppress_middleware_mounts=suppress_middleware_mounts
|
|
644
|
+
)
|
|
607
645
|
return Output(mounts=mounts, shadowRoutes=shadow_routes)
|
|
608
646
|
|
|
609
647
|
|
|
610
648
|
def main() -> None:
|
|
611
|
-
|
|
612
|
-
|
|
649
|
+
variable_name, output_path, project_root, module_name = sys.argv[1:5]
|
|
650
|
+
suppress_middleware_mounts = sys.argv[5] != "0"
|
|
651
|
+
write_output(output_path, discover(variable_name, project_root, module_name, suppress_middleware_mounts))
|
|
613
652
|
|
|
614
653
|
|
|
615
654
|
if __name__ == "__main__":
|