Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/Paste/paste/response.py
0001# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
0002# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
0003"""Routines to generate WSGI responses"""
0004
0005############################################################
0006## Headers
0007############################################################
0008import warnings
0009
0010class HeaderDict(dict):
0011
0012    """
0013    This represents response headers.  It handles the headers as a
0014    dictionary, with case-insensitive keys.
0015
0016    Also there is an ``.add(key, value)`` method, which sets the key,
0017    or adds the value to the current value (turning it into a list if
0018    necessary).
0019
0020    For passing to WSGI there is a ``.headeritems()`` method which is
0021    like ``.items()`` but unpacks value that are lists.  It also
0022    handles encoding -- all headers are encoded in ASCII (if they are
0023    unicode).
0024
0025    @@: Should that encoding be ISO-8859-1 or UTF-8?  I'm not sure
0026    what the spec says.
0027    """
0028
0029    def __getitem__(self, key):
0030        return dict.__getitem__(self, self.normalize(key))
0031
0032    def __setitem__(self, key, value):
0033        dict.__setitem__(self, self.normalize(key), value)
0034
0035    def __delitem__(self, key):
0036        dict.__delitem__(self, self.normalize(key))
0037
0038    def __contains__(self, key):
0039        return dict.__contains__(self, self.normalize(key))
0040
0041    has_key = __contains__
0042
0043    def get(self, key, failobj=None):
0044        return dict.get(self, self.normalize(key), failobj)
0045
0046    def setdefault(self, key, failobj=None):
0047        return dict.setdefault(self, self.normalize(key), failobj)
0048
0049    def pop(self, key):
0050        return dict.pop(self, self.normalize(key))
0051
0052    def update(self, other):
0053        for key in other:
0054            self[self.normalize(key)] = other[key]
0055
0056    def normalize(self, key):
0057        return str(key).lower().strip()
0058
0059    def add(self, key, value):
0060        key = self.normalize(key)
0061        if key in self:
0062            if isinstance(self[key], list):
0063                self[key].append(value)
0064            else:
0065                self[key] = [self[key], value]
0066        else:
0067            self[key] = value
0068
0069    def headeritems(self):
0070        result = []
0071        for key in self:
0072            if isinstance(self[key], list):
0073                for v in self[key]:
0074                    result.append((key, str(v)))
0075            else:
0076                result.append((key, str(self[key])))
0077        return result
0078
0079    #@classmethod
0080    def fromlist(cls, seq):
0081        self = cls()
0082        for name, value in seq:
0083            self.add(name, value)
0084        return self
0085
0086    fromlist = classmethod(fromlist)
0087
0088def has_header(headers, name):
0089    """
0090    Is header named ``name`` present in headers?
0091    """
0092    name = name.lower()
0093    for header, value in headers:
0094        if header.lower() == name:
0095            return True
0096    return False
0097
0098def header_value(headers, name):
0099    """
0100    Returns the header's value, or None if no such header.  If a
0101    header appears more than once, all the values of the headers
0102    are joined with ','.   Note that this is consistent /w RFC 2616
0103    section 4.2 which states:
0104
0105        It MUST be possible to combine the multiple header fields
0106        into one "field-name: field-value" pair, without changing
0107        the semantics of the message, by appending each subsequent
0108        field-value to the first, each separated by a comma.
0109
0110    However, note that the original netscape usage of 'Set-Cookie',
0111    especially in MSIE which contains an 'expires' date will is not
0112    compatible with this particular concatination method.
0113    """
0114    name = name.lower()
0115    result = [value for header, value in headers
0116              if header.lower() == name]
0117    if result:
0118        return ','.join(result)
0119    else:
0120        return None
0121
0122def remove_header(headers, name):
0123    """
0124    Removes the named header from the list of headers.  Returns the
0125    value of that header, or None if no header found.  If multiple
0126    headers are found, only the last one is returned.
0127    """
0128    name = name.lower()
0129    i = 0
0130    result = None
0131    while i < len(headers):
0132        if headers[i][0].lower() == name:
0133            result = headers[i][1]
0134            del headers[i]
0135            continue
0136        i += 1
0137    return result
0138
0139def replace_header(headers, name, value):
0140    """
0141    Updates the headers replacing the first occurance of the given name
0142    with the value provided; asserting that no further occurances
0143    happen. Note that this is _not_ the same as remove_header and then
0144    append, as two distinct operations (del followed by an append) are
0145    not atomic in a threaded environment. Returns the previous header
0146    value for the provided name, if any.   Clearly one should not use
0147    this function with ``set-cookie`` or other names that may have more
0148    than one occurance in the headers.
0149    """
0150    name = name.lower()
0151    i = 0
0152    result = None
0153    while i < len(headers):
0154        if headers[i][0].lower() == name:
0155            assert not result, "two values for the header '%s' found" % name
0156            result = headers[i][1]
0157            headers[i] = (name, value)
0158        i += 1
0159    if not result:
0160        headers.append((name, value))
0161    return result
0162
0163
0164############################################################
0165## Deprecated methods
0166############################################################
0167
0168def error_body_response(error_code, message, __warn=True):
0169    """
0170    Returns a standard HTML response page for an HTTP error.
0171    **Note:** Deprecated
0172    """
0173    if __warn:
0174        warnings.warn(
0175            'wsgilib.error_body_response is deprecated; use the '
0176            'wsgi_application method on an HTTPException object '
0177            'instead', DeprecationWarning, 2)
0178    return '''\
0179<html>
0180  <head>
0181    <title>%(error_code)s</title>
0182  </head>
0183  <body>
0184  <h1>%(error_code)s</h1>
0185  %(message)s
0186  </body>
0187</html>''' % {
0188        'error_code': error_code,
0189        'message': message,
0190        }
0191
0192
0193def error_response(environ, error_code, message,
0194                   debug_message=None, __warn=True):
0195    """
0196    Returns the status, headers, and body of an error response.
0197
0198    Use like:
0199    
0200    .. code-block:: Python
0201
0202        status, headers, body = wsgilib.error_response(
0203            '301 Moved Permanently', 'Moved to <a href="%s">%s</a>'
0204            % (url, url))
0205        start_response(status, headers)
0206        return [body]
0207    
0208    **Note:** Deprecated
0209    """
0210    if __warn:
0211        warnings.warn(
0212            'wsgilib.error_response is deprecated; use the '
0213            'wsgi_application method on an HTTPException object '
0214            'instead', DeprecationWarning, 2)
0215    if debug_message and environ.get('paste.config', {}).get('debug'):
0216        message += '\n\n<!-- %s -->' % debug_message
0217    body = error_body_response(error_code, message, __warn=False)
0218    headers = [('content-type', 'text/html'),
0219               ('content-length', str(len(body)))]
0220    return error_code, headers, body
0221
0222def error_response_app(error_code, message, debug_message=None,
0223                       __warn=True):
0224    """
0225    An application that emits the given error response.
0226
0227    **Note:** Deprecated
0228    """
0229    if __warn:
0230        warnings.warn(
0231            'wsgilib.error_response_app is deprecated; use the '
0232            'wsgi_application method on an HTTPException object '
0233            'instead', DeprecationWarning, 2)
0234    def application(environ, start_response):
0235        status, headers, body = error_response(
0236            environ, error_code, message,
0237            debug_message=debug_message, __warn=False)
0238        start_response(status, headers)
0239        return [body]
0240    return application

Top