Skip to content

Commit

Permalink
Merge pull request #32 from Starou/drop-python-2-support
Browse files Browse the repository at this point in the history
Drop python 2 support
  • Loading branch information
Starou committed Mar 8, 2022
2 parents 59c4c2f + ebca808 commit 29579ad
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/runtests.yml
Expand Up @@ -14,7 +14,7 @@ jobs:
max-parallel: 4
matrix:
python-version: [3.7, 3.8, 3.9]
django-version: [3.0.14, 3.1.14]
django-version: [2.1.15, 3.0.14, 3.1.14]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand Down
32 changes: 20 additions & 12 deletions README.rst
Expand Up @@ -8,12 +8,14 @@ django-thumborstorage
.. image:: https://img.shields.io/pypi/v/django-thumborstorage.svg
:target: https://pypi.python.org/pypi/django-thumborstorage

A Django custom storage for Thumbor backend.
A Django custom storage for Thumbor.

This app provide 2 classes ``ThumborStorage`` and ``ThumborMigrationStorage``. The last one
is a storage you can use for ``Imagefield`` initialy using a ``FileSystemStorage`` you want
to migrate to Thumbor without batch-moving all of them. That way, Django continues to serve
them from the file system until you change the image on that field.
Provides 2 custom storages classes: ``ThumborStorage`` and ``ThumborMigrationStorage``.

Use ``ThumborMigrationStorage`` on an ``Imagefield`` that started with a classic
``FileSystemStorage`` you want to upgrade to Thumbor without migrating your old
media. That way, Django continues to serve them from the file system until the
image is changed.

Install
=======
Expand All @@ -25,9 +27,8 @@ Install
Dependencies
''''''''''''

* Python 2.7
* Python 3.5+
* Django 1.11
* Python 3.6+
* Django 2.1 to 3.1
* Requests_
* Libthumbor_

Expand All @@ -42,17 +43,17 @@ Usage
settings.py
'''''''''''

Add ``django_thumborstorage`` in your ``INSTALLED_APPS``.
Add ``django_thumborstorage`` in ``INSTALLED_APPS``.

And set the following:
And set the following with your values:

.. code-block:: python
THUMBOR_SERVER = 'http://localhost:8888'
THUMBOR_SERVER = 'https://my.thumbor.server.com:8888'
THUMBOR_SECURITY_KEY = 'MY_SECURE_KEY'
# This may be a different host than THUMBOR_SERVER
# only reachable by your Django server.
THUMBOR_RW_SERVER = 'http://localhost:8888'
THUMBOR_RW_SERVER = 'https://my.rw.thumbor.server.local:8888'
models.py
'''''''''
Expand Down Expand Up @@ -88,6 +89,13 @@ you can pass the key as url parameter.
CHANGELOG
=========

1.13.0
''''''

* Drop support for Django < 2.1 and Python 2.7, 3.4 and 3.5
* Use GitHub actions for CI instead of Travis.


1.11.0
''''''

Expand Down
2 changes: 1 addition & 1 deletion django_thumborstorage/exceptions.py
Expand Up @@ -14,7 +14,7 @@ class ThumborPostException(DjangoThumborStorageException):
_error = None

def __init__(self, response):
self._error = "%d - %s" % (response.status_code, response.reason)
self._error = f"{response.status_code} - {response.reason}"

def __str__(self):
return repr(self._error)
64 changes: 24 additions & 40 deletions django_thumborstorage/storages.py
@@ -1,28 +1,20 @@
import mimetypes
import os
import re

from io import BytesIO
from urllib.parse import quote, unquote

import requests

from libthumbor import CryptoURL
from requests.packages.urllib3.exceptions import LocationParseError
from io import BytesIO
from django.conf import settings
from django.core.files.images import ImageFile
from django.core.files.storage import Storage, FileSystemStorage
from django.utils.deconstruct import deconstructible
from . import exceptions

# Python 3
try:
from urllib.parse import quote, unquote
except ImportError:
from urllib import quote, unquote

# Django < 1.7
try:
from django.utils.deconstruct import deconstructible
except ImportError:
def deconstructible(cls):
return cls


# Match 'key', 'key/filename.ext' and 'key.ext'.
THUMBOR_PATH_PATTERN = r"^/image/(?P<key>\w{32})(?:(/|\.).*){0,1}$"
Expand All @@ -40,7 +32,7 @@ def write(self, *args, **kwargs):
image_content = content.file.read()
content.file.seek(0)

