From 7208fafe3071f0e2319f49379ad0bbc3c40992bc Mon Sep 17 00:00:00 2001 From: Anton Agestam Date: Sun, 6 Oct 2019 11:35:56 +0200 Subject: [PATCH] fix 151: correctly guess strategy for storage subclasses --- collectfast/__init__.py | 2 +- .../management/commands/collectstatic.py | 6 --- collectfast/strategies/base.py | 43 ++++++++++++++++--- collectfast/tests/boto3_subclass.py | 5 +++ collectfast/tests/boto_subclass.py | 5 +++ .../tests/strategies/test_guess_strategy.py | 37 ++++++++++++++++ 6 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 collectfast/tests/boto3_subclass.py create mode 100644 collectfast/tests/boto_subclass.py create mode 100644 collectfast/tests/strategies/test_guess_strategy.py diff --git a/collectfast/__init__.py b/collectfast/__init__.py index 6849410..a82b376 100644 --- a/collectfast/__init__.py +++ b/collectfast/__init__.py @@ -1 +1 @@ -__version__ = "1.1.0" +__version__ = "1.1.1" diff --git a/collectfast/management/commands/collectstatic.py b/collectfast/management/commands/collectstatic.py index a5215ae..cf0f57d 100644 --- a/collectfast/management/commands/collectstatic.py +++ b/collectfast/management/commands/collectstatic.py @@ -1,4 +1,3 @@ -import warnings from multiprocessing.dummy import Pool from typing import Any from typing import Dict @@ -39,11 +38,6 @@ def _load_strategy(): storage_str = getattr(django_settings, "STATICFILES_STORAGE", None) if storage_str is not None: - warnings.warn( - "Falling back to guessing strategy for backwards compatibility. This " - "is deprecated and will be removed in a future release. Explicitly " - "set COLLECTFAST_STRATEGY to silence this warning." - ) return load_strategy(guess_strategy(storage_str)) raise ImproperlyConfigured( diff --git a/collectfast/strategies/base.py b/collectfast/strategies/base.py index 8d9a958..833de96 100644 --- a/collectfast/strategies/base.py +++ b/collectfast/strategies/base.py @@ -3,6 +3,7 @@ import hashlib import logging import mimetypes +import warnings from functools import lru_cache from io import BytesIO from pydoc import locate @@ -16,6 +17,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.files.storage import Storage from django.utils.encoding import force_bytes +from typing_extensions import Final from collectfast import settings @@ -141,9 +143,38 @@ def load_strategy(klass: Union[str, type, object]) -> Type[Strategy]: return klass -def guess_strategy(storage: str) -> Optional[str]: - if storage == "storages.backends.s3boto.S3BotoStorage": - return "collectfast.strategies.boto.BotoStrategy" - if storage == "storages.backends.s3boto3.S3Boto3Storage": - return "collectfast.strategies.boto3.Boto3Strategy" - return None +_BOTO_STORAGE = "storages.backends.s3boto.S3BotoStorage" # type: Final +_BOTO_STRATEGY = "collectfast.strategies.boto.BotoStrategy" # type: Final +_BOTO3_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" # type: Final +_BOTO3_STRATEGY = "collectfast.strategies.boto3.Boto3Strategy" # type: Final + + +def _resolves_to_subclass(subclass_ref: str, superclass_ref: str) -> bool: + subclass = locate(subclass_ref) + assert isinstance(subclass, type) + try: + superclass = locate(superclass_ref) + assert isinstance(superclass, type) + except (ImportError, AssertionError) as e: + logger.debug("Failed to import %s: %s" % (superclass_ref, e)) + return False + return issubclass(subclass, superclass) + + +def guess_strategy(storage: str) -> str: + warnings.warn( + "Falling back to guessing strategy for backwards compatibility. This " + "is deprecated and will be removed in a future release. Explicitly " + "set COLLECTFAST_STRATEGY to silence this warning." + ) + if storage == _BOTO_STORAGE: + return _BOTO_STRATEGY + if storage == _BOTO3_STORAGE: + return _BOTO3_STRATEGY + if _resolves_to_subclass(storage, _BOTO_STORAGE): + return _BOTO_STRATEGY + if _resolves_to_subclass(storage, _BOTO3_STORAGE): + return _BOTO3_STRATEGY + raise ImproperlyConfigured( + "No strategy configured, please make sure COLLECTFAST_STRATEGY is set." + ) diff --git a/collectfast/tests/boto3_subclass.py b/collectfast/tests/boto3_subclass.py new file mode 100644 index 0000000..22154b8 --- /dev/null +++ b/collectfast/tests/boto3_subclass.py @@ -0,0 +1,5 @@ +from storages.backends.s3boto3 import S3Boto3Storage + + +class CustomStorage(S3Boto3Storage): + pass diff --git a/collectfast/tests/boto_subclass.py b/collectfast/tests/boto_subclass.py new file mode 100644 index 0000000..4917bc6 --- /dev/null +++ b/collectfast/tests/boto_subclass.py @@ -0,0 +1,5 @@ +from storages.backends.s3boto import S3BotoStorage + + +class CustomStorage(S3BotoStorage): + pass diff --git a/collectfast/tests/strategies/test_guess_strategy.py b/collectfast/tests/strategies/test_guess_strategy.py new file mode 100644 index 0000000..c640f78 --- /dev/null +++ b/collectfast/tests/strategies/test_guess_strategy.py @@ -0,0 +1,37 @@ +from unittest import TestCase + +from collectfast.strategies.base import _BOTO3_STORAGE +from collectfast.strategies.base import _BOTO3_STRATEGY +from collectfast.strategies.base import _BOTO_STORAGE +from collectfast.strategies.base import _BOTO_STRATEGY +from collectfast.strategies.base import guess_strategy +from collectfast.tests.utils import test + + +@test +def test_guesses_boto_from_exact(case): + # type: (TestCase) -> None + case.assertEqual(guess_strategy(_BOTO_STORAGE), _BOTO_STRATEGY) + + +@test +def test_guesses_boto3_from_exact(case): + # type: (TestCase) -> None + case.assertEqual(guess_strategy(_BOTO3_STORAGE), _BOTO3_STRATEGY) + + +@test +def test_guesses_boto_from_subclass(case): + # type: (TestCase) -> None + case.assertEqual( + guess_strategy("collectfast.tests.boto_subclass.CustomStorage"), _BOTO_STRATEGY + ) + + +@test +def test_guesses_boto3_from_subclass(case): + # type: (TestCase) -> None + case.assertEqual( + guess_strategy("collectfast.tests.boto3_subclass.CustomStorage"), + _BOTO3_STRATEGY, + )