diff --git a/pyproject.toml b/pyproject.toml index 5fb2684c..7d325e54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,10 +70,12 @@ source-includes = [ name = "fastapi-cli-slim" [tool.tiangolo._internal-slim-build.packages.fastapi-cli] -include-optional-dependencies = ["standard"] +# No default dependencies included for now +include-optional-dependencies = [] [tool.tiangolo._internal-slim-build.packages.fastapi-cli.project] -optional-dependencies = {} +# No custom optional dependencies for now +# optional-dependencies = {} [tool.pytest.ini_options] addopts = [ diff --git a/src/fastapi_cli/cli.py b/src/fastapi_cli/cli.py index 2aea254c..d5bcb8e5 100644 --- a/src/fastapi_cli/cli.py +++ b/src/fastapi_cli/cli.py @@ -3,7 +3,6 @@ from typing import Any, Union import typer -import uvicorn from rich import print from rich.padding import Padding from rich.panel import Panel @@ -20,6 +19,11 @@ setup_logging() logger = getLogger(__name__) +try: + import uvicorn +except ImportError: # pragma: no cover + uvicorn = None # type: ignore[assignment] + def version_callback(value: bool) -> None: if value: @@ -81,6 +85,10 @@ def _run( style="green", ) print(Padding(panel, 1)) + if not uvicorn: + raise FastAPICLIException( + "Could not import Uvicorn, try running 'pip install uvicorn'" + ) from None uvicorn.run( app=use_uvicorn_app, host=host, diff --git a/src/fastapi_cli/discover.py b/src/fastapi_cli/discover.py index befcdfd0..17dd08dd 100644 --- a/src/fastapi_cli/discover.py +++ b/src/fastapi_cli/discover.py @@ -5,7 +5,6 @@ from pathlib import Path from typing import Union -from fastapi import FastAPI from rich import print from rich.padding import Padding from rich.panel import Panel @@ -16,6 +15,11 @@ logger = getLogger(__name__) +try: + from fastapi import FastAPI +except ImportError: # pragma: no cover + FastAPI = None # type: ignore[misc, assignment] + def get_default_path() -> Path: path = Path("main.py") @@ -107,6 +111,10 @@ def get_app_name(*, mod_data: ModuleData, app_name: Union[str, None] = None) -> "Ensure all the package directories have an [blue]__init__.py[/blue] file" ) raise + if not FastAPI: # type: ignore[truthy-function] + raise FastAPICLIException( + "Could not import FastAPI, try running 'pip install fastapi'" + ) from None object_names = dir(mod) object_names_set = set(object_names) if app_name: diff --git a/tests/test_requirements.py b/tests/test_requirements.py new file mode 100644 index 00000000..bcad1b91 --- /dev/null +++ b/tests/test_requirements.py @@ -0,0 +1,45 @@ +from pathlib import Path + +import pytest +from fastapi_cli.discover import get_import_string +from fastapi_cli.exceptions import FastAPICLIException +from typer.testing import CliRunner + +from .utils import changing_dir + +runner = CliRunner() + +assets_path = Path(__file__).parent / "assets" + + +def test_no_uvicorn() -> None: + import fastapi_cli.cli + import uvicorn + + fastapi_cli.cli.uvicorn = None # type: ignore[attr-defined, assignment] + + with changing_dir(assets_path): + result = runner.invoke(fastapi_cli.cli.app, ["dev", "single_file_app.py"]) + assert result.exit_code == 1 + assert result.exception is not None + assert ( + "Could not import Uvicorn, try running 'pip install uvicorn'" + in result.exception.args[0] + ) + + fastapi_cli.cli.uvicorn = uvicorn # type: ignore[attr-defined] + + +def test_no_fastapi() -> None: + import fastapi_cli.discover + from fastapi import FastAPI + + fastapi_cli.discover.FastAPI = None # type: ignore[attr-defined, assignment] + with changing_dir(assets_path): + with pytest.raises(FastAPICLIException) as exc_info: + get_import_string(path=Path("single_file_app.py")) + assert "Could not import FastAPI, try running 'pip install fastapi'" in str( + exc_info.value + ) + + fastapi_cli.discover.FastAPI = FastAPI # type: ignore[attr-defined]