Skip to content

Commit efa83f6

Browse files
committed
feat: update discover function to return fastapi app
1 parent 2173760 commit efa83f6

File tree

7 files changed

+58
-48
lines changed

7 files changed

+58
-48
lines changed

src/fastapi_cli/cli.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from rich.panel import Panel
99
from typing_extensions import Annotated
1010

11-
from fastapi_cli.discover import get_import_string
11+
from fastapi_cli.discover import get_import_string_and_app
1212
from fastapi_cli.exceptions import FastAPICLIException
1313

1414
from . import __version__
@@ -62,7 +62,9 @@ def _run(
6262
proxy_headers: bool = False,
6363
) -> None:
6464
try:
65-
use_uvicorn_app = get_import_string(path=path, app_name=app)
65+
# Get import_string app to use it with uvicorn and enable reload or workers.
66+
# Get FastAPI app to conditional printing API docs URLs
67+
import_string, app = get_import_string_and_app(path=path, app_name=app_name)
6668
except FastAPICLIException as e:
6769
logger.error(str(e))
6870
raise typer.Exit(code=1) from None

src/fastapi_cli/discover.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ def get_module_data_from_path(path: Path) -> ModuleData:
9898
)
9999

100100

101-
def get_app_name(*, mod_data: ModuleData, app_name: Union[str, None] = None) -> str:
101+
def get_app_name_add_app(
102+
*, mod_data: ModuleData, app_name: Union[str, None] = None
103+
) -> tuple[str, FastAPI]:
102104
try:
103105
mod = importlib.import_module(mod_data.module_import_str)
104106
except (ImportError, ValueError) as e:
@@ -123,22 +125,22 @@ def get_app_name(*, mod_data: ModuleData, app_name: Union[str, None] = None) ->
123125
raise FastAPICLIException(
124126
f"The app name {app_name} in {mod_data.module_import_str} doesn't seem to be a FastAPI app"
125127
)
126-
return app_name
128+
return app_name, app
127129
for preferred_name in ["app", "api"]:
128130
if preferred_name in object_names_set:
129131
obj = getattr(mod, preferred_name)
130132
if isinstance(obj, FastAPI):
131-
return preferred_name
133+
return preferred_name, obj
132134
for name in object_names:
133135
obj = getattr(mod, name)
134136
if isinstance(obj, FastAPI):
135-
return name
137+
return name, obj
136138
raise FastAPICLIException("Could not find FastAPI app in module, try using --app")
137139

138140

