Skip to content

Commit 697b27f

Browse files
committed
Merge branch 'release/5.0'
* release/5.0: Bump version to 5.0.0 Update setup.py Refactor how we get the default storage class Convert the anchor kwarg from SafeString to str. Add tests for new Django 4.2 settings Fix github workflow python versions Use proper envlist matrix Upgrade project to latest versions of python and django docs: Fix a few typos Fix equality checking of paths by normalizing them Fix: only seek files when possible Properly close files after finish using them Use yield from Remove u'' prefix Update setup.py Remove workaround Small whitespace and line length fixes Prefer not in Do not call getattr with a constant attribute value, it is not any safer than normal property access. Use dict/set literals/comprehensions Use Python 3 style classes and super calls Clean up imports Use @register.tag decorator Remove Python 2 compat method Remove _autodiscover_modules_fallback Use the parse_bits implementation from Django Remove usage of six Remove lib module Remove lib.StringIO Remove lib.NullHandler in favor of using logging.NullHandler directly Remove lib.smart_text / lib.force_text / lib.force_bytes Remove compatibility with South Remove __future__ imports Remove fallback imports Remove fallback settings Only test on currently supported versions of Python and Django
2 parents 7105292 + 07f2b3c commit 697b27f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+454
-575
lines changed

.github/workflows/python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
max-parallel: 4
1515
matrix:
16-
python-version: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10']
16+
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
1717

1818
steps:
1919
- uses: actions/checkout@v2

docs/_themes/README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ krTheme Sphinx Style
22
====================
33

44
This repository contains sphinx styles Kenneth Reitz uses in most of
5-
his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related
5+
his projects. It is a derivative of Mitsuhiko's themes for Flask and Flask related
66
projects. To use this style in your Sphinx documentation, follow
77
this guide:
88

docs/_themes/flask_theme_support.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# flasky extensions. flasky pygments style based on tango style
22
from pygments.style import Style
3-
from pygments.token import Keyword, Name, Comment, String, Error, \
4-
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
3+
from pygments.token import (Comment, Error, Generic, Keyword, Literal, Name,
4+
Number, Operator, Other, Punctuation, String,
5+
Whitespace)
56

67

78
class FlaskyStyle(Style):

docs/caching.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ differently and for example do not cache values if timeout is ``None``.
117117
If you clear your cache durring deployment or some other reason probably
118118
you do not want to lose the cache for generated images especcialy if you
119119
are using some slow remote storage (like Amazon S3). Then you can configure
120-
seprate cache (for example redis) in your ``CACHES`` config and tell ImageKit
120+
separate cache (for example redis) in your ``CACHES`` config and tell ImageKit
121121
to use it instead of the default cache by setting ``IMAGEKIT_CACHE_BACKEND``.
122122

123123

@@ -180,7 +180,7 @@ Or, in Python:
180180

181181
If you are using an "async" backend in combination with the "optimistic"
182182
cache file strategy (see `Removing Safeguards`_ below), checking for
183-
thruthiness as described above will not work. The "optimistic" backend is
183+
truthiness as described above will not work. The "optimistic" backend is
184184
very optimistic so to say, and removes the check. Create and use the
185185
following strategy to a) have images only created on save, and b) retain
186186
the ability to check whether the images have already been created::
@@ -211,7 +211,7 @@ operation. However, if the state isn't cached, ImageKit will need to query the
211211
storage backend.
212212

213213
For those who aren't willing to accept that cost (and who never want ImageKit
214-
to generate images in the request-responce cycle), there's the "optimistic"
214+
to generate images in the request-response cycle), there's the "optimistic"
215215
cache file strategy. This strategy only generates a new image when a spec's
216216
source image is created or changed. Unlike with the "just in time" strategy,
217217
accessing the file won't cause it to be generated, ImageKit will just assume

docs/conf.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
#
32
# ImageKit documentation build configuration file, created by
43
# sphinx-quickstart on Sun Sep 25 17:05:55 2011.
@@ -11,7 +10,9 @@
1110
# All configuration values have a default; values that are commented out
1211
# serve to show the default.
1312

