Update app and tooling

This commit is contained in:
Lawrence Chen 2026-01-29 17:36:26 -08:00
parent 3046531bdd
commit e620ec7349
4950 changed files with 2975120 additions and 10 deletions

202
node_modules/@vercel/python/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 Vercel, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

4730
node_modules/@vercel/python/dist/index.js generated vendored Normal file

File diff suppressed because it is too large Load diff

43
node_modules/@vercel/python/package.json generated vendored Normal file
View file

@ -0,0 +1,43 @@
{
"name": "@vercel/python",
"version": "6.4.1",
"main": "./dist/index.js",
"license": "Apache-2.0",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
"files": [
"dist",
"vc_init.py",
"vc_init_dev_asgi.py",
"vc_init_dev_wsgi.py"
],
"repository": {
"type": "git",
"url": "https://github.com/vercel/vercel.git",
"directory": "packages/python"
},
"devDependencies": {
"@renovatebot/pep440": "4.2.1",
"@types/execa": "^0.9.0",
"@types/fs-extra": "11.0.2",
"@types/jest": "27.4.1",
"@types/node": "20.11.0",
"@types/which": "3.0.0",
"cross-env": "7.0.3",
"execa": "^1.0.0",
"fs-extra": "11.1.1",
"jest-junit": "16.0.0",
"minimatch": "10.1.1",
"pip-requirements-js": "1.0.2",
"smol-toml": "1.5.2",
"which": "3.0.0",
"@vercel/error-utils": "2.0.3",
"@vercel/build-utils": "13.2.16"
},
"scripts": {
"build": "node ../../utils/build-builder.mjs",
"test": "cross-env VERCEL_FORCE_PYTHON_STREAMING=1 jest --reporters=default --reporters=jest-junit --env node --verbose --runInBand --bail",
"test-unit": "pnpm test test/unit.test.ts",
"test-e2e": "pnpm test test/integration-*",
"type-check": "tsc --noEmit"
}
}

916
node_modules/@vercel/python/vc_init.py generated vendored Normal file
View file

