Textualize/textual · error · AppFail

Unable to find {find_app!r} in {module!r}

Error message

Unable to find {find_app!r} in {module!r}

What it means

Raised when Textual imports an app from an installed module (a dotted import path like `mylib.myapp`) and the attribute lookup for the requested name (or the default "app") fails with AttributeError. Unlike the file-based path, this branch uses importlib and getattr on the module object, so the name must be a real attribute of that package/module. It is wrapped in AppFail and typically surfaces from `textual run mylib.myapp:Something`.

Source

Thrown at src/textual/_import_app.py:119

                    raise AppFail(
                        f'Multiple apps found {lib!r}, try specifying app with "foo.py:app"'
                    )
                app = apps[0]
        app._BASE_PATH = path

    else:
        # Assuming the user wants to import the file
        sys.path.append("")
        try:
            module = importlib.import_module(lib)
        except ImportError as error:
            raise AppFail(str(error))

        find_app = name or "app"
        try:
            app = getattr(module, find_app or "app")
        except AttributeError:
            raise AppFail(f"Unable to find {find_app!r} in {module!r}")

        sys.argv[:] = [import_name, *argv]

    if inspect.isclass(app) and issubclass(app, App):
        app = app()

    return cast(App, app)

View on GitHub (pinned to 06dbeef4bb)

Solutions

  1. Reference the exact submodule and attribute: `textual run mylib.app:app`
  2. Re-export the app in the package's __init__.py (e.g. `from mylib.app import app`
  3. Check for typos and the installed package version (pip show / pip install -e .)
  4. If you meant a file on disk, use the path form `./mylib/app.py:app` instead of the dotted module form

Example fix

# before
# mylib/__init__.py is empty; app lives in mylib/main.py
# CLI: textual run mylib:app  -> AppFail

# after
# mylib/__init__.py
from mylib.main import app
# CLI: textual run mylib:app
Defensive patterns

Strategy: validation

Validate before calling

import importlib

def module_has(module_name: str, attr: str) -> bool:
    try:
        mod = importlib.import_module(module_name)
    except ImportError:
        return False
    return hasattr(mod, attr)

# assert module_has("mylib", "app") before textual run mylib:app

Type guard

def exposes_app(mod) -> TypeGuard[object]:
    return hasattr(mod, "app")

Try / catch

try:
    app = import_app("mylib:app")
except AppFail as e:
    print(f"Missing export: {e}"); raise

Prevention

When it happens

Trigger: Running `textual run mylib:MyApp` where MyApp is not exported from mylib/__init__.py; using the default `app` name when the package exposes none; typo in the attribute or package path; the app lives in a submodule like mylib.app but you referenced mylib.

Common situations: Packages that don't re-export the app in __init__.py; renaming the public app variable without updating docs/CI commands; mixing up file paths and module paths (using dots where a path is needed); stale installed version of the package missing the symbol.

Related errors


AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27). Data as JSON: /api/errors/b4919d9cf6a2982b. Report an issue: GitHub.