Split before_access into two signals

Differentiating between when the generated file content is required and
when the generated file is just required to exist gives us more
flexibility with strategies.
This commit is contained in:
Matthew Tretter 2013-05-10 04:39:46 -04:00
parent 6db082bca2
commit c89b18aa95
6 changed files with 64 additions and 18 deletions

View file

@ -35,18 +35,25 @@ Cache File Strategy
Each ``ImageCacheFile`` has a cache file strategy, which abstracts away when
image is actually generated. It can implement the following four methods:
* ``before_access`` - called by ``ImageCacheFile`` when you access its url,
width, or height attribute.
* ``on_content_required`` - called by ``ImageCacheFile`` when it requires the
contents of the generated image. For example, when you call ``read()`` or
try to access information contained in the file.
* ``on_existence_required`` - called by ``ImageCacheFile`` when it requires the
generated image to exist but may not be concerned with its contents. For
example, when you access its ``url`` or ``path`` attribute.
* ``on_source_created`` - called when the source of a spec is created
* ``on_source_changed`` - called when the source of a spec is changed
* ``on_source_deleted`` - called when the source of a spec is deleted
The default strategy only defines the first of these, as follows:
The default strategy only defines the first two of these, as follows:
.. code-block:: python
class JustInTime(object):
def before_access(self, file):
def on_content_required(self, file):
file.generate()
def on_existence_required(self, file):
file.generate()

View file

@ -4,7 +4,7 @@ from django.core.files.images import ImageFile
from django.utils.functional import LazyObject
from ..files import BaseIKFile
from ..registry import generator_registry
from ..signals import before_access
from ..signals import content_required, existence_required
from ..utils import get_logger, get_singleton, generate, get_by_qname
@ -57,7 +57,30 @@ class ImageCacheFile(BaseIKFile, ImageFile):
def _require_file(self):
if not getattr(self, '_file', None):
before_access.send(sender=self, file=self)
content_required.send(sender=self, file=self)
self._file = self.storage.open(self.name, 'rb')
# The ``path`` and ``url`` properties are overridden so as to not call
# ``_require_file``, which is only meant to be called when the file object
# will be directly interacted with (e.g. when using ``read()``). These only
# require the file to exist; they do not need its contents to work. This
# distinction gives the user the flexibility to create a cache file
# strategy that assumes the existence of a file, but can still make the file
# available when its contents are required.
def _storage_attr(self, attr):
if not getattr(self, '_file', None):
existence_required.send(sender=self, file=self)
fn = getattr(self.storage, attr)
return fn(self.name)
@property
def path(self):
return self._storage_attr('path')
@property
def url(self):
return self._storage_attr('url')
def generate(self, force=False):
"""
@ -101,9 +124,9 @@ class ImageCacheFile(BaseIKFile, ImageFile):
if not self.name:
return False
# Dispatch the before_access signal before checking to see if the file
# exists. This gives the strategy a chance to create the file.
before_access.send(sender=self, file=self)
# Dispatch the existence_required signal before checking to see if the
# file exists. This gives the strategy a chance to create the file.
existence_required.send(sender=self, file=self)
return self.cachefile_backend.exists(self)

View file

@ -8,7 +8,10 @@ class JustInTime(object):
"""
def before_access(self, file):
def on_existence_required(self, file):
file.generate()
def on_content_required(self, file):
file.generate()

View file

@ -1,5 +1,6 @@
from .exceptions import AlreadyRegistered, NotRegistered
from .signals import before_access, source_created, source_changed, source_deleted
from .signals import (content_required, existence_required, source_created,
source_changed, source_deleted)
from .utils import call_strategy_method
@ -12,7 +13,8 @@ class GeneratorRegistry(object):
"""
def __init__(self):
self._generators = {}
before_access.connect(self.before_access_receiver)
content_required.connect(self.content_required_receiver)
existence_required.connect(self.existence_required_receiver)
def register(self, id, generator):
registered_generator = self._generators.get(id)
@ -42,13 +44,19 @@ class GeneratorRegistry(object):
def get_ids(self):
return self._generators.keys()
def before_access_receiver(self, sender, file, **kwargs):
def content_required_receiver(self, sender, file, **kwargs):
self._receive(file, 'on_content_required')
def existence_required_receiver(self, sender, file, **kwargs):
self._receive(file, 'on_existence_required')
def _receive(self, file, callback):
generator = file.generator
# FIXME: I guess this means you can't register functions?
if generator.__class__ in self._generators.values():
# Only invoke the strategy method for registered generators.
call_strategy_method(file, 'before_access')
call_strategy_method(file, callback)
class SourceGroupRegistry(object):

View file

@ -2,7 +2,8 @@ from django.dispatch import Signal
# Generated file signals
before_access = Signal()
content_required = Signal()
existence_required = Signal()
# Source group signals
source_created = Signal()

View file

@ -29,12 +29,16 @@ class ProcessedImageFieldModel(models.Model):
class CountingCacheFileStrategy(object):
def __init__(self):
self.before_access_count = 0
self.on_existence_required_count = 0
self.on_content_required_count = 0
self.on_source_changed_count = 0
self.on_source_created_count = 0
def before_access(self, file):
self.before_access_count += 1
def on_existence_required(self, file):
self.on_existence_required_count += 1
def on_content_required(self, file):
self.on_content_required_count += 1
def on_source_changed(self, file):
self.on_source_changed_count += 1