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