Skip to content

Commit d2326f6

Browse files
committed
✨ Trim default startup output and add --verbose to dev and run
Shortcake-Parent: 2026-07-09-redesign-startup-output-style
1 parent eada899 commit d2326f6

3 files changed

Lines changed: 167 additions & 124 deletions

File tree

src/fastapi_cli/cli.py

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ def callback(
9191
"--version", help="Show the version and exit.", callback=version_callback
9292
),
9393
] = None,
94-
verbose: bool = typer.Option(False, help="Enable verbose output"),
9594
) -> None:
9695
"""
9796
FastAPI CLI - The [bold]fastapi[/bold] command line app. 😎
@@ -101,9 +100,7 @@ def callback(
101100
Read more in the docs: [link=https://fastapi.tiangolo.com/fastapi-cli/]https://fastapi.tiangolo.com/fastapi-cli/[/link].
102101
"""
103102

104-
log_level = logging.DEBUG if verbose else logging.INFO
105-
106-
setup_logging(level=log_level)
103+
setup_logging()
107104

108105

109106
def _get_module_tree(module_paths: list[Path]) -> Tree:
@@ -142,17 +139,25 @@ def _run(
142139
proxy_headers: bool = False,
143140
forwarded_allow_ips: str | None = None,
144141
public_url: str | None = None,
142+
verbose: bool = False,
145143
) -> None:
144+
# setup_logging() in the root callback runs before command options are
145+
# parsed, so the per-command --verbose only adjusts the level here
146+
logging.getLogger("fastapi_cli").setLevel(
147+
logging.DEBUG if verbose else logging.INFO
148+
)
149+
146150
with get_rich_toolkit() as toolkit:
147151
server_type = "development" if command == "dev" else "production"
148152

149153
toolkit.print(f"Starting FastAPI in {server_type} mode", emoji="⚡️")
150154

151-
toolkit.print_line()
152-
toolkit.print(
153-
"Searching for package file structure from directories with [blue]__init__.py[/blue] files",
154-
emoji="🔎",
155-
)
155+
if verbose:
156+
toolkit.print_line()
157+
toolkit.print(
158+
"Searching for package file structure from directories with [blue]__init__.py[/blue] files",
159+
emoji="🔎",
160+
)
156161

