@vercel/python 9.0.0 → 10.0.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.
- package/dist/index.js +16 -6
- package/package.json +3 -3
- package/templates/vc_fastapi_static.py +44 -10
package/dist/index.js
CHANGED
|
@@ -10311,7 +10311,7 @@ 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");
|
|
@@ -10320,7 +10320,14 @@ async function getFastAPIStaticDiscovery(venvPath, entrypointAbs, variableName,
|
|
|
10320
10320
|
try {
|
|
10321
10321
|
const { stderr } = await (0, import_execa9.default)(
|
|
10322
10322
|
pythonPath,
|
|
10323
|
-
[
|
|
10323
|
+
[
|
|
10324
|
+
scriptPath3,
|
|
10325
|
+
variableName,
|
|
10326
|
+
outputPath,
|
|
10327
|
+
workPath,
|
|
10328
|
+
moduleName,
|
|
10329
|
+
suppressMiddlewareMounts ? "1" : "0"
|
|
10330
|
+
],
|
|
10324
10331
|
{ env, cwd: workPath }
|
|
10325
10332
|
);
|
|
10326
10333
|
if (stderr) {
|
|
@@ -10384,13 +10391,14 @@ function shadowSrc(bodies) {
|
|
|
10384
10391
|
return `^/((?:${bodies.join("|")})/?)$`;
|
|
10385
10392
|
}
|
|
10386
10393
|
var SHADOW_SRC_WRAPPER_LENGTH = shadowSrc([]).length;
|
|
10387
|
-
async function runFastAPICollectStatic(venvPath, workPath, env, outputStaticDir, entrypointAbs, variableName) {
|
|
10394
|
+
async function runFastAPICollectStatic(venvPath, workPath, env, outputStaticDir, entrypointAbs, variableName, suppressMiddlewareMounts = true) {
|
|
10388
10395
|
const { mounts, shadowRoutes } = await getFastAPIStaticDiscovery(
|
|
10389
10396
|
venvPath,
|
|
10390
10397
|
entrypointAbs,
|
|
10391
10398
|
variableName,
|
|
10392
10399
|
env,
|
|
10393
|
-
workPath
|
|
10400
|
+
workPath,
|
|
10401
|
+
suppressMiddlewareMounts
|
|
10394
10402
|
);
|
|
10395
10403
|
if (mounts.length === 0) {
|
|
10396
10404
|
(0, import_build_utils20.debug)("FastAPI: no StaticFiles mounts found, skipping");
|
|
@@ -10506,7 +10514,7 @@ function fastapiFallbackRoutes(discovery) {
|
|
|
10506
10514
|
});
|
|
10507
10515
|
}
|
|
10508
10516
|
function getFastAPICdnBundle(collectedMounts, workPath, fastapiConfig) {
|
|
10509
|
-
if (fastapiConfig?.static?.exclude
|
|
10517
|
+
if (fastapiConfig?.static?.exclude !== true)
|
|
10510
10518
|
return { excludePatterns: [], directoryStubs: {}, redirects: [] };
|
|
10511
10519
|
const excludePatterns = [];
|
|
10512
10520
|
const directoryStubs = {};
|
|
@@ -11463,7 +11471,9 @@ ${detail}`
|
|
|
11463
11471
|
pythonEnv,
|
|
11464
11472
|
outputStaticDir,
|
|
11465
11473
|
entrypointAbs,
|
|
11466
|
-
variableName
|
|
11474
|
+
variableName,
|
|
11475
|
+
// Suppress middleware mounts unless the user explicitly set cdn = true.
|
|
11476
|
+
staticCdn !== true
|
|
11467
11477
|
);
|
|
11468
11478
|
if (!fastapiStatic)
|
|
11469
11479
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/python",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.1",
|
|
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.6.1"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@renovatebot/pep440": "4.2.1",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"smol-toml": "1.5.2",
|
|
39
39
|
"vitest": "4.1.10",
|
|
40
40
|
"which": "3.0.0",
|
|
41
|
-
"@vercel/build-utils": "14.
|
|
41
|
+
"@vercel/build-utils": "14.6.1",
|
|
42
42
|
"@vercel/error-utils": "2.2.1",
|
|
43
43
|
"@vercel/python-runtime": "0.22.1"
|
|
44
44
|
},
|
|
@@ -25,8 +25,13 @@ 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
|
+
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
|
+
|
|
28
33
|
Usage:
|
|
29
|
-
python <this_script> <variable_name> <output_path> <project_root> <module_name>
|
|
34
|
+
python <this_script> <variable_name> <output_path> <project_root> <module_name> <suppress_middleware_mounts>
|
|
30
35
|
"""
|
|
31
36
|
|
|
32
37
|
from __future__ import annotations
|
|
@@ -399,7 +404,12 @@ def _subtree_shadow_route(prefix: str, mounts: list[StaticMount]) -> ShadowRoute
|
|
|
399
404
|
|
|
400
405
|
|
|
401
406
|
def _collect_mount(
|
|
402
|
-
route: Mount,
|
|
407
|
+
route: Mount,
|
|
408
|
+
prefix: str,
|
|
409
|
+
prior: Precedence,
|
|
410
|
+
*,
|
|
411
|
+
has_middleware: bool,
|
|
412
|
+
suppress_middleware_mounts: bool,
|
|
403
413
|
) -> tuple[list[StaticMount], list[ShadowRoute]]:
|
|
404
414
|
"""Discover one app.mount(): a StaticFiles mount serves from the CDN, a
|
|
405
415
|
Router/sub-app recurses, and a raw ASGI app owns its subtree opaquely.
|
|
@@ -408,11 +418,14 @@ def _collect_mount(
|
|
|
408
418
|
if prior.eclipses(url_prefix):
|
|
409
419
|
return [], []
|
|
410
420
|
|
|
421
|
+
if suppress_middleware_mounts:
|
|
422
|
+
has_middleware = has_middleware or bool(getattr(route.app, "user_middleware", []))
|
|
423
|
+
|
|
411
424
|
mounts: list[StaticMount] = []
|
|
412
425
|
shadow_routes: list[ShadowRoute] = []
|
|
413
426
|
|
|
414
427
|
static = StaticMount.from_route(route, prefix, frontend=False)
|
|
415
|
-
if static:
|
|
428
|
+
if static and not has_middleware:
|
|
416
429
|
mounts.append(static)
|
|
417
430
|
shadow_routes += prior.shadow_bodies(static)
|
|
418
431
|
shadow_routes += _divergent_url_shadows(static, html=route.app.html)
|
|
@@ -428,7 +441,13 @@ def _collect_mount(
|
|
|
428
441
|
# Recurse for the sub-app's own StaticFiles mounts, then shadow the
|
|
429
442
|
# rest of its subtree to the Lambda.
|
|
430
443
|
sub_prefix = prefix + route.path.rstrip("/")
|
|
431
|
-
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
|
+
)
|
|
432
451
|
mounts.extend(sub_mounts)
|
|
433
452
|
shadow_routes += sub_shadow
|
|
434
453
|
shadow_routes.append(_subtree_shadow_route(sub_prefix, sub_mounts))
|
|
@@ -446,6 +465,9 @@ def collect(
|
|
|
446
465
|
router: Router,
|
|
447
466
|
prefix: str = "",
|
|
448
467
|
prior: Precedence | None = None,
|
|
468
|
+
*,
|
|
469
|
+
has_middleware: bool,
|
|
470
|
+
suppress_middleware_mounts: bool,
|
|
449
471
|
) -> tuple[list[StaticMount], list[ShadowRoute]]:
|
|
450
472
|
"""Walk the route table for (static mounts to copy, shadow routes).
|
|
451
473
|
|
|
@@ -464,7 +486,11 @@ def collect(
|
|
|
464
486
|
return frozenset(m) if m else None
|
|
465
487
|
|
|
466
488
|
def collect_mount(mount_route: Mount) -> None:
|
|
467
|
-
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
|
+
)
|
|
468
494
|
mounts.extend(sub_mounts)
|
|
469
495
|
shadow_routes.extend(sub_shadow)
|
|
470
496
|
|
|
@@ -500,7 +526,7 @@ def collect(
|
|
|
500
526
|
|
|
501
527
|
# app.include_router() with a frontend build.
|
|
502
528
|
for ctx in _effective_low_priority_routes(route):
|
|
503
|
-
if _frontend_has_dependencies(ctx):
|
|
529
|
+
if suppress_middleware_mounts and (_frontend_has_dependencies(ctx) or has_middleware):
|
|
504
530
|
continue
|
|
505
531
|
for r in ctx.original_route.routes:
|
|
506
532
|
if m := StaticMount.from_route(
|
|
@@ -510,7 +536,7 @@ def collect(
|
|
|
510
536
|
|
|
511
537
|
# app.frontend() builds.
|
|
512
538
|
for group in _frontend_groups(router):
|
|
513
|
-
if _frontend_has_dependencies(group):
|
|
539
|
+
if suppress_middleware_mounts and (_frontend_has_dependencies(group) or has_middleware):
|
|
514
540
|
continue
|
|
515
541
|
for route in group.routes:
|
|
516
542
|
if m := StaticMount.from_route(route, prefix, frontend=True):
|
|
@@ -586,7 +612,7 @@ def write_output(output_path: str, discovery: Output) -> None:
|
|
|
586
612
|
json.dump(discovery.to_dict(), f)
|
|
587
613
|
|
|
588
614
|
|
|
589
|
-
def discover(variable_name: str, project_root: str, module_name: str) -> Output:
|
|
615
|
+
def discover(variable_name: str, project_root: str, module_name: str, suppress_middleware_mounts: bool) -> Output:
|
|
590
616
|
"""Import the entrypoint as its real module and walk its app's router.
|
|
591
617
|
|
|
592
618
|
The project root is put on sys.path so importlib.import_module loads the
|
|
@@ -608,13 +634,21 @@ def discover(variable_name: str, project_root: str, module_name: str) -> Output:
|
|
|
608
634
|
if router is None:
|
|
609
635
|
return Output()
|
|
610
636
|
|
|
611
|
-
|
|
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
|
+
)
|
|
612
645
|
return Output(mounts=mounts, shadowRoutes=shadow_routes)
|
|
613
646
|
|
|
614
647
|
|
|
615
648
|
def main() -> None:
|
|
616
649
|
variable_name, output_path, project_root, module_name = sys.argv[1:5]
|
|
617
|
-
|
|
650
|
+
suppress_middleware_mounts = sys.argv[5] != "0"
|
|
651
|
+
write_output(output_path, discover(variable_name, project_root, module_name, suppress_middleware_mounts))
|
|
618
652
|
|
|
619
653
|
|
|
620
654
|
if __name__ == "__main__":
|