diff --git a/docs/src/django-settings.md b/docs/src/django-settings.md index 8efbf6ce..b9344512 100644 --- a/docs/src/django-settings.md +++ b/docs/src/django-settings.md @@ -138,8 +138,9 @@ def force_download_pdfs(headers, path, url): url: The host-relative URL of the file e.g. `/static/styles/app.css` """ - if path.endswith('.pdf'): - headers['Content-Disposition'] = 'attachment' + if path.endswith(".pdf"): + headers["Content-Disposition"] = "attachment" + SERVESTATIC_ADD_HEADERS_FUNCTION = force_download_pdfs ``` @@ -167,7 +168,8 @@ def immutable_file_test(path, url): """ # Match filename with 12 hex digits before the extension # e.g. app.db8f2edc0c8a.js - return re.match(r'^.+\.[0-9a-f]{12}\..+$', url) + return re.match(r"^.+\.[0-9a-f]{12}\..+$", url) + SERVESTATIC_IMMUTABLE_FILE_TEST = immutable_file_test ``` diff --git a/docs/src/servestatic.md b/docs/src/servestatic.md index 48589780..0632a913 100644 --- a/docs/src/servestatic.md +++ b/docs/src/servestatic.md @@ -58,7 +58,7 @@ If you want to something other than `index.html` as the index file, then you can A dictionary mapping file extensions (lowercase) to the mimetype for that extension. For example: ```python linenums="0" -{'.foo': 'application/x-foo'} +{".foo": "application/x-foo"} ``` Note that ServeStatic ships with its own default set of mimetypes and does not use the system-supplied ones (e.g. `/etc/mime.types`). This ensures that it behaves consistently regardless of the environment in which it's run. View the defaults in the `media_types.py` file. @@ -114,13 +114,14 @@ def force_download_pdfs(headers, path, url): None. Changes should be made by modifying the headers \ dictionary directly. """ - if path.endswith('.pdf'): - headers['Content-Disposition'] = 'attachment' + if path.endswith(".pdf"): + headers["Content-Disposition"] = "attachment" + application = ServeStatic( application, add_headers_function=force_download_pdfs, - ) +) ``` --- @@ -151,7 +152,7 @@ def immutable_file_test(path, url): """ # Match filename with 12 hex digits before the extension # e.g. app.db8f2edc0c8a.js - return re.match(r'^.+\.[0-9a-f]{12}\..+$', url) + return re.match(r"^.+\.[0-9a-f]{12}\..+$", url) ``` ## Compression Support diff --git a/src/servestatic/base.py b/src/servestatic/base.py index ab829f5f..db03cbf2 100644 --- a/src/servestatic/base.py +++ b/src/servestatic/base.py @@ -9,12 +9,7 @@ from wsgiref.headers import Headers from servestatic.media_types import MediaTypes -from servestatic.responders import ( - IsDirectoryError, - MissingFileError, - Redirect, - StaticFile, -) +from servestatic.responders import IsDirectoryError, MissingFileError, Redirect, StaticFile from servestatic.utils import ensure_leading_trailing_slash, scantree diff --git a/src/servestatic/middleware.py b/src/servestatic/middleware.py index ca45990b..81ab9307 100644 --- a/src/servestatic/middleware.py +++ b/src/servestatic/middleware.py @@ -11,26 +11,12 @@ from asgiref.sync import iscoroutinefunction, markcoroutinefunction from django.conf import settings as django_settings from django.contrib.staticfiles import finders -from django.contrib.staticfiles.storage import ( - ManifestStaticFilesStorage, - staticfiles_storage, -) +from django.contrib.staticfiles.storage import ManifestStaticFilesStorage, staticfiles_storage from django.http import FileResponse, HttpRequest -from servestatic.responders import ( - AsyncSlicedFile, - MissingFileError, - SlicedFile, - StaticFile, -) -from servestatic.utils import ( - AsyncFile, - AsyncFileIterator, - AsyncToSyncIterator, - EmptyAsyncIterator, - ensure_leading_trailing_slash, - stat_files, -) +from servestatic.responders import AsyncSlicedFile, MissingFileError, SlicedFile, StaticFile +from servestatic.utils import (AsyncFile, AsyncFileIterator, AsyncToSyncIterator, + EmptyAsyncIterator, ensure_leading_trailing_slash, stat_files) from servestatic.wsgi import ServeStaticBase __all__ = ["ServeStaticMiddleware"] diff --git a/src/servestatic/storage.py b/src/servestatic/storage.py index adef7ef5..a49c713a 100644 --- a/src/servestatic/storage.py +++ b/src/servestatic/storage.py @@ -7,19 +7,17 @@ import os import re import textwrap -from typing import Any, Iterator, Tuple, Union +from collections.abc import Iterator +from typing import Any, Tuple, Union from django.conf import settings -from django.contrib.staticfiles.storage import ( - ManifestStaticFilesStorage, - StaticFilesStorage, -) +from django.contrib.staticfiles.storage import ManifestStaticFilesStorage, StaticFilesStorage from django.core.files.base import ContentFile from servestatic.compress import Compressor from servestatic.utils import stat_files -_PostProcessT = Iterator[Union[Tuple[str, str, bool], Tuple[str, None, RuntimeError]]] +_PostProcessT = Iterator[Union[tuple[str, str, bool], tuple[str, None, RuntimeError]]] class CompressedStaticFilesStorage(StaticFilesStorage): diff --git a/src/servestatic/utils.py b/src/servestatic/utils.py index 857b9c85..5f056175 100644 --- a/src/servestatic/utils.py +++ b/src/servestatic/utils.py @@ -6,9 +6,10 @@ import functools import os import threading +from collections.abc import AsyncIterable from concurrent.futures import ThreadPoolExecutor from io import IOBase -from typing import AsyncIterable, Callable +from typing import Callable # This is the same size as wsgiref.FileWrapper ASGI_BLOCK_SIZE = 8192 @@ -83,10 +84,11 @@ def __iter__(self): def open_lazy(f): """Decorator that ensures the file is open before calling a function. - This can be turned into a @staticmethod on `AsyncFile` once we drop Python 3.9 compatibility.""" + This can be turned into a @staticmethod on `AsyncFile` once we drop Python 3.9 compatibility. + """ @functools.wraps(f) - async def wrapper(self: "AsyncFile", *args, **kwargs): + async def wrapper(self: AsyncFile, *args, **kwargs): if self.closed: raise ValueError("I/O operation on closed file.") if self.file_obj is None: diff --git a/tests/test_django_servestatic.py b/tests/test_django_servestatic.py index 3a19cd65..ff7fcef7 100644 --- a/tests/test_django_servestatic.py +++ b/tests/test_django_servestatic.py @@ -23,14 +23,8 @@ from servestatic.middleware import AsyncServeStaticFileResponse, ServeStaticMiddleware from servestatic.utils import AsyncFile -from .utils import ( - AppServer, - AsgiAppServer, - AsgiReceiveEmulator, - AsgiScopeEmulator, - AsgiSendEmulator, - Files, -) +from .utils import (AppServer, AsgiAppServer, AsgiReceiveEmulator, AsgiScopeEmulator, + AsgiSendEmulator, Files) def reset_lazy_object(obj):