vercel-runtime 0.6.0__tar.gz → 0.7.0__tar.gz
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.
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/PKG-INFO +1 -1
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/pyproject.toml +1 -1
- vercel_runtime-0.7.0/src/vercel_runtime/dev.py +421 -0
- vercel_runtime-0.7.0/src/vercel_runtime/resolver.py +92 -0
- vercel_runtime-0.7.0/src/vercel_runtime/routing.py +177 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/vc_init.py +45 -182
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/LICENSE.txt +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/_compat.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/_termui_impl.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/_textwrap.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/_winconsole.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/core.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/decorators.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/exceptions.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/formatting.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/globals.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/parser.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/py.typed +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/shell_completion.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/termui.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/testing.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/types.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/click/utils.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/colorama/LICENSE.txt +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/colorama/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/colorama/ansi.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/colorama/ansitowin32.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/colorama/initialise.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/colorama/win32.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/colorama/winterm.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/colorama.pyi +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/LICENSE.txt +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/_abnf.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/_connection.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/_events.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/_headers.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/_readers.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/_receivebuffer.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/_state.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/_util.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/_version.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/_writers.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/h11/py.typed +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/markupsafe/LICENSE.txt +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/markupsafe/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/markupsafe/_native.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/markupsafe/_speedups.c +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/markupsafe/_speedups.pyi +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/markupsafe/py.typed +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/LICENSE.md +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/__main__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/_compat.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/_subprocess.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/_types.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/config.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/importer.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/lifespan/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/lifespan/off.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/lifespan/on.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/logging.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/loops/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/loops/asyncio.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/loops/auto.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/loops/uvloop.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/main.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/middleware/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/middleware/asgi2.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/middleware/message_logger.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/middleware/proxy_headers.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/middleware/wsgi.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/http/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/http/auto.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/http/flow_control.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/http/h11_impl.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/http/httptools_impl.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/utils.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/websockets/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/websockets/auto.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/websockets/websockets_impl.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/websockets/websockets_sansio_impl.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/protocols/websockets/wsproto_impl.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/py.typed +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/server.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/supervisors/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/supervisors/basereload.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/supervisors/multiprocess.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/supervisors/statreload.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/supervisors/watchfilesreload.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/uvicorn/workers.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/vendor.txt +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/ICON_LICENSE.md +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/LICENSE.txt +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/_internal.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/_reloader.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/accept.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/auth.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/cache_control.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/csp.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/etag.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/file_storage.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/headers.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/mixins.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/range.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/datastructures/structures.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/debug/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/debug/console.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/debug/repr.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/debug/shared/ICON_LICENSE.md +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/debug/shared/console.png +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/debug/shared/debugger.js +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/debug/shared/less.png +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/debug/shared/more.png +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/debug/shared/style.css +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/debug/tbtools.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/exceptions.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/formparser.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/http.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/local.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/middleware/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/middleware/dispatcher.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/middleware/http_proxy.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/middleware/lint.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/middleware/profiler.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/middleware/proxy_fix.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/middleware/shared_data.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/py.typed +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/routing/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/routing/converters.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/routing/exceptions.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/routing/map.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/routing/matcher.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/routing/rules.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/sansio/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/sansio/http.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/sansio/multipart.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/sansio/request.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/sansio/response.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/sansio/utils.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/security.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/serving.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/test.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/testapp.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/urls.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/user_agent.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/utils.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/wrappers/__init__.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/wrappers/request.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/wrappers/response.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/_vendor/werkzeug/wsgi.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/asgi.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/crons.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/headers.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/utils.py +0 -0
- {vercel_runtime-0.6.0 → vercel_runtime-0.7.0}/src/vercel_runtime/workers.py +0 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import logging.config
|
|
5
|
+
import mimetypes
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
9
|
+
|
|
10
|
+
from vercel_runtime._vendor import uvicorn as vendored_uvicorn
|
|
11
|
+
from vercel_runtime._vendor.uvicorn.config import (
|
|
12
|
+
LOGGING_CONFIG as UVICORN_LOGGING_CONFIG,
|
|
13
|
+
)
|
|
14
|
+
from vercel_runtime._vendor.werkzeug.serving import run_simple
|
|
15
|
+
from vercel_runtime.resolver import detect_app_type, import_module, resolve_app
|
|
16
|
+
from vercel_runtime.routing import (
|
|
17
|
+
apply_service_route_prefix_to_asgi_scope,
|
|
18
|
+
strip_service_route_prefix,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Awaitable, Callable
|
|
23
|
+
from wsgiref.types import WSGIApplication
|
|
24
|
+
|
|
25
|
+
from vercel_runtime.asgi import ASGI
|
|
26
|
+
|
|
27
|
+
_NO_COLOR = "NO_COLOR" in os.environ
|
|
28
|
+
|
|
29
|
+
_log = logging.getLogger("vercel.dev")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Used to split DEBUG-WARNING -> stdout and ERROR-CRITICAL -> stderr
|
|
33
|
+
class _MaxLevelFilter(logging.Filter):
|
|
34
|
+
def __init__(self, max_level: int) -> None:
|
|
35
|
+
super().__init__()
|
|
36
|
+
self.max_level = max_level
|
|
37
|
+
|
|
38
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
39
|
+
return record.levelno <= self.max_level
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _build_log_config(
|
|
43
|
+
loggers: dict[str, Any],
|
|
44
|
+
_filter_ref: object | None = None,
|
|
45
|
+
) -> dict[str, Any]:
|
|
46
|
+
if _filter_ref is None:
|
|
47
|
+
_filter_ref = "vercel_runtime.dev._MaxLevelFilter"
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
"version": 1,
|
|
51
|
+
"disable_existing_loggers": False,
|
|
52
|
+
"filters": {
|
|
53
|
+
"max_warning": {
|
|
54
|
+
"()": _filter_ref,
|
|
55
|
+
"max_level": logging.WARNING,
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"handlers": {
|
|
59
|
+
"stdout": {
|
|
60
|
+
"class": "logging.StreamHandler",
|
|
61
|
+
"stream": "ext://sys.stdout",
|
|
62
|
+
"filters": ["max_warning"],
|
|
63
|
+
},
|
|
64
|
+
"stderr": {
|
|
65
|
+
"class": "logging.StreamHandler",
|
|
66
|
+
"stream": "ext://sys.stderr",
|
|
67
|
+
"level": "ERROR",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
"loggers": loggers,
|
|
71
|
+
"root": {
|
|
72
|
+
"handlers": ["stdout", "stderr"],
|
|
73
|
+
"level": "INFO",
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _setup_server_log_routing(logger_name: str | None = None) -> None:
|
|
79
|
+
loggers: dict[str, Any] = {}
|
|
80
|
+
|
|
81
|
+
if logger_name:
|
|
82
|
+
loggers[logger_name] = {
|
|
83
|
+
"handlers": ["stdout", "stderr"],
|
|
84
|
+
"level": "INFO",
|
|
85
|
+
"propagate": False,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
logging.config.dictConfig(
|
|
89
|
+
_build_log_config(
|
|
90
|
+
loggers=loggers,
|
|
91
|
+
_filter_ref=_MaxLevelFilter,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _build_uvicorn_log_config(
|
|
97
|
+
default_fmt: dict[str, Any] | None = None,
|
|
98
|
+
access_fmt: dict[str, Any] | None = None,
|
|
99
|
+
) -> dict[str, Any]:
|
|
100
|
+
uvicorn_fmts: dict[str, Any] = UVICORN_LOGGING_CONFIG["formatters"]
|
|
101
|
+
|
|
102
|
+
cfg = _build_log_config(
|
|
103
|
+
loggers={
|
|
104
|
+
"uvicorn": {
|
|
105
|
+
"handlers": ["stdout", "stderr"],
|
|
106
|
+
"level": "INFO",
|
|
107
|
+
"propagate": False,
|
|
108
|
+
},
|
|
109
|
+
"uvicorn.error": {"level": "INFO"},
|
|
110
|
+
"uvicorn.access": {
|
|
111
|
+
"handlers": ["access"],
|
|
112
|
+
"level": "INFO",
|
|
113
|
+
"propagate": False,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if default_fmt is None:
|
|
119
|
+
default_fmt = {
|
|
120
|
+
**uvicorn_fmts["default"],
|
|
121
|
+
"use_colors": not _NO_COLOR,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if access_fmt is None:
|
|
125
|
+
access_fmt = {
|
|
126
|
+
**uvicorn_fmts["access"],
|
|
127
|
+
"use_colors": not _NO_COLOR,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
cfg["formatters"] = {"default": default_fmt, "access": access_fmt}
|
|
131
|
+
cfg["handlers"]["stdout"]["formatter"] = "default"
|
|
132
|
+
cfg["handlers"]["stderr"]["formatter"] = "default"
|
|
133
|
+
cfg["handlers"]["access"] = {
|
|
134
|
+
"class": "logging.StreamHandler",
|
|
135
|
+
"stream": "ext://sys.stdout",
|
|
136
|
+
"formatter": "access",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return cfg
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _patch_fastapi_cli_log_config() -> None:
|
|
143
|
+
try:
|
|
144
|
+
import fastapi_cli.cli as _fcli_cli # type: ignore[import-not-found] # noqa: PLC0415 # pyright: ignore[reportMissingImports]
|
|
145
|
+
import fastapi_cli.utils.cli as _fcli # type: ignore[import-not-found] # noqa: PLC0415 # pyright: ignore[reportMissingImports]
|
|
146
|
+
|
|
147
|
+
orig_get_config = cast(
|
|
148
|
+
"Callable[[], dict[str, Any]]",
|
|
149
|
+
_fcli.get_uvicorn_log_config, # pyright: ignore[reportUnknownMemberType]
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# we want to ensure that attr exists
|
|
153
|
+
# because we're going to patch it as well
|
|
154
|
+
_fcli_cli.get_uvicorn_log_config # noqa: B018 # pyright: ignore[reportUnknownMemberType]
|
|
155
|
+
except (ImportError, AttributeError):
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
def _get_routed_config() -> dict[str, Any]:
|
|
159
|
+
orig = orig_get_config()
|
|
160
|
+
return _build_uvicorn_log_config(
|
|
161
|
+
default_fmt={
|
|
162
|
+
"()": "fastapi_cli.utils.cli.CustomFormatter",
|
|
163
|
+
"fmt": orig["formatters"]["default"].get(
|
|
164
|
+
"fmt", "%(levelprefix)s %(message)s"
|
|
165
|
+
),
|
|
166
|
+
"use_colors": orig["formatters"]["default"].get("use_colors"),
|
|
167
|
+
},
|
|
168
|
+
access_fmt={
|
|
169
|
+
"()": "fastapi_cli.utils.cli.CustomFormatter",
|
|
170
|
+
"fmt": orig["formatters"]["access"].get(
|
|
171
|
+
"fmt",
|
|
172
|
+
"%(levelprefix)s %(client_addr)s - "
|
|
173
|
+
"'%(request_line)s' %(status_code)s",
|
|
174
|
+
),
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
_fcli.get_uvicorn_log_config = _get_routed_config
|
|
179
|
+
_fcli_cli.get_uvicorn_log_config = _get_routed_config
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
PUBLIC_DIR = "public"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _build_static_asgi_app() -> ASGI | None:
|
|
186
|
+
static_files_cls: type[Any] | None = None
|
|
187
|
+
try:
|
|
188
|
+
from fastapi.staticfiles import ( # type: ignore[import-not-found] # noqa: PLC0415 # pyright: ignore[reportMissingImports]
|
|
189
|
+
StaticFiles, # pyright: ignore[reportUnknownVariableType]
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
static_files_cls = StaticFiles # pyright: ignore[reportUnknownVariableType]
|
|
193
|
+
except Exception:
|
|
194
|
+
try:
|
|
195
|
+
from starlette.staticfiles import ( # type: ignore[import-not-found] # noqa: PLC0415 # pyright: ignore[reportMissingImports]
|
|
196
|
+
StaticFiles, # pyright: ignore[reportUnknownVariableType]
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
static_files_cls = StaticFiles # pyright: ignore[reportUnknownVariableType]
|
|
200
|
+
except Exception:
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
if static_files_cls is not None:
|
|
204
|
+
try:
|
|
205
|
+
try:
|
|
206
|
+
return cast(
|
|
207
|
+
"ASGI",
|
|
208
|
+
static_files_cls(directory=PUBLIC_DIR, check_dir=False),
|
|
209
|
+
)
|
|
210
|
+
except TypeError:
|
|
211
|
+
return cast("ASGI", static_files_cls(directory=PUBLIC_DIR))
|
|
212
|
+
except Exception:
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _is_safe_file(base_dir: str, target: str) -> bool:
|
|
219
|
+
try:
|
|
220
|
+
base = os.path.realpath(base_dir)
|
|
221
|
+
tgt = os.path.realpath(target)
|
|
222
|
+
return (
|
|
223
|
+
tgt == base or tgt.startswith(base + os.sep)
|
|
224
|
+
) and os.path.isfile(tgt)
|
|
225
|
+
except Exception:
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _static_wsgi_app(
|
|
230
|
+
environ: dict[str, Any],
|
|
231
|
+
start_response: Callable[..., Any],
|
|
232
|
+
) -> list[bytes]:
|
|
233
|
+
if environ.get("REQUEST_METHOD", "GET") not in ("GET", "HEAD"):
|
|
234
|
+
return _not_found(start_response)
|
|
235
|
+
|
|
236
|
+
req_path = environ.get("PATH_INFO", "/") or "/"
|
|
237
|
+
safe = os.path.normpath(req_path).lstrip("/")
|
|
238
|
+
full = os.path.join(PUBLIC_DIR, safe)
|
|
239
|
+
if not _is_safe_file(PUBLIC_DIR, full):
|
|
240
|
+
return _not_found(start_response)
|
|
241
|
+
|
|
242
|
+
ctype, _ = mimetypes.guess_type(full)
|
|
243
|
+
headers = [("Content-Type", ctype or "application/octet-stream")]
|
|
244
|
+
try:
|
|
245
|
+
if environ.get("REQUEST_METHOD") == "HEAD":
|
|
246
|
+
start_response("200 OK", headers)
|
|
247
|
+
return []
|
|
248
|
+
with open(full, "rb") as f:
|
|
249
|
+
data = f.read()
|
|
250
|
+
headers.append(("Content-Length", str(len(data))))
|
|
251
|
+
start_response("200 OK", headers)
|
|
252
|
+
return [data]
|
|
253
|
+
except Exception:
|
|
254
|
+
return _not_found(start_response)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _not_found(
|
|
258
|
+
start_response: Callable[..., Any],
|
|
259
|
+
) -> list[bytes]:
|
|
260
|
+
start_response(
|
|
261
|
+
"404 Not Found",
|
|
262
|
+
[("Content-Type", "text/plain; charset=utf-8")],
|
|
263
|
+
)
|
|
264
|
+
return [b"Not Found"]
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
static_asgi = _build_static_asgi_app()
|
|
268
|
+
_asgi_user_app: ASGI | None = None
|
|
269
|
+
_wsgi_user_app: WSGIApplication | None = None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
async def asgi_app(
|
|
273
|
+
scope: dict[str, Any],
|
|
274
|
+
receive: Callable[[], Awaitable[Any]],
|
|
275
|
+
send: Callable[[Any], Awaitable[None]],
|
|
276
|
+
) -> None:
|
|
277
|
+
effective_scope = dict(scope)
|
|
278
|
+
apply_service_route_prefix_to_asgi_scope(effective_scope)
|
|
279
|
+
|
|
280
|
+
if static_asgi is not None and effective_scope.get("type") == "http":
|
|
281
|
+
req_path = effective_scope.get("path", "/") or "/"
|
|
282
|
+
safe = os.path.normpath(req_path).lstrip("/")
|
|
283
|
+
full = os.path.join(PUBLIC_DIR, safe)
|
|
284
|
+
try:
|
|
285
|
+
base = os.path.realpath(PUBLIC_DIR)
|
|
286
|
+
target = os.path.realpath(full)
|
|
287
|
+
if (
|
|
288
|
+
target == base or target.startswith(base + os.path.sep)
|
|
289
|
+
) and os.path.isfile(target):
|
|
290
|
+
await static_asgi(effective_scope, receive, send)
|
|
291
|
+
return
|
|
292
|
+
except Exception:
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
assert _asgi_user_app is not None
|
|
296
|
+
await _asgi_user_app(effective_scope, receive, send)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def wsgi_app(
|
|
300
|
+
environ: dict[str, Any],
|
|
301
|
+
start_response: Callable[..., Any],
|
|
302
|
+
) -> Any:
|
|
303
|
+
path_info, matched_prefix = strip_service_route_prefix(
|
|
304
|
+
environ.get("PATH_INFO", "/") or "/"
|
|
305
|
+
)
|
|
306
|
+
environ["PATH_INFO"] = path_info
|
|
307
|
+
if matched_prefix:
|
|
308
|
+
script_name = environ.get("SCRIPT_NAME", "") or ""
|
|
309
|
+
if script_name and script_name != "/":
|
|
310
|
+
script_name = script_name.rstrip("/")
|
|
311
|
+
else:
|
|
312
|
+
script_name = ""
|
|
313
|
+
environ["SCRIPT_NAME"] = f"{script_name}{matched_prefix}"
|
|
314
|
+
|
|
315
|
+
# Try static first; if 404 then delegate to user app
|
|
316
|
+
captured_status = ""
|
|
317
|
+
captured_headers: tuple[tuple[str, str], ...] = ()
|
|
318
|
+
body_chunks: list[bytes] = []
|
|
319
|
+
|
|
320
|
+
def capture_start_response(
|
|
321
|
+
status: str,
|
|
322
|
+
headers: list[tuple[str, str]],
|
|
323
|
+
exc_info: Any = None,
|
|
324
|
+
) -> Callable[[bytes], None]:
|
|
325
|
+
nonlocal captured_status, captured_headers
|
|
326
|
+
captured_status = status
|
|
327
|
+
captured_headers = tuple(headers)
|
|
328
|
+
|
|
329
|
+
def write(chunk: bytes) -> None:
|
|
330
|
+
body_chunks.append(chunk)
|
|
331
|
+
|
|
332
|
+
return write
|
|
333
|
+
|
|
334
|
+
result = _static_wsgi_app(environ, capture_start_response)
|
|
335
|
+
# If static handler produced 200, forward its response
|
|
336
|
+
if captured_status.startswith("200 "):
|
|
337
|
+
# Send headers and any chunks collected
|
|
338
|
+
writer = start_response(captured_status, list(captured_headers))
|
|
339
|
+
for chunk in body_chunks:
|
|
340
|
+
writer(chunk)
|
|
341
|
+
return result
|
|
342
|
+
|
|
343
|
+
# Otherwise, delegate to user's WSGI app
|
|
344
|
+
assert _wsgi_user_app is not None
|
|
345
|
+
return _wsgi_user_app(environ, start_response)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _start_wsgi(host: str, port: int) -> None:
|
|
349
|
+
_setup_server_log_routing("werkzeug")
|
|
350
|
+
run_simple(host, port, wsgi_app, use_reloader=True, threaded=True)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _start_asgi(host: str, port: int) -> None:
|
|
354
|
+
# Prefer user-installed fastapi-cli when available
|
|
355
|
+
try:
|
|
356
|
+
from fastapi_cli.cli import ( # noqa: PLC0415 # pyright: ignore[reportMissingImports]
|
|
357
|
+
dev as fastapi_dev, # pyright: ignore[reportUnknownVariableType]
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
_patch_fastapi_cli_log_config()
|
|
361
|
+
fastapi_dev(
|
|
362
|
+
entrypoint="vercel_runtime.dev:asgi_app",
|
|
363
|
+
host=host,
|
|
364
|
+
port=port,
|
|
365
|
+
reload=True,
|
|
366
|
+
)
|
|
367
|
+
sys.exit(0)
|
|
368
|
+
except (ImportError, AttributeError):
|
|
369
|
+
pass
|
|
370
|
+
|
|
371
|
+
vendored_uvicorn.run(
|
|
372
|
+
"vercel_runtime.dev:asgi_app",
|
|
373
|
+
host=host,
|
|
374
|
+
port=port,
|
|
375
|
+
use_colors=not _NO_COLOR,
|
|
376
|
+
reload=True,
|
|
377
|
+
log_config=_build_uvicorn_log_config(),
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _setup_apps() -> None:
|
|
382
|
+
global _asgi_user_app, _wsgi_user_app # noqa: PLW0603
|
|
383
|
+
|
|
384
|
+
module_name = os.environ["VERCEL_DEV_MODULE_NAME"]
|
|
385
|
+
entry_abs = os.environ["VERCEL_DEV_ENTRY_ABS"]
|
|
386
|
+
|
|
387
|
+
_setup_server_log_routing()
|
|
388
|
+
|
|
389
|
+
mod = import_module(module_name, entry_abs)
|
|
390
|
+
app_name, user_app = resolve_app(mod, module_name)
|
|
391
|
+
try:
|
|
392
|
+
result = detect_app_type(user_app, module_name, app_name)
|
|
393
|
+
except RuntimeError:
|
|
394
|
+
_log.exception("could not detect application type")
|
|
395
|
+
sys.exit(1)
|
|
396
|
+
|
|
397
|
+
if result[0] == "asgi":
|
|
398
|
+
_asgi_user_app = result[1]
|
|
399
|
+
else:
|
|
400
|
+
_wsgi_user_app = result[1]
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# Setup apps at import time so that servers reloader can pick them up.
|
|
404
|
+
if os.environ.get("VERCEL_DEV_MODULE_NAME") and os.environ.get(
|
|
405
|
+
"VERCEL_DEV_ENTRY_ABS"
|
|
406
|
+
):
|
|
407
|
+
_setup_apps()
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def main() -> None:
|
|
411
|
+
host = "127.0.0.1"
|
|
412
|
+
raw_port = os.environ.get("PORT")
|
|
413
|
+
if not raw_port:
|
|
414
|
+
_log.error("PORT environment variable is required")
|
|
415
|
+
sys.exit(1)
|
|
416
|
+
port = int(raw_port)
|
|
417
|
+
|
|
418
|
+
if _wsgi_user_app is not None:
|
|
419
|
+
_start_wsgi(host, port)
|
|
420
|
+
else:
|
|
421
|
+
_start_asgi(host, port)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import sys
|
|
5
|
+
from importlib import util as _importlib_util
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from types import ModuleType
|
|
10
|
+
from wsgiref.types import WSGIApplication
|
|
11
|
+
|
|
12
|
+
from vercel_runtime.asgi import ASGI
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Import relative path
|
|
16
|
+
# https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
|
|
17
|
+
def import_module(name: str, path: str) -> ModuleType:
|
|
18
|
+
spec = _importlib_util.spec_from_file_location(name, path)
|
|
19
|
+
if spec is None or spec.loader is None:
|
|
20
|
+
msg = f'could not load module spec for "{name}" at {path}'
|
|
21
|
+
raise RuntimeError(msg)
|
|
22
|
+
mod = _importlib_util.module_from_spec(spec)
|
|
23
|
+
sys.modules[name] = mod
|
|
24
|
+
spec.loader.exec_module(mod)
|
|
25
|
+
return mod
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def resolve_app(mod: ModuleType, module_name: str) -> tuple[str, Any]:
|
|
29
|
+
for app_var in ("app", "application"):
|
|
30
|
+
if hasattr(mod, app_var):
|
|
31
|
+
return app_var, getattr(mod, app_var)
|
|
32
|
+
|
|
33
|
+
raise RuntimeError(
|
|
34
|
+
f"Missing 'app' or 'application' in module '{module_name}': "
|
|
35
|
+
"define `app = ...` or `application = ...` in your entrypoint"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _get_positional_param_count(obj: object) -> int | None:
|
|
40
|
+
try:
|
|
41
|
+
sig = inspect.signature(obj) # type: ignore[arg-type]
|
|
42
|
+
return sum(
|
|
43
|
+
1
|
|
44
|
+
for p in sig.parameters.values()
|
|
45
|
+
if p.default is inspect.Parameter.empty
|
|
46
|
+
and p.kind
|
|
47
|
+
in (
|
|
48
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
49
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
except (ValueError, TypeError):
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def detect_app_type(
|
|
57
|
+
app_obj: object,
|
|
58
|
+
module_name: str,
|
|
59
|
+
app_name: str,
|
|
60
|
+
) -> tuple[Literal["asgi"], ASGI] | tuple[Literal["wsgi"], WSGIApplication]:
|
|
61
|
+
# Try .asgi attribute if it's available and is callable
|
|
62
|
+
asgi_attr = getattr(app_obj, "asgi", None)
|
|
63
|
+
if asgi_attr is not None and callable(asgi_attr):
|
|
64
|
+
return "asgi", cast("ASGI", asgi_attr)
|
|
65
|
+
|
|
66
|
+
# For async detection, check the object itself first
|
|
67
|
+
# (works for plain functions/methods).
|
|
68
|
+
# For class instances iscoroutinefunction(obj) is False,
|
|
69
|
+
# so fall back to __call__.
|
|
70
|
+
is_async = inspect.iscoroutinefunction(app_obj)
|
|
71
|
+
if not is_async and callable(app_obj):
|
|
72
|
+
is_async = inspect.iscoroutinefunction(
|
|
73
|
+
app_obj.__call__ # pyright: ignore[reportFunctionMemberAccess]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
param_count = _get_positional_param_count(app_obj)
|
|
77
|
+
|
|
78
|
+
# that's ASGI (scope, receive, send)
|
|
79
|
+
if is_async and param_count == 3:
|
|
80
|
+
return "asgi", cast("ASGI", app_obj)
|
|
81
|
+
|
|
82
|
+
# that's WSGI (environ, start_response)
|
|
83
|
+
if param_count == 2:
|
|
84
|
+
return "wsgi", cast("WSGIApplication", app_obj)
|
|
85
|
+
|
|
86
|
+
raise RuntimeError(
|
|
87
|
+
f"Could not determine the application interface for "
|
|
88
|
+
f"'{module_name}:{app_name}'\n"
|
|
89
|
+
f"Expected either:\n"
|
|
90
|
+
f" - an ASGI app: async callable(scope, receive, send)\n"
|
|
91
|
+
f" - a WSGI app: callable(environ, start_response)"
|
|
92
|
+
)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
from urllib.parse import urlsplit
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def normalize_service_route_prefix(raw_prefix: str | None) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Normalize a configured service prefix into canonical mount-path form.
|
|
11
|
+
|
|
12
|
+
Returns an empty string when unset/blank/root ("/"), otherwise:
|
|
13
|
+
- always starts with "/"
|
|
14
|
+
- has no trailing slash
|
|
15
|
+
"""
|
|
16
|
+
if not raw_prefix:
|
|
17
|
+
return ""
|
|
18
|
+
|
|
19
|
+
prefix = raw_prefix.strip()
|
|
20
|
+
if not prefix:
|
|
21
|
+
return ""
|
|
22
|
+
|
|
23
|
+
if not prefix.startswith("/"):
|
|
24
|
+
prefix = f"/{prefix}"
|
|
25
|
+
|
|
26
|
+
# Treat "/" as an unset mount prefix.
|
|
27
|
+
return "" if prefix == "/" else prefix.rstrip("/")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_service_route_prefix_strip_enabled() -> bool:
|
|
31
|
+
raw = os.environ.get("VERCEL_SERVICE_ROUTE_PREFIX_STRIP")
|
|
32
|
+
if not raw:
|
|
33
|
+
return False
|
|
34
|
+
return raw.lower() in ("1", "true")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_service_route_prefix() -> str:
|
|
38
|
+
if not is_service_route_prefix_strip_enabled():
|
|
39
|
+
return ""
|
|
40
|
+
|
|
41
|
+
return normalize_service_route_prefix(
|
|
42
|
+
os.environ.get("VERCEL_SERVICE_ROUTE_PREFIX")
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_service_route_prefix = get_service_route_prefix()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def split_request_target(target: str) -> tuple[str, str]:
|
|
50
|
+
"""
|
|
51
|
+
Split an HTTP request-target into (path, query).
|
|
52
|
+
|
|
53
|
+
Supports:
|
|
54
|
+
- origin-form: "/a/b?x=1"
|
|
55
|
+
- absolute-form: "https://example.com/a/b?x=1"
|
|
56
|
+
- asterisk-form: "*"
|
|
57
|
+
"""
|
|
58
|
+
if not target:
|
|
59
|
+
return "/", ""
|
|
60
|
+
|
|
61
|
+
parsed = urlsplit(target)
|
|
62
|
+
path = parsed.path
|
|
63
|
+
query = parsed.query
|
|
64
|
+
|
|
65
|
+
# Absolute-form request-target (RFC 7230 section 5.3.2).
|
|
66
|
+
if parsed.scheme in ("http", "https") and parsed.netloc:
|
|
67
|
+
return path or "/", query
|
|
68
|
+
|
|
69
|
+
# Asterisk-form request-target (RFC 7230 section 5.3.4).
|
|
70
|
+
if path == "*":
|
|
71
|
+
return "*", query
|
|
72
|
+
|
|
73
|
+
if not path:
|
|
74
|
+
path = "/"
|
|
75
|
+
elif not path.startswith("/"):
|
|
76
|
+
path = f"/{path}"
|
|
77
|
+
|
|
78
|
+
return path, query
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def strip_service_route_prefix(
|
|
82
|
+
path: str, prefix: str | None = None
|
|
83
|
+
) -> tuple[str, str]:
|
|
84
|
+
"""
|
|
85
|
+
Strip a service route prefix from a request path.
|
|
86
|
+
|
|
87
|
+
Returns a tuple of:
|
|
88
|
+
- stripped path passed to the user app
|
|
89
|
+
- matched mount prefix (empty string when no prefix matched)
|
|
90
|
+
|
|
91
|
+
When *prefix* is ``None`` (the default), reads the current value
|
|
92
|
+
from ``get_service_route_prefix()``.
|
|
93
|
+
|
|
94
|
+
Example with prefix "/_/backend":
|
|
95
|
+
"/_/backend/ping" -> ("/ping", "/_/backend")
|
|
96
|
+
"/foo" -> ("/foo", "")
|
|
97
|
+
"""
|
|
98
|
+
if prefix is None:
|
|
99
|
+
prefix = _service_route_prefix
|
|
100
|
+
|
|
101
|
+
if path == "*":
|
|
102
|
+
return path, ""
|
|
103
|
+
if not path:
|
|
104
|
+
path = "/"
|
|
105
|
+
elif not path.startswith("/"):
|
|
106
|
+
path = f"/{path}"
|
|
107
|
+
|
|
108
|
+
if not prefix:
|
|
109
|
+
return path, ""
|
|
110
|
+
|
|
111
|
+
if path == prefix:
|
|
112
|
+
return "/", prefix
|
|
113
|
+
|
|
114
|
+
if path.startswith(f"{prefix}/"):
|
|
115
|
+
stripped = path[len(prefix) :]
|
|
116
|
+
return stripped if stripped else "/", prefix
|
|
117
|
+
|
|
118
|
+
return path, ""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def apply_service_route_prefix_to_asgi_scope(
|
|
122
|
+
scope: dict[str, Any], prefix: str | None = None
|
|
123
|
+
) -> None:
|
|
124
|
+
if prefix is None:
|
|
125
|
+
prefix = _service_route_prefix
|
|
126
|
+
|
|
127
|
+
if not prefix:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
path = scope.get("path", "/") or "/"
|
|
131
|
+
stripped_path, matched_prefix = strip_service_route_prefix(path, prefix)
|
|
132
|
+
if not matched_prefix:
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
scope["path"] = stripped_path
|
|
136
|
+
|
|
137
|
+
raw_path = scope.get("raw_path")
|
|
138
|
+
if isinstance(raw_path, (bytes, bytearray)):
|
|
139
|
+
try:
|
|
140
|
+
decoded = bytes(raw_path).decode("utf-8", "surrogateescape")
|
|
141
|
+
stripped_raw, _ = strip_service_route_prefix(decoded, prefix)
|
|
142
|
+
scope["raw_path"] = stripped_raw.encode("utf-8", "surrogateescape")
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
existing_root = scope.get("root_path", "") or ""
|
|
147
|
+
if existing_root and existing_root != "/":
|
|
148
|
+
existing_root = existing_root.rstrip("/")
|
|
149
|
+
else:
|
|
150
|
+
existing_root = ""
|
|
151
|
+
|
|
152
|
+
scope["root_path"] = f"{existing_root}{matched_prefix}"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def apply_service_route_prefix_to_target(
|
|
156
|
+
target: str,
|
|
157
|
+
prefix: str | None = None,
|
|
158
|
+
) -> tuple[str, str]:
|
|
159
|
+
"""
|
|
160
|
+
Apply service-prefix stripping to a full request target.
|
|
161
|
+
|
|
162
|
+
When *prefix* is ``None`` (the default), reads the current value
|
|
163
|
+
from ``get_service_route_prefix()``.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
- updated request target (path + optional query)
|
|
167
|
+
- matched mount prefix for framework metadata (or "")
|
|
168
|
+
"""
|
|
169
|
+
if prefix is None:
|
|
170
|
+
prefix = _service_route_prefix
|
|
171
|
+
|
|
172
|
+
path, query = split_request_target(target)
|
|
173
|
+
path, root_path = strip_service_route_prefix(path, prefix)
|
|
174
|
+
if query:
|
|
175
|
+
return f"{path}?{query}", root_path
|
|
176
|
+
|
|
177
|
+
return path, root_path
|