From 1c3dfd4545faf25cb381390d0fb7cadfa754365c Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Sat, 17 Jan 2026 23:04:07 +0000 Subject: [PATCH] Setup script cleanup. Use discovered app module and var names when creating __main__.py from template. README updates. --- README.md | 13 ++- fastapi_vue_setup.py | 171 +++++++++++++++++------------------ template/backend/__main__.py | 6 +- 3 files changed, 98 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 34e70dc..6764a09 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,16 @@ Options: In development, you access the Vite dev server at `http://localhost:5173`. Vite proxies `/api/*` requests to FastAPI at port 5180. Ports and hosts of Vite and FastAPI are configurable by `devserver.py` arguments. -In production, FastAPI serves both the API and static files at `http://localhost:5080`. Configurable by `host:port` argument. +In production, FastAPI serves both the API and static files at `http://localhost:5080`. Configurable by `host:port` argument with defaults set in `__main__.py` + +## Main CLI + +If your project didn't already have `__main__.py`, we create one that runs the FastAPI app with richer configuration than what the FastAPI CLI offers. Running your module starts it in production mode, and optionally host:port may be given as argument to specify where it listens. + +If you are running behind a reverse proxy like [Caddy](https://caddyserver.com/) on localhost, your app will trust the proxy headers it sends. However, if you need to configure another proxy host or IP, set `FORWARDED_ALLOW_IPS` env variable before running the server. + +The devserver script depends on this CLI entry for running the backend. You will have to modify the `devserver.py` script if your app has its own incompatible main module. Note that we set FastAPI debug mode and Uvicorn reload when configured via `FASTAPI_VUE_BACKEND_URL` env variable (set by `devserver.py`), while for normal production use these stay disabled. The same variable also controls static files serving (disabled in dev mode). + ## Vite Plugin Configuration @@ -108,7 +117,7 @@ my-app/ └── pyproject.toml ``` -The project directory tree looks roughly like this after project creation or patching. The script finds your existing fastpi app module and other files and patches them with minimal changes to enable the Vue-FastAPI interconnection. New Python and Vue projects are created automatically if none exist. +The project directory tree looks roughly like this after project creation or patching. The script finds your existing app module and other files and patches them with minimal changes to enable the Vue-FastAPI interconnection. New Python and Vue projects are created automatically if none exist. ## Development Workflow diff --git a/fastapi_vue_setup.py b/fastapi_vue_setup.py index f9faa18..2a8685c 100644 --- a/fastapi_vue_setup.py +++ b/fastapi_vue_setup.py @@ -44,6 +44,71 @@ PYPROJECT_ADDITIONS = { } +# Frontend instantiation block for patching existing apps +FRONTEND_BLOCK = """ +# Vue Frontend static files +frontend = Frontend( + Path(__file__).with_name("frontend-build"), spa=True, cached=["/assets/"] +) +""" + +# TypeScript health check script for Vue components +TS_HEALTH_CHECK_SCRIPT = """\ +import { ref, onMounted } from 'vue' + +const backendStatus = ref<'checking' | 'connected' | 'error'>('checking') + +onMounted(async () => { + try { + const res = await fetch('/api/health') + backendStatus.value = res.ok ? 'connected' : 'error' + } catch { + backendStatus.value = 'error' + } +}) +""" + +# JavaScript health check script for Vue components +JS_HEALTH_CHECK_SCRIPT = """\ +import { ref, onMounted } from 'vue' + +const backendStatus = ref('checking') + +onMounted(async () => { + try { + const res = await fetch('/api/health') + backendStatus.value = res.ok ? 'connected' : 'error' + } catch { + backendStatus.value = 'error' + } +}) +""" + +# Status indicator template for Vue components +STATUS_SPAN_TEMPLATE = """\ + + — FastAPI: + + + ❌ not reachable + +""" + +# Setup complete message template +SETUP_COMPLETE_MESSAGE = """ +Next steps: + +1. Build for production: + CD_CMDuv build + +2. Start development server: + CD_CMDuv run scripts/devserver.py + +3. Run production server: + CD_CMDuv run SCRIPT_NAME +""" + + # ============================================================================= # Utility functions # ============================================================================= @@ -149,16 +214,6 @@ def patch_app_file( lines = content.split("\n") import_line = "from fastapi_vue import Frontend" - # Frontend instantiation block - use module_name for the frontend-build path - frontend_block = """ -# Frontend static file server - configure options here -frontend = Frontend( - Path(__file__).parent / "frontend-build", - spa=True, - favicon="/assets/favicon", - cached=["/assets/"], -) -""" route_line = f'frontend.route({app_var}, "/")' # Find last import line and check if pathlib is imported @@ -179,7 +234,7 @@ frontend = Frontend( lines.insert(last_import_idx + 1, "from pathlib import Path") last_import_idx += 1 lines.insert(last_import_idx + 1, import_line) - lines.insert(last_import_idx + 2, frontend_block) + lines.insert(last_import_idx + 2, FRONTEND_BLOCK) # Append route at end lines.append("") @@ -343,44 +398,9 @@ def patch_frontend_health_check(frontend_dir: Path, dry_run: bool = False) -> bo is_typescript = 'lang="ts"' in content # Build the script content based on JS/TS - if is_typescript: - script_addition = """ -import { ref, onMounted } from 'vue' - -const backendStatus = ref<'checking' | 'connected' | 'error'>('checking') - -onMounted(async () => { - try { - const res = await fetch('/api/health') - backendStatus.value = res.ok ? 'connected' : 'error' - } catch { - backendStatus.value = 'error' - } -}) -""" - else: - script_addition = """ -import { ref, onMounted } from 'vue' - -const backendStatus = ref('checking') - -onMounted(async () => { - try { - const res = await fetch('/api/health') - backendStatus.value = res.ok ? 'connected' : 'error' - } catch { - backendStatus.value = 'error' - } -}) -""" - - # Template addition - inline status indicator using emoji for colors - status_span = """ - — FastAPI: - - - ❌ not reachable - """ + script_addition = ( + TS_HEALTH_CHECK_SCRIPT if is_typescript else JS_HEALTH_CHECK_SCRIPT + ) # Insert script addition before script_end_match = re.search(r"", content) @@ -396,7 +416,7 @@ onMounted(async () => { h3_close = content.find("") if h3_close != -1: content = ( - f"{content[:h3_close]}\n {status_span}\n {content[h3_close:]}" + f"{content[:h3_close]}\n{STATUS_SPAN_TEMPLATE} {content[h3_close:]}" ) else: # Minimal App.vue - insert before the last

