Skip to content

Commit 6bdcfbf

Browse files
patrick91tiangolo
andauthored
✨ Improve UI for fastapi dev and fastapi run (#95)
Co-authored-by: Sebastián Ramírez <[email protected]>
1 parent 059a25a commit 6bdcfbf

22 files changed

+625
-754
lines changed

README.md

+27-40
Original file line numberDiff line numberDiff line change
@@ -32,46 +32,33 @@ To run your FastAPI app for development, you can use the `fastapi dev` command:
3232

3333
```console
3434
$ fastapi dev main.py
35-
INFO Using path main.py
36-
INFO Resolved absolute path /home/user/code/awesomeapp/main.py
37-
INFO Searching for package file structure from directories with __init__.py files
38-
INFO Importing from /home/user/code/awesomeapp
39-
40-
╭─ Python module file ─╮
41-
│ │
42-
│ 🐍 main.py │
43-
│ │
44-
╰──────────────────────╯
45-
46-
INFO Importing module main
47-
INFO Found importable FastAPI app
48-
49-
╭─ Importable FastAPI app ─╮
50-
│ │
51-
│ from main import app │
52-
│ │
53-
╰──────────────────────────╯
54-
55-
INFO Using import string main:app
56-
57-
╭────────── FastAPI CLI - Development mode ───────────╮
58-
│ │
59-
│ Serving at: http://127.0.0.1:8000 │
60-
│ │
61-
│ API docs: http://127.0.0.1:8000/docs │
62-
│ │
63-
│ Running in development mode, for production use: │
64-
│ │
65-
│ fastapi run │
66-
│ │
67-
╰─────────────────────────────────────────────────────╯
68-
69-
INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
70-
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
71-
INFO: Started reloader process [56345] using WatchFiles
72-
INFO: Started server process [56352]
73-
INFO: Waiting for application startup.
74-
INFO: Application startup complete.
35+
36+
FastAPI Starting development server 🚀
37+
38+
Searching for package file structure from directories with __init__.py files
39+
Importing from /home/user/code/awesomeapp
40+
41+
module 🐍 main.py
42+
43+
code Importing the FastAPI app object from the module with the following code:
44+
45+
from main import app
46+
47+
app Using import string: main:app
48+
49+
server Server started at http://127.0.0.1:8000
50+
server Documentation at http://127.0.0.1:8000/docs
51+
52+
tip Running in development mode, for production use: fastapi run
53+
54+
Logs:
55+
56+
INFO Will watch for changes in these directories: ['/home/user/code/awesomeapp']
57+
INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
58+
INFO Started reloader process [4106097] using WatchFiles
59+
INFO Started server process [4106120]
60+
INFO Waiting for application startup.
61+
INFO Application startup complete.
7562
```
7663

7764
</div>

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ classifiers = [
3434
dependencies = [
3535
"typer >= 0.12.3",
3636
"uvicorn[standard] >= 0.15.0",
37+
"rich-toolkit >= 0.11.1"
3738
]
3839

3940
[project.optional-dependencies]

src/fastapi_cli/cli.py

+113-44
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
from logging import getLogger
1+
import logging
22
from pathlib import Path
3-
from typing import Any, Union
3+
from typing import Any, List, Union
44

55
import typer
66
from rich import print
7-
from rich.padding import Padding
8-
from rich.panel import Panel
7+
from rich.tree import Tree
98
from typing_extensions import Annotated
109

11-
from fastapi_cli.discover import get_import_string
10+
from fastapi_cli.discover import get_import_data
1211
from fastapi_cli.exceptions import FastAPICLIException
1312

1413
from . import __version__
1514
from .logging import setup_logging
15+
from .utils.cli import get_rich_toolkit, get_uvicorn_log_config
1616

1717
app = typer.Typer(rich_markup_mode="rich")
1818

19-
setup_logging()
20-
logger = getLogger(__name__)
19+
logger = logging.getLogger(__name__)
20+
2121

2222
try:
2323
import uvicorn
@@ -39,6 +39,7 @@ def callback(
3939
"--version", help="Show the version and exit.", callback=version_callback
4040
),
4141
] = None,
42+
verbose: bool = typer.Option(False, help="Enable verbose output"),
4243
) -> None:
4344
"""
4445
FastAPI CLI - The [bold]fastapi[/bold] command line app. 😎
@@ -48,6 +49,31 @@ def callback(
4849
Read more in the docs: [link=https://fastapi.tiangolo.com/fastapi-cli/]https://fastapi.tiangolo.com/fastapi-cli/[/link].
4950
"""
5051

52+
log_level = logging.DEBUG if verbose else logging.INFO
53+
54+
setup_logging(level=log_level)
55+
56+
57+
def _get_module_tree(module_paths: List[Path]) -> Tree:
58+
root = module_paths[0]
59+
name = f"🐍 {root.name}" if root.is_file() else f"📁 {root.name}"
60+
61+
root_tree = Tree(name)
62+
63+
if root.is_dir():
64+
root_tree.add("[dim]🐍 __init__.py[/dim]")
65+
66+
tree = root_tree
67+
for sub_path in module_paths[1:]:
68+
sub_name = (
69+
f"🐍 {sub_path.name}" if sub_path.is_file() else f"📁 {sub_path.name}"
70+
)
71+
tree = tree.add(sub_name)
72+
if sub_path.is_dir():
73+
tree.add("[dim]🐍 __init__.py[/dim]")
74+
75+
return root_tree
76+
5177

