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