root/galaxy-central/eggs/Paste-1.6-py2.6.egg/paste/registry.py @ 3

リビジョン 3, 19.4 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# (c) 2005 Ben Bangert
2# This module is part of the Python Paste Project and is released under
3# the MIT License: http://www.opensource.org/licenses/mit-license.php
4"""Registry for handling request-local module globals sanely
5
6Dealing with module globals in a thread-safe way is good if your
7application is the sole responder in a thread, however that approach fails
8to properly account for various scenarios that occur with WSGI applications
9and middleware.
10
11What is actually needed in the case where a module global is desired that
12is always set properly depending on the current request, is a stacked
13thread-local object. Such an object is popped or pushed during the request
14cycle so that it properly represents the object that should be active for
15the current request.
16
17To make it easy to deal with such variables, this module provides a special
18StackedObjectProxy class which you can instantiate and attach to your
19module where you'd like others to access it. The object you'd like this to
20actually "be" during the request is then registered with the
21RegistryManager middleware, which ensures that for the scope of the current
22WSGI application everything will work properly.
23
24Example:
25
26.. code-block:: Python
27   
28    #yourpackage/__init__.py
29   
30    from paste.registry import RegistryManager, StackedObjectProxy
31    myglobal = StackedObjectProxy()
32   
33    #wsgi app stack
34    app = RegistryManager(yourapp)
35   
36    #inside your wsgi app
37    class yourapp(object):
38        def __call__(self, environ, start_response):
39            obj = someobject  # The request-local object you want to access
40                              # via yourpackage.myglobal
41            if environ.has_key('paste.registry'):
42                environ['paste.registry'].register(myglobal, obj)
43
44You will then be able to import yourpackage anywhere in your WSGI app or in
45the calling stack below it and be assured that it is using the object you
46registered with Registry.
47
48RegistryManager can be in the WSGI stack multiple times, each time it
49appears it registers a new request context.
50
51
52Performance
53===========
54
55The overhead of the proxy object is very minimal, however if you are using
56proxy objects extensively (Thousands of accesses per request or more), there
57are some ways to avoid them. A proxy object runs approximately 3-20x slower
58than direct access to the object, this is rarely your performance bottleneck
59when developing web applications.
60
61Should you be developing a system which may be accessing the proxy object
62thousands of times per request, the performance of the proxy will start to
63become more noticeable. In that circumstance, the problem can be avoided by
64getting at the actual object via the proxy with the ``_current_obj`` function:
65
66.. code-block:: Python
67   
68    #sessions.py
69    Session = StackedObjectProxy()
70    # ... initialization code, etc.
71   
72    # somemodule.py
73    import sessions
74   
75    def somefunc():
76        session = sessions.Session._current_obj()
77        # ... tons of session access
78
79This way the proxy is used only once to retrieve the object for the current
80context and the overhead is minimized while still making it easy to access
81the underlying object. The ``_current_obj`` function is preceded by an
82underscore to more likely avoid clashing with the contained object's
83attributes.
84
85**NOTE:** This is *highly* unlikely to be an issue in the vast majority of
86cases, and requires incredibly large amounts of proxy object access before
87one should consider the proxy object to be causing slow-downs. This section
88is provided solely in the extremely rare case that it is an issue so that a
89quick way to work around it is documented.
90
91"""
92import sys
93import paste.util.threadinglocal as threadinglocal
94
95__all__ = ['StackedObjectProxy', 'RegistryManager', 'StackedObjectRestorer',
96           'restorer']
97
98class NoDefault(object): pass
99
100class StackedObjectProxy(object):
101    """Track an object instance internally using a stack
102   
103    The StackedObjectProxy proxies access to an object internally using a
104    stacked thread-local. This makes it safe for complex WSGI environments
105    where access to the object may be desired in multiple places without
106    having to pass the actual object around.
107   
108    New objects are added to the top of the stack with _push_object while
109    objects can be removed with _pop_object.
110   
111    """
112    def __init__(self, default=NoDefault, name="Default"):
113        """Create a new StackedObjectProxy
114       
115        If a default is given, its used in every thread if no other object
116        has been pushed on.
117       
118        """
119        self.__dict__['____name__'] = name
120        self.__dict__['____local__'] = threadinglocal.local()
121        if default is not NoDefault:
122            self.__dict__['____default_object__'] = default
123   
124    def __getattr__(self, attr):
125        return getattr(self._current_obj(), attr)
126   
127    def __setattr__(self, attr, value):
128        setattr(self._current_obj(), attr, value)
129   
130    def __delattr__(self, name):
131        delattr(self._current_obj(), name)
132   
133    def __getitem__(self, key):
134        return self._current_obj()[key]
135   
136    def __setitem__(self, key, value):
137        self._current_obj()[key] = value
138   
139    def __delitem__(self, key):
140        del self._current_obj()[key]
141
142    def __call__(self, *args, **kw):
143        return self._current_obj()(*args, **kw)
144   
145    def __repr__(self):
146        try:
147            return repr(self._current_obj())
148        except (TypeError, AttributeError):
149            return '<%s.%s object at 0x%x>' % (self.__class__.__module__,
150                                               self.__class__.__name__,
151                                               id(self))
152   
153    def __iter__(self):
154        return iter(self._current_obj())
155   
156    def __len__(self):
157        return len(self._current_obj())
158   
159    def __contains__(self, key):
160        return key in self._current_obj()
161
162    def __nonzero__(self):
163        return bool(self._current_obj())
164
165    def _current_obj(self):
166        """Returns the current active object being proxied to
167       
168        In the event that no object was pushed, the default object if
169        provided will be used. Otherwise, a TypeError will be raised.
170       
171        """
172        objects = getattr(self.____local__, 'objects', None)
173        if objects:
174            return objects[-1]
175        else:
176            obj = self.__dict__.get('____default_object__', NoDefault)
177            if obj is not NoDefault:
178                return obj
179            else:
180                raise TypeError(
181                    'No object (name: %s) has been registered for this '
182                    'thread' % self.____name__)
183
184    def _push_object(self, obj):
185        """Make ``obj`` the active object for this thread-local.
186       
187        This should be used like:
188       
189        .. code-block:: Python
190
191            obj = yourobject()
192            module.glob = StackedObjectProxy()
193            module.glob._push_object(obj)
194            try:
195                ... do stuff ...
196            finally:
197                module.glob._pop_object(conf)
198       
199        """
200        if not hasattr(self.____local__, 'objects'):
201            self.____local__.objects = []
202        self.____local__.objects.append(obj)
203   
204    def _pop_object(self, obj=None):
205        """Remove a thread-local object.
206       
207        If ``obj`` is given, it is checked against the popped object and an
208        error is emitted if they don't match.
209       
210        """
211        if not hasattr(self.____local__, 'objects'):
212            raise AssertionError('No object has been registered for this thread')
213        popped = self.____local__.objects.pop()
214        if obj:
215            if popped is not obj:
216                raise AssertionError(
217                    'The object popped (%s) is not the same as the object '
218                    'expected (%s)' % (popped, obj))
219
220    def _object_stack(self):
221        """Returns all of the objects stacked in this container
222
223        (Might return [] if there are none)
224        """
225        try:
226            return self.____local__.objects[:]
227        except AssertionError:
228            return []
229
230    # The following methods will be swapped for their original versions by
231    # StackedObjectRestorer when restoration is enabled. The original
232    # functions (e.g. _current_obj) will be available at _current_obj_orig
233
234    def _current_obj_restoration(self):
235        request_id = restorer.in_restoration()
236        if request_id:
237            return restorer.get_saved_proxied_obj(self, request_id)
238        return self._current_obj_orig()
239    _current_obj_restoration.__doc__ = \
240        ('%s\n(StackedObjectRestorer restoration enabled)' % \
241         _current_obj.__doc__)
242
243    def _push_object_restoration(self, obj):
244        if not restorer.in_restoration():
245            self._push_object_orig(obj)
246    _push_object_restoration.__doc__ = \
247        ('%s\n(StackedObjectRestorer restoration enabled)' % \
248         _push_object.__doc__)
249
250    def _pop_object_restoration(self, obj=None):
251        if not restorer.in_restoration():
252            self._pop_object_orig(obj)
253    _pop_object_restoration.__doc__ = \
254        ('%s\n(StackedObjectRestorer restoration enabled)' % \
255         _pop_object.__doc__)
256
257class Registry(object):
258    """Track objects and stacked object proxies for removal
259   
260    The Registry object is instantiated a single time for the request no
261    matter how many times the RegistryManager is used in a WSGI stack. Each
262    RegistryManager must call ``prepare`` before continuing the call to
263    start a new context for object registering.
264   
265    Each context is tracked with a dict inside a list. The last list
266    element is the currently executing context. Each context dict is keyed
267    by the id of the StackedObjectProxy instance being proxied, the value
268    is a tuple of the StackedObjectProxy instance and the object being
269    tracked.
270   
271    """
272    def __init__(self):
273        """Create a new Registry object
274       
275        ``prepare`` must still be called before this Registry object can be
276        used to register objects.
277       
278        """
279        self.reglist = []
280   
281    def prepare(self):
282        """Used to create a new registry context
283       
284        Anytime a new RegistryManager is called, ``prepare`` needs to be
285        called on the existing Registry object. This sets up a new context
286        for registering objects.
287       
288        """
289        self.reglist.append({})
290   
291    def register(self, stacked, obj):
292        """Register an object with a StackedObjectProxy"""
293        myreglist = self.reglist[-1]
294        stacked_id = id(stacked)
295        if stacked_id in myreglist:
296            stacked._pop_object(myreglist[stacked_id][1])
297            del myreglist[stacked_id]
298        stacked._push_object(obj)
299        myreglist[stacked_id] = (stacked, obj)
300   
301    # Replace now does the same thing as register
302    replace = register
303   
304    def cleanup(self):
305        """Remove all objects from all StackedObjectProxy instances that
306        were tracked at this Registry context"""
307        for stacked, obj in self.reglist[-1].itervalues():
308            stacked._pop_object(obj)
309        self.reglist.pop()
310       
311class RegistryManager(object):
312    """Creates and maintains a Registry context
313   
314    RegistryManager creates a new registry context for the registration of
315    StackedObjectProxy instances. Multiple RegistryManager's can be in a
316    WSGI stack and will manage the context so that the StackedObjectProxies
317    always proxy to the proper object.
318   
319    The object being registered can be any object sub-class, list, or dict.
320   
321    Registering objects is done inside a WSGI application under the
322    RegistryManager instance, using the ``environ['paste.registry']``
323    object which is a Registry instance.
324       
325    """
326    def __init__(self, application):
327        self.application = application
328       
329    def __call__(self, environ, start_response):
330        app_iter = None
331        reg = environ.setdefault('paste.registry', Registry())
332        reg.prepare()
333        try:
334            app_iter = self.application(environ, start_response)
335        except Exception, e:
336            # Regardless of if the content is an iterable, generator, list
337            # or tuple, we clean-up right now. If its an iterable/generator
338            # care should be used to ensure the generator has its own ref
339            # to the actual object
340            if environ.get('paste.evalexception'):
341                # EvalException is present in the WSGI stack
342                expected = False
343                for expect in environ.get('paste.expected_exceptions', []):
344                    if isinstance(e, expect):
345                        expected = True
346                if not expected:
347                    # An unexpected exception: save state for EvalException
348                    restorer.save_registry_state(environ)
349            reg.cleanup()
350            raise
351        except:
352            # Save state for EvalException if it's present
353            if environ.get('paste.evalexception'):
354                restorer.save_registry_state(environ)
355            reg.cleanup()
356            raise
357        else:
358            reg.cleanup()
359       
360        return app_iter
361
362class StackedObjectRestorer(object):
363    """Track StackedObjectProxies and their proxied objects for automatic
364    restoration within EvalException's interactive debugger.
365
366    An instance of this class tracks all StackedObjectProxy state in existence
367    when unexpected exceptions are raised by WSGI applications housed by
368    EvalException and RegistryManager. Like EvalException, this information is
369    stored for the life of the process.
370
371    When an unexpected exception occurs and EvalException is present in the
372    WSGI stack, save_registry_state is intended to be called to store the
373    Registry state and enable automatic restoration on all currently registered
374    StackedObjectProxies.
375
376    With restoration enabled, those StackedObjectProxies' _current_obj
377    (overwritten by _current_obj_restoration) method's strategy is modified:
378    it will return its appropriate proxied object from the restorer when
379    a restoration context is active in the current thread.
380
381    The StackedObjectProxies' _push/pop_object methods strategies are also
382    changed: they no-op when a restoration context is active in the current
383    thread (because the pushing/popping work is all handled by the
384    Registry/restorer).
385
386    The request's Registry objects' reglists are restored from the restorer
387    when a restoration context begins, enabling the Registry methods to work
388    while their changes are tracked by the restorer.
389
390    The overhead of enabling restoration is negligible (another threadlocal
391    access for the changed StackedObjectProxy methods) for normal use outside
392    of a restoration context, but worth mentioning when combined with
393    StackedObjectProxies normal overhead. Once enabled it does not turn off,
394    however:
395
396    o Enabling restoration only occurs after an unexpected exception is
397    detected. The server is likely to be restarted shortly after the exception
398    is raised to fix the cause
399
400    o StackedObjectRestorer is only enabled when EvalException is enabled (not
401    on a production server) and RegistryManager exists in the middleware
402    stack"""
403    def __init__(self):
404        # Registries and their saved reglists by request_id
405        self.saved_registry_states = {}
406        self.restoration_context_id = threadinglocal.local()
407
408    def save_registry_state(self, environ):
409        """Save the state of this request's Registry (if it hasn't already been
410        saved) to the saved_registry_states dict, keyed by the request's unique
411        identifier"""
412        registry = environ.get('paste.registry')
413        if not registry or not len(registry.reglist) or \
414                self.get_request_id(environ) in self.saved_registry_states:
415            # No Registry, no state to save, or this request's state has
416            # already been saved
417            return
418
419        self.saved_registry_states[self.get_request_id(environ)] = \
420            (registry, registry.reglist[:])
421
422        # Tweak the StackedObjectProxies we want to save state for -- change
423        # their methods to act differently when a restoration context is active
424        # in the current thread
425        for reglist in registry.reglist:
426            for stacked, obj in reglist.itervalues():
427                self.enable_restoration(stacked)
428
429    def get_saved_proxied_obj(self, stacked, request_id):
430        """Retrieve the saved object proxied by the specified
431        StackedObjectProxy for the request identified by request_id"""
432        # All state for the request identified by request_id
433        reglist = self.saved_registry_states[request_id][1]
434
435        # The top of the stack was current when the exception occurred
436        stack_level = len(reglist) - 1
437        stacked_id = id(stacked)
438        while True:
439            if stack_level < 0:
440                # Nothing registered: Call _current_obj_orig to raise a
441                # TypeError
442                return stacked._current_obj_orig()
443            context = reglist[stack_level]
444            if stacked_id in context:
445                break
446            # This StackedObjectProxy may not have been registered by the
447            # RegistryManager that was active when the exception was raised --
448            # continue searching down the stack until it's found
449            stack_level -= 1
450        return context[stacked_id][1]
451
452    def enable_restoration(self, stacked):
453        """Replace the specified StackedObjectProxy's methods with their
454        respective restoration versions.
455
456        _current_obj_restoration forces recovery of the saved proxied object
457        when a restoration context is active in the current thread.
458
459        _push/pop_object_restoration avoid pushing/popping data
460        (pushing/popping is only done at the Registry level) when a restoration
461        context is active in the current thread"""
462        if '_current_obj_orig' in stacked.__dict__:
463            # Restoration already enabled
464            return
465
466        for func_name in ('_current_obj', '_push_object', '_pop_object'):
467            orig_func = getattr(stacked, func_name)
468            restoration_func = getattr(stacked, func_name + '_restoration')
469            stacked.__dict__[func_name + '_orig'] = orig_func
470            stacked.__dict__[func_name] = restoration_func
471
472    def get_request_id(self, environ):
473        """Return a unique identifier for the current request"""
474        from paste.evalexception.middleware import get_debug_count
475        return get_debug_count(environ)
476
477    def restoration_begin(self, request_id):
478        """Enable a restoration context in the current thread for the specified
479        request_id"""
480        if request_id in self.saved_registry_states:
481            # Restore the old Registry object's state
482            registry, reglist = self.saved_registry_states[request_id]
483            registry.reglist = reglist
484
485        self.restoration_context_id.request_id = request_id
486
487    def restoration_end(self):
488        """Register a restoration context as finished, if one exists"""
489        try:
490            del self.restoration_context_id.request_id
491        except AttributeError:
492            pass
493
494    def in_restoration(self):
495        """Determine if a restoration context is active for the current thread.
496        Returns the request_id it's active for if so, otherwise False"""
497        return getattr(self.restoration_context_id, 'request_id', False)
498
499restorer = StackedObjectRestorer()
500
501
502# Paste Deploy entry point
503def make_registry_manager(app, global_conf):
504    return RegistryManager(app)
505
506make_registry_manager.__doc__ = RegistryManager.__doc__
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。