Skip to content

Commit 5e78424

Browse files
authored
🔧 Make FastAPI and Uvicorn optional dependencies, to avoid circular dependencies (#25)
1 parent 35c0428 commit 5e78424

File tree

4 files changed

+67
-4
lines changed

4 files changed

+67
-4
lines changed

‎pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,12 @@ source-includes = [
7070
name = "fastapi-cli-slim"
7171

7272
[tool.tiangolo._internal-slim-build.packages.fastapi-cli]
73-
include-optional-dependencies = ["standard"]
73+
# No default dependencies included for now
74+
include-optional-dependencies = []
7475

7576
[tool.tiangolo._internal-slim-build.packages.fastapi-cli.project]
76-
optional-dependencies = {}
77+
# No custom optional dependencies for now
78+
# optional-dependencies = {}
7779

7880
[tool.pytest.ini_options]
7981
addopts = [

‎src/fastapi_cli/cli.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from typing import Any, Union
44

55
import typer
6-
import uvicorn
76
from rich import print
87
from rich.padding import Padding
98
from rich.panel import Panel
@@ -20,6 +19,11 @@
2019
setup_logging()
2120
logger = getLogger(__name__)
2221

22+
try:
23+
import uvicorn
24+
except ImportError: # pragma: no cover
25+
uvicorn = None # type: ignore[assignment]
26+
2327

2428
def version_callback(value: bool) -> None:
2529
if value:
@@ -81,6 +85,10 @@ def _run(
8185
style="green",
8286
)
8387
print(Padding(panel, 1))
88+
if not uvicorn:
89+
raise FastAPICLIException(
90+
"Could not import Uvicorn, try running 'pip install uvicorn'"
91+
) from None
8492
uvicorn.run(
8593
app=use_uvicorn_app,
8694
host=host,

‎src/fastapi_cli/discover.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from pathlib import Path
66
from typing import Union
77

8-
from fastapi import FastAPI
98
from rich import print
109
from rich.padding import Padding
1110
from rich.panel import Panel
@@ -16,6 +15,11 @@
1615

1716
logger = getLogger(__name__)
1817

18+
try:
19+
from fastapi import FastAPI
20+
except ImportError: # pragma: no cover
21+
FastAPI = None # type: ignore[misc, assignment]
22+
1923

2024
def get_default_path() -> Path:
2125
path = Path("main.py")
@@ -107,6 +111,10 @@ def get_app_name(*, mod_data: ModuleData, app_name: Union[str, None] = None) ->
107111
"Ensure all the package directories have an [blue]__init__.py[/blue] file"
108112
)
109113
raise
114+
if not FastAPI: # type: ignore[truthy-function]
115+
raise FastAPICLIException(
116+
"Could not import FastAPI, try running 'pip install fastapi'"
117+
) from None
110118
object_names = dir(mod)
111119
object_names_set = set(object_names)
112120
if app_name:

‎tests/test_requirements.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
from fastapi_cli.discover import get_import_string
5+
from fastapi_cli.exceptions import FastAPICLIException
6+
from typer.testing import CliRunner
7+
8+
from .utils import changing_dir
9+
10+
runner = CliRunner()
11+
12+
assets_path = Path(__file__).parent / "assets"
13+
14+
15+
def test_no_uvicorn() -> None:
16+
import fastapi_cli.cli
17+
import uvicorn
18+
19+
fastapi_cli.cli.uvicorn = None # type: ignore[attr-defined, assignment]
20+
21+
with changing_dir(assets_path):
22+
result = runner.invoke(fastapi_cli.cli.app, ["dev", "single_file_app.py"])
23+
assert result.exit_code == 1
24+
assert result.exception is not None
25+
assert (
26+
"Could not import Uvicorn, try running 'pip install uvicorn'"
27+
in result.exception.args[0]
28+
)
29+
30+
fastapi_cli.cli.uvicorn = uvicorn # type: ignore[attr-defined]
31+
32+
33+
def test_no_fastapi() -> None:
34+
import fastapi_cli.discover
35+
from fastapi import FastAPI
36+
37+
fastapi_cli.discover.FastAPI = None # type: ignore[attr-defined, assignment]
38+
with changing_dir(assets_path):
39+
with pytest.raises(FastAPICLIException) as exc_info:
40+
get_import_string(path=Path("single_file_app.py"))
41+
assert "Could not import FastAPI, try running 'pip install fastapi'" in str(
42+
exc_info.value
43+
)
44+
45+
fastapi_cli.discover.FastAPI = FastAPI # type: ignore[attr-defined]

0 commit comments

Comments
 (0)