Skip to content

Commit 22a2c03

Browse files
committed
added factory support
1 parent 4903ed1 commit 22a2c03

File tree

2 files changed

+28
-10
lines changed

2 files changed

+28
-10
lines changed

src/fastapi_cli/cli.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ def _run(
6060
command: str,
6161
app: Union[str, None] = None,
6262
proxy_headers: bool = False,
63+
is_factory: bool = False,
6364
) -> None:
6465
try:
65-
use_uvicorn_app = get_import_string(path=path, app_name=app)
66+
use_uvicorn_app = get_import_string(path=path, app_name=app, is_factory=is_factory)
6667
except FastAPICLIException as e:
6768
logger.error(str(e))
6869
raise typer.Exit(code=1) from None
@@ -84,7 +85,6 @@ def _run(
8485
padding=(1, 2),
8586
style="green",
8687
)
87-
print(Padding(panel, 1))
8888
if not uvicorn:
8989
raise FastAPICLIException(
9090
"Could not import Uvicorn, try running 'pip install uvicorn'"
@@ -97,6 +97,7 @@ def _run(
9797
workers=workers,
9898
root_path=root_path,
9999
proxy_headers=proxy_headers,
100+
factory=is_factory,
100101
)
101102

102103

@@ -105,7 +106,7 @@ def dev(
105106
path: Annotated[
106107
Union[Path, None],
107108
typer.Argument(
108-
help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAPI[/bold] app. If not provided, a default set of paths will be tried."
109+
help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAPI[/bold] app or app factory. If not provided, a default set of paths will be tried."
109110
),
110111
] = None,
111112
*,
@@ -145,6 +146,10 @@ def dev(
145146
help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info."
146147
),
147148
] = True,
149+
factory: Annotated[
150+
bool,
151+
typer.Option(help="Treat [bold]path[bold] as an application factory, i.e. a () -> <ASGI app> callable.")
152+
] = False,
148153
) -> Any:
149154
"""
150155
Run a [bold]FastAPI[/bold] app in [yellow]development[/yellow] mode. 🧪
@@ -180,6 +185,7 @@ def dev(
180185
app=app,
181186
command="dev",
182187
proxy_headers=proxy_headers,
188+
is_factory=factory,
183189
)
184190

185191

@@ -234,6 +240,10 @@ def run(
234240
help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info."
235241
),
236242
] = True,
243+
factory: Annotated[
244+
bool,
245+
typer.Option(help="Treat [bold]path[bold] as an application factory, i.e. a () -> <ASGI app> callable.")
246+
] = False,
237247
) -> Any:
238248
"""
239249
Run a [bold]FastAPI[/bold] app in [green]production[/green] mode. 🚀
@@ -270,6 +280,7 @@ def run(
270280
app=app,
271281
command="run",
272282
proxy_headers=proxy_headers,
283+
is_factory=factory
273284
)
274285

275286

src/fastapi_cli/discover.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ 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(*, mod_data: ModuleData, app_name: Union[str, None] = None, is_factory: bool = False) -> str:
102102
try:
103103
mod = importlib.import_module(mod_data.module_import_str)
104104
except (ImportError, ValueError) as e:
@@ -119,25 +119,32 @@ def get_app_name(*, mod_data: ModuleData, app_name: Union[str, None] = None) ->
119119
f"Could not find app name {app_name} in {mod_data.module_import_str}"
120120
)
121121
app = getattr(mod, app_name)
122-
if not isinstance(app, FastAPI):
122+
if not isinstance(app, FastAPI) and not is_factory:
123123
raise FastAPICLIException(
124124
f"The app name {app_name} in {mod_data.module_import_str} doesn't seem to be a FastAPI app"
125125
)
126+
else:
127+
if not callable(app) and is_factory:
128+
raise FastAPICLIException(
129+
f"The app factory {app_name} in {mod_data.module_import_str} doesn't seem to be a function"
130+
)
126131
return app_name
127132
for preferred_name in ["app", "api"]:
128133
if preferred_name in object_names_set:
129134
obj = getattr(mod, preferred_name)
130-
if isinstance(obj, FastAPI):
135+
if isinstance(obj, FastAPI) and not is_factory:
131136
return preferred_name
132137
for name in object_names:
133138
obj = getattr(mod, name)
134-
if isinstance(obj, FastAPI):
139+
if isinstance(obj, FastAPI) and not is_factory:
140+
return name
141+
elif callable(name) and is_factory:
135142
return name
136-
raise FastAPICLIException("Could not find FastAPI app in module, try using --app")
143+
raise FastAPICLIException("Could not find FastAPI app or app factory in module, try using --app")
137144

138145

139146
def get_import_string(
140-
*, path: Union[Path, None] = None, app_name: Union[str, None] = None
147+
*, path: Union[Path, None] = None, app_name: Union[str, None] = None, is_factory: bool = False,
141148
) -> str:
142149
if not path:
143150
path = get_default_path()
@@ -147,7 +154,7 @@ def get_import_string(
147154
raise FastAPICLIException(f"Path does not exist {path}")
148155
mod_data = get_module_data_from_path(path)
149156
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)
157+
use_app_name = get_app_name(mod_data=mod_data, app_name=app_name, is_factory=is_factory)
151158
import_example = Syntax(
152159
f"from {mod_data.module_import_str} import {use_app_name}", "python"
153160
)

0 commit comments

Comments
 (0)