Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): ensure that the app loads correctly before the service starts #2444

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ def test_cli_uds(uds_file: Path) -> None: # pragma: py-win32
result = runner.invoke(cli, ["tests.test_cli:App", "--workers=2", "--uds", str(uds_file)])

assert result.exit_code == 0
assert result.output == ""
assert (
result.output
== "WARNING: ASGI app factory detected. Using it, but please consider setting the --factory flag explicitly.\n"
)
mock_bind_socket.assert_called_once()
mock_run.assert_called_once()
assert not uds_file.exists()
Expand Down
40 changes: 22 additions & 18 deletions uvicorn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,31 @@ def configure_logging(self) -> None:
logging.getLogger("uvicorn.access").handlers = []
logging.getLogger("uvicorn.access").propagate = False

def check_load_app(self) -> Any:
try:
app = import_from_string(self.app)
except ImportFromStringError as exc:
logger.error("Error loading ASGI app. %s" % exc)
sys.exit(1)

try:
app = app()
except TypeError as exc:
if self.factory:
logger.error("Error loading ASGI app factory: %s", exc)
sys.exit(1)
else:
if not self.factory:
logger.warning(
"ASGI app factory detected. Using it, " "but please consider setting the --factory flag explicitly."
)
return app

def load(self) -> None:
assert not self.loaded

self.loaded_app = self.check_load_app()

if self.is_ssl:
assert self.ssl_certfile
self.ssl: ssl.SSLContext | None = create_ssl_context(
Expand Down Expand Up @@ -430,24 +452,6 @@ def load(self) -> None:

self.lifespan_class = import_from_string(LIFESPAN[self.lifespan])

try:
self.loaded_app = import_from_string(self.app)
except ImportFromStringError as exc:
logger.error("Error loading ASGI app. %s" % exc)
sys.exit(1)

try:
self.loaded_app = self.loaded_app()
except TypeError as exc:
if self.factory:
logger.error("Error loading ASGI app factory: %s", exc)
sys.exit(1)
else:
if not self.factory:
logger.warning(
"ASGI app factory detected. Using it, " "but please consider setting the --factory flag explicitly."
)

if self.interface == "auto":
if inspect.isclass(self.loaded_app):
use_asgi_3 = hasattr(self.loaded_app, "__await__")
Expand Down
5 changes: 3 additions & 2 deletions uvicorn/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ def run(
factory=factory,
h11_max_incomplete_event_size=h11_max_incomplete_event_size,
)
config.check_load_app()
server = Server(config=config)

if (config.reload or config.workers > 1) and not isinstance(app, str):
Expand All @@ -577,8 +578,8 @@ def run(
Multiprocess(config, target=server.run, sockets=[sock]).run()
else:
server.run()
except KeyboardInterrupt:
pass # pragma: full coverage
except KeyboardInterrupt: # pragma: full coverage
pass
finally:
if config.uds and os.path.exists(config.uds):
os.remove(config.uds) # pragma: py-win32
Expand Down
Loading