Fastapi-vue-setup updated to 1.4.0: prettier logs.

This commit is contained in:
2026-08-31 20:50:38 +00:00
parent fd1987c9b3
commit 7a4544731d
5 changed files with 57 additions and 25 deletions
+2
View File
@@ -5,6 +5,7 @@
* Configures Vite for FastAPI backend integration:
* - Proxies /api/* requests to the FastAPI backend
* - Builds to the Python module's frontend-build directory
* - Disables Vite's screen clearing on startup
*
* Options:
* paths - Array of paths to proxy (default: ["/api"])
@@ -26,6 +27,7 @@ export default function fastapiVue({ paths = ["/api"] } = {}) {
return {
name: "vite-plugin-fastapi-pagerite",
config: () => ({
clearScreen: false,
server: { proxy },
build: {
outDir: "../pagerite/frontend-build",
+1 -2
View File
@@ -98,13 +98,12 @@ def main() -> None:
os.environ["PAGERITE_HOSTNAME"] = args.hostname
if args.dbip:
_download_dbip()
dev = {"reload": True, "reload_dirs": ["pagerite"]} if DEVMODE else {}
server.run(
"pagerite.app:app",
listen=args.listen,
default_port=DEFAULT_PORT,
server_header=False,
**dev,
reload=Path(__file__).parent if DEVMODE else False,
)
+2 -1
View File
@@ -17,7 +17,7 @@ readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"blake3>=1.0.9",
"fastapi-vue>=1.3.1",
"fastapi-vue~=1.4.0",
"fastapi[standard]>=0.141.1",
"html5tagger>=2.0.0",
"httpx>=0.28.1",
@@ -54,6 +54,7 @@ packages = ["pagerite"]
# the frontend build).
artifacts = ["pagerite/frontend-build", "pagerite/themes", "pagerite/seed-assets"]
only-packages = true
packages = ["pagerite"]
[tool.hatch.build.targets.sdist.hooks.custom]
path = "scripts/fastapi-vue/buildhook.py"
+4 -1
View File
@@ -8,6 +8,8 @@ import sys
from contextlib import suppress
from pathlib import Path
import tracerite
# Import util.py from scripts/fastapi-vue (not a package, so we adjust sys.path)
sys.path.insert(0, str(Path(__file__).with_name("fastapi-vue")))
from devutil import (
@@ -39,7 +41,7 @@ async def run_devserver(
viteurl, npm_install, vite = setup_vite(listen, DEFAULT_VITE_PORT)
backurl, pagerite = setup_cli("pagerite", backend, DEFAULT_DEV_PORT)
# Tell the everyone by environment (vite proxy and backend devmode use these)
# Tell everyone via environment (vite proxy and backend devmode use these)
os.environ["PAGERITE_VITE_URL"] = viteurl
os.environ["PAGERITE_BACKEND_URL"] = backurl
os.environ["PAGERITE_DEV"] = "1"
@@ -54,6 +56,7 @@ async def run_devserver(
def main() -> None:
"""Parse CLI arguments and run the devserver."""
tracerite.load()
parser = argparse.ArgumentParser(
description="Run Vite and FastAPI development servers",
formatter_class=argparse.RawDescriptionHelpFormatter,
+45 -18
View File
@@ -7,8 +7,8 @@ import sys
from contextlib import suppress
from pathlib import Path
from typing import TYPE_CHECKING, Any, Self
from urllib.parse import urlsplit
import httpx
from buildutil import find_dev_tool, find_install_tool, logger
from fastapi_vue.hostutil import parse_endpoint
@@ -105,18 +105,47 @@ class ProcessGroup:
await p.wait()
async def http_get_server(url: str, timeout: float) -> str | None: # noqa: ASYNC109
"""GET url with plain asyncio streams, return the response Server header.
Returns an empty string when the server responds without a Server header,
and None when the server is unreachable or doesn't answer in time.
"""
parts = urlsplit(url)
host = parts.hostname or "localhost"
port = parts.port or (443 if parts.scheme == "https" else 80)
path = parts.path or "/"
if parts.query:
path += f"?{parts.query}"
try:
async with asyncio.timeout(timeout):
reader, writer = await asyncio.open_connection(host, port)
try:
writer.write(f"GET {path} HTTP/1.0\r\nHost: {host}\r\n\r\n".encode())
await writer.drain()
data = await reader.readuntil(b"\r\n\r\n")
finally:
writer.close()
except OSError, EOFError, ValueError, TimeoutError:
return None
for line in data.decode("latin-1").split("\r\n"):
if line.lower().startswith("server:"):
return line.split(":", 1)[1].strip()
return ""
async def check_ports_free(*urls: str) -> None:
"""Verify URLs are not responding (ports are free). Raise SystemExit if any respond."""
async def check(client: httpx.AsyncClient, url: str) -> None:
with suppress(httpx.RequestError):
res = await client.get(url, timeout=0.1)
server = res.headers.get("server", "server")
logger.warning("Conflicting %s already running at %s", server, url)
async def check(url: str) -> None:
server = await http_get_server(url, timeout=0.1)
if server is not None:
logger.warning(
"Conflicting %s already running at %s", server or "server", url
)
raise SystemExit(1)
async with httpx.AsyncClient() as client:
await asyncio.gather(*[check(client, url) for url in urls])
await asyncio.gather(*[check(url) for url in urls])
async def ready(url: str, path: str = "", max_attempts: int = 50) -> None:
@@ -128,18 +157,14 @@ async def ready(url: str, path: str = "", max_attempts: int = 50) -> None:
if not path:
return
async with httpx.AsyncClient() as client:
for attempt in range(max_attempts):
try:
await client.get(f"{url}{path}", timeout=1.0)
except httpx.RequestError:
if attempt == max_attempts - 1:
logger.warning("Backend didn't start in time")
raise SystemExit(1) from None
await asyncio.sleep(0.1)
else:
if await http_get_server(f"{url}{path}", timeout=1.0) is not None:
logger.info("✓ Backend ready!")
return
if attempt == max_attempts - 1:
logger.warning("Backend didn't start in time")
raise SystemExit(1)
await asyncio.sleep(0.1)
def setup_vite(
@@ -222,5 +247,7 @@ def setup_cli(
host = endpoints[0]["host"]
port = endpoints[0]["port"]
cmd = [cli, f"--listen={host}:{port}"]
# Run the package as a module with the current interpreter, instead of
# relying on a PATH-installed CLI entry point.
cmd = [sys.executable, "-m", cli, f"--listen={host}:{port}"]
return f"http://{host}:{port}", cmd