diff options
Diffstat (limited to 'creactiweb/_templates/lib/werkzeug/utils.py')
-rw-r--r-- | creactiweb/_templates/lib/werkzeug/utils.py | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/creactiweb/_templates/lib/werkzeug/utils.py b/creactiweb/_templates/lib/werkzeug/utils.py new file mode 100644 index 0000000..072de7e --- /dev/null +++ b/creactiweb/_templates/lib/werkzeug/utils.py @@ -0,0 +1,674 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.utils + ~~~~~~~~~~~~~~ + + This module implements various utilities for WSGI applications. Most of + them are used by the request and response wrappers but especially for + middleware development it makes sense to use them without the wrappers. + + :copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" +import re +import os +from time import time +from datetime import datetime, timedelta + +from werkzeug._internal import _decode_unicode, \ + _iter_modules, _ExtendedCookie, _ExtendedMorsel, \ + _DictAccessorProperty, _dump_date, _parse_signature, _missing + + +_format_re = re.compile(r'\$(?:(%s)|\{(%s)\})' % (('[a-zA-Z_][a-zA-Z0-9_]*',) * 2)) +_entity_re = re.compile(r'&([^;]+);') +_filename_ascii_strip_re = re.compile(r'[^A-Za-z0-9_.-]') +_windows_device_files = ('CON', 'AUX', 'COM1', 'COM2', 'COM3', 'COM4', 'LPT1', + 'LPT2', 'LPT3', 'PRN', 'NUL') + + +class cached_property(object): + """A decorator that converts a function into a lazy property. The + function wrapped is called the first time to retrieve the result + and then that calculated result is used the next time you access + the value:: + + class Foo(object): + + @cached_property + def foo(self): + # calculate something important here + return 42 + + The class has to have a `__dict__` in order for this property to + work. + + .. versionchanged:: 0.6 + the `writeable` attribute and parameter was deprecated. If a + cached property is writeable or not has to be documented now. + For performance reasons the implementation does not honor the + writeable setting and will always make the property writeable. + """ + + # implementation detail: this property is implemented as non-data + # descriptor. non-data descriptors are only invoked if there is + # no entry with the same name in the instance's __dict__. + # this allows us to completely get rid of the access function call + # overhead. If one choses to invoke __get__ by hand the property + # will still work as expected because the lookup logic is replicated + # in __get__ for manual invocation. + + def __init__(self, func, name=None, doc=None, writeable=False): + if writeable: + from warnings import warn + warn(DeprecationWarning('the writeable argument to the ' + 'cached property is a noop since 0.6 ' + 'because the property is writeable ' + 'by default for performance reasons')) + + self.__name__ = name or func.__name__ + self.__module__ = func.__module__ + self.__doc__ = doc or func.__doc__ + self.func = func + + def __get__(self, obj, type=None): + if obj is None: + return self + value = obj.__dict__.get(self.__name__, _missing) + if value is _missing: + value = self.func(obj) + obj.__dict__[self.__name__] = value + return value + + +class environ_property(_DictAccessorProperty): + """Maps request attributes to environment variables. This works not only + for the Werzeug request object, but also any other class with an + environ attribute: + + >>> class Test(object): + ... environ = {'key': 'value'} + ... test = environ_property('key') + >>> var = Test() + >>> var.test + 'value' + + If you pass it a second value it's used as default if the key does not + exist, the third one can be a converter that takes a value and converts + it. If it raises :exc:`ValueError` or :exc:`TypeError` the default value + is used. If no default value is provided `None` is used. + + Per default the property is read only. You have to explicitly enable it + by passing ``read_only=False`` to the constructor. + """ + + read_only = True + + def lookup(self, obj): + return obj.environ + + +class header_property(_DictAccessorProperty): + """Like `environ_property` but for headers.""" + + def lookup(self, obj): + return obj.headers + + +class HTMLBuilder(object): + """Helper object for HTML generation. + + Per default there are two instances of that class. The `html` one, and + the `xhtml` one for those two dialects. The class uses keyword parameters + and positional parameters to generate small snippets of HTML. + + Keyword parameters are converted to XML/SGML attributes, positional + arguments are used as children. Because Python accepts positional + arguments before keyword arguments it's a good idea to use a list with the + star-syntax for some children: + + >>> html.p(class_='foo', *[html.a('foo', href='foo.html'), ' ', + ... html.a('bar', href='bar.html')]) + u'<p class="foo"><a href="foo.html">foo</a> <a href="bar.html">bar</a></p>' + + This class works around some browser limitations and can not be used for + arbitrary SGML/XML generation. For that purpose lxml and similar + libraries exist. + + Calling the builder escapes the string passed: + + >>> html.p(html("<foo>")) + u'<p><foo></p>' + """ + + from htmlentitydefs import name2codepoint + _entity_re = re.compile(r'&([^;]+);') + _entities = name2codepoint.copy() + _entities['apos'] = 39 + _empty_elements = set([ + 'area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', + 'input', 'isindex', 'link', 'meta', 'param' + ]) + _boolean_attributes = set([ + 'selected', 'checked', 'compact', 'declare', 'defer', 'disabled', + 'ismap', 'multiple', 'nohref', 'noresize', 'noshade', 'nowrap' + ]) + _plaintext_elements = set(['textarea']) + _c_like_cdata = set(['script', 'style']) + del name2codepoint + + def __init__(self, dialect): + self._dialect = dialect + + def __call__(self, s): + return escape(s) + + def __getattr__(self, tag): + if tag[:2] == '__': + raise AttributeError(tag) + def proxy(*children, **arguments): + buffer = ['<' + tag] + write = buffer.append + for key, value in arguments.iteritems(): + if value is None: + continue + if key.endswith('_'): + key = key[:-1] + if key in self._boolean_attributes: + if not value: + continue + value = self._dialect == 'xhtml' and '="%s"' % key or '' + else: + value = '="%s"' % escape(value, True) + write(' ' + key + value) + if not children and tag in self._empty_elements: + write(self._dialect == 'xhtml' and ' />' or '>') + return ''.join(buffer) + write('>') + children_as_string = ''.join(unicode(x) for x in children + if x is not None) + if children_as_string: + if tag in self._plaintext_elements: + children_as_string = escape(children_as_string) + elif tag in self._c_like_cdata and self._dialect == 'xhtml': + children_as_string = '/*<![CDATA[*/%s/*]]>*/' % \ + children_as_string + buffer.extend((children_as_string, '</%s>' % tag)) + return ''.join(buffer) + return proxy + + def __repr__(self): + return '<%s for %r>' % ( + self.__class__.__name__, + self._dialect + ) + + +html = HTMLBuilder('html') +xhtml = HTMLBuilder('xhtml') + + +def get_content_type(mimetype, charset): + """Return the full content type string with charset for a mimetype. + + If the mimetype represents text the charset will be appended as charset + parameter, otherwise the mimetype is returned unchanged. + + :param mimetype: the mimetype to be used as content type. + :param charset: the charset to be appended in case it was a text mimetype. + :return: the content type. + """ + if mimetype.startswith('text/') or \ + mimetype == 'application/xml' or \ + (mimetype.startswith('application/') and + mimetype.endswith('+xml')): + mimetype += '; charset=' + charset + return mimetype + + +def format_string(string, context): + """String-template format a string: + + >>> format_string('$foo and ${foo}s', dict(foo=42)) + '42 and 42s' + + This does not do any attribute lookup etc. For more advanced string + formattings have a look at the `werkzeug.template` module. + + :param string: the format string. + :param context: a dict with the variables to insert. + """ + def lookup_arg(match): + x = context[match.group(1) or match.group(2)] + if not isinstance(x, basestring): + x = type(string)(x) + return x + return _format_re.sub(lookup_arg, string) + + +def secure_filename(filename): + r"""Pass it a filename and it will return a secure version of it. This + filename can then safely be stored on a regular file system and passed + to :func:`os.path.join`. The filename returned is an ASCII only string + for maximum portability. + + On windows system the function also makes sure that the file is not + named after one of the special device files. + + >>> secure_filename("My cool movie.mov") + 'My_cool_movie.mov' + >>> secure_filename("../../../etc/passwd") + 'etc_passwd' + >>> secure_filename(u'i contain cool \xfcml\xe4uts.txt') + 'i_contain_cool_umlauts.txt' + + The function might return an empty filename. It's your responsibility + to ensure that the filename is unique and that you generate random + filename if the function returned an empty one. + + .. versionadded:: 0.5 + + :param filename: the filename to secure + """ + if isinstance(filename, unicode): + from unicodedata import normalize + filename = normalize('NFKD', filename).encode('ascii', 'ignore') + for sep in os.path.sep, os.path.altsep: + if sep: + filename = filename.replace(sep, ' ') + filename = str(_filename_ascii_strip_re.sub('', '_'.join( + filename.split()))).strip('._') + + # on nt a couple of special files are present in each folder. We + # have to ensure that the target file is not such a filename. In + # this case we prepend an underline + if os.name == 'nt' and filename and \ + filename.split('.')[0].upper() in _windows_device_files: + filename = '_' + filename + + return filename + + +def escape(s, quote=False): + """Replace special characters "&", "<" and ">" to HTML-safe sequences. If + the optional flag `quote` is `True`, the quotation mark character (") is + also translated. + + There is a special handling for `None` which escapes to an empty string. + + :param s: the string to escape. + :param quote: set to true to also escape double quotes. + """ + if s is None: + return '' + elif hasattr(s, '__html__'): + return s.__html__() + elif not isinstance(s, basestring): + s = unicode(s) + s = s.replace('&', '&').replace('<', '<').replace('>', '>') + if quote: + s = s.replace('"', """) + return s + + +def unescape(s): + """The reverse function of `escape`. This unescapes all the HTML + entities, not only the XML entities inserted by `escape`. + + :param s: the string to unescape. + """ + def handle_match(m): + name = m.group(1) + if name in HTMLBuilder._entities: + return unichr(HTMLBuilder._entities[name]) + try: + if name[:2] in ('#x', '#X'): + return unichr(int(name[2:], 16)) + elif name.startswith('#'): + return unichr(int(name[1:])) + except ValueError: + pass + return u'' + return _entity_re.sub(handle_match, s) + + +def cookie_date(expires=None): + """Formats the time to ensure compatibility with Netscape's cookie + standard. + + Accepts a floating point number expressed in seconds since the epoch in, a + datetime object or a timetuple. All times in UTC. The :func:`parse_date` + function can be used to parse such a date. + + Outputs a string in the format ``Wdy, DD-Mon-YYYY HH:MM:SS GMT``. + + :param expires: If provided that date is used, otherwise the current. + """ + return _dump_date(expires, '-') + + +def parse_cookie(header, charset='utf-8', errors='ignore', + cls=None): + """Parse a cookie. Either from a string or WSGI environ. + + Per default encoding errors are ignored. If you want a different behavior + you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a + :exc:`HTTPUnicodeError` is raised. + + .. versionchanged:: 0.5 + This function now returns a :class:`TypeConversionDict` instead of a + regular dict. The `cls` parameter was added. + + :param header: the header to be used to parse the cookie. Alternatively + this can be a WSGI environment. + :param charset: the charset for the cookie values. + :param errors: the error behavior for the charset decoding. + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`TypeConversionDict` is + used. + """ + if isinstance(header, dict): + header = header.get('HTTP_COOKIE', '') + if cls is None: + cls = TypeConversionDict + cookie = _ExtendedCookie() + cookie.load(header) + result = {} + + # decode to unicode and skip broken items. Our extended morsel + # and extended cookie will catch CookieErrors and convert them to + # `None` items which we have to skip here. + for key, value in cookie.iteritems(): + if value.value is not None: + result[key] = _decode_unicode(unquote_header_value(value.value), + charset, errors) + + return cls(result) + + +def dump_cookie(key, value='', max_age=None, expires=None, path='/', + domain=None, secure=None, httponly=False, charset='utf-8', + sync_expires=True): + """Creates a new Set-Cookie header without the ``Set-Cookie`` prefix + The parameters are the same as in the cookie Morsel object in the + Python standard library but it accepts unicode data, too. + + :param max_age: should be a number of seconds, or `None` (default) if + the cookie should last only as long as the client's + browser session. Additionally `timedelta` objects + are accepted, too. + :param expires: should be a `datetime` object or unix timestamp. + :param path: limits the cookie to a given path, per default it will + span the whole domain. + :param domain: Use this if you want to set a cross-domain cookie. For + example, ``domain=".example.com"`` will set a cookie + that is readable by the domain ``www.example.com``, + ``foo.example.com`` etc. Otherwise, a cookie will only + be readable by the domain that set it. + :param secure: The cookie will only be available via HTTPS + :param httponly: disallow JavaScript to access the cookie. This is an + extension to the cookie standard and probably not + supported by all browsers. + :param charset: the encoding for unicode values. + :param sync_expires: automatically set expires if max_age is defined + but expires not. + """ + try: + key = str(key) + except UnicodeError: + raise TypeError('invalid key %r' % key) + if isinstance(value, unicode): + value = value.encode(charset) + value = quote_header_value(value) + morsel = _ExtendedMorsel(key, value) + if isinstance(max_age, timedelta): + max_age = (max_age.days * 60 * 60 * 24) + max_age.seconds + if expires is not None: + if not isinstance(expires, basestring): + expires = cookie_date(expires) + morsel['expires'] = expires + elif max_age is not None and sync_expires: + morsel['expires'] = cookie_date(time() + max_age) + for k, v in (('path', path), ('domain', domain), ('secure', secure), + ('max-age', max_age), ('httponly', httponly)): + if v is not None and v is not False: + morsel[k] = str(v) + return morsel.output(header='').lstrip() + + +def http_date(timestamp=None): + """Formats the time to match the RFC1123 date format. + + Accepts a floating point number expressed in seconds since the epoch in, a + datetime object or a timetuple. All times in UTC. The :func:`parse_date` + function can be used to parse such a date. + + Outputs a string in the format ``Wdy, DD Mon YYYY HH:MM:SS GMT``. + + :param timestamp: If provided that date is used, otherwise the current. + """ + return _dump_date(timestamp, ' ') + + +def redirect(location, code=302): + """Return a response object (a WSGI application) that, if called, + redirects the client to the target location. Supported codes are 301, + 302, 303, 305, and 307. 300 is not supported because it's not a real + redirect and 304 because it's the answer for a request with a request + with defined If-Modified-Since headers. + + .. versionadded:: 0.6 + The location can now be a unicode string that is encoded using + the :func:`iri_to_uri` function. + + :param location: the location the response should redirect to. + :param code: the redirect status code. + """ + assert code in (301, 302, 303, 305, 307), 'invalid code' + from werkzeug.wrappers import BaseResponse + display_location = location + if isinstance(location, unicode): + from werkzeug.urls import iri_to_uri + location = iri_to_uri(location) + response = BaseResponse( + '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n' + '<title>Redirecting...</title>\n' + '<h1>Redirecting...</h1>\n' + '<p>You should be redirected automatically to target URL: ' + '<a href="%s">%s</a>. If not click the link.' % + (location, display_location), code, mimetype='text/html') + response.headers['Location'] = location + return response + + +def append_slash_redirect(environ, code=301): + """Redirect to the same URL but with a slash appended. The behavior + of this function is undefined if the path ends with a slash already. + + :param environ: the WSGI environment for the request that triggers + the redirect. + :param code: the status code for the redirect. + """ + new_path = environ['PATH_INFO'].strip('/') + '/' + query_string = environ.get('QUERY_STRING') + if query_string: + new_path += '?' + query_string + return redirect(new_path, code) + + +def import_string(import_name, silent=False): + """Imports an object based on a string. This is useful if you want to + use import paths as endpoints or something similar. An import path can + be specified either in dotted notation (``xml.sax.saxutils.escape``) + or with a colon as object delimiter (``xml.sax.saxutils:escape``). + + If `silent` is True the return value will be `None` if the import fails. + + :param import_name: the dotted name for the object to import. + :param silent: if set to `True` import errors are ignored and + `None` is returned instead. + :return: imported object + """ + # force the import name to automatically convert to strings + if isinstance(import_name, unicode): + import_name = str(import_name) + try: + if ':' in import_name: + module, obj = import_name.split(':', 1) + elif '.' in import_name: + module, obj = import_name.rsplit('.', 1) + else: + return __import__(import_name) + # __import__ is not able to handle unicode strings in the fromlist + # if the module is a package + if isinstance(obj, unicode): + obj = obj.encode('utf-8') + return getattr(__import__(module, None, None, [obj]), obj) + except (ImportError, AttributeError): + if not silent: + raise + + +def find_modules(import_path, include_packages=False, recursive=False): + """Find all the modules below a package. This can be useful to + automatically import all views / controllers so that their metaclasses / + function decorators have a chance to register themselves on the + application. + + Packages are not returned unless `include_packages` is `True`. This can + also recursively list modules but in that case it will import all the + packages to get the correct load path of that module. + + :param import_name: the dotted name for the package to find child modules. + :param include_packages: set to `True` if packages should be returned, too. + :param recursive: set to `True` if recursion should happen. + :return: generator + """ + module = import_string(import_path) + path = getattr(module, '__path__', None) + if path is None: + raise ValueError('%r is not a package' % import_path) + basename = module.__name__ + '.' + for modname, ispkg in _iter_modules(path): + modname = basename + modname + if ispkg: + if include_packages: + yield modname + if recursive: + for item in find_modules(modname, include_packages, True): + yield item + else: + yield modname + + +def validate_arguments(func, args, kwargs, drop_extra=True): + """Check if the function accepts the arguments and keyword arguments. + Returns a new ``(args, kwargs)`` tuple that can safely be passed to + the function without causing a `TypeError` because the function signature + is incompatible. If `drop_extra` is set to `True` (which is the default) + any extra positional or keyword arguments are dropped automatically. + + The exception raised provides three attributes: + + `missing` + A set of argument names that the function expected but where + missing. + + `extra` + A dict of keyword arguments that the function can not handle but + where provided. + + `extra_positional` + A list of values that where given by positional argument but the + function cannot accept. + + This can be useful for decorators that forward user submitted data to + a view function:: + + from werkzeug import ArgumentValidationError, validate_arguments + + def sanitize(f): + def proxy(request): + data = request.values.to_dict() + try: + args, kwargs = validate_arguments(f, (request,), data) + except ArgumentValidationError: + raise BadRequest('The browser failed to transmit all ' + 'the data expected.') + return f(*args, **kwargs) + return proxy + + :param func: the function the validation is performed against. + :param args: a tuple of positional arguments. + :param kwargs: a dict of keyword arguments. + :param drop_extra: set to `False` if you don't want extra arguments + to be silently dropped. + :return: tuple in the form ``(args, kwargs)``. + """ + parser = _parse_signature(func) + args, kwargs, missing, extra, extra_positional = parser(args, kwargs)[:5] + if missing: + raise ArgumentValidationError(tuple(missing)) + elif (extra or extra_positional) and not drop_extra: + raise ArgumentValidationError(None, extra, extra_positional) + return tuple(args), kwargs + + +def bind_arguments(func, args, kwargs): + """Bind the arguments provided into a dict. When passed a function, + a tuple of arguments and a dict of keyword arguments `bind_arguments` + returns a dict of names as the function would see it. This can be useful + to implement a cache decorator that uses the function arguments to build + the cache key based on the values of the arguments. + + :param func: the function the arguments should be bound for. + :param args: tuple of positional arguments. + :param kwargs: a dict of keyword arguments. + :return: a :class:`dict` of bound keyword arguments. + """ + args, kwargs, missing, extra, extra_positional, \ + arg_spec, vararg_var, kwarg_var = _parse_signature(func)(args, kwargs) + values = {} + for (name, has_default, default), value in zip(arg_spec, args): + values[name] = value + if vararg_var is not None: + values[vararg_var] = tuple(extra_positional) + elif extra_positional: + raise TypeError('too many positional arguments') + if kwarg_var is not None: + multikw = set(extra) & set([x[0] for x in arg_spec]) + if multikw: + raise TypeError('got multiple values for keyword argument ' + + repr(iter(multikw).next())) + values[kwarg_var] = extra + elif extra: + raise TypeError('got unexpected keyword argument ' + + repr(iter(extra).next())) + return values + + +class ArgumentValidationError(ValueError): + """Raised if :func:`validate_arguments` fails to validate""" + + def __init__(self, missing=None, extra=None, extra_positional=None): + self.missing = set(missing or ()) + self.extra = extra or {} + self.extra_positional = extra_positional or [] + ValueError.__init__(self, 'function arguments invalid. (' + '%d missing, %d additional)' % ( + len(self.missing), + len(self.extra) + len(self.extra_positional) + )) + + +# circular dependencies +from werkzeug.http import quote_header_value, unquote_header_value +from werkzeug.exceptions import BadRequest +from werkzeug.datastructures import TypeConversionDict + + +# DEPRECATED +# these objects were previously in this module as well. we import +# them here for backwards compatibility with old pickles. +from werkzeug.datastructures import MultiDict, CombinedMultiDict, \ + Headers, EnvironHeaders |