url = "%s/image" % settings.THUMBOR_RW_SERVER
url = f"{settings.THUMBOR_RW_SERVER}/image"
headers = {
"Content-Type": mimetypes.guess_type(self.name)[0] or "image/jpeg",
"Slug": quote(
Expand All @@ -54,23 +46,23 @@ def write(self, *args, **kwargs):
self._location = self._location.decode('utf-8')
except AttributeError:
pass
return super(ThumborStorageFile, self).write(image_content)
return super().write(image_content)

def delete(self):
url = "%s%s" % (settings.THUMBOR_RW_SERVER, self.name)
url = f"{settings.THUMBOR_RW_SERVER}{self.name}"
response = requests.delete(url)
if response.status_code == 405:
raise exceptions.MethodNotAllowedException
elif response.status_code == 404:
if response.status_code == 404:
raise exceptions.NotFoundException
elif response.status_code == 204:
if response.status_code == 204:
return

def _get_file(self):
if self._file is None or self._file.closed:
self._file = BytesIO()
if 'r' in self._mode:
url = "%s%s" % (settings.THUMBOR_RW_SERVER, self.name)
url = f"{settings.THUMBOR_RW_SERVER}{self.name}"
response = requests.get(url)
self._file.write(response.content)
self._file.seek(0)
Expand All @@ -87,7 +79,7 @@ def size(self):
return self.tell()

def close(self):
super(ThumborStorageFile, self).close()
super().close()
self._file = None


Expand Down Expand Up @@ -120,8 +112,7 @@ def exists(self, name):
if re.match(THUMBOR_PATH_PATTERN, name):
return thumbor_original_exists(thumbor_original_image_url(name))
# name as defined in 'upload_to' > new image.
else:
return False
return False

def size(self, name):
f = self.open(name)
Expand Down Expand Up @@ -170,38 +161,32 @@ def __init__(self, **kwargs):
def _open(self, name, mode='rb'):
if self.is_thumbor(name):
return ThumborStorage._open(self, name, mode)
else:
return FileSystemStorage._open(self, name, mode)
return FileSystemStorage._open(self, name, mode)

def delete(self, name):
if self.is_thumbor(name):
return ThumborStorage.delete(self, name)
else:
return FileSystemStorage.delete(self, name)
return FileSystemStorage.delete(self, name)

def exists(self, name):
if self.is_thumbor(name):
return ThumborStorage.exists(self, name)
else:
return FileSystemStorage.exists(self, name)
return FileSystemStorage.exists(self, name)

def url(self, name):
if self.is_thumbor(name):
return ThumborStorage.url(self, name)
else:
return FileSystemStorage.url(self, name)
return FileSystemStorage.url(self, name)

def key(self, name):
if self.is_thumbor(name):
return ThumborStorage.key(self, name)
else:
raise NotImplementedError
raise NotImplementedError

def path(self, name):
if self.is_thumbor(name):
return ThumborStorage.path(self, name)
else:
return FileSystemStorage.path(self, name)
return FileSystemStorage.path(self, name)

def is_thumbor(self, name):
return re.match(THUMBOR_PATH_PATTERN, name)
Expand All @@ -218,8 +203,7 @@ def thumbor_original_exists(url):
return False
if response.status_code == 200:
return True
else:
return False
return False


# These functions because some methods in ThumborStorage may be called with
Expand All @@ -228,17 +212,17 @@ def thumbor_original_exists(url):

def thumbor_image_url(key):
crypto = CryptoURL(key=settings.THUMBOR_SECURITY_KEY)
return "%s%s" % (settings.THUMBOR_SERVER, crypto.generate(image_url=key))
return f"{settings.THUMBOR_SERVER}{crypto.generate(image_url=key)}"


def thumbor_original_image_url(name):
return "%s%s" % (settings.THUMBOR_RW_SERVER, name)
return f"{settings.THUMBOR_RW_SERVER}{name}"


# Utils

def readonly_to_rw_url(readonly_url):
matches = re.match(r"^%s/(?P<secu>[\w\-=]{28})/(?P<key>\w{32})(?P<extra>(?:.*))$" %
re.escape(settings.THUMBOR_SERVER), readonly_url).groupdict()
name = "/image/%s%s" % (matches["key"], matches["extra"])
name = f"/image/{matches['key']}{matches['extra']}"
return thumbor_original_image_url(name)
11 changes: 4 additions & 7 deletions setup.py
@@ -1,9 +1,6 @@
import os
from setuptools import setup

# Python 2.7
from io import open

with open(os.path.join(os.path.dirname(__file__), 'README.rst'), encoding='utf-8') as f:
README = f.read()

Expand All @@ -12,7 +9,7 @@

setup(
name='django-thumborstorage',
version='1.12.0',
version='1.13.0',
license='MIT Licence',
author='Stanislas Guerra',
author_email='stanislas.guerra@gmail.com',
Expand All @@ -32,11 +29,11 @@
'License :: OSI Approved :: BSD 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 :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
Expand Down

0 comments on commit 29579ad

Please sign in to comment.