14-
import re, sys, os
13+
import os
14+
import re
15+
import sys
1516

1617
# If extensions (or modules to document with autodoc) are in another directory,
1718
# add these directories to sys.path here. If the directory is relative to the
@@ -42,8 +43,8 @@
4243
master_doc = 'index'
4344

4445
# General information about the project.
45-
project = u'ImageKit'
46-
copyright = u'2011, Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter'
46+
project = 'ImageKit'
47+
copyright = '2011, Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter'
4748

4849
pkgmeta = {}
4950
execfile(os.path.join(os.path.dirname(__file__), '..', 'imagekit',
@@ -189,8 +190,8 @@
189190
# Grouping the document tree into LaTeX files. List of tuples
190191
# (source start file, target name, title, author, documentclass [howto/manual]).
191192
latex_documents = [
192-
('index', 'ImageKit.tex', u'ImageKit Documentation',
193-
u'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett \\& Matthew Tretter', 'manual'),
193+
('index', 'ImageKit.tex', 'ImageKit Documentation',
194+
'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett \\& Matthew Tretter', 'manual'),
194195
]
195196

196197
# The name of an image file (relative to this directory) to place at the top of
@@ -219,8 +220,8 @@
219220
# One entry per manual page. List of tuples
220221
# (source start file, name, description, authors, manual section).
221222
man_pages = [
222-
('index', 'imagekit', u'ImageKit Documentation',
223-
[u'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter'], 1)
223+
('index', 'imagekit', 'ImageKit Documentation',
224+
['Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter'], 1)
224225
]
225226

226227
# If true, show URL addresses after external links.
@@ -233,7 +234,7 @@
233234
# (source start file, target name, title, author,
234235
# dir menu entry, description, category)
235236
texinfo_documents = [
236-
('index', 'ImageKit', u'ImageKit Documentation', u'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter',
237+
('index', 'ImageKit', 'ImageKit Documentation', 'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter',
237238
'ImageKit', 'One line description of project.', 'Miscellaneous'),
238239
]
239240

docs/configuration.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,23 @@ Settings
2121

2222
:default: ``None``
2323

24+
Starting with Django 4.2, if you defined ``settings.STORAGES``:
25+
the Django storage backend alias to retrieve the storage instance defined
26+
in your settings, as described in the `Django file storage documentation`_.
27+
If no value is provided for ``IMAGEKIT_DEFAULT_FILE_STORAGE``,
28+
and none is specified by the spec definition, the ``default`` file storage
29+
will be used.
30+
31+
Before Django 4.2, or if ``settings.STORAGES`` is not defined:
2432
The qualified class name of a Django storage backend to use to save the
2533
cached images. If no value is provided for ``IMAGEKIT_DEFAULT_FILE_STORAGE``,
2634
and none is specified by the spec definition, `your default file storage`__
2735
will be used.
2836

2937

38+
.. _`Django file storage documentation`: https://docs.djangoproject.com/en/dev/ref/files/storage/
39+
40+
3041
.. attribute:: IMAGEKIT_DEFAULT_CACHEFILE_BACKEND
3142

3243
:default: ``'imagekit.cachefiles.backends.Simple'``
@@ -52,7 +63,7 @@ Settings
5263
The cache is then used to store information like the state of cached
5364
images (i.e. validated or not).
5465

55-
.. _`Django cache section`: https://docs.djangoproject.com/en/1.8/topics/cache/#accessing-the-cache
66+
.. _`Django cache section`: https://docs.djangoproject.com/en/dev/topics/cache/#accessing-the-cache
5667

5768

5869
.. attribute:: IMAGEKIT_CACHE_TIMEOUT

imagekit/__init__.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
# flake8: noqa
2-
from . import conf
3-
from . import generatorlibrary
4-
from .specs import ImageSpec
1+
from . import conf, generatorlibrary
52
from .pkgmeta import *
63
from .registry import register, unregister
4+
from .specs import ImageSpec
5+
6+
__all__ = [
7+
'ImageSpec', 'conf', 'generatorlibrary', 'register', 'unregister',
8+
'__title__', '__author__', '__version__', '__license__'
9+
]

imagekit/admin.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
from django import VERSION
2-
if VERSION[0] < 2:
3-
# ugettext is an alias for gettext() since Django 2.0,
4-
# and deprecated as of Django 3.0.
5-
from django.utils.translation import ugettext_lazy as _
6-
else:
7-
from django.utils.translation import gettext_lazy as _
81
from django.template.loader import render_to_string
2+
from django.utils.translation import gettext_lazy as _
93

104

11-
class AdminThumbnail(object):
5+
class AdminThumbnail:
126
"""
137
A convenience utility for adding thumbnails to Django's admin change list.
148

imagekit/cachefiles/__init__.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1+
import os.path
12
from copy import copy
3+
24
from django.conf import settings
35
from django.core.files import File
46
from django.core.files.images import ImageFile
5-
from django.utils.functional import SimpleLazyObject
67
from django.utils.encoding import smart_str
8+
from django.utils.functional import SimpleLazyObject
9+
710
from ..files import BaseIKFile
811
from ..registry import generator_registry
912
from ..signals import content_required, existence_required
10-
from ..utils import get_logger, get_singleton, generate, get_by_qname
13+
from ..utils import (
14+
generate, get_by_qname, get_logger, get_singleton, get_storage
15+
)
1116

1217

1318
class ImageCacheFile(BaseIKFile, ImageFile):
@@ -41,8 +46,7 @@ def __init__(self, generator, name=None, storage=None, cachefile_backend=None, c
4146
self.name = name
4247

4348
storage = (callable(storage) and storage()) or storage or \
44-
getattr(generator, 'cachefile_storage', None) or get_singleton(
45-
settings.IMAGEKIT_DEFAULT_FILE_STORAGE, 'file storage backend')
49+
getattr(generator, 'cachefile_storage', None) or get_storage()
4650
self.cachefile_backend = (
4751
cachefile_backend
4852
or getattr(generator, 'cachefile_backend', None)
@@ -55,7 +59,7 @@ def __init__(self, generator, name=None, storage=None, cachefile_backend=None, c
5559
'cache file strategy')
5660
)
5761

58-
super(ImageCacheFile, self).__init__(storage=storage)
62+
super().__init__(storage=storage)
5963

6064
def _require_file(self):
6165
if getattr(self, '_file', None) is None:
@@ -100,15 +104,22 @@ def _generate(self):
100104
actual_name = self.storage.save(self.name, content)
101105

102106
# We're going to reuse the generated file, so we need to reset the pointer.
103-
content.seek(0)
107+
if not hasattr(content, "seekable") or content.seekable():
108+
content.seek(0)
104109

105110
# Store the generated file. If we don't do this, the next time the
106111
# "file" attribute is accessed, it will result in a call to the storage
107112
# backend (in ``BaseIKFile._get_file``). Since we already have the
108113
# contents of the file, what would the point of that be?
109114
self.file = File(content)
110115

111-
if actual_name != self.name:
116+
# ``actual_name`` holds the output of ``self.storage.save()`` that
117+
# by default returns filenames with forward slashes, even on windows.
118+
# On the other hand, ``self.name`` holds OS-specific paths results
119+
# from applying path normalizers like ``os.path.normpath()`` in the
120+
# ``namer``. So, the filenames should be normalized before their
121+
# equality checking.
122+
if os.path.normpath(actual_name) != os.path.normpath(self.name):
112123
get_logger().warning(
113124
'The storage backend %s did not save the file with the'
114125
' requested name ("%s") and instead used "%s". This may be'
@@ -146,26 +157,16 @@ def __getstate__(self):
146157

147158
# remove storage from state as some non-FileSystemStorage can't be
148159
# pickled
149-
settings_storage = get_singleton(
150-
settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
151-
'file storage backend'
152-
)
160+
settings_storage = get_storage()
153161
if state['storage'] == settings_storage:
154162
state.pop('storage')
155163
return state
156164

157165
def __setstate__(self, state):
158166
if 'storage' not in state:
159-
state['storage'] = get_singleton(
160-
settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
161-
'file storage backend'
162-
)
167+
state['storage'] = get_storage()
163168
self.__dict__.update(state)
164169

165-
def __nonzero__(self):
166-
# Python 2 compatibility
167-
return self.__bool__()
168-
169170
def __repr__(self):
170171
return smart_str("<%s: %s>" % (
171172
self.__class__.__name__, self if self.name else "None")
@@ -177,7 +178,7 @@ def __init__(self, generator_id, *args, **kwargs):
177178
def setup():
178179
generator = generator_registry.get(generator_id, *args, **kwargs)
179180
return ImageCacheFile(generator)
180-
super(LazyImageCacheFile, self).__init__(setup)
181+
super().__init__(setup)
181182

182183
def __repr__(self):
183184
return '<%s: %s>' % (self.__class__.__name__, str(self) or 'None')

imagekit/cachefiles/backends.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
from ..utils import get_singleton, get_cache, sanitize_cache_key
21
import warnings
32
from copy import copy
4-
from django.core.exceptions import ImproperlyConfigured
3+
54
from django.conf import settings
5+
from django.core.exceptions import ImproperlyConfigured
6+
7+
from ..utils import get_cache, get_singleton, sanitize_cache_key
68

79

8-
class CacheFileState(object):
10+
class CacheFileState:
911
EXISTS = 'exists'
1012
GENERATING = 'generating'
1113
DOES_NOT_EXIST = 'does_not_exist'
@@ -25,7 +27,7 @@ class InvalidFileBackendError(ImproperlyConfigured):
2527
pass
2628

2729

28-
class AbstractCacheFileBackend(object):
30+
class AbstractCacheFileBackend:
2931
"""
3032
An abstract cache file backend. This isn't used by any internal classes and
3133
is included simply to illustrate the minimum interface of a cache file
@@ -39,7 +41,7 @@ def exists(self, file):
3941
raise NotImplementedError
4042

4143

42-
class CachedFileBackend(object):
44+
class CachedFileBackend:
4345
existence_check_timeout = 5
4446
"""
4547
The number of seconds to wait before rechecking to see if the file exists.
@@ -157,7 +159,7 @@ def __init__(self, *args, **kwargs):
157159
except ImportError:
158160
raise ImproperlyConfigured('You must install celery to use'
159161
' imagekit.cachefiles.backends.Celery.')
160-
super(Celery, self).__init__(*args, **kwargs)
162+
super().__init__(*args, **kwargs)
161163

162164
def schedule_generation(self, file, force=False):
163165
_celery_task.delay(self, file, force=force)
@@ -168,7 +170,7 @@ class Async(Celery):
168170
def __init__(self, *args, **kwargs):
169171
message = '{path}.Async is deprecated. Use {path}.Celery instead.'
170172
warnings.warn(message.format(path=__name__), DeprecationWarning)
171-
super(Async, self).__init__(*args, **kwargs)
173+
super().__init__(*args, **kwargs)
172174

173175

174176
try:
@@ -189,7 +191,7 @@ def __init__(self, *args, **kwargs):
189191
except ImportError:
190192
raise ImproperlyConfigured('You must install django-rq to use'
191193
' imagekit.cachefiles.backends.RQ.')
192-
super(RQ, self).__init__(*args, **kwargs)
194+
super().__init__(*args, **kwargs)
193195

194196
def schedule_generation(self, file, force=False):
195197
_rq_job.delay(self, file, force=force)
@@ -213,7 +215,7 @@ def __init__(self, *args, **kwargs):
213215
except ImportError:
214216
raise ImproperlyConfigured('You must install django-dramatiq to use'
215217
' imagekit.cachefiles.backends.Dramatiq.')
216-
super(Dramatiq, self).__init__(*args, **kwargs)
218+
super().__init__(*args, **kwargs)
217219

218220
def schedule_generation(self, file, force=False):
219221
_dramatiq_actor.send(self, file, force=force)

0 commit comments

Comments
 (0)