Back to index

moin  1.9.0~rc2
_internal.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 """
00003     werkzeug._internal
00004     ~~~~~~~~~~~~~~~~~~
00005 
00006     This module provides internally used helpers and constants.
00007 
00008     :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
00009     :license: BSD, see LICENSE for more details.
00010 """
00011 import inspect
00012 from weakref import WeakKeyDictionary
00013 from cStringIO import StringIO
00014 from Cookie import BaseCookie, Morsel, CookieError
00015 from time import gmtime
00016 from datetime import datetime
00017 
00018 
00019 _logger = None
00020 _empty_stream = StringIO('')
00021 _signature_cache = WeakKeyDictionary()
00022 
00023 
00024 HTTP_STATUS_CODES = {
00025     100:    'Continue',
00026     101:    'Switching Protocols',
00027     102:    'Processing',
00028     200:    'OK',
00029     201:    'Created',
00030     202:    'Accepted',
00031     203:    'Non Authoritative Information',
00032     204:    'No Content',
00033     205:    'Reset Content',
00034     206:    'Partial Content',
00035     207:    'Multi Status',
00036     226:    'IM Used',              # see RFC 3229
00037     300:    'Multiple Choices',
00038     301:    'Moved Permanently',
00039     302:    'Found',
00040     303:    'See Other',
00041     304:    'Not Modified',
00042     305:    'Use Proxy',
00043     307:    'Temporary Redirect',
00044     400:    'Bad Request',
00045     401:    'Unauthorized',
00046     402:    'Payment Required',     # unused
00047     403:    'Forbidden',
00048     404:    'Not Found',
00049     405:    'Method Not Allowed',
00050     406:    'Not Acceptable',
00051     407:    'Proxy Authentication Required',
00052     408:    'Request Timeout',
00053     409:    'Conflict',
00054     410:    'Gone',
00055     411:    'Length Required',
00056     412:    'Precondition Failed',
00057     413:    'Request Entity Too Large',
00058     414:    'Request URI Too Long',
00059     415:    'Unsupported Media Type',
00060     416:    'Requested Range Not Satisfiable',
00061     417:    'Expectation Failed',
00062     418:    'I\'m a teapot',        # see RFC 2324
00063     422:    'Unprocessable Entity',
00064     423:    'Locked',
00065     424:    'Failed Dependency',
00066     426:    'Upgrade Required',
00067     449:    'Retry With',           # propritary MS extension
00068     500:    'Internal Server Error',
00069     501:    'Not Implemented',
00070     502:    'Bad Gateway',
00071     503:    'Service Unavailable',
00072     504:    'Gateway Timeout',
00073     505:    'HTTP Version Not Supported',
00074     507:    'Insufficient Storage',
00075     510:    'Not Extended'
00076 }
00077 
00078 
00079 class _Missing(object):
00080 
00081     def __repr__(self):
00082         return 'no value'
00083 
00084     def __reduce__(self):
00085         return '_missing'
00086 
00087 _missing = _Missing()
00088 
00089 
00090 def _proxy_repr(cls):
00091     def proxy_repr(self):
00092         return '%s(%s)' % (self.__class__.__name__, cls.__repr__(self))
00093     return proxy_repr
00094 
00095 
00096 def _log(type, message, *args, **kwargs):
00097     """Log into the internal werkzeug logger."""
00098     global _logger
00099     if _logger is None:
00100         import logging
00101         _logger = logging.getLogger('werkzeug')
00102         if _logger.level == logging.NOTSET:
00103             _logger.setLevel(logging.INFO)
00104             handler = logging.StreamHandler()
00105             _logger.addHandler(handler)
00106     getattr(_logger, type)(message.rstrip(), *args, **kwargs)
00107 
00108 
00109 def _parse_signature(func):
00110     """Return a signature object for the function."""
00111     if hasattr(func, 'im_func'):
00112         func = func.im_func
00113 
00114     # if we have a cached validator for this function, return it
00115     parse = _signature_cache.get(func)
00116     if parse is not None:
00117         return parse
00118 
00119     # inspect the function signature and collect all the information
00120     positional, vararg_var, kwarg_var, defaults = inspect.getargspec(func)
00121     defaults = defaults or ()
00122     arg_count = len(positional)
00123     arguments = []
00124     for idx, name in enumerate(positional):
00125         if isinstance(name, list):
00126             raise TypeError('cannot parse functions that unpack tuples '
00127                             'in the function signature')
00128         try:
00129             default = defaults[idx - arg_count]
00130         except IndexError:
00131             param = (name, False, None)
00132         else:
00133             param = (name, True, default)
00134         arguments.append(param)
00135     arguments = tuple(arguments)
00136 
00137     def parse(args, kwargs):
00138         new_args = []
00139         missing = []
00140         extra = {}
00141 
00142         # consume as many arguments as positional as possible
00143         for idx, (name, has_default, default) in enumerate(arguments):
00144             try:
00145                 new_args.append(args[idx])
00146             except IndexError:
00147                 try:
00148                     new_args.append(kwargs.pop(name))
00149                 except KeyError:
00150                     if has_default:
00151                         new_args.append(default)
00152                     else:
00153                         missing.append(name)
00154             else:
00155                 if name in kwargs:
00156                     extra[name] = kwargs.pop(name)
00157 
00158         # handle extra arguments
00159         extra_positional = args[arg_count:]
00160         if vararg_var is not None:
00161             new_args.extend(extra_positional)
00162             extra_positional = ()
00163         if kwargs and not kwarg_var is not None:
00164             extra.update(kwargs)
00165             kwargs = {}
00166 
00167         return new_args, kwargs, missing, extra, extra_positional, \
00168                arguments, vararg_var, kwarg_var
00169     _signature_cache[func] = parse
00170     return parse
00171 
00172 
00173 def _patch_wrapper(old, new):
00174     """Helper function that forwards all the function details to the
00175     decorated function."""
00176     try:
00177         new.__name__ = old.__name__
00178         new.__module__ = old.__module__
00179         new.__doc__ = old.__doc__
00180         new.__dict__ = old.__dict__
00181     except:
00182         pass
00183     return new
00184 
00185 
00186 def _decode_unicode(value, charset, errors):
00187     """Like the regular decode function but this one raises an
00188     `HTTPUnicodeError` if errors is `strict`."""
00189     fallback = None
00190     if errors.startswith('fallback:'):
00191         fallback = errors[9:]
00192         errors = 'strict'
00193     try:
00194         return value.decode(charset, errors)
00195     except UnicodeError, e:
00196         if fallback is not None:
00197             return value.decode(fallback, 'ignore')
00198         from werkzeug.exceptions import HTTPUnicodeError
00199         raise HTTPUnicodeError(str(e))
00200 
00201 
00202 def _iter_modules(path):
00203     """Iterate over all modules in a package."""
00204     import os
00205     import pkgutil
00206     if hasattr(pkgutil, 'iter_modules'):
00207         for importer, modname, ispkg in pkgutil.iter_modules(path):
00208             yield modname, ispkg
00209         return
00210     from inspect import getmodulename
00211     from pydoc import ispackage
00212     found = set()
00213     for path in path:
00214         for filename in os.listdir(path):
00215             p = os.path.join(path, filename)
00216             modname = getmodulename(filename)
00217             if modname and modname != '__init__':
00218                 if modname not in found:
00219                     found.add(modname)
00220                     yield modname, ispackage(modname)
00221 
00222 
00223 def _dump_date(d, delim):
00224     """Used for `http_date` and `cookie_date`."""
00225     if d is None:
00226         d = gmtime()
00227     elif isinstance(d, datetime):
00228         d = d.utctimetuple()
00229     elif isinstance(d, (int, long, float)):
00230         d = gmtime(d)
00231     return '%s, %02d%s%s%s%s %02d:%02d:%02d GMT' % (
00232         ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')[d.tm_wday],
00233         d.tm_mday, delim,
00234         ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
00235          'Oct', 'Nov', 'Dec')[d.tm_mon - 1],
00236         delim, str(d.tm_year), d.tm_hour, d.tm_min, d.tm_sec
00237     )
00238 
00239 
00240 _timegm = None
00241 def _date_to_unix(arg):
00242     """Converts a timetuple, integer or datetime object into the seconds from
00243     epoch in utc.
00244     """
00245     global _timegm
00246     if isinstance(arg, datetime):
00247         arg = arg.utctimetuple()
00248     elif isinstance(arg, (int, long, float)):
00249         return int(arg)
00250     if _timegm is None:
00251         from calendar import timegm as _timegm
00252     return _timegm(arg)
00253 
00254 
00255 class _ExtendedMorsel(Morsel):
00256     _reserved = {'httponly': 'HttpOnly'}
00257     _reserved.update(Morsel._reserved)
00258 
00259     def __init__(self, name=None, value=None):
00260         Morsel.__init__(self)
00261         if name is not None:
00262             self.set(name, value, value)
00263 
00264     def OutputString(self, attrs=None):
00265         httponly = self.pop('httponly', False)
00266         result = Morsel.OutputString(self, attrs).rstrip('\t ;')
00267         if httponly:
00268             result += '; HttpOnly'
00269         return result
00270 
00271 
00272 class _ExtendedCookie(BaseCookie):
00273     """Form of the base cookie that doesn't raise a `CookieError` for
00274     malformed keys.  This has the advantage that broken cookies submitted
00275     by nonstandard browsers don't cause the cookie to be empty.
00276     """
00277 
00278     def _BaseCookie__set(self, key, real_value, coded_value):
00279         morsel = self.get(key, _ExtendedMorsel())
00280         try:
00281             morsel.set(key, real_value, coded_value)
00282         except CookieError:
00283             pass
00284         dict.__setitem__(self, key, morsel)
00285 
00286 
00287 class _DictAccessorProperty(object):
00288     """Baseclass for `environ_property` and `header_property`."""
00289     read_only = False
00290 
00291     def __init__(self, name, default=None, load_func=None, dump_func=None,
00292                  read_only=None, doc=None):
00293         self.name = name
00294         self.default = default
00295         self.load_func = load_func
00296         self.dump_func = dump_func
00297         if read_only is not None:
00298             self.read_only = read_only
00299         self.__doc__ = doc
00300 
00301     def __get__(self, obj, type=None):
00302         if obj is None:
00303             return self
00304         storage = self.lookup(obj)
00305         if self.name not in storage:
00306             return self.default
00307         rv = storage[self.name]
00308         if self.load_func is not None:
00309             try:
00310                 rv = self.load_func(rv)
00311             except (ValueError, TypeError):
00312                 rv = self.default
00313         return rv
00314 
00315     def __set__(self, obj, value):
00316         if self.read_only:
00317             raise AttributeError('read only property')
00318         if self.dump_func is not None:
00319             value = self.dump_func(value)
00320         self.lookup(obj)[self.name] = value
00321 
00322     def __delete__(self, obj):
00323         if self.read_only:
00324             raise AttributeError('read only property')
00325         self.lookup(obj).pop(self.name, None)
00326 
00327     def __repr__(self):
00328         return '<%s %s>' % (
00329             self.__class__.__name__,
00330             self.name
00331         )
00332 
00333 
00334 def _easteregg(app):
00335     """Like the name says.  But who knows how it works?"""
00336     gyver = '\n'.join([x + (77 - len(x)) * ' ' for x in '''
00337 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
00367 7f2zLkGNv8b191cD/3vs9Q833z8t'''.decode('base64').decode('zlib').splitlines()])
00368     def easteregged(environ, start_response):
00369         def injecting_start_response(status, headers, exc_info=None):
00370             headers.append(('X-Powered-By', 'Werkzeug'))
00371             return start_response(status, headers, exc_info)
00372         if environ.get('QUERY_STRING') != 'macgybarchakku':
00373             return app(environ, injecting_start_response)
00374         injecting_start_response('200 OK', [('Content-Type', 'text/html')])
00375         return ['''<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
00376 <title>About Werkzeug</>
00377 <style type="text/css">
00378   body { font: 15px Georgia, serif; text-align: center; }
00379   a { color: #333; text-decoration: none; }
00380   h1 { font-size: 30px; margin: 20px 0 10px 0; }
00381   p { margin: 0 0 30px 0; }
00382   pre { font: 11px 'Consolas', 'Monaco', monospace; line-height: 0.95; }
00383 </style>
00384 <h1><a href="http://werkzeug.pocoo.org/">Werkzeug</a></h1>
00385 <p>the Swiss Army knife of Python web development.
00386 <pre>%s\n\n\n</>''' % gyver]
00387     return easteregged