@vercel/python 6.5.1 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +99 -147
  2. package/package.json +2 -2
  3. package/vc_init.py +0 -916
package/dist/index.js CHANGED
@@ -2822,10 +2822,17 @@ var UvRunner = class {
2822
2822
  `Failed to parse 'uv python list' output: ${err instanceof Error ? err.message : String(err)}`
2823
2823
  );
2824
2824
  }
2825
- return new Set(
2826
- pyList.filter(
2825
+ if (process.env.VERCEL_BUILD_IMAGE) {
2826
+ pyList = pyList.filter(
2827
2827
  (entry) => entry.path !== null && entry.path.startsWith(UV_PYTHON_PATH_PREFIX) && entry.implementation === "cpython"
2828
- ).map(
2828
+ );
2829
+ } else {
2830
+ pyList = pyList.filter(
2831
+ (entry) => entry.path !== null && entry.implementation === "cpython"
2832
+ );
2833
+ }
2834
+ return new Set(
2835
+ pyList.map(
2829
2836
  (entry) => `${entry.version_parts.major}.${entry.version_parts.minor}`
2830
2837
  )
2831
2838
  );
@@ -2869,6 +2876,14 @@ var UvRunner = class {
2869
2876
  (0, import_build_utils.debug)(`Running "uv ${args.join(" ")}" in ${projectDir}...`);
2870
2877
  await this.runUvCmd(args, projectDir, venvPath);
2871
2878
  }
2879
+ /**
2880
+ * Run a `uv pip` command (e.g., `uv pip install`).
2881
+ */
2882
+ async pip(options) {
2883
+ const { venvPath, projectDir, args } = options;
2884
+ const fullArgs = ["pip", ...args];
2885
+ await this.runUvCmd(fullArgs, projectDir, venvPath);
2886
+ }
2872
2887
  async runUvCmd(args, cwd, venvPath) {
2873
2888
  const pretty = `uv ${args.join(" ")}`;
2874
2889
  (0, import_build_utils.debug)(`Running "${pretty}"...`);
@@ -3529,27 +3544,6 @@ async function createPyprojectToml({
3529
3544
  ].join("\n");
3530
3545
  await import_fs3.default.promises.writeFile(pyprojectPath, content);
3531
3546
  }
3532
- function getDependencyName(spec) {
3533
- const match = spec.match(/^[A-Za-z0-9_.-]+/);
3534
- return match ? match[0].toLowerCase() : spec.toLowerCase();
3535
- }
3536
- async function filterMissingRuntimeDependencies({
3537
- pyprojectPath,
3538
- runtimeDependencies
3539
- }) {
3540
- let declared = [];
3541
- try {
3542
- const config = await (0, import_build_utils4.readConfigFile)(pyprojectPath);
3543
- declared = config?.project?.dependencies || [];
3544
- } catch (err) {
3545
- (0, import_build_utils4.debug)("Failed to parse pyproject.toml when filtering runtime deps", err);
3546
- }
3547
- const declaredNames = new Set(declared.map(getDependencyName));
3548
- return runtimeDependencies.filter((spec) => {
3549
- const name = getDependencyName(spec);
3550
- return !declaredNames.has(name);
3551
- });
3552
- }
3553
3547
  function findUvLockUpwards(startDir, repoRootPath) {
3554
3548
  const start = (0, import_path4.resolve)(startDir);
3555
3549
  const base = repoRootPath ? (0, import_path4.resolve)(repoRootPath) : void 0;
@@ -3572,8 +3566,7 @@ async function ensureUvProject({
3572
3566
  pythonVersion,
3573
3567
  uv,
3574
3568
  venvPath,
3575
- meta,
3576
- runtimeDependencies
3569
+ meta
3577
3570
  }) {
3578
3571
  const uvPath = uv.getPath();
3579
3572
  const installInfo = await detectInstallSource({
@@ -3680,19 +3673,6 @@ async function ensureUvProject({
3680
3673
  });
3681
3674
  await uv.lock(projectDir);
3682
3675
  }
3683
- if (runtimeDependencies.length) {
3684
- const missingRuntimeDeps = await filterMissingRuntimeDependencies({
3685
- pyprojectPath,
3686
- runtimeDependencies
3687
- });
3688
- if (missingRuntimeDeps.length) {
3689
- await uv.addDependencies({
3690
- venvPath,
3691
- projectDir,
3692
- dependencies: missingRuntimeDeps
3693
- });
3694
- }
3695
- }
3696
3676
  const resolvedLockPath = lockPath && import_fs3.default.existsSync(lockPath) ? lockPath : findUvLockUpwards(projectDir, repoRootPath) || (0, import_path4.join)(projectDir, "uv.lock");
3697
3677
  return { projectDir, pyprojectPath, lockPath: resolvedLockPath };
3698
3678
  }
@@ -3977,6 +3957,20 @@ var ANSI_ESCAPE_RE = new RegExp(ANSI_PATTERN, "g");
3977
3957
  var stripAnsi = (s) => s.replace(ANSI_ESCAPE_RE, "");
3978
3958
  var ASGI_SHIM_MODULE = "vc_init_dev_asgi";
3979
3959
  var WSGI_SHIM_MODULE = "vc_init_dev_wsgi";
3960
+ function createLogListener(callback, stream) {
3961
+ return (buf) => {
3962
+ if (callback) {
3963
+ callback(buf);
3964
+ } else {
3965
+ const s = buf.toString();
3966
+ for (const line of s.split(/\r?\n/)) {
3967
+ if (line) {
3968
+ stream.write(line.endsWith("\n") ? line : line + "\n");
3969
+ }
3970
+ }
3971
+ }
3972
+ };
3973
+ }
3980
3974
  var PERSISTENT_SERVERS = /* @__PURE__ */ new Map();
3981
3975
  var PENDING_STARTS = /* @__PURE__ */ new Map();
3982
3976
  var restoreWarnings = null;
@@ -4053,7 +4047,14 @@ function createDevWsgiShim(workPath, modulePath) {
4053
4047
  }
4054
4048
  }
4055
4049
  var startDevServer = async (opts) => {
4056
- const { entrypoint: rawEntrypoint, workPath, meta = {}, config } = opts;
4050
+ const {
4051
+ entrypoint: rawEntrypoint,
4052
+ workPath,
4053
+ meta = {},
4054
+ config,
4055
+ onStdout,
4056
+ onStderr
4057
+ } = opts;
4057
4058
  const framework = config?.framework;
4058
4059
  if (framework !== "fastapi" && framework !== "flask") {
4059
4060
  return null;
@@ -4161,22 +4162,8 @@ If you are using a virtual environment, activate it before running "vercel dev",
4161
4162
  stdio: ["inherit", "pipe", "pipe"]
4162
4163
  });
4163
4164
  childProcess = child;
4164
- stdoutLogListener = (buf) => {
4165
- const s = buf.toString();
4166
- for (const line of s.split(/\r?\n/)) {
4167
- if (line) {
4168
- process.stdout.write(line.endsWith("\n") ? line : line + "\n");
4169
- }
4170
- }
4171
- };
4172
- stderrLogListener = (buf) => {
4173
- const s = buf.toString();
4174
- for (const line of s.split(/\r?\n/)) {
4175
- if (line) {
4176
- process.stderr.write(line.endsWith("\n") ? line : line + "\n");
4177
- }
4178
- }
4179
- };
4165
+ stdoutLogListener = createLogListener(onStdout, process.stdout);
4166
+ stderrLogListener = createLogListener(onStderr, process.stderr);
4180
4167
  child.stdout?.on("data", stdoutLogListener);
4181
4168
  child.stderr?.on("data", stderrLogListener);
4182
4169
  const readinessRegexes = [
@@ -4239,22 +4226,8 @@ If you are using a virtual environment, activate it before running "vercel dev",
4239
4226
  stdio: ["inherit", "pipe", "pipe"]
4240
4227
  });
4241
4228
  childProcess = child;
4242
- stdoutLogListener = (buf) => {
4243
- const s = buf.toString();
4244
- for (const line of s.split(/\r?\n/)) {
4245
- if (line) {
4246
- process.stdout.write(line.endsWith("\n") ? line : line + "\n");
4247
- }
4248
- }
4249
- };
4250
- stderrLogListener = (buf) => {
4251
- const s = buf.toString();
4252
- for (const line of s.split(/\r?\n/)) {
4253
- if (line) {
4254
- process.stderr.write(line.endsWith("\n") ? line : line + "\n");
4255
- }
4256
- }
4257
- };
4229
+ stdoutLogListener = createLogListener(onStdout, process.stdout);
4230
+ stderrLogListener = createLogListener(onStderr, process.stderr);
4258
4231
  child.stdout?.on("data", stdoutLogListener);
4259
4232
  child.stderr?.on("data", stderrLogListener);
4260
4233
  const readinessRegexes = [
@@ -4515,86 +4488,70 @@ var build = async ({
4515
4488
  pythonPath: pythonVersion.pythonPath,
4516
4489
  venvPath
4517
4490
  });
4518
- const hasCustomInstallCommand = (0, import_build_utils8.isPythonFramework)(framework) && !!projectInstallCommand;
4519
- let useRuntime = false;
4520
- if (hasCustomInstallCommand) {
4521
- const baseEnv = spawnEnv || process.env;
4522
- const pythonEnv = createVenvEnv(venvPath, baseEnv);
4523
- pythonEnv.VERCEL_PYTHON_VENV_PATH = venvPath;
4524
- const installCommand = projectInstallCommand;
4525
- console.log(`Running "install" command: \`${installCommand}\`...`);
4526
- await (0, import_build_utils8.execCommand)(installCommand, {
4491
+ const baseEnv = spawnEnv || process.env;
4492
+ const pythonEnv = createVenvEnv(venvPath, baseEnv);
4493
+ pythonEnv.VERCEL_PYTHON_VENV_PATH = venvPath;
4494
+ let assumeDepsInstalled = false;
4495
+ if (projectInstallCommand) {
4496
+ console.log(`Running "install" command: \`${projectInstallCommand}\`...`);
4497
+ await (0, import_build_utils8.execCommand)(projectInstallCommand, {
4527
4498
  env: pythonEnv,
4528
4499
  cwd: workPath
4529
4500
  });
4501
+ assumeDepsInstalled = true;
4530
4502
  } else {
4531
- let ranPyprojectInstall = false;
4532
- if ((0, import_build_utils8.isPythonFramework)(framework)) {
4533
- const baseEnv = spawnEnv || process.env;
4534
- const pythonEnv = createVenvEnv(venvPath, baseEnv);
4535
- pythonEnv.VERCEL_PYTHON_VENV_PATH = venvPath;
4536
- ranPyprojectInstall = await runPyprojectScript(
4537
- workPath,
4538
- ["vercel-install", "now-install", "install"],
4539
- pythonEnv,
4540
- /* useUserVirtualEnv */
4541
- false
4542
- );
4543
- }
4544
- if (!ranPyprojectInstall) {
4545
- let uv;
4546
- try {
4547
- const uvPath = await getUvBinaryOrInstall(pythonVersion.pythonPath);
4548
- console.log(`Using uv at "${uvPath}"`);
4549
- uv = new UvRunner(uvPath);
4550
- } catch (err) {
4551
- console.log("Failed to install or locate uv");
4552
- throw new Error(
4553
- `uv is required for this project but failed to install: ${err instanceof Error ? err.message : String(err)}`
4554
- );
4555
- }
4556
- const baseEnv = spawnEnv || process.env;
4557
- useRuntime = !!baseEnv.VERCEL_RUNTIME_PYTHON_ENABLED;
4558
- const runtimeDependencies = [];
4559
- if (useRuntime) {
4560
- runtimeDependencies.push(
4561
- baseEnv.VERCEL_RUNTIME_PYTHON || `vercel-runtime==${VERCEL_RUNTIME_VERSION}`
4562
- );
4563
- } else {
4564
- runtimeDependencies.push("werkzeug>=1.0.1");
4565
- if (framework !== "flask") {
4566
- runtimeDependencies.push("uvicorn>=0.24");
4567
- }
4568
- }
4569
- const { projectDir } = await ensureUvProject({
4570
- workPath,
4571
- entryDirectory,
4572
- fsFiles,
4573
- repoRootPath,
4574
- pythonPath: pythonVersion.pythonPath,
4575
- pipPath: pythonVersion.pipPath,
4576
- pythonVersion: pythonVersion.version,
4577
- uv,
4578
- venvPath,
4579
- meta,
4580
- runtimeDependencies
4581
- });
4582
- await uv.sync({
4583
- venvPath,
4584
- projectDir,
4585
- locked: true
4586
- });
4587
- }
4503
+ assumeDepsInstalled = await runPyprojectScript(
4504
+ workPath,
4505
+ ["vercel-install", "now-install", "install"],
4506
+ pythonEnv,
4507
+ /* useUserVirtualEnv */
4508
+ false
4509
+ );
4510
+ }
4511
+ let uv;
4512
+ try {
4513
+ const uvPath = await getUvBinaryOrInstall(pythonVersion.pythonPath);
4514
+ console.log(`Using uv at "${uvPath}"`);
4515
+ uv = new UvRunner(uvPath);
4516
+ } catch (err) {
4517
+ console.log("Failed to install or locate uv");
4518
+ throw new Error(
4519
+ `uv is required for this project but failed to install: ${err instanceof Error ? err.message : String(err)}`
4520
+ );
4588
4521
  }
4522
+ if (!assumeDepsInstalled) {
4523
+ const { projectDir } = await ensureUvProject({
4524
+ workPath,
4525
+ entryDirectory,
4526
+ fsFiles,
4527
+ repoRootPath,
4528
+ pythonPath: pythonVersion.pythonPath,
4529
+ pipPath: pythonVersion.pipPath,
4530
+ pythonVersion: pythonVersion.version,
4531
+ uv,
4532
+ venvPath,
4533
+ meta
4534
+ });
4535
+ await uv.sync({
4536
+ venvPath,
4537
+ projectDir,
4538
+ locked: true
4539
+ });
4540
+ }
4541
+ const runtimeDep = baseEnv.VERCEL_RUNTIME_PYTHON || `vercel-runtime==${VERCEL_RUNTIME_VERSION}`;
4542
+ (0, import_build_utils8.debug)(`Installing ${runtimeDep}`);
4543
+ await uv.pip({
4544
+ venvPath,
4545
+ projectDir: entryDirectory,
4546
+ args: ["install", runtimeDep]
4547
+ });
4589
4548
  (0, import_build_utils8.debug)("Entrypoint is", entrypoint);
4590
4549
  const moduleName = entrypoint.replace(/\//g, ".").replace(/\.py$/i, "");
4591
4550
  const vendorDir = resolveVendorDir();
4592
4551
  const suffix = meta.isDev && !entrypoint.endsWith(".py") ? ".py" : "";
4593
4552
  const entrypointWithSuffix = `${entrypoint}${suffix}`;
4594
4553
  (0, import_build_utils8.debug)("Entrypoint with suffix is", entrypointWithSuffix);
4595
- let handlerPyContents;
4596
- if (useRuntime) {
4597
- handlerPyContents = `
4554
+ const runtimeTrampoline = `
4598
4555
  import importlib
4599
4556
  import os
4600
4557
  import os.path
@@ -4632,11 +4589,6 @@ if os.path.isdir(_vendor):
4632
4589
 
4633
4590
  from vercel_runtime.vc_init import vc_handler
4634
4591
  `;
4635
- } else {
4636
- const originalPyPath = (0, import_path7.join)(__dirname, "..", "vc_init.py");
4637
- const originalHandlerPyContents = await readFile(originalPyPath, "utf8");
4638
- handlerPyContents = originalHandlerPyContents.replace(/__VC_HANDLER_MODULE_NAME/g, moduleName).replace(/__VC_HANDLER_ENTRYPOINT/g, entrypointWithSuffix).replace(/__VC_HANDLER_VENDOR_DIR/g, vendorDir);
4639
- }
4640
4592
  const predefinedExcludes = [
4641
4593
  ".git/**",
4642
4594
  ".gitignore",
@@ -4670,7 +4622,7 @@ from vercel_runtime.vc_init import vc_handler
4670
4622
  files[p] = f;
4671
4623
  }
4672
4624
  const handlerPyFilename = "vc__handler__python";
4673
- files[`${handlerPyFilename}.py`] = new import_build_utils8.FileBlob({ data: handlerPyContents });
4625
+ files[`${handlerPyFilename}.py`] = new import_build_utils8.FileBlob({ data: runtimeTrampoline });
4674
4626
  if (config.framework === "fasthtml") {
4675
4627
  const { SESSKEY = "" } = process.env;
4676
4628
  files[".sesskey"] = new import_build_utils8.FileBlob({ data: `"${SESSKEY}"` });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "6.5.1",
3
+ "version": "6.7.0",
4
4
  "main": "./dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -31,7 +31,7 @@
31
31
  "smol-toml": "1.5.2",
32
32
  "vitest": "2.1.4",
33
33
  "which": "3.0.0",
34
- "@vercel/build-utils": "13.2.17",
34
+ "@vercel/build-utils": "13.3.2",
35
35
  "@vercel/error-utils": "2.0.3"
36
36
  },
37
37
  "scripts": {
package/vc_init.py DELETED
@@ -1,916 +0,0 @@
1
- from __future__ import annotations
2
- import sys
3
- import os
4
- import site
5
- import importlib
6
- import base64
7
- import json
8
- import inspect
9
- import asyncio
10
- import http
11
- import time
12
- import traceback
13
- from importlib import util
14
- from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
15
- import socket
16
- import functools
17
- import logging
18
- import builtins
19
- from typing import Callable, Literal, TextIO
20
- import contextvars
21
- import contextlib
22
- import atexit
23
-
24
-
25
- _here = os.path.dirname(__file__)
26
- _vendor_rel = '__VC_HANDLER_VENDOR_DIR'
27
- _vendor = os.path.normpath(os.path.join(_here, _vendor_rel))
28
-
29
- if os.path.isdir(_vendor):
30
- # Process .pth files like a real site-packages dir
31
- site.addsitedir(_vendor)
32
-
33
- # Move _vendor to the front (after script dir if present)
34
- try:
35
- while _vendor in sys.path:
36
- sys.path.remove(_vendor)
37
- except ValueError:
38
- pass
39
-
40
- # Put vendored deps ahead of site-packages but after the script dir
41
- idx = 1 if (sys.path and sys.path[0] in ('', _here)) else 0
42
- sys.path.insert(idx, _vendor)
43
-
44
- importlib.invalidate_caches()
45
-
46
-
47
- def setup_logging(send_message: Callable[[dict], None], storage: contextvars.ContextVar[dict | None]):
48
- # Override logging.Handler to send logs to the platform when a request context is available.
49
- class VCLogHandler(logging.Handler):
50
- def emit(self, record: logging.LogRecord):
51
- try:
52
- message = record.getMessage()
53
- except Exception:
54
- message = repr(getattr(record, "msg", ""))
55
-
56
- with contextlib.suppress(Exception):
57
- if record.exc_info:
58
- # logging allows exc_info=True or a (type, value, tb) tuple
59
- exc_info = record.exc_info
60
- if exc_info is True:
61
- exc_info = sys.exc_info()
62
- if isinstance(exc_info, tuple):
63
- tb = ''.join(traceback.format_exception(*exc_info))
64
- if tb:
65
- if message:
66
- message = f"{message}\n{tb}"
67
- else:
68
- message = tb
69
-
70
- if record.levelno >= logging.CRITICAL:
71
- level = "fatal"
72
- elif record.levelno >= logging.ERROR:
73
- level = "error"
74
- elif record.levelno >= logging.WARNING:
75
- level = "warn"
76
- elif record.levelno >= logging.INFO:
77
- level = "info"
78
- else:
79
- level = "debug"
80
-
81
- context = storage.get()
82
- if context is not None:
83
- send_message({
84
- "type": "log",
85
- "payload": {
86
- "context": {
87
- "invocationId": context['invocationId'],
88
- "requestId": context['requestId'],
89
- },
90
- "message": base64.b64encode(message.encode()).decode(),
91
- "level": level,
92
- }
93
- })
94
- else:
95
- # If IPC is not ready, enqueue the message to be sent later.
96
- enqueue_or_send_message({
97
- "type": "log",
98
- "payload": {
99
- "context": {"invocationId": "0", "requestId": 0},
100
- "message": base64.b64encode(message.encode()).decode(),
101
- "level": level,
102
- }
103
- })
104
-
105
- # Override sys.stdout and sys.stderr to map logs to the correct request
106
- class StreamWrapper:
107
- def __init__(self, stream: TextIO, stream_name: Literal["stdout", "stderr"]):
108
- self.stream = stream
109
- self.stream_name = stream_name
110
-
111
- def write(self, message: str):
112
- context = storage.get()
113
- if context is not None:
114
- send_message({
115
- "type": "log",
116
- "payload": {
117
- "context": {
118
- "invocationId": context['invocationId'],
119
- "requestId": context['requestId'],
120
- },
121
- "message": base64.b64encode(message.encode()).decode(),
122
- "stream": self.stream_name,
123
- }
124
- })
125
- else:
126
- enqueue_or_send_message({
127
- "type": "log",
128
- "payload": {
129
- "context": {"invocationId": "0", "requestId": 0},
130
- "message": base64.b64encode(message.encode()).decode(),
131
- "stream": self.stream_name,
132
- }
133
- })
134
-
135
- def __getattr__(self, name):
136
- return getattr(self.stream, name)
137
-
138
- sys.stdout = StreamWrapper(sys.stdout, "stdout")
139
- sys.stderr = StreamWrapper(sys.stderr, "stderr")
140
-
141
- logging.basicConfig(level=logging.INFO, handlers=[VCLogHandler()], force=True)
142
-
143
- # Ensure built-in print funnels through stdout wrapper so prints are
144
- # attributed to the current request context.
145
- def print_wrapper(func: Callable[..., None]) -> Callable[..., None]:
146
- @functools.wraps(func)
147
- def wrapper(*args, sep=' ', end='\n', file=None, flush=False):
148
- if file is None:
149
- file = sys.stdout
150
- if file in (sys.stdout, sys.stderr):
151
- file.write(sep.join(map(str, args)) + end)
152
- if flush:
153
- file.flush()
154
- else:
155
- # User specified a different file, use original print behavior
156
- func(*args, sep=sep, end=end, file=file, flush=flush)
157
- return wrapper
158
-
159
- builtins.print = print_wrapper(builtins.print)
160
-
161
-
162
- def _stderr(message: str):
163
- with contextlib.suppress(Exception):
164
- _original_stderr.write(message + "\n")
165
- _original_stderr.flush()
166
-
167
-
168
- # If running in the platform (IPC present), logging must be setup before importing user code so that
169
- # logs happening outside the request context are emitted correctly.
170
- ipc_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
171
- storage: contextvars.ContextVar[dict | None] = contextvars.ContextVar('storage', default=None)
172
- send_message = lambda m: None
173
- _original_stderr = sys.stderr
174
-
175
-
176
- # Buffer for pre-handshake logs (to avoid blocking IPC on startup)
177
- _ipc_ready = False
178
- _init_log_buf: list[dict] = []
179
- _INIT_LOG_BUF_MAX_BYTES = 1_000_000
180
- _init_log_buf_bytes = 0
181
-
182
-
183
- def enqueue_or_send_message(msg: dict):
184
- global _init_log_buf_bytes
185
- if _ipc_ready:
186
- send_message(msg)
187
- return
188
-
189
- enc_len = len(json.dumps(msg))
190
-
191
- if _init_log_buf_bytes + enc_len <= _INIT_LOG_BUF_MAX_BYTES:
192
- _init_log_buf.append(msg)
193
- _init_log_buf_bytes += enc_len
194
- else:
195
- # Fallback so message is not lost if buffer is full
196
- with contextlib.suppress(Exception):
197
- payload = msg.get("payload", {})
198
- decoded = base64.b64decode(payload.get("message", "")).decode(errors="ignore")
199
- _original_stderr.write(decoded + "\n")
200
-
201
-
202
- def flush_init_log_buf_to_stderr():
203
- global _init_log_buf, _init_log_buf_bytes
204
- try:
205
- combined: list[str] = []
206
- for m in _init_log_buf:
207
- payload = m.get("payload", {})
208
- msg = payload.get("message")
209
- if not msg:
210
- continue
211
- with contextlib.suppress(Exception):
212
- decoded = base64.b64decode(msg).decode(errors="ignore")
213
- combined.append(decoded)
214
- if combined:
215
- _stderr("".join(combined))
216
- except Exception:
217
- pass
218
- finally:
219
- _init_log_buf.clear()
220
- _init_log_buf_bytes = 0
221
-
222
-
223
- atexit.register(flush_init_log_buf_to_stderr)
224
-
225
-
226
- if 'VERCEL_IPC_PATH' in os.environ:
227
- with contextlib.suppress(Exception):
228
- ipc_sock.connect(os.getenv("VERCEL_IPC_PATH", ""))
229
-
230
- def send_message(message: dict):
231
- with contextlib.suppress(Exception):
232
- ipc_sock.sendall((json.dumps(message) + '\0').encode())
233
-
234
- setup_logging(send_message, storage)
235
-
236
-
237
- # Import relative path https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
238
- try:
239
- user_mod_path = os.path.join(_here, "__VC_HANDLER_ENTRYPOINT") # absolute
240
- __vc_spec = util.spec_from_file_location("__VC_HANDLER_MODULE_NAME", user_mod_path)
241
- __vc_module = util.module_from_spec(__vc_spec)
242
- sys.modules["__VC_HANDLER_MODULE_NAME"] = __vc_module
243
- __vc_spec.loader.exec_module(__vc_module)
244
- __vc_variables = dir(__vc_module)
245
- except Exception:
246
- _stderr(f'Error importing __VC_HANDLER_ENTRYPOINT:')
247
- _stderr(traceback.format_exc())
248
- exit(1)
249
-
250
- _use_legacy_asyncio = sys.version_info < (3, 10)
251
-
252
- def format_headers(headers, decode=False):
253
- keyToList = {}
254
- for key, value in headers.items():
255
- if decode and 'decode' in dir(key) and 'decode' in dir(value):
256
- key = key.decode()
257
- value = value.decode()
258
- if key not in keyToList:
259
- keyToList[key] = []
260
- keyToList[key].append(value)
261
- return keyToList
262
-
263
-
264
- class ASGIMiddleware:
265
- """
266
- ASGI middleware that preserves Vercel IPC semantics for request lifecycle:
267
- - Handles /_vercel/ping
268
- - Extracts x-vercel-internal-* headers and removes them from downstream app
269
- - Sets request context into `storage` for logging/metrics
270
- - Emits handler-started and end IPC messages
271
- """
272
- def __init__(self, app):
273
- self.app = app
274
-
275
- async def __call__(self, scope, receive, send):
276
- if scope.get('type') != 'http':
277
- # Non-HTTP traffic is forwarded verbatim
278
- await self.app(scope, receive, send)
279
- return
280
-
281
- if scope.get('path') == '/_vercel/ping':
282
- await send({
283
- 'type': 'http.response.start',
284
- 'status': 200,
285
- 'headers': [],
286
- })
287
- await send({
288
- 'type': 'http.response.body',
289
- 'body': b'',
290
- 'more_body': False,
291
- })
292
- return
293
-
294
- # Extract internal headers and set per-request context
295
- headers_list = scope.get('headers', []) or []
296
- new_headers = []
297
- invocation_id = "0"
298
- request_id = 0
299
-
300
- def _b2s(b: bytes) -> str:
301
- try:
302
- return b.decode()
303
- except Exception:
304
- return ''
305
-
306
- for k, v in headers_list:
307
- key = _b2s(k).lower()
308
- val = _b2s(v)
309
- if key == 'x-vercel-internal-invocation-id':
310
- invocation_id = val
311
- continue
312
- if key == 'x-vercel-internal-request-id':
313
- request_id = int(val) if val.isdigit() else 0
314
- continue
315
- if key in ('x-vercel-internal-span-id', 'x-vercel-internal-trace-id'):
316
- continue
317
- new_headers.append((k, v))
318
-
319
- new_scope = dict(scope)
320
- new_scope['headers'] = new_headers
321
-
322
- # Announce handler start and set context for logging/metrics
323
- send_message({
324
- "type": "handler-started",
325
- "payload": {
326
- "handlerStartedAt": int(time.time() * 1000),
327
- "context": {
328
- "invocationId": invocation_id,
329
- "requestId": request_id,
330
- }
331
- }
332
- })
333
-
334
- token = storage.set({
335
- "invocationId": invocation_id,
336
- "requestId": request_id,
337
- })
338
-
339
- try:
340
- await self.app(new_scope, receive, send)
341
- finally:
342
- storage.reset(token)
343
- send_message({
344
- "type": "end",
345
- "payload": {
346
- "context": {
347
- "invocationId": invocation_id,
348
- "requestId": request_id,
349
- }
350
- }
351
- })
352
-
353
- if 'VERCEL_IPC_PATH' in os.environ:
354
- start_time = time.time()
355
-
356
- # Override urlopen from urllib3 (& requests) to send Request Metrics
357
- try:
358
- import urllib3
359
- from urllib.parse import urlparse
360
-
361
- def timed_request(func):
362
- fetchId = 0
363
- @functools.wraps(func)
364
- def wrapper(self, method, url, *args, **kwargs):
365
- nonlocal fetchId
366
- fetchId += 1
367
- start_time = int(time.time() * 1000)
368
- result = func(self, method, url, *args, **kwargs)
369
- elapsed_time = int(time.time() * 1000) - start_time
370
- parsed_url = urlparse(url)
371
- context = storage.get()
372
- if context is not None:
373
- send_message({
374
- "type": "metric",
375
- "payload": {
376
- "context": {
377
- "invocationId": context['invocationId'],
378
- "requestId": context['requestId'],
379
- },
380
- "type": "fetch-metric",
381
- "payload": {
382
- "pathname": parsed_url.path,
383
- "search": parsed_url.query,
384
- "start": start_time,
385
- "duration": elapsed_time,
386
- "host": parsed_url.hostname or self.host,
387
- "statusCode": result.status,
388
- "method": method,
389
- "id": fetchId
390
- }
391
- }
392
- })
393
- return result
394
- return wrapper
395
- urllib3.connectionpool.HTTPConnectionPool.urlopen = timed_request(urllib3.connectionpool.HTTPConnectionPool.urlopen)
396
- except:
397
- pass
398
-
399
- class BaseHandler(BaseHTTPRequestHandler):
400
- # Re-implementation of BaseHTTPRequestHandler's log_message method to
401
- # log to stdout instead of stderr.
402
- def log_message(self, format, *args):
403
- message = format % args
404
- sys.stdout.write("%s - - [%s] %s\n" %
405
- (self.address_string(),
406
- self.log_date_time_string(),
407
- message.translate(self._control_char_table)))
408
-
409
- # Re-implementation of BaseHTTPRequestHandler's handle_one_request method
410
- # to send the end message after the response is fully sent.
411
- def handle_one_request(self):
412
- self.raw_requestline = self.rfile.readline(65537)
413
- if not self.raw_requestline:
414
- self.close_connection = True
415
- return
416
- if not self.parse_request():
417
- return
418
-
419
- if self.path == '/_vercel/ping':
420
- self.send_response(200)
421
- self.end_headers()
422
- return
423
-
424
- invocationId = self.headers.get('x-vercel-internal-invocation-id')
425
- requestId = int(self.headers.get('x-vercel-internal-request-id'))
426
- del self.headers['x-vercel-internal-invocation-id']
427
- del self.headers['x-vercel-internal-request-id']
428
- del self.headers['x-vercel-internal-span-id']
429
- del self.headers['x-vercel-internal-trace-id']
430
-
431
- send_message({
432
- "type": "handler-started",
433
- "payload": {
434
- "handlerStartedAt": int(time.time() * 1000),
435
- "context": {
436
- "invocationId": invocationId,
437
- "requestId": requestId,
438
- }
439
- }
440
- })
441
-
442
- token = storage.set({
443
- "invocationId": invocationId,
444
- "requestId": requestId,
445
- })
446
-
447
- try:
448
- self.handle_request()
449
- finally:
450
- storage.reset(token)
451
- send_message({
452
- "type": "end",
453
- "payload": {
454
- "context": {
455
- "invocationId": invocationId,
456
- "requestId": requestId,
457
- }
458
- }
459
- })
460
-
461
- if 'handler' in __vc_variables or 'Handler' in __vc_variables:
462
- base = __vc_module.handler if ('handler' in __vc_variables) else __vc_module.Handler
463
- if not issubclass(base, BaseHTTPRequestHandler):
464
- _stderr('Handler must inherit from BaseHTTPRequestHandler')
465
- _stderr('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
466
- exit(1)
467
-
468
- class Handler(BaseHandler, base):
469
- def handle_request(self):
470
- mname = 'do_' + self.command
471
- if not hasattr(self, mname):
472
- self.send_error(
473
- http.HTTPStatus.NOT_IMPLEMENTED,
474
- "Unsupported method (%r)" % self.command)
475
- return
476
- method = getattr(self, mname)
477
- method()
478
- self.wfile.flush()
479
- elif 'app' in __vc_variables:
480
- if (
481
- not inspect.iscoroutinefunction(__vc_module.app) and
482
- not inspect.iscoroutinefunction(__vc_module.app.__call__)
483
- ):
484
- from io import BytesIO
485
-
486
- string_types = (str,)
487
- app = __vc_module.app
488
-
489
- def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
490
- if isinstance(s, str):
491
- s = s.encode(charset)
492
- return s.decode("latin1", errors)
493
-
494
- class Handler(BaseHandler):
495
- def handle_request(self):
496
- # Prepare WSGI environment
497
- if '?' in self.path:
498
- path, query = self.path.split('?', 1)
499
- else:
500
- path, query = self.path, ''
501
- content_length = int(self.headers.get('Content-Length', 0))
502
- env = {
503
- 'CONTENT_LENGTH': str(content_length),
504
- 'CONTENT_TYPE': self.headers.get('content-type', ''),
505
- 'PATH_INFO': path,
506
- 'QUERY_STRING': query,
507
- 'REMOTE_ADDR': self.headers.get(
508
- 'x-forwarded-for', self.headers.get(
509
- 'x-real-ip')),
510
- 'REQUEST_METHOD': self.command,
511
- 'SERVER_NAME': self.headers.get('host', 'lambda'),
512
- 'SERVER_PORT': self.headers.get('x-forwarded-port', '80'),
513
- 'SERVER_PROTOCOL': 'HTTP/1.1',
514
- 'wsgi.errors': sys.stderr,
515
- 'wsgi.input': BytesIO(self.rfile.read(content_length)),
516
- 'wsgi.multiprocess': False,
517
- 'wsgi.multithread': False,
518
- 'wsgi.run_once': False,
519
- 'wsgi.url_scheme': self.headers.get('x-forwarded-proto', 'http'),
520
- 'wsgi.version': (1, 0),
521
- }
522
- for key, value in env.items():
523
- if isinstance(value, string_types):
524
- env[key] = wsgi_encoding_dance(value)
525
- for k, v in self.headers.items():
526
- env['HTTP_' + k.replace('-', '_').upper()] = v
527
-
528
- def start_response(status, headers, exc_info=None):
529
- self.send_response(int(status.split(' ')[0]))
530
- for name, value in headers:
531
- self.send_header(name, value)
532
- self.end_headers()
533
- return self.wfile.write
534
-
535
- # Call the application
536
- response = app(env, start_response)
537
- try:
538
- for data in response:
539
- if data:
540
- self.wfile.write(data)
541
- self.wfile.flush()
542
- finally:
543
- if hasattr(response, 'close'):
544
- response.close()
545
- else:
546
- # ASGI: Run with Uvicorn so we get proper lifespan and protocol handling
547
- try:
548
- import uvicorn
549
- except Exception:
550
- _stderr('Uvicorn is required to run ASGI apps. Please ensure it is installed.')
551
- exit(1)
552
-
553
- # Prefer a callable app.asgi when available; some frameworks expose a boolean here
554
- user_app_candidate = getattr(__vc_module.app, 'asgi', None)
555
- user_app = user_app_candidate if callable(user_app_candidate) else __vc_module.app
556
- asgi_app = ASGIMiddleware(user_app)
557
-
558
- # Pre-bind a socket to obtain an ephemeral port for IPC announcement
559
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
560
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
561
- sock.bind(('127.0.0.1', 0))
562
- sock.listen(2048)
563
- http_port = sock.getsockname()[1]
564
-
565
- config = uvicorn.Config(
566
- app=asgi_app,
567
- fd=sock.fileno(),
568
- lifespan='auto',
569
- access_log=False,
570
- log_config=None,
571
- log_level='warning',
572
- )
573
- server = uvicorn.Server(config)
574
-
575
- send_message({
576
- "type": "server-started",
577
- "payload": {
578
- "initDuration": int((time.time() - start_time) * 1000),
579
- "httpPort": http_port,
580
- }
581
- })
582
-
583
- # Mark IPC as ready and flush any buffered init logs
584
- _ipc_ready = True
585
- for m in _init_log_buf:
586
- send_message(m)
587
- _init_log_buf.clear()
588
-
589
- # Run the server (blocking)
590
- server.run()
591
- # If the server ever returns, exit
592
- sys.exit(0)
593
-
594
- if 'Handler' in locals():
595
- server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
596
- send_message({
597
- "type": "server-started",
598
- "payload": {
599
- "initDuration": int((time.time() - start_time) * 1000),
600
- "httpPort": server.server_address[1],
601
- }
602
- })
603
- # Mark IPC as ready and flush any buffered init logs
604
- _ipc_ready = True
605
- for m in _init_log_buf:
606
- send_message(m)
607
- _init_log_buf.clear()
608
- server.serve_forever()
609
-
610
- _stderr('Missing variable `handler` or `app` in file "__VC_HANDLER_ENTRYPOINT".')
611
- _stderr('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
612
- exit(1)
613
-
614
- if 'handler' in __vc_variables or 'Handler' in __vc_variables:
615
- base = __vc_module.handler if ('handler' in __vc_variables) else __vc_module.Handler
616
- if not issubclass(base, BaseHTTPRequestHandler):
617
- print('Handler must inherit from BaseHTTPRequestHandler')
618
- print('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
619
- exit(1)
620
-
621
- print('using HTTP Handler')
622
- from http.server import HTTPServer
623
- import http
624
- import _thread
625
-
626
- server = HTTPServer(('127.0.0.1', 0), base)
627
- port = server.server_address[1]
628
-
629
- def vc_handler(event, context):
630
- _thread.start_new_thread(server.handle_request, ())
631
-
632
- payload = json.loads(event['body'])
633
- path = payload['path']
634
- headers = payload['headers']
635
- method = payload['method']
636
- encoding = payload.get('encoding')
637
- body = payload.get('body')
638
-
639
- if (
640
- (body is not None and len(body) > 0) and
641
- (encoding is not None and encoding == 'base64')
642
- ):
643
- body = base64.b64decode(body)
644
-
645
- request_body = body.encode('utf-8') if isinstance(body, str) else body
646
- conn = http.client.HTTPConnection('127.0.0.1', port)
647
- try:
648
- conn.request(method, path, headers=headers, body=request_body)
649
- except (http.client.HTTPException, socket.error) as ex:
650
- print ("Request Error: %s" % ex)
651
- res = conn.getresponse()
652
-
653
- return_dict = {
654
- 'statusCode': res.status,
655
- 'headers': format_headers(res.headers),
656
- }
657
-
658
- data = res.read()
659
-
660
- try:
661
- return_dict['body'] = data.decode('utf-8')
662
- except UnicodeDecodeError:
663
- return_dict['body'] = base64.b64encode(data).decode('utf-8')
664
- return_dict['encoding'] = 'base64'
665
-
666
- return return_dict
667
-
668
- elif 'app' in __vc_variables:
669
- if (
670
- not inspect.iscoroutinefunction(__vc_module.app) and
671
- not inspect.iscoroutinefunction(__vc_module.app.__call__)
672
- ):
673
- print('using Web Server Gateway Interface (WSGI)')
674
- from io import BytesIO
675
- from urllib.parse import urlparse
676
- from werkzeug.datastructures import Headers
677
- from werkzeug.wrappers import Response
678
-
679
- string_types = (str,)
680
-
681
- def to_bytes(x, charset=sys.getdefaultencoding(), errors="strict"):
682
- if x is None:
683
- return None
684
- if isinstance(x, (bytes, bytearray, memoryview)):
685
- return bytes(x)
686
- if isinstance(x, str):
687
- return x.encode(charset, errors)
688
- raise TypeError("Expected bytes")
689
-
690
- def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
691
- if isinstance(s, str):
692
- s = s.encode(charset)
693
- return s.decode("latin1", errors)
694
-
695
- def vc_handler(event, context):
696
- payload = json.loads(event['body'])
697
-
698
- headers = Headers(payload.get('headers', {}))
699
-
700
- body = payload.get('body', '')
701
- if body != '':
702
- if payload.get('encoding') == 'base64':
703
- body = base64.b64decode(body)
704
- if isinstance(body, string_types):
705
- body = to_bytes(body, charset='utf-8')
706
-
707
- url = urlparse(payload['path'])
708
- query = url.query
709
- path = url.path
710
-
711
- environ = {
712
- 'CONTENT_LENGTH': str(len(body)),
713
- 'CONTENT_TYPE': headers.get('content-type', ''),
714
- 'PATH_INFO': path,
715
- 'QUERY_STRING': query,
716
- 'REMOTE_ADDR': headers.get(
717
- 'x-forwarded-for', headers.get(
718
- 'x-real-ip', payload.get(
719
- 'true-client-ip', ''))),
720
- 'REQUEST_METHOD': payload['method'],
721
- 'SERVER_NAME': headers.get('host', 'lambda'),
722
- 'SERVER_PORT': headers.get('x-forwarded-port', '80'),
723
- 'SERVER_PROTOCOL': 'HTTP/1.1',
724
- 'event': event,
725
- 'context': context,
726
- 'wsgi.errors': sys.stderr,
727
- 'wsgi.input': BytesIO(body),
728
- 'wsgi.multiprocess': False,
729
- 'wsgi.multithread': False,
730
- 'wsgi.run_once': False,
731
- 'wsgi.url_scheme': headers.get('x-forwarded-proto', 'http'),
732
- 'wsgi.version': (1, 0),
733
- }
734
-
735
- for key, value in environ.items():
736
- if isinstance(value, string_types):
737
- environ[key] = wsgi_encoding_dance(value)
738
-
739
- for key, value in headers.items():
740
- key = 'HTTP_' + key.upper().replace('-', '_')
741
- if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
742
- environ[key] = value
743
-
744
- response = Response.from_app(__vc_module.app, environ)
745
-
746
- return_dict = {
747
- 'statusCode': response.status_code,
748
- 'headers': format_headers(response.headers)
749
- }
750
-
751
- if response.data:
752
- return_dict['body'] = base64.b64encode(response.data).decode('utf-8')
753
- return_dict['encoding'] = 'base64'
754
-
755
- return return_dict
756
- else:
757
- print('using Asynchronous Server Gateway Interface (ASGI)')
758
- # Originally authored by Jordan Eremieff and included under MIT license:
759
- # https://github.com/erm/mangum/blob/b4d21c8f5e304a3e17b88bc9fa345106acc50ad7/mangum/__init__.py
760
- # https://github.com/erm/mangum/blob/b4d21c8f5e304a3e17b88bc9fa345106acc50ad7/LICENSE
761
- import asyncio
762
- import enum
763
- from urllib.parse import urlparse
764
- from werkzeug.datastructures import Headers
765
-
766
-
767
- class ASGICycleState(enum.Enum):
768
- REQUEST = enum.auto()
769
- RESPONSE = enum.auto()
770
-
771
-
772
- class ASGICycle:
773
- def __init__(self, scope):
774
- self.scope = scope
775
- self.body = b''
776
- self.state = ASGICycleState.REQUEST
777
- self.app_queue = None
778
- self.response = {}
779
-
780
- def __call__(self, app, body):
781
- """
782
- Receives the application and any body included in the request, then builds the
783
- ASGI instance using the connection scope.
784
- Runs until the response is completely read from the application.
785
- """
786
- if _use_legacy_asyncio:
787
- loop = asyncio.new_event_loop()
788
- self.app_queue = asyncio.Queue(loop=loop)
789
- else:
790
- self.app_queue = asyncio.Queue()
791
- self.put_message({'type': 'http.request', 'body': body, 'more_body': False})
792
-
793
- asgi_instance = app(self.scope, self.receive, self.send)
794
-
795
- if _use_legacy_asyncio:
796
- asgi_task = loop.create_task(asgi_instance)
797
- loop.run_until_complete(asgi_task)
798
- else:
799
- asyncio.run(self.run_asgi_instance(asgi_instance))
800
- return self.response
801
-
802
- async def run_asgi_instance(self, asgi_instance):
803
- await asgi_instance
804
-
805
- def put_message(self, message):
806
- self.app_queue.put_nowait(message)
807
-
808
- async def receive(self):
809
- """
810
- Awaited by the application to receive messages in the queue.
811
- """
812
- message = await self.app_queue.get()
813
- return message
814
-
815
- async def send(self, message):
816
- """
817
- Awaited by the application to send messages to the current cycle instance.
818
- """
819
- message_type = message['type']
820
-
821
- if self.state is ASGICycleState.REQUEST:
822
- if message_type != 'http.response.start':
823
- raise RuntimeError(
824
- f"Expected 'http.response.start', received: {message_type}"
825
- )
826
-
827
- status_code = message['status']
828
- raw_headers = message.get('headers', [])
829
-
830
- # Headers from werkzeug transform bytes header value
831
- # from b'value' to "b'value'" so we need to process
832
- # ASGI headers manually
833
- decoded_headers = []
834
- for key, value in raw_headers:
835
- decoded_key = key.decode() if isinstance(key, bytes) else key
836
- decoded_value = value.decode() if isinstance(value, bytes) else value
837
- decoded_headers.append((decoded_key, decoded_value))
838
-
839
- headers = Headers(decoded_headers)
840
-
841
- self.on_request(headers, status_code)
842
- self.state = ASGICycleState.RESPONSE
843
-
844
- elif self.state is ASGICycleState.RESPONSE:
845
- if message_type != 'http.response.body':
846
- raise RuntimeError(
847
- f"Expected 'http.response.body', received: {message_type}"
848
- )
849
-
850
- body = message.get('body', b'')
851
- more_body = message.get('more_body', False)
852
-
853
- # The body must be completely read before returning the response.
854
- self.body += body
855
-
856
- if not more_body:
857
- self.on_response()
858
- self.put_message({'type': 'http.disconnect'})
859
-
860
- def on_request(self, headers, status_code):
861
- self.response['statusCode'] = status_code
862
- self.response['headers'] = format_headers(headers, decode=True)
863
-
864
- def on_response(self):
865
- if self.body:
866
- self.response['body'] = base64.b64encode(self.body).decode('utf-8')
867
- self.response['encoding'] = 'base64'
868
-
869
- def vc_handler(event, context):
870
- payload = json.loads(event['body'])
871
-
872
- headers = payload.get('headers', {})
873
-
874
- body = payload.get('body', b'')
875
- if payload.get('encoding') == 'base64':
876
- body = base64.b64decode(body)
877
- elif not isinstance(body, bytes):
878
- body = body.encode()
879
-
880
- url = urlparse(payload['path'])
881
- query = url.query.encode()
882
- path = url.path
883
-
884
- headers_encoded = []
885
- for k, v in headers.items():
886
- # Cope with repeated headers in the encoding.
887
- if isinstance(v, list):
888
- headers_encoded.append([k.lower().encode(), [i.encode() for i in v]])
889
- else:
890
- headers_encoded.append([k.lower().encode(), v.encode()])
891
-
892
- scope = {
893
- 'server': (headers.get('host', 'lambda'), headers.get('x-forwarded-port', 80)),
894
- 'client': (headers.get(
895
- 'x-forwarded-for', headers.get(
896
- 'x-real-ip', payload.get(
897
- 'true-client-ip', ''))), 0),
898
- 'scheme': headers.get('x-forwarded-proto', 'http'),
899
- 'root_path': '',
900
- 'query_string': query,
901
- 'headers': headers_encoded,
902
- 'type': 'http',
903
- 'http_version': '1.1',
904
- 'method': payload['method'],
905
- 'path': path,
906
- 'raw_path': path.encode(),
907
- }
908
-
909
- asgi_cycle = ASGICycle(scope)
910
- response = asgi_cycle(__vc_module.app, body)
911
- return response
912
-
913
- else:
914
- print('Missing variable `handler` or `app` in file "__VC_HANDLER_ENTRYPOINT".')
915
- print('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
916
- exit(1)