diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db9bce0..6003058 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,7 @@ +--- name: "build" -on: +on: # yamllint disable-line rule:truthy pull_request: push: branches: master @@ -47,27 +48,34 @@ jobs: flask-version: "1.1.*" - python-version: 3.9 flask-version: "2.0.*" + coverage: true steps: - - name: Checkout - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 - - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} - - name: Install Flask ${{ matrix.flask-version }} - run: | - pip install Flask==${{ matrix.flask-version }} - pip install pillow - pip install codecov - pip install mock + - name: Install Flask ${{ matrix.flask-version }} + run: | + pip install Flask==${{ matrix.flask-version }} + pip install pillow + pip install coverage + pip install mock - - name: Run tests - run: | - PYTHONPATH=".:tests:$PYTHONPATH" coverage run --omit='setup.py' --omit='tests/*' --source='.' setup.py test + - name: Run tests + run: | + make test - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + - if: ${{ matrix.coverage }} + run: | + make coverage + - if: ${{ matrix.coverage }} + name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..13c3c22 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,30 @@ +--- +name: "Lint" + +on: # yamllint disable-line rule:truthy + pull_request: + push: + branches: master + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Python 3.9 + uses: actions/setup-python@v1 + with: + python-version: 3.9 + + - name: Upgrade Setuptools + run: pip install --upgrade setuptools wheel + + - name: Install requirements + run: pip install -r requirements-dev.txt + + - name: Run lint + run: make lint diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index d2e8fb2..1047f20 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -1,6 +1,7 @@ -name: PyPI Release +--- +name: "PyPI Release" -on: +on: # yamllint disable-line rule:truthy push: tags: - 'v*' @@ -11,22 +12,22 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 - - name: Setup Python 3.9 - uses: actions/setup-python@v1 - with: - python-version: 3.9 + - name: Setup Python 3.9 + uses: actions/setup-python@v1 + with: + python-version: 3.9 - - name: Upgrade Setuptools - run: pip install --upgrade setuptools wheel + - name: Upgrade Setuptools + run: pip install --upgrade setuptools wheel - - name: Build Distribution - run: python setup.py sdist bdist_wheel --universal + - name: Build Distribution + run: python setup.py sdist bdist_wheel --universal - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_password }} + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.pypi_password }} diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..4f8ab42 --- /dev/null +++ b/.yamllint @@ -0,0 +1,10 @@ +--- +extends: default + +rules: + line-length: disable + +ignore: | + *.venv/ + *.mypy_cache/ + *.eggs/ diff --git a/Makefile b/Makefile index 100a065..ca72558 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,4 @@ -.PHONY: test sdist wheel release pre-release clean - -test: - python setup.py test +.PHONY: check-black check-isort check-pylint static-analysis test sdist wheel release pre-release clean sdist: python setup.py sdist @@ -19,4 +16,42 @@ clean: find . | grep -E '(__pycache__|\.pyc|\.pyo$)' | xargs rm -rf rm -rf build rm -rf dist - rm -rf *.egg-info \ No newline at end of file + rm -rf *.egg-info + +check-black: + @echo "--> Running black checks" + @black --check --diff . + +check-isort: + @echo "--> Running isort checks" + @isort --check-only . + +check-pylint: + @echo "--> Running pylint checks" + @pylint `git ls-files '*.py'` + +check-yamllint: + @echo "--> Running yamllint checks" + @yamllint . + +lint: check-black check-isort check-pylint check-yamllint + +# Format code +.PHONY: fmt + +fmt: + @echo "--> Running isort" + @isort . + @echo "--> Running black" + @black . + +# Test +.PHONY: test + +test: + @echo "--> Running tests" + PYTHONPATH=".:tests:${PYTHONPATH}" python setup.py test + +coverage: + @echo "--> Running coverage" + PYTHONPATH=".:tests:${PYTHONPATH}" coverage run --omit='setup.py' --omit='tests/*' --source='.' setup.py test diff --git a/README.rst b/README.rst index 1819282..ebfdf09 100644 --- a/README.rst +++ b/README.rst @@ -91,7 +91,7 @@ If you want to store the thumbnail in a folder other than the ``THUMBNAIL_MEDIA_ app.config['THUMBNAIL_MEDIA_THUMBNAIL_ROOT'] = '/home/www/media/cache' app.config['THUMBNAIL_MEDIA_THUMBNAIL_URL'] = '/media/cache/' app.config['THUMBNAIL_STORAGE_BACKEND'] = 'flask_thumbnails.storage_backends.FilesystemStorageBackend' - app.config['THUMBNAIL_DEFAUL_FORMAT'] = 'JPEG' + app.config['THUMBNAIL_DEFAULT_FORMAT'] = 'JPEG' Migrate 0.X to 1.X diff --git a/flask_thumbnails/__init__.py b/flask_thumbnails/__init__.py index 73c4c36..7000c9d 100644 --- a/flask_thumbnails/__init__.py +++ b/flask_thumbnails/__init__.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals -__author__ = 'Dmitriy Sokolov' -__version__ = '1.1.0' +__author__ = "Dmitriy Sokolov" +__version__ = "1.1.0" from .thumbnail import Thumbnail diff --git a/flask_thumbnails/storage_backends.py b/flask_thumbnails/storage_backends.py index de516de..4b4e370 100644 --- a/flask_thumbnails/storage_backends.py +++ b/flask_thumbnails/storage_backends.py @@ -10,7 +10,7 @@ class BaseStorageBackend(object): def __init__(self, app=None): self.app = app - def read(self, filepath, **kwargs): + def read(self, filepath, mode="rb", **kwargs): raise NotImplementedError def exists(self, filepath): @@ -21,12 +21,12 @@ def save(self, filepath, data): class FilesystemStorageBackend(BaseStorageBackend): - def read(self, filepath, mode='rb'): - with open(filepath, mode) as f: + def read(self, filepath, mode="rb", **kwargs): + with open(filepath, mode) as f: # pylint: disable=unspecified-encoding return f.read() - def exists(self, name): - return os.path.exists(name) + def exists(self, filepath): + return os.path.exists(filepath) def save(self, filepath, data): directory = os.path.dirname(filepath) @@ -39,7 +39,7 @@ def save(self, filepath, data): raise if not os.path.isdir(directory): - raise IOError('{} is not a directory'.format(directory)) + raise IOError("{} is not a directory".format(directory)) - with open(filepath, 'wb') as f: + with open(filepath, "wb") as f: f.write(data) diff --git a/flask_thumbnails/thumbnail.py b/flask_thumbnails/thumbnail.py index eeaac54..2697202 100644 --- a/flask_thumbnails/thumbnail.py +++ b/flask_thumbnails/thumbnail.py @@ -3,28 +3,28 @@ from __future__ import unicode_literals import os - from io import BytesIO try: from PIL import Image, ImageOps except ImportError: - raise RuntimeError('Get Pillow at https://pypi.python.org/pypi/Pillow ' - 'or run command "pip install Pillow".') + raise RuntimeError( # pylint: disable=raise-missing-from + "Get Pillow at https://pypi.python.org/pypi/Pillow or run command 'pip install pillow'." + ) -from .utils import import_from_string, generate_filename, parse_size, aspect_to_string +from .utils import aspect_to_string, generate_filename, import_from_string, parse_size class Thumbnail(object): def __init__(self, app=None, configure_jinja=True): self.app = app self._configure_jinja = configure_jinja - self._default_root_directory = 'media' - self._default_thumbnail_directory = 'media' - self._default_root_url = '/' - self._default_thumbnail_root_url = '/' - self._default_format = 'JPEG' - self._default_storage_backend = 'flask_thumbnails.storage_backends.FilesystemStorageBackend' + self._default_root_directory = "media" + self._default_thumbnail_directory = "media" + self._default_root_url = "/" + self._default_thumbnail_root_url = "/" + self._default_format = "JPEG" + self._default_storage_backend = "flask_thumbnails.storage_backends.FilesystemStorageBackend" if app is not None: self.init_app(app) @@ -34,20 +34,20 @@ def init_app(self, app): self.app = app app.thumbnail_instance = self - if not hasattr(app, 'extensions'): + if not hasattr(app, "extensions"): app.extensions = {} - if 'thumbnail' in app.extensions: - raise RuntimeError('Flask-thumbnail extension already initialized') + if "thumbnail" in app.extensions: + raise RuntimeError("Flask-thumbnail extension already initialized") - app.extensions['thumbnail'] = self + app.extensions["thumbnail"] = self - app.config.setdefault('THUMBNAIL_MEDIA_ROOT', self._default_root_directory) - app.config.setdefault('THUMBNAIL_MEDIA_THUMBNAIL_ROOT', self._default_thumbnail_directory) - app.config.setdefault('THUMBNAIL_MEDIA_URL', self._default_root_url) - app.config.setdefault('THUMBNAIL_MEDIA_THUMBNAIL_URL', self._default_thumbnail_root_url) - app.config.setdefault('THUMBNAIL_STORAGE_BACKEND', self._default_storage_backend) - app.config.setdefault('THUMBNAIL_DEFAUL_FORMAT', self._default_format) + app.config.setdefault("THUMBNAIL_MEDIA_ROOT", self._default_root_directory) + app.config.setdefault("THUMBNAIL_MEDIA_THUMBNAIL_ROOT", self._default_thumbnail_directory) + app.config.setdefault("THUMBNAIL_MEDIA_URL", self._default_root_url) + app.config.setdefault("THUMBNAIL_MEDIA_THUMBNAIL_URL", self._default_thumbnail_root_url) + app.config.setdefault("THUMBNAIL_STORAGE_BACKEND", self._default_storage_backend) + app.config.setdefault("THUMBNAIL_DEFAULT_FORMAT", self._default_format) if self._configure_jinja: app.jinja_env.filters.update( @@ -56,7 +56,7 @@ def init_app(self, app): @property def root_directory(self): - path = self.app.config['THUMBNAIL_MEDIA_ROOT'] + path = self.app.config["THUMBNAIL_MEDIA_ROOT"] if os.path.isabs(path): return path @@ -65,7 +65,7 @@ def root_directory(self): @property def thumbnail_directory(self): - path = self.app.config['THUMBNAIL_MEDIA_THUMBNAIL_ROOT'] + path = self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_ROOT"] if os.path.isabs(path): return path @@ -74,15 +74,15 @@ def thumbnail_directory(self): @property def root_url(self): - return self.app.config['THUMBNAIL_MEDIA_URL'] + return self.app.config["THUMBNAIL_MEDIA_URL"] @property def thumbnail_url(self): - return self.app.config['THUMBNAIL_MEDIA_THUMBNAIL_URL'] + return self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_URL"] @property def storage_backend(self): - return self.app.config['THUMBNAIL_STORAGE_BACKEND'] + return self.app.config["THUMBNAIL_STORAGE_BACKEND"] def get_storage_backend(self): backend_class = import_from_string(self.storage_backend) @@ -90,16 +90,20 @@ def get_storage_backend(self): def get_thumbnail(self, original, size, **options): storage = self.get_storage_backend() - crop = options.get('crop', 'fit') - background = options.get('background') - quality = options.get('quality', 90) + crop = options.get("crop", "fit") + background = options.get("background") + quality = options.get("quality", 90) thumbnail_size = parse_size(size) original_path, original_filename = os.path.split(original) - thumbnail_filename = generate_filename(original_filename, aspect_to_string(size), crop, background, quality) + thumbnail_filename = generate_filename( + original_filename, aspect_to_string(size), crop, background, quality + ) original_filepath = os.path.join(self.root_directory, original_path, original_filename) - thumbnail_filepath = os.path.join(self.thumbnail_directory, original_path, thumbnail_filename) + thumbnail_filepath = os.path.join( + self.thumbnail_directory, original_path, thumbnail_filename + ) thumbnail_url = os.path.join(self.thumbnail_url, original_path, thumbnail_filename) if storage.exists(thumbnail_filepath): @@ -109,14 +113,13 @@ def get_thumbnail(self, original, size, **options): try: image.load() except (IOError, OSError): - self.app.logger.warning('Thumbnail not load image: %s', original_filepath) + self.app.logger.warning("Thumbnail not load image: %s", original_filepath) return thumbnail_url # get original image format - options['format'] = options.get('format', image.format) + options["format"] = options.get("format", image.format) - image = self._create_thumbnail(image, thumbnail_size, crop, - background=background) + image = self._create_thumbnail(image, thumbnail_size, crop, background=background) raw_data = self.get_raw_data(image, **options) storage.save(thumbnail_filepath, raw_data) @@ -125,8 +128,8 @@ def get_thumbnail(self, original, size, **options): def get_raw_data(self, image, **options): data = { - 'format': self._get_format(image, **options), - 'quality': options.get('quality', 90), + "format": self._get_format(image, **options), + "quality": options.get("quality", 90), } _file = BytesIO() @@ -134,37 +137,40 @@ def get_raw_data(self, image, **options): return _file.getvalue() @staticmethod - def colormode(image, colormode='RGB'): - if colormode == 'RGB' or colormode == 'RGBA': - if image.mode == 'RGBA': + def colormode(image, colormode="RGB"): + if colormode == "RGB" or colormode == "RGBA": + if image.mode == "RGBA": return image - if image.mode == 'LA': - return image.convert('RGBA') + if image.mode == "LA": + return image.convert("RGBA") return image.convert(colormode) - if colormode == 'GRAY': - return image.convert('L') + if colormode == "GRAY": + return image.convert("L") return image.convert(colormode) @staticmethod - def background(original_image, color=0xff): + def background(original_image, color=0xFF): size = (max(original_image.size),) * 2 - image = Image.new('L', size, color) - image.paste(original_image, tuple(map(lambda x: (x[0] - x[1]) / 2, zip(size, original_image.size)))) + image = Image.new("L", size, color) + image.paste( + original_image, + tuple(map(lambda x: (x[0] - x[1]) / 2, zip(size, original_image.size))), + ) return image def _get_format(self, image, **options): - if options.get('format'): - return options.get('format') + if options.get("format"): + return options.get("format") if image.format: return image.format - return self.app.config['THUMBNAIL_DEFAUL_FORMAT'] + return self.app.config["THUMBNAIL_DEFAULT_FORMAT"] - def _create_thumbnail(self, image, size, crop='fit', background=None): - if crop == 'fit': + def _create_thumbnail(self, image, size, crop="fit", background=None): + if crop == "fit": image = ImageOps.fit(image, size, Image.ANTIALIAS) else: image = image.copy() diff --git a/flask_thumbnails/utils.py b/flask_thumbnails/utils.py index cdab1e2..b6c59aa 100644 --- a/flask_thumbnails/utils.py +++ b/flask_thumbnails/utils.py @@ -2,19 +2,19 @@ from __future__ import unicode_literals +import importlib import os import sys -import importlib def import_from_string(path): - path_bits = path.split('.') + path_bits = path.split(".") class_name = path_bits.pop() - module_path = '.'.join(path_bits) + module_path = ".".join(path_bits) module_itself = importlib.import_module(module_path) if not hasattr(module_itself, class_name): - raise ImportError('The Python module \'%s\' has no \'%s\' class.' % (module_path, class_name)) + raise ImportError("The Python module '%s' has no '%s' class." % (module_path, class_name)) return getattr(module_itself, class_name) @@ -23,7 +23,7 @@ def generate_filename(original_filename, *options): name, ext = os.path.splitext(original_filename) for v in options: if v: - name += '_%s' % v + name += "_%s" % v name += ext return name @@ -31,7 +31,7 @@ def generate_filename(original_filename, *options): def parse_size(size): if sys.version_info < (3,): - integer_types = (int, long) + integer_types = (int,) else: integer_types = (int,) @@ -46,9 +46,11 @@ def parse_size(size): return size try: - thumbnail_size = [int(x) for x in size.lower().split('x', 1)] + thumbnail_size = [int(x) for x in size.lower().split("x", 1)] except ValueError: - raise ValueError('Bad thumbnail size format. Valid format is INTxINT.') + raise ValueError( # pylint: disable=raise-missing-from + "Bad thumbnail size format. Valid format is INTxINT." + ) if len(thumbnail_size) == 1: # If the size parameter only contains a single integer, assume square aspect. @@ -59,11 +61,11 @@ def parse_size(size): def aspect_to_string(size): if sys.version_info < (3,): - str_type = basestring + str_type = basestring # pylint: disable=undefined-variable else: str_type = str if isinstance(size, str_type): return size - return 'x'.join(map(str, size)) + return "x".join(map(str, size)) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..45eba76 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[tool.black] +line-length = 100 +target-version = ['py38', 'py39'] +exclude = '(\.eggs|\.git|\.mypy_cache|\.venv|venv|env|_build|build|build|dist|eggs)' + +[tool.isort] +line_length = 100 +profile = "black" +use_parentheses = true +skip = '.eggs/,.mypy_cache/,.venv/,venv/,env/,eggs/' + +[tool.pylint] +[tool.pylint.master] +py-version = 3.9 + +[tool.pylint.messages-control] +disable=[ + 'C', + 'R', + + # Redundant with mypy + 'typecheck', + + # There are many places where we want to catch a maximally generic exception. + 'bare-except', + 'broad-except', + + # Pylint is by default very strict on logging string interpolations, but the + # (performance-motivated) rules do not make sense for infrequent log messages (like error reports) + # and make messages less readable. + 'logging-fstring-interpolation', + 'logging-format-interpolation', + 'logging-not-lazy', + 'too-many-arguments', + 'duplicate-code', +] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..57c278f --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,7 @@ +Flask==2.0 +Pillow==9.2.0 +mock==4.0.3 +black==22.3.0 +isort==5.10.1 +pylint==2.14.4 +yamllint==1.26.3 diff --git a/setup.py b/setup.py index d7f8adf..e76c25b 100644 --- a/setup.py +++ b/setup.py @@ -3,53 +3,57 @@ import os import re -from os.path import join, dirname +from os.path import dirname, join + from setuptools import setup def get_version(package): - init_py = open(os.path.join(package, '__init__.py')).read() + init_py = open(os.path.join(package, "__init__.py"), encoding="utf-8").read() return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1) def get_packages(package): - return [dirpath for dirpath, dirnames, filenames in os.walk(package) - if os.path.exists(os.path.join(dirpath, '__init__.py'))] + return [ + dirpath + for dirpath, dirnames, filenames in os.walk(package) + if os.path.exists(os.path.join(dirpath, "__init__.py")) + ] setup( - name='Flask-thumbnails', - version=get_version('flask_thumbnails'), - url='https://github.com/silentsokolov/flask-thumbnails', - license='MIT', - description='A simple extension to create a thumbs for the Flask', - long_description_content_type='text/x-rst', - long_description=open(join(dirname(__file__), 'README.rst')).read(), - author='Dmitriy Sokolov', - author_email='silentsokolov@gmail.com', - packages=get_packages('flask_thumbnails'), + name="flask-thumbnails", + version=get_version("flask_thumbnails"), + url="https://github.com/silentsokolov/flask-thumbnails", + license="MIT", + description="A simple extension to create a thumbs for the Flask", + long_description_content_type="text/x-rst", + long_description=open(join(dirname(__file__), "README.rst"), encoding="utf-8").read(), + author="Dmitriy Sokolov", + author_email="silentsokolov@gmail.com", + packages=get_packages("flask_thumbnails"), include_package_data=True, install_requires=[], python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*", zip_safe=False, - platforms='any', - test_suite='tests', + platforms="any", + test_suite="tests", classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Web Environment', - 'Framework :: Flask', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Topic :: Utilities', - ] + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Flask", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Utilities", + ], ) diff --git a/tests/core_tests.py b/tests/core_tests.py index 67316c6..c2de829 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -3,16 +3,15 @@ from __future__ import unicode_literals import os -import unittest import tempfile -import mock +import unittest from io import BytesIO +import mock +from flask import Flask from PIL import Image from flask_thumbnails import Thumbnail -from flask import Flask - from flask_thumbnails.storage_backends import FilesystemStorageBackend @@ -22,111 +21,125 @@ def setUp(self): self.thumbnail = Thumbnail(app=self.app) self.client = self.app.test_client() - self.image = Image.new('RGB', (100, 100), 'black') + self.image = Image.new("RGB", (100, 100), "black") def test_root_directory(self): - self.app.config['THUMBNAIL_MEDIA_ROOT'] = 'media' + self.app.config["THUMBNAIL_MEDIA_ROOT"] = "media" self.assertEqual( self.thumbnail.root_directory, - os.path.join(self.app.root_path, self.app.config['THUMBNAIL_MEDIA_ROOT']) + os.path.join(self.app.root_path, self.app.config["THUMBNAIL_MEDIA_ROOT"]), ) - self.app.config['THUMBNAIL_MEDIA_ROOT'] = '/tmp/media' - self.assertEqual(self.thumbnail.root_directory, self.app.config['THUMBNAIL_MEDIA_ROOT']) + self.app.config["THUMBNAIL_MEDIA_ROOT"] = "/tmp/media" + self.assertEqual(self.thumbnail.root_directory, self.app.config["THUMBNAIL_MEDIA_ROOT"]) def test_thumbnail_directory(self): - self.app.config['THUMBNAIL_MEDIA_THUMBNAIL_ROOT'] = 'media' + self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_ROOT"] = "media" self.assertEqual( self.thumbnail.thumbnail_directory, - os.path.join(self.app.root_path, self.app.config['THUMBNAIL_MEDIA_THUMBNAIL_ROOT']) + os.path.join(self.app.root_path, self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_ROOT"]), ) - self.app.config['THUMBNAIL_MEDIA_THUMBNAIL_ROOT'] = '/tmp/media' + self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_ROOT"] = "/tmp/media" self.assertEqual( self.thumbnail.thumbnail_directory, - self.app.config['THUMBNAIL_MEDIA_THUMBNAIL_ROOT'] + self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_ROOT"], ) def test_root_url(self): - self.app.config['THUMBNAIL_MEDIA_URL'] = '/media' - self.assertEqual(self.thumbnail.root_url, self.app.config['THUMBNAIL_MEDIA_URL']) + self.app.config["THUMBNAIL_MEDIA_URL"] = "/media" + self.assertEqual(self.thumbnail.root_url, self.app.config["THUMBNAIL_MEDIA_URL"]) def test_thumbnail_url(self): - self.app.config['THUMBNAIL_MEDIA_THUMBNAIL_URL'] = '/media' - self.assertEqual(self.thumbnail.thumbnail_url, self.app.config['THUMBNAIL_MEDIA_THUMBNAIL_URL']) + self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_URL"] = "/media" + self.assertEqual( + self.thumbnail.thumbnail_url, + self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_URL"], + ) def test_storage_backend(self): - self.assertEqual(self.thumbnail.storage_backend, self.app.config['THUMBNAIL_STORAGE_BACKEND']) + self.assertEqual( + self.thumbnail.storage_backend, self.app.config["THUMBNAIL_STORAGE_BACKEND"] + ) def test_get_storage_backend(self): self.assertIsInstance(self.thumbnail.get_storage_backend(), FilesystemStorageBackend) def test_colormode(self): - image = Image.new('L', (10, 10)) + image = Image.new("L", (10, 10)) new_image = self.thumbnail.colormode(image) - self.assertEqual(new_image.mode, 'RGB') + self.assertEqual(new_image.mode, "RGB") - image = Image.new('LA', (10, 10)) + image = Image.new("LA", (10, 10)) new_image = self.thumbnail.colormode(image) - self.assertEqual(new_image.mode, 'RGBA') + self.assertEqual(new_image.mode, "RGBA") - image = Image.new('RGBA', (10, 10)) + image = Image.new("RGBA", (10, 10)) new_image = self.thumbnail.colormode(image) - self.assertEqual(new_image.mode, 'RGBA') + self.assertEqual(new_image.mode, "RGBA") - image = Image.new('RGB', (10, 10)) + image = Image.new("RGB", (10, 10)) new_image = self.thumbnail.colormode(image) - self.assertEqual(new_image.mode, 'RGB') + self.assertEqual(new_image.mode, "RGB") def test_get_format(self): - image = Image.new('RGB', (10, 10)) + image = Image.new("RGB", (10, 10)) new_image = self.thumbnail.colormode(image) - self.assertEqual(new_image.mode, 'RGB') + self.assertEqual(new_image.mode, "RGB") - options = {'format': 'PNG'} - self.assertEqual(self.thumbnail._get_format(image, **options), 'PNG') + options = {"format": "PNG"} + self.assertEqual( + self.thumbnail._get_format(image, **options), "PNG" # pylint: disable=protected-access + ) options = {} - self.assertEqual(self.thumbnail._get_format(image, **options), self.app.config['THUMBNAIL_DEFAUL_FORMAT']) + self.assertEqual( + self.thumbnail._get_format(image, **options), # pylint: disable=protected-access + self.app.config["THUMBNAIL_DEFAULT_FORMAT"], + ) def test_get_raw_data(self): - image = Image.new('L', (10, 10)) + image = Image.new("L", (10, 10)) - options = {'format': 'JPEG'} + options = {"format": "JPEG"} data = self.thumbnail.get_raw_data(image, **options) new_image = Image.open(BytesIO(data)) self.assertEqual(image.mode, new_image.mode) self.assertEqual(image.size, new_image.size) - self.assertEqual(new_image.format, 'JPEG') + self.assertEqual(new_image.format, "JPEG") def test_create_thumbnail(self): - image = Image.new('L', (100, 100)) + image = Image.new("L", (100, 100)) - new_image = self.thumbnail._create_thumbnail(image, size=(50, 50)) + new_image = self.thumbnail._create_thumbnail( # pylint: disable=protected-access + image, size=(50, 50) + ) self.assertEqual(new_image.size, (50, 50)) - new_image = self.thumbnail._create_thumbnail(image, size=(50, 50), crop=None) + new_image = self.thumbnail._create_thumbnail( # pylint: disable=protected-access + image, size=(50, 50), crop=None + ) self.assertEqual(new_image.size, (50, 50)) - @mock.patch('flask_thumbnails.utils.generate_filename') + @mock.patch("flask_thumbnails.utils.generate_filename") def test_get_thumbnail(self, mock_thumb_name): - with tempfile.NamedTemporaryFile(suffix='.jpg') as original: - with tempfile.NamedTemporaryFile(suffix='.jpg') as thumb: + with tempfile.NamedTemporaryFile(suffix=".jpg") as original: + with tempfile.NamedTemporaryFile(suffix=".jpg") as thumb: mock_thumb_name.return_value = os.path.basename(thumb.name) - self.app.config['THUMBNAIL_MEDIA_ROOT'] = os.path.dirname(original.name) - self.app.config['THUMBNAIL_MEDIA_THUMBNAIL_ROOT'] = os.path.dirname(thumb.name) + self.app.config["THUMBNAIL_MEDIA_ROOT"] = os.path.dirname(original.name) + self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_ROOT"] = os.path.dirname(thumb.name) - image = Image.new('RGB', (100, 100), 'black') + image = Image.new("RGB", (100, 100), "black") image.save(original.name) - thumb_url = self.thumbnail.get_thumbnail(os.path.basename(original.name), '200x200') + thumb_url = self.thumbnail.get_thumbnail(os.path.basename(original.name), "200x200") self.assertTrue(thumb_url) diff --git a/tests/storage_backends_tests.py b/tests/storage_backends_tests.py index 09da3fe..c2e86c4 100644 --- a/tests/storage_backends_tests.py +++ b/tests/storage_backends_tests.py @@ -4,9 +4,10 @@ import os import shutil -import unittest import tempfile +import unittest from io import BytesIO + from PIL import Image from flask_thumbnails.storage_backends import FilesystemStorageBackend @@ -14,8 +15,8 @@ class FilesystemStorageBackendTestCase(unittest.TestCase): def setUp(self): - image = Image.new('RGB', (100, 100), 'black') - tmp_file = tempfile.NamedTemporaryFile(suffix='.jpg') + image = Image.new("RGB", (100, 100), "black") + tmp_file = tempfile.NamedTemporaryFile(suffix=".jpg") image.save(tmp_file) tmp_file.seek(0) @@ -28,21 +29,21 @@ def test_read(self): self.assertEqual(image.size, (100, 100)) def test_exists(self): - self.assertTrue(self.backend.exists(os.path.join(os.getcwd(), 'setup.py'))) - self.assertFalse(self.backend.exists(os.path.join(os.getcwd(), 'stup.py'))) + self.assertTrue(self.backend.exists(os.path.join(os.getcwd(), "setup.py"))) + self.assertFalse(self.backend.exists(os.path.join(os.getcwd(), "stup.py"))) def test_save(self): with tempfile.NamedTemporaryFile() as tmp_file: - self.backend.save(tmp_file.name, b'123') + self.backend.save(tmp_file.name, b"123") self.assertTrue(os.path.exists(tmp_file.name)) def test_save_with_missing_dir(self): directory = tempfile.mkdtemp() - filepath = os.path.join(directory, 'test_dir/more_test_dir', 'img.jpg') + filepath = os.path.join(directory, "test_dir/more_test_dir", "img.jpg") try: self.assertFalse(os.path.exists(os.path.dirname(filepath))) - self.backend.save(filepath, b'123') + self.backend.save(filepath, b"123") self.assertTrue(os.path.exists(os.path.dirname(filepath))) finally: shutil.rmtree(directory) diff --git a/tests/utils_tests.py b/tests/utils_tests.py index d2e905d..2ea9924 100644 --- a/tests/utils_tests.py +++ b/tests/utils_tests.py @@ -4,27 +4,32 @@ import unittest -from flask_thumbnails.utils import import_from_string, generate_filename, parse_size, aspect_to_string +from flask_thumbnails.utils import ( + aspect_to_string, + generate_filename, + import_from_string, + parse_size, +) class UtilsTestCase(unittest.TestCase): def test_import_from_string(self): - c = import_from_string('unittest.TestCase') + c = import_from_string("unittest.TestCase") self.assertEqual(c, unittest.TestCase) def test_import_from_string_none(self): with self.assertRaises(ImportError): - import_from_string('django.test.NonModel') + import_from_string("django.test.NonModel") def test_generate_filename(self): - name = generate_filename('test.jpg', '200x200', 'fit', '100') - self.assertEqual(name, 'test_200x200_fit_100.jpg') + name = generate_filename("test.jpg", "200x200", "fit", "100") + self.assertEqual(name, "test_200x200_fit_100.jpg") def test_parse_size(self): - size = parse_size('200x200') + size = parse_size("200x200") self.assertEqual(size, [200, 200]) - size = parse_size('200') + size = parse_size("200") self.assertEqual(size, [200, 200]) size = parse_size(200) @@ -36,21 +41,25 @@ def test_parse_size(self): size = parse_size((200,)) self.assertEqual(size, (200, 200)) - size = parse_size([200, ]) + size = parse_size( + [ + 200, + ] + ) self.assertEqual(size, [200, 200]) with self.assertRaises(ValueError): - parse_size('this_is_invalid') + parse_size("this_is_invalid") with self.assertRaises(ValueError): - parse_size('') + parse_size("") def test_aspect_to_string(self): - size = aspect_to_string('200x200') - self.assertEqual(size, '200x200') + size = aspect_to_string("200x200") + self.assertEqual(size, "200x200") size = aspect_to_string([200, 200]) - self.assertEqual(size, '200x200') + self.assertEqual(size, "200x200") size = aspect_to_string((200, 200)) - self.assertEqual(size, '200x200') + self.assertEqual(size, "200x200")