Remove specs.SpecRegistry; add registry module

The registry module splits the work that specs.SpecRegistry
used to do into two classes: GeneratorRegistry and
SourceGroupRegistry. These two registries are wrapped in
Register and Unregister utilities for API convenience.
This commit is contained in:
Eric Eldredge 2012-12-01 17:03:51 -05:00
parent d253fe281a
commit e0567e8fa7
7 changed files with 170 additions and 121 deletions

View file

@ -3,3 +3,4 @@
from . import conf
from .specs import ImageSpec
from .pkgmeta import *
from .registry import register, unregister

View file

@ -14,5 +14,5 @@ class UnknownFormatError(Exception):
pass
class MissingSpecId(Exception):
class MissingGeneratorId(Exception):
pass

View file

@ -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)

View file

@ -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)])

156
imagekit/registry.py Normal file
View file

@ -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()

View file

@ -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)

View file

@ -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):