Static Files - StaticFiles

You can use the StaticFiles class to serve static files, like JavaScript, CSS, images, etc.

Read more about it in the FastAPI docs for Static Files.

You can import it directly from fastapi.staticfiles:

  1. from fastapi.staticfiles import StaticFiles

fastapi.staticfiles.StaticFiles

  1. StaticFiles(
  2. *,
  3. directory=None,
  4. packages=None,
  5. html=False,
  6. check_dir=True,
  7. follow_symlink=False
  8. )
PARAMETERDESCRIPTION
directory

TYPE: Optional[PathLike] DEFAULT: None

packages

TYPE: Optional[List[Union[str, Tuple[str, str]]]] DEFAULT: None

html

TYPE: bool DEFAULT: False

check_dir

TYPE: bool DEFAULT: True

follow_symlink

TYPE: bool DEFAULT: False

Source code in starlette/staticfiles.py

  1. 39
  2. 40
  3. 41
  4. 42
  5. 43
  6. 44
  7. 45
  8. 46
  9. 47
  10. 48
  11. 49
  12. 50
  13. 51
  14. 52
  15. 53
  16. 54
  17. 55
  18. 56
  19. 57
  1. def init(
  2. self,
  3. *,
  4. directory: typing.Optional[PathLike] = None,
  5. packages: typing.Optional[
  6. typing.List[typing.Union[str, typing.Tuple[str, str]]]
  7. ] = None,
  8. html: bool = False,
  9. check_dir: bool = True,
  10. follow_symlink: bool = False,
  11. ) -> None:
  12. self.directory = directory
  13. self.packages = packages
  14. self.all_directories = self.get_directories(directory, packages)
  15. self.html = html
  16. self.config_checked = False
  17. self.follow_symlink = follow_symlink
  18. if check_dir and directory is not None and not os.path.isdir(directory):
  19. raise RuntimeError(fDirectory {directory} does not exist)

directory instance-attribute

  1. directory = directory

packages instance-attribute

  1. packages = packages

all_directories instance-attribute

  1. all_directories = get_directories(directory, packages)

html instance-attribute

  1. html = html

config_checked instance-attribute

  1. config_checked = False
  1. follow_symlink = follow_symlink

get_directories

  1. get_directories(directory=None, packages=None)

Given directory and packages arguments, return a list of all the directories that should be used for serving static files from.

PARAMETERDESCRIPTION
directory

TYPE: Optional[PathLike] DEFAULT: None

packages

TYPE: Optional[List[Union[str, Tuple[str, str]]]] DEFAULT: None

Source code in starlette/staticfiles.py

  1. 59
  2. 60
  3. 61
  4. 62
  5. 63
  6. 64
  7. 65
  8. 66
  9. 67
  10. 68
  11. 69
  12. 70
  13. 71
  14. 72
  15. 73
  16. 74
  17. 75
  18. 76
  19. 77
  20. 78
  21. 79
  22. 80
  23. 81
  24. 82
  25. 83
  26. 84
  27. 85
  28. 86
  29. 87
  30. 88
  31. 89
  32. 90
  1. def get_directories(
  2. self,
  3. directory: typing.Optional[PathLike] = None,
  4. packages: typing.Optional[
  5. typing.List[typing.Union[str, typing.Tuple[str, str]]]
  6. ] = None,
  7. ) -> typing.List[PathLike]:
  8. “””
  9. Given directory and packages arguments, return a list of all the
  10. directories that should be used for serving static files from.
  11. “””
  12. directories = []
  13. if directory is not None:
  14. directories.append(directory)
  15. for package in packages or []:
  16. if isinstance(package, tuple):
  17. package, statics_dir = package
  18. else:
  19. statics_dir = statics
  20. spec = importlib.util.find_spec(package)
  21. assert spec is not None, fPackage {package!r} could not be found.”
  22. assert spec.origin is not None, fPackage {package!r} could not be found.”
  23. package_directory = os.path.normpath(
  24. os.path.join(spec.origin, “..”, statics_dir)
  25. )
  26. assert os.path.isdir(
  27. package_directory
  28. ), fDirectory {statics_dir!r} in package {package!r} could not be found.”
  29. directories.append(package_directory)
  30. return directories

get_path

  1. get_path(scope)