before @@ -405,7 +425,9 @@ onMounted(async () => { # Find last

before last_p = content.rfind("

", 0, template_end) if last_p != -1: - content = f"{content[:last_p]}\n {status_span}\n {content[last_p:]}" + content = ( + f"{content[:last_p]}\n{STATUS_SPAN_TEMPLATE} {content[last_p:]}" + ) target_file.write_text(content) print(f"✅ Patched {target_file}") @@ -625,12 +647,12 @@ def cmd_setup(args: argparse.Namespace) -> int: if dry_run: print("\n🏃 DRY RUN MODE - no changes will be made\n") - # Step 1: Ensure Python project exists - if not ensure_python_project(project_dir, dry_run): + # Step 1: Ensure frontend exists (do this first so cancellation doesn't leave partial setup) + if not ensure_frontend(project_dir, dry_run): return 1 - # Step 2: Ensure frontend exists - if not ensure_frontend(project_dir, dry_run): + # Step 2: Ensure Python project exists + if not ensure_python_project(project_dir, dry_run): return 1 # Detect module name @@ -819,18 +841,10 @@ def cmd_setup(args: argparse.Namespace) -> int: cd_cmd = "" if project_dir == Path.cwd() else f"cd {project_dir}\n " script_name = module_name.replace("_", "-") - print(f""" -Next steps: - -1. Build for production: - {cd_cmd}uv build - -2. Start development server: - {cd_cmd}uv run scripts/devserver.py - -3. Run production server: - {cd_cmd}uv run {script_name} -""") + message = SETUP_COMPLETE_MESSAGE.replace("CD_CMD", cd_cmd).replace( + "SCRIPT_NAME", script_name + ) + print(message) return 0 @@ -875,7 +889,7 @@ Examples: "project_dir", nargs="?", default=None, - help="Project directory (default: current directory)", + help="Project directory (use . for current directory)", ) parser.add_argument("--module-name", help="Python module name (auto-detected)") parser.add_argument( @@ -884,26 +898,9 @@ Examples: args = parser.parse_args() - # Handle default project directory with safety check if args.project_dir is None: - cwd = Path.cwd() - if not is_uninitialized_folder(cwd) and not is_already_patched(cwd): - print( - "⚠️ Current directory contains an existing project that hasn't been patched yet." - ) - print() - print( - " If you want to set up FastAPI+Vue integration in the current directory, run:" - ) - print(" fastapi-vue-setup .") - print() - print(" Or specify a new project directory:") - print(" fastapi-vue-setup my-new-project") - print() - print(" Python project will be at root, while Vue lives in frontend/.") - print() - return 1 - args.project_dir = "." + parser.print_help() + return 0 return cmd_setup(args) diff --git a/template/backend/__main__.py b/template/backend/__main__.py index 0f934b4..8c9d9c6 100644 --- a/template/backend/__main__.py +++ b/template/backend/__main__.py @@ -6,20 +6,20 @@ import uvicorn from fastapi_vue.hostutil import parse_endpoint from uvicorn import Config, Server -from .app import app +from .APP_MODULE import APP_VAR DEFAULT_PORT = 5080 def run_server(endpoints: list[dict], *, proxy="", devmode=False): - conf: dict[str, object] = {"app": "MODULE_NAME.app:app"} + conf: dict[str, object] = {"app": "MODULE_NAME.APP_MODULE:APP_VAR"} if proxy: conf["proxy_headers"] = True conf["forwarded_allow_ips"] = proxy if devmode: conf["reload"] = True conf["reload_dirs"] = ["MODULE_NAME"] - app.debug = True + APP_VAR.debug = True if len(endpoints) > 1: # Run separate servers for multiple endpoints