diff --git a/.travis.yml b/.travis.yml index 838398d..fc1fdf8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,16 +9,23 @@ python: - '3.7' install: - pip install django=="$DJANGO" + - pip install -r test-requirements.txt + # these are only supported on Python 3.7, so we allow the installation to fail + - pip install black sorti || true env: - DJANGO=1.11 - DJANGO=2.1 - DJANGO=2.2 -script: +before_script: # see https://github.com/travis-ci/travis-ci/issues/7940 - sudo rm -f /etc/boto.cfg - - pip install -r test-requirements.txt - - flake8 +script: + - '[[ $(python --version) == "Python 3.7."* ]] && flake8 || true' + - '[[ $(python --version) == "Python 3.7."* ]] && black --check . || true' + - '[[ $(python --version) == "Python 3.7."* ]] && sorti --check . || true' + - '[[ $(python --version) == "Python 3.7."* ]] && mypy . || true' - coverage run --source collectfast ./runtests.py +after_script: - coveralls matrix: exclude: diff --git a/README.rst b/README.rst index 079299b..f79922a 100644 --- a/README.rst +++ b/README.rst @@ -92,7 +92,7 @@ Django's cache framework. `_ **Note:** We recommend you to set the ``MAX_ENTRIES`` setting if you -have more than 300 static files, see +have more than 300 static files, see `#47 `_ @@ -150,6 +150,7 @@ Run linter and test suite: .. code:: bash flake8 + black --check . make test diff --git a/collectfast/boto.py b/collectfast/boto.py index c41294c..47cd27c 100644 --- a/collectfast/boto.py +++ b/collectfast/boto.py @@ -1,24 +1,35 @@ +from typing import Any + from . import settings +has_boto = True # type: bool +has_boto3 = True # type: bool + try: from storages.backends.s3boto import S3BotoStorage - from storages.backends.s3boto3 import S3Boto3Storage - has_boto = True -except: +except ImportError: has_boto = False +try: + from storages.backends.s3boto3 import S3Boto3Storage +except ImportError: + has_boto3 = False + def is_boto3(storage): - return has_boto and isinstance(storage, S3Boto3Storage) + # type: (Any) -> bool + return has_boto3 and isinstance(storage, S3Boto3Storage) def is_boto(storage): + # type: (Any) -> bool return has_boto and ( - isinstance(storage, S3BotoStorage) or - isinstance(storage, S3Boto3Storage)) + isinstance(storage, S3BotoStorage) or isinstance(storage, S3Boto3Storage) + ) def reset_connection(storage): + # type: (Any) -> None """ Reset connection if thread pooling is enabled and storage is boto3. """ diff --git a/collectfast/etag.py b/collectfast/etag.py index 321220d..80651e0 100644 --- a/collectfast/etag.py +++ b/collectfast/etag.py @@ -2,59 +2,53 @@ import hashlib import logging import mimetypes +from functools import lru_cache +from io import BytesIO +from typing import Optional from django.core.cache import caches +from django.core.files.storage import Storage from django.utils.encoding import force_bytes -from django.utils.six import BytesIO - from storages.utils import safe_join from collectfast import settings -try: - from functools import lru_cache -except ImportError: - # make lru_cache do nothing in python 2.7 - def lru_cache(maxsize=128, typed=False): - def decorator(func): - return func - return decorator - cache = caches[settings.cache] logger = logging.getLogger(__name__) @lru_cache() def get_cache_key(path): + # type: (str) -> str """ Create a cache key by concatenating the prefix with a hash of the path. """ - # Python 2/3 support for path hashing - try: - path_hash = hashlib.md5(path).hexdigest() - except TypeError: - path_hash = hashlib.md5(path.encode('utf-8')).hexdigest() + path_hash = hashlib.md5(path.encode()).hexdigest() return settings.cache_key_prefix + path_hash def get_remote_etag(storage, prefixed_path): + # type: (Storage, str) -> Optional[str] """ Get etag of path from S3 using boto or boto3. """ - normalized_path = safe_join(storage.location, prefixed_path).replace( - '\\', '/') + normalized_path = safe_join( + storage.location, # type: ignore + prefixed_path, + ).replace("\\", "/") try: - return storage.bucket.get_key(normalized_path).etag + return storage.bucket.get_key(normalized_path).etag # type: ignore except AttributeError: pass try: - return storage.bucket.Object(normalized_path).e_tag + return storage.bucket.Object(normalized_path).e_tag # type: ignore except: pass return None def get_etag(storage, path, prefixed_path): + # type: (Storage, str, str) -> str """ Get etag of path from cache or S3 - in that order. """ @@ -67,6 +61,7 @@ def get_etag(storage, path, prefixed_path): def destroy_etag(path): + # type: (str) -> None """ Clear etag of path from cache. """ @@ -74,6 +69,7 @@ def destroy_etag(path): def get_file_hash(storage, path): + # type: (Storage, str) -> str """ Create md5 hash from file contents. """ @@ -81,14 +77,13 @@ def get_file_hash(storage, path): file_hash = hashlib.md5(contents).hexdigest() # Check if content should be gzipped and hash gzipped content - content_type = mimetypes.guess_type(path)[0] or 'application/octet-stream' + content_type = mimetypes.guess_type(path)[0] or "application/octet-stream" if settings.is_gzipped and content_type in settings.gzip_content_types: - cache_key = get_cache_key('gzip_hash_%s' % file_hash) + cache_key = get_cache_key("gzip_hash_%s" % file_hash) file_hash = cache.get(cache_key, False) if file_hash is False: buffer = BytesIO() - zf = gzip.GzipFile( - mode='wb', compresslevel=6, fileobj=buffer, mtime=0.0) + zf = gzip.GzipFile(mode="wb", compresslevel=6, fileobj=buffer, mtime=0.0) zf.write(force_bytes(contents)) zf.close() file_hash = hashlib.md5(buffer.getvalue()).hexdigest() @@ -98,6 +93,7 @@ def get_file_hash(storage, path): def has_matching_etag(remote_storage, source_storage, path, prefixed_path): + # type: (Storage, Storage, str, str) -> bool """ Compare etag of path in source storage with remote. """ @@ -107,11 +103,11 @@ def has_matching_etag(remote_storage, source_storage, path, prefixed_path): def should_copy_file(remote_storage, path, prefixed_path, source_storage): + # type: (Storage, str, str, Storage) -> bool """ Returns True if the file should be copied, otherwise False. """ - if has_matching_etag( - remote_storage, source_storage, path, prefixed_path): + if has_matching_etag(remote_storage, source_storage, path, prefixed_path): logger.info("%s: Skipping based on matching file hashes" % path) return False diff --git a/collectfast/management/commands/collectstatic.py b/collectfast/management/commands/collectstatic.py index 846a180..2dd3883 100644 --- a/collectfast/management/commands/collectstatic.py +++ b/collectfast/management/commands/collectstatic.py @@ -1,55 +1,65 @@ -from __future__ import with_statement, unicode_literals -from multiprocessing.dummy import Pool import warnings +from multiprocessing.dummy import Pool +from typing import List +from typing import Tuple from django.contrib.staticfiles.management.commands import collectstatic +from django.core.files.storage import Storage +from django.core.management.base import CommandParser from django.utils.encoding import smart_str -from collectfast.etag import should_copy_file -from collectfast.boto import reset_connection, is_boto from collectfast import settings +from collectfast.boto import is_boto +from collectfast.boto import reset_connection +from collectfast.etag import should_copy_file + +Task = Tuple[str, str, Storage] class Command(collectstatic.Command): def add_arguments(self, parser): + # type: (CommandParser) -> None super(Command, self).add_arguments(parser) parser.add_argument( - '--ignore-etag', - action='store_true', - dest='ignore_etag', + "--ignore-etag", + action="store_true", + dest="ignore_etag", default=False, - help="Deprecated since 0.5.0, use --disable-collectfast instead.") + help="Deprecated since 0.5.0, use --disable-collectfast instead.", + ) parser.add_argument( - '--disable-collectfast', - action='store_true', - dest='disable_collectfast', + "--disable-collectfast", + action="store_true", + dest="disable_collectfast", default=False, - help="Disable Collectfast.") + help="Disable Collectfast.", + ) def __init__(self, *args, **kwargs): super(Command, self).__init__(*args, **kwargs) self.num_copied_files = 0 - self.tasks = [] - self.etags = {} + self.tasks = [] # type: List[Task] self.collectfast_enabled = settings.enabled if is_boto(self.storage) and self.storage.preload_metadata is not True: self.storage.preload_metadata = True warnings.warn( - "Collectfast does not work properly without " - "`preload_metadata` set to `True` on the storage class. " - "Overriding `storage.preload_metadata` and continuing.") + "Collectfast does not work properly without `preload_metadata` " + "set to `True` on the storage class. Overriding " + "`storage.preload_metadata` and continuing." + ) def set_options(self, **options): """ Set options and handle deprecation. """ - ignore_etag = options.pop('ignore_etag', False) - disable = options.pop('disable_collectfast', False) + ignore_etag = options.pop("ignore_etag", False) + disable = options.pop("disable_collectfast", False) if ignore_etag: warnings.warn( "--ignore-etag is deprecated since 0.5.0, use " - "--disable-collectfast instead.") + "--disable-collectfast instead." + ) if ignore_etag or disable: self.collectfast_enabled = False super(Command, self).set_options(**options) @@ -70,8 +80,8 @@ def handle(self, **options): """ super(Command, self).handle(**options) return "{} static file{} copied.".format( - self.num_copied_files, - '' if self.num_copied_files == 1 else 's') + self.num_copied_files, "" if self.num_copied_files == 1 else "s" + ) def do_copy_file(self, args): """ @@ -84,24 +94,28 @@ def do_copy_file(self, args): if self.collectfast_enabled and not self.dry_run: try: if not should_copy_file( - self.storage, path, prefixed_path, source_storage): + self.storage, path, prefixed_path, source_storage + ): return False except Exception as e: if settings.debug: raise # Ignore errors and let default collectstatic handle copy - self.stdout.write(smart_str( - "Ignored error in Collectfast:\n%s\n--> Continuing using " - "default collectstatic." % e)) + self.stdout.write( + smart_str( + "Ignored error in Collectfast:\n%s\n--> Continuing using " + "default collectstatic." % e + ) + ) self.num_copied_files += 1 - return super(Command, self).copy_file( - path, prefixed_path, source_storage) + return super(Command, self).copy_file(path, prefixed_path, source_storage) def copy_file(self, path, prefixed_path, source_storage): + # type: (str, str, Storage) -> None """ - Appends path to task queue if threads are enabled, otherwise copies - the file with a blocking call. + Appends path to task queue if threads are enabled, otherwise copies the + file with a blocking call. """ args = (path, prefixed_path, source_storage) if settings.threads: @@ -110,12 +124,16 @@ def copy_file(self, path, prefixed_path, source_storage): self.do_copy_file(args) def delete_file(self, path, prefixed_path, source_storage): + # type: (str, str, Storage) -> bool """ Override delete_file to skip modified time and exists lookups. """ if not self.collectfast_enabled: - return super(Command, self).delete_file( - path, prefixed_path, source_storage) + # The delete_file method is incorrectly annotated in django-stubs, + # see https://github.com/typeddjango/django-stubs/issues/130 + return super(Command, self).delete_file( # type: ignore + path, prefixed_path, source_storage + ) if not self.dry_run: self.log("Deleting '%s'" % path) self.storage.delete(prefixed_path) diff --git a/collectfast/settings.py b/collectfast/settings.py index b5faadb..64351f5 100644 --- a/collectfast/settings.py +++ b/collectfast/settings.py @@ -1,14 +1,21 @@ from django.conf import settings -debug = getattr( - settings, "COLLECTFAST_DEBUG", getattr(settings, "DEBUG", False)) +debug = getattr(settings, "COLLECTFAST_DEBUG", getattr(settings, "DEBUG", False)) cache_key_prefix = getattr( - settings, "COLLECTFAST_CACHE_KEY_PREFIX", "collectfast05_asset_") + settings, "COLLECTFAST_CACHE_KEY_PREFIX", "collectfast05_asset_" +) cache = getattr(settings, "COLLECTFAST_CACHE", "default") threads = getattr(settings, "COLLECTFAST_THREADS", False) enabled = getattr(settings, "COLLECTFAST_ENABLED", True) is_gzipped = getattr(settings, "AWS_IS_GZIPPED", False) gzip_content_types = getattr( - settings, "GZIP_CONTENT_TYPES", ( - "text/css", "text/javascript", "application/javascript", - "application/x-javascript", "image/svg+xml")) + settings, + "GZIP_CONTENT_TYPES", + ( + "text/css", + "text/javascript", + "application/javascript", + "application/x-javascript", + "image/svg+xml", + ), +) diff --git a/collectfast/tests/no_preload_metadata.py b/collectfast/tests/no_preload_metadata.py index 4ec539d..0228c4b 100644 --- a/collectfast/tests/no_preload_metadata.py +++ b/collectfast/tests/no_preload_metadata.py @@ -4,6 +4,7 @@ class NPM(S3Boto3Storage): """ Dummy class for testing that collectfast warns about overriding the - `preload_metadata` attriute. + `preload_metadata` attribute. """ + preload_metadata = False diff --git a/collectfast/tests/test_command.py b/collectfast/tests/test_command.py index ce95505..fcf8430 100644 --- a/collectfast/tests/test_command.py +++ b/collectfast/tests/test_command.py @@ -1,17 +1,19 @@ import warnings from django.core.management import call_command -from django.utils.six import StringIO from django.test import override_settings as override_django_settings +from django.utils.six import StringIO -from .utils import test, clean_static_dir, create_static_file, override_setting +from .utils import clean_static_dir +from .utils import create_static_file +from .utils import override_setting from .utils import override_storage_attr +from .utils import test def call_collectstatic(*args, **kwargs): out = StringIO() - call_command( - 'collectstatic', *args, interactive=False, stdout=out, **kwargs) + call_command("collectstatic", *args, interactive=False, stdout=out, **kwargs) return out.getvalue() @@ -40,14 +42,15 @@ def test_threads(case): @test @override_django_settings( - STATICFILES_STORAGE="collectfast.tests.no_preload_metadata.NPM") + STATICFILES_STORAGE="collectfast.tests.no_preload_metadata.NPM" +) def test_warn_preload_metadata(case): clean_static_dir() create_static_file() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") call_collectstatic() - case.assertIn('preload_metadata', str(w[0].message)) + case.assertIn("preload_metadata", str(w[0].message)) @test @@ -60,7 +63,8 @@ def test_collectfast_disabled(case): @test @override_django_settings( - STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage") + STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage" +) def test_collectfast_disabled_default_storage(case): clean_static_dir() create_static_file() @@ -83,7 +87,7 @@ def test_ignore_etag_deprecated(case): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") call_collectstatic(ignore_etag=True) - case.assertIn('ignore-etag is deprecated', str(w[0].message)) + case.assertIn("ignore-etag is deprecated", str(w[0].message)) @test diff --git a/collectfast/tests/test_etag.py b/collectfast/tests/test_etag.py index f668031..4c362f0 100644 --- a/collectfast/tests/test_etag.py +++ b/collectfast/tests/test_etag.py @@ -1,25 +1,25 @@ +import platform import string import tempfile -import platform -from mock import patch from django.contrib.staticfiles.storage import StaticFilesStorage +from mock import patch from storages.backends.s3boto import S3BotoStorage +from .utils import test from collectfast import etag from collectfast import settings -from .utils import test hash_characters = string.ascii_letters + string.digits @test def test_get_cache_key(case): - cache_key = etag.get_cache_key('/some/random/path') + cache_key = etag.get_cache_key("/some/random/path") prefix_len = len(settings.cache_key_prefix) case.assertTrue(cache_key.startswith(settings.cache_key_prefix)) case.assertEqual(32 + prefix_len, len(cache_key)) - expected_chars = hash_characters + '_' + expected_chars = hash_characters + "_" for c in cache_key: case.assertIn(c, expected_chars) @@ -27,35 +27,35 @@ def test_get_cache_key(case): @test @patch("collectfast.etag.get_remote_etag") def test_get_destroy_etag(case, mocked): - mocked.return_value = expected_hash = 'hash' + mocked.return_value = expected_hash = "hash" # empty cache - h = etag.get_etag('storage', 'path', 'prefixed_path') + h = etag.get_etag("storage", "path", "prefixed_path") case.assertEqual(h, expected_hash) - mocked.assert_called_once_with('storage', 'prefixed_path') + mocked.assert_called_once_with("storage", "prefixed_path") # populated cache mocked.reset_mock() - h = etag.get_etag('storage', 'path', 'prefixed_path') + h = etag.get_etag("storage", "path", "prefixed_path") case.assertEqual(h, expected_hash) mocked.assert_not_called() # test destroy_etag mocked.reset_mock() - etag.destroy_etag('path') - h = etag.get_etag('storage', 'path', 'prefixed_path') + etag.destroy_etag("path") + h = etag.get_etag("storage", "path", "prefixed_path") case.assertEqual(h, expected_hash) - mocked.assert_called_once_with('storage', 'prefixed_path') + mocked.assert_called_once_with("storage", "prefixed_path") @test def test_get_file_hash(case): # disable this test on appveyor until permissions issue is solved - if platform.system() == 'Windows': + if platform.system() == "Windows": return storage = StaticFilesStorage() with tempfile.NamedTemporaryFile(dir=storage.base_location) as f: - f.write(b'spam') + f.write(b"spam") h = etag.get_file_hash(storage, f.name) case.assertEqual(len(h), 34) case.assertTrue(h.startswith('"')) @@ -65,29 +65,29 @@ def test_get_file_hash(case): @test -@patch('collectfast.etag.get_etag') -@patch('collectfast.etag.get_file_hash') +@patch("collectfast.etag.get_etag") +@patch("collectfast.etag.get_file_hash") def test_has_matching_etag(case, mocked_get_etag, mocked_get_file_hash): - mocked_get_etag.return_value = mocked_get_file_hash.return_value = 'hash' - case.assertTrue( - etag.has_matching_etag('rs', 'ss', 'path', 'prefixed_path')) - mocked_get_etag.return_value = 'not same' - case.assertFalse( - etag.has_matching_etag('rs', 'ss', 'path', 'prefixed_path')) + mocked_get_etag.return_value = mocked_get_file_hash.return_value = "hash" + case.assertTrue(etag.has_matching_etag("rs", "ss", "path", "prefixed_path")) + mocked_get_etag.return_value = "not same" + case.assertFalse(etag.has_matching_etag("rs", "ss", "path", "prefixed_path")) @test -@patch('collectfast.etag.has_matching_etag') -@patch('collectfast.etag.destroy_etag') +@patch("collectfast.etag.has_matching_etag") +@patch("collectfast.etag.destroy_etag") def test_should_copy_file(case, mocked_destroy_etag, mocked_has_matching_etag): remote_storage = S3BotoStorage() mocked_has_matching_etag.return_value = True - case.assertFalse(etag.should_copy_file( - remote_storage, 'path', 'prefixed_path', 'source_storage')) + case.assertFalse( + etag.should_copy_file(remote_storage, "path", "prefixed_path", "source_storage") + ) mocked_destroy_etag.assert_not_called() mocked_has_matching_etag.return_value = False - case.assertTrue(etag.should_copy_file( - remote_storage, 'path', 'prefixed_path', 'source_storage')) - mocked_destroy_etag.assert_called_once_with('path') + case.assertTrue( + etag.should_copy_file(remote_storage, "path", "prefixed_path", "source_storage") + ) + mocked_destroy_etag.assert_called_once_with("path") diff --git a/collectfast/tests/utils.py b/collectfast/tests/utils.py index 444375e..70e1aa1 100644 --- a/collectfast/tests/utils.py +++ b/collectfast/tests/utils.py @@ -1,8 +1,8 @@ +import functools +import os import random import unittest import uuid -import os -import functools from django.conf import settings as django_settings from django.utils.module_loading import import_string @@ -20,19 +20,19 @@ def test(func): >>> def test_fn(case): >>> case.assertEqual(fn(), 1337) """ - return type(func.__name__, (unittest.TestCase, ), {func.__name__: func}) + return type(func.__name__, (unittest.TestCase,), {func.__name__: func}) def create_static_file(): - filename = 'static/%s.txt' % uuid.uuid4() - with open(filename, 'w+') as f: + filename = "static/%s.txt" % uuid.uuid4() + with open(filename, "w+") as f: for _ in range(500): f.write(chr(int(random.random() * 64))) f.close() def clean_static_dir(): - d = 'static/' + d = "static/" for f in os.listdir(d): path = os.path.join(d, f) if os.path.isfile(path): @@ -48,7 +48,9 @@ def wrapper(*args, **kwargs): ret = fn(*args, **kwargs) setattr(settings, name, original) return ret + return wrapper + return decorator @@ -56,12 +58,13 @@ def override_storage_attr(name, value): def decorator(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): - storage = import_string( - getattr(django_settings, 'STATICFILES_STORAGE')) + storage = import_string(getattr(django_settings, "STATICFILES_STORAGE")) original = getattr(storage, name) setattr(storage, name, value) ret = fn(*args, **kwargs) setattr(storage, name, original) return ret + return wrapper + return decorator diff --git a/runtests.py b/runtests.py index 0207f57..56f4b1a 100755 --- a/runtests.py +++ b/runtests.py @@ -1,8 +1,7 @@ #!/usr/bin/env python - import os -import sys import shutil +import sys from optparse import OptionParser import django @@ -17,7 +16,7 @@ def main(): options, args = parser.parse_args() # check for app in args - app_path = 'collectfast' + app_path = "collectfast" parent_dir, app_name = os.path.split(app_path) sys.path.insert(0, parent_dir) @@ -29,43 +28,42 @@ def main(): if not os.path.exists(staticroot_dir): os.makedirs(staticroot_dir) - settings.configure(**{ - # Set USE_TZ to True to work around bug in django-storages - "USE_TZ": True, - "CACHES": { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': 'test-collectfast' - } - }, - "TEMPLATE_LOADERS": ( - "django.template.loaders.filesystem.Loader", - "django.template.loaders.app_directories.Loader", - "django.template.loaders.eggs.Loader", - ), - "TEMPLATE_DIRS": ( - os.path.join(os.path.dirname(__file__), "collectfast/templates"), - ), - "INSTALLED_APPS": ( - app_name, - "django.contrib.staticfiles", - ), - "STATIC_URL": "/staticfiles/", - "STATIC_ROOT": staticroot_dir, - "STATICFILES_DIRS": [staticfiles_dir], - "STATICFILES_STORAGE": "storages.backends.s3boto3.S3Boto3Storage", - "MIDDLEWARE_CLASSES": [], - "AWS_PRELOAD_METADATA": True, - "AWS_STORAGE_BUCKET_NAME": "collectfast", - "AWS_IS_GZIPPED": False, - "GZIP_CONTENT_TYPES": ('text/plain', ), - "AWS_ACCESS_KEY_ID": os.environ.get("AWS_ACCESS_KEY_ID").strip(), - "AWS_SECRET_ACCESS_KEY": os.environ.get("AWS_SECRET_ACCESS_KEY").strip(), - "AWS_S3_REGION_NAME": "eu-central-1", - "AWS_S3_SIGNATURE_VERSION": "s3v4", - "AWS_QUERYSTRING_AUTH": False, - "AWS_DEFAULT_ACL": None, - }) + settings.configure( + **{ + # Set USE_TZ to True to work around bug in django-storages + "USE_TZ": True, + "CACHES": { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "test-collectfast", + } + }, + "TEMPLATE_LOADERS": ( + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + "django.template.loaders.eggs.Loader", + ), + "TEMPLATE_DIRS": ( + os.path.join(os.path.dirname(__file__), "collectfast/templates"), + ), + "INSTALLED_APPS": (app_name, "django.contrib.staticfiles"), + "STATIC_URL": "/staticfiles/", + "STATIC_ROOT": staticroot_dir, + "STATICFILES_DIRS": [staticfiles_dir], + "STATICFILES_STORAGE": "storages.backends.s3boto3.S3Boto3Storage", + "MIDDLEWARE_CLASSES": [], + "AWS_PRELOAD_METADATA": True, + "AWS_STORAGE_BUCKET_NAME": "collectfast", + "AWS_IS_GZIPPED": False, + "GZIP_CONTENT_TYPES": ("text/plain",), + "AWS_ACCESS_KEY_ID": os.environ.get("AWS_ACCESS_KEY_ID").strip(), + "AWS_SECRET_ACCESS_KEY": os.environ.get("AWS_SECRET_ACCESS_KEY").strip(), + "AWS_S3_REGION_NAME": "eu-central-1", + "AWS_S3_SIGNATURE_VERSION": "s3v4", + "AWS_QUERYSTRING_AUTH": False, + "AWS_DEFAULT_ACL": None, + } + ) if options.TEST_SUITE is not None: test_arg = "%s.%s" % (app_name, options.TEST_SUITE) diff --git a/setup.cfg b/setup.cfg index 68117ec..8519c00 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,3 +7,18 @@ enable-extensions = # see this discussion as to why we're ignoring E722: # https://github.com/PyCQA/pycodestyle/issues/703 extend-ignore = F403,E265,E722 + +[mypy] +ignore_missing_imports = True +strict_optional = True +no_implicit_optional = True +check_untyped_defs = True +disallow_incomplete_defs = True +new_semantic_analyzer = True +ignore_errors = False + +[mypy-runtests] +ignore_errors = True + +[mypy-collectfast.tests.*] +ignore_errors = True diff --git a/setup.py b/setup.py index 5c62167..498d420 100644 --- a/setup.py +++ b/setup.py @@ -1,30 +1,35 @@ #!/usr/bin/env python - from distutils.core import setup + from setuptools import find_packages setup( - name='Collectfast', - description='A Faster Collectstatic', - version='0.6.3', - long_description=open('README.rst').read(), - author='Anton Agestam', - author_email='msn@antonagestam.se', + name="Collectfast", + description="A Faster Collectstatic", + version="0.6.3", + long_description=open("README.rst").read(), + author="Anton Agestam", + author_email="msn@antonagestam.se", packages=find_packages(), - url='https://github.com/antonagestam/collectfast/', - license='MIT License', + url="https://github.com/antonagestam/collectfast/", + license="MIT License", include_package_data=True, - install_requires=['Django>=1.11', 'django-storages>=1.6'], + install_requires=[ + "Django>=1.11", + "django-storages>=1.6", + "typing", + "typing-extensions", + ], classifiers=[ - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3', - 'Framework :: Django', - ] + "Environment :: Web Environment", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3", + "Framework :: Django", + ], ) diff --git a/test-requirements.txt b/test-requirements.txt index 77aa361..69be697 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,9 @@ flake8 flake8-mutable mypy +typing +typing-extensions +django-stubs mock==1.3.0 coveralls django-storages