157162
if entrypoint and (path or app):
158163
toolkit.print_line()
@@ -194,40 +199,54 @@ def _run(
194199

195200
module_data = import_data.module_data
196201
import_string = import_data.import_string
202+
is_auto_discovery = import_data.module_config_source == "auto-discovery"
197203

198-
toolkit.print_line()
199-
toolkit.print(f"Importing from {module_data.extra_sys_path}", emoji="📂")
200-
toolkit.print_line()
204+
if verbose:
205+
toolkit.print_line()
206+
toolkit.print(f"Importing from {module_data.extra_sys_path}", emoji="📂")
207+
toolkit.print_line()
201208

202-
if module_data.module_paths:
203-
root_tree = _get_module_tree(module_data.module_paths)
209+
if module_data.module_paths:
210+
root_tree = _get_module_tree(module_data.module_paths)
204211

205-
toolkit.print(root_tree)
212+
toolkit.print(root_tree)
206213

207-
toolkit.print_line()
214+
toolkit.print_line()
215+
216+
toolkit.print(
217+
"Importing the FastAPI app object from the module with the following code:",
218+
)
219+
toolkit.print_line()
220+
toolkit.print(
221+
f"[blue]from [bold]{module_data.module_import_str}[/bold] import [bold]{import_data.app_name}[/bold]",
222+
)
223+
224+
# Outside --verbose, fold the resolution source into the import string
225+
# line and point newcomers at --verbose for the full explanation
226+
if is_auto_discovery and not verbose:
227+
app_note = " [dim](auto-discovered, use --verbose to learn more)[/]"
228+
else:
229+
app_note = ""
208230

209-
toolkit.print(
210-
"Importing the FastAPI app object from the module with the following code:",
211-
)
212231
toolkit.print_line()
213232
toolkit.print(
214-
f"[blue]from [bold]{module_data.module_import_str}[/bold] import [bold]{import_data.app_name}[/bold]",
233+
f"Using import string: [blue]{import_string}[/]{app_note}", emoji="🐍"
215234
)
216235

217-
toolkit.print_line()
218-
toolkit.print(f"Using import string: [blue]{import_string}[/]", emoji="🐍")
219-
220-
toolkit.print_line()
221-
mod_source_desc = SOURCE_DESCRIPTIONS[import_data.module_config_source]
222-
app_source_desc = SOURCE_DESCRIPTIONS[import_data.app_name_config_source]
223-
toolkit.print("Configuration sources:", emoji="📋")
224-
if mod_source_desc == app_source_desc:
225-
toolkit.print(f"• Import string: {mod_source_desc}")
226-
else:
227-
toolkit.print(f"• Module: {mod_source_desc}")
228-
toolkit.print(f"• App name: {app_source_desc}")
236+
if verbose:
237+
toolkit.print_line()
238+
mod_source_desc = SOURCE_DESCRIPTIONS[import_data.module_config_source]
239+
app_source_desc = SOURCE_DESCRIPTIONS[import_data.app_name_config_source]
240+
toolkit.print("Configuration sources:", emoji="📋")
241+
if mod_source_desc == app_source_desc:
242+
toolkit.print(f"• Import string: {mod_source_desc}")
243+
else:
244+
toolkit.print(f"• Module: {mod_source_desc}")
245+
toolkit.print(f"• App name: {app_source_desc}")
229246

230-
if import_data.module_config_source == "auto-discovery":
247+
# Nudge to pin the entrypoint whenever it was auto-discovered, so it's
248+
# explicit next time — shown in the default output, not just --verbose
249+
if is_auto_discovery:
231250
toolkit.print_line()
232251
toolkit.print(
233252
"You can configure an entrypoint in [blue]pyproject.toml[/] for this app with:",
@@ -252,13 +271,6 @@ def _run(
252271
toolkit.print(f"Server started at [link={url}]{url}[/]", emoji="🌐")
253272
toolkit.print(f"Documentation at [link={url_docs}]{url_docs}[/]")
254273

255-
if command == "dev":
256-
toolkit.print_line()
257-
toolkit.print(
258-
"Running in development mode, for production use: [bold]fastapi run[/]",
259-
emoji="💡",
260-
)
261-
262274
if not uvicorn:
263275
raise FastAPICLIException(
264276
"Could not import Uvicorn, try running 'pip install uvicorn'"
@@ -356,6 +368,14 @@ def dev(
356368
help="Comma separated list of IP Addresses to trust with proxy headers. The literal '*' means trust everything."
357369
),
358370
] = None,
371+
verbose: Annotated[
372+
bool,
373+
typer.Option(
374+
"--verbose",
375+
"-v",
376+
help="Show detailed startup output and debug logs: how the [bold]FastAPI[/bold] app was discovered, imported, and configured.",
377+
),
378+
] = False,
359379
) -> Any:
360380
"""
361381
Run a [bold]FastAPI[/bold] app in [yellow]development[/yellow] mode. 🧪
@@ -395,6 +415,7 @@ def dev(
395415
proxy_headers=proxy_headers,
396416
forwarded_allow_ips=forwarded_allow_ips,
397417
public_url=os.getenv("FASTAPI_PUBLIC_URL"),
418+
verbose=verbose,
398419
)
399420

400421

@@ -464,6 +485,14 @@ def run(
464485
help="Comma separated list of IP Addresses to trust with proxy headers. The literal '*' means trust everything."
465486
),
466487
] = None,
488+
verbose: Annotated[
489+
bool,
490+
typer.Option(
491+
"--verbose",
492+
"-v",
493+
help="Show detailed startup output and debug logs: how the [bold]FastAPI[/bold] app was discovered, imported, and configured.",
494+
),
495+
] = False,
467496
) -> Any:
468497
"""
469498
Run a [bold]FastAPI[/bold] app in [green]production[/green] mode. 🚀
@@ -503,6 +532,7 @@ def run(
503532
proxy_headers=proxy_headers,
504533
forwarded_allow_ips=forwarded_allow_ips,
505534
public_url=os.getenv("FASTAPI_PUBLIC_URL"),
535+
verbose=verbose,
506536
)
507537

508538

0 commit comments

Comments
 (0)