Given the ASGI scope, return the path string to serve up, with OS specific path separators, and any ‘..’, ‘.’ components removed.

PARAMETERDESCRIPTION
scope

TYPE: Scope

Source code in starlette/staticfiles.py

  1. 106
  2. 107
  3. 108
  4. 109
  5. 110
  6. 111
  1. def get_path(self, scope: Scope) -> str:
  2. “””
  3. Given the ASGI scope, return the path string to serve up,
  4. with OS specific path separators, and any ‘..’, ‘.’ components removed.
  5. “””
  6. return os.path.normpath(os.path.join(*scope[path].split(“/“)))

get_response async

  1. get_response(path, scope)

Returns an HTTP response, given the incoming path, method and request headers.

PARAMETERDESCRIPTION
path

TYPE: str

scope

TYPE: Scope

Source code in starlette/staticfiles.py

  1. 113
  2. 114
  3. 115
  4. 116
  5. 117
  6. 118
  7. 119
  8. 120
  9. 121
  10. 122
  11. 123
  12. 124
  13. 125
  14. 126
  15. 127
  16. 128
  17. 129
  18. 130
  19. 131
  20. 132
  21. 133
  22. 134
  23. 135
  24. 136
  25. 137
  26. 138
  27. 139
  28. 140
  29. 141
  30. 142
  31. 143
  32. 144
  33. 145
  34. 146
  35. 147
  36. 148
  37. 149
  38. 150
  39. 151
  40. 152
  41. 153
  42. 154
  43. 155
  44. 156
  45. 157
  46. 158
  47. 159
  48. 160
  1. async def get_response(self, path: str, scope: Scope) -> Response:
  2. “””
  3. Returns an HTTP response, given the incoming path, method and request headers.
  4. “””
  5. if scope[method] not in (GET, HEAD):
  6. raise HTTPException(status_code=405)
  7. try:
  8. full_path, stat_result = await anyio.to_thread.run_sync(
  9. self.lookup_path, path
  10. )
  11. except PermissionError:
  12. raise HTTPException(status_code=401)
  13. except OSError:
  14. raise
  15. if stat_result and stat.S_ISREG(stat_result.st_mode):
  16. # We have a static file to serve.
  17. return self.file_response(full_path, stat_result, scope)
  18. elif stat_result and stat.S_ISDIR(stat_result.st_mode) and self.html:
  19. # We’re in HTML mode, and have got a directory URL.
  20. # Check if we have ‘index.html’ file to serve.
  21. index_path = os.path.join(path, index.html)
  22. full_path, stat_result = await anyio.to_thread.run_sync(
  23. self.lookup_path, index_path
  24. )
  25. if stat_result is not None and stat.S_ISREG(stat_result.st_mode):
  26. if not scope[path].endswith(“/“):
  27. # Directory URLs should redirect to always end in “/“.
  28. url = URL(scope=scope)
  29. url = url.replace(path=url.path + “/“)
  30. return RedirectResponse(url=url)
  31. return self.file_response(full_path, stat_result, scope)
  32. if self.html:
  33. # Check for ‘404.html’ if we’re in HTML mode.
  34. full_path, stat_result = await anyio.to_thread.run_sync(
  35. self.lookup_path, 404.html
  36. )
  37. if stat_result and stat.S_ISREG(stat_result.st_mode):
  38. return FileResponse(
  39. full_path,
  40. stat_result=stat_result,
  41. method=scope[method],
  42. status_code=404,
  43. )
  44. raise HTTPException(status_code=404)

lookup_path

  1. lookup_path(path)
PARAMETERDESCRIPTION
path

TYPE: str

Source code in starlette/staticfiles.py

  1. 162
  2. 163
  3. 164
  4. 165
  5. 166
  6. 167
  7. 168
  8. 169
  9. 170
  10. 171
  11. 172
  12. 173
  13. 174
  14. 175
  15. 176
  16. 177
  17. 178
  18. 179
  19. 180
  1. def lookup_path(
  2. self, path: str
  3. ) -> typing.Tuple[str, typing.Optional[os.stat_result]]:
  4. for directory in self.all_directories:
  5. joined_path = os.path.join(directory, path)
  6. if self.follow_symlink:
  7. full_path = os.path.abspath(joined_path)
  8. else:
  9. full_path = os.path.realpath(joined_path)
  10. directory = os.path.realpath(directory)
  11. if os.path.commonpath([full_path, directory]) != directory:
  12. # Don’t allow misbehaving clients to break out of the static files
  13. # directory.
  14. continue
  15. try:
  16. return full_path, os.stat(full_path)
  17. except (FileNotFoundError, NotADirectoryError):
  18. continue
  19. return “”, None

