aio-libs/aiohttp · error · ValueError

' ' is not a directory

Error message

'{directory}' is not a directory

What it means

After confirming the path exists (resolve(strict=True) succeeded), StaticResource.__init__ checks directory.is_dir(). If the path exists but is a regular file (or a special file), it raises ValueError. A static resource must point at a directory because the route serves files relative to that root.

Solutions

  1. Pass the directory that contains the files, not an individual file.
  2. If you want to serve a single file, use a route returning FileResponse instead of add_static.
  3. Check with Path(p).is_dir() before registering.

Example fix

// before
app.router.add_static('/static', '/app/assets/logo.png')
// after
app.router.add_static('/static', '/app/assets')  # the directory
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def assert_is_directory(p):
    p = Path(p)
    if not p.is_dir():
        raise ValueError(f'{p} exists but is not a directory')
    return p

Prevention

When it happens

Trigger: Calling add_static('/static', '/path/to/file.txt') — the path resolves successfully (no FileNotFoundError) but is_dir() returns False. The error fires at registration time.

Common situations: Pointing add_static at a file instead of a directory; a symlink that resolves to a file; misreading docs and passing the bundle file rather than its containing folder; path manipulation that strips a trailing component.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/f29dad22d642c240. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/web_urldispatcher.py:521

    def __init__(
        self,
        prefix: str,
        directory: PathLike,
        *,
        name: str | None = None,
        expect_handler: _ExpectHandler | None = None,
        chunk_size: int = DEFAULT_CHUNK_SIZE,
        show_index: bool = False,
        break_symlink_sandbox: bool = False,
        append_version: bool = False,
    ) -> None:
        super().__init__(prefix, name=name)
        try:
            directory = Path(directory).expanduser().resolve(strict=True)
        except FileNotFoundError as error:
            raise ValueError(f"'{directory}' does not exist") from error
        if not directory.is_dir():
            raise ValueError(f"'{directory}' is not a directory")
        self._directory = directory
        self._show_index = show_index
        self._chunk_size = chunk_size
        self._break_symlink_sandbox = break_symlink_sandbox
        self._expect_handler = expect_handler
        self._append_version = append_version

        self._routes = {
            "GET": ResourceRoute(
                "GET", self._handle, self, expect_handler=expect_handler
            ),
            "HEAD": ResourceRoute(
                "HEAD", self._handle, self, expect_handler=expect_handler
            ),
        }
        self._allowed_methods = set(self._routes)

    def url_for(  # type: ignore[override]

View on GitHub (pinned to d041d4d0fd)