@ -0,0 +1,916 @@
from __future__ import annotations
import sys
import os
import site
import importlib
import base64
import json
import inspect
import asyncio
import http
import time
import traceback
from importlib import util
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import socket
import functools
import logging
import builtins
from typing import Callable, Literal, TextIO
import contextvars
import contextlib
import atexit
_here = os.path.dirname(__file__)
_vendor_rel = '__VC_HANDLER_VENDOR_DIR'
_vendor = os.path.normpath(os.path.join(_here, _vendor_rel))
if os.path.isdir(_vendor):
# Process .pth files like a real site-packages dir
site.addsitedir(_vendor)
# Move _vendor to the front (after script dir if present)
try:
while _vendor in sys.path:
sys.path.remove(_vendor)
except ValueError:
pass
# Put vendored deps ahead of site-packages but after the script dir
idx = 1 if (sys.path and sys.path[0] in ('', _here)) else 0
sys.path.insert(idx, _vendor)
importlib.invalidate_caches()
def setup_logging(send_message: Callable[[dict], None], storage: contextvars.ContextVar[dict | None]):
# Override logging.Handler to send logs to the platform when a request context is available.
class VCLogHandler(logging.Handler):
def emit(self, record: logging.LogRecord):
try:
message = record.getMessage()
except Exception:
message = repr(getattr(record, "msg", ""))
with contextlib.suppress(Exception):
if record.exc_info:
# logging allows exc_info=True or a (type, value, tb) tuple
exc_info = record.exc_info
if exc_info is True:
exc_info = sys.exc_info()
if isinstance(exc_info, tuple):
tb = ''.join(traceback.format_exception(*exc_info))
if tb:
if message:
message = f"{message}\n{tb}"
else:
message = tb
if record.levelno >= logging.CRITICAL:
level = "fatal"
elif record.levelno >= logging.ERROR:
level = "error"
elif record.levelno >= logging.WARNING:
level = "warn"
elif record.levelno >= logging.INFO:
level = "info"
else:
level = "debug"
context = storage.get()
if context is not None:
send_message({
"type": "log",
"payload": {
"context": {
"invocationId": context['invocationId'],
"requestId": context['requestId'],
},
"message": base64.b64encode(message.encode()).decode(),
"level": level,
}
})
else:
# If IPC is not ready, enqueue the message to be sent later.
enqueue_or_send_message({
"type": "log",
"payload": {
"context": {"invocationId": "0", "requestId": 0},
"message": base64.b64encode(message.encode()).decode(),
"level": level,
}
})
# Override sys.stdout and sys.stderr to map logs to the correct request
class StreamWrapper:
def __init__(self, stream: TextIO, stream_name: Literal["stdout", "stderr"]):
self.stream = stream
self.stream_name = stream_name
def write(self, message: str):
context = storage.get()
if context is not None:
send_message({
"type": "log",
"payload": {
"context": {
"invocationId": context['invocationId'],
"requestId": context['requestId'],
},
"message": base64.b64encode(message.encode()).decode(),
"stream": self.stream_name,
}
})
else:
enqueue_or_send_message({
"type": "log",
"payload": {
"context": {"invocationId": "0", "requestId": 0},
"message": base64.b64encode(message.encode()).decode(),
"stream": self.stream_name,
}
})
def __getattr__(self, name):
return getattr(self.stream, name)
sys.stdout = StreamWrapper(sys.stdout, "stdout")
sys.stderr = StreamWrapper(sys.stderr, "stderr")
logging.basicConfig(level=logging.INFO, handlers=[VCLogHandler()], force=True)
# Ensure built-in print funnels through stdout wrapper so prints are
# attributed to the current request context.
def print_wrapper(func: Callable[..., None]) -> Callable[..., None]:
@functools.wraps(func)
def wrapper(*args, sep=' ', end='\n', file=None, flush=False):
if file is None:
file = sys.stdout
if file in (sys.stdout, sys.stderr):
file.write(sep.join(map(str, args)) + end)
if flush:
file.flush()
else:
# User specified a different file, use original print behavior
func(*args, sep=sep, end=end, file=file, flush=flush)
return wrapper
builtins.print = print_wrapper(builtins.print)
def _stderr(message: str):
with contextlib.suppress(Exception):
_original_stderr.write(message + "\n")
_original_stderr.flush()
# If running in the platform (IPC present), logging must be setup before importing user code so that
# logs happening outside the request context are emitted correctly.
ipc_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
storage: contextvars.ContextVar[dict | None] = contextvars.ContextVar('storage', default=None)
send_message = lambda m: None
_original_stderr = sys.stderr
# Buffer for pre-handshake logs (to avoid blocking IPC on startup)
_ipc_ready = False
_init_log_buf: list[dict] = []
_INIT_LOG_BUF_MAX_BYTES = 1_000_000
_init_log_buf_bytes = 0
def enqueue_or_send_message(msg: dict):
global _init_log_buf_bytes
if _ipc_ready:
send_message(msg)
return
enc_len = len(json.dumps(msg))
if _init_log_buf_bytes + enc_len <= _INIT_LOG_BUF_MAX_BYTES:
_init_log_buf.append(msg)
_init_log_buf_bytes += enc_len
else:
# Fallback so message is not lost if buffer is full
with contextlib.suppress(Exception):
payload = msg.get("payload", {})
decoded = base64.b64decode(payload.get("message", "")).decode(errors="ignore")
_original_stderr.write(decoded + "\n")
def flush_init_log_buf_to_stderr():
global _init_log_buf, _init_log_buf_bytes
try:
combined: list[str] = []
for m in _init_log_buf:
payload = m.get("payload", {})
msg = payload.get("message")
if not msg:
continue
with contextlib.suppress(Exception):
decoded = base64.b64decode(msg).decode(errors="ignore")
combined.append(decoded)
if combined:
_stderr("".join(combined))
except Exception:
pass
finally:
_init_log_buf.clear()
_init_log_buf_bytes = 0
atexit.register(flush_init_log_buf_to_stderr)
if 'VERCEL_IPC_PATH' in os.environ:
with contextlib.suppress(Exception):
ipc_sock.connect(os.getenv("VERCEL_IPC_PATH", ""))
def send_message(message: dict):
with contextlib.suppress(Exception):
ipc_sock.sendall((json.dumps(message) + '\0').encode())
setup_logging(send_message, storage)
# Import relative path https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
try:
user_mod_path = os.path.join(_here, "__VC_HANDLER_ENTRYPOINT") # absolute
__vc_spec = util.spec_from_file_location("__VC_HANDLER_MODULE_NAME", user_mod_path)
__vc_module = util.module_from_spec(__vc_spec)
sys.modules["__VC_HANDLER_MODULE_NAME"] = __vc_module
__vc_spec.loader.exec_module(__vc_module)
__vc_variables = dir(__vc_module)
except Exception:
_stderr(f'Error importing __VC_HANDLER_ENTRYPOINT:')
_stderr(traceback.format_exc())
exit(1)
_use_legacy_asyncio = sys.version_info < (3, 10)
def format_headers(headers, decode=False):
keyToList = {}
for key, value in headers.items():
if decode and 'decode' in dir(key) and 'decode' in dir(value):
key = key.decode()
value = value.decode()
if key not in keyToList:
keyToList[key] = []
keyToList[key].append(value)
return keyToList
class ASGIMiddleware:
"""
ASGI middleware that preserves Vercel IPC semantics for request lifecycle:
- Handles /_vercel/ping
- Extracts x-vercel-internal-* headers and removes them from downstream app
- Sets request context into `storage` for logging/metrics
- Emits handler-started and end IPC messages
"""
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope.get('type') != 'http':
# Non-HTTP traffic is forwarded verbatim
await self.app(scope, receive, send)
return
if scope.get('path') == '/_vercel/ping':
await send({
'type': 'http.response.start',
'status': 200,
'headers': [],
})
await send({
'type': 'http.response.body',
'body': b'',
'more_body': False,
})
return
# Extract internal headers and set per-request context
headers_list = scope.get('headers', []) or []
new_headers = []
invocation_id = "0"
request_id = 0
def _b2s(b: bytes) -> str:
try:
return b.decode()
except Exception:
return ''
for k, v in headers_list:
key = _b2s(k).lower()
val = _b2s(v)
if key == 'x-vercel-internal-invocation-id':
invocation_id = val
continue
if key == 'x-vercel-internal-request-id':
request_id = int(val) if val.isdigit() else 0
continue
if key in ('x-vercel-internal-span-id', 'x-vercel-internal-trace-id'):
continue
new_headers.append((k, v))
new_scope = dict(scope)
new_scope['headers'] = new_headers
# Announce handler start and set context for logging/metrics
send_message({
"type": "handler-started",
"payload": {
"handlerStartedAt": int(time.time() * 1000),
"context": {
"invocationId": invocation_id,
"requestId": request_id,
}
}
})
token = storage.set({
"invocationId": invocation_id,
"requestId": request_id,
})
try:
await self.app(new_scope, receive, send)
finally:
storage.reset(token)
send_message({
"type": "end",
"payload": {
"context": {
"invocationId": invocation_id,
"requestId": request_id,
}
}
})
if 'VERCEL_IPC_PATH' in os.environ:
start_time = time.time()
# Override urlopen from urllib3 (& requests) to send Request Metrics
try:
import urllib3
from urllib.parse import urlparse
def timed_request(func):
fetchId = 0
@functools.wraps(func)
def wrapper(self, method, url, *args, **kwargs):
nonlocal fetchId
fetchId += 1
start_time = int(time.time() * 1000)
result = func(self, method, url, *args, **kwargs)
elapsed_time = int(time.time() * 1000) - start_time
parsed_url = urlparse(url)
context = storage.get()
if context is not None:
send_message({
"type": "metric",
"payload": {
"context": {
"invocationId": context['invocationId'],
"requestId": context['requestId'],
},
"type": "fetch-metric",
"payload": {
"pathname": parsed_url.path,
"search": parsed_url.query,
"start": start_time,
"duration": elapsed_time,
"host": parsed_url.hostname or self.host,
"statusCode": result.status,
"method": method,
"id": fetchId
}
}
})
return result
return wrapper
urllib3.connectionpool.HTTPConnectionPool.urlopen = timed_request(urllib3.connectionpool.HTTPConnectionPool.urlopen)
except:
pass
class BaseHandler(BaseHTTPRequestHandler):
# Re-implementation of BaseHTTPRequestHandler's log_message method to
# log to stdout instead of stderr.
def log_message(self, format, *args):
message = format % args
sys.stdout.write("%s - - [%s] %s\n" %
(self.address_string(),
self.log_date_time_string(),
message.translate(self._control_char_table)))
# Re-implementation of BaseHTTPRequestHandler's handle_one_request method
# to send the end message after the response is fully sent.
def handle_one_request(self):
self.raw_requestline = self.rfile.readline(65537)
if not self.raw_requestline:
self.close_connection = True
return
if not self.parse_request():
return
if self.path == '/_vercel/ping':
self.send_response(200)
self.end_headers()
return
invocationId = self.headers.get('x-vercel-internal-invocation-id')
requestId = int(self.headers.get('x-vercel-internal-request-id'))
del self.headers['x-vercel-internal-invocation-id']
del self.headers['x-vercel-internal-request-id']
del self.headers['x-vercel-internal-span-id']
del self.headers['x-vercel-internal-trace-id']
send_message({
"type": "handler-started",
"payload": {
"handlerStartedAt": int(time.time() * 1000),
"context": {
"invocationId": invocationId,
"requestId": requestId,
}
}
})
token = storage.set({
"invocationId": invocationId,
"requestId": requestId,
})
try:
self.handle_request()
finally:
storage.reset(token)
send_message({
"type": "end",
"payload": {
"context": {
"invocationId": invocationId,
"requestId": requestId,
}
}
})
if 'handler' in __vc_variables or 'Handler' in __vc_variables:
base = __vc_module.handler if ('handler' in __vc_variables) else __vc_module.Handler
if not issubclass(base, BaseHTTPRequestHandler):
_stderr('Handler must inherit from BaseHTTPRequestHandler')
_stderr('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
exit(1)
class Handler(BaseHandler, base):
def handle_request(self):
mname = 'do_' + self.command
if not hasattr(self, mname):
self.send_error(
http.HTTPStatus.NOT_IMPLEMENTED,
"Unsupported method (%r)" % self.command)
return
method = getattr(self, mname)
method()
self.wfile.flush()
elif 'app' in __vc_variables:
if (
not inspect.iscoroutinefunction(__vc_module.app) and
not inspect.iscoroutinefunction(__vc_module.app.__call__)
):
from io import BytesIO
string_types = (str,)
app = __vc_module.app
def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
if isinstance(s, str):
s = s.encode(charset)
return s.decode("latin1", errors)
class Handler(BaseHandler):
def handle_request(self):
# Prepare WSGI environment
if '?' in self.path:
path, query = self.path.split('?', 1)
else:
path, query = self.path, ''
content_length = int(self.headers.get('Content-Length', 0))
env = {
'CONTENT_LENGTH': str(content_length),
'CONTENT_TYPE': self.headers.get('content-type', ''),
'PATH_INFO': path,
'QUERY_STRING': query,
'REMOTE_ADDR': self.headers.get(
'x-forwarded-for', self.headers.get(
'x-real-ip')),
'REQUEST_METHOD': self.command,
'SERVER_NAME': self.headers.get('host', 'lambda'),
'SERVER_PORT': self.headers.get('x-forwarded-port', '80'),
'SERVER_PROTOCOL': 'HTTP/1.1',
'wsgi.errors': sys.stderr,
'wsgi.input': BytesIO(self.rfile.read(content_length)),
'wsgi.multiprocess': False,
'wsgi.multithread': False,
'wsgi.run_once': False,
'wsgi.url_scheme': self.headers.get('x-forwarded-proto', 'http'),
'wsgi.version': (1, 0),
}
for key, value in env.items():
if isinstance(value, string_types):
env[key] = wsgi_encoding_dance(value)
for k, v in self.headers.items():
env['HTTP_' + k.replace('-', '_').upper()] = v
def start_response(status, headers, exc_info=None):
self.send_response(int(status.split(' ')[0]))
for name, value in headers:
self.send_header(name, value)
self.end_headers()
return self.wfile.write
# Call the application
response = app(env, start_response)
try:
for data in response:
if data:
self.wfile.write(data)
self.wfile.flush()
finally:
if hasattr(response, 'close'):
response.close()
else:
# ASGI: Run with Uvicorn so we get proper lifespan and protocol handling
try:
import uvicorn
except Exception:
_stderr('Uvicorn is required to run ASGI apps. Please ensure it is installed.')
exit(1)
# Prefer a callable app.asgi when available; some frameworks expose a boolean here
user_app_candidate = getattr(__vc_module.app, 'asgi', None)
user_app = user_app_candidate if callable(user_app_candidate) else __vc_module.app
asgi_app = ASGIMiddleware(user_app)
# Pre-bind a socket to obtain an ephemeral port for IPC announcement
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 0))
sock.listen(2048)
http_port = sock.getsockname()[1]
config = uvicorn.Config(
app=asgi_app,
fd=sock.fileno(),
lifespan='auto',
access_log=False,
log_config=None,
log_level='warning',
)
server = uvicorn.Server(config)
send_message({
"type": "server-started",
"payload": {
"initDuration": int((time.time() - start_time) * 1000),
"httpPort": http_port,
}
})
# Mark IPC as ready and flush any buffered init logs
_ipc_ready = True
for m in _init_log_buf:
send_message(m)
_init_log_buf.clear()
# Run the server (blocking)
server.run()
# If the server ever returns, exit
sys.exit(0)
if 'Handler' in locals():
server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
send_message({
"type": "server-started",
"payload": {
"initDuration": int((time.time() - start_time) * 1000),
"httpPort": server.server_address[1],
}
})
# Mark IPC as ready and flush any buffered init logs
_ipc_ready = True
for m in _init_log_buf:
send_message(m)
_init_log_buf.clear()
server.serve_forever()
_stderr('Missing variable `handler` or `app` in file "__VC_HANDLER_ENTRYPOINT".')
_stderr('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
exit(1)
if 'handler' in __vc_variables or 'Handler' in __vc_variables:
base = __vc_module.handler if ('handler' in __vc_variables) else __vc_module.Handler
if not issubclass(base, BaseHTTPRequestHandler):
print('Handler must inherit from BaseHTTPRequestHandler')
print('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
exit(1)
print('using HTTP Handler')
from http.server import HTTPServer
import http
import _thread
server = HTTPServer(('127.0.0.1', 0), base)
port = server.server_address[1]
def vc_handler(event, context):
_thread.start_new_thread(server.handle_request, ())
payload = json.loads(event['body'])
path = payload['path']
headers = payload['headers']
method = payload['method']
encoding = payload.get('encoding')
body = payload.get('body')
if (
(body is not None and len(body) > 0) and
(encoding is not None and encoding == 'base64')
):
body = base64.b64decode(body)
request_body = body.encode('utf-8') if isinstance(body, str) else body
conn = http.client.HTTPConnection('127.0.0.1', port)
try:
conn.request(method, path, headers=headers, body=request_body)
except (http.client.HTTPException, socket.error) as ex:
print ("Request Error: %s" % ex)
res = conn.getresponse()
return_dict = {
'statusCode': res.status,
'headers': format_headers(res.headers),
}
data = res.read()
try:
return_dict['body'] = data.decode('utf-8')
except UnicodeDecodeError:
return_dict['body'] = base64.b64encode(data).decode('utf-8')
return_dict['encoding'] = 'base64'
return return_dict
elif 'app' in __vc_variables:
if (
not inspect.iscoroutinefunction(__vc_module.app) and
not inspect.iscoroutinefunction(__vc_module.app.__call__)
):
print('using Web Server Gateway Interface (WSGI)')
from io import BytesIO
from urllib.parse import urlparse
from werkzeug.datastructures import Headers
from werkzeug.wrappers import Response
string_types = (str,)
def to_bytes(x, charset=sys.getdefaultencoding(), errors="strict"):
if x is None:
return None
if isinstance(x, (bytes, bytearray, memoryview)):
return bytes(x)
if isinstance(x, str):
return x.encode(charset, errors)
raise TypeError("Expected bytes")
def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
if isinstance(s, str):
s = s.encode(charset)
return s.decode("latin1", errors)
def vc_handler(event, context):
payload = json.loads(event['body'])
headers = Headers(payload.get('headers', {}))
body = payload.get('body', '')
if body != '':
if payload.get('encoding') == 'base64':
body = base64.b64decode(body)
if isinstance(body, string_types):
body = to_bytes(body, charset='utf-8')
url = urlparse(payload['path'])
query = url.query
path = url.path
environ = {
'CONTENT_LENGTH': str(len(body)),
'CONTENT_TYPE': headers.get('content-type', ''),
'PATH_INFO': path,
'QUERY_STRING': query,
'REMOTE_ADDR': headers.get(
'x-forwarded-for', headers.get(
'x-real-ip', payload.get(
'true-client-ip', ''))),
'REQUEST_METHOD': payload['method'],
'SERVER_NAME': headers.get('host', 'lambda'),
'SERVER_PORT': headers.get('x-forwarded-port', '80'),
'SERVER_PROTOCOL': 'HTTP/1.1',
'event': event,
'context': context,
'wsgi.errors': sys.stderr,
'wsgi.input': BytesIO(body),
'wsgi.multiprocess': False,
'wsgi.multithread': False,
'wsgi.run_once': False,
'wsgi.url_scheme': headers.get('x-forwarded-proto', 'http'),
'wsgi.version': (1, 0),
}
for key, value in environ.items():
if isinstance(value, string_types):
environ[key] = wsgi_encoding_dance(value)
for key, value in headers.items():
key = 'HTTP_' + key.upper().replace('-', '_')
if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
environ[key] = value
response = Response.from_app(__vc_module.app, environ)
return_dict = {
'statusCode': response.status_code,
'headers': format_headers(response.headers)
}
if response.data:
return_dict['body'] = base64.b64encode(response.data).decode('utf-8')
return_dict['encoding'] = 'base64'
return return_dict
else:
print('using Asynchronous Server Gateway Interface (ASGI)')
# Originally authored by Jordan Eremieff and included under MIT license:
# https://github.com/erm/mangum/blob/b4d21c8f5e304a3e17b88bc9fa345106acc50ad7/mangum/__init__.py
# https://github.com/erm/mangum/blob/b4d21c8f5e304a3e17b88bc9fa345106acc50ad7/LICENSE
import asyncio
import enum
from urllib.parse import urlparse
from werkzeug.datastructures import Headers
class ASGICycleState(enum.Enum):
REQUEST = enum.auto()
RESPONSE = enum.auto()
class ASGICycle:
def __init__(self, scope):
self.scope = scope
self.body = b''
self.state = ASGICycleState.REQUEST
self.app_queue = None
self.response = {}
def __call__(self, app, body):
"""
Receives the application and any body included in the request, then builds the
ASGI instance using the connection scope.
Runs until the response is completely read from the application.
"""
if _use_legacy_asyncio:
loop = asyncio.new_event_loop()
self.app_queue = asyncio.Queue(loop=loop)
else:
self.app_queue = asyncio.Queue()
self.put_message({'type': 'http.request', 'body': body, 'more_body': False})
asgi_instance = app(self.scope, self.receive, self.send)
if _use_legacy_asyncio:
asgi_task = loop.create_task(asgi_instance)
loop.run_until_complete(asgi_task)
else:
asyncio.run(self.run_asgi_instance(asgi_instance))
return self.response
async def run_asgi_instance(self, asgi_instance):
await asgi_instance
def put_message(self, message):
self.app_queue.put_nowait(message)
async def receive(self):
"""
Awaited by the application to receive messages in the queue.
"""
message = await self.app_queue.get()
return message
async def send(self, message):
"""
Awaited by the application to send messages to the current cycle instance.
"""
message_type = message['type']
if self.state is ASGICycleState.REQUEST:
if message_type != 'http.response.start':
raise RuntimeError(
f"Expected 'http.response.start', received: {message_type}"
)
status_code = message['status']
raw_headers = message.get('headers', [])
# Headers from werkzeug transform bytes header value
# from b'value' to "b'value'" so we need to process
# ASGI headers manually
decoded_headers = []
for key, value in raw_headers:
decoded_key = key.decode() if isinstance(key, bytes) else key
decoded_value = value.decode() if isinstance(value, bytes) else value
decoded_headers.append((decoded_key, decoded_value))
headers = Headers(decoded_headers)
self.on_request(headers, status_code)
self.state = ASGICycleState.RESPONSE
elif self.state is ASGICycleState.RESPONSE:
if message_type != 'http.response.body':
raise RuntimeError(
f"Expected 'http.response.body', received: {message_type}"
)
body = message.get('body', b'')
more_body = message.get('more_body', False)
# The body must be completely read before returning the response.
self.body += body
if not more_body:
self.on_response()
self.put_message({'type': 'http.disconnect'})
def on_request(self, headers, status_code):
self.response['statusCode'] = status_code
self.response['headers'] = format_headers(headers, decode=True)
def on_response(self):
if self.body:
self.response['body'] = base64.b64encode(self.body).decode('utf-8')
self.response['encoding'] = 'base64'
def vc_handler(event, context):
payload = json.loads(event['body'])
headers = payload.get('headers', {})
body = payload.get('body', b'')
if payload.get('encoding') == 'base64':
body = base64.b64decode(body)
elif not isinstance(body, bytes):
body = body.encode()
url = urlparse(payload['path'])
query = url.query.encode()
path = url.path
headers_encoded = []
for k, v in headers.items():
# Cope with repeated headers in the encoding.
if isinstance(v, list):
headers_encoded.append([k.lower().encode(), [i.encode() for i in v]])
else:
headers_encoded.append([k.lower().encode(), v.encode()])
scope = {
'server': (headers.get('host', 'lambda'), headers.get('x-forwarded-port', 80)),
'client': (headers.get(
'x-forwarded-for', headers.get(
'x-real-ip', payload.get(
'true-client-ip', ''))), 0),
'scheme': headers.get('x-forwarded-proto', 'http'),
'root_path': '',
'query_string': query,
'headers': headers_encoded,
'type': 'http',
'http_version': '1.1',
'method': payload['method'],
'path': path,
'raw_path': path.encode(),
}
asgi_cycle = ASGICycle(scope)
response = asgi_cycle(__vc_module.app, body)
return response
else:
print('Missing variable `handler` or `app` in file "__VC_HANDLER_ENTRYPOINT".')
print('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
exit(1)

100
node_modules/@vercel/python/vc_init_dev_asgi.py generated vendored Normal file
View file

@ -0,0 +1,100 @@
# Auto-generated template used by vercel dev (Python, ASGI)
# Serves static files from PUBLIC_DIR before delegating to the user ASGI app.
import sys
import os
from os import path as _p
from importlib import import_module
# Simple ANSI coloring. Respect NO_COLOR environment variable.
_NO_COLOR = 'NO_COLOR' in os.environ
_RESET = "\x1b[0m"
_YELLOW = "\x1b[33m"
_GREEN = "\x1b[32m"
_RED = "\x1b[31m"
def _color(text: str, code: str) -> str:
if _NO_COLOR:
return text
return f"{code}{text}{_RESET}"
# Optional StaticFiles import; tolerate missing deps
StaticFiles = None
try:
from fastapi.staticfiles import StaticFiles as _SF
StaticFiles = _SF
except Exception:
try:
from starlette.staticfiles import StaticFiles as _SF
StaticFiles = _SF
except Exception:
StaticFiles = None
USER_MODULE = "__VC_DEV_MODULE_PATH__"
_mod = import_module(USER_MODULE)
_app = getattr(_mod, 'app', None)
if _app is None:
raise RuntimeError(
f"Missing 'app' in module '{USER_MODULE}'. Define `app = ...` (ASGI app)."
)
# Prefer a callable app.asgi when available; some frameworks expose a boolean here
_CAND = getattr(_app, 'asgi', None)
USER_ASGI_APP = _CAND if callable(_CAND) else _app
PUBLIC_DIR = 'public'
# Prepare static files app (if starlette/fastapi installed)
static_app = None
if StaticFiles is not None:
try:
try:
static_app = StaticFiles(directory=PUBLIC_DIR, check_dir=False)
except TypeError:
# Older Starlette without check_dir parameter
static_app = StaticFiles(directory=PUBLIC_DIR)
except Exception:
static_app = None
async def app(scope, receive, send):
if static_app is not None and scope.get('type') == 'http':
req_path = scope.get('path', '/') or '/'
safe = _p.normpath(req_path).lstrip('/')
full = _p.join(PUBLIC_DIR, safe)
try:
base = _p.realpath(PUBLIC_DIR)
target = _p.realpath(full)
if (target == base or target.startswith(base + _p.sep)) and _p.isfile(target):
await static_app(scope, receive, send)
return
except Exception:
pass
await USER_ASGI_APP(scope, receive, send)
if __name__ == '__main__':
# Development runner for ASGI: prefer uvicorn, then hypercorn.
# Bind to localhost on an ephemeral port and emit a recognizable log line
# so the caller can detect the bound port.
host = '127.0.0.1'
try:
import uvicorn
uvicorn.run('vc_init_dev_asgi:app', host=host, port=0, use_colors=True)
except Exception:
try:
import asyncio
from hypercorn.config import Config
from hypercorn.asyncio import serve
config = Config()
config.bind = [f'{host}:0']
async def _run():
await serve(app, config)
asyncio.run(_run())
except Exception:
print(_color('No ASGI server found. Please install either "uvicorn" or "hypercorn" (e.g. "pip install uvicorn").', _RED), file=sys.stderr)
sys.exit(1)

123
node_modules/@vercel/python/vc_init_dev_wsgi.py generated vendored Normal file
View file

@ -0,0 +1,123 @@
"""
Auto-generated template used by vercel dev (Python, WSGI)
Serves static files from PUBLIC_DIR before delegating to the user WSGI app.
This file is written to the project at .vercel/python/vc_init_dev_wsgi.py
and imported by the dev server launcher.
"""
from importlib import import_module
from os import path as _p
import os
import mimetypes
# Simple ANSI coloring. Respect NO_COLOR environment variable.
_NO_COLOR = 'NO_COLOR' in os.environ
_RESET = "\x1b[0m"
_YELLOW = "\x1b[33m"
_GREEN = "\x1b[32m"
def _color(text: str, code: str) -> str:
if _NO_COLOR:
return text
return f"{code}{text}{_RESET}"
USER_MODULE = "__VC_DEV_MODULE_PATH__"
PUBLIC_DIR = "public"
_mod = import_module(USER_MODULE)
_app = getattr(_mod, "app", None)
if _app is None:
raise RuntimeError(
f"Missing 'app' in module '{USER_MODULE}'. Define `app = ...` (WSGI app)."
)
def _is_safe_file(base_dir: str, target: str) -> bool:
try:
base = _p.realpath(base_dir)
tgt = _p.realpath(target)
return (tgt == base or tgt.startswith(base + os.sep)) and _p.isfile(tgt)
except Exception:
return False
def _static_wsgi_app(environ, start_response):
# Only handle GET/HEAD requests for static assets
if environ.get("REQUEST_METHOD", "GET") not in ("GET", "HEAD"):
return _not_found(start_response)
req_path = environ.get("PATH_INFO", "/") or "/"
safe = _p.normpath(req_path).lstrip("/")
full = _p.join(PUBLIC_DIR, safe)
if not _is_safe_file(PUBLIC_DIR, full):
return _not_found(start_response)
ctype, encoding = mimetypes.guess_type(full)
headers = [("Content-Type", ctype or "application/octet-stream")]
try:
# For HEAD requests, send headers only
if environ.get("REQUEST_METHOD") == "HEAD":
start_response("200 OK", headers)
return []
with open(full, "rb") as f:
data = f.read()
headers.append(("Content-Length", str(len(data))))
start_response("200 OK", headers)
return [data]
except Exception:
return _not_found(start_response)
def _not_found(start_response):
start_response("404 Not Found", [("Content-Type", "text/plain; charset=utf-8")])
return [b"Not Found"]
def _combined_app(environ, start_response):
# Try static first; if 404 then delegate to user app
captured_status = ""
captured_headers = tuple()
body_chunks = []
def capture_start_response(status, headers, exc_info=None): # type: ignore[no-redef]
nonlocal captured_status, captured_headers
captured_status = status
captured_headers = tuple(headers)
# Return a writer that buffers the body
def write(chunk: bytes):
body_chunks.append(chunk)
return write
result = _static_wsgi_app(environ, capture_start_response)
# If static handler produced 200, forward its response
if captured_status.startswith("200 "):
# Send headers and any chunks collected
writer = start_response(captured_status, list(captured_headers))
for chunk in body_chunks:
writer(chunk)
return result
# Otherwise, delegate to user's WSGI app
return _app(environ, start_response)
# Public WSGI application consumed by the dev runner
app = _combined_app
if __name__ == "__main__":
# Development runner: prefer Werkzeug, fall back to stdlib wsgiref.
# Bind to localhost on an ephemeral port and emit a recognizable log line
# so the caller can detect the bound port.
host = "127.0.0.1"
try:
from werkzeug.serving import run_simple
run_simple(host, 0, app, use_reloader=False)
except Exception:
import sys
print(_color("Werkzeug not installed; falling back to wsgiref (no reloader).", _YELLOW), file=sys.stderr)
from wsgiref.simple_server import make_server
httpd = make_server(host, 0, app)
port = httpd.server_port
print(_color(f"Serving on http://{host}:{port}", _GREEN))
httpd.serve_forever()