Skip to content

fix(cli): don't crash autodiscovery on unresolvable type hints - #4943

Open
Gooh456 wants to merge 1 commit into
litestar-org:mainfrom
Gooh456:fix/cli-autodiscover-typing-nameerror
Open

fix(cli): don't crash autodiscovery on unresolvable type hints#4943
Gooh456 wants to merge 1 commit into
litestar-org:mainfrom
Gooh456:fix/cli-autodiscover-typing-nameerror

Conversation

@Gooh456

@Gooh456 Gooh456 commented Jul 24, 2026

Copy link
Copy Markdown

ran into this poking at the third loop in _autodiscover_app (litestar/cli/_utils.py) — it walks every callable in a scanned module's __dict__ looking for a factory, and calls get_type_hints() on each one. thing is, if that module doesn't define app/application/create_app and just has something like from litestar import Request sitting there for annotations, Request itself gets probed too since classes count as callable.

Request pulls in ASGIConnection, and its scope: Scope attribute only has Scope importable under TYPE_CHECKING (litestar/connection/base.py). So get_type_hints(Request) throws a bare NameError, uncaught, and the whole CLI dies instead of just skipping that attr and moving on to the actual factory.

confirmed on main:

from litestar.cli._utils import _autodiscover_app
_autodiscover_app(Path("."))
# NameError: name 'Scope' is not defined

for a directory containing just:

from litestar import Request

def helper() -> None: ...

fix: pulled the get_type_hints call into a small helper (_get_return_type_hint) that catches NameError and returns None, treated the same as "no return annotation" — so autodiscovery just moves on to the next attr instead of blowing up. also happened to keep _autodiscover_app under ruff's C901 complexity limit, which the inline try/except pushed it over.

added a regression test in test_env_resolution.py that reproduces the original scenario (factory function alongside an unrelated Request import) — fails with NameError on unpatched code, passes with the fix. ran the full tests/unit/test_cli/ suite (876 passed), plus mypy/pyright/ruff clean on the touched files.

Fixes #3895


📚 Documentation preview 📚: https://litestar-org.github.io/litestar-docs-preview/4943

_autodiscover_app() calls get_type_hints() on every callable in a
scanned module's __dict__ while looking for a factory function. If a
module just imports something like Request for annotation purposes
(no app/application/create_app defined there), that import itself
gets probed too, since classes are callable. Request pulls in
ASGIConnection, whose `scope: Scope` annotation only has Scope
importable under TYPE_CHECKING, so get_type_hints() raises a bare
NameError that propagates straight out of the CLI instead of just
skipping that attr and moving on.

Pulled the get_type_hints call into a small helper that swallows
NameError and returns None, same as "no return annotation" -- keeps
_autodiscover_app's complexity under the ruff C901 limit too.

Fixes litestar-org#3895

Signed-off-by: Kyue <164024549+Gooh456@users.noreply.github.com>
@Gooh456
Gooh456 requested review from a team as code owners July 24, 2026 16:53
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.31%. Comparing base (5427d26) to head (5595828).

Files with missing lines Patch % Lines
litestar/cli/_utils.py 75.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4943   +/-   ##
=======================================
  Coverage   67.31%   67.31%           
=======================================
  Files         293      293           
  Lines       15233    15240    +7     
  Branches     1728     1729    +1     
=======================================
+ Hits        10254    10259    +5     
- Misses       4832     4833    +1     
- Partials      147      148    +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread litestar/cli/_utils.py
Comment on lines +353 to +360
"""Return the resolved ``"return"`` type hint for ``value``, or ``None`` if it has none or can't be resolved.

``value`` may be an unrelated callable/class pulled in via a module-level import (e.g.
``from litestar import Request``) rather than something the user actually intends as an app
factory. Its annotations can reference names that only exist under ``TYPE_CHECKING`` in their
defining module, so resolving them can raise a ``NameError`` even though it has nothing to do
with the app factory we're looking for.
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Way too much docstring for so little functionality :)

Comment thread litestar/cli/_utils.py
Comment on lines +361 to +362
if not hasattr(value, "__annotations__"):
return None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems redundant. If you catch errors from get_type_hints, that should be good enough

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: CLI - litestar run raises 'Scope' is not defined when using Request in type annotation

2 participants