file_response

  1. file_response(
  2. full_path, stat_result, scope, status_code=200
  3. )
PARAMETERDESCRIPTION
full_path

TYPE: PathLike

stat_result

TYPE: stat_result

scope

TYPE: Scope

status_code

TYPE: int DEFAULT: 200

Source code in starlette/staticfiles.py

  1. 182
  2. 183
  3. 184
  4. 185
  5. 186
  6. 187
  7. 188
  8. 189
  9. 190
  10. 191
  11. 192
  12. 193
  13. 194
  14. 195
  15. 196
  16. 197
  1. def file_response(
  2. self,
  3. full_path: PathLike,
  4. stat_result: os.stat_result,
  5. scope: Scope,
  6. status_code: int = 200,
  7. ) -> Response:
  8. method = scope[method]
  9. request_headers = Headers(scope=scope)
  10. response = FileResponse(
  11. full_path, status_code=status_code, stat_result=stat_result, method=method
  12. )
  13. if self.is_not_modified(response.headers, request_headers):
  14. return NotModifiedResponse(response.headers)
  15. return response

check_config async

  1. check_config()

Perform a one-off configuration check that StaticFiles is actually pointed at a directory, so that we can raise loud errors rather than just returning 404 responses.

Source code in starlette/staticfiles.py

  1. 199
  2. 200
  3. 201
  4. 202
  5. 203
  6. 204
  7. 205
  8. 206
  9. 207
  10. 208
  11. 209
  12. 210
  13. 211
  14. 212
  15. 213
  16. 214
  17. 215
  18. 216
  19. 217
  1. async def check_config(self) -> None:
  2. “””
  3. Perform a one-off configuration check that StaticFiles is actually
  4. pointed at a directory, so that we can raise loud errors rather than
  5. just returning 404 responses.
  6. “””
  7. if self.directory is None:
  8. return
  9. try:
  10. stat_result = await anyio.to_thread.run_sync(os.stat, self.directory)
  11. except FileNotFoundError:
  12. raise RuntimeError(
  13. fStaticFiles directory {self.directory} does not exist.”
  14. )
  15. if not (stat.S_ISDIR(stat_result.st_mode) or stat.S_ISLNK(stat_result.st_mode)):
  16. raise RuntimeError(
  17. fStaticFiles path {self.directory} is not a directory.”
  18. )

is_not_modified

  1. is_not_modified(response_headers, request_headers)

Given the request and response headers, return True if an HTTP “Not Modified” response could be returned instead.

PARAMETERDESCRIPTION
response_headers

TYPE: Headers

request_headers

TYPE: Headers

Source code in starlette/staticfiles.py

  1. 219
  2. 220
  3. 221
  4. 222
  5. 223
  6. 224
  7. 225
  8. 226
  9. 227
  10. 228
  11. 229
  12. 230
  13. 231
  14. 232
  15. 233
  16. 234
  17. 235
  18. 236
  19. 237
  20. 238
  21. 239
  22. 240
  23. 241
  24. 242
  25. 243
  26. 244
  27. 245
  28. 246
  1. def is_not_modified(
  2. self, response_headers: Headers, request_headers: Headers
  3. ) -> bool:
  4. “””
  5. Given the request and response headers, return True if an HTTP
  6. Not Modified response could be returned instead.
  7. “””
  8. try:
  9. if_none_match = request_headers[if-none-match]
  10. etag = response_headers[etag]
  11. if if_none_match == etag:
  12. return True
  13. except KeyError:
  14. pass
  15. try:
  16. if_modified_since = parsedate(request_headers[if-modified-since])
  17. last_modified = parsedate(response_headers[last-modified])
  18. if (
  19. if_modified_since is not None
  20. and last_modified is not None
  21. and if_modified_since >= last_modified
  22. ):
  23. return True
  24. except KeyError:
  25. pass
  26. return False