From 35c265f77ad8fa6da429544ca659dcdc266190c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Thu, 16 Dec 2021 18:35:03 +0100 Subject: [PATCH 1/4] make possible to set compressor class from settings (or command line argument) --- docs/django.rst | 7 +++++++ src/whitenoise/compress.py | 31 ++++++++++++++++++++++++++++--- src/whitenoise/storage.py | 6 +++--- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/docs/django.rst b/docs/django.rst index 3b6e6421..1fb6a442 100644 --- a/docs/django.rst +++ b/docs/django.rst @@ -389,6 +389,13 @@ arguments upper-cased with a 'WHITENOISE\_' prefix. just skip over them. +.. attribute:: WHITENOISE_COMPRESSOR_CLASS + + :default: ``'whitenoise.compress.Compressor'`` + + String with custom Compressor class dotted path. + + .. attribute:: WHITENOISE_ADD_HEADERS_FUNCTION :default: ``None`` diff --git a/src/whitenoise/compress.py b/src/whitenoise/compress.py index 143e1e44..53353f9d 100644 --- a/src/whitenoise/compress.py +++ b/src/whitenoise/compress.py @@ -6,6 +6,9 @@ import re from io import BytesIO +from django.conf import settings +from django.utils.module_loading import import_string + try: import brotli @@ -134,6 +137,16 @@ def write_data(self, path, data, suffix, stat_result): return filename +def get_compressor_class(): + return import_string( + getattr( + settings, + "WHITENOISE_COMPRESSOR_CLASS", + "whitenoise.compress.Compressor", + ), + ) + + def main(argv=None): parser = argparse.ArgumentParser( description="Search for all files inside *not* matching " @@ -150,6 +163,13 @@ def main(argv=None): action="store_false", dest="use_gzip", ) + parser.add_argument( + "--compressor-class", + help="Path to compressor class", + dest="compressor_class", + type=str, + default=None, + ) parser.add_argument( "--no-brotli", help="Don't produce brotli '.br' files", @@ -157,7 +177,12 @@ def main(argv=None): dest="use_brotli", ) parser.add_argument("root", help="Path root from which to search for files") - default_exclude = ", ".join(Compressor.SKIP_COMPRESS_EXTENSIONS) + compressor_class_str = parser.parse_args(argv).compressor_class + if compressor_class_str is None: + compressor_class = get_compressor_class() + else: + compressor_class = import_string(compressor_class_str) + default_exclude = ", ".join(compressor_class.SKIP_COMPRESS_EXTENSIONS) parser.add_argument( "extensions", nargs="*", @@ -165,11 +190,11 @@ def main(argv=None): "File extensions to exclude from compression " + f"(default: {default_exclude})" ), - default=Compressor.SKIP_COMPRESS_EXTENSIONS, + default=compressor_class.SKIP_COMPRESS_EXTENSIONS, ) args = parser.parse_args(argv) - compressor = Compressor( + compressor = compressor_class( extensions=args.extensions, use_gzip=args.use_gzip, use_brotli=args.use_brotli, diff --git a/src/whitenoise/storage.py b/src/whitenoise/storage.py index b7d357b6..5d1bb988 100644 --- a/src/whitenoise/storage.py +++ b/src/whitenoise/storage.py @@ -13,7 +13,7 @@ from django.contrib.staticfiles.storage import ManifestStaticFilesStorage from django.contrib.staticfiles.storage import StaticFilesStorage -from whitenoise.compress import Compressor +from .compress import get_compressor_class, Compressor _PostProcessT = Iterator[Union[Tuple[str, str, bool], Tuple[str, None, RuntimeError]]] @@ -41,7 +41,7 @@ def post_process( yield path, compressed_name, True def create_compressor(self, **kwargs: Any) -> Compressor: - return Compressor(**kwargs) + return get_compressor_class()(**kwargs) class MissingFileError(ValueError): @@ -126,7 +126,7 @@ def delete_files(self, files_to_delete): raise def create_compressor(self, **kwargs): - return Compressor(**kwargs) + return get_compressor_class()(**kwargs) def compress_files(self, names): extensions = getattr(settings, "WHITENOISE_SKIP_COMPRESS_EXTENSIONS", None) From 4e1904eddaae5a5b1a849fffc48a2e55f6fa1de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Sun, 28 Jul 2024 11:35:47 +0200 Subject: [PATCH 2/4] add tests for the compress --compressor-class option --- tests/custom_compressor.py | 10 +++++++ tests/test_compress.py | 55 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 tests/custom_compressor.py diff --git a/tests/custom_compressor.py b/tests/custom_compressor.py new file mode 100644 index 00000000..b25ad87d --- /dev/null +++ b/tests/custom_compressor.py @@ -0,0 +1,10 @@ +from whitenoise.compress import Compressor + +called = False + +class CustomCompressor(Compressor): + def compress(self, path): + global called + print("CALLLED") + called = True + return super().compress(path) diff --git a/tests/test_compress.py b/tests/test_compress.py index 11ea9471..1f7c2b33 100644 --- a/tests/test_compress.py +++ b/tests/test_compress.py @@ -9,9 +9,12 @@ import pytest +from django.test import override_settings from whitenoise.compress import Compressor from whitenoise.compress import main as compress_main +from . import custom_compressor + COMPRESSABLE_FILE = "application.css" TOO_SMALL_FILE = "too-small.css" WRONG_EXTENSION = "image.jpg" @@ -84,3 +87,55 @@ def test_compressed_effectively_no_orig_size(): assert not compressor.is_compressed_effectively( "test_encoding", "test_path", 0, "test_data" ) + + +def test_default_compressor(files_dir): + # Run the compression command with default compressor + custom_compressor.called = False + + compress_main([files_dir]) + + for path in TEST_FILES.keys(): + full_path = os.path.join(files_dir, path) + if path.endswith(".jpg"): + assert not os.path.exists(full_path + ".gz") + else: + if TOO_SMALL_FILE not in full_path: + assert os.path.exists(full_path + ".gz") + + assert custom_compressor.called is False + + +def test_custom_compressor(files_dir): + custom_compressor.called = False + + # Run the compression command with the custom compressor + compress_main([files_dir, "--compressor-class=tests.custom_compressor.CustomCompressor"]) + + assert custom_compressor.called is True + + for path in TEST_FILES.keys(): + full_path = os.path.join(files_dir, path) + if path.endswith(".jpg"): + assert not os.path.exists(full_path + ".gz") + else: + if TOO_SMALL_FILE not in full_path: + assert os.path.exists(full_path + ".gz") + + +@override_settings(WHITENOISE_COMPRESSOR_CLASS='tests.custom_compressor.CustomCompressor') +def test_custom_compressor_settings(files_dir): + """ Test if the custom compressor can be set via WHITENOISE_COMPRESSOR_CLASS settings """ + custom_compressor.called = False + + compress_main([files_dir]) + + assert custom_compressor.called is True + + for path in TEST_FILES.keys(): + full_path = os.path.join(files_dir, path) + if path.endswith(".jpg"): + assert not os.path.exists(full_path + ".gz") + else: + if TOO_SMALL_FILE not in full_path: + assert os.path.exists(full_path + ".gz") From 7aa187c89a5c3655292b15d1cb17c5c2f29c1811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Sun, 28 Jul 2024 12:06:57 +0200 Subject: [PATCH 3/4] --compressor-class: add more docs --- docs/base.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/base.rst b/docs/base.rst index b0b272ae..7b65b1b8 100644 --- a/docs/base.rst +++ b/docs/base.rst @@ -69,7 +69,7 @@ Usage is simple: .. code-block:: console $ python -m whitenoise.compress --help - usage: compress.py [-h] [-q] [--no-gzip] [--no-brotli] + usage: compress.py [-h] [-q] [--no-gzip] [--compressor-class COMPRESSOR_CLASS] [--no-brotli] root [extensions [extensions ...]] Search for all files inside *not* matching and produce @@ -87,6 +87,8 @@ Usage is simple: -q, --quiet Don't produce log output --no-gzip Don't produce gzip '.gz' files --no-brotli Don't produce brotli '.br' files + --compressor-class COMPRESSOR_CLASS + Custom compressor class to use (default: whitenoise.compress.Compressor) You can either run this during development and commit your compressed files to your repository, or you can run this as part of your build and deploy processes. From 31ea199039b9491a4ead6aff85ef20f4d7bb9c09 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 28 Jul 2024 10:08:14 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/whitenoise/storage.py | 3 ++- tests/custom_compressor.py | 3 +++ tests/test_compress.py | 12 ++++++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/whitenoise/storage.py b/src/whitenoise/storage.py index 5d1bb988..d6645959 100644 --- a/src/whitenoise/storage.py +++ b/src/whitenoise/storage.py @@ -13,7 +13,8 @@ from django.contrib.staticfiles.storage import ManifestStaticFilesStorage from django.contrib.staticfiles.storage import StaticFilesStorage -from .compress import get_compressor_class, Compressor +from .compress import Compressor +from .compress import get_compressor_class _PostProcessT = Iterator[Union[Tuple[str, str, bool], Tuple[str, None, RuntimeError]]] diff --git a/tests/custom_compressor.py b/tests/custom_compressor.py index b25ad87d..22fc6e69 100644 --- a/tests/custom_compressor.py +++ b/tests/custom_compressor.py @@ -1,7 +1,10 @@ +from __future__ import annotations + from whitenoise.compress import Compressor called = False + class CustomCompressor(Compressor): def compress(self, path): global called diff --git a/tests/test_compress.py b/tests/test_compress.py index 1f7c2b33..b383df97 100644 --- a/tests/test_compress.py +++ b/tests/test_compress.py @@ -8,8 +8,8 @@ import tempfile import pytest - from django.test import override_settings + from whitenoise.compress import Compressor from whitenoise.compress import main as compress_main @@ -110,7 +110,9 @@ def test_custom_compressor(files_dir): custom_compressor.called = False # Run the compression command with the custom compressor - compress_main([files_dir, "--compressor-class=tests.custom_compressor.CustomCompressor"]) + compress_main( + [files_dir, "--compressor-class=tests.custom_compressor.CustomCompressor"] + ) assert custom_compressor.called is True @@ -123,9 +125,11 @@ def test_custom_compressor(files_dir): assert os.path.exists(full_path + ".gz") -@override_settings(WHITENOISE_COMPRESSOR_CLASS='tests.custom_compressor.CustomCompressor') +@override_settings( + WHITENOISE_COMPRESSOR_CLASS="tests.custom_compressor.CustomCompressor" +) def test_custom_compressor_settings(files_dir): - """ Test if the custom compressor can be set via WHITENOISE_COMPRESSOR_CLASS settings """ + """Test if the custom compressor can be set via WHITENOISE_COMPRESSOR_CLASS settings""" custom_compressor.called = False compress_main([files_dir])