fastreact 0.1.0__py3-none-any.whl

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.
fastreact/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ from .core import FastReact
2
+
3
+ __version__ = "0.1.0"
4
+ __all__ = ["FastReact", "FlaskReact"]
5
+
6
+
7
+ def __getattr__(name):
8
+ """
9
+ Lazy import FlaskReact only when explicitly requested.
10
+ This prevents Flask import errors for users who only use FastReact.
11
+ """
12
+ if name == "FlaskReact":
13
+ from .flask_core import FlaskReact
14
+ return FlaskReact
15
+ raise AttributeError(f"module 'fastreact' has no attribute {name!r}")
fastreact/cli.py ADDED
@@ -0,0 +1,557 @@
1
+ import subprocess
2
+ import sys
3
+ import os
4
+ import re
5
+ import signal
6
+ import threading
7
+ import argparse
8
+ from pathlib import Path
9
+
10
+
11
+ BANNER = """
12
+ ███████╗ █████╗ ███████╗████████╗██████╗ ███████╗ █████╗ ██████╗████████╗
13
+ ██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔════╝██╔══██╗██╔════╝╚══██╔══╝
14
+ █████╗ ███████║███████╗ ██║ ██████╔╝█████╗ ███████║██║ ██║
15
+ ██╔══╝ ██╔══██║╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══██║██║ ██║
16
+ ██║ ██║ ██║███████║ ██║ ██║ ██║███████╗██║ ██║╚██████╗ ██║
17
+ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝
18
+
19
+ FastAPI + React = One Unified Stack 🚀
20
+ """
21
+
22
+
23
+ # ── Node helpers ─────────────────────────────────────────────────────────────
24
+
25
+ def get_version(cmd):
26
+ for flag in ["--version", "-v", "-V"]:
27
+ try:
28
+ out = subprocess.check_output(
29
+ [cmd, flag],
30
+ stderr=subprocess.DEVNULL,
31
+ shell=(sys.platform == "win32")
32
+ ).decode().strip()
33
+ if out:
34
+ return out
35
+ except Exception:
36
+ continue
37
+ return None
38
+
39
+
40
+ def auto_install_node():
41
+ platform = sys.platform
42
+ print("\n 🔧 Attempting to auto-install Node.js...\n")
43
+ try:
44
+ if platform == "win32":
45
+ print(" 📦 Using winget...")
46
+ result = subprocess.run(
47
+ ["winget", "install", "OpenJS.NodeJS.LTS",
48
+ "--accept-source-agreements", "--accept-package-agreements"],
49
+ shell=True
50
+ )
51
+ if result.returncode == 0:
52
+ print("\n ✅ Node.js installed! Please reopen your terminal and run again.")
53
+ return True
54
+ raise Exception("winget failed")
55
+ elif platform == "darwin":
56
+ result = subprocess.run(["brew", "install", "node"])
57
+ if result.returncode == 0:
58
+ return True
59
+ raise Exception("brew failed")
60
+ elif platform.startswith("linux"):
61
+ subprocess.run(["sudo", "apt", "update"], check=True)
62
+ result = subprocess.run(["sudo", "apt", "install", "-y", "nodejs", "npm"])
63
+ if result.returncode == 0:
64
+ return True
65
+ raise Exception("apt failed")
66
+ except Exception as e:
67
+ print(f"\n ❌ Auto-install failed: {e}")
68
+ print(" 👉 Install manually from: https://nodejs.org")
69
+ return False
70
+
71
+
72
+ def check_node(auto_install=True):
73
+ node_version = get_version("node")
74
+ npm_version = get_version("npm")
75
+ if node_version and npm_version:
76
+ print(f" ✅ Node.js {node_version}")
77
+ print(f" ✅ npm v{npm_version}")
78
+ return True
79
+ print(" ❌ Node.js is not installed.")
80
+ if auto_install:
81
+ print(" 💡 Attempting auto-install...\n")
82
+ if auto_install_node():
83
+ node_version = get_version("node")
84
+ npm_version = get_version("npm")
85
+ if node_version and npm_version:
86
+ return True
87
+ return False
88
+ print(" 👉 Install from: https://nodejs.org")
89
+ return False
90
+
91
+
92
+ # ── Scaffold ─────────────────────────────────────────────────────────────────
93
+
94
+ def scaffold_react(project_name: str, target_dir: Path):
95
+ print(BANNER)
96
+ print(f"🛠️ Scaffolding React project: '{project_name}'")
97
+ print(f"📁 Location: {target_dir / project_name}\n")
98
+ print("🔍 Checking dependencies...")
99
+
100
+ if not check_node(auto_install=True):
101
+ sys.exit(1)
102
+
103
+ react_path = target_dir / project_name
104
+ is_windows = sys.platform == "win32"
105
+
106
+ print(f"\n⚡ Running: npm create vite@latest {project_name} -- --template react\n")
107
+ result = subprocess.run(
108
+ ["npm", "create", "vite@latest", project_name, "--", "--template", "react"],
109
+ cwd=str(target_dir),
110
+ input=b"\n",
111
+ shell=is_windows,
112
+ )
113
+ if result.returncode != 0:
114
+ print("\n❌ Failed to scaffold React project.")
115
+ sys.exit(1)
116
+
117
+ print(f"\n📦 Installing npm dependencies...\n")
118
+ result = subprocess.run(
119
+ ["npm", "install"],
120
+ cwd=str(react_path),
121
+ shell=is_windows,
122
+ )
123
+ if result.returncode != 0:
124
+ print("\n❌ Failed to install npm dependencies.")
125
+ sys.exit(1)
126
+
127
+ inject_vite_config(react_path)
128
+ print_success(project_name, react_path)
129
+
130
+
131
+ def inject_vite_config(react_path: Path, extra_proxies: list[str] = None):
132
+ """
133
+ Write vite.config.js with proxy config.
134
+ extra_proxies: list of path prefixes to proxy to FastAPI
135
+ e.g. ['/data', '/ui']
136
+ """
137
+ # Default proxies always included
138
+ proxy_prefixes = ['/data', '/api', '/ui']
139
+
140
+ if extra_proxies:
141
+ for p in extra_proxies:
142
+ if p not in proxy_prefixes:
143
+ proxy_prefixes.append(p)
144
+
145
+ # Build proxy entries
146
+ proxy_entries = ""
147
+ for prefix in proxy_prefixes:
148
+ proxy_entries += f""" '{prefix}': {{
149
+ target: 'http://127.0.0.1:8000',
150
+ changeOrigin: true,
151
+ secure: false,
152
+ }},
153
+ """
154
+
155
+ config_content = f"""import {{ defineConfig }} from 'vite'
156
+ import react from '@vitejs/plugin-react'
157
+
158
+ // FastReact: Tunnels API/page calls from React dev server to FastAPI
159
+ export default defineConfig({{
160
+ plugins: [react()],
161
+ server: {{
162
+ port: 5173,
163
+ proxy: {{
164
+ {proxy_entries} }}
165
+ }},
166
+ build: {{
167
+ outDir: '../frontend_build',
168
+ emptyOutDir: true,
169
+ }}
170
+ }})
171
+ """
172
+ vite_config = react_path / "vite.config.js"
173
+ with open(vite_config, "w") as f:
174
+ f.write(config_content)
175
+ print(" ✅ Injected FastReact proxy config into vite.config.js")
176
+
177
+
178
+ # ── Dev mode ─────────────────────────────────────────────────────────────────
179
+
180
+ VALID_UVICORN_OPTIONS = {
181
+ "--host", "--port", "--reload", "--reload-delay", "--reload-dir",
182
+ "--workers", "--log-level", "--access-log", "--no-access-log",
183
+ "--use-colors", "--no-use-colors", "--proxy-headers",
184
+ "--forwarded-allow-ips", "--root-path", "--limit-concurrency",
185
+ "--limit-max-requests", "--timeout-keep-alive", "--ssl-keyfile",
186
+ "--ssl-certfile", "--ssl-version", "--ssl-cert-reqs",
187
+ "--ssl-ca-certs", "--ssl-ciphers", "--h11-max-incomplete-event-size",
188
+ "--interface", "--http", "--ws", "--ws-max-size", "--ws-ping-interval",
189
+ "--ws-ping-timeout", "--lifespan", "--env-file", "--app-dir",
190
+ "--factory", "--loop", "--backlog",
191
+ }
192
+
193
+ def _validate_uvicorn_args(args: list[str]):
194
+ """
195
+ Check uvicorn args for typos before launching.
196
+ Exits with a clear error message if invalid option found.
197
+ """
198
+ for arg in args:
199
+ if arg.startswith("--"):
200
+ # Strip value part e.g. --port=8000 → --port
201
+ flag = arg.split("=")[0]
202
+ if flag not in VALID_UVICORN_OPTIONS:
203
+ # Find closest match
204
+ import difflib
205
+ close = difflib.get_close_matches(flag, VALID_UVICORN_OPTIONS, n=1, cutoff=0.6)
206
+ suggestion = f" Did you mean: {close[0]}" if close else ""
207
+ print(f"""
208
+ ╔══════════════════════════════════════════════╗
209
+ ║ ❌ FastReact — Invalid Option ║
210
+ ╠══════════════════════════════════════════════╣
211
+ ║ ║
212
+ ║ Unknown option: {flag:<28}║
213
+ ║ {suggestion:<46}║
214
+ ║ ║
215
+ ║ Run: fastreact dev --help ║
216
+ ║ ║
217
+ ╚══════════════════════════════════════════════╝
218
+ """)
219
+ sys.exit(1)
220
+
221
+ def detect_react_dir(cwd: Path) -> Path | None:
222
+ """Find the React project folder (contains package.json + vite.config.js)."""
223
+ for candidate in ["frontend", "client", "web", "react-app"]:
224
+ p = cwd / candidate
225
+ if (p / "package.json").exists() and (p / "vite.config.js").exists():
226
+ return p
227
+ # fallback: any subfolder with vite.config.js
228
+ for item in cwd.iterdir():
229
+ if item.is_dir() and (item / "vite.config.js").exists():
230
+ return item
231
+ return None
232
+
233
+
234
+ def extract_prefixes_from_main(main_file: Path) -> list[str]:
235
+ """
236
+ Parse main.py to extract react_prefix and all route paths.
237
+ Returns list of unique path prefixes to proxy.
238
+ e.g. ['/data', '/ui', '/items']
239
+ """
240
+ prefixes = set()
241
+ try:
242
+ content = main_file.read_text()
243
+
244
+ # Extract react_prefix value e.g. react_prefix="ui" or react_prefix="/api/"
245
+ match = re.search(r'react_prefix\s*=\s*["\']([^"\']+)["\']', content)
246
+ if match:
247
+ raw = match.group(1).strip("/")
248
+ prefixes.add("/" + raw)
249
+
250
+ # Extract all @app.get("/something/...") route prefixes
251
+ for m in re.finditer(r'@app\.\w+\(["\'](/[^/"\']*)', content):
252
+ segment = m.group(1) # e.g. /data or /ui
253
+ if segment and segment != "/":
254
+ prefixes.add(segment)
255
+
256
+ except Exception as e:
257
+ print(f" ⚠️ Could not parse {main_file.name}: {e}")
258
+
259
+ return list(prefixes)
260
+
261
+
262
+ def run_dev(app_string: str, uvicorn_args: list[str], cwd: Path, show_calls: bool = False):
263
+ """
264
+ Start both Uvicorn and Vite dev server together.
265
+ app_string: e.g. "main:app"
266
+ uvicorn_args: extra args passed through e.g. ["--reload", "--port", "8000"]
267
+ """
268
+ print(BANNER)
269
+ is_windows = sys.platform == "win32"
270
+
271
+ # Parse port from uvicorn args
272
+ port = "8000"
273
+ if "--port" in uvicorn_args:
274
+ port = uvicorn_args[uvicorn_args.index("--port") + 1]
275
+
276
+ # Find React project dir
277
+ react_dir = detect_react_dir(cwd)
278
+ if not react_dir:
279
+ print("❌ Could not find React project folder.")
280
+ print(" Make sure you ran: fastreact create frontend")
281
+ sys.exit(1)
282
+
283
+ print(f"📁 React project: {react_dir.name}/")
284
+
285
+ # Extract prefixes from main file and silently update vite.config.js
286
+ main_file_name = app_string.split(":")[0] + ".py"
287
+ main_file = cwd / main_file_name
288
+ prefixes = extract_prefixes_from_main(main_file)
289
+
290
+ if prefixes:
291
+ print(f" 🔍 Detected route prefixes: {prefixes}")
292
+ inject_vite_config(react_dir, extra_proxies=prefixes)
293
+ else:
294
+ inject_vite_config(react_dir)
295
+
296
+ call_line = "║ 📡 Call monitor → watching all requests ║" if show_calls else "║ 💡 Tip: add --call to monitor all requests ║"
297
+
298
+ print(f"""
299
+ ╔══════════════════════════════════════════════════════════╗
300
+ ║ ⚡ FastReact Dev Mode ║
301
+ ╠══════════════════════════════════════════════════════════╣
302
+ ║ ║
303
+ ║ 🌐 Open → http://localhost:5173 ║
304
+ ║ ║
305
+ ║ 🐍 FastAPI → http://127.0.0.1:{port} ║
306
+ ║ ⚛️ Vite → http://localhost:5173 (HMR on) ║
307
+ ║ 🔀 Proxy → all routes tunneled ║
308
+ ║ ║
309
+ {call_line}
310
+ ║ ║
311
+ ║ 🛑 Ctrl+C to stop everything ║
312
+ ║ ║
313
+ ╚══════════════════════════════════════════════════════════╝
314
+ """)
315
+
316
+ # Strip out fastreact-owned flags before validating uvicorn args
317
+ # --call is ours, not uvicorn's
318
+ FASTREACT_FLAGS = {"--call"}
319
+ uvicorn_only_args = [a for a in uvicorn_args if a not in FASTREACT_FLAGS]
320
+
321
+ # Validate uvicorn args before starting anything
322
+ _validate_uvicorn_args(uvicorn_only_args)
323
+ uvicorn_args = uvicorn_only_args
324
+
325
+ # Always add --reload if not present
326
+ uvicorn_cmd = ["uvicorn", app_string] + uvicorn_args
327
+ if "--reload" not in uvicorn_args:
328
+ uvicorn_cmd.append("--reload")
329
+
330
+ # Filter output — only show errors + reload notices from uvicorn
331
+ uvicorn_proc = subprocess.Popen(
332
+ uvicorn_cmd,
333
+ cwd=str(cwd),
334
+ shell=is_windows,
335
+ stdout=subprocess.PIPE,
336
+ stderr=subprocess.STDOUT,
337
+ )
338
+
339
+ # Vite output fully suppressed — we show our own banner
340
+ vite_proc = subprocess.Popen(
341
+ ["npm", "run", "dev"],
342
+ cwd=str(react_dir),
343
+ shell=is_windows,
344
+ stdout=subprocess.DEVNULL,
345
+ stderr=subprocess.DEVNULL,
346
+ )
347
+
348
+ # Handle Ctrl+C — kill both cleanly
349
+ def shutdown(sig=None, frame=None):
350
+ print("\n\n🛑 Shutting down FastReact...")
351
+ print(" ✅ Uvicorn stopped")
352
+ print(" ✅ Vite stopped")
353
+ try:
354
+ uvicorn_proc.terminate()
355
+ vite_proc.terminate()
356
+ except Exception:
357
+ pass
358
+ sys.exit(0)
359
+
360
+ signal.signal(signal.SIGINT, shutdown)
361
+ if sys.platform != "win32":
362
+ signal.signal(signal.SIGTERM, shutdown)
363
+
364
+ # Stream uvicorn output — filter based on --call flag
365
+ def stream_uvicorn():
366
+ SHOW_ALWAYS = (
367
+ "error", "Error", "ERROR",
368
+ "Reloading", "reloading",
369
+ "Application startup complete",
370
+ "Traceback",
371
+ )
372
+ SKIP_PATTERNS = (
373
+ "Will watch for changes",
374
+ "Started reloader",
375
+ "Started server process",
376
+ "Waiting for application",
377
+ "Uvicorn running on",
378
+ )
379
+ # HTTP method colors
380
+ METHOD_COLORS = {
381
+ "GET": "\033[32m", # green
382
+ "POST": "\033[34m", # blue
383
+ "PUT": "\033[33m", # yellow
384
+ "DELETE": "\033[31m", # red
385
+ "PATCH": "\033[35m", # magenta
386
+ }
387
+ STATUS_COLORS = {
388
+ "2": "\033[32m", # 2xx green
389
+ "3": "\033[36m", # 3xx cyan
390
+ "4": "\033[33m", # 4xx yellow
391
+ "5": "\033[31m", # 5xx red
392
+ }
393
+ RESET = "\033[0m"
394
+ BOLD = "\033[1m"
395
+ DIM = "\033[2m"
396
+
397
+ for line in uvicorn_proc.stdout:
398
+ text = line.decode(errors="replace").rstrip()
399
+ if not text:
400
+ continue
401
+ if any(skip in text for skip in SKIP_PATTERNS):
402
+ continue
403
+
404
+ # Always show errors/reloads
405
+ if any(show in text for show in SHOW_ALWAYS):
406
+ print(f" {text}")
407
+ continue
408
+
409
+ # HTTP request lines e.g:
410
+ # 127.0.0.1:PORT - "GET /api/users HTTP/1.1" 200 OK
411
+ if show_calls and '" ' in text and "HTTP/" in text:
412
+ try:
413
+ # Parse: IP - "METHOD PATH HTTP" STATUS
414
+ import re
415
+ m = re.search(r'"(\w+) ([^ ]+) HTTP/[\d.]+" (\d+)', text)
416
+ if m:
417
+ method = m.group(1)
418
+ path = m.group(2)
419
+ status = m.group(3)
420
+
421
+ method_color = METHOD_COLORS.get(method, "\033[37m")
422
+ status_color = STATUS_COLORS.get(status[0], "\033[37m")
423
+
424
+ import datetime
425
+ ts = datetime.datetime.now().strftime("%H:%M:%S")
426
+
427
+ print(
428
+ f" {DIM}{ts}{RESET} "
429
+ f"{method_color}{BOLD}{method:<7}{RESET} "
430
+ f"{path:<40} "
431
+ f"{status_color}{BOLD}{status}{RESET}"
432
+ )
433
+ else:
434
+ print(f" {text}")
435
+ except Exception:
436
+ print(f" {text}")
437
+
438
+ print("\n⚠️ FastAPI server stopped. Press Ctrl+C to exit.")
439
+
440
+ def watch_vite():
441
+ vite_proc.wait()
442
+ print("\n⚠️ Vite server stopped. Press Ctrl+C to exit.")
443
+
444
+ t1 = threading.Thread(target=stream_uvicorn, daemon=True)
445
+ t2 = threading.Thread(target=watch_vite, daemon=True)
446
+ t1.start()
447
+ t2.start()
448
+
449
+ try:
450
+ t1.join()
451
+ t2.join()
452
+ except KeyboardInterrupt:
453
+ shutdown()
454
+
455
+
456
+ # ── Print success ─────────────────────────────────────────────────────────────
457
+
458
+ def print_success(project_name: str, react_path: Path):
459
+ print(f"""
460
+ ╔══════════════════════════════════════════════════════╗
461
+ ║ ✅ FastReact scaffold complete! ║
462
+ ╚══════════════════════════════════════════════════════╝
463
+
464
+ 📁 React project created at:
465
+ {react_path}
466
+
467
+ 🚀 Next steps:
468
+
469
+ Dev mode (both servers at once):
470
+ fastreact dev main:app --reload
471
+
472
+ Or manually:
473
+ uvicorn main:app --reload
474
+ cd {project_name} && npm run dev
475
+
476
+ Production build:
477
+ cd {project_name} && npm run build
478
+
479
+ ⚡ Powered by FastReact
480
+ """)
481
+
482
+
483
+ # ── CLI entry ─────────────────────────────────────────────────────────────────
484
+
485
+
486
+
487
+ HELP_TEXT = (
488
+ "\033[1m\033[35m\n"
489
+ "███████╗ █████╗ ███████╗████████╗██████╗ ███████╗ █████╗ ██████╗████████╗\n"
490
+ "██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔════╝██╔══██╗██╔════╝╚══██╔══╝\n"
491
+ "█████╗ ███████║███████╗ ██║ ██████╔╝█████╗ ███████║██║ ██║ \n"
492
+ "██╔══╝ ██╔══██║╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══██║██║ ██║ \n"
493
+ "██║ ██║ ██║███████║ ██║ ██║ ██║███████╗██║ ██║╚██████╗ ██║ \n"
494
+ "\033[0m\n"
495
+ "\033[2m FastAPI + React = One Unified Stack\033[0m\n\n"
496
+ "\033[1m\033[36m COMMANDS\033[0m\n\n"
497
+ " \033[1m\033[32mfastreact create\033[0m \033[33m<name>\033[0m\n"
498
+ " \033[2m Scaffold a Vite React app wired into your FastAPI project\033[0m\n\n"
499
+ " $ fastreact create frontend\n\n"
500
+ " \033[1m\033[32mfastreact dev\033[0m \033[33m<file:app>\033[0m \033[2m[options]\033[0m\n"
501
+ " \033[2m Start FastAPI + Vite together. Same syntax as uvicorn.\033[0m\n\n"
502
+ " $ fastreact dev main:app --reload\n"
503
+ " $ fastreact dev main:app --reload --port 8000\n"
504
+ " $ fastreact dev main:app --reload --host 0.0.0.0\n"
505
+ " $ fastreact dev main:app --reload --call\n\n"
506
+ "\033[1m\033[36m UVICORN OPTIONS\033[0m\n\n"
507
+ " \033[33m--reload\033[0m Auto-restart on file save\n"
508
+ " \033[33m--port\033[0m \033[35m<number>\033[0m Port number \033[2mdefault: 8000\033[0m\n"
509
+ " \033[33m--host\033[0m \033[35m<address>\033[0m Host address \033[2mdefault: 127.0.0.1\033[0m\n"
510
+ " \033[33m--workers\033[0m \033[35m<number>\033[0m Worker processes \033[2mdefault: 1\033[0m\n\n"
511
+ "\033[1m\033[36m FASTREACT FLAGS\033[0m\n\n"
512
+ " \033[33m--call\033[0m Live monitor - every request colored by method and status\n\n"
513
+ "\033[1m\033[36m HOW ROUTING WORKS\033[0m\n\n"
514
+ " \033[32m@app.get(\"/ui/users\")\033[0m React page - browser gets React, Postman gets \033[31m405\033[0m\n"
515
+ " \033[32m@app.get(\"/data/users\")\033[0m Normal route - everyone gets JSON \033[32m200\033[0m\n\n"
516
+ "\033[1m\033[36m QUICK START\033[0m\n\n"
517
+ " fastreact create frontend\n"
518
+ " fastreact dev main:app --reload --call\n\n"
519
+ " \033[2m# build for prod\033[0m\n"
520
+ " cd frontend && npm run build\n"
521
+ " uvicorn main:app --host 0.0.0.0 --port 8000\n\n"
522
+ " \033[36m pip install fastreact\033[0m\n"
523
+ )
524
+
525
+
526
+ def main():
527
+ if len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] in ("--help", "-h", "help")):
528
+ print(HELP_TEXT)
529
+ sys.exit(0)
530
+
531
+ parser = argparse.ArgumentParser(add_help=False)
532
+ subparsers = parser.add_subparsers(dest="command")
533
+
534
+ create_parser = subparsers.add_parser("create", add_help=False)
535
+ create_parser.add_argument("name")
536
+
537
+ dev_parser = subparsers.add_parser("dev", add_help=False)
538
+ dev_parser.add_argument("app")
539
+ dev_parser.add_argument("uvicorn_args", nargs=argparse.REMAINDER)
540
+
541
+ args = parser.parse_args()
542
+ cwd = Path(os.getcwd())
543
+
544
+ if args.command == "create":
545
+ scaffold_react(args.name, cwd)
546
+
547
+ elif args.command == "dev":
548
+ show_calls = "--call" in args.uvicorn_args
549
+ uvicorn_args = [a for a in args.uvicorn_args if a != "--call"]
550
+ run_dev(args.app, uvicorn_args, cwd, show_calls=show_calls)
551
+
552
+ else:
553
+ print(HELP_TEXT)
554
+
555
+
556
+ if __name__ == "__main__":
557
+ main()