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
- Pass the directory that contains the files, not an individual file.
- If you want to serve a single file, use a route returning FileResponse instead of add_static.
- 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
- Pass directories to add_static, never individual files.
- For single files, use a route returning FileResponse.
- Add a Path.is_dir() assertion in your bootstrap.
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
- ' ' does not exist
- OPTIONS route was set already
- 403: Forbidden
- 404: Not Found
- aiohttp only supports http(s) proxies
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)