Skip to content

Commit

Permalink
Merge branch 'release/5.0'
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
vstoykov committed Sep 28, 2023
2 parents 7105292 + 07f2b3c commit 697b27f
Show file tree
Hide file tree
Showing 48 changed files with 454 additions and 575 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10']
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']

steps:
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion docs/_themes/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ krTheme Sphinx Style
====================

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

Expand Down
5 changes: 3 additions & 2 deletions docs/_themes/flask_theme_support.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
from pygments.token import (Comment, Error, Generic, Keyword, Literal, Name,
Number, Operator, Other, Punctuation, String,
Whitespace)


class FlaskyStyle(Style):
Expand Down
6 changes: 3 additions & 3 deletions docs/caching.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ differently and for example do not cache values if timeout is ``None``.
If you clear your cache durring deployment or some other reason probably
you do not want to lose the cache for generated images especcialy if you
are using some slow remote storage (like Amazon S3). Then you can configure
seprate cache (for example redis) in your ``CACHES`` config and tell ImageKit
separate cache (for example redis) in your ``CACHES`` config and tell ImageKit
to use it instead of the default cache by setting ``IMAGEKIT_CACHE_BACKEND``.


Expand Down Expand Up @@ -180,7 +180,7 @@ Or, in Python:

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

For those who aren't willing to accept that cost (and who never want ImageKit
to generate images in the request-responce cycle), there's the "optimistic"
to generate images in the request-response cycle), there's the "optimistic"
cache file strategy. This strategy only generates a new image when a spec's
source image is created or changed. Unlike with the "just in time" strategy,
accessing the file won't cause it to be generated, ImageKit will just assume
Expand Down
19 changes: 10 additions & 9 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# ImageKit documentation build configuration file, created by
# sphinx-quickstart on Sun Sep 25 17:05:55 2011.
Expand All @@ -11,7 +10,9 @@
# All configuration values have a default; values that are commented out
# serve to show the default.

import re, sys, os
import os
import re
import sys

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

# General information about the project.
project = u'ImageKit'
copyright = u'2011, Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter'
project = 'ImageKit'
copyright = '2011, Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter'

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

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

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

Expand Down
13 changes: 12 additions & 1 deletion docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,23 @@ Settings

:default: ``None``

Starting with Django 4.2, if you defined ``settings.STORAGES``:
the Django storage backend alias to retrieve the storage instance defined
in your settings, as described in the `Django file storage documentation`_.
If no value is provided for ``IMAGEKIT_DEFAULT_FILE_STORAGE``,
and none is specified by the spec definition, the ``default`` file storage
will be used.

Before Django 4.2, or if ``settings.STORAGES`` is not defined:
The qualified class name of a Django storage backend to use to save the
cached images. If no value is provided for ``IMAGEKIT_DEFAULT_FILE_STORAGE``,
and none is specified by the spec definition, `your default file storage`__
will be used.


.. _`Django file storage documentation`: https://docs.djangoproject.com/en/dev/ref/files/storage/


.. attribute:: IMAGEKIT_DEFAULT_CACHEFILE_BACKEND

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

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


.. attribute:: IMAGEKIT_CACHE_TIMEOUT
Expand Down
11 changes: 7 additions & 4 deletions imagekit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# flake8: noqa
from . import conf
from . import generatorlibrary
from .specs import ImageSpec
from . import conf, generatorlibrary
from .pkgmeta import *
from .registry import register, unregister
from .specs import ImageSpec

__all__ = [
'ImageSpec', 'conf', 'generatorlibrary', 'register', 'unregister',
'__title__', '__author__', '__version__', '__license__'
]
10 changes: 2 additions & 8 deletions imagekit/admin.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
from django import VERSION
if VERSION[0] < 2:
# ugettext is an alias for gettext() since Django 2.0,
# and deprecated as of Django 3.0.
from django.utils.translation import ugettext_lazy as _
else:
from django.utils.translation import gettext_lazy as _
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _


