diff --git a/imagekit/generatorlibrary.py b/imagekit/generatorlibrary.py index bd9da9a..977e960 100644 --- a/imagekit/generatorlibrary.py +++ b/imagekit/generatorlibrary.py @@ -10,4 +10,4 @@ class Thumbnail(ImageSpec): super(Thumbnail, self).__init__(**kwargs) -register.spec('ik:thumbnail', Thumbnail) +register.generator('ik:thumbnail', Thumbnail) diff --git a/imagekit/imagecache/strategies.py b/imagekit/imagecache/strategies.py index 630b77c..a0b8f76 100644 --- a/imagekit/imagecache/strategies.py +++ b/imagekit/imagecache/strategies.py @@ -14,19 +14,19 @@ class JustInTime(object): class Optimistic(object): """ - A caching strategy that acts immediately when the source file chages and - assumes that the cache files will not be removed (i.e. doesn't revalidate - on access). + A caching strategy that acts immediately when the cacheable file changes + and assumes that the cache files will not be removed (i.e. doesn't + revalidate on access). """ - def on_source_created(self, file): + def on_cacheable_created(self, file): validate_now(file) - def on_source_deleted(self, file): + def on_cacheable_deleted(self, file): clear_now(file) - def on_source_changed(self, file): + def on_cacheable_changed(self, file): validate_now(file) diff --git a/imagekit/management/commands/warmimagecache.py b/imagekit/management/commands/warmimagecache.py index 6c9822d..42a005d 100644 --- a/imagekit/management/commands/warmimagecache.py +++ b/imagekit/management/commands/warmimagecache.py @@ -1,35 +1,35 @@ from django.core.management.base import BaseCommand import re from ...files import GeneratedImageCacheFile -from ...registry import generator_registry, source_group_registry +from ...registry import generator_registry, cacheable_registry class Command(BaseCommand): - help = ('Warm the image cache for the specified specs (or all specs if none' - ' was provided). Simple wildcard matching (using asterisks) is' - ' supported.') - args = '[spec_ids]' + help = ('Warm the image cache for the specified generators' + ' (or all generators if none was provided).' + ' Simple wildcard matching (using asterisks) is supported.') + args = '[generator_ids]' def handle(self, *args, **options): - specs = generator_registry.get_ids() + generators = generator_registry.get_ids() if args: patterns = self.compile_patterns(args) - specs = (id for id in specs if any(p.match(id) for p in patterns)) + generators = (id for id in generators if any(p.match(id) for p in patterns)) - for spec_id in specs: - self.stdout.write('Validating spec: %s\n' % spec_id) - for source_group in source_group_registry.get(spec_id): - for source in source_group.files(): - if source: - spec = generator_registry.get(spec_id, source=source) # TODO: HINTS! (Probably based on source, so this will need to be moved into loop below.) - self.stdout.write(' %s\n' % source) + for generator_id in generators: + self.stdout.write('Validating generator: %s\n' % generator_id) + for cacheables in cacheable_registry.get(generator_id): + for cacheable in cacheables.files(): + if cacheable: + generator = generator_registry.get(generator_id, cacheable=cacheable) # TODO: HINTS! (Probably based on cacheable, so this will need to be moved into loop below.) + self.stdout.write(' %s\n' % cacheable) try: # TODO: Allow other validation actions through command option - GeneratedImageCacheFile(spec).validate() + GeneratedImageCacheFile(generator).validate() except Exception, err: # TODO: How should we handle failures? Don't want to error, but should call it out more than this. self.stdout.write(' FAILED: %s\n' % err) - def compile_patterns(self, spec_ids): - return [re.compile('%s$' % '.*'.join(re.escape(part) for part in id.split('*'))) for id in spec_ids] + def compile_patterns(self, generator_ids): + return [re.compile('%s$' % '.*'.join(re.escape(part) for part in id.split('*'))) for id in generator_ids] diff --git a/imagekit/models/fields/__init__.py b/imagekit/models/fields/__init__.py index 131ff33..449faac 100644 --- a/imagekit/models/fields/__init__.py +++ b/imagekit/models/fields/__init__.py @@ -45,8 +45,8 @@ class ImageSpecField(SpecHostField): self.set_spec_id(cls, name) # Add the model and field as a source for this spec id - register.sources(self.spec_id, - [ImageFieldSourceGroup(cls, self.source)]) + register.cacheables(self.spec_id, + ImageFieldSourceGroup(cls, self.source)) class ProcessedImageField(models.ImageField, SpecHostField): diff --git a/imagekit/registry.py b/imagekit/registry.py index 8d082cd..93e491f 100644 --- a/imagekit/registry.py +++ b/imagekit/registry.py @@ -1,11 +1,11 @@ from .exceptions import AlreadyRegistered, NotRegistered -from .signals import (before_access, source_created, source_changed, - source_deleted) +from .signals import (before_access, cacheable_created, cacheable_changed, + cacheable_deleted) class GeneratorRegistry(object): """ - An object for registering generators (specs). This registry provides + An object for registering generators. This registry provides a convenient way for a distributable app to define default generators without locking the users of the app into it. @@ -15,7 +15,7 @@ class GeneratorRegistry(object): def register(self, id, generator): if id in self._generators: - raise AlreadyRegistered('The spec or generator with id %s is' + raise AlreadyRegistered('The generator with id %s is' ' already registered' % id) self._generators[id] = generator @@ -24,14 +24,14 @@ class GeneratorRegistry(object): try: del self._generators[id] except KeyError: - raise NotRegistered('The spec or generator with id %s is not' + raise NotRegistered('The generator with id %s is not' ' registered' % id) def get(self, id, **kwargs): try: generator = self._generators[id] except KeyError: - raise NotRegistered('The spec or generator with id %s is not' + raise NotRegistered('The generator with id %s is not' ' registered' % id) if callable(generator): return generator(**kwargs) @@ -42,108 +42,114 @@ class GeneratorRegistry(object): return self._generators.keys() -class SourceGroupRegistry(object): +class CacheableRegistry(object): """ - An object for registering source groups with specs. The two are + An object for registering cacheables with generators. The two are associated with each other via a string id. We do this (as opposed to - associating them directly by, for example, putting a ``source_groups`` - attribute on specs) so that specs can be overridden without losing the - associated sources. That way, a distributable app can define its own - specs without locking the users of the app into it. + associating them directly by, for example, putting a ``cacheables`` + attribute on generators) so that generators can be overridden without + losing the associated cacheables. That way, a distributable app can define + its own generators without locking the users of the app into it. """ _signals = [ - source_created, - source_changed, - source_deleted, + cacheable_created, + cacheable_changed, + cacheable_deleted, ] def __init__(self): - self._source_groups = {} + self._cacheables = {} for signal in self._signals: - signal.connect(self.source_group_receiver) + signal.connect(self.cacheable_receiver) before_access.connect(self.before_access_receiver) - def register(self, spec_id, source_groups): + def register(self, generator_id, cacheables): """ - Associates source groups with a spec id + Associates cacheables with a generator id """ - for source_group in source_groups: - if source_group not in self._source_groups: - self._source_groups[source_group] = set() - self._source_groups[source_group].add(spec_id) + for cacheable in cacheables: + if cacheable not in self._cacheables: + self._cacheables[cacheable] = set() + self._cacheables[cacheable].add(generator_id) - def unregister(self, spec_id, source_groups): + def unregister(self, generator_id, cacheables): """ - Disassociates sources with a spec id + Disassociates cacheables with a generator id """ - for source_group in source_groups: + for cacheable in cacheables: try: - self._source_groups[source_group].remove(spec_id) + self._cacheables[cacheable].remove(generator_id) except KeyError: continue - def get(self, spec_id): - return [source_group for source_group in self._source_groups - if spec_id in self._source_groups[source_group]] + def get(self, generator_id): + return [cacheable for cacheable in self._cacheables + if generator_id in self._cacheables[cacheable]] - def before_access_receiver(self, sender, generator, file, **kwargs): - generator.image_cache_strategy.invoke_callback('before_access', file) + def before_access_receiver(self, sender, generator, cacheable, **kwargs): + generator.image_cache_strategy.invoke_callback('before_access', cacheable) - def source_group_receiver(self, sender, source, signal, info, **kwargs): + def cacheable_receiver(self, sender, cacheable, signal, info, **kwargs): """ - Redirects signals dispatched on sources to the appropriate specs. + Redirects signals dispatched on cacheables + to the appropriate generators. """ - source_group = sender - if source_group not in self._source_groups: + cacheable = sender + if cacheable not in self._cacheables: return - for spec in (generator_registry.get(id, source=source, **info) - for id in self._source_groups[source_group]): + for generator in (generator_registry.get(id, cacheable=cacheable, **info) + for id in self._cacheables[cacheable]): event_name = { - source_created: 'source_created', - source_changed: 'source_changed', - source_deleted: 'source_deleted', + cacheable_created: 'cacheable_created', + cacheable_changed: 'cacheable_changed', + cacheable_deleted: 'cacheable_deleted', } - spec._handle_source_event(event_name, source) + generator._handle_cacheable_event(event_name, cacheable) class Register(object): """ - Register specs and sources. + Register generators and cacheables. """ - def spec(self, id, spec=None): - if spec is None: + def generator(self, id, generator=None): + if generator is None: # Return a decorator def decorator(cls): - self.spec(id, cls) + self.generator(id, cls) return cls return decorator - generator_registry.register(id, spec) + generator_registry.register(id, generator) - def sources(self, spec_id, sources): - source_group_registry.register(spec_id, sources) + # iterable that returns kwargs or callable that returns iterable of kwargs + def cacheables(self, generator_id, cacheables): + if callable(cacheables): + cacheables = cacheables() + cacheable_registry.register(generator_id, cacheables) class Unregister(object): """ - Unregister specs and sources. + Unregister generators and cacheables. """ - def spec(self, id, spec): - generator_registry.unregister(id, spec) + def generator(self, id, generator): + generator_registry.unregister(id, generator) - def sources(self, spec_id, sources): - source_group_registry.unregister(spec_id, sources) + def cacheables(self, generator_id, cacheables): + if callable(cacheables): + cacheables = cacheables() + cacheable_registry.unregister(generator_id, cacheables) generator_registry = GeneratorRegistry() -source_group_registry = SourceGroupRegistry() +cacheable_registry = CacheableRegistry() register = Register() unregister = Unregister() diff --git a/imagekit/signals.py b/imagekit/signals.py index 4c7aefa..97a9d9c 100644 --- a/imagekit/signals.py +++ b/imagekit/signals.py @@ -1,6 +1,6 @@ from django.dispatch import Signal before_access = Signal() -source_created = Signal(providing_args=[]) -source_changed = Signal() -source_deleted = Signal() +cacheable_created = Signal(providing_args=[]) +cacheable_changed = Signal() +cacheable_deleted = Signal() diff --git a/imagekit/specs/__init__.py b/imagekit/specs/__init__.py index 9bfab2c..015d4a6 100644 --- a/imagekit/specs/__init__.py +++ b/imagekit/specs/__init__.py @@ -228,7 +228,7 @@ class SpecHost(object): """ self.spec_id = id - register.spec(id, self._original_spec) + register.generator(id, self._original_spec) def get_spec(self, **kwargs): """ diff --git a/imagekit/specs/sourcegroups.py b/imagekit/specs/sourcegroups.py index b36b211..6bd1eed 100644 --- a/imagekit/specs/sourcegroups.py +++ b/imagekit/specs/sourcegroups.py @@ -1,6 +1,6 @@ from django.db.models.signals import post_init, post_save, post_delete from django.utils.functional import wraps -from ..signals import source_created, source_changed, source_deleted +from ..signals import cacheable_created, cacheable_changed, cacheable_deleted def ik_model_receiver(fn): @@ -58,23 +58,25 @@ class ModelSignalRouter(object): src in self._source_groups if src.model_class is instance.__class__) @ik_model_receiver - def post_save_receiver(self, sender, instance=None, created=False, raw=False, **kwargs): + def post_save_receiver(self, sender, instance=None, created=False, + raw=False, **kwargs): if not raw: self.init_instance(instance) old_hashes = instance._ik.get('source_hashes', {}).copy() new_hashes = self.update_source_hashes(instance) for attname, file in self.get_field_dict(instance).items(): if created: - self.dispatch_signal(source_created, file, sender, instance, - attname) + self.dispatch_signal(cacheable_created, file, sender, + instance, attname) elif old_hashes[attname] != new_hashes[attname]: - self.dispatch_signal(source_changed, file, sender, instance, - attname) + self.dispatch_signal(cacheable_changed, file, sender, + instance, attname) @ik_model_receiver def post_delete_receiver(self, sender, instance=None, **kwargs): for attname, file in self.get_field_dict(instance).items(): - self.dispatch_signal(source_deleted, file, sender, instance, attname) + self.dispatch_signal(cacheable_deleted, file, sender, instance, + attname) @ik_model_receiver def post_init_receiver(self, sender, instance=None, **kwargs): @@ -119,5 +121,7 @@ class ImageFieldSourceGroup(object): for instance in self.model_class.objects.all(): yield getattr(instance, self.image_field) + def __call__(self): + return self.files() signal_router = ModelSignalRouter() diff --git a/tests/imagespecs.py b/tests/imagespecs.py index 06d2dab..11e87b3 100644 --- a/tests/imagespecs.py +++ b/tests/imagespecs.py @@ -12,5 +12,5 @@ class ResizeTo1PixelSquare(ImageSpec): super(ResizeTo1PixelSquare, self).__init__(**kwargs) -register.spec('testspec', TestSpec) -register.spec('1pxsq', ResizeTo1PixelSquare) +register.generator('testspec', TestSpec) +register.generator('1pxsq', ResizeTo1PixelSquare)