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