@vercel/python 6.21.0 → 6.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2873,7 +2873,7 @@ var import_fs10 = __toESM(require("fs"));
2873
2873
  var import_path12 = require("path");
2874
2874
 
2875
2875
  // src/package-versions.ts
2876
- var VERCEL_RUNTIME_VERSION = "0.6.0";
2876
+ var VERCEL_RUNTIME_VERSION = "0.7.0";
2877
2877
  var VERCEL_WORKERS_VERSION = "0.0.12";
2878
2878
 
2879
2879
  // src/index.ts
@@ -4568,6 +4568,77 @@ async function runSync({
4568
4568
  });
4569
4569
  });
4570
4570
  }
4571
+ async function installVercelRuntime({
4572
+ workPath,
4573
+ uvPath,
4574
+ pythonBin,
4575
+ env,
4576
+ onStdout,
4577
+ onStderr
4578
+ }) {
4579
+ const targetDir = (0, import_path8.join)(workPath, ".vercel", "python");
4580
+ (0, import_fs6.mkdirSync)(targetDir, { recursive: true });
4581
+ const localRuntimeDir = (0, import_path8.join)(
4582
+ __dirname,
4583
+ "..",
4584
+ "..",
4585
+ "..",
4586
+ "python",
4587
+ "vercel-runtime"
4588
+ );
4589
+ const isLocalDev = (0, import_fs6.existsSync)((0, import_path8.join)(localRuntimeDir, "pyproject.toml"));
4590
+ const runtimeDep = env.VERCEL_RUNTIME_PYTHON || (isLocalDev ? localRuntimeDir : `vercel-runtime==${VERCEL_RUNTIME_VERSION}`);
4591
+ if (!isLocalDev && !env.VERCEL_RUNTIME_PYTHON) {
4592
+ const distInfo = (0, import_path8.join)(
4593
+ targetDir,
4594
+ `vercel_runtime-${VERCEL_RUNTIME_VERSION}.dist-info`
4595
+ );
4596
+ if ((0, import_fs6.existsSync)(distInfo)) {
4597
+ (0, import_build_utils8.debug)(
4598
+ `vercel-runtime ${VERCEL_RUNTIME_VERSION} already installed, skipping`
4599
+ );
4600
+ return;
4601
+ }
4602
+ }
4603
+ (0, import_build_utils8.debug)(
4604
+ `Installing vercel-runtime into ${targetDir} (type: ${isLocalDev ? "local" : "pypi"}, source: ${runtimeDep})`
4605
+ );
4606
+ const pip = uvPath ? { cmd: uvPath, prefix: ["pip", "install"] } : { cmd: pythonBin, prefix: ["-m", "pip", "install"] };
4607
+ const spawnArgs = [...pip.prefix, "--target", targetDir, runtimeDep];
4608
+ await new Promise((resolve2, reject) => {
4609
+ const child = (0, import_child_process2.spawn)(pip.cmd, spawnArgs, {
4610
+ cwd: workPath,
4611
+ env: getProtectedUvEnv(env),
4612
+ stdio: ["inherit", "pipe", "pipe"]
4613
+ });
4614
+ child.stdout?.on("data", (data) => {
4615
+ if (onStdout) {
4616
+ onStdout(data);
4617
+ } else {
4618
+ (0, import_build_utils8.debug)(data.toString());
4619
+ }
4620
+ });
4621
+ child.stderr?.on("data", (data) => {
4622
+ if (onStderr) {
4623
+ onStderr(data);
4624
+ } else {
4625
+ (0, import_build_utils8.debug)(data.toString());
4626
+ }
4627
+ });
4628
+ child.on("error", reject);
4629
+ child.on("exit", (code, signal) => {
4630
+ if (code === 0) {
4631
+ resolve2();
4632
+ } else {
4633
+ reject(
4634
+ new Error(
4635
+ `Installing vercel-runtime failed with code ${code}, signal ${signal}`
4636
+ )
4637
+ );
4638
+ }
4639
+ });
4640
+ });
4641
+ }
4571
4642
  var PERSISTENT_SERVERS = /* @__PURE__ */ new Map();
