Skip to content

Commit c635a56

Browse files
committed
Show import string source
1 parent 77b09ee commit c635a56

4 files changed

Lines changed: 84 additions & 10 deletions

File tree

src/fastapi_cli/cli.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
from rich.tree import Tree
99

1010
from fastapi_cli.config import FastAPIConfig
11-
from fastapi_cli.discover import get_import_data, get_import_data_from_import_string
11+
from fastapi_cli.discover import (
12+
AppConfigSource,
13+
ModuleConfigSource,
14+
get_import_data,
15+
get_import_data_from_import_string,
16+
)
1217
from fastapi_cli.exceptions import FastAPICLIException
1318

1419
from . import __version__
@@ -22,6 +27,15 @@
2227
logger = logging.getLogger(__name__)
2328

2429

30+
SOURCE_DESCRIPTIONS: dict[ModuleConfigSource | AppConfigSource, str] = {
31+
"entrypoint-option": "[blue]--entrypoint[/] option",
32+
"entrypoint-pyproject": "[blue]entrypoint[/] in [blue]pyproject.toml[/]",
33+
"path-argument": "path argument",
34+
"app-option": "[blue]--app[/] option",
35+
"auto-discovery": "auto-discovery",
36+
}
37+
38+
2539
try:
2640
import uvicorn
2741
except ImportError: # pragma: no cover
@@ -151,7 +165,9 @@ def _run(
151165
if path or app:
152166
import_data = get_import_data(path=path, app_name=app)
153167
elif config.entrypoint:
154-
import_data = get_import_data_from_import_string(config.entrypoint)
168+
import_data = get_import_data_from_import_string(
169+
config.entrypoint, config.from_pyproject
170+
)
155171
else:
156172
import_data = get_import_data()
157173
except FastAPICLIException as e:
@@ -189,6 +205,17 @@ def _run(
189205
tag="app",
190206
)
191207

208+
# Pring configuration sources
209+
mod_source_desc = SOURCE_DESCRIPTIONS[import_data.module_config_source]
210+
app_source_desc = SOURCE_DESCRIPTIONS[import_data.app_name_config_source]
211+
toolkit.print_line()
212+
toolkit.print("Configuration sources:", tag="info")
213+
if mod_source_desc == app_source_desc:
214+
toolkit.print(f" • Import string: {mod_source_desc}")
215+
else:
216+
toolkit.print(f" • Module: {mod_source_desc}")
217+
toolkit.print(f" • App name: {app_source_desc}")
218+
192219
url = f"http://{host}:{port}"
193220
url_docs = f"{url}/docs"
194221

src/fastapi_cli/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
class FastAPIConfig(BaseModel):
1111
entrypoint: StrictStr | None = None
12+
from_pyproject: bool = False
1213

1314
@classmethod
1415
def _read_pyproject_toml(cls) -> dict[str, Any]:
@@ -39,4 +40,6 @@ def resolve(cls, entrypoint: str | None = None) -> "FastAPIConfig":
3940
if entrypoint is not None:
4041
config["entrypoint"] = entrypoint
4142

43+
config["from_pyproject"] = ("entrypoint" in config) and (entrypoint is None)
44+
4245
return cls.model_validate(config)

src/fastapi_cli/discover.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from dataclasses import dataclass
44
from logging import getLogger
55
from pathlib import Path
6+
from typing import Literal, TypeAlias
67

78
from fastapi_cli.exceptions import FastAPICLIException
89

@@ -102,18 +103,35 @@ def get_app_name(*, mod_data: ModuleData, app_name: str | None = None) -> str:
102103
raise FastAPICLIException("Could not find FastAPI app in module, try using --app")
103104

104105

106+
ModuleConfigSource: TypeAlias = Literal[
107+
"entrypoint-option",
108+
"entrypoint-pyproject",
109+
"path-argument",
110+
"auto-discovery",
111+
]
112+
113+
AppConfigSource: TypeAlias = Literal[
114+
"entrypoint-option", "entrypoint-pyproject", "app-option", "auto-discovery"
115+
]
116+
117+
105118
@dataclass
106119
class ImportData:
107120
app_name: str
108121
module_data: ModuleData
109122
import_string: str
110123

124+
module_config_source: ModuleConfigSource
125+
app_name_config_source: AppConfigSource
126+
111127

112128
def get_import_data(
113129
*, path: Path | None = None, app_name: str | None = None
114130
) -> ImportData:
131+
path_config_source: ModuleConfigSource = "path-argument"
115132
if not path:
116133
path = get_default_path()
134+
path_config_source = "auto-discovery"
117135

118136
logger.debug(f"Using path [blue]{path}[/blue]")
119137
logger.debug(f"Resolved absolute path {path.resolve()}")
@@ -127,11 +145,17 @@ def get_import_data(
127145
import_string = f"{mod_data.module_import_str}:{use_app_name}"
128146

129147
return ImportData(
130-
app_name=use_app_name, module_data=mod_data, import_string=import_string
148+
app_name=use_app_name,
149+
module_data=mod_data,
150+
import_string=import_string,
151+
module_config_source=path_config_source,
152+
app_name_config_source="app-option" if app_name else "auto-discovery",
131153
)
132154

133155

134-
def get_import_data_from_import_string(import_string: str) -> ImportData:
156+
def get_import_data_from_import_string(
157+
import_string: str, from_pyproject: bool
158+
) -> ImportData:
135159
module_str, _, app_name = import_string.partition(":")
136160

137161
if not module_str or not app_name:
@@ -151,4 +175,10 @@ def get_import_data_from_import_string(import_string: str) -> ImportData:
151175
module_paths=[],
152176
),
153177
import_string=import_string,
178+
module_config_source=(
179+
"entrypoint-pyproject" if from_pyproject else "entrypoint-option"
180+
),
181+
app_name_config_source=(
182+
"entrypoint-pyproject" if from_pyproject else "entrypoint-option"
183+
),
154184
)

