Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: fetch app docs_url #24

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
21 changes: 20 additions & 1 deletion src/fastapi_cli/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import importlib
from logging import getLogger
from pathlib import Path
from typing import Any, Union

import typer
import uvicorn
from fastapi import FastAPI
from rich import print
from rich.padding import Padding
from rich.panel import Panel
Expand Down Expand Up @@ -49,6 +52,13 @@ def callback(
"""


def _get_docs_url(uvicorn_path: str) -> Union[str, None]:
module_path, app_name = uvicorn_path.split(sep=":")
module = importlib.import_module(module_path)
fastapi_app: FastAPI = getattr(module, app_name)
return fastapi_app.docs_url


def _run(
path: Union[Path, None] = None,
*,
Expand All @@ -66,7 +76,16 @@ def _run(
except FastAPICLIException as e:
logger.error(str(e))
raise typer.Exit(code=1) from None
serving_str = f"[dim]Serving at:[/dim] [link]http://{host}:{port}[/link]\n\n[dim]API docs:[/dim] [link]http://{host}:{port}/docs[/link]"

docs_url = _get_docs_url(use_uvicorn_app)

api_docs_string = (
f"API docs:[/dim] [link]http://{host}:{port}{docs_url}[/link]"
if docs_url
else ""
)

serving_str = f"[dim]Serving at:[/dim] [link]http://{host}:{port}[/link]\n\n[dim]{api_docs_string}"

if command == "dev":
panel = Panel(
Expand Down
8 changes: 8 additions & 0 deletions tests/assets/with_docs_url_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from fastapi import FastAPI

app = FastAPI(docs_url="/any-other-path")


@app.get("/")
def api_root():
return {"message": "any message"}
8 changes: 8 additions & 0 deletions tests/assets/without_docs_url_none.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from fastapi import FastAPI

app = FastAPI(docs_url=None)


@app.get("/")
def api_root():
return {"message": "any message"}
8 changes: 8 additions & 0 deletions tests/assets/without_docs_url_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def api_root():
return {"message": "any message"}
170 changes: 170 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,173 @@ def test_script() -> None:
encoding="utf-8",
)
assert "Usage" in result.stdout


def test_dev_and_fastapi_app_with_docs_url_set_should_show_correctly_url_in_stdout() -> (
None
):
with changing_dir(assets_path):
with patch.object(uvicorn, "run") as mock_run:
result = runner.invoke(app, ["dev", "with_docs_url_set.py"])
assert result.exit_code == 0, result.output
assert mock_run.called
assert mock_run.call_args
assert mock_run.call_args.kwargs == {
"app": "with_docs_url_set:app",
"host": "127.0.0.1",
"port": 8000,
"reload": True,
"workers": None,
"root_path": "",
"proxy_headers": True,
}
assert "Using import string with_docs_url_set:app" in result.output
assert (
"╭────────── FastAPI CLI - Development mode ───────────╮" in result.output
)
assert "│ Serving at: http://127.0.0.1:8000" in result.output
assert "│ API docs: http://127.0.0.1:8000/any-other-path" in result.output
assert "│ Running in development mode, for production use:" in result.output
assert "│ fastapi run" in result.output


def test_dev_and_fastapi_app_without_docs_url_set_should_show_default_url_in_stdout() -> (
None
):
with changing_dir(assets_path):
with patch.object(uvicorn, "run") as mock_run:
result = runner.invoke(app, ["dev", "without_docs_url_set.py"])
assert result.exit_code == 0, result.output
assert mock_run.called
assert mock_run.call_args
assert mock_run.call_args.kwargs == {
"app": "without_docs_url_set:app",
"host": "127.0.0.1",
"port": 8000,
"reload": True,
"workers": None,
"root_path": "",
"proxy_headers": True,
}
assert "Using import string without_docs_url_set:app" in result.output
assert (
"╭────────── FastAPI CLI - Development mode ───────────╮" in result.output
)
assert "│ Serving at: http://127.0.0.1:8000" in result.output
assert "│ API docs: http://127.0.0.1:8000/docs" in result.output
assert "│ Running in development mode, for production use:" in result.output
assert "│ fastapi run" in result.output


def test_run_and_fastapi_app_with_docs_url_set_should_show_correctly_url_in_stdout() -> (
None
):
with changing_dir(assets_path):
with patch.object(uvicorn, "run") as mock_run:
result = runner.invoke(app, ["run", "with_docs_url_set.py"])
assert result.exit_code == 0, result.output
assert mock_run.called
assert mock_run.call_args
assert mock_run.call_args.kwargs == {
"app": "with_docs_url_set:app",
"host": "0.0.0.0",
"port": 8000,
"reload": False,
"workers": None,
"root_path": "",
"proxy_headers": True,
}
assert "Using import string with_docs_url_set:app" in result.output
assert (
"╭─────────── FastAPI CLI - Production mode ───────────╮" in result.output
)
assert "│ Serving at: http://0.0.0.0:8000" in result.output
assert "│ API docs: http://0.0.0.0:8000/any-other-path" in result.output
assert "│ Running in production mode, for development use:" in result.output
assert "│ fastapi dev" in result.output


def test_run_and_fastapi_app_without_docs_url_set_should_show_default_url_in_stdout() -> (
None
):
with changing_dir(assets_path):
with patch.object(uvicorn, "run") as mock_run:
result = runner.invoke(app, ["run", "without_docs_url_set.py"])
assert result.exit_code == 0, result.output
assert mock_run.called
assert mock_run.call_args
assert mock_run.call_args.kwargs == {
"app": "without_docs_url_set:app",
"host": "0.0.0.0",
"port": 8000,
"reload": False,
"workers": None,
"root_path": "",
"proxy_headers": True,
}
assert "Using import string without_docs_url_set:app" in result.output
assert (
"╭─────────── FastAPI CLI - Production mode ───────────╮" in result.output
)
assert "│ Serving at: http://0.0.0.0:8000" in result.output
assert "│ API docs: http://0.0.0.0:8000/docs" in result.output
assert "│ Running in production mode, for development use:" in result.output
assert "│ fastapi dev" in result.output


def test_run_and_fastapi_app_docs_url_set_to_none_should_not_show_api_docs_section() -> (
None
):
with changing_dir(assets_path):
with patch.object(uvicorn, "run") as mock_run:
result = runner.invoke(app, ["run", "without_docs_url_none.py"])
assert result.exit_code == 0, result.output
assert mock_run.called
assert mock_run.call_args
assert mock_run.call_args.kwargs == {
"app": "without_docs_url_none:app",
"host": "0.0.0.0",
"port": 8000,
"reload": False,
"workers": None,
"root_path": "",
"proxy_headers": True,
}
assert "Using import string without_docs_url_none:app" in result.output
assert (
"╭─────────── FastAPI CLI - Production mode ───────────╮" in result.output
)
assert "│ Serving at: http://0.0.0.0:8000" in result.output
assert "│ Running in production mode, for development use:" in result.output
assert "│ fastapi dev" in result.output

assert "│ API docs" not in result.output


def test_dev_and_fastapi_app_docs_url_set_to_none_should_not_show_api_docs_section() -> (
None
):
with changing_dir(assets_path):
with patch.object(uvicorn, "run") as mock_run:
result = runner.invoke(app, ["dev", "without_docs_url_none.py"])
assert result.exit_code == 0, result.output
assert mock_run.called
assert mock_run.call_args
assert mock_run.call_args.kwargs == {
"app": "without_docs_url_none:app",
"host": "127.0.0.1",
"port": 8000,
"reload": True,
"workers": None,
"root_path": "",
"proxy_headers": True,
}
assert "Using import string without_docs_url_none:app" in result.output
assert (
"╭────────── FastAPI CLI - Development mode ───────────"
) in result.output
assert "│ Serving at: http://127.0.0.1:8000" in result.output
assert "│ Running in development mode, for production use:" in result.output
assert "│ fastapi run" in result.output

assert "│ API docs" not in result.output
Loading