diff options
| -rw-r--r-- | CONTRIBUTORS.txt | 4 | ||||
| -rw-r--r-- | COPYRIGHT.txt | 1 | ||||
| -rw-r--r-- | docs/narr/project.rst | 11 | ||||
| -rw-r--r-- | repoze/bfg/functional.py | 257 | ||||
| -rw-r--r-- | repoze/bfg/wsgi.py | 10 |
5 files changed, 280 insertions, 3 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt new file mode 100644 index 000000000..4e0081457 --- /dev/null +++ b/CONTRIBUTORS.txt @@ -0,0 +1,4 @@ +Chris McDonough +Paul Everitt +Tres Seaver +Carlos de la Guardia diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index b024da853..1e5b8aff0 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -1,3 +1,4 @@ Copyright (c) 2008 Agendaless Consulting and Contributors. (http://www.agendaless.com), All Rights Reserved +Portions (c) Django Project (http://djangoproject.com). diff --git a/docs/narr/project.rst b/docs/narr/project.rst index a050f8824..2ff8011ee 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -136,6 +136,17 @@ port 6543. development easier, as changes to Python code under :mod:`repoze.bfg` is not put into effect until the server restarts. +.. note:: When :mod:`repoze.bfg` starts, it writes a ``.pck`` file. + In a typical setup this file will be written as + ``configure.zcml.pck`` in the same directory that your + application's ``configure.zcml`` is stored. This is temporary, + cached data that can help your :mod:`repoze.bfg` application start + slightly faster (its existence prevents the need to parse the XML + in the .zcml file). You can delete it at will as necessary; it + will be recreated. If it cannot be written due to filesystem + permissions, :mod:`repoze.bfg` will just reparse the .zcml file + every time it starts. + Viewing the Application ----------------------- diff --git a/repoze/bfg/functional.py b/repoze/bfg/functional.py new file mode 100644 index 000000000..2b6fb6dbf --- /dev/null +++ b/repoze/bfg/functional.py @@ -0,0 +1,257 @@ +# This file was lifted wholesale from Django <http://djangoproject.com> +# (see http://code.djangoproject.com/browser/django/trunk/LICENSE for +# license text; BSD-like) + +# License for code in this file that was taken from Python 2.5. + +# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +# -------------------------------------------- +# +# 1. This LICENSE AGREEMENT is between the Python Software Foundation +# ("PSF"), and the Individual or Organization ("Licensee") accessing and +# otherwise using this software ("Python") in source or binary form and +# its associated documentation. +# +# 2. Subject to the terms and conditions of this License Agreement, PSF +# hereby grants Licensee a nonexclusive, royalty-free, world-wide +# license to reproduce, analyze, test, perform and/or display publicly, +# prepare derivative works, distribute, and otherwise use Python +# alone or in any derivative version, provided, however, that PSF's +# License Agreement and PSF's notice of copyright, i.e., "Copyright (c) +# 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation; +# All Rights Reserved" are retained in Python alone or in any derivative +# version prepared by Licensee. +# +# 3. In the event Licensee prepares a derivative work that is based on +# or incorporates Python or any part thereof, and wants to make +# the derivative work available to others as provided herein, then +# Licensee hereby agrees to include in any such work a brief summary of +# the changes made to Python. +# +# 4. PSF is making Python available to Licensee on an "AS IS" +# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +# INFRINGE ANY THIRD PARTY RIGHTS. +# +# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +# +# 6. This License Agreement will automatically terminate upon a material +# breach of its terms and conditions. +# +# 7. Nothing in this License Agreement shall be deemed to create any +# relationship of agency, partnership, or joint venture between PSF and +# Licensee. This License Agreement does not grant permission to use PSF +# trademarks or trade name in a trademark sense to endorse or promote +# products or services of Licensee, or any third party. +# +# 8. By copying, installing or otherwise using Python, Licensee +# agrees to be bound by the terms and conditions of this License +# Agreement. + + +def curry(_curried_func, *args, **kwargs): + def _curried(*moreargs, **morekwargs): + return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs)) + return _curried + +### Begin from Python 2.5 functools.py ######################################## + +# Summary of changes made to the Python 2.5 code below: +# * swapped ``partial`` for ``curry`` to maintain backwards-compatibility +# in Django. +# * Wrapped the ``setattr`` call in ``update_wrapper`` with a try-except +# block to make it compatible with Python 2.3, which doesn't allow +# assigning to ``__name__``. + +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation. +# All Rights Reserved. + +############################################################################### + +# update_wrapper() and wraps() are tools to help write +# wrapper functions that can handle naive introspection + +WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__') +WRAPPER_UPDATES = ('__dict__',) +def update_wrapper(wrapper, + wrapped, + assigned = WRAPPER_ASSIGNMENTS, + updated = WRAPPER_UPDATES): + """Update a wrapper function to look like the wrapped function + + wrapper is the function to be updated + wrapped is the original function + assigned is a tuple naming the attributes assigned directly + from the wrapped function to the wrapper function (defaults to + functools.WRAPPER_ASSIGNMENTS) + updated is a tuple naming the attributes off the wrapper that + are updated with the corresponding attribute from the wrapped + function (defaults to functools.WRAPPER_UPDATES) + """ + for attr in assigned: + try: + setattr(wrapper, attr, getattr(wrapped, attr)) + except TypeError: # Python 2.3 doesn't allow assigning to __name__. + pass + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr)) + # Return the wrapper so this can be used as a decorator via curry() + return wrapper + +def wraps(wrapped, + assigned = WRAPPER_ASSIGNMENTS, + updated = WRAPPER_UPDATES): + """Decorator factory to apply update_wrapper() to a wrapper function + + Returns a decorator that invokes update_wrapper() with the decorated + function as the wrapper argument and the arguments to wraps() as the + remaining arguments. Default arguments are as for update_wrapper(). + This is a convenience function to simplify applying curry() to + update_wrapper(). + """ + return curry(update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + +### End from Python 2.5 functools.py ########################################## + +def memoize(func, cache, num_args): + """ + Wrap a function so that results for any argument tuple are stored in + 'cache'. Note that the args to the function must be usable as dictionary + keys. + + Only the first num_args are considered when creating the key. + """ + def wrapper(*args): + mem_args = args[:num_args] + if mem_args in cache: + return cache[mem_args] + result = func(*args) + cache[mem_args] = result + return result + return wraps(func)(wrapper) + +class Promise(object): + """ + This is just a base class for the proxy class created in + the closure of the lazy function. It can be used to recognize + promises in code. + """ + pass + +def lazy(func, *resultclasses): + """ + Turns any callable into a lazy evaluated callable. You need to give result + classes or types -- at least one is needed so that the automatic forcing of + the lazy evaluation code is triggered. Results are not memoized; the + function is evaluated on every access. + """ + class __proxy__(Promise): + """ + Encapsulate a function call and act as a proxy for methods that are + called on the result of that function. The function is not evaluated + until one of the methods on the result is called. + """ + __dispatch = None + + def __init__(self, args, kw): + self.__func = func + self.__args = args + self.__kw = kw + if self.__dispatch is None: + self.__prepare_class__() + + def __prepare_class__(cls): + cls.__dispatch = {} + for resultclass in resultclasses: + cls.__dispatch[resultclass] = {} + for (k, v) in resultclass.__dict__.items(): + if hasattr(cls, k): + continue + setattr(cls, k, cls.__promise__(resultclass, k, v)) + cls._delegate_str = str in resultclasses + cls._delegate_unicode = unicode in resultclasses + assert not (cls._delegate_str and cls._delegate_unicode), "Cannot call lazy() with both str and unicode return types." + if cls._delegate_unicode: + cls.__unicode__ = cls.__unicode_cast + elif cls._delegate_str: + cls.__str__ = cls.__str_cast + __prepare_class__ = classmethod(__prepare_class__) + + def __promise__(cls, klass, funcname, func): + # Builds a wrapper around some magic method and registers that magic + # method for the given type and method name. + def __wrapper__(self, *args, **kw): + # Automatically triggers the evaluation of a lazy value and + # applies the given magic method of the result type. + res = self.__func(*self.__args, **self.__kw) + for t in type(res).mro(): + if t in self.__dispatch: + return self.__dispatch[t][funcname](res, *args, **kw) + raise TypeError("Lazy object returned unexpected type.") + + if klass not in cls.__dispatch: + cls.__dispatch[klass] = {} + cls.__dispatch[klass][funcname] = func + return __wrapper__ + __promise__ = classmethod(__promise__) + + def __unicode_cast(self): + return self.__func(*self.__args, **self.__kw) + + def __str_cast(self): + return str(self.__func(*self.__args, **self.__kw)) + + def __cmp__(self, rhs): + if self._delegate_str: + s = str(self.__func(*self.__args, **self.__kw)) + elif self._delegate_unicode: + s = unicode(self.__func(*self.__args, **self.__kw)) + else: + s = self.__func(*self.__args, **self.__kw) + if isinstance(rhs, Promise): + return -cmp(rhs, s) + else: + return cmp(s, rhs) + + def __mod__(self, rhs): + if self._delegate_str: + return str(self) % rhs + elif self._delegate_unicode: + return unicode(self) % rhs + else: + raise AssertionError('__mod__ not supported for non-string types') + + def __deepcopy__(self, memo): + # Instances of this class are effectively immutable. It's just a + # collection of functions. So we don't need to do anything + # complicated for copying. + memo[id(self)] = self + return self + + def __wrapper__(*args, **kw): + # Creates the proxy object, instead of the actual value. + return __proxy__(args, kw) + + return wraps(func)(__wrapper__) + +def allow_lazy(func, *resultclasses): + """ + A decorator that allows a function to be called with one or more lazy + arguments. If none of the args are lazy, the function is evaluated + immediately, otherwise a __proxy__ is returned that will evaluate the + function when needed. + """ + def wrapper(*args, **kwargs): + for arg in list(args) + kwargs.values(): + if isinstance(arg, Promise): + break + else: + return func(*args, **kwargs) + return lazy(func, *resultclasses)(*args, **kwargs) + return wraps(func)(wrapper) diff --git a/repoze/bfg/wsgi.py b/repoze/bfg/wsgi.py index 4efe40afc..2a23fe7aa 100644 --- a/repoze/bfg/wsgi.py +++ b/repoze/bfg/wsgi.py @@ -1,4 +1,9 @@ from webob import Response +try: + from functools import wraps +except ImportError: + # < 2.5 + from repoze.bfg.functional import wraps def wsgiapp(wrapped): """ Decorator to turn a WSGI application into a repoze.bfg view callable. @@ -24,7 +29,7 @@ def wsgiapp(wrapped): application to a Response and return it to repoze.bfg as if the WSGI app were a repoze.bfg view. """ - def _curried(context, request): + def decorator(context, request): caught = [] def catch_start_response(status, headers, exc_info=None): caught[:] = (status, headers, exc_info) @@ -39,6 +44,5 @@ def wsgiapp(wrapped): return response else: raise RuntimeError('WSGI start_response not called') - _curried.__name__ = wrapped.__name__ - return _curried + return wraps(wrapped)(decorator) # for pickleability |