4572
4643
  var PENDING_STARTS = /* @__PURE__ */ new Map();
4573
4644
  var restoreWarnings = null;
@@ -4809,6 +4880,12 @@ If you are using a virtual environment, activate it before running "vercel dev",
4809
4880
  onStderr
4810
4881
  });
4811
4882
  }
4883
+ await installVercelRuntime({
4884
+ workPath,
4885
+ uvPath,
4886
+ pythonBin: spawnCommand,
4887
+ env
4888
+ });
4812
4889
  const port = typeof meta.port === "number" ? meta.port : await (0, import_get_port.default)();
4813
4890
  env.PORT = `${port}`;
4814
4891
  const devShim = createDevShim(workPath, entry, modulePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "6.21.0",
3
+ "version": "6.22.0",
4
4
  "main": "./dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -15,7 +15,7 @@
15
15
  "directory": "packages/python"
16
16
  },
17
17
  "dependencies": {
18
- "@vercel/python-analysis": "0.9.0"
18
+ "@vercel/python-analysis": "0.9.1"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@renovatebot/pep440": "4.2.1",
@@ -35,9 +35,9 @@
35
35
  "which": "3.0.0",
36
36
  "get-port": "5.1.1",
37
37
  "is-port-reachable": "3.1.0",
38
- "@vercel/build-utils": "13.7.0",
39
- "@vercel/error-utils": "2.0.3",
40
- "@vercel/python-runtime": "0.6.0"
38
+ "@vercel/python-runtime": "0.7.0",
39
+ "@vercel/build-utils": "13.8.0",
40
+ "@vercel/error-utils": "2.0.3"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "node ../../utils/build-builder.mjs",
@@ -1,596 +1,15 @@
1
- # Auto-generated template used by vercel dev (Python, ASGI/WSGI)
2
- # Serves static files from PUBLIC_DIR before delegating to the user app.
1
+ # Auto-generated trampoline used by vercel dev (Python, ASGI/WSGI)
3
2
 
4
- import sys
5
3
  import os
6
- import inspect
7
- import logging
8
- import logging.config
9
- from os import path as _p
10
- from importlib import util as _importlib_util
11
- import mimetypes
12
4
 
13
-
14
- # Simple ANSI coloring. Respect NO_COLOR environment variable.
15
- _NO_COLOR = "NO_COLOR" in os.environ
16
- _RESET = "\x1b[0m"
17
- _YELLOW = "\x1b[33m"
18
- _GREEN = "\x1b[32m"
19
- _RED = "\x1b[31m"
20
-
21
-
22
- def _color(text: str, code: str) -> str:
23
- if _NO_COLOR:
24
- return text
25
- return f"{code}{text}{_RESET}"
26
-
27
-
28
- # Configure logging to output DEBUG-WARNING to the stdout
29
- # and ERROR-CRITICAL to the stderr.
30
-
31
-
32
- # We need a custom filter for the stdout stream
33
- # so it won't print anything higher than WARNING.
34
- class _MaxLevelFilter(logging.Filter):
35
- def __init__(self, max_level):
36
- super().__init__()
37
- self.max_level = max_level
38
-
39
- def filter(self, record):
40
- return record.levelno <= self.max_level
41
-
42
-
43
- def _build_log_config(loggers, _filter_ref=None) -> dict:
44
- if _filter_ref is None:
45
- _filter_ref = "vc_init_dev._MaxLevelFilter"
46
-
47
- return {
48
- "version": 1,
49
- "disable_existing_loggers": False,
50
- "filters": {
51
- "max_warning": {
52
- "()": _filter_ref,
53
- "max_level": logging.WARNING,
54
- }
55
- },
56
- "handlers": {
57
- "stdout": {
58
- "class": "logging.StreamHandler",
59
- "stream": "ext://sys.stdout",
60
- "filters": ["max_warning"],
61
- },
62
- "stderr": {
63
- "class": "logging.StreamHandler",
64
- "stream": "ext://sys.stderr",
65
- "level": "ERROR",
66
- },
67
- },
68
- "loggers": loggers,
69
- "root": {
70
- "handlers": ["stdout", "stderr"],
71
- "level": "INFO",
72
- },
73
- }
74
-
75
-
76
- def _setup_server_log_routing(logger_name=None):
77
- loggers = {}
78
-
79
- if logger_name:
80
- loggers[logger_name] = {
81
- "handlers": ["stdout", "stderr"],
82
- "level": "INFO",
83
- "propagate": False,
84
- }
85
-
86
- logging.config.dictConfig(
87
- _build_log_config(
88
- loggers=loggers,
89
- _filter_ref=_MaxLevelFilter,
90
- ),
91
- )
92
-
93
-
94
- def _build_uvicorn_log_config(default_fmt=None, access_fmt=None) -> dict:
95
- try:
96
- from uvicorn.config import LOGGING_CONFIG # type: ignore
97
-
98
- uvicorn_fmts = LOGGING_CONFIG["formatters"]
99
- except ImportError:
100
- uvicorn_fmts = {
101
- "default": {
102
- "()": "uvicorn.logging.DefaultFormatter",
103
- "fmt": "%(levelprefix)s %(message)s",
104
- "use_colors": None,
105
- },
106
- "access": {
107
- "()": "uvicorn.logging.AccessFormatter",
108
- "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s',
109
- },
110
- }
111
-
112
- cfg = _build_log_config(
113
- loggers={
114
- "uvicorn": {
115
- "handlers": ["stdout", "stderr"],
116
- "level": "INFO",
117
- "propagate": False,
118
- },
119
- "uvicorn.error": {"level": "INFO"},
120
- "uvicorn.access": {
121
- "handlers": ["access"],
122
- "level": "INFO",
123
- "propagate": False,
124
- },
125
- },
126
- )
127
-
128
- if default_fmt is None:
129
- default_fmt = {**uvicorn_fmts["default"], "use_colors": not _NO_COLOR}
130
-
131
- if access_fmt is None:
132
- access_fmt = {**uvicorn_fmts["access"], "use_colors": not _NO_COLOR}
133
-
134
- cfg["formatters"] = {"default": default_fmt, "access": access_fmt}
135
- cfg["handlers"]["stdout"]["formatter"] = "default"
136
- cfg["handlers"]["stderr"]["formatter"] = "default"
137
- cfg["handlers"]["access"] = {
138
- "class": "logging.StreamHandler",
139
- "stream": "ext://sys.stdout",
140
- "formatter": "access",
5
+ os.environ.update(
6
+ {
7
+ "VERCEL_DEV_MODULE_NAME": "__VC_DEV_MODULE_NAME__",
8
+ "VERCEL_DEV_ENTRY_ABS": "__VC_DEV_ENTRY_ABS__",
141
9
  }
142
-
143
- return cfg
144
-
145
-
146
- def _build_hypercorn_log_config():
147
- return _build_log_config(
148
- loggers={
149
- "hypercorn.error": {
150
- "handlers": ["stdout", "stderr"],
151
- "level": "INFO",
152
- "propagate": False,
153
- },
154
- "hypercorn.access": {
155
- "handlers": ["stdout"],
156
- "level": "INFO",
157
- "propagate": False,
158
- },
159
- },
160
- )
161
-
162
-
163
- def _patch_fastapi_cli_log_config():
164
- try:
165
- import fastapi_cli.utils.cli as _fcli # type: ignore
166
- import fastapi_cli.cli as _fcli_cli # type: ignore
167
-
168
- _orig_get_config = _fcli.get_uvicorn_log_config # to ensure it's there
169
- _fcli_cli.get_uvicorn_log_config # to ensure it's there
170
- except (ImportError, AttributeError):
171
- return
172
-
173
- def _get_routed_config():
174
- orig = _orig_get_config()
175
- return _build_uvicorn_log_config(
176
- default_fmt={
177
- "()": "fastapi_cli.utils.cli.CustomFormatter",
178
- "fmt": orig["formatters"]["default"].get(
179
- "fmt", "%(levelprefix)s %(message)s"
180
- ),
181
- "use_colors": orig["formatters"]["default"].get("use_colors"),
182
- },
183
- access_fmt={
184
- "()": "fastapi_cli.utils.cli.CustomFormatter",
185
- "fmt": orig["formatters"]["access"].get(
186
- "fmt",
187
- "%(levelprefix)s %(client_addr)s - '%(request_line)s' %(status_code)s",
188
- ),
189
- },
190
- )
191
-
192
- _fcli.get_uvicorn_log_config = _get_routed_config
193
- # we need to patch the local binding as well
194
- _fcli_cli.get_uvicorn_log_config = _get_routed_config
195
-
196
-
197
- def _normalize_service_route_prefix(raw_prefix):
198
- if not raw_prefix:
199
- return ""
200
-
201
- prefix = raw_prefix.strip()
202
- if not prefix:
203
- return ""
204
-
205
- if not prefix.startswith("/"):
206
- prefix = f"/{prefix}"
207
-
208
- return "" if prefix == "/" else prefix.rstrip("/")
209
-
210
-
211
- def _is_service_route_prefix_strip_enabled():
212
- raw = os.environ.get("VERCEL_SERVICE_ROUTE_PREFIX_STRIP")
213
- if not raw:
214
- return False
215
- return raw.lower() in ("1", "true")
216
-
217
-
218
- _SERVICE_ROUTE_PREFIX = (
219
- _normalize_service_route_prefix(os.environ.get("VERCEL_SERVICE_ROUTE_PREFIX"))
220
- if _is_service_route_prefix_strip_enabled()
221
- else ""
222
10
  )
223
11
 
224
-
225
- def _strip_service_route_prefix(path_value):
226
- if not path_value:
227
- path_value = "/"
228
- elif not path_value.startswith("/"):
229
- path_value = f"/{path_value}"
230
-
231
- prefix = _SERVICE_ROUTE_PREFIX
232
- if not prefix:
233
- return path_value, ""
234
-
235
- if path_value == prefix:
236
- return "/", prefix
237
-
238
- if path_value.startswith(f"{prefix}/"):
239
- stripped = path_value[len(prefix) :]
240
- return stripped if stripped else "/", prefix
241
-
242
- return path_value, ""
243
-
244
-
245
- # Pre-configure the root logger before user module import so that any log
246
- # calls emitted at import time are routed to stdout/stderr correctly.
247
- _setup_server_log_routing()
248
-
249
-
250
- # ASGI/WSGI app detection
251
- _MODULE_NAME = "__VC_DEV_MODULE_NAME__"
252
- _ENTRY_ABS = "__VC_DEV_ENTRY_ABS__"
253
-
254
- # Import user module by file path, matching vc_init.py's approach.
255
- # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
256
- _spec = _importlib_util.spec_from_file_location(_MODULE_NAME, _ENTRY_ABS)
257
- if _spec is None or _spec.loader is None:
258
- raise RuntimeError(
259
- f"Could not load module spec for '{_MODULE_NAME}' at {_ENTRY_ABS}"
260
- )
261
- _mod = _importlib_util.module_from_spec(_spec)
262
- sys.modules[_MODULE_NAME] = _mod
263
- _spec.loader.exec_module(_mod)
264
-
265
- _user_app_name = (
266
- "app"
267
- if hasattr(_mod, "app")
268
- else "application"
269
- if hasattr(_mod, "application")
270
- else None
271
- )
272
- if _user_app_name is None:
273
- raise RuntimeError(
274
- f"Missing 'app' or 'application' in module '{_MODULE_NAME}'. "
275
- f"Define `app = ...` or `application = ...` in your entrypoint."
276
- )
277
-
278
- _user_app = getattr(_mod, _user_app_name, None)
279
-
280
-
281
- def _get_positional_param_count(obj):
282
- try:
283
- sig = inspect.signature(obj)
284
- return sum(
285
- 1
286
- for p in sig.parameters.values()
287
- if p.default is inspect.Parameter.empty
288
- and p.kind
289
- in (
290
- inspect.Parameter.POSITIONAL_ONLY,
291
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
292
- )
293
- )
294
- except (ValueError, TypeError):
295
- return None
296
-
297
-
298
- def _detect_app_type(app_obj):
299
- # try .asgi attribute if it's available and is callable
300
- asgi_attr = getattr(app_obj, "asgi", None)
301
- if asgi_attr is not None and callable(asgi_attr):
302
- return "asgi", asgi_attr
303
-
304
- # For async detection, check the object itself first (works for plain
305
- # functions/methods).
306
- # For class instances iscoroutinefunction(obj) is False,
307
- # so fall back to __call__.
308
- is_async = inspect.iscoroutinefunction(app_obj)
309
- if not is_async:
310
- call_method = getattr(app_obj, "__call__", None)
311
- if call_method is not None:
312
- is_async = inspect.iscoroutinefunction(call_method)
313
-
314
- # inspect.signature() already delegates to __call__ for class instances,
315
- # and works directly on plain functions, so always inspect app_obj.
316
- param_count = _get_positional_param_count(app_obj)
317
-
318
- # ASGI (scope, receive, send)
319
- if is_async and param_count == 3:
320
- return "asgi", app_obj
321
-
322
- # WSGI (environ, start_response)
323
- if param_count == 2:
324
- return "wsgi", app_obj
325
-
326
- print(
327
- _color(
328
- f"Could not determine the application interface for '{_MODULE_NAME}:{_user_app_name}'\n"
329
- f"Expected either:\n"
330
- f" - An ASGI app: async callable(scope, receive, send)\n"
331
- f" - A WSGI app: callable(environ, start_response)",
332
- _RED,
333
- ),
334
- file=sys.stderr,
335
- )
336
- sys.exit(1)
337
-
338
-
339
- app_type, resolved_app = _detect_app_type(_user_app)
340
-
341
- if app_type == "asgi":
342
- _asgi_app = resolved_app
343
- _wsgi_app = None
344
- else:
345
- _wsgi_app = resolved_app
346
- _asgi_app = None
347
-
348
- PUBLIC_DIR = "public"
349
-
350
- # Prepare ASGI app
351
-
352
- # Optional StaticFiles import; tolerate missing deps
353
- StaticFiles = None
354
- try:
355
- from fastapi.staticfiles import StaticFiles as _SF # type: ignore
356
-
357
- StaticFiles = _SF
358
- except Exception:
359
- try:
360
- from starlette.staticfiles import StaticFiles as _SF # type: ignore
361
-
362
- StaticFiles = _SF
363
- except Exception:
364
- StaticFiles = None
365
-
366
- # Prepare static files app (if starlette/fastapi installed)
367
- _static_app = None
368
- if StaticFiles is not None:
369
- try:
370
- try:
371
- _static_app = StaticFiles(directory=PUBLIC_DIR, check_dir=False)
372
- except TypeError:
373
- _static_app = StaticFiles(directory=PUBLIC_DIR)
374
- except Exception:
375
- _static_app = None
376
-
377
-
378
- def _apply_service_route_prefix_to_scope(scope):
379
- path_value, matched_prefix = _strip_service_route_prefix(scope.get("path", "/"))
380
- if not matched_prefix:
381
- return scope
382
-
383
- updated_scope = dict(scope)
384
- updated_scope["path"] = path_value
385
-
386
- raw_path = scope.get("raw_path")
387
- if isinstance(raw_path, (bytes, bytearray)):
388
- try:
389
- decoded = bytes(raw_path).decode("utf-8", "surrogateescape")
390
- stripped_raw, _ = _strip_service_route_prefix(decoded)
391
- updated_scope["raw_path"] = stripped_raw.encode("utf-8", "surrogateescape")
392
- except Exception:
393
- pass
394
-
395
- existing_root = scope.get("root_path", "") or ""
396
- if existing_root and existing_root != "/":
397
- existing_root = existing_root.rstrip("/")
398
- else:
399
- existing_root = ""
400
- updated_scope["root_path"] = f"{existing_root}{matched_prefix}"
401
- return updated_scope
402
-
403
-
404
- async def asgi_app(scope, receive, send):
405
- effective_scope = _apply_service_route_prefix_to_scope(scope)
406
-
407
- if _static_app is not None and effective_scope.get("type") == "http":
408
- req_path = effective_scope.get("path", "/") or "/"
409
- safe = _p.normpath(req_path).lstrip("/")
410
- full = _p.join(PUBLIC_DIR, safe)
411
- try:
412
- base = _p.realpath(PUBLIC_DIR)
413
- target = _p.realpath(full)
414
- if (target == base or target.startswith(base + _p.sep)) and _p.isfile(
415
- target
416
- ):
417
- await _static_app(effective_scope, receive, send)
418
- return
419
- except Exception:
420
- pass
421
- await _asgi_app(effective_scope, receive, send)
422
-
423
-
424
- # Prepare WSGI
425
-
426
-
427
- def _is_safe_file(base_dir: str, target: str) -> bool:
428
- try:
429
- base = _p.realpath(base_dir)
430
- tgt = _p.realpath(target)
431
- return (tgt == base or tgt.startswith(base + os.sep)) and _p.isfile(tgt)
432
- except Exception:
433
- return False
434
-
435
-
436
- def _static_wsgi_app(environ, start_response):
437
- # Only handle GET/HEAD requests for static assets
438
- if environ.get("REQUEST_METHOD", "GET") not in ("GET", "HEAD"):
439
- return _not_found(start_response)
440
-
441
- req_path = environ.get("PATH_INFO", "/") or "/"
442
- safe = _p.normpath(req_path).lstrip("/")
443
- full = _p.join(PUBLIC_DIR, safe)
444
- if not _is_safe_file(PUBLIC_DIR, full):
445
- return _not_found(start_response)
446
-
447
- ctype, _ = mimetypes.guess_type(full)
448
- headers = [("Content-Type", ctype or "application/octet-stream")]
449
- try:
450
- # For HEAD requests, send headers only
451
- if environ.get("REQUEST_METHOD") == "HEAD":
452
- start_response("200 OK", headers)
453
- return []
454
- with open(full, "rb") as f:
455
- data = f.read()
456
- headers.append(("Content-Length", str(len(data))))
457
- start_response("200 OK", headers)
458
- return [data]
459
- except Exception:
460
- return _not_found(start_response)
461
-
462
-
463
- def _not_found(start_response):
464
- start_response("404 Not Found", [("Content-Type", "text/plain; charset=utf-8")])
465
- return [b"Not Found"]
466
-
467
-
468
- def wsgi_app(environ, start_response):
469
- path_info, matched_prefix = _strip_service_route_prefix(
470
- environ.get("PATH_INFO", "/") or "/"
471
- )
472
- environ["PATH_INFO"] = path_info
473
- if matched_prefix:
474
- script_name = environ.get("SCRIPT_NAME", "") or ""
475
- if script_name and script_name != "/":
476
- script_name = script_name.rstrip("/")
477
- else:
478
- script_name = ""
479
- environ["SCRIPT_NAME"] = f"{script_name}{matched_prefix}"
480
-
481
- # Try static first; if 404 then delegate to user app
482
- captured_status = ""
483
- captured_headers = tuple()
484
- body_chunks = []
485
-
486
- def capture_start_response(status, headers, exc_info=None):
487
- nonlocal captured_status, captured_headers
488
- captured_status = status
489
- captured_headers = tuple(headers)
490
-
491
- # Return a writer that buffers the body
492
- def write(chunk: bytes):
493
- body_chunks.append(chunk)
494
-
495
- return write
496
-
497
- result = _static_wsgi_app(environ, capture_start_response)
498
- # If static handler produced 200, forward its response
499
- if captured_status.startswith("200 "):
500
- # Send headers and any chunks collected
501
- writer = start_response(captured_status, list(captured_headers))
502
- for chunk in body_chunks:
503
- writer(chunk)
504
- return result
505
-
506
- # Otherwise, delegate to user's WSGI app
507
- return _wsgi_app(environ, start_response)
508
-
509
-
510
- # Run dev server
12
+ from vercel_runtime.dev import main # noqa: E402
511
13
 
512
14
  if __name__ == "__main__":
513
- # Development runner
514
- #
515
- # For WSGI: prefer Werkzeug, but fall back to stdlib wsgiref.
516
- # For ASGI: prefer FastAPI CLI (dev command), then uvicorn, then hypercorn.
517
- #
518
- # The port is provided by the caller via the PORT environment variable.
519
-
520
- host = "127.0.0.1"
521
- _raw_port = os.environ.get("PORT")
522
- if not _raw_port:
523
- print(
524
- _color("PORT environment variable is required.", _RED),
525
- file=sys.stderr,
526
- )
527
- sys.exit(1)
528
- port = int(_raw_port)
529
-
530
- if app_type == "wsgi":
531
- try:
532
- from werkzeug.serving import run_simple # type: ignore
533
-
534
- _setup_server_log_routing("werkzeug")
535
- run_simple(host, port, wsgi_app, use_reloader=True, threaded=True)
536
- except Exception:
537
- print(
538
- _color(
539
- "Werkzeug not installed; falling back to wsgiref (no reloader).",
540
- _YELLOW,
541
- ),
542
- file=sys.stderr,
543
- )
544
- from wsgiref.simple_server import make_server
545
-
546
- httpd = make_server(host, port, wsgi_app)
547
- print(_color(f"Serving on http://{host}:{port}", _GREEN))
548
- httpd.serve_forever()
549
- else:
550
- try:
551
- from fastapi_cli.cli import dev as fastapi_dev # type: ignore
552
- except ImportError:
553
- fastapi_dev = None
554
-
555
- if fastapi_dev is not None:
556
- _patch_fastapi_cli_log_config()
557
- fastapi_dev(
558
- entrypoint="vc_init_dev:asgi_app", host=host, port=port, reload=True
559
- )
560
- sys.exit(0)
561
-
562
- try:
563
- import uvicorn # type: ignore
564
-
565
- uvicorn.run(
566
- "vc_init_dev:asgi_app",
567
- host=host,
568
- port=port,
569
- use_colors=True,
570
- reload=True,
571
- log_config=_build_uvicorn_log_config(),
572
- )
573
- except Exception:
574
- try:
575
- import asyncio
576
- from hypercorn.config import Config # type: ignore
577
- from hypercorn.asyncio import serve # type: ignore
578
-
579
- config = Config()
580
- config.bind = [f"{host}:{port}"]
581
- config.use_reloader = True
582
- config.logconfig_dict = _build_hypercorn_log_config()
583
-
584
- async def _run():
585
- await serve(asgi_app, config)
586
-
587
- asyncio.run(_run())
588
- except Exception:
589
- print(
590
- _color(
591
- 'No ASGI server found. Please install either "uvicorn" or "hypercorn" (e.g. "pip install uvicorn").',
592
- _RED,
593
- ),
594
- file=sys.stderr,
595
- )
596
- sys.exit(1)
15
+ main()