File should be a value model, right?
[philo.git] / philo / utils / registry.py
1 from django.core.validators import slug_re
2 from django.template.defaultfilters import slugify
3 from django.utils.encoding import smart_str
4
5
6 class RegistryIterator(object):
7         """
8         Wraps the iterator returned by calling ``getattr(registry, iterattr)`` to provide late instantiation of the wrapped iterator and to allow copying of the iterator for even later instantiation.
9         
10         :param registry: The object which provides the iterator at ``iterattr``.
11         :param iterattr: The name of the method on ``registry`` that provides the iterator.
12         :param transform: A function which will be called on each result from the wrapped iterator before it is returned.
13         
14         """
15         def __init__(self, registry, iterattr='__iter__', transform=lambda x:x):
16                 if not hasattr(registry, iterattr):
17                         raise AttributeError("Registry has no attribute %s" % iterattr)
18                 self.registry = registry
19                 self.iterattr = iterattr
20                 self.transform = transform
21         
22         def __iter__(self):
23                 return self
24         
25         def next(self):
26                 if not hasattr(self, '_iter'):
27                         self._iter = getattr(self.registry, self.iterattr)()
28                 
29                 return self.transform(self._iter.next())
30         
31         def copy(self):
32                 """Returns a fresh copy of this iterator."""
33                 return self.__class__(self.registry, self.iterattr, self.transform)
34
35
36 class RegistrationError(Exception):
37         """Raised if there is a problem registering a object with a :class:`Registry`"""
38         pass
39
40
41 class Registry(object):
42         """Holds a registry of arbitrary objects by slug."""
43         
44         def __init__(self):
45                 self._registry = {}
46         
47         def register(self, obj, slug=None, verbose_name=None):
48                 """
49                 Register an object with the registry.
50                 
51                 :param obj: The object to register.
52                 :param slug: The slug which will be used to register the object. If ``slug`` is ``None``, it will be generated from ``verbose_name`` or looked for at ``obj.slug``.
53                 :param verbose_name: The verbose name for the object. If ``verbose_name`` is ``None``, it will be looked for at ``obj.verbose_name``.
54                 :raises: :class:`RegistrationError` if a different object is already registered with ``slug``, or if ``slug`` is not a valid slug.
55                 
56                 """
57                 verbose_name = verbose_name if verbose_name is not None else obj.verbose_name
58                 
59                 if slug is None:
60                         slug = getattr(obj, 'slug', slugify(verbose_name))
61                 slug = smart_str(slug)
62                 
63                 if not slug_re.search(slug):
64                         raise RegistrationError(u"%s is not a valid slug." % slug)
65                 
66                 
67                 if slug in self._registry:
68                         reg = self._registry[slug]
69                         if reg['obj'] != obj:
70                                 raise RegistrationError(u"A different object is already registered as `%s`" % slug)
71                 else:
72                         self._registry[slug] = {
73                                 'obj': obj,
74                                 'verbose_name': verbose_name
75                         }
76         
77         def unregister(self, obj, slug=None):
78                 """
79                 Unregister an object from the registry.
80                 
81                 :param obj: The object to unregister.
82                 :param slug: If provided, the object will only be removed if it was registered with ``slug``. If not provided, the object will be unregistered no matter what slug it was registered with.
83                 :raises: :class:`RegistrationError` if ``slug`` is provided and an object other than ``obj`` is registered as ``slug``.
84                 
85                 """
86                 if slug is not None:
87                         if slug in self._registry:
88                                 if self._registry[slug]['obj'] == obj:
89                                         del self._registry[slug]
90                                 else:
91                                         raise RegistrationError(u"`%s` is not registered as `%s`" % (obj, slug))
92                 else:
93                         for slug, reg in self.items():
94                                 if obj == reg:
95                                         del self._registry[slug]
96         
97         def items(self):
98                 """Returns a list of (slug, obj) items in the registry."""
99                 return [(slug, self[slug]) for slug in self._registry]
100         
101         def values(self):
102                 """Returns a list of objects in the registry."""
103                 return [self[slug] for slug in self._registry]
104         
105         def iteritems(self):
106                 """Returns a :class:`RegistryIterator` over the (slug, obj) pairs in the registry."""
107                 return RegistryIterator(self._registry, 'iteritems', lambda x: (x[0], x[1]['obj']))
108         
109         def itervalues(self):
110                 """Returns a :class:`RegistryIterator` over the objects in the registry."""
111                 return RegistryIterator(self._registry, 'itervalues', lambda x: x['obj'])
112         
113         def iterchoices(self):
114                 """Returns a :class:`RegistryIterator` over (slug, verbose_name) pairs for the registry."""
115                 return RegistryIterator(self._registry, 'iteritems', lambda x: (x[0], x[1]['verbose_name']))
116         choices = property(iterchoices)
117         
118         def get(self, key, default=None):
119                 """Returns the object registered with ``key`` or ``default`` if no object was registered."""
120                 try:
121                         return self[key]
122                 except KeyError:
123                         return default
124         
125         def get_slug(self, obj, default=None):
126                 """Returns the slug used to register ``obj`` or ``default`` if ``obj`` was not registered."""
127                 for slug, reg in self.iteritems():
128                         if obj == reg:
129                                 return slug
130                 return default
131         
132         def __getitem__(self, key):
133                 """Returns the obj registered with ``key``."""
134                 return self._registry[key]['obj']
135         
136         def __iter__(self):
137                 """Returns an iterator over the keys in the registry."""
138                 return self._registry.__iter__()
139         
140         def __contains__(self, item):
141                 return self._registry.__contains__(item)