diff --git a/imagekit/__init__.py b/imagekit/__init__.py index 747ce27..2c72bf4 100644 --- a/imagekit/__init__.py +++ b/imagekit/__init__.py @@ -3,3 +3,4 @@ from . import conf from .specs import ImageSpec from .pkgmeta import * +from .registry import register, unregister diff --git a/imagekit/exceptions.py b/imagekit/exceptions.py index b453f16..308c0a1 100644 --- a/imagekit/exceptions.py +++ b/imagekit/exceptions.py @@ -14,5 +14,5 @@ class UnknownFormatError(Exception): pass -class MissingSpecId(Exception): +class MissingGeneratorId(Exception): pass diff --git a/imagekit/management/commands/warmimagecache.py b/imagekit/management/commands/warmimagecache.py index 8c25aaa..db8ebaa 100644 --- a/imagekit/management/commands/warmimagecache.py +++ b/imagekit/management/commands/warmimagecache.py @@ -1,7 +1,7 @@ from django.core.management.base import BaseCommand import re from ...files import ImageSpecCacheFile -from ...specs import registry +from ...registry import generator_registry, source_group_registry class Command(BaseCommand): @@ -11,7 +11,7 @@ class Command(BaseCommand): args = '[spec_ids]' def handle(self, *args, **options): - specs = registry.get_spec_ids() + specs = generator_registry.get_ids() if args: patterns = self.compile_patterns(args) @@ -19,8 +19,8 @@ class Command(BaseCommand): for spec_id in specs: self.stdout.write('Validating spec: %s\n' % spec_id) - spec = registry.get_spec(spec_id) # TODO: HINTS! (Probably based on source, so this will need to be moved into loop below.) - for source in registry.get_sources(spec_id): + spec = generator_registry.get(spec_id) # TODO: HINTS! (Probably based on source, so this will need to be moved into loop below.) + for source in source_group_registry.get(spec_id): for source_file in source.files(): if source_file: self.stdout.write(' %s\n' % source_file) diff --git a/imagekit/models/fields/__init__.py b/imagekit/models/fields/__init__.py index 95d5914..6b3287c 100644 --- a/imagekit/models/fields/__init__.py +++ b/imagekit/models/fields/__init__.py @@ -1,9 +1,9 @@ from django.db import models from .files import ProcessedImageFieldFile from .utils import ImageSpecFileDescriptor -from ... import specs from ...specs import SpecHost from ...specs.sourcegroups import ImageFieldSourceGroup +from ...registry import register class SpecHostField(SpecHost): @@ -44,7 +44,7 @@ class ImageSpecField(SpecHostField): self.set_spec_id(cls, name) # Add the model and field as a source for this spec id - specs.registry.add_sources(self.spec_id, + register.sources(self.spec_id, [ImageFieldSourceGroup(cls, self.source)]) diff --git a/imagekit/registry.py b/imagekit/registry.py new file mode 100644 index 0000000..5952b4e --- /dev/null +++ b/imagekit/registry.py @@ -0,0 +1,156 @@ +from .exceptions import AlreadyRegistered, NotRegistered, MissingGeneratorId +from .signals import (before_access, source_created, source_changed, + source_deleted) + + +class GeneratorRegistry(object): + """ + An object for registering generators (specs). This registry provides + a convenient way for a distributable app to define default generators + without locking the users of the app into it. + + """ + def __init__(self): + self._generators = {} + + def register(self, generator, id=None): + # TODO: Should we really allow a nested Config class, since it's not necessarily associated with its container? + config = getattr(generator, 'Config', None) + + if id is None: + id = getattr(config, 'id', None) + + if id is None: + raise MissingGeneratorId('No id provided for %s. You must either' + ' pass an id to the register function, or add' + ' an id attribute to the inner Config class of' + ' your spec or generator.' % generator) + + if id in self._generators: + raise AlreadyRegistered('The spec or generator with id %s is' + ' already registered' % id) + self._generators[id] = generator + + source_groups = getattr(config, 'source_groups', None) or [] + source_group_registry.register(id, source_groups) + + def unregister(self, id, generator): + try: + del self._generators[id] + except KeyError: + raise NotRegistered('The spec or 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' + ' registered' % id) + if callable(generator): + return generator(**kwargs) + else: + return generator + + def get_ids(self): + return self._generators.keys() + + +class SourceGroupRegistry(object): + """ + An object for registering source groups with specs. 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. + + """ + + _source_signals = [ + source_created, + source_changed, + source_deleted, + ] + + def __init__(self): + self._sources = {} + for signal in self._source_signals: + signal.connect(self.source_receiver) + before_access.connect(self.before_access_receiver) + + def register(self, spec_id, sources): + """ + Associates sources with a spec id + + """ + for source in sources: + if source not in self._sources: + self._sources[source] = set() + self._sources[source].add(spec_id) + + def unregister(self, spec_id, sources): + """ + Disassociates sources with a spec id + + """ + for source in sources: + try: + self._sources[source].remove(spec_id) + except KeyError: + continue + + def get(self, spec_id): + return [source for source in self._sources + if spec_id in self._sources[source]] + + def before_access_receiver(self, sender, generator, file, **kwargs): + generator.image_cache_strategy.invoke_callback('before_access', file) + + def source_receiver(self, sender, source_file, signal, info, **kwargs): + """ + Redirects signals dispatched on sources to the appropriate specs. + + """ + source = sender + if source not in self._sources: + return + + for spec in (generator_registry.get(id, source_file=source_file, **info) + for id in self._sources[source]): + event_name = { + source_created: 'source_created', + source_changed: 'source_changed', + source_deleted: 'source_deleted', + } + spec._handle_source_event(event_name, source_file) + + +class Register(object): + """ + Register specs and sources. + + """ + def spec(self, id, spec): + generator_registry.register(id, spec) + + def sources(self, spec_id, sources): + source_group_registry.register(spec_id, sources) + + +class Unregister(object): + """ + Unregister specs and sources. + + """ + def spec(self, id, spec): + generator_registry.unregister(id, spec) + + def sources(self, spec_id, sources): + source_group_registry.unregister(spec_id, sources) + + +generator_registry = GeneratorRegistry() +source_group_registry = SourceGroupRegistry() +register = Register() +unregister = Unregister() diff --git a/imagekit/specs/__init__.py b/imagekit/specs/__init__.py index 6361549..2f41676 100644 --- a/imagekit/specs/__init__.py +++ b/imagekit/specs/__init__.py @@ -2,113 +2,13 @@ from django.conf import settings from hashlib import md5 import os import pickle -from ..exceptions import (UnknownExtensionError, AlreadyRegistered, - NotRegistered, MissingSpecId) +from ..exceptions import UnknownExtensionError from ..files import ImageSpecCacheFile, IKContentFile from ..imagecache.backends import get_default_image_cache_backend from ..imagecache.strategies import StrategyWrapper from ..processors import ProcessorPipeline -from ..signals import (before_access, source_created, source_changed, - source_deleted) from ..utils import open_image, extension_to_format, img_to_fobj - - -class SpecRegistry(object): - """ - An object for registering specs and sources. The two are associated with - eachother via a string id. We do this (as opposed to associating them - directly by, for example, putting a ``sources`` 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. - - """ - - _source_signals = [ - source_created, - source_changed, - source_deleted, - ] - - def __init__(self): - self._specs = {} - self._sources = {} - for signal in self._source_signals: - signal.connect(self.source_receiver) - before_access.connect(self.before_access_receiver) - - def register(self, spec, id=None): - # TODO: Should we really allow a nested Config class, since it's not necessarily associated with its container? - config = getattr(spec, 'Config', None) - - if id is None: - id = getattr(config, 'id', None) - - if id is None: - raise MissingSpecId('No id provided for %s. You must either pass an' - ' id to the register function, or add an id' - ' attribute to the inner Config class of your' - ' spec.' % spec) - - if id in self._specs: - raise AlreadyRegistered('The spec with id %s is already registered' % id) - self._specs[id] = spec - - sources = getattr(config, 'source_groups', None) or [] - self.add_sources(id, sources) - - def unregister(self, id, spec): - try: - del self._specs[id] - except KeyError: - raise NotRegistered('The spec with id %s is not registered' % id) - - def get_spec(self, id, **kwargs): - try: - spec = self._specs[id] - except KeyError: - raise NotRegistered('The spec with id %s is not registered' % id) - if callable(spec): - return spec(**kwargs) - else: - return spec - - def get_spec_ids(self): - return self._specs.keys() - - def add_sources(self, spec_id, sources): - """ - Associates sources with a spec id - - """ - for source in sources: - if source not in self._sources: - self._sources[source] = set() - self._sources[source].add(spec_id) - - def get_sources(self, spec_id): - return [source for source in self._sources if spec_id in self._sources[source]] - - def before_access_receiver(self, sender, generator, file, **kwargs): - generator.image_cache_strategy.invoke_callback('before_access', file) - - def source_receiver(self, sender, source_file, signal, info, **kwargs): - """ - Redirects signals dispatched on sources to the appropriate specs. - - """ - source = sender - if source not in self._sources: - return - - for spec in (self.get_spec(id, source_file=source_file, **info) - for id in self._sources[source]): - event_name = { - source_created: 'source_created', - source_changed: 'source_changed', - source_deleted: 'source_deleted', - } - spec._handle_source_event(event_name, source_file) +from ..registry import generator_registry, register class BaseImageSpec(object): @@ -270,7 +170,7 @@ class SpecHost(object): """ self.spec_id = id - registry.register(self._original_spec, id) + register.spec(self._original_spec, id) def get_spec(self, **kwargs): """ @@ -282,12 +182,4 @@ class SpecHost(object): """ if not getattr(self, 'spec_id', None): raise Exception('Object %s has no spec id.' % self) - return registry.get_spec(self.spec_id, **kwargs) - - -registry = SpecRegistry() -register = registry.register - - -def unregister(id, spec): - registry.unregister(id, spec) + return generator_registry.get(self.spec_id, **kwargs) diff --git a/imagekit/templatetags/imagekit.py b/imagekit/templatetags/imagekit.py index ade02d8..eea4f8c 100644 --- a/imagekit/templatetags/imagekit.py +++ b/imagekit/templatetags/imagekit.py @@ -2,7 +2,7 @@ from django import template from django.utils.safestring import mark_safe import re from ..files import ImageSpecCacheFile -from .. import specs +from ..registry import generator_registry register = template.Library() @@ -34,7 +34,7 @@ class SpecResultNodeMixin(object): from ..utils import autodiscover autodiscover() spec_id = self._spec_id.resolve(context) - spec = specs.registry.get_spec(spec_id) # TODO: What "hints" here? + spec = generator_registry.get(spec_id) # TODO: What "hints" here? return spec def get_source_file(self, context):