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