Steve Piercy
2018-09-22 2a45fe74f9598b4e726ab17ce17948d4e709894b
commit | author | age
e49638 1 import operator
c15cbc 2 import threading
e49638 3
CM 4 from zope.interface import implementer
5
0dde01 6 from zope.interface.registry import Components
857de3 7
e6c2d2 8 from pyramid.compat import text_
398465 9 from pyramid.decorator import reify
e49638 10
3b5ccb 11 from pyramid.interfaces import (
e49638 12     IIntrospector,
CM 13     IIntrospectable,
5e20f9 14     ISettings,
3b5ccb 15     )
5e20f9 16
MM 17 from pyramid.path import (
18     CALLER_PACKAGE,
19     caller_package,
20 )
e6c2d2 21
CM 22 empty = text_('')
e35dc1 23
17b8bc 24 class Registry(Components, dict):
5e20f9 25     """ A registry object is an :term:`application registry`.
MM 26
27     It is used by the framework itself to perform mappings of URLs to view
28     callables, as well as servicing other various framework duties. A registry
29     has its own internal API, but this API is rarely used by Pyramid
30     application developers (it's usually only used by developers of the
31     Pyramid framework and Pyramid addons).  But it has a number of attributes
32     that may be useful to application developers within application code,
33     such as ``settings``, which is a dictionary containing application
34     deployment settings.
a1365e 35
CM 36     For information about the purpose and usage of the application registry,
37     see :ref:`zca_chapter`.
38
5e20f9 39     The registry may be used both as an :class:`pyramid.interfaces.IDict` and
MM 40     as a Zope component registry.
41     These two ways of storing configuration are independent.
42     Applications will tend to prefer to store information as key-values
43     whereas addons may prefer to use the component registry to avoid naming
44     conflicts and to provide more complex lookup mechanisms.
45
a1365e 46     The application registry is usually accessed as ``request.registry`` in
5e20f9 47     application code. By the time a registry is used to handle requests it
MM 48     should be considered frozen and read-only. Any changes to its internal
49     state should be done with caution and concern for thread-safety.
a1365e 50
CM 51     """
2bb9b7 52
CM 53     # for optimization purposes, if no listeners are listening, don't try
54     # to notify them
7292d4 55     has_listeners = False
3b5ccb 56
5a972b 57     _settings = None
17b8bc 58
f6af10 59     def __init__(self, package_name=CALLER_PACKAGE, *args, **kw):
c15cbc 60         # add a registry-instance-specific lock, which is used when the lookup
CM 61         # cache is mutated
62         self._lock = threading.Lock()
63         # add a view lookup cache
64         self._clear_view_lookup_cache()
5e20f9 65         if package_name is CALLER_PACKAGE:
MM 66             package_name = caller_package().__name__
f6af10 67         Components.__init__(self, package_name, *args, **kw)
5e20f9 68         dict.__init__(self)
c15cbc 69
CM 70     def _clear_view_lookup_cache(self):
71         self._view_lookup_cache = {}
72
79ef3d 73     def __nonzero__(self):
CM 74         # defeat bool determination via dict.__len__
75         return True
76
398465 77     @reify
HB 78     def package_name(self):
79         return self.__name__
80
2bb9b7 81     def registerSubscriptionAdapter(self, *arg, **kw):
CM 82         result = Components.registerSubscriptionAdapter(self, *arg, **kw)
7292d4 83         self.has_listeners = True
2bb9b7 84         return result
c9aece 85
e6c2d2 86     def registerSelfAdapter(self, required=None, provided=None, name=empty,
CM 87                             info=empty, event=True):
d868ff 88         # registerAdapter analogue which always returns the object itself
CM 89         # when required is matched
90         return self.registerAdapter(lambda x: x, required=required,
91                                     provided=provided, name=name,
92                                     info=info, event=event)
93
c209f8 94     def queryAdapterOrSelf(self, object, interface, default=None):
d868ff 95         # queryAdapter analogue which returns the object if it implements
CM 96         # the interface, otherwise it will return an adaptation to the
97         # interface
1a6fc7 98         if not interface.providedBy(object):
c209f8 99             return self.queryAdapter(object, interface, default=default)
d868ff 100         return object
CM 101
2bb9b7 102     def registerHandler(self, *arg, **kw):
CM 103         result = Components.registerHandler(self, *arg, **kw)
7292d4 104         self.has_listeners = True
2bb9b7 105         return result
CM 106
971537 107     def notify(self, *events):
7292d4 108         if self.has_listeners:
2bb9b7 109             # iterating over subscribers assures they get executed
58fdd1 110             [ _ for _ in self.subscribers(events, None) ]
3fd912 111
5a972b 112     # backwards compatibility for code that wants to look up a settings
CM 113     # object via ``registry.getUtility(ISettings)``
114     def _get_settings(self):
115         return self._settings
116
117     def _set_settings(self, settings):
118         self.registerUtility(settings, ISettings)
119         self._settings = settings
120
121     settings = property(_get_settings, _set_settings)
122
e49638 123 @implementer(IIntrospector)
CM 124 class Introspector(object):
125     def __init__(self):
126         self._refs = {}
127         self._categories = {}
128         self._counter = 0
3b5ccb 129
e49638 130     def add(self, intr):
CM 131         category = self._categories.setdefault(intr.category_name, {})
132         category[intr.discriminator] = intr
133         category[intr.discriminator_hash] = intr
134         intr.order = self._counter
135         self._counter += 1
3b5ccb 136
e49638 137     def get(self, category_name, discriminator, default=None):
CM 138         category = self._categories.setdefault(category_name, {})
139         intr = category.get(discriminator, default)
140         return intr
3b5ccb 141
79f34b 142     def get_category(self, category_name, default=None, sort_key=None):
57a0d7 143         if sort_key is None:
CM 144             sort_key = operator.attrgetter('order')
79f34b 145         category = self._categories.get(category_name)
CM 146         if category is None:
147             return default
e49638 148         values = category.values()
57a0d7 149         values = sorted(set(values), key=sort_key)
79f34b 150         return [
25c64c 151             {'introspectable': intr,
JA 152              'related': self.related(intr)}
153             for intr in values
154         ]
e49638 155
57a0d7 156     def categorized(self, sort_key=None):
e49638 157         L = []
CM 158         for category_name in self.categories():
79f34b 159             L.append((category_name, self.get_category(category_name,
CM 160                                                        sort_key=sort_key)))
e49638 161         return L
57a0d7 162
CM 163     def categories(self):
164         return sorted(self._categories.keys())
e49638 165
CM 166     def remove(self, category_name, discriminator):
167         intr = self.get(category_name, discriminator)
168         if intr is None:
169             return
57a0d7 170         L = self._refs.pop(intr, [])
e49638 171         for d in L:
CM 172             L2 = self._refs[d]
57a0d7 173             L2.remove(intr)
e49638 174         category = self._categories[intr.category_name]
CM 175         del category[intr.discriminator]
176         del category[intr.discriminator_hash]
177
178     def _get_intrs_by_pairs(self, pairs):
179         introspectables = []
180         for pair in pairs:
181             category_name, discriminator = pair
182             intr = self._categories.get(category_name, {}).get(discriminator)
183             if intr is None:
184                 raise KeyError((category_name, discriminator))
185             introspectables.append(intr)
186         return introspectables
187
188     def relate(self, *pairs):
189         introspectables = self._get_intrs_by_pairs(pairs)
190         relatable = ((x,y) for x in introspectables for y in introspectables)
191         for x, y in relatable:
192             L = self._refs.setdefault(x, [])
193             if x is not y and y not in L:
194                 L.append(y)
195
196     def unrelate(self, *pairs):
197         introspectables = self._get_intrs_by_pairs(pairs)
198         relatable = ((x,y) for x in introspectables for y in introspectables)
199         for x, y in relatable:
200             L = self._refs.get(x, [])
201             if y in L:
202                 L.remove(y)
203
204     def related(self, intr):
205         category_name, discriminator = intr.category_name, intr.discriminator
206         intr = self._categories.get(category_name, {}).get(discriminator)
207         if intr is None:
208             raise KeyError((category_name, discriminator))
209         return self._refs.get(intr, [])
210
211 @implementer(IIntrospectable)
212 class Introspectable(dict):
213
35ad08 214     order = 0 # mutated by introspector.add
57a9d6 215     action_info = None # mutated by self.register
e49638 216
CM 217     def __init__(self, category_name, discriminator, title, type_name):
218         self.category_name = category_name
219         self.discriminator = discriminator
220         self.title = title
221         self.type_name = type_name
35ad08 222         self._relations = []
e49638 223
CM 224     def relate(self, category_name, discriminator):
35ad08 225         self._relations.append((True, category_name, discriminator))
e49638 226
CM 227     def unrelate(self, category_name, discriminator):
35ad08 228         self._relations.append((False, category_name, discriminator))
e49638 229
d98612 230     def _assert_resolved(self):
CM 231         assert undefer(self.discriminator) is self.discriminator
232
e49638 233     @property
CM 234     def discriminator_hash(self):
d98612 235         self._assert_resolved()
e49638 236         return hash(self.discriminator)
CM 237
238     def __hash__(self):
d98612 239         self._assert_resolved()
e49638 240         return hash((self.category_name,) + (self.discriminator,))
CM 241
242     def __repr__(self):
d98612 243         self._assert_resolved()
e49638 244         return '<%s category %r, discriminator %r>' % (self.__class__.__name__,
CM 245                                                        self.category_name,
246                                                        self.discriminator)
247
248     def __nonzero__(self):
249         return True
250
251     __bool__ = __nonzero__ # py3
3b5ccb 252
57a9d6 253     def register(self, introspector, action_info):
d98612 254         self.discriminator = undefer(self.discriminator)
57a9d6 255         self.action_info = action_info
CM 256         introspector.add(self)
257         for relate, category_name, discriminator in self._relations:
d98612 258             discriminator = undefer(discriminator)
57a9d6 259             if relate:
CM 260                 method = introspector.relate
261             else:
262                 method = introspector.unrelate
263             method(
264                 (self.category_name, self.discriminator),
265                 (category_name, discriminator)
266                 )
267
d98612 268 class Deferred(object):
CM 269     """ Can be used by a third-party configuration extender to wrap a
270     :term:`discriminator` during configuration if an immediately hashable
271     discriminator cannot be computed because it relies on unresolved values.
272     The function should accept no arguments and should return a hashable
273     discriminator."""
274     def __init__(self, func):
275         self.func = func
276
78f6ef 277     @reify
MM 278     def value(self):
71e92d 279         result = self.func()
KK 280         del self.func
281         return result
d98612 282
78f6ef 283     def resolve(self):
MM 284         return self.value
285
d98612 286 def undefer(v):
CM 287     """ Function which accepts an object and returns it unless it is a
288     :class:`pyramid.registry.Deferred` instance.  If it is an instance of
289     that class, its ``resolve`` method is called, and the result of the
290     method is returned."""
291     if isinstance(v, Deferred):
292         v = v.resolve()
293     return v
294
295 class predvalseq(tuple):
296     """ A subtype of tuple used to represent a sequence of predicate values """
297     pass
298
59c5df 299 global_registry = Registry('global')