tests/test_discover.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,34 @@
1212

1313

1414
def test_get_import_data_from_import_string_valid() -> None:
15-
result = get_import_data_from_import_string("module.submodule:app")
15+
result = get_import_data_from_import_string("module.submodule:app", False)
1616

1717
assert isinstance(result, ImportData)
1818
assert result.app_name == "app"
1919
assert result.import_string == "module.submodule:app"
2020
assert result.module_data.module_import_str == "module.submodule"
2121
assert result.module_data.extra_sys_path == Path(".").resolve()
2222
assert result.module_data.module_paths == []
23+
assert result.module_config_source == "entrypoint-option"
24+
assert result.app_name_config_source == "entrypoint-option"
25+
26+
27+
def test_get_import_data_from_import_string_pyproject_valid() -> None:
28+
result = get_import_data_from_import_string("module.submodule:app", True)
29+
30+
assert isinstance(result, ImportData)
31+
assert result.app_name == "app"
32+
assert result.import_string == "module.submodule:app"
33+
assert result.module_data.module_import_str == "module.submodule"
34+
assert result.module_data.extra_sys_path == Path(".").resolve()
35+
assert result.module_data.module_paths == []
36+
assert result.module_config_source == "entrypoint-pyproject"
37+
assert result.app_name_config_source == "entrypoint-pyproject"
2338

2439

2540
def test_get_import_data_from_import_string_missing_colon() -> None:
2641
with pytest.raises(FastAPICLIException) as exc_info:
27-
get_import_data_from_import_string("module.submodule")
42+
get_import_data_from_import_string("module.submodule", False)
2843

2944
assert "Import string must be in the format module.submodule:app_name" in str(
3045
exc_info.value
@@ -33,7 +48,7 @@ def test_get_import_data_from_import_string_missing_colon() -> None:
3348

3449
def test_get_import_data_from_import_string_missing_app() -> None:
3550
with pytest.raises(FastAPICLIException) as exc_info:
36-
get_import_data_from_import_string("module.submodule:")
51+
get_import_data_from_import_string("module.submodule:", False)
3752

3853
assert "Import string must be in the format module.submodule:app_name" in str(
3954
exc_info.value
@@ -42,7 +57,7 @@ def test_get_import_data_from_import_string_missing_app() -> None:
4257

4358
def test_get_import_data_from_import_string_missing_module() -> None:
4459
with pytest.raises(FastAPICLIException) as exc_info:
45-
get_import_data_from_import_string(":app")
60+
get_import_data_from_import_string(":app", False)
4661

4762
assert "Import string must be in the format module.submodule:app_name" in str(
4863
exc_info.value
@@ -51,8 +66,7 @@ def test_get_import_data_from_import_string_missing_module() -> None:
5166

5267
def test_get_import_data_from_import_string_empty() -> None:
5368
with pytest.raises(FastAPICLIException) as exc_info:
54-
get_import_data_from_import_string("")
55-
69+
get_import_data_from_import_string("", False)
5670
assert "Import string must be in the format module.submodule:app_name" in str(
5771
exc_info.value
5872
)

0 commit comments

Comments
 (0)