{"record":{"id":"f29dad22d642c240","repo":"aio-libs/aiohttp","slug":"directory-is-not-a-directory","errorCode":null,"errorMessage":"'{directory}' is not a directory","messagePattern":"'(.+?)' is not a directory","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":521,"sourceCode":"    def __init__(\n        self,\n        prefix: str,\n        directory: PathLike,\n        *,\n        name: str | None = None,\n        expect_handler: _ExpectHandler | None = None,\n        chunk_size: int = DEFAULT_CHUNK_SIZE,\n        show_index: bool = False,\n        break_symlink_sandbox: bool = False,\n        append_version: bool = False,\n    ) -> None:\n        super().__init__(prefix, name=name)\n        try:\n            directory = Path(directory).expanduser().resolve(strict=True)\n        except FileNotFoundError as error:\n            raise ValueError(f\"'{directory}' does not exist\") from error\n        if not directory.is_dir():\n            raise ValueError(f\"'{directory}' is not a directory\")\n        self._directory = directory\n        self._show_index = show_index\n        self._chunk_size = chunk_size\n        self._break_symlink_sandbox = break_symlink_sandbox\n        self._expect_handler = expect_handler\n        self._append_version = append_version\n\n        self._routes = {\n            \"GET\": ResourceRoute(\n                \"GET\", self._handle, self, expect_handler=expect_handler\n            ),\n            \"HEAD\": ResourceRoute(\n                \"HEAD\", self._handle, self, expect_handler=expect_handler\n            ),\n        }\n        self._allowed_methods = set(self._routes)\n\n    def url_for(  # type: ignore[override]","sourceCodeStart":503,"sourceCodeEnd":539,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L503-L539","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\napp.router.add_static('/static', '/app/assets/logo.png')\n// after\napp.router.add_static('/static', '/app/assets')  # the directory","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef assert_is_directory(p):\n    p = Path(p)\n    if not p.is_dir():\n        raise ValueError(f'{p} exists but is not a directory')\n    return p","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["static-files","configuration","filesystem"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}