5278
def _run(
5379
path: Union[Path, None] = None,
@@ -61,45 +87,88 @@ def _run(
6187
app: Union[str, None] = None,
6288
proxy_headers: bool = False,
6389
) -> None:
64-
try:
65-
use_uvicorn_app = get_import_string(path=path, app_name=app)
66-
except FastAPICLIException as e:
67-
logger.error(str(e))
68-
raise typer.Exit(code=1) from None
69-
url = f"http://{host}:{port}"
70-
url_docs = f"{url}/docs"
71-
serving_str = f"[dim]Serving at:[/dim] [link={url}]{url}[/link]\n\n[dim]API docs:[/dim] [link={url_docs}]{url_docs}[/link]"
72-
73-
if command == "dev":
74-
panel = Panel(
75-
f"{serving_str}\n\n[dim]Running in development mode, for production use:[/dim] \n\n[b]fastapi run[/b]",
76-
title="FastAPI CLI - Development mode",
77-
expand=False,
78-
padding=(1, 2),
79-
style="black on yellow",
90+
with get_rich_toolkit() as toolkit:
91+
server_type = "development" if command == "dev" else "production"
92+
93+
toolkit.print_title(f"Starting {server_type} server 🚀", tag="FastAPI")
94+
toolkit.print_line()
95+
96+
toolkit.print(
97+
"Searching for package file structure from directories with [blue]__init__.py[/blue] files"
98+
)
99+
100+
try:
101+
import_data = get_import_data(path=path, app_name=app)
102+
except FastAPICLIException as e:
103+
toolkit.print_line()
104+
toolkit.print(f"[error]{e}")
105+
raise typer.Exit(code=1) from None
106+
107+
logger.debug(f"Importing from {import_data.module_data.extra_sys_path}")
108+
logger.debug(f"Importing module {import_data.module_data.module_import_str}")
109+
110+
module_data = import_data.module_data
111+
import_string = import_data.import_string
112+
113+
toolkit.print(f"Importing from {module_data.extra_sys_path}")
114+
toolkit.print_line()
115+
116+
root_tree = _get_module_tree(module_data.module_paths)
117+
118+
toolkit.print(root_tree, tag="module")
119+
toolkit.print_line()
120+
121+
toolkit.print(
122+
"Importing the FastAPI app object from the module with the following code:",
123+
tag="code",
80124
)
81-
else:
82-
panel = Panel(
83-
f"{serving_str}\n\n[dim]Running in production mode, for development use:[/dim] \n\n[b]fastapi dev[/b]",
84-
title="FastAPI CLI - Production mode",
85-
expand=False,
86-
padding=(1, 2),
87-
style="green",
125+
toolkit.print_line()
126+
toolkit.print(
127+
f"[underline]from [bold]{module_data.module_import_str}[/bold] import [bold]{import_data.app_name}[/bold]"
128+
)
129+
toolkit.print_line()
130+
131+
toolkit.print(
132+
f"Using import string: [blue]{import_string}[/]",
133+
tag="app",
134+
)
135+
136+
url = f"http://{host}:{port}"
137+
url_docs = f"{url}/docs"
138+
139+
toolkit.print_line()
140+
toolkit.print(
141+
f"Server started at [link={url}]{url}[/]",
142+
f"Documentation at [link={url_docs}]{url_docs}[/]",
143+
tag="server",
144+
)
145+
146+
if command == "dev":
147+
toolkit.print_line()
148+
toolkit.print(
149+
"Running in development mode, for production use: [bold]fastapi run[/]",
150+
tag="tip",
151+
)
152+
153+
if not uvicorn:
154+
raise FastAPICLIException(
155+
"Could not import Uvicorn, try running 'pip install uvicorn'"
156+
) from None
157+
158+
toolkit.print_line()
159+
toolkit.print("Logs:")
160+
toolkit.print_line()
161+
162+
uvicorn.run(
163+
app=import_string,
164+
host=host,
165+
port=port,
166+
reload=reload,
167+
workers=workers,
168+
root_path=root_path,
169+
proxy_headers=proxy_headers,
170+
log_config=get_uvicorn_log_config(),
88171
)
89-
print(Padding(panel, 1))
90-
if not uvicorn:
91-
raise FastAPICLIException(
92-
"Could not import Uvicorn, try running 'pip install uvicorn'"
93-
) from None
94-
uvicorn.run(
95-
app=use_uvicorn_app,
96-
host=host,
97-
port=port,
98-
reload=reload,
99-
workers=workers,
100-
root_path=root_path,
101-
proxy_headers=proxy_headers,
102-
)
103172

104173

105174
@app.command()

0 commit comments

Comments
 (0)