139-
def get_import_string(
141+
def get_import_string_and_app(
140142
*, path: Union[Path, None] = None, app_name: Union[str, None] = None
141-
) -> str:
143+
) -> tuple[str, FastAPI]:
142144
if not path:
143145
path = get_default_path()
144146
logger.info(f"Using path [blue]{path}[/blue]")
@@ -147,7 +149,7 @@ def get_import_string(
147149
raise FastAPICLIException(f"Path does not exist {path}")
148150
mod_data = get_module_data_from_path(path)
149151
sys.path.insert(0, str(mod_data.extra_sys_path))
150-
use_app_name = get_app_name(mod_data=mod_data, app_name=app_name)
152+
use_app_name, app = get_app_name_add_app(mod_data=mod_data, app_name=app_name)
151153
import_example = Syntax(
152154
f"from {mod_data.module_import_str} import {use_app_name}", "python"
153155
)
@@ -164,4 +166,4 @@ def get_import_string(
164166
print(import_panel)
165167
import_string = f"{mod_data.module_import_str}:{use_app_name}"
166168
logger.info(f"Using import string [b green]{import_string}[/b green]")
167-
return import_string
169+
return import_string, app

tests/test_requirements.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from pathlib import Path
22

33
import pytest
4-
from fastapi_cli.discover import get_import_string
4+
from fastapi_cli.discover import get_import_string_and_app
55
from fastapi_cli.exceptions import FastAPICLIException
66
from typer.testing import CliRunner
77

@@ -37,7 +37,7 @@ def test_no_fastapi() -> None:
3737
fastapi_cli.discover.FastAPI = None # type: ignore[attr-defined, assignment]
3838
with changing_dir(assets_path):
3939
with pytest.raises(FastAPICLIException) as exc_info:
40-
get_import_string(path=Path("single_file_app.py"))
40+
get_import_string_and_app(path=Path("single_file_app.py"))
4141
assert "Could not import FastAPI, try running 'pip install fastapi'" in str(
4242
exc_info.value
4343
)

tests/test_utils_default_dir.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from pathlib import Path
22

33
import pytest
4-
from fastapi_cli.discover import get_import_string
4+
from fastapi_cli.discover import get_import_string_and_app
55
from fastapi_cli.exceptions import FastAPICLIException
66
from pytest import CaptureFixture
77

@@ -12,7 +12,7 @@
1212

1313
def test_app_dir_main(capsys: CaptureFixture[str]) -> None:
1414
with changing_dir(assets_path / "default_files" / "default_app_dir_main"):
15-
import_string = get_import_string()
15+
import_string = get_import_string_and_app()
1616
assert import_string == "app.main:app"
1717

1818
captured = capsys.readouterr()
@@ -36,7 +36,7 @@ def test_app_dir_main(capsys: CaptureFixture[str]) -> None:
3636

3737
def test_app_dir_app(capsys: CaptureFixture[str]) -> None:
3838
with changing_dir(assets_path / "default_files" / "default_app_dir_app"):
39-
import_string = get_import_string()
39+
import_string = get_import_string_and_app()
4040
assert import_string == "app.app:app"
4141

4242
captured = capsys.readouterr()
@@ -58,7 +58,7 @@ def test_app_dir_app(capsys: CaptureFixture[str]) -> None:
5858

5959
def test_app_dir_api(capsys: CaptureFixture[str]) -> None:
6060
with changing_dir(assets_path / "default_files" / "default_app_dir_api"):
61-
import_string = get_import_string()
61+
import_string = get_import_string_and_app()
6262
assert import_string == "app.api:app"
6363

6464
captured = capsys.readouterr()
@@ -81,7 +81,7 @@ def test_app_dir_api(capsys: CaptureFixture[str]) -> None:
8181
def test_app_dir_non_default() -> None:
8282
with changing_dir(assets_path / "default_files" / "default_app_dir_non_default"):
8383
with pytest.raises(FastAPICLIException) as e:
84-
get_import_string()
84+
get_import_string_and_app()
8585
assert (
8686
"Could not find a default file to run, please provide an explicit path"
8787
in e.value.args[0]

tests/test_utils_default_file.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44

55
import pytest
6-
from fastapi_cli.discover import get_import_string
6+
from fastapi_cli.discover import get_import_string_and_app
77
from fastapi_cli.exceptions import FastAPICLIException
88
from pytest import CaptureFixture
99

@@ -20,7 +20,7 @@ def test_single_file_main(capsys: CaptureFixture[str]) -> None:
2020
mod = importlib.import_module("main")
2121

2222
importlib.reload(mod)
23-
import_string = get_import_string()
23+
import_string = get_import_string_and_app()
2424
assert import_string == "main:app"
2525

2626
captured = capsys.readouterr()
@@ -47,7 +47,7 @@ def test_single_file_app(capsys: CaptureFixture[str]) -> None:
4747
mod = importlib.import_module("app")
4848

4949
importlib.reload(mod)
50-
import_string = get_import_string()
50+
import_string = get_import_string_and_app()
5151
assert import_string == "app:app"
5252

5353
captured = capsys.readouterr()
@@ -74,7 +74,7 @@ def test_single_file_api(capsys: CaptureFixture[str]) -> None:
7474
mod = importlib.import_module("api")
7575

7676
importlib.reload(mod)
77-
import_string = get_import_string()
77+
import_string = get_import_string_and_app()
7878
assert import_string == "api:app"
7979

8080
captured = capsys.readouterr()
@@ -96,7 +96,7 @@ def test_single_file_api(capsys: CaptureFixture[str]) -> None:
9696
def test_non_default_file(capsys: CaptureFixture[str]) -> None:
9797
with changing_dir(assets_path / "default_files" / "non_default"):
9898
with pytest.raises(FastAPICLIException) as e:
99-
get_import_string()
99+
get_import_string_and_app()
100100
assert (
101101
"Could not find a default file to run, please provide an explicit path"
102102
in e.value.args[0]

tests/test_utils_package.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from pathlib import Path
22

33
import pytest
4-
from fastapi_cli.discover import get_import_string
4+
from fastapi_cli.discover import get_import_string_and_app
55
from fastapi_cli.exceptions import FastAPICLIException
66
from pytest import CaptureFixture
77

@@ -12,7 +12,7 @@
1212

1313
def test_package_app_root(capsys: CaptureFixture[str]) -> None:
1414
with changing_dir(assets_path):
15-
import_string = get_import_string(path=Path("package/mod/app.py"))
15+
import_string = get_import_string_and_app(path=Path("package/mod/app.py"))
1616
assert import_string == "package.mod.app:app"
1717

1818
captured = capsys.readouterr()
@@ -40,7 +40,7 @@ def test_package_app_root(capsys: CaptureFixture[str]) -> None:
4040

4141
def test_package_api_root(capsys: CaptureFixture[str]) -> None:
4242
with changing_dir(assets_path):
43-
import_string = get_import_string(path=Path("package/mod/api.py"))
43+
import_string = get_import_string_and_app(path=Path("package/mod/api.py"))
4444
assert import_string == "package.mod.api:api"
4545

4646
captured = capsys.readouterr()
@@ -68,7 +68,7 @@ def test_package_api_root(capsys: CaptureFixture[str]) -> None:
6868

6969
def test_package_other_root(capsys: CaptureFixture[str]) -> None:
7070
with changing_dir(assets_path):
71-
import_string = get_import_string(path=Path("package/mod/other.py"))
71+
import_string = get_import_string_and_app(path=Path("package/mod/other.py"))
7272
assert import_string == "package.mod.other:first_other"
7373

7474
captured = capsys.readouterr()
@@ -96,7 +96,7 @@ def test_package_other_root(capsys: CaptureFixture[str]) -> None:
9696

9797
def test_package_app_mod(capsys: CaptureFixture[str]) -> None:
9898
with changing_dir(assets_path / "package/mod"):
99-
import_string = get_import_string(path=Path("app.py"))
99+
import_string = get_import_string_and_app(path=Path("app.py"))
100100
assert import_string == "package.mod.app:app"
101101

102102
captured = capsys.readouterr()
@@ -124,7 +124,7 @@ def test_package_app_mod(capsys: CaptureFixture[str]) -> None:
124124

125125
def test_package_api_mod(capsys: CaptureFixture[str]) -> None:
126126
with changing_dir(assets_path / "package/mod"):
127-
import_string = get_import_string(path=Path("api.py"))
127+
import_string = get_import_string_and_app(path=Path("api.py"))
128128
assert import_string == "package.mod.api:api"
129129

130130
captured = capsys.readouterr()
@@ -152,7 +152,7 @@ def test_package_api_mod(capsys: CaptureFixture[str]) -> None:
152152

153153
def test_package_other_mod(capsys: CaptureFixture[str]) -> None:
154154
with changing_dir(assets_path / "package/mod"):
155-
import_string = get_import_string(path=Path("other.py"))
155+
import_string = get_import_string_and_app(path=Path("other.py"))
156156
assert import_string == "package.mod.other:first_other"
157157

158158
captured = capsys.readouterr()
@@ -180,7 +180,9 @@ def test_package_other_mod(capsys: CaptureFixture[str]) -> None:
180180

181181
def test_package_app_above(capsys: CaptureFixture[str]) -> None:
182182
with changing_dir(assets_path.parent):
183-
import_string = get_import_string(path=Path("assets/package/mod/app.py"))
183+
import_string = get_import_string_and_app(
184+
path=Path("assets/package/mod/app.py")
185+
)
184186
assert import_string == "package.mod.app:app"
185187

186188
captured = capsys.readouterr()
@@ -208,7 +210,9 @@ def test_package_app_above(capsys: CaptureFixture[str]) -> None:
208210

209211
def test_package_api_parent(capsys: CaptureFixture[str]) -> None:
210212
with changing_dir(assets_path.parent):
211-
import_string = get_import_string(path=Path("assets/package/mod/api.py"))
213+
import_string = get_import_string_and_app(
214+
path=Path("assets/package/mod/api.py")
215+
)
212216
assert import_string == "package.mod.api:api"
213217

214218
captured = capsys.readouterr()
@@ -236,7 +240,9 @@ def test_package_api_parent(capsys: CaptureFixture[str]) -> None:
236240

237241
def test_package_other_parent(capsys: CaptureFixture[str]) -> None:
238242
with changing_dir(assets_path.parent):
239-
import_string = get_import_string(path=Path("assets/package/mod/other.py"))
243+
import_string = get_import_string_and_app(
244+
path=Path("assets/package/mod/other.py")
245+
)
240246
assert import_string == "package.mod.other:first_other"
241247

242248
captured = capsys.readouterr()
@@ -264,7 +270,7 @@ def test_package_other_parent(capsys: CaptureFixture[str]) -> None:
264270

265271
def test_package_mod_init_inside(capsys: CaptureFixture[str]) -> None:
266272
with changing_dir(assets_path / "package/mod"):
267-
import_string = get_import_string(path=Path("__init__.py"))
273+
import_string = get_import_string_and_app(path=Path("__init__.py"))
268274
assert import_string == "package.mod:app"
269275

270276
captured = capsys.readouterr()
@@ -291,7 +297,7 @@ def test_package_mod_init_inside(capsys: CaptureFixture[str]) -> None:
291297

292298
def test_package_mod_dir(capsys: CaptureFixture[str]) -> None:
293299
with changing_dir(assets_path):
294-
import_string = get_import_string(path=Path("package/mod"))
300+
import_string = get_import_string_and_app(path=Path("package/mod"))
295301
assert import_string == "package.mod:app"
296302

297303
captured = capsys.readouterr()
@@ -318,7 +324,7 @@ def test_package_mod_dir(capsys: CaptureFixture[str]) -> None:
318324

319325
def test_package_init_inside(capsys: CaptureFixture[str]) -> None:
320326
with changing_dir(assets_path / "package"):
321-
import_string = get_import_string(path=Path("__init__.py"))
327+
import_string = get_import_string_and_app(path=Path("__init__.py"))
322328
assert import_string == "package:app"
323329

324330
captured = capsys.readouterr()
@@ -343,7 +349,7 @@ def test_package_init_inside(capsys: CaptureFixture[str]) -> None:
343349

344350
def test_package_dir_inside_package(capsys: CaptureFixture[str]) -> None:
345351
with changing_dir(assets_path / "package/mod"):
346-
import_string = get_import_string(path=Path("../"))
352+
import_string = get_import_string_and_app(path=Path("../"))
347353
assert import_string == "package:app"
348354

349355
captured = capsys.readouterr()
@@ -368,7 +374,7 @@ def test_package_dir_inside_package(capsys: CaptureFixture[str]) -> None:
368374

369375
def test_package_dir_above_package(capsys: CaptureFixture[str]) -> None:
370376
with changing_dir(assets_path.parent):
371-
import_string = get_import_string(path=Path("assets/package"))
377+
import_string = get_import_string_and_app(path=Path("assets/package"))
372378
assert import_string == "package:app"
373379

374380
captured = capsys.readouterr()
@@ -393,7 +399,7 @@ def test_package_dir_above_package(capsys: CaptureFixture[str]) -> None:
393399

394400
def test_package_dir_explicit_app(capsys: CaptureFixture[str]) -> None:
395401
with changing_dir(assets_path):
396-
import_string = get_import_string(path=Path("package"), app_name="api")
402+
import_string = get_import_string_and_app(path=Path("package"), app_name="api")
397403
assert import_string == "package:api"
398404

399405
captured = capsys.readouterr()
@@ -420,7 +426,7 @@ def test_broken_package_dir(capsys: CaptureFixture[str]) -> None:
420426
with changing_dir(assets_path):
421427
# TODO (when deprecating Python 3.8): remove ValueError
422428
with pytest.raises((ImportError, ValueError)):
423-
get_import_string(path=Path("broken_package/mod/app.py"))
429+
get_import_string_and_app(path=Path("broken_package/mod/app.py"))
424430

425431
captured = capsys.readouterr()
426432
assert "Import error:" in captured.out
@@ -430,7 +436,7 @@ def test_broken_package_dir(capsys: CaptureFixture[str]) -> None:
430436
def test_package_dir_no_app() -> None:
431437
with changing_dir(assets_path):
432438
with pytest.raises(FastAPICLIException) as e:
433-
get_import_string(path=Path("package/core/utils.py"))
439+
get_import_string_and_app(path=Path("package/core/utils.py"))
434440
assert (
435441
"Could not find FastAPI app in module, try using --app" in e.value.args[0]
436442
)
@@ -439,7 +445,7 @@ def test_package_dir_no_app() -> None:
439445
def test_package_dir_object_not_an_app() -> None:
440446
with changing_dir(assets_path):
441447
with pytest.raises(FastAPICLIException) as e:
442-
get_import_string(
448+
get_import_string_and_app(
443449
path=Path("package/core/utils.py"), app_name="get_hello_world"
444450
)
445451
assert (
@@ -451,5 +457,5 @@ def test_package_dir_object_not_an_app() -> None:
451457
def test_package_dir_object_app_name_not_found() -> None:
452458
with changing_dir(assets_path):
453459
with pytest.raises(FastAPICLIException) as e:
454-
get_import_string(path=Path("package"), app_name="non_existent_app")
460+
get_import_string_and_app(path=Path("package"), app_name="non_existent_app")
455461
assert "Could not find app name non_existent_app in package" in e.value.args[0]

0 commit comments

Comments
 (0)