class AdminThumbnail(object):
class AdminThumbnail:
"""
A convenience utility for adding thumbnails to Django's admin change list.
Expand Down
41 changes: 21 additions & 20 deletions imagekit/cachefiles/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import os.path
from copy import copy

from django.conf import settings
from django.core.files import File
from django.core.files.images import ImageFile
from django.utils.functional import SimpleLazyObject
from django.utils.encoding import smart_str
from django.utils.functional import SimpleLazyObject

from ..files import BaseIKFile
from ..registry import generator_registry
from ..signals import content_required, existence_required
from ..utils import get_logger, get_singleton, generate, get_by_qname
from ..utils import (
generate, get_by_qname, get_logger, get_singleton, get_storage
)


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

storage = (callable(storage) and storage()) or storage or \
getattr(generator, 'cachefile_storage', None) or get_singleton(
settings.IMAGEKIT_DEFAULT_FILE_STORAGE, 'file storage backend')
getattr(generator, 'cachefile_storage', None) or get_storage()
self.cachefile_backend = (
cachefile_backend
or getattr(generator, 'cachefile_backend', None)
Expand All @@ -55,7 +59,7 @@ def __init__(self, generator, name=None, storage=None, cachefile_backend=None, c
'cache file strategy')
)

super(ImageCacheFile, self).__init__(storage=storage)
super().__init__(storage=storage)

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

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

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

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

# remove storage from state as some non-FileSystemStorage can't be
# pickled
settings_storage = get_singleton(
settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
'file storage backend'
)
settings_storage = get_storage()
if state['storage'] == settings_storage:
state.pop('storage')
return state

def __setstate__(self, state):
if 'storage' not in state:
state['storage'] = get_singleton(
settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
'file storage backend'
)
state['storage'] = get_storage()
self.__dict__.update(state)

def __nonzero__(self):
# Python 2 compatibility
return self.__bool__()

def __repr__(self):
return smart_str("<%s: %s>" % (
self.__class__.__name__, self if self.name else "None")
Expand All @@ -177,7 +178,7 @@ def __init__(self, generator_id, *args, **kwargs):
def setup():
generator = generator_registry.get(generator_id, *args, **kwargs)
return ImageCacheFile(generator)
super(LazyImageCacheFile, self).__init__(setup)
super().__init__(setup)

def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, str(self) or 'None')
20 changes: 11 additions & 9 deletions imagekit/cachefiles/backends.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from ..utils import get_singleton, get_cache, sanitize_cache_key
import warnings
from copy import copy
from django.core.exceptions import ImproperlyConfigured

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

from ..utils import get_cache, get_singleton, sanitize_cache_key


class CacheFileState(object):
class CacheFileState:
EXISTS = 'exists'
GENERATING = 'generating'
DOES_NOT_EXIST = 'does_not_exist'
Expand All @@ -25,7 +27,7 @@ class InvalidFileBackendError(ImproperlyConfigured):
pass


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


class CachedFileBackend(object):
class CachedFileBackend:
existence_check_timeout = 5
"""
The number of seconds to wait before rechecking to see if the file exists.
Expand Down Expand Up @@ -157,7 +159,7 @@ def __init__(self, *args, **kwargs):
except ImportError:
raise ImproperlyConfigured('You must install celery to use'
' imagekit.cachefiles.backends.Celery.')
super(Celery, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)

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


try:
Expand All @@ -189,7 +191,7 @@ def __init__(self, *args, **kwargs):
except ImportError:
raise ImproperlyConfigured('You must install django-rq to use'
' imagekit.cachefiles.backends.RQ.')
super(RQ, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)

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

def schedule_generation(self, file, force=False):
_dramatiq_actor.send(self, file, force=force)
4 changes: 3 additions & 1 deletion imagekit/cachefiles/namers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
"""

from django.conf import settings
import os

from django.conf import settings

from ..utils import format_to_extension, suggest_extension


Expand Down
Loading

0 comments on commit 697b27f

Please sign in to comment.