Code import
This commit is contained in:
151
venv/lib/python2.7/site-packages/werkzeug/__init__.py
Normal file
151
venv/lib/python2.7/site-packages/werkzeug/__init__.py
Normal file
@@ -0,0 +1,151 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug
|
||||
~~~~~~~~
|
||||
|
||||
Werkzeug is the Swiss Army knife of Python web development.
|
||||
|
||||
It provides useful classes and functions for any WSGI application to make
|
||||
the life of a python web developer much easier. All of the provided
|
||||
classes are independent from each other so you can mix it with any other
|
||||
library.
|
||||
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from types import ModuleType
|
||||
import sys
|
||||
|
||||
from werkzeug._compat import iteritems
|
||||
|
||||
__version__ = '0.12.2'
|
||||
|
||||
|
||||
# This import magic raises concerns quite often which is why the implementation
|
||||
# and motivation is explained here in detail now.
|
||||
#
|
||||
# The majority of the functions and classes provided by Werkzeug work on the
|
||||
# HTTP and WSGI layer. There is no useful grouping for those which is why
|
||||
# they are all importable from "werkzeug" instead of the modules where they are
|
||||
# implemented. The downside of that is, that now everything would be loaded at
|
||||
# once, even if unused.
|
||||
#
|
||||
# The implementation of a lazy-loading module in this file replaces the
|
||||
# werkzeug package when imported from within. Attribute access to the werkzeug
|
||||
# module will then lazily import from the modules that implement the objects.
|
||||
|
||||
|
||||
# import mapping to objects in other modules
|
||||
all_by_module = {
|
||||
'werkzeug.debug': ['DebuggedApplication'],
|
||||
'werkzeug.local': ['Local', 'LocalManager', 'LocalProxy', 'LocalStack',
|
||||
'release_local'],
|
||||
'werkzeug.serving': ['run_simple'],
|
||||
'werkzeug.test': ['Client', 'EnvironBuilder', 'create_environ',
|
||||
'run_wsgi_app'],
|
||||
'werkzeug.testapp': ['test_app'],
|
||||
'werkzeug.exceptions': ['abort', 'Aborter'],
|
||||
'werkzeug.urls': ['url_decode', 'url_encode', 'url_quote',
|
||||
'url_quote_plus', 'url_unquote', 'url_unquote_plus',
|
||||
'url_fix', 'Href', 'iri_to_uri', 'uri_to_iri'],
|
||||
'werkzeug.formparser': ['parse_form_data'],
|
||||
'werkzeug.utils': ['escape', 'environ_property', 'append_slash_redirect',
|
||||
'redirect', 'cached_property', 'import_string',
|
||||
'dump_cookie', 'parse_cookie', 'unescape',
|
||||
'format_string', 'find_modules', 'header_property',
|
||||
'html', 'xhtml', 'HTMLBuilder', 'validate_arguments',
|
||||
'ArgumentValidationError', 'bind_arguments',
|
||||
'secure_filename'],
|
||||
'werkzeug.wsgi': ['get_current_url', 'get_host', 'pop_path_info',
|
||||
'peek_path_info', 'SharedDataMiddleware',
|
||||
'DispatcherMiddleware', 'ClosingIterator', 'FileWrapper',
|
||||
'make_line_iter', 'LimitedStream', 'responder',
|
||||
'wrap_file', 'extract_path_info'],
|
||||
'werkzeug.datastructures': ['MultiDict', 'CombinedMultiDict', 'Headers',
|
||||
'EnvironHeaders', 'ImmutableList',
|
||||
'ImmutableDict', 'ImmutableMultiDict',
|
||||
'TypeConversionDict',
|
||||
'ImmutableTypeConversionDict', 'Accept',
|
||||
'MIMEAccept', 'CharsetAccept',
|
||||
'LanguageAccept', 'RequestCacheControl',
|
||||
'ResponseCacheControl', 'ETags', 'HeaderSet',
|
||||
'WWWAuthenticate', 'Authorization',
|
||||
'FileMultiDict', 'CallbackDict', 'FileStorage',
|
||||
'OrderedMultiDict', 'ImmutableOrderedMultiDict'
|
||||
],
|
||||
'werkzeug.useragents': ['UserAgent'],
|
||||
'werkzeug.http': ['parse_etags', 'parse_date', 'http_date', 'cookie_date',
|
||||
'parse_cache_control_header', 'is_resource_modified',
|
||||
'parse_accept_header', 'parse_set_header', 'quote_etag',
|
||||
'unquote_etag', 'generate_etag', 'dump_header',
|
||||
'parse_list_header', 'parse_dict_header',
|
||||
'parse_authorization_header',
|
||||
'parse_www_authenticate_header', 'remove_entity_headers',
|
||||
'is_entity_header', 'remove_hop_by_hop_headers',
|
||||
'parse_options_header', 'dump_options_header',
|
||||
'is_hop_by_hop_header', 'unquote_header_value',
|
||||
'quote_header_value', 'HTTP_STATUS_CODES'],
|
||||
'werkzeug.wrappers': ['BaseResponse', 'BaseRequest', 'Request', 'Response',
|
||||
'AcceptMixin', 'ETagRequestMixin',
|
||||
'ETagResponseMixin', 'ResponseStreamMixin',
|
||||
'CommonResponseDescriptorsMixin', 'UserAgentMixin',
|
||||
'AuthorizationMixin', 'WWWAuthenticateMixin',
|
||||
'CommonRequestDescriptorsMixin'],
|
||||
'werkzeug.security': ['generate_password_hash', 'check_password_hash'],
|
||||
# the undocumented easteregg ;-)
|
||||
'werkzeug._internal': ['_easteregg']
|
||||
}
|
||||
|
||||
# modules that should be imported when accessed as attributes of werkzeug
|
||||
attribute_modules = frozenset(['exceptions', 'routing', 'script'])
|
||||
|
||||
|
||||
object_origins = {}
|
||||
for module, items in iteritems(all_by_module):
|
||||
for item in items:
|
||||
object_origins[item] = module
|
||||
|
||||
|
||||
class module(ModuleType):
|
||||
|
||||
"""Automatically import objects from the modules."""
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in object_origins:
|
||||
module = __import__(object_origins[name], None, None, [name])
|
||||
for extra_name in all_by_module[module.__name__]:
|
||||
setattr(self, extra_name, getattr(module, extra_name))
|
||||
return getattr(module, name)
|
||||
elif name in attribute_modules:
|
||||
__import__('werkzeug.' + name)
|
||||
return ModuleType.__getattribute__(self, name)
|
||||
|
||||
def __dir__(self):
|
||||
"""Just show what we want to show."""
|
||||
result = list(new_module.__all__)
|
||||
result.extend(('__file__', '__path__', '__doc__', '__all__',
|
||||
'__docformat__', '__name__', '__path__',
|
||||
'__package__', '__version__'))
|
||||
return result
|
||||
|
||||
# keep a reference to this module so that it's not garbage collected
|
||||
old_module = sys.modules['werkzeug']
|
||||
|
||||
|
||||
# setup the new module and patch it into the dict of loaded modules
|
||||
new_module = sys.modules['werkzeug'] = module('werkzeug')
|
||||
new_module.__dict__.update({
|
||||
'__file__': __file__,
|
||||
'__package__': 'werkzeug',
|
||||
'__path__': __path__,
|
||||
'__doc__': __doc__,
|
||||
'__version__': __version__,
|
||||
'__all__': tuple(object_origins) + tuple(attribute_modules),
|
||||
'__docformat__': 'restructuredtext en'
|
||||
})
|
||||
|
||||
|
||||
# Due to bootstrapping issues we need to import exceptions here.
|
||||
# Don't ask :-(
|
||||
__import__('werkzeug.exceptions')
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/__init__.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/__init__.pyc
Normal file
Binary file not shown.
206
venv/lib/python2.7/site-packages/werkzeug/_compat.py
Normal file
206
venv/lib/python2.7/site-packages/werkzeug/_compat.py
Normal file
@@ -0,0 +1,206 @@
|
||||
# flake8: noqa
|
||||
# This whole file is full of lint errors
|
||||
import codecs
|
||||
import sys
|
||||
import operator
|
||||
import functools
|
||||
import warnings
|
||||
|
||||
try:
|
||||
import builtins
|
||||
except ImportError:
|
||||
import __builtin__ as builtins
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
WIN = sys.platform.startswith('win')
|
||||
|
||||
_identity = lambda x: x
|
||||
|
||||
if PY2:
|
||||
unichr = unichr
|
||||
text_type = unicode
|
||||
string_types = (str, unicode)
|
||||
integer_types = (int, long)
|
||||
|
||||
iterkeys = lambda d, *args, **kwargs: d.iterkeys(*args, **kwargs)
|
||||
itervalues = lambda d, *args, **kwargs: d.itervalues(*args, **kwargs)
|
||||
iteritems = lambda d, *args, **kwargs: d.iteritems(*args, **kwargs)
|
||||
|
||||
iterlists = lambda d, *args, **kwargs: d.iterlists(*args, **kwargs)
|
||||
iterlistvalues = lambda d, *args, **kwargs: d.iterlistvalues(*args, **kwargs)
|
||||
|
||||
int_to_byte = chr
|
||||
iter_bytes = iter
|
||||
|
||||
exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
|
||||
|
||||
def fix_tuple_repr(obj):
|
||||
def __repr__(self):
|
||||
cls = self.__class__
|
||||
return '%s(%s)' % (cls.__name__, ', '.join(
|
||||
'%s=%r' % (field, self[index])
|
||||
for index, field in enumerate(cls._fields)
|
||||
))
|
||||
obj.__repr__ = __repr__
|
||||
return obj
|
||||
|
||||
def implements_iterator(cls):
|
||||
cls.next = cls.__next__
|
||||
del cls.__next__
|
||||
return cls
|
||||
|
||||
def implements_to_string(cls):
|
||||
cls.__unicode__ = cls.__str__
|
||||
cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
|
||||
return cls
|
||||
|
||||
def native_string_result(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs).encode('utf-8')
|
||||
return functools.update_wrapper(wrapper, func)
|
||||
|
||||
def implements_bool(cls):
|
||||
cls.__nonzero__ = cls.__bool__
|
||||
del cls.__bool__
|
||||
return cls
|
||||
|
||||
from itertools import imap, izip, ifilter
|
||||
range_type = xrange
|
||||
|
||||
from StringIO import StringIO
|
||||
from cStringIO import StringIO as BytesIO
|
||||
NativeStringIO = BytesIO
|
||||
|
||||
def make_literal_wrapper(reference):
|
||||
return _identity
|
||||
|
||||
def normalize_string_tuple(tup):
|
||||
"""Normalizes a string tuple to a common type. Following Python 2
|
||||
rules, upgrades to unicode are implicit.
|
||||
"""
|
||||
if any(isinstance(x, text_type) for x in tup):
|
||||
return tuple(to_unicode(x) for x in tup)
|
||||
return tup
|
||||
|
||||
def try_coerce_native(s):
|
||||
"""Try to coerce a unicode string to native if possible. Otherwise,
|
||||
leave it as unicode.
|
||||
"""
|
||||
try:
|
||||
return to_native(s)
|
||||
except UnicodeError:
|
||||
return s
|
||||
|
||||
wsgi_get_bytes = _identity
|
||||
|
||||
def wsgi_decoding_dance(s, charset='utf-8', errors='replace'):
|
||||
return s.decode(charset, errors)
|
||||
|
||||
def wsgi_encoding_dance(s, charset='utf-8', errors='replace'):
|
||||
if isinstance(s, bytes):
|
||||
return s
|
||||
return s.encode(charset, errors)
|
||||
|
||||
def to_bytes(x, charset=sys.getdefaultencoding(), errors='strict'):
|
||||
if x is None:
|
||||
return None
|
||||
if isinstance(x, (bytes, bytearray, buffer)):
|
||||
return bytes(x)
|
||||
if isinstance(x, unicode):
|
||||
return x.encode(charset, errors)
|
||||
raise TypeError('Expected bytes')
|
||||
|
||||
def to_native(x, charset=sys.getdefaultencoding(), errors='strict'):
|
||||
if x is None or isinstance(x, str):
|
||||
return x
|
||||
return x.encode(charset, errors)
|
||||
|
||||
else:
|
||||
unichr = chr
|
||||
text_type = str
|
||||
string_types = (str, )
|
||||
integer_types = (int, )
|
||||
|
||||
iterkeys = lambda d, *args, **kwargs: iter(d.keys(*args, **kwargs))
|
||||
itervalues = lambda d, *args, **kwargs: iter(d.values(*args, **kwargs))
|
||||
iteritems = lambda d, *args, **kwargs: iter(d.items(*args, **kwargs))
|
||||
|
||||
iterlists = lambda d, *args, **kwargs: iter(d.lists(*args, **kwargs))
|
||||
iterlistvalues = lambda d, *args, **kwargs: iter(d.listvalues(*args, **kwargs))
|
||||
|
||||
int_to_byte = operator.methodcaller('to_bytes', 1, 'big')
|
||||
iter_bytes = functools.partial(map, int_to_byte)
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
fix_tuple_repr = _identity
|
||||
implements_iterator = _identity
|
||||
implements_to_string = _identity
|
||||
implements_bool = _identity
|
||||
native_string_result = _identity
|
||||
imap = map
|
||||
izip = zip
|
||||
ifilter = filter
|
||||
range_type = range
|
||||
|
||||
from io import StringIO, BytesIO
|
||||
NativeStringIO = StringIO
|
||||
|
||||
_latin1_encode = operator.methodcaller('encode', 'latin1')
|
||||
|
||||
def make_literal_wrapper(reference):
|
||||
if isinstance(reference, text_type):
|
||||
return _identity
|
||||
return _latin1_encode
|
||||
|
||||
def normalize_string_tuple(tup):
|
||||
"""Ensures that all types in the tuple are either strings
|
||||
or bytes.
|
||||
"""
|
||||
tupiter = iter(tup)
|
||||
is_text = isinstance(next(tupiter, None), text_type)
|
||||
for arg in tupiter:
|
||||
if isinstance(arg, text_type) != is_text:
|
||||
raise TypeError('Cannot mix str and bytes arguments (got %s)'
|
||||
% repr(tup))
|
||||
return tup
|
||||
|
||||
try_coerce_native = _identity
|
||||
wsgi_get_bytes = _latin1_encode
|
||||
|
||||
def wsgi_decoding_dance(s, charset='utf-8', errors='replace'):
|
||||
return s.encode('latin1').decode(charset, errors)
|
||||
|
||||
def wsgi_encoding_dance(s, charset='utf-8', errors='replace'):
|
||||
if isinstance(s, text_type):
|
||||
s = s.encode(charset)
|
||||
return s.decode('latin1', errors)
|
||||
|
||||
def to_bytes(x, charset=sys.getdefaultencoding(), errors='strict'):
|
||||
if x is None:
|
||||
return None
|
||||
if isinstance(x, (bytes, bytearray, memoryview)): # noqa
|
||||
return bytes(x)
|
||||
if isinstance(x, str):
|
||||
return x.encode(charset, errors)
|
||||
raise TypeError('Expected bytes')
|
||||
|
||||
def to_native(x, charset=sys.getdefaultencoding(), errors='strict'):
|
||||
if x is None or isinstance(x, str):
|
||||
return x
|
||||
return x.decode(charset, errors)
|
||||
|
||||
|
||||
def to_unicode(x, charset=sys.getdefaultencoding(), errors='strict',
|
||||
allow_none_charset=False):
|
||||
if x is None:
|
||||
return None
|
||||
if not isinstance(x, bytes):
|
||||
return text_type(x)
|
||||
if charset is None and allow_none_charset:
|
||||
return x
|
||||
return x.decode(charset, errors)
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/_compat.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/_compat.pyc
Normal file
Binary file not shown.
418
venv/lib/python2.7/site-packages/werkzeug/_internal.py
Normal file
418
venv/lib/python2.7/site-packages/werkzeug/_internal.py
Normal file
@@ -0,0 +1,418 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug._internal
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides internally used helpers and constants.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import string
|
||||
import inspect
|
||||
from weakref import WeakKeyDictionary
|
||||
from datetime import datetime, date
|
||||
from itertools import chain
|
||||
|
||||
from werkzeug._compat import iter_bytes, text_type, BytesIO, int_to_byte, \
|
||||
range_type, integer_types
|
||||
|
||||
|
||||
_logger = None
|
||||
_empty_stream = BytesIO()
|
||||
_signature_cache = WeakKeyDictionary()
|
||||
_epoch_ord = date(1970, 1, 1).toordinal()
|
||||
_cookie_params = set((b'expires', b'path', b'comment',
|
||||
b'max-age', b'secure', b'httponly',
|
||||
b'version'))
|
||||
_legal_cookie_chars = (string.ascii_letters +
|
||||
string.digits +
|
||||
u"!#$%&'*+-.^_`|~:").encode('ascii')
|
||||
|
||||
_cookie_quoting_map = {
|
||||
b',': b'\\054',
|
||||
b';': b'\\073',
|
||||
b'"': b'\\"',
|
||||
b'\\': b'\\\\',
|
||||
}
|
||||
for _i in chain(range_type(32), range_type(127, 256)):
|
||||
_cookie_quoting_map[int_to_byte(_i)] = ('\\%03o' % _i).encode('latin1')
|
||||
|
||||
|
||||
_octal_re = re.compile(b'\\\\[0-3][0-7][0-7]')
|
||||
_quote_re = re.compile(b'[\\\\].')
|
||||
_legal_cookie_chars_re = b'[\w\d!#%&\'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]'
|
||||
_cookie_re = re.compile(b"""
|
||||
(?P<key>[^=]+)
|
||||
\s*=\s*
|
||||
(?P<val>
|
||||
"(?:[^\\\\"]|\\\\.)*" |
|
||||
(?:.*?)
|
||||
)
|
||||
\s*;
|
||||
""", flags=re.VERBOSE)
|
||||
|
||||
|
||||
class _Missing(object):
|
||||
|
||||
def __repr__(self):
|
||||
return 'no value'
|
||||
|
||||
def __reduce__(self):
|
||||
return '_missing'
|
||||
|
||||
_missing = _Missing()
|
||||
|
||||
|
||||
def _get_environ(obj):
|
||||
env = getattr(obj, 'environ', obj)
|
||||
assert isinstance(env, dict), \
|
||||
'%r is not a WSGI environment (has to be a dict)' % type(obj).__name__
|
||||
return env
|
||||
|
||||
|
||||
def _log(type, message, *args, **kwargs):
|
||||
"""Log into the internal werkzeug logger."""
|
||||
global _logger
|
||||
if _logger is None:
|
||||
import logging
|
||||
_logger = logging.getLogger('werkzeug')
|
||||
# Only set up a default log handler if the
|
||||
# end-user application didn't set anything up.
|
||||
if not logging.root.handlers and _logger.level == logging.NOTSET:
|
||||
_logger.setLevel(logging.INFO)
|
||||
handler = logging.StreamHandler()
|
||||
_logger.addHandler(handler)
|
||||
getattr(_logger, type)(message.rstrip(), *args, **kwargs)
|
||||
|
||||
|
||||
def _parse_signature(func):
|
||||
"""Return a signature object for the function."""
|
||||
if hasattr(func, 'im_func'):
|
||||
func = func.im_func
|
||||
|
||||
# if we have a cached validator for this function, return it
|
||||
parse = _signature_cache.get(func)
|
||||
if parse is not None:
|
||||
return parse
|
||||
|
||||
# inspect the function signature and collect all the information
|
||||
if hasattr(inspect, 'getfullargspec'):
|
||||
tup = inspect.getfullargspec(func)
|
||||
else:
|
||||
tup = inspect.getargspec(func)
|
||||
positional, vararg_var, kwarg_var, defaults = tup[:4]
|
||||
defaults = defaults or ()
|
||||
arg_count = len(positional)
|
||||
arguments = []
|
||||
for idx, name in enumerate(positional):
|
||||
if isinstance(name, list):
|
||||
raise TypeError('cannot parse functions that unpack tuples '
|
||||
'in the function signature')
|
||||
try:
|
||||
default = defaults[idx - arg_count]
|
||||
except IndexError:
|
||||
param = (name, False, None)
|
||||
else:
|
||||
param = (name, True, default)
|
||||
arguments.append(param)
|
||||
arguments = tuple(arguments)
|
||||
|
||||
def parse(args, kwargs):
|
||||
new_args = []
|
||||
missing = []
|
||||
extra = {}
|
||||
|
||||
# consume as many arguments as positional as possible
|
||||
for idx, (name, has_default, default) in enumerate(arguments):
|
||||
try:
|
||||
new_args.append(args[idx])
|
||||
except IndexError:
|
||||
try:
|
||||
new_args.append(kwargs.pop(name))
|
||||
except KeyError:
|
||||
if has_default:
|
||||
new_args.append(default)
|
||||
else:
|
||||
missing.append(name)
|
||||
else:
|
||||
if name in kwargs:
|
||||
extra[name] = kwargs.pop(name)
|
||||
|
||||
# handle extra arguments
|
||||
extra_positional = args[arg_count:]
|
||||
if vararg_var is not None:
|
||||
new_args.extend(extra_positional)
|
||||
extra_positional = ()
|
||||
if kwargs and kwarg_var is None:
|
||||
extra.update(kwargs)
|
||||
kwargs = {}
|
||||
|
||||
return new_args, kwargs, missing, extra, extra_positional, \
|
||||
arguments, vararg_var, kwarg_var
|
||||
_signature_cache[func] = parse
|
||||
return parse
|
||||
|
||||
|
||||
def _date_to_unix(arg):
|
||||
"""Converts a timetuple, integer or datetime object into the seconds from
|
||||
epoch in utc.
|
||||
"""
|
||||
if isinstance(arg, datetime):
|
||||
arg = arg.utctimetuple()
|
||||
elif isinstance(arg, integer_types + (float,)):
|
||||
return int(arg)
|
||||
year, month, day, hour, minute, second = arg[:6]
|
||||
days = date(year, month, 1).toordinal() - _epoch_ord + day - 1
|
||||
hours = days * 24 + hour
|
||||
minutes = hours * 60 + minute
|
||||
seconds = minutes * 60 + second
|
||||
return seconds
|
||||
|
||||
|
||||
class _DictAccessorProperty(object):
|
||||
|
||||
"""Baseclass for `environ_property` and `header_property`."""
|
||||
read_only = False
|
||||
|
||||
def __init__(self, name, default=None, load_func=None, dump_func=None,
|
||||
read_only=None, doc=None):
|
||||
self.name = name
|
||||
self.default = default
|
||||
self.load_func = load_func
|
||||
self.dump_func = dump_func
|
||||
if read_only is not None:
|
||||
self.read_only = read_only
|
||||
self.__doc__ = doc
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return self
|
||||
storage = self.lookup(obj)
|
||||
if self.name not in storage:
|
||||
return self.default
|
||||
rv = storage[self.name]
|
||||
if self.load_func is not None:
|
||||
try:
|
||||
rv = self.load_func(rv)
|
||||
except (ValueError, TypeError):
|
||||
rv = self.default
|
||||
return rv
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if self.read_only:
|
||||
raise AttributeError('read only property')
|
||||
if self.dump_func is not None:
|
||||
value = self.dump_func(value)
|
||||
self.lookup(obj)[self.name] = value
|
||||
|
||||
def __delete__(self, obj):
|
||||
if self.read_only:
|
||||
raise AttributeError('read only property')
|
||||
self.lookup(obj).pop(self.name, None)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s>' % (
|
||||
self.__class__.__name__,
|
||||
self.name
|
||||
)
|
||||
|
||||
|
||||
def _cookie_quote(b):
|
||||
buf = bytearray()
|
||||
all_legal = True
|
||||
_lookup = _cookie_quoting_map.get
|
||||
_push = buf.extend
|
||||
|
||||
for char in iter_bytes(b):
|
||||
if char not in _legal_cookie_chars:
|
||||
all_legal = False
|
||||
char = _lookup(char, char)
|
||||
_push(char)
|
||||
|
||||
if all_legal:
|
||||
return bytes(buf)
|
||||
return bytes(b'"' + buf + b'"')
|
||||
|
||||
|
||||
def _cookie_unquote(b):
|
||||
if len(b) < 2:
|
||||
return b
|
||||
if b[:1] != b'"' or b[-1:] != b'"':
|
||||
return b
|
||||
|
||||
b = b[1:-1]
|
||||
|
||||
i = 0
|
||||
n = len(b)
|
||||
rv = bytearray()
|
||||
_push = rv.extend
|
||||
|
||||
while 0 <= i < n:
|
||||
o_match = _octal_re.search(b, i)
|
||||
q_match = _quote_re.search(b, i)
|
||||
if not o_match and not q_match:
|
||||
rv.extend(b[i:])
|
||||
break
|
||||
j = k = -1
|
||||
if o_match:
|
||||
j = o_match.start(0)
|
||||
if q_match:
|
||||
k = q_match.start(0)
|
||||
if q_match and (not o_match or k < j):
|
||||
_push(b[i:k])
|
||||
_push(b[k + 1:k + 2])
|
||||
i = k + 2
|
||||
else:
|
||||
_push(b[i:j])
|
||||
rv.append(int(b[j + 1:j + 4], 8))
|
||||
i = j + 4
|
||||
|
||||
return bytes(rv)
|
||||
|
||||
|
||||
def _cookie_parse_impl(b):
|
||||
"""Lowlevel cookie parsing facility that operates on bytes."""
|
||||
i = 0
|
||||
n = len(b)
|
||||
|
||||
while i < n:
|
||||
match = _cookie_re.search(b + b';', i)
|
||||
if not match:
|
||||
break
|
||||
|
||||
key = match.group('key').strip()
|
||||
value = match.group('val')
|
||||
i = match.end(0)
|
||||
|
||||
# Ignore parameters. We have no interest in them.
|
||||
if key.lower() not in _cookie_params:
|
||||
yield _cookie_unquote(key), _cookie_unquote(value)
|
||||
|
||||
|
||||
def _encode_idna(domain):
|
||||
# If we're given bytes, make sure they fit into ASCII
|
||||
if not isinstance(domain, text_type):
|
||||
domain.decode('ascii')
|
||||
return domain
|
||||
|
||||
# Otherwise check if it's already ascii, then return
|
||||
try:
|
||||
return domain.encode('ascii')
|
||||
except UnicodeError:
|
||||
pass
|
||||
|
||||
# Otherwise encode each part separately
|
||||
parts = domain.split('.')
|
||||
for idx, part in enumerate(parts):
|
||||
parts[idx] = part.encode('idna')
|
||||
return b'.'.join(parts)
|
||||
|
||||
|
||||
def _decode_idna(domain):
|
||||
# If the input is a string try to encode it to ascii to
|
||||
# do the idna decoding. if that fails because of an
|
||||
# unicode error, then we already have a decoded idna domain
|
||||
if isinstance(domain, text_type):
|
||||
try:
|
||||
domain = domain.encode('ascii')
|
||||
except UnicodeError:
|
||||
return domain
|
||||
|
||||
# Decode each part separately. If a part fails, try to
|
||||
# decode it with ascii and silently ignore errors. This makes
|
||||
# most sense because the idna codec does not have error handling
|
||||
parts = domain.split(b'.')
|
||||
for idx, part in enumerate(parts):
|
||||
try:
|
||||
parts[idx] = part.decode('idna')
|
||||
except UnicodeError:
|
||||
parts[idx] = part.decode('ascii', 'ignore')
|
||||
|
||||
return '.'.join(parts)
|
||||
|
||||
|
||||
def _make_cookie_domain(domain):
|
||||
if domain is None:
|
||||
return None
|
||||
domain = _encode_idna(domain)
|
||||
if b':' in domain:
|
||||
domain = domain.split(b':', 1)[0]
|
||||
if b'.' in domain:
|
||||
return domain
|
||||
raise ValueError(
|
||||
'Setting \'domain\' for a cookie on a server running locally (ex: '
|
||||
'localhost) is not supported by complying browsers. You should '
|
||||
'have something like: \'127.0.0.1 localhost dev.localhost\' on '
|
||||
'your hosts file and then point your server to run on '
|
||||
'\'dev.localhost\' and also set \'domain\' for \'dev.localhost\''
|
||||
)
|
||||
|
||||
|
||||
def _easteregg(app=None):
|
||||
"""Like the name says. But who knows how it works?"""
|
||||
def bzzzzzzz(gyver):
|
||||
import base64
|
||||
import zlib
|
||||
return zlib.decompress(base64.b64decode(gyver)).decode('ascii')
|
||||
gyver = u'\n'.join([x + (77 - len(x)) * u' ' for x in bzzzzzzz(b'''
|
||||
eJyFlzuOJDkMRP06xRjymKgDJCDQStBYT8BCgK4gTwfQ2fcFs2a2FzvZk+hvlcRvRJD148efHt9m
|
||||
9Xz94dRY5hGt1nrYcXx7us9qlcP9HHNh28rz8dZj+q4rynVFFPdlY4zH873NKCexrDM6zxxRymzz
|
||||
4QIxzK4bth1PV7+uHn6WXZ5C4ka/+prFzx3zWLMHAVZb8RRUxtFXI5DTQ2n3Hi2sNI+HK43AOWSY
|
||||
jmEzE4naFp58PdzhPMdslLVWHTGUVpSxImw+pS/D+JhzLfdS1j7PzUMxij+mc2U0I9zcbZ/HcZxc
|
||||
q1QjvvcThMYFnp93agEx392ZdLJWXbi/Ca4Oivl4h/Y1ErEqP+lrg7Xa4qnUKu5UE9UUA4xeqLJ5
|
||||
jWlPKJvR2yhRI7xFPdzPuc6adXu6ovwXwRPXXnZHxlPtkSkqWHilsOrGrvcVWXgGP3daXomCj317
|
||||
8P2UOw/NnA0OOikZyFf3zZ76eN9QXNwYdD8f8/LdBRFg0BO3bB+Pe/+G8er8tDJv83XTkj7WeMBJ
|
||||
v/rnAfdO51d6sFglfi8U7zbnr0u9tyJHhFZNXYfH8Iafv2Oa+DT6l8u9UYlajV/hcEgk1x8E8L/r
|
||||
XJXl2SK+GJCxtnyhVKv6GFCEB1OO3f9YWAIEbwcRWv/6RPpsEzOkXURMN37J0PoCSYeBnJQd9Giu
|
||||
LxYQJNlYPSo/iTQwgaihbART7Fcyem2tTSCcwNCs85MOOpJtXhXDe0E7zgZJkcxWTar/zEjdIVCk
|
||||
iXy87FW6j5aGZhttDBoAZ3vnmlkx4q4mMmCdLtnHkBXFMCReqthSGkQ+MDXLLCpXwBs0t+sIhsDI
|
||||
tjBB8MwqYQpLygZ56rRHHpw+OAVyGgaGRHWy2QfXez+ZQQTTBkmRXdV/A9LwH6XGZpEAZU8rs4pE
|
||||
1R4FQ3Uwt8RKEtRc0/CrANUoes3EzM6WYcFyskGZ6UTHJWenBDS7h163Eo2bpzqxNE9aVgEM2CqI
|
||||
GAJe9Yra4P5qKmta27VjzYdR04Vc7KHeY4vs61C0nbywFmcSXYjzBHdiEjraS7PGG2jHHTpJUMxN
|
||||
Jlxr3pUuFvlBWLJGE3GcA1/1xxLcHmlO+LAXbhrXah1tD6Ze+uqFGdZa5FM+3eHcKNaEarutAQ0A
|
||||
QMAZHV+ve6LxAwWnXbbSXEG2DmCX5ijeLCKj5lhVFBrMm+ryOttCAeFpUdZyQLAQkA06RLs56rzG
|
||||
8MID55vqr/g64Qr/wqwlE0TVxgoiZhHrbY2h1iuuyUVg1nlkpDrQ7Vm1xIkI5XRKLedN9EjzVchu
|
||||
jQhXcVkjVdgP2O99QShpdvXWoSwkp5uMwyjt3jiWCqWGSiaaPAzohjPanXVLbM3x0dNskJsaCEyz
|
||||
DTKIs+7WKJD4ZcJGfMhLFBf6hlbnNkLEePF8Cx2o2kwmYF4+MzAxa6i+6xIQkswOqGO+3x9NaZX8
|
||||
MrZRaFZpLeVTYI9F/djY6DDVVs340nZGmwrDqTCiiqD5luj3OzwpmQCiQhdRYowUYEA3i1WWGwL4
|
||||
GCtSoO4XbIPFeKGU13XPkDf5IdimLpAvi2kVDVQbzOOa4KAXMFlpi/hV8F6IDe0Y2reg3PuNKT3i
|
||||
RYhZqtkQZqSB2Qm0SGtjAw7RDwaM1roESC8HWiPxkoOy0lLTRFG39kvbLZbU9gFKFRvixDZBJmpi
|
||||
Xyq3RE5lW00EJjaqwp/v3EByMSpVZYsEIJ4APaHmVtpGSieV5CALOtNUAzTBiw81GLgC0quyzf6c
|
||||
NlWknzJeCsJ5fup2R4d8CYGN77mu5vnO1UqbfElZ9E6cR6zbHjgsr9ly18fXjZoPeDjPuzlWbFwS
|
||||
pdvPkhntFvkc13qb9094LL5NrA3NIq3r9eNnop9DizWOqCEbyRBFJTHn6Tt3CG1o8a4HevYh0XiJ
|
||||
sR0AVVHuGuMOIfbuQ/OKBkGRC6NJ4u7sbPX8bG/n5sNIOQ6/Y/BX3IwRlTSabtZpYLB85lYtkkgm
|
||||
p1qXK3Du2mnr5INXmT/78KI12n11EFBkJHHp0wJyLe9MvPNUGYsf+170maayRoy2lURGHAIapSpQ
|
||||
krEDuNoJCHNlZYhKpvw4mspVWxqo415n8cD62N9+EfHrAvqQnINStetek7RY2Urv8nxsnGaZfRr/
|
||||
nhXbJ6m/yl1LzYqscDZA9QHLNbdaSTTr+kFg3bC0iYbX/eQy0Bv3h4B50/SGYzKAXkCeOLI3bcAt
|
||||
mj2Z/FM1vQWgDynsRwNvrWnJHlespkrp8+vO1jNaibm+PhqXPPv30YwDZ6jApe3wUjFQobghvW9p
|
||||
7f2zLkGNv8b191cD/3vs9Q833z8t''').splitlines()])
|
||||
|
||||
def easteregged(environ, start_response):
|
||||
def injecting_start_response(status, headers, exc_info=None):
|
||||
headers.append(('X-Powered-By', 'Werkzeug'))
|
||||
return start_response(status, headers, exc_info)
|
||||
if app is not None and environ.get('QUERY_STRING') != 'macgybarchakku':
|
||||
return app(environ, injecting_start_response)
|
||||
injecting_start_response('200 OK', [('Content-Type', 'text/html')])
|
||||
return [(u'''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>About Werkzeug</title>
|
||||
<style type="text/css">
|
||||
body { font: 15px Georgia, serif; text-align: center; }
|
||||
a { color: #333; text-decoration: none; }
|
||||
h1 { font-size: 30px; margin: 20px 0 10px 0; }
|
||||
p { margin: 0 0 30px 0; }
|
||||
pre { font: 11px 'Consolas', 'Monaco', monospace; line-height: 0.95; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1><a href="http://werkzeug.pocoo.org/">Werkzeug</a></h1>
|
||||
<p>the Swiss Army knife of Python web development.</p>
|
||||
<pre>%s\n\n\n</pre>
|
||||
</body>
|
||||
</html>''' % gyver).encode('latin1')]
|
||||
return easteregged
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/_internal.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/_internal.pyc
Normal file
Binary file not shown.
267
venv/lib/python2.7/site-packages/werkzeug/_reloader.py
Normal file
267
venv/lib/python2.7/site-packages/werkzeug/_reloader.py
Normal file
@@ -0,0 +1,267 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
import threading
|
||||
from itertools import chain
|
||||
|
||||
from werkzeug._internal import _log
|
||||
from werkzeug._compat import PY2, iteritems, text_type
|
||||
|
||||
|
||||
def _iter_module_files():
|
||||
"""This iterates over all relevant Python files. It goes through all
|
||||
loaded files from modules, all files in folders of already loaded modules
|
||||
as well as all files reachable through a package.
|
||||
"""
|
||||
# The list call is necessary on Python 3 in case the module
|
||||
# dictionary modifies during iteration.
|
||||
for module in list(sys.modules.values()):
|
||||
if module is None:
|
||||
continue
|
||||
filename = getattr(module, '__file__', None)
|
||||
if filename:
|
||||
old = None
|
||||
while not os.path.isfile(filename):
|
||||
old = filename
|
||||
filename = os.path.dirname(filename)
|
||||
if filename == old:
|
||||
break
|
||||
else:
|
||||
if filename[-4:] in ('.pyc', '.pyo'):
|
||||
filename = filename[:-1]
|
||||
yield filename
|
||||
|
||||
|
||||
def _find_observable_paths(extra_files=None):
|
||||
"""Finds all paths that should be observed."""
|
||||
rv = set(os.path.abspath(x) for x in sys.path)
|
||||
|
||||
for filename in extra_files or ():
|
||||
rv.add(os.path.dirname(os.path.abspath(filename)))
|
||||
|
||||
for module in list(sys.modules.values()):
|
||||
fn = getattr(module, '__file__', None)
|
||||
if fn is None:
|
||||
continue
|
||||
fn = os.path.abspath(fn)
|
||||
rv.add(os.path.dirname(fn))
|
||||
|
||||
return _find_common_roots(rv)
|
||||
|
||||
|
||||
def _get_args_for_reloading():
|
||||
"""Returns the executable. This contains a workaround for windows
|
||||
if the executable is incorrectly reported to not have the .exe
|
||||
extension which can cause bugs on reloading.
|
||||
"""
|
||||
rv = [sys.executable]
|
||||
py_script = sys.argv[0]
|
||||
if os.name == 'nt' and not os.path.exists(py_script) and \
|
||||
os.path.exists(py_script + '.exe'):
|
||||
py_script += '.exe'
|
||||
rv.append(py_script)
|
||||
rv.extend(sys.argv[1:])
|
||||
return rv
|
||||
|
||||
|
||||
def _find_common_roots(paths):
|
||||
"""Out of some paths it finds the common roots that need monitoring."""
|
||||
paths = [x.split(os.path.sep) for x in paths]
|
||||
root = {}
|
||||
for chunks in sorted(paths, key=len, reverse=True):
|
||||
node = root
|
||||
for chunk in chunks:
|
||||
node = node.setdefault(chunk, {})
|
||||
node.clear()
|
||||
|
||||
rv = set()
|
||||
|
||||
def _walk(node, path):
|
||||
for prefix, child in iteritems(node):
|
||||
_walk(child, path + (prefix,))
|
||||
if not node:
|
||||
rv.add('/'.join(path))
|
||||
_walk(root, ())
|
||||
return rv
|
||||
|
||||
|
||||
class ReloaderLoop(object):
|
||||
name = None
|
||||
|
||||
# monkeypatched by testsuite. wrapping with `staticmethod` is required in
|
||||
# case time.sleep has been replaced by a non-c function (e.g. by
|
||||
# `eventlet.monkey_patch`) before we get here
|
||||
_sleep = staticmethod(time.sleep)
|
||||
|
||||
def __init__(self, extra_files=None, interval=1):
|
||||
self.extra_files = set(os.path.abspath(x)
|
||||
for x in extra_files or ())
|
||||
self.interval = interval
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def restart_with_reloader(self):
|
||||
"""Spawn a new Python interpreter with the same arguments as this one,
|
||||
but running the reloader thread.
|
||||
"""
|
||||
while 1:
|
||||
_log('info', ' * Restarting with %s' % self.name)
|
||||
args = _get_args_for_reloading()
|
||||
new_environ = os.environ.copy()
|
||||
new_environ['WERKZEUG_RUN_MAIN'] = 'true'
|
||||
|
||||
# a weird bug on windows. sometimes unicode strings end up in the
|
||||
# environment and subprocess.call does not like this, encode them
|
||||
# to latin1 and continue.
|
||||
if os.name == 'nt' and PY2:
|
||||
for key, value in iteritems(new_environ):
|
||||
if isinstance(value, text_type):
|
||||
new_environ[key] = value.encode('iso-8859-1')
|
||||
|
||||
exit_code = subprocess.call(args, env=new_environ,
|
||||
close_fds=False)
|
||||
if exit_code != 3:
|
||||
return exit_code
|
||||
|
||||
def trigger_reload(self, filename):
|
||||
self.log_reload(filename)
|
||||
sys.exit(3)
|
||||
|
||||
def log_reload(self, filename):
|
||||
filename = os.path.abspath(filename)
|
||||
_log('info', ' * Detected change in %r, reloading' % filename)
|
||||
|
||||
|
||||
class StatReloaderLoop(ReloaderLoop):
|
||||
name = 'stat'
|
||||
|
||||
def run(self):
|
||||
mtimes = {}
|
||||
while 1:
|
||||
for filename in chain(_iter_module_files(),
|
||||
self.extra_files):
|
||||
try:
|
||||
mtime = os.stat(filename).st_mtime
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
old_time = mtimes.get(filename)
|
||||
if old_time is None:
|
||||
mtimes[filename] = mtime
|
||||
continue
|
||||
elif mtime > old_time:
|
||||
self.trigger_reload(filename)
|
||||
self._sleep(self.interval)
|
||||
|
||||
|
||||
class WatchdogReloaderLoop(ReloaderLoop):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
ReloaderLoop.__init__(self, *args, **kwargs)
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
self.observable_paths = set()
|
||||
|
||||
def _check_modification(filename):
|
||||
if filename in self.extra_files:
|
||||
self.trigger_reload(filename)
|
||||
dirname = os.path.dirname(filename)
|
||||
if dirname.startswith(tuple(self.observable_paths)):
|
||||
if filename.endswith(('.pyc', '.pyo')):
|
||||
self.trigger_reload(filename[:-1])
|
||||
elif filename.endswith('.py'):
|
||||
self.trigger_reload(filename)
|
||||
|
||||
class _CustomHandler(FileSystemEventHandler):
|
||||
|
||||
def on_created(self, event):
|
||||
_check_modification(event.src_path)
|
||||
|
||||
def on_modified(self, event):
|
||||
_check_modification(event.src_path)
|
||||
|
||||
def on_moved(self, event):
|
||||
_check_modification(event.src_path)
|
||||
_check_modification(event.dest_path)
|
||||
|
||||
def on_deleted(self, event):
|
||||
_check_modification(event.src_path)
|
||||
|
||||
reloader_name = Observer.__name__.lower()
|
||||
if reloader_name.endswith('observer'):
|
||||
reloader_name = reloader_name[:-8]
|
||||
reloader_name += ' reloader'
|
||||
|
||||
self.name = reloader_name
|
||||
|
||||
self.observer_class = Observer
|
||||
self.event_handler = _CustomHandler()
|
||||
self.should_reload = False
|
||||
|
||||
def trigger_reload(self, filename):
|
||||
# This is called inside an event handler, which means throwing
|
||||
# SystemExit has no effect.
|
||||
# https://github.com/gorakhargosh/watchdog/issues/294
|
||||
self.should_reload = True
|
||||
self.log_reload(filename)
|
||||
|
||||
def run(self):
|
||||
watches = {}
|
||||
observer = self.observer_class()
|
||||
observer.start()
|
||||
|
||||
while not self.should_reload:
|
||||
to_delete = set(watches)
|
||||
paths = _find_observable_paths(self.extra_files)
|
||||
for path in paths:
|
||||
if path not in watches:
|
||||
try:
|
||||
watches[path] = observer.schedule(
|
||||
self.event_handler, path, recursive=True)
|
||||
except OSError:
|
||||
# Clear this path from list of watches We don't want
|
||||
# the same error message showing again in the next
|
||||
# iteration.
|
||||
watches[path] = None
|
||||
to_delete.discard(path)
|
||||
for path in to_delete:
|
||||
watch = watches.pop(path, None)
|
||||
if watch is not None:
|
||||
observer.unschedule(watch)
|
||||
self.observable_paths = paths
|
||||
self._sleep(self.interval)
|
||||
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
reloader_loops = {
|
||||
'stat': StatReloaderLoop,
|
||||
'watchdog': WatchdogReloaderLoop,
|
||||
}
|
||||
|
||||
try:
|
||||
__import__('watchdog.observers')
|
||||
except ImportError:
|
||||
reloader_loops['auto'] = reloader_loops['stat']
|
||||
else:
|
||||
reloader_loops['auto'] = reloader_loops['watchdog']
|
||||
|
||||
|
||||
def run_with_reloader(main_func, extra_files=None, interval=1,
|
||||
reloader_type='auto'):
|
||||
"""Run the given function in an independent python interpreter."""
|
||||
import signal
|
||||
reloader = reloader_loops[reloader_type](extra_files, interval)
|
||||
signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
|
||||
try:
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
|
||||
t = threading.Thread(target=main_func, args=())
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
reloader.run()
|
||||
else:
|
||||
sys.exit(reloader.restart_with_reloader())
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/_reloader.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/_reloader.pyc
Normal file
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.contrib
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Contains user-submitted code that other users may find useful, but which
|
||||
is not part of the Werkzeug core. Anyone can write code for inclusion in
|
||||
the `contrib` package. All modules in this package are distributed as an
|
||||
add-on library and thus are not part of Werkzeug itself.
|
||||
|
||||
This file itself is mostly for informational purposes and to tell the
|
||||
Python interpreter that `contrib` is a package.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/__init__.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/__init__.pyc
Normal file
Binary file not shown.
355
venv/lib/python2.7/site-packages/werkzeug/contrib/atom.py
Normal file
355
venv/lib/python2.7/site-packages/werkzeug/contrib/atom.py
Normal file
@@ -0,0 +1,355 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.contrib.atom
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides a class called :class:`AtomFeed` which can be
|
||||
used to generate feeds in the Atom syndication format (see :rfc:`4287`).
|
||||
|
||||
Example::
|
||||
|
||||
def atom_feed(request):
|
||||
feed = AtomFeed("My Blog", feed_url=request.url,
|
||||
url=request.host_url,
|
||||
subtitle="My example blog for a feed test.")
|
||||
for post in Post.query.limit(10).all():
|
||||
feed.add(post.title, post.body, content_type='html',
|
||||
author=post.author, url=post.url, id=post.uid,
|
||||
updated=post.last_update, published=post.pub_date)
|
||||
return feed.get_response()
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
from werkzeug.utils import escape
|
||||
from werkzeug.wrappers import BaseResponse
|
||||
from werkzeug._compat import implements_to_string, string_types
|
||||
|
||||
|
||||
XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'
|
||||
|
||||
|
||||
def _make_text_block(name, content, content_type=None):
|
||||
"""Helper function for the builder that creates an XML text block."""
|
||||
if content_type == 'xhtml':
|
||||
return u'<%s type="xhtml"><div xmlns="%s">%s</div></%s>\n' % \
|
||||
(name, XHTML_NAMESPACE, content, name)
|
||||
if not content_type:
|
||||
return u'<%s>%s</%s>\n' % (name, escape(content), name)
|
||||
return u'<%s type="%s">%s</%s>\n' % (name, content_type,
|
||||
escape(content), name)
|
||||
|
||||
|
||||
def format_iso8601(obj):
|
||||
"""Format a datetime object for iso8601"""
|
||||
iso8601 = obj.isoformat()
|
||||
if obj.tzinfo:
|
||||
return iso8601
|
||||
return iso8601 + 'Z'
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class AtomFeed(object):
|
||||
|
||||
"""A helper class that creates Atom feeds.
|
||||
|
||||
:param title: the title of the feed. Required.
|
||||
:param title_type: the type attribute for the title element. One of
|
||||
``'html'``, ``'text'`` or ``'xhtml'``.
|
||||
:param url: the url for the feed (not the url *of* the feed)
|
||||
:param id: a globally unique id for the feed. Must be an URI. If
|
||||
not present the `feed_url` is used, but one of both is
|
||||
required.
|
||||
:param updated: the time the feed was modified the last time. Must
|
||||
be a :class:`datetime.datetime` object. If not
|
||||
present the latest entry's `updated` is used.
|
||||
Treated as UTC if naive datetime.
|
||||
:param feed_url: the URL to the feed. Should be the URL that was
|
||||
requested.
|
||||
:param author: the author of the feed. Must be either a string (the
|
||||
name) or a dict with name (required) and uri or
|
||||
email (both optional). Can be a list of (may be
|
||||
mixed, too) strings and dicts, too, if there are
|
||||
multiple authors. Required if not every entry has an
|
||||
author element.
|
||||
:param icon: an icon for the feed.
|
||||
:param logo: a logo for the feed.
|
||||
:param rights: copyright information for the feed.
|
||||
:param rights_type: the type attribute for the rights element. One of
|
||||
``'html'``, ``'text'`` or ``'xhtml'``. Default is
|
||||
``'text'``.
|
||||
:param subtitle: a short description of the feed.
|
||||
:param subtitle_type: the type attribute for the subtitle element.
|
||||
One of ``'text'``, ``'html'``, ``'text'``
|
||||
or ``'xhtml'``. Default is ``'text'``.
|
||||
:param links: additional links. Must be a list of dictionaries with
|
||||
href (required) and rel, type, hreflang, title, length
|
||||
(all optional)
|
||||
:param generator: the software that generated this feed. This must be
|
||||
a tuple in the form ``(name, url, version)``. If
|
||||
you don't want to specify one of them, set the item
|
||||
to `None`.
|
||||
:param entries: a list with the entries for the feed. Entries can also
|
||||
be added later with :meth:`add`.
|
||||
|
||||
For more information on the elements see
|
||||
http://www.atomenabled.org/developers/syndication/
|
||||
|
||||
Everywhere where a list is demanded, any iterable can be used.
|
||||
"""
|
||||
|
||||
default_generator = ('Werkzeug', None, None)
|
||||
|
||||
def __init__(self, title=None, entries=None, **kwargs):
|
||||
self.title = title
|
||||
self.title_type = kwargs.get('title_type', 'text')
|
||||
self.url = kwargs.get('url')
|
||||
self.feed_url = kwargs.get('feed_url', self.url)
|
||||
self.id = kwargs.get('id', self.feed_url)
|
||||
self.updated = kwargs.get('updated')
|
||||
self.author = kwargs.get('author', ())
|
||||
self.icon = kwargs.get('icon')
|
||||
self.logo = kwargs.get('logo')
|
||||
self.rights = kwargs.get('rights')
|
||||
self.rights_type = kwargs.get('rights_type')
|
||||
self.subtitle = kwargs.get('subtitle')
|
||||
self.subtitle_type = kwargs.get('subtitle_type', 'text')
|
||||
self.generator = kwargs.get('generator')
|
||||
if self.generator is None:
|
||||
self.generator = self.default_generator
|
||||
self.links = kwargs.get('links', [])
|
||||
self.entries = entries and list(entries) or []
|
||||
|
||||
if not hasattr(self.author, '__iter__') \
|
||||
or isinstance(self.author, string_types + (dict,)):
|
||||
self.author = [self.author]
|
||||
for i, author in enumerate(self.author):
|
||||
if not isinstance(author, dict):
|
||||
self.author[i] = {'name': author}
|
||||
|
||||
if not self.title:
|
||||
raise ValueError('title is required')
|
||||
if not self.id:
|
||||
raise ValueError('id is required')
|
||||
for author in self.author:
|
||||
if 'name' not in author:
|
||||
raise TypeError('author must contain at least a name')
|
||||
|
||||
def add(self, *args, **kwargs):
|
||||
"""Add a new entry to the feed. This function can either be called
|
||||
with a :class:`FeedEntry` or some keyword and positional arguments
|
||||
that are forwarded to the :class:`FeedEntry` constructor.
|
||||
"""
|
||||
if len(args) == 1 and not kwargs and isinstance(args[0], FeedEntry):
|
||||
self.entries.append(args[0])
|
||||
else:
|
||||
kwargs['feed_url'] = self.feed_url
|
||||
self.entries.append(FeedEntry(*args, **kwargs))
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r (%d entries)>' % (
|
||||
self.__class__.__name__,
|
||||
self.title,
|
||||
len(self.entries)
|
||||
)
|
||||
|
||||
def generate(self):
|
||||
"""Return a generator that yields pieces of XML."""
|
||||
# atom demands either an author element in every entry or a global one
|
||||
if not self.author:
|
||||
if any(not e.author for e in self.entries):
|
||||
self.author = ({'name': 'Unknown author'},)
|
||||
|
||||
if not self.updated:
|
||||
dates = sorted([entry.updated for entry in self.entries])
|
||||
self.updated = dates and dates[-1] or datetime.utcnow()
|
||||
|
||||
yield u'<?xml version="1.0" encoding="utf-8"?>\n'
|
||||
yield u'<feed xmlns="http://www.w3.org/2005/Atom">\n'
|
||||
yield ' ' + _make_text_block('title', self.title, self.title_type)
|
||||
yield u' <id>%s</id>\n' % escape(self.id)
|
||||
yield u' <updated>%s</updated>\n' % format_iso8601(self.updated)
|
||||
if self.url:
|
||||
yield u' <link href="%s" />\n' % escape(self.url)
|
||||
if self.feed_url:
|
||||
yield u' <link href="%s" rel="self" />\n' % \
|
||||
escape(self.feed_url)
|
||||
for link in self.links:
|
||||
yield u' <link %s/>\n' % ''.join('%s="%s" ' %
|
||||
(k, escape(link[k])) for k in link)
|
||||
for author in self.author:
|
||||
yield u' <author>\n'
|
||||
yield u' <name>%s</name>\n' % escape(author['name'])
|
||||
if 'uri' in author:
|
||||
yield u' <uri>%s</uri>\n' % escape(author['uri'])
|
||||
if 'email' in author:
|
||||
yield ' <email>%s</email>\n' % escape(author['email'])
|
||||
yield ' </author>\n'
|
||||
if self.subtitle:
|
||||
yield ' ' + _make_text_block('subtitle', self.subtitle,
|
||||
self.subtitle_type)
|
||||
if self.icon:
|
||||
yield u' <icon>%s</icon>\n' % escape(self.icon)
|
||||
if self.logo:
|
||||
yield u' <logo>%s</logo>\n' % escape(self.logo)
|
||||
if self.rights:
|
||||
yield ' ' + _make_text_block('rights', self.rights,
|
||||
self.rights_type)
|
||||
generator_name, generator_url, generator_version = self.generator
|
||||
if generator_name or generator_url or generator_version:
|
||||
tmp = [u' <generator']
|
||||
if generator_url:
|
||||
tmp.append(u' uri="%s"' % escape(generator_url))
|
||||
if generator_version:
|
||||
tmp.append(u' version="%s"' % escape(generator_version))
|
||||
tmp.append(u'>%s</generator>\n' % escape(generator_name))
|
||||
yield u''.join(tmp)
|
||||
for entry in self.entries:
|
||||
for line in entry.generate():
|
||||
yield u' ' + line
|
||||
yield u'</feed>\n'
|
||||
|
||||
def to_string(self):
|
||||
"""Convert the feed into a string."""
|
||||
return u''.join(self.generate())
|
||||
|
||||
def get_response(self):
|
||||
"""Return a response object for the feed."""
|
||||
return BaseResponse(self.to_string(), mimetype='application/atom+xml')
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""Use the class as WSGI response object."""
|
||||
return self.get_response()(environ, start_response)
|
||||
|
||||
def __str__(self):
|
||||
return self.to_string()
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class FeedEntry(object):
|
||||
|
||||
"""Represents a single entry in a feed.
|
||||
|
||||
:param title: the title of the entry. Required.
|
||||
:param title_type: the type attribute for the title element. One of
|
||||
``'html'``, ``'text'`` or ``'xhtml'``.
|
||||
:param content: the content of the entry.
|
||||
:param content_type: the type attribute for the content element. One
|
||||
of ``'html'``, ``'text'`` or ``'xhtml'``.
|
||||
:param summary: a summary of the entry's content.
|
||||
:param summary_type: the type attribute for the summary element. One
|
||||
of ``'html'``, ``'text'`` or ``'xhtml'``.
|
||||
:param url: the url for the entry.
|
||||
:param id: a globally unique id for the entry. Must be an URI. If
|
||||
not present the URL is used, but one of both is required.
|
||||
:param updated: the time the entry was modified the last time. Must
|
||||
be a :class:`datetime.datetime` object. Treated as
|
||||
UTC if naive datetime. Required.
|
||||
:param author: the author of the entry. Must be either a string (the
|
||||
name) or a dict with name (required) and uri or
|
||||
email (both optional). Can be a list of (may be
|
||||
mixed, too) strings and dicts, too, if there are
|
||||
multiple authors. Required if the feed does not have an
|
||||
author element.
|
||||
:param published: the time the entry was initially published. Must
|
||||
be a :class:`datetime.datetime` object. Treated as
|
||||
UTC if naive datetime.
|
||||
:param rights: copyright information for the entry.
|
||||
:param rights_type: the type attribute for the rights element. One of
|
||||
``'html'``, ``'text'`` or ``'xhtml'``. Default is
|
||||
``'text'``.
|
||||
:param links: additional links. Must be a list of dictionaries with
|
||||
href (required) and rel, type, hreflang, title, length
|
||||
(all optional)
|
||||
:param categories: categories for the entry. Must be a list of dictionaries
|
||||
with term (required), scheme and label (all optional)
|
||||
:param xml_base: The xml base (url) for this feed item. If not provided
|
||||
it will default to the item url.
|
||||
|
||||
For more information on the elements see
|
||||
http://www.atomenabled.org/developers/syndication/
|
||||
|
||||
Everywhere where a list is demanded, any iterable can be used.
|
||||
"""
|
||||
|
||||
def __init__(self, title=None, content=None, feed_url=None, **kwargs):
|
||||
self.title = title
|
||||
self.title_type = kwargs.get('title_type', 'text')
|
||||
self.content = content
|
||||
self.content_type = kwargs.get('content_type', 'html')
|
||||
self.url = kwargs.get('url')
|
||||
self.id = kwargs.get('id', self.url)
|
||||
self.updated = kwargs.get('updated')
|
||||
self.summary = kwargs.get('summary')
|
||||
self.summary_type = kwargs.get('summary_type', 'html')
|
||||
self.author = kwargs.get('author', ())
|
||||
self.published = kwargs.get('published')
|
||||
self.rights = kwargs.get('rights')
|
||||
self.links = kwargs.get('links', [])
|
||||
self.categories = kwargs.get('categories', [])
|
||||
self.xml_base = kwargs.get('xml_base', feed_url)
|
||||
|
||||
if not hasattr(self.author, '__iter__') \
|
||||
or isinstance(self.author, string_types + (dict,)):
|
||||
self.author = [self.author]
|
||||
for i, author in enumerate(self.author):
|
||||
if not isinstance(author, dict):
|
||||
self.author[i] = {'name': author}
|
||||
|
||||
if not self.title:
|
||||
raise ValueError('title is required')
|
||||
if not self.id:
|
||||
raise ValueError('id is required')
|
||||
if not self.updated:
|
||||
raise ValueError('updated is required')
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (
|
||||
self.__class__.__name__,
|
||||
self.title
|
||||
)
|
||||
|
||||
def generate(self):
|
||||
"""Yields pieces of ATOM XML."""
|
||||
base = ''
|
||||
if self.xml_base:
|
||||
base = ' xml:base="%s"' % escape(self.xml_base)
|
||||
yield u'<entry%s>\n' % base
|
||||
yield u' ' + _make_text_block('title', self.title, self.title_type)
|
||||
yield u' <id>%s</id>\n' % escape(self.id)
|
||||
yield u' <updated>%s</updated>\n' % format_iso8601(self.updated)
|
||||
if self.published:
|
||||
yield u' <published>%s</published>\n' % \
|
||||
format_iso8601(self.published)
|
||||
if self.url:
|
||||
yield u' <link href="%s" />\n' % escape(self.url)
|
||||
for author in self.author:
|
||||
yield u' <author>\n'
|
||||
yield u' <name>%s</name>\n' % escape(author['name'])
|
||||
if 'uri' in author:
|
||||
yield u' <uri>%s</uri>\n' % escape(author['uri'])
|
||||
if 'email' in author:
|
||||
yield u' <email>%s</email>\n' % escape(author['email'])
|
||||
yield u' </author>\n'
|
||||
for link in self.links:
|
||||
yield u' <link %s/>\n' % ''.join('%s="%s" ' %
|
||||
(k, escape(link[k])) for k in link)
|
||||
for category in self.categories:
|
||||
yield u' <category %s/>\n' % ''.join('%s="%s" ' %
|
||||
(k, escape(category[k])) for k in category)
|
||||
if self.summary:
|
||||
yield u' ' + _make_text_block('summary', self.summary,
|
||||
self.summary_type)
|
||||
if self.content:
|
||||
yield u' ' + _make_text_block('content', self.content,
|
||||
self.content_type)
|
||||
yield u'</entry>\n'
|
||||
|
||||
def to_string(self):
|
||||
"""Convert the feed item into a unicode object."""
|
||||
return u''.join(self.generate())
|
||||
|
||||
def __str__(self):
|
||||
return self.to_string()
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/atom.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/atom.pyc
Normal file
Binary file not shown.
858
venv/lib/python2.7/site-packages/werkzeug/contrib/cache.py
Normal file
858
venv/lib/python2.7/site-packages/werkzeug/contrib/cache.py
Normal file
@@ -0,0 +1,858 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.contrib.cache
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The main problem with dynamic Web sites is, well, they're dynamic. Each
|
||||
time a user requests a page, the webserver executes a lot of code, queries
|
||||
the database, renders templates until the visitor gets the page he sees.
|
||||
|
||||
This is a lot more expensive than just loading a file from the file system
|
||||
and sending it to the visitor.
|
||||
|
||||
For most Web applications, this overhead isn't a big deal but once it
|
||||
becomes, you will be glad to have a cache system in place.
|
||||
|
||||
How Caching Works
|
||||
=================
|
||||
|
||||
Caching is pretty simple. Basically you have a cache object lurking around
|
||||
somewhere that is connected to a remote cache or the file system or
|
||||
something else. When the request comes in you check if the current page
|
||||
is already in the cache and if so, you're returning it from the cache.
|
||||
Otherwise you generate the page and put it into the cache. (Or a fragment
|
||||
of the page, you don't have to cache the full thing)
|
||||
|
||||
Here is a simple example of how to cache a sidebar for 5 minutes::
|
||||
|
||||
def get_sidebar(user):
|
||||
identifier = 'sidebar_for/user%d' % user.id
|
||||
value = cache.get(identifier)
|
||||
if value is not None:
|
||||
return value
|
||||
value = generate_sidebar_for(user=user)
|
||||
cache.set(identifier, value, timeout=60 * 5)
|
||||
return value
|
||||
|
||||
Creating a Cache Object
|
||||
=======================
|
||||
|
||||
To create a cache object you just import the cache system of your choice
|
||||
from the cache module and instantiate it. Then you can start working
|
||||
with that object:
|
||||
|
||||
>>> from werkzeug.contrib.cache import SimpleCache
|
||||
>>> c = SimpleCache()
|
||||
>>> c.set("foo", "value")
|
||||
>>> c.get("foo")
|
||||
'value'
|
||||
>>> c.get("missing") is None
|
||||
True
|
||||
|
||||
Please keep in mind that you have to create the cache and put it somewhere
|
||||
you have access to it (either as a module global you can import or you just
|
||||
put it into your WSGI application).
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import errno
|
||||
import tempfile
|
||||
import platform
|
||||
from hashlib import md5
|
||||
from time import time
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError: # pragma: no cover
|
||||
import pickle
|
||||
|
||||
from werkzeug._compat import iteritems, string_types, text_type, \
|
||||
integer_types, to_native
|
||||
from werkzeug.posixemulation import rename
|
||||
|
||||
|
||||
def _items(mappingorseq):
|
||||
"""Wrapper for efficient iteration over mappings represented by dicts
|
||||
or sequences::
|
||||
|
||||
>>> for k, v in _items((i, i*i) for i in xrange(5)):
|
||||
... assert k*k == v
|
||||
|
||||
>>> for k, v in _items(dict((i, i*i) for i in xrange(5))):
|
||||
... assert k*k == v
|
||||
|
||||
"""
|
||||
if hasattr(mappingorseq, 'items'):
|
||||
return iteritems(mappingorseq)
|
||||
return mappingorseq
|
||||
|
||||
|
||||
class BaseCache(object):
|
||||
|
||||
"""Baseclass for the cache systems. All the cache systems implement this
|
||||
API or a superset of it.
|
||||
|
||||
:param default_timeout: the default timeout (in seconds) that is used if
|
||||
no timeout is specified on :meth:`set`. A timeout
|
||||
of 0 indicates that the cache never expires.
|
||||
"""
|
||||
|
||||
def __init__(self, default_timeout=300):
|
||||
self.default_timeout = default_timeout
|
||||
|
||||
def _normalize_timeout(self, timeout):
|
||||
if timeout is None:
|
||||
timeout = self.default_timeout
|
||||
return timeout
|
||||
|
||||
def get(self, key):
|
||||
"""Look up key in the cache and return the value for it.
|
||||
|
||||
:param key: the key to be looked up.
|
||||
:returns: The value if it exists and is readable, else ``None``.
|
||||
"""
|
||||
return None
|
||||
|
||||
def delete(self, key):
|
||||
"""Delete `key` from the cache.
|
||||
|
||||
:param key: the key to delete.
|
||||
:returns: Whether the key existed and has been deleted.
|
||||
:rtype: boolean
|
||||
"""
|
||||
return True
|
||||
|
||||
def get_many(self, *keys):
|
||||
"""Returns a list of values for the given keys.
|
||||
For each key a item in the list is created::
|
||||
|
||||
foo, bar = cache.get_many("foo", "bar")
|
||||
|
||||
Has the same error handling as :meth:`get`.
|
||||
|
||||
:param keys: The function accepts multiple keys as positional
|
||||
arguments.
|
||||
"""
|
||||
return map(self.get, keys)
|
||||
|
||||
def get_dict(self, *keys):
|
||||
"""Like :meth:`get_many` but return a dict::
|
||||
|
||||
d = cache.get_dict("foo", "bar")
|
||||
foo = d["foo"]
|
||||
bar = d["bar"]
|
||||
|
||||
:param keys: The function accepts multiple keys as positional
|
||||
arguments.
|
||||
"""
|
||||
return dict(zip(keys, self.get_many(*keys)))
|
||||
|
||||
def set(self, key, value, timeout=None):
|
||||
"""Add a new key/value to the cache (overwrites value, if key already
|
||||
exists in the cache).
|
||||
|
||||
:param key: the key to set
|
||||
:param value: the value for the key
|
||||
:param timeout: the cache timeout for the key in seconds (if not
|
||||
specified, it uses the default timeout). A timeout of
|
||||
0 idicates that the cache never expires.
|
||||
:returns: ``True`` if key has been updated, ``False`` for backend
|
||||
errors. Pickling errors, however, will raise a subclass of
|
||||
``pickle.PickleError``.
|
||||
:rtype: boolean
|
||||
"""
|
||||
return True
|
||||
|
||||
def add(self, key, value, timeout=None):
|
||||
"""Works like :meth:`set` but does not overwrite the values of already
|
||||
existing keys.
|
||||
|
||||
:param key: the key to set
|
||||
:param value: the value for the key
|
||||
:param timeout: the cache timeout for the key in seconds (if not
|
||||
specified, it uses the default timeout). A timeout of
|
||||
0 idicates that the cache never expires.
|
||||
:returns: Same as :meth:`set`, but also ``False`` for already
|
||||
existing keys.
|
||||
:rtype: boolean
|
||||
"""
|
||||
return True
|
||||
|
||||
def set_many(self, mapping, timeout=None):
|
||||
"""Sets multiple keys and values from a mapping.
|
||||
|
||||
:param mapping: a mapping with the keys/values to set.
|
||||
:param timeout: the cache timeout for the key in seconds (if not
|
||||
specified, it uses the default timeout). A timeout of
|
||||
0 idicates that the cache never expires.
|
||||
:returns: Whether all given keys have been set.
|
||||
:rtype: boolean
|
||||
"""
|
||||
rv = True
|
||||
for key, value in _items(mapping):
|
||||
if not self.set(key, value, timeout):
|
||||
rv = False
|
||||
return rv
|
||||
|
||||
def delete_many(self, *keys):
|
||||
"""Deletes multiple keys at once.
|
||||
|
||||
:param keys: The function accepts multiple keys as positional
|
||||
arguments.
|
||||
:returns: Whether all given keys have been deleted.
|
||||
:rtype: boolean
|
||||
"""
|
||||
return all(self.delete(key) for key in keys)
|
||||
|
||||
def has(self, key):
|
||||
"""Checks if a key exists in the cache without returning it. This is a
|
||||
cheap operation that bypasses loading the actual data on the backend.
|
||||
|
||||
This method is optional and may not be implemented on all caches.
|
||||
|
||||
:param key: the key to check
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
'%s doesn\'t have an efficient implementation of `has`. That '
|
||||
'means it is impossible to check whether a key exists without '
|
||||
'fully loading the key\'s data. Consider using `self.get` '
|
||||
'explicitly if you don\'t care about performance.'
|
||||
)
|
||||
|
||||
def clear(self):
|
||||
"""Clears the cache. Keep in mind that not all caches support
|
||||
completely clearing the cache.
|
||||
|
||||
:returns: Whether the cache has been cleared.
|
||||
:rtype: boolean
|
||||
"""
|
||||
return True
|
||||
|
||||
def inc(self, key, delta=1):
|
||||
"""Increments the value of a key by `delta`. If the key does
|
||||
not yet exist it is initialized with `delta`.
|
||||
|
||||
For supporting caches this is an atomic operation.
|
||||
|
||||
:param key: the key to increment.
|
||||
:param delta: the delta to add.
|
||||
:returns: The new value or ``None`` for backend errors.
|
||||
"""
|
||||
value = (self.get(key) or 0) + delta
|
||||
return value if self.set(key, value) else None
|
||||
|
||||
def dec(self, key, delta=1):
|
||||
"""Decrements the value of a key by `delta`. If the key does
|
||||
not yet exist it is initialized with `-delta`.
|
||||
|
||||
For supporting caches this is an atomic operation.
|
||||
|
||||
:param key: the key to increment.
|
||||
:param delta: the delta to subtract.
|
||||
:returns: The new value or `None` for backend errors.
|
||||
"""
|
||||
value = (self.get(key) or 0) - delta
|
||||
return value if self.set(key, value) else None
|
||||
|
||||
|
||||
class NullCache(BaseCache):
|
||||
|
||||
"""A cache that doesn't cache. This can be useful for unit testing.
|
||||
|
||||
:param default_timeout: a dummy parameter that is ignored but exists
|
||||
for API compatibility with other caches.
|
||||
"""
|
||||
|
||||
|
||||
class SimpleCache(BaseCache):
|
||||
|
||||
"""Simple memory cache for single process environments. This class exists
|
||||
mainly for the development server and is not 100% thread safe. It tries
|
||||
to use as many atomic operations as possible and no locks for simplicity
|
||||
but it could happen under heavy load that keys are added multiple times.
|
||||
|
||||
:param threshold: the maximum number of items the cache stores before
|
||||
it starts deleting some.
|
||||
:param default_timeout: the default timeout that is used if no timeout is
|
||||
specified on :meth:`~BaseCache.set`. A timeout of
|
||||
0 indicates that the cache never expires.
|
||||
"""
|
||||
|
||||
def __init__(self, threshold=500, default_timeout=300):
|
||||
BaseCache.__init__(self, default_timeout)
|
||||
self._cache = {}
|
||||
self.clear = self._cache.clear
|
||||
self._threshold = threshold
|
||||
|
||||
def _prune(self):
|
||||
if len(self._cache) > self._threshold:
|
||||
now = time()
|
||||
toremove = []
|
||||
for idx, (key, (expires, _)) in enumerate(self._cache.items()):
|
||||
if (expires != 0 and expires <= now) or idx % 3 == 0:
|
||||
toremove.append(key)
|
||||
for key in toremove:
|
||||
self._cache.pop(key, None)
|
||||
|
||||
def _normalize_timeout(self, timeout):
|
||||
timeout = BaseCache._normalize_timeout(self, timeout)
|
||||
if timeout > 0:
|
||||
timeout = time() + timeout
|
||||
return timeout
|
||||
|
||||
def get(self, key):
|
||||
try:
|
||||
expires, value = self._cache[key]
|
||||
if expires == 0 or expires > time():
|
||||
return pickle.loads(value)
|
||||
except (KeyError, pickle.PickleError):
|
||||
return None
|
||||
|
||||
def set(self, key, value, timeout=None):
|
||||
expires = self._normalize_timeout(timeout)
|
||||
self._prune()
|
||||
self._cache[key] = (expires, pickle.dumps(value,
|
||||
pickle.HIGHEST_PROTOCOL))
|
||||
return True
|
||||
|
||||
def add(self, key, value, timeout=None):
|
||||
expires = self._normalize_timeout(timeout)
|
||||
self._prune()
|
||||
item = (expires, pickle.dumps(value,
|
||||
pickle.HIGHEST_PROTOCOL))
|
||||
if key in self._cache:
|
||||
return False
|
||||
self._cache.setdefault(key, item)
|
||||
return True
|
||||
|
||||
def delete(self, key):
|
||||
return self._cache.pop(key, None) is not None
|
||||
|
||||
def has(self, key):
|
||||
try:
|
||||
expires, value = self._cache[key]
|
||||
return expires == 0 or expires > time()
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
_test_memcached_key = re.compile(r'[^\x00-\x21\xff]{1,250}$').match
|
||||
|
||||
|
||||
class MemcachedCache(BaseCache):
|
||||
|
||||
"""A cache that uses memcached as backend.
|
||||
|
||||
The first argument can either be an object that resembles the API of a
|
||||
:class:`memcache.Client` or a tuple/list of server addresses. In the
|
||||
event that a tuple/list is passed, Werkzeug tries to import the best
|
||||
available memcache library.
|
||||
|
||||
This cache looks into the following packages/modules to find bindings for
|
||||
memcached:
|
||||
|
||||
- ``pylibmc``
|
||||
- ``google.appengine.api.memcached``
|
||||
- ``memcached``
|
||||
|
||||
Implementation notes: This cache backend works around some limitations in
|
||||
memcached to simplify the interface. For example unicode keys are encoded
|
||||
to utf-8 on the fly. Methods such as :meth:`~BaseCache.get_dict` return
|
||||
the keys in the same format as passed. Furthermore all get methods
|
||||
silently ignore key errors to not cause problems when untrusted user data
|
||||
is passed to the get methods which is often the case in web applications.
|
||||
|
||||
:param servers: a list or tuple of server addresses or alternatively
|
||||
a :class:`memcache.Client` or a compatible client.
|
||||
:param default_timeout: the default timeout that is used if no timeout is
|
||||
specified on :meth:`~BaseCache.set`. A timeout of
|
||||
0 indicates taht the cache never expires.
|
||||
:param key_prefix: a prefix that is added before all keys. This makes it
|
||||
possible to use the same memcached server for different
|
||||
applications. Keep in mind that
|
||||
:meth:`~BaseCache.clear` will also clear keys with a
|
||||
different prefix.
|
||||
"""
|
||||
|
||||
def __init__(self, servers=None, default_timeout=300, key_prefix=None):
|
||||
BaseCache.__init__(self, default_timeout)
|
||||
if servers is None or isinstance(servers, (list, tuple)):
|
||||
if servers is None:
|
||||
servers = ['127.0.0.1:11211']
|
||||
self._client = self.import_preferred_memcache_lib(servers)
|
||||
if self._client is None:
|
||||
raise RuntimeError('no memcache module found')
|
||||
else:
|
||||
# NOTE: servers is actually an already initialized memcache
|
||||
# client.
|
||||
self._client = servers
|
||||
|
||||
self.key_prefix = to_native(key_prefix)
|
||||
|
||||
def _normalize_key(self, key):
|
||||
key = to_native(key, 'utf-8')
|
||||
if self.key_prefix:
|
||||
key = self.key_prefix + key
|
||||
return key
|
||||
|
||||
def _normalize_timeout(self, timeout):
|
||||
timeout = BaseCache._normalize_timeout(self, timeout)
|
||||
if timeout > 0:
|
||||
timeout = int(time()) + timeout
|
||||
return timeout
|
||||
|
||||
def get(self, key):
|
||||
key = self._normalize_key(key)
|
||||
# memcached doesn't support keys longer than that. Because often
|
||||
# checks for so long keys can occur because it's tested from user
|
||||
# submitted data etc we fail silently for getting.
|
||||
if _test_memcached_key(key):
|
||||
return self._client.get(key)
|
||||
|
||||
def get_dict(self, *keys):
|
||||
key_mapping = {}
|
||||
have_encoded_keys = False
|
||||
for key in keys:
|
||||
encoded_key = self._normalize_key(key)
|
||||
if not isinstance(key, str):
|
||||
have_encoded_keys = True
|
||||
if _test_memcached_key(key):
|
||||
key_mapping[encoded_key] = key
|
||||
d = rv = self._client.get_multi(key_mapping.keys())
|
||||
if have_encoded_keys or self.key_prefix:
|
||||
rv = {}
|
||||
for key, value in iteritems(d):
|
||||
rv[key_mapping[key]] = value
|
||||
if len(rv) < len(keys):
|
||||
for key in keys:
|
||||
if key not in rv:
|
||||
rv[key] = None
|
||||
return rv
|
||||
|
||||
def add(self, key, value, timeout=None):
|
||||
key = self._normalize_key(key)
|
||||
timeout = self._normalize_timeout(timeout)
|
||||
return self._client.add(key, value, timeout)
|
||||
|
||||
def set(self, key, value, timeout=None):
|
||||
key = self._normalize_key(key)
|
||||
timeout = self._normalize_timeout(timeout)
|
||||
return self._client.set(key, value, timeout)
|
||||
|
||||
def get_many(self, *keys):
|
||||
d = self.get_dict(*keys)
|
||||
return [d[key] for key in keys]
|
||||
|
||||
def set_many(self, mapping, timeout=None):
|
||||
new_mapping = {}
|
||||
for key, value in _items(mapping):
|
||||
key = self._normalize_key(key)
|
||||
new_mapping[key] = value
|
||||
|
||||
timeout = self._normalize_timeout(timeout)
|
||||
failed_keys = self._client.set_multi(new_mapping, timeout)
|
||||
return not failed_keys
|
||||
|
||||
def delete(self, key):
|
||||
key = self._normalize_key(key)
|
||||
if _test_memcached_key(key):
|
||||
return self._client.delete(key)
|
||||
|
||||
def delete_many(self, *keys):
|
||||
new_keys = []
|
||||
for key in keys:
|
||||
key = self._normalize_key(key)
|
||||
if _test_memcached_key(key):
|
||||
new_keys.append(key)
|
||||
return self._client.delete_multi(new_keys)
|
||||
|
||||
def has(self, key):
|
||||
key = self._normalize_key(key)
|
||||
if _test_memcached_key(key):
|
||||
return self._client.append(key, '')
|
||||
return False
|
||||
|
||||
def clear(self):
|
||||
return self._client.flush_all()
|
||||
|
||||
def inc(self, key, delta=1):
|
||||
key = self._normalize_key(key)
|
||||
return self._client.incr(key, delta)
|
||||
|
||||
def dec(self, key, delta=1):
|
||||
key = self._normalize_key(key)
|
||||
return self._client.decr(key, delta)
|
||||
|
||||
def import_preferred_memcache_lib(self, servers):
|
||||
"""Returns an initialized memcache client. Used by the constructor."""
|
||||
try:
|
||||
import pylibmc
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
return pylibmc.Client(servers)
|
||||
|
||||
try:
|
||||
from google.appengine.api import memcache
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
return memcache.Client()
|
||||
|
||||
try:
|
||||
import memcache
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
return memcache.Client(servers)
|
||||
|
||||
|
||||
# backwards compatibility
|
||||
GAEMemcachedCache = MemcachedCache
|
||||
|
||||
|
||||
class RedisCache(BaseCache):
|
||||
|
||||
"""Uses the Redis key-value store as a cache backend.
|
||||
|
||||
The first argument can be either a string denoting address of the Redis
|
||||
server or an object resembling an instance of a redis.Redis class.
|
||||
|
||||
Note: Python Redis API already takes care of encoding unicode strings on
|
||||
the fly.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
.. versionadded:: 0.8
|
||||
`key_prefix` was added.
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
This cache backend now properly serializes objects.
|
||||
|
||||
.. versionchanged:: 0.8.3
|
||||
This cache backend now supports password authentication.
|
||||
|
||||
.. versionchanged:: 0.10
|
||||
``**kwargs`` is now passed to the redis object.
|
||||
|
||||
:param host: address of the Redis server or an object which API is
|
||||
compatible with the official Python Redis client (redis-py).
|
||||
:param port: port number on which Redis server listens for connections.
|
||||
:param password: password authentication for the Redis server.
|
||||
:param db: db (zero-based numeric index) on Redis Server to connect.
|
||||
:param default_timeout: the default timeout that is used if no timeout is
|
||||
specified on :meth:`~BaseCache.set`. A timeout of
|
||||
0 indicates that the cache never expires.
|
||||
:param key_prefix: A prefix that should be added to all keys.
|
||||
|
||||
Any additional keyword arguments will be passed to ``redis.Redis``.
|
||||
"""
|
||||
|
||||
def __init__(self, host='localhost', port=6379, password=None,
|
||||
db=0, default_timeout=300, key_prefix=None, **kwargs):
|
||||
BaseCache.__init__(self, default_timeout)
|
||||
if isinstance(host, string_types):
|
||||
try:
|
||||
import redis
|
||||
except ImportError:
|
||||
raise RuntimeError('no redis module found')
|
||||
if kwargs.get('decode_responses', None):
|
||||
raise ValueError('decode_responses is not supported by '
|
||||
'RedisCache.')
|
||||
self._client = redis.Redis(host=host, port=port, password=password,
|
||||
db=db, **kwargs)
|
||||
else:
|
||||
self._client = host
|
||||
self.key_prefix = key_prefix or ''
|
||||
|
||||
def _normalize_timeout(self, timeout):
|
||||
timeout = BaseCache._normalize_timeout(self, timeout)
|
||||
if timeout == 0:
|
||||
timeout = -1
|
||||
return timeout
|
||||
|
||||
def dump_object(self, value):
|
||||
"""Dumps an object into a string for redis. By default it serializes
|
||||
integers as regular string and pickle dumps everything else.
|
||||
"""
|
||||
t = type(value)
|
||||
if t in integer_types:
|
||||
return str(value).encode('ascii')
|
||||
return b'!' + pickle.dumps(value)
|
||||
|
||||
def load_object(self, value):
|
||||
"""The reversal of :meth:`dump_object`. This might be called with
|
||||
None.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if value.startswith(b'!'):
|
||||
try:
|
||||
return pickle.loads(value[1:])
|
||||
except pickle.PickleError:
|
||||
return None
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
# before 0.8 we did not have serialization. Still support that.
|
||||
return value
|
||||
|
||||
def get(self, key):
|
||||
return self.load_object(self._client.get(self.key_prefix + key))
|
||||
|
||||
def get_many(self, *keys):
|
||||
if self.key_prefix:
|
||||
keys = [self.key_prefix + key for key in keys]
|
||||
return [self.load_object(x) for x in self._client.mget(keys)]
|
||||
|
||||
def set(self, key, value, timeout=None):
|
||||
timeout = self._normalize_timeout(timeout)
|
||||
dump = self.dump_object(value)
|
||||
if timeout == -1:
|
||||
result = self._client.set(name=self.key_prefix + key,
|
||||
value=dump)
|
||||
else:
|
||||
result = self._client.setex(name=self.key_prefix + key,
|
||||
value=dump, time=timeout)
|
||||
return result
|
||||
|
||||
def add(self, key, value, timeout=None):
|
||||
timeout = self._normalize_timeout(timeout)
|
||||
dump = self.dump_object(value)
|
||||
return (
|
||||
self._client.setnx(name=self.key_prefix + key, value=dump) and
|
||||
self._client.expire(name=self.key_prefix + key, time=timeout)
|
||||
)
|
||||
|
||||
def set_many(self, mapping, timeout=None):
|
||||
timeout = self._normalize_timeout(timeout)
|
||||
# Use transaction=False to batch without calling redis MULTI
|
||||
# which is not supported by twemproxy
|
||||
pipe = self._client.pipeline(transaction=False)
|
||||
|
||||
for key, value in _items(mapping):
|
||||
dump = self.dump_object(value)
|
||||
if timeout == -1:
|
||||
pipe.set(name=self.key_prefix + key, value=dump)
|
||||
else:
|
||||
pipe.setex(name=self.key_prefix + key, value=dump,
|
||||
time=timeout)
|
||||
return pipe.execute()
|
||||
|
||||
def delete(self, key):
|
||||
return self._client.delete(self.key_prefix + key)
|
||||
|
||||
def delete_many(self, *keys):
|
||||
if not keys:
|
||||
return
|
||||
if self.key_prefix:
|
||||
keys = [self.key_prefix + key for key in keys]
|
||||
return self._client.delete(*keys)
|
||||
|
||||
def has(self, key):
|
||||
return self._client.exists(self.key_prefix + key)
|
||||
|
||||
def clear(self):
|
||||
status = False
|
||||
if self.key_prefix:
|
||||
keys = self._client.keys(self.key_prefix + '*')
|
||||
if keys:
|
||||
status = self._client.delete(*keys)
|
||||
else:
|
||||
status = self._client.flushdb()
|
||||
return status
|
||||
|
||||
def inc(self, key, delta=1):
|
||||
return self._client.incr(name=self.key_prefix + key, amount=delta)
|
||||
|
||||
def dec(self, key, delta=1):
|
||||
return self._client.decr(name=self.key_prefix + key, amount=delta)
|
||||
|
||||
|
||||
class FileSystemCache(BaseCache):
|
||||
|
||||
"""A cache that stores the items on the file system. This cache depends
|
||||
on being the only user of the `cache_dir`. Make absolutely sure that
|
||||
nobody but this cache stores files there or otherwise the cache will
|
||||
randomly delete files therein.
|
||||
|
||||
:param cache_dir: the directory where cache files are stored.
|
||||
:param threshold: the maximum number of items the cache stores before
|
||||
it starts deleting some.
|
||||
:param default_timeout: the default timeout that is used if no timeout is
|
||||
specified on :meth:`~BaseCache.set`. A timeout of
|
||||
0 indicates that the cache never expires.
|
||||
:param mode: the file mode wanted for the cache files, default 0600
|
||||
"""
|
||||
|
||||
#: used for temporary files by the FileSystemCache
|
||||
_fs_transaction_suffix = '.__wz_cache'
|
||||
|
||||
def __init__(self, cache_dir, threshold=500, default_timeout=300,
|
||||
mode=0o600):
|
||||
BaseCache.__init__(self, default_timeout)
|
||||
self._path = cache_dir
|
||||
self._threshold = threshold
|
||||
self._mode = mode
|
||||
|
||||
try:
|
||||
os.makedirs(self._path)
|
||||
except OSError as ex:
|
||||
if ex.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
def _normalize_timeout(self, timeout):
|
||||
timeout = BaseCache._normalize_timeout(self, timeout)
|
||||
if timeout != 0:
|
||||
timeout = time() + timeout
|
||||
return int(timeout)
|
||||
|
||||
def _list_dir(self):
|
||||
"""return a list of (fully qualified) cache filenames
|
||||
"""
|
||||
return [os.path.join(self._path, fn) for fn in os.listdir(self._path)
|
||||
if not fn.endswith(self._fs_transaction_suffix)]
|
||||
|
||||
def _prune(self):
|
||||
entries = self._list_dir()
|
||||
if len(entries) > self._threshold:
|
||||
now = time()
|
||||
for idx, fname in enumerate(entries):
|
||||
try:
|
||||
remove = False
|
||||
with open(fname, 'rb') as f:
|
||||
expires = pickle.load(f)
|
||||
remove = (expires != 0 and expires <= now) or idx % 3 == 0
|
||||
|
||||
if remove:
|
||||
os.remove(fname)
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
|
||||
def clear(self):
|
||||
for fname in self._list_dir():
|
||||
try:
|
||||
os.remove(fname)
|
||||
except (IOError, OSError):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_filename(self, key):
|
||||
if isinstance(key, text_type):
|
||||
key = key.encode('utf-8') # XXX unicode review
|
||||
hash = md5(key).hexdigest()
|
||||
return os.path.join(self._path, hash)
|
||||
|
||||
def get(self, key):
|
||||
filename = self._get_filename(key)
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
pickle_time = pickle.load(f)
|
||||
if pickle_time == 0 or pickle_time >= time():
|
||||
return pickle.load(f)
|
||||
else:
|
||||
os.remove(filename)
|
||||
return None
|
||||
except (IOError, OSError, pickle.PickleError):
|
||||
return None
|
||||
|
||||
def add(self, key, value, timeout=None):
|
||||
filename = self._get_filename(key)
|
||||
if not os.path.exists(filename):
|
||||
return self.set(key, value, timeout)
|
||||
return False
|
||||
|
||||
def set(self, key, value, timeout=None):
|
||||
timeout = self._normalize_timeout(timeout)
|
||||
filename = self._get_filename(key)
|
||||
self._prune()
|
||||
try:
|
||||
fd, tmp = tempfile.mkstemp(suffix=self._fs_transaction_suffix,
|
||||
dir=self._path)
|
||||
with os.fdopen(fd, 'wb') as f:
|
||||
pickle.dump(timeout, f, 1)
|
||||
pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
|
||||
rename(tmp, filename)
|
||||
os.chmod(filename, self._mode)
|
||||
except (IOError, OSError):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def delete(self, key):
|
||||
try:
|
||||
os.remove(self._get_filename(key))
|
||||
except (IOError, OSError):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def has(self, key):
|
||||
filename = self._get_filename(key)
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
pickle_time = pickle.load(f)
|
||||
if pickle_time == 0 or pickle_time >= time():
|
||||
return True
|
||||
else:
|
||||
os.remove(filename)
|
||||
return False
|
||||
except (IOError, OSError, pickle.PickleError):
|
||||
return False
|
||||
|
||||
|
||||
class UWSGICache(BaseCache):
|
||||
""" Implements the cache using uWSGI's caching framework.
|
||||
|
||||
.. note::
|
||||
This class cannot be used when running under PyPy, because the uWSGI
|
||||
API implementation for PyPy is lacking the needed functionality.
|
||||
|
||||
:param default_timeout: The default timeout in seconds.
|
||||
:param cache: The name of the caching instance to connect to, for
|
||||
example: mycache@localhost:3031, defaults to an empty string, which
|
||||
means uWSGI will cache in the local instance. If the cache is in the
|
||||
same instance as the werkzeug app, you only have to provide the name of
|
||||
the cache.
|
||||
"""
|
||||
def __init__(self, default_timeout=300, cache=''):
|
||||
BaseCache.__init__(self, default_timeout)
|
||||
|
||||
if platform.python_implementation() == 'PyPy':
|
||||
raise RuntimeError("uWSGI caching does not work under PyPy, see "
|
||||
"the docs for more details.")
|
||||
|
||||
try:
|
||||
import uwsgi
|
||||
self._uwsgi = uwsgi
|
||||
except ImportError:
|
||||
raise RuntimeError("uWSGI could not be imported, are you "
|
||||
"running under uWSGI?")
|
||||
|
||||
self.cache = cache
|
||||
|
||||
def get(self, key):
|
||||
rv = self._uwsgi.cache_get(key, self.cache)
|
||||
if rv is None:
|
||||
return
|
||||
return pickle.loads(rv)
|
||||
|
||||
def delete(self, key):
|
||||
return self._uwsgi.cache_del(key, self.cache)
|
||||
|
||||
def set(self, key, value, timeout=None):
|
||||
return self._uwsgi.cache_update(key, pickle.dumps(value),
|
||||
self._normalize_timeout(timeout),
|
||||
self.cache)
|
||||
|
||||
def add(self, key, value, timeout=None):
|
||||
return self._uwsgi.cache_set(key, pickle.dumps(value),
|
||||
self._normalize_timeout(timeout),
|
||||
self.cache)
|
||||
|
||||
def clear(self):
|
||||
return self._uwsgi.cache_clear(self.cache)
|
||||
|
||||
def has(self, key):
|
||||
return self._uwsgi.cache_exists(key, self.cache) is not None
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/cache.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/cache.pyc
Normal file
Binary file not shown.
254
venv/lib/python2.7/site-packages/werkzeug/contrib/fixers.py
Normal file
254
venv/lib/python2.7/site-packages/werkzeug/contrib/fixers.py
Normal file
@@ -0,0 +1,254 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.contrib.fixers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
This module includes various helpers that fix bugs in web servers. They may
|
||||
be necessary for some versions of a buggy web server but not others. We try
|
||||
to stay updated with the status of the bugs as good as possible but you have
|
||||
to make sure whether they fix the problem you encounter.
|
||||
|
||||
If you notice bugs in webservers not fixed in this module consider
|
||||
contributing a patch.
|
||||
|
||||
:copyright: Copyright 2009 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
try:
|
||||
from urllib import unquote
|
||||
except ImportError:
|
||||
from urllib.parse import unquote
|
||||
|
||||
from werkzeug.http import parse_options_header, parse_cache_control_header, \
|
||||
parse_set_header
|
||||
from werkzeug.useragents import UserAgent
|
||||
from werkzeug.datastructures import Headers, ResponseCacheControl
|
||||
|
||||
|
||||
class CGIRootFix(object):
|
||||
|
||||
"""Wrap the application in this middleware if you are using FastCGI or CGI
|
||||
and you have problems with your app root being set to the cgi script's path
|
||||
instead of the path users are going to visit
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
Added `app_root` parameter and renamed from `LighttpdCGIRootFix`.
|
||||
|
||||
:param app: the WSGI application
|
||||
:param app_root: Defaulting to ``'/'``, you can set this to something else
|
||||
if your app is mounted somewhere else.
|
||||
"""
|
||||
|
||||
def __init__(self, app, app_root='/'):
|
||||
self.app = app
|
||||
self.app_root = app_root
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
# only set PATH_INFO for older versions of Lighty or if no
|
||||
# server software is provided. That's because the test was
|
||||
# added in newer Werkzeug versions and we don't want to break
|
||||
# people's code if they are using this fixer in a test that
|
||||
# does not set the SERVER_SOFTWARE key.
|
||||
if 'SERVER_SOFTWARE' not in environ or \
|
||||
environ['SERVER_SOFTWARE'] < 'lighttpd/1.4.28':
|
||||
environ['PATH_INFO'] = environ.get('SCRIPT_NAME', '') + \
|
||||
environ.get('PATH_INFO', '')
|
||||
environ['SCRIPT_NAME'] = self.app_root.strip('/')
|
||||
return self.app(environ, start_response)
|
||||
|
||||
# backwards compatibility
|
||||
LighttpdCGIRootFix = CGIRootFix
|
||||
|
||||
|
||||
class PathInfoFromRequestUriFix(object):
|
||||
|
||||
"""On windows environment variables are limited to the system charset
|
||||
which makes it impossible to store the `PATH_INFO` variable in the
|
||||
environment without loss of information on some systems.
|
||||
|
||||
This is for example a problem for CGI scripts on a Windows Apache.
|
||||
|
||||
This fixer works by recreating the `PATH_INFO` from `REQUEST_URI`,
|
||||
`REQUEST_URL`, or `UNENCODED_URL` (whatever is available). Thus the
|
||||
fix can only be applied if the webserver supports either of these
|
||||
variables.
|
||||
|
||||
:param app: the WSGI application
|
||||
"""
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
for key in 'REQUEST_URL', 'REQUEST_URI', 'UNENCODED_URL':
|
||||
if key not in environ:
|
||||
continue
|
||||
request_uri = unquote(environ[key])
|
||||
script_name = unquote(environ.get('SCRIPT_NAME', ''))
|
||||
if request_uri.startswith(script_name):
|
||||
environ['PATH_INFO'] = request_uri[len(script_name):] \
|
||||
.split('?', 1)[0]
|
||||
break
|
||||
return self.app(environ, start_response)
|
||||
|
||||
|
||||
class ProxyFix(object):
|
||||
|
||||
"""This middleware can be applied to add HTTP proxy support to an
|
||||
application that was not designed with HTTP proxies in mind. It
|
||||
sets `REMOTE_ADDR`, `HTTP_HOST` from `X-Forwarded` headers. While
|
||||
Werkzeug-based applications already can use
|
||||
:py:func:`werkzeug.wsgi.get_host` to retrieve the current host even if
|
||||
behind proxy setups, this middleware can be used for applications which
|
||||
access the WSGI environment directly.
|
||||
|
||||
If you have more than one proxy server in front of your app, set
|
||||
`num_proxies` accordingly.
|
||||
|
||||
Do not use this middleware in non-proxy setups for security reasons.
|
||||
|
||||
The original values of `REMOTE_ADDR` and `HTTP_HOST` are stored in
|
||||
the WSGI environment as `werkzeug.proxy_fix.orig_remote_addr` and
|
||||
`werkzeug.proxy_fix.orig_http_host`.
|
||||
|
||||
:param app: the WSGI application
|
||||
:param num_proxies: the number of proxy servers in front of the app.
|
||||
"""
|
||||
|
||||
def __init__(self, app, num_proxies=1):
|
||||
self.app = app
|
||||
self.num_proxies = num_proxies
|
||||
|
||||
def get_remote_addr(self, forwarded_for):
|
||||
"""Selects the new remote addr from the given list of ips in
|
||||
X-Forwarded-For. By default it picks the one that the `num_proxies`
|
||||
proxy server provides. Before 0.9 it would always pick the first.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
if len(forwarded_for) >= self.num_proxies:
|
||||
return forwarded_for[-self.num_proxies]
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
getter = environ.get
|
||||
forwarded_proto = getter('HTTP_X_FORWARDED_PROTO', '')
|
||||
forwarded_for = getter('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||
forwarded_host = getter('HTTP_X_FORWARDED_HOST', '')
|
||||
environ.update({
|
||||
'werkzeug.proxy_fix.orig_wsgi_url_scheme': getter('wsgi.url_scheme'),
|
||||
'werkzeug.proxy_fix.orig_remote_addr': getter('REMOTE_ADDR'),
|
||||
'werkzeug.proxy_fix.orig_http_host': getter('HTTP_HOST')
|
||||
})
|
||||
forwarded_for = [x for x in [x.strip() for x in forwarded_for] if x]
|
||||
remote_addr = self.get_remote_addr(forwarded_for)
|
||||
if remote_addr is not None:
|
||||
environ['REMOTE_ADDR'] = remote_addr
|
||||
if forwarded_host:
|
||||
environ['HTTP_HOST'] = forwarded_host
|
||||
if forwarded_proto:
|
||||
environ['wsgi.url_scheme'] = forwarded_proto
|
||||
return self.app(environ, start_response)
|
||||
|
||||
|
||||
class HeaderRewriterFix(object):
|
||||
|
||||
"""This middleware can remove response headers and add others. This
|
||||
is for example useful to remove the `Date` header from responses if you
|
||||
are using a server that adds that header, no matter if it's present or
|
||||
not or to add `X-Powered-By` headers::
|
||||
|
||||
app = HeaderRewriterFix(app, remove_headers=['Date'],
|
||||
add_headers=[('X-Powered-By', 'WSGI')])
|
||||
|
||||
:param app: the WSGI application
|
||||
:param remove_headers: a sequence of header keys that should be
|
||||
removed.
|
||||
:param add_headers: a sequence of ``(key, value)`` tuples that should
|
||||
be added.
|
||||
"""
|
||||
|
||||
def __init__(self, app, remove_headers=None, add_headers=None):
|
||||
self.app = app
|
||||
self.remove_headers = set(x.lower() for x in (remove_headers or ()))
|
||||
self.add_headers = list(add_headers or ())
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
def rewriting_start_response(status, headers, exc_info=None):
|
||||
new_headers = []
|
||||
for key, value in headers:
|
||||
if key.lower() not in self.remove_headers:
|
||||
new_headers.append((key, value))
|
||||
new_headers += self.add_headers
|
||||
return start_response(status, new_headers, exc_info)
|
||||
return self.app(environ, rewriting_start_response)
|
||||
|
||||
|
||||
class InternetExplorerFix(object):
|
||||
|
||||
"""This middleware fixes a couple of bugs with Microsoft Internet
|
||||
Explorer. Currently the following fixes are applied:
|
||||
|
||||
- removing of `Vary` headers for unsupported mimetypes which
|
||||
causes troubles with caching. Can be disabled by passing
|
||||
``fix_vary=False`` to the constructor.
|
||||
see: http://support.microsoft.com/kb/824847/en-us
|
||||
|
||||
- removes offending headers to work around caching bugs in
|
||||
Internet Explorer if `Content-Disposition` is set. Can be
|
||||
disabled by passing ``fix_attach=False`` to the constructor.
|
||||
|
||||
If it does not detect affected Internet Explorer versions it won't touch
|
||||
the request / response.
|
||||
"""
|
||||
|
||||
# This code was inspired by Django fixers for the same bugs. The
|
||||
# fix_vary and fix_attach fixers were originally implemented in Django
|
||||
# by Michael Axiak and is available as part of the Django project:
|
||||
# http://code.djangoproject.com/ticket/4148
|
||||
|
||||
def __init__(self, app, fix_vary=True, fix_attach=True):
|
||||
self.app = app
|
||||
self.fix_vary = fix_vary
|
||||
self.fix_attach = fix_attach
|
||||
|
||||
def fix_headers(self, environ, headers, status=None):
|
||||
if self.fix_vary:
|
||||
header = headers.get('content-type', '')
|
||||
mimetype, options = parse_options_header(header)
|
||||
if mimetype not in ('text/html', 'text/plain', 'text/sgml'):
|
||||
headers.pop('vary', None)
|
||||
|
||||
if self.fix_attach and 'content-disposition' in headers:
|
||||
pragma = parse_set_header(headers.get('pragma', ''))
|
||||
pragma.discard('no-cache')
|
||||
header = pragma.to_header()
|
||||
if not header:
|
||||
headers.pop('pragma', '')
|
||||
else:
|
||||
headers['Pragma'] = header
|
||||
header = headers.get('cache-control', '')
|
||||
if header:
|
||||
cc = parse_cache_control_header(header,
|
||||
cls=ResponseCacheControl)
|
||||
cc.no_cache = None
|
||||
cc.no_store = False
|
||||
header = cc.to_header()
|
||||
if not header:
|
||||
headers.pop('cache-control', '')
|
||||
else:
|
||||
headers['Cache-Control'] = header
|
||||
|
||||
def run_fixed(self, environ, start_response):
|
||||
def fixing_start_response(status, headers, exc_info=None):
|
||||
headers = Headers(headers)
|
||||
self.fix_headers(environ, headers, status)
|
||||
return start_response(status, headers.to_wsgi_list(), exc_info)
|
||||
return self.app(environ, fixing_start_response)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
ua = UserAgent(environ)
|
||||
if ua.browser != 'msie':
|
||||
return self.app(environ, start_response)
|
||||
return self.run_fixed(environ, start_response)
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/fixers.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/fixers.pyc
Normal file
Binary file not shown.
352
venv/lib/python2.7/site-packages/werkzeug/contrib/iterio.py
Normal file
352
venv/lib/python2.7/site-packages/werkzeug/contrib/iterio.py
Normal file
@@ -0,0 +1,352 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""
|
||||
werkzeug.contrib.iterio
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module implements a :class:`IterIO` that converts an iterator into
|
||||
a stream object and the other way round. Converting streams into
|
||||
iterators requires the `greenlet`_ module.
|
||||
|
||||
To convert an iterator into a stream all you have to do is to pass it
|
||||
directly to the :class:`IterIO` constructor. In this example we pass it
|
||||
a newly created generator::
|
||||
|
||||
def foo():
|
||||
yield "something\n"
|
||||
yield "otherthings"
|
||||
stream = IterIO(foo())
|
||||
print stream.read() # read the whole iterator
|
||||
|
||||
The other way round works a bit different because we have to ensure that
|
||||
the code execution doesn't take place yet. An :class:`IterIO` call with a
|
||||
callable as first argument does two things. The function itself is passed
|
||||
an :class:`IterIO` stream it can feed. The object returned by the
|
||||
:class:`IterIO` constructor on the other hand is not an stream object but
|
||||
an iterator::
|
||||
|
||||
def foo(stream):
|
||||
stream.write("some")
|
||||
stream.write("thing")
|
||||
stream.flush()
|
||||
stream.write("otherthing")
|
||||
iterator = IterIO(foo)
|
||||
print iterator.next() # prints something
|
||||
print iterator.next() # prints otherthing
|
||||
iterator.next() # raises StopIteration
|
||||
|
||||
.. _greenlet: https://github.com/python-greenlet/greenlet
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
try:
|
||||
import greenlet
|
||||
except ImportError:
|
||||
greenlet = None
|
||||
|
||||
from werkzeug._compat import implements_iterator
|
||||
|
||||
|
||||
def _mixed_join(iterable, sentinel):
|
||||
"""concatenate any string type in an intelligent way."""
|
||||
iterator = iter(iterable)
|
||||
first_item = next(iterator, sentinel)
|
||||
if isinstance(first_item, bytes):
|
||||
return first_item + b''.join(iterator)
|
||||
return first_item + u''.join(iterator)
|
||||
|
||||
|
||||
def _newline(reference_string):
|
||||
if isinstance(reference_string, bytes):
|
||||
return b'\n'
|
||||
return u'\n'
|
||||
|
||||
|
||||
@implements_iterator
|
||||
class IterIO(object):
|
||||
|
||||
"""Instances of this object implement an interface compatible with the
|
||||
standard Python :class:`file` object. Streams are either read-only or
|
||||
write-only depending on how the object is created.
|
||||
|
||||
If the first argument is an iterable a file like object is returned that
|
||||
returns the contents of the iterable. In case the iterable is empty
|
||||
read operations will return the sentinel value.
|
||||
|
||||
If the first argument is a callable then the stream object will be
|
||||
created and passed to that function. The caller itself however will
|
||||
not receive a stream but an iterable. The function will be be executed
|
||||
step by step as something iterates over the returned iterable. Each
|
||||
call to :meth:`flush` will create an item for the iterable. If
|
||||
:meth:`flush` is called without any writes in-between the sentinel
|
||||
value will be yielded.
|
||||
|
||||
Note for Python 3: due to the incompatible interface of bytes and
|
||||
streams you should set the sentinel value explicitly to an empty
|
||||
bytestring (``b''``) if you are expecting to deal with bytes as
|
||||
otherwise the end of the stream is marked with the wrong sentinel
|
||||
value.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
`sentinel` parameter was added.
|
||||
"""
|
||||
|
||||
def __new__(cls, obj, sentinel=''):
|
||||
try:
|
||||
iterator = iter(obj)
|
||||
except TypeError:
|
||||
return IterI(obj, sentinel)
|
||||
return IterO(iterator, sentinel)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def tell(self):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
return self.pos
|
||||
|
||||
def isatty(self):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
return False
|
||||
|
||||
def seek(self, pos, mode=0):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
raise IOError(9, 'Bad file descriptor')
|
||||
|
||||
def truncate(self, size=None):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
raise IOError(9, 'Bad file descriptor')
|
||||
|
||||
def write(self, s):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
raise IOError(9, 'Bad file descriptor')
|
||||
|
||||
def writelines(self, list):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
raise IOError(9, 'Bad file descriptor')
|
||||
|
||||
def read(self, n=-1):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
raise IOError(9, 'Bad file descriptor')
|
||||
|
||||
def readlines(self, sizehint=0):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
raise IOError(9, 'Bad file descriptor')
|
||||
|
||||
def readline(self, length=None):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
raise IOError(9, 'Bad file descriptor')
|
||||
|
||||
def flush(self):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
raise IOError(9, 'Bad file descriptor')
|
||||
|
||||
def __next__(self):
|
||||
if self.closed:
|
||||
raise StopIteration()
|
||||
line = self.readline()
|
||||
if not line:
|
||||
raise StopIteration()
|
||||
return line
|
||||
|
||||
|
||||
class IterI(IterIO):
|
||||
|
||||
"""Convert an stream into an iterator."""
|
||||
|
||||
def __new__(cls, func, sentinel=''):
|
||||
if greenlet is None:
|
||||
raise RuntimeError('IterI requires greenlet support')
|
||||
stream = object.__new__(cls)
|
||||
stream._parent = greenlet.getcurrent()
|
||||
stream._buffer = []
|
||||
stream.closed = False
|
||||
stream.sentinel = sentinel
|
||||
stream.pos = 0
|
||||
|
||||
def run():
|
||||
func(stream)
|
||||
stream.close()
|
||||
|
||||
g = greenlet.greenlet(run, stream._parent)
|
||||
while 1:
|
||||
rv = g.switch()
|
||||
if not rv:
|
||||
return
|
||||
yield rv[0]
|
||||
|
||||
def close(self):
|
||||
if not self.closed:
|
||||
self.closed = True
|
||||
self._flush_impl()
|
||||
|
||||
def write(self, s):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
if s:
|
||||
self.pos += len(s)
|
||||
self._buffer.append(s)
|
||||
|
||||
def writelines(self, list):
|
||||
for item in list:
|
||||
self.write(item)
|
||||
|
||||
def flush(self):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
self._flush_impl()
|
||||
|
||||
def _flush_impl(self):
|
||||
data = _mixed_join(self._buffer, self.sentinel)
|
||||
self._buffer = []
|
||||
if not data and self.closed:
|
||||
self._parent.switch()
|
||||
else:
|
||||
self._parent.switch((data,))
|
||||
|
||||
|
||||
class IterO(IterIO):
|
||||
|
||||
"""Iter output. Wrap an iterator and give it a stream like interface."""
|
||||
|
||||
def __new__(cls, gen, sentinel=''):
|
||||
self = object.__new__(cls)
|
||||
self._gen = gen
|
||||
self._buf = None
|
||||
self.sentinel = sentinel
|
||||
self.closed = False
|
||||
self.pos = 0
|
||||
return self
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def _buf_append(self, string):
|
||||
'''Replace string directly without appending to an empty string,
|
||||
avoiding type issues.'''
|
||||
if not self._buf:
|
||||
self._buf = string
|
||||
else:
|
||||
self._buf += string
|
||||
|
||||
def close(self):
|
||||
if not self.closed:
|
||||
self.closed = True
|
||||
if hasattr(self._gen, 'close'):
|
||||
self._gen.close()
|
||||
|
||||
def seek(self, pos, mode=0):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
if mode == 1:
|
||||
pos += self.pos
|
||||
elif mode == 2:
|
||||
self.read()
|
||||
self.pos = min(self.pos, self.pos + pos)
|
||||
return
|
||||
elif mode != 0:
|
||||
raise IOError('Invalid argument')
|
||||
buf = []
|
||||
try:
|
||||
tmp_end_pos = len(self._buf)
|
||||
while pos > tmp_end_pos:
|
||||
item = next(self._gen)
|
||||
tmp_end_pos += len(item)
|
||||
buf.append(item)
|
||||
except StopIteration:
|
||||
pass
|
||||
if buf:
|
||||
self._buf_append(_mixed_join(buf, self.sentinel))
|
||||
self.pos = max(0, pos)
|
||||
|
||||
def read(self, n=-1):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
if n < 0:
|
||||
self._buf_append(_mixed_join(self._gen, self.sentinel))
|
||||
result = self._buf[self.pos:]
|
||||
self.pos += len(result)
|
||||
return result
|
||||
new_pos = self.pos + n
|
||||
buf = []
|
||||
try:
|
||||
tmp_end_pos = 0 if self._buf is None else len(self._buf)
|
||||
while new_pos > tmp_end_pos or (self._buf is None and not buf):
|
||||
item = next(self._gen)
|
||||
tmp_end_pos += len(item)
|
||||
buf.append(item)
|
||||
except StopIteration:
|
||||
pass
|
||||
if buf:
|
||||
self._buf_append(_mixed_join(buf, self.sentinel))
|
||||
|
||||
if self._buf is None:
|
||||
return self.sentinel
|
||||
|
||||
new_pos = max(0, new_pos)
|
||||
try:
|
||||
return self._buf[self.pos:new_pos]
|
||||
finally:
|
||||
self.pos = min(new_pos, len(self._buf))
|
||||
|
||||
def readline(self, length=None):
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file')
|
||||
|
||||
nl_pos = -1
|
||||
if self._buf:
|
||||
nl_pos = self._buf.find(_newline(self._buf), self.pos)
|
||||
buf = []
|
||||
try:
|
||||
if self._buf is None:
|
||||
pos = self.pos
|
||||
else:
|
||||
pos = len(self._buf)
|
||||
while nl_pos < 0:
|
||||
item = next(self._gen)
|
||||
local_pos = item.find(_newline(item))
|
||||
buf.append(item)
|
||||
if local_pos >= 0:
|
||||
nl_pos = pos + local_pos
|
||||
break
|
||||
pos += len(item)
|
||||
except StopIteration:
|
||||
pass
|
||||
if buf:
|
||||
self._buf_append(_mixed_join(buf, self.sentinel))
|
||||
|
||||
if self._buf is None:
|
||||
return self.sentinel
|
||||
|
||||
if nl_pos < 0:
|
||||
new_pos = len(self._buf)
|
||||
else:
|
||||
new_pos = nl_pos + 1
|
||||
if length is not None and self.pos + length < new_pos:
|
||||
new_pos = self.pos + length
|
||||
try:
|
||||
return self._buf[self.pos:new_pos]
|
||||
finally:
|
||||
self.pos = min(new_pos, len(self._buf))
|
||||
|
||||
def readlines(self, sizehint=0):
|
||||
total = 0
|
||||
lines = []
|
||||
line = self.readline()
|
||||
while line:
|
||||
lines.append(line)
|
||||
total += len(line)
|
||||
if 0 < sizehint <= total:
|
||||
break
|
||||
line = self.readline()
|
||||
return lines
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/iterio.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/iterio.pyc
Normal file
Binary file not shown.
264
venv/lib/python2.7/site-packages/werkzeug/contrib/jsrouting.py
Normal file
264
venv/lib/python2.7/site-packages/werkzeug/contrib/jsrouting.py
Normal file
@@ -0,0 +1,264 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.contrib.jsrouting
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Addon module that allows to create a JavaScript function from a map
|
||||
that generates rules.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
try:
|
||||
from simplejson import dumps
|
||||
except ImportError:
|
||||
try:
|
||||
from json import dumps
|
||||
except ImportError:
|
||||
def dumps(*args):
|
||||
raise RuntimeError('simplejson required for jsrouting')
|
||||
|
||||
from inspect import getmro
|
||||
from werkzeug.routing import NumberConverter
|
||||
from werkzeug._compat import iteritems
|
||||
|
||||
|
||||
def render_template(name_parts, rules, converters):
|
||||
result = u''
|
||||
if name_parts:
|
||||
for idx in range(0, len(name_parts) - 1):
|
||||
name = u'.'.join(name_parts[:idx + 1])
|
||||
result += u"if (typeof %s === 'undefined') %s = {}\n" % (name, name)
|
||||
result += '%s = ' % '.'.join(name_parts)
|
||||
result += """(function (server_name, script_name, subdomain, url_scheme) {
|
||||
var converters = [%(converters)s];
|
||||
var rules = %(rules)s;
|
||||
function in_array(array, value) {
|
||||
if (array.indexOf != undefined) {
|
||||
return array.indexOf(value) != -1;
|
||||
}
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
if (array[i] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function array_diff(array1, array2) {
|
||||
array1 = array1.slice();
|
||||
for (var i = array1.length-1; i >= 0; i--) {
|
||||
if (in_array(array2, array1[i])) {
|
||||
array1.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return array1;
|
||||
}
|
||||
function split_obj(obj) {
|
||||
var names = [];
|
||||
var values = [];
|
||||
for (var name in obj) {
|
||||
if (typeof(obj[name]) != 'function') {
|
||||
names.push(name);
|
||||
values.push(obj[name]);
|
||||
}
|
||||
}
|
||||
return {names: names, values: values, original: obj};
|
||||
}
|
||||
function suitable(rule, args) {
|
||||
var default_args = split_obj(rule.defaults || {});
|
||||
var diff_arg_names = array_diff(rule.arguments, default_args.names);
|
||||
|
||||
for (var i = 0; i < diff_arg_names.length; i++) {
|
||||
if (!in_array(args.names, diff_arg_names[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (array_diff(rule.arguments, args.names).length == 0) {
|
||||
if (rule.defaults == null) {
|
||||
return true;
|
||||
}
|
||||
for (var i = 0; i < default_args.names.length; i++) {
|
||||
var key = default_args.names[i];
|
||||
var value = default_args.values[i];
|
||||
if (value != args.original[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
function build(rule, args) {
|
||||
var tmp = [];
|
||||
var processed = rule.arguments.slice();
|
||||
for (var i = 0; i < rule.trace.length; i++) {
|
||||
var part = rule.trace[i];
|
||||
if (part.is_dynamic) {
|
||||
var converter = converters[rule.converters[part.data]];
|
||||
var data = converter(args.original[part.data]);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
tmp.push(data);
|
||||
processed.push(part.name);
|
||||
} else {
|
||||
tmp.push(part.data);
|
||||
}
|
||||
}
|
||||
tmp = tmp.join('');
|
||||
var pipe = tmp.indexOf('|');
|
||||
var subdomain = tmp.substring(0, pipe);
|
||||
var url = tmp.substring(pipe+1);
|
||||
|
||||
var unprocessed = array_diff(args.names, processed);
|
||||
var first_query_var = true;
|
||||
for (var i = 0; i < unprocessed.length; i++) {
|
||||
if (first_query_var) {
|
||||
url += '?';
|
||||
} else {
|
||||
url += '&';
|
||||
}
|
||||
first_query_var = false;
|
||||
url += encodeURIComponent(unprocessed[i]);
|
||||
url += '=';
|
||||
url += encodeURIComponent(args.original[unprocessed[i]]);
|
||||
}
|
||||
return {subdomain: subdomain, path: url};
|
||||
}
|
||||
function lstrip(s, c) {
|
||||
while (s && s.substring(0, 1) == c) {
|
||||
s = s.substring(1);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
function rstrip(s, c) {
|
||||
while (s && s.substring(s.length-1, s.length) == c) {
|
||||
s = s.substring(0, s.length-1);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
return function(endpoint, args, force_external) {
|
||||
args = split_obj(args);
|
||||
var rv = null;
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
var rule = rules[i];
|
||||
if (rule.endpoint != endpoint) continue;
|
||||
if (suitable(rule, args)) {
|
||||
rv = build(rule, args);
|
||||
if (rv != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rv == null) {
|
||||
return null;
|
||||
}
|
||||
if (!force_external && rv.subdomain == subdomain) {
|
||||
return rstrip(script_name, '/') + '/' + lstrip(rv.path, '/');
|
||||
} else {
|
||||
return url_scheme + '://'
|
||||
+ (rv.subdomain ? rv.subdomain + '.' : '')
|
||||
+ server_name + rstrip(script_name, '/')
|
||||
+ '/' + lstrip(rv.path, '/');
|
||||
}
|
||||
};
|
||||
})""" % {'converters': u', '.join(converters),
|
||||
'rules': rules}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_map(map, name='url_map'):
|
||||
"""
|
||||
Generates a JavaScript function containing the rules defined in
|
||||
this map, to be used with a MapAdapter's generate_javascript
|
||||
method. If you don't pass a name the returned JavaScript code is
|
||||
an expression that returns a function. Otherwise it's a standalone
|
||||
script that assigns the function with that name. Dotted names are
|
||||
resolved (so you an use a name like 'obj.url_for')
|
||||
|
||||
In order to use JavaScript generation, simplejson must be installed.
|
||||
|
||||
Note that using this feature will expose the rules
|
||||
defined in your map to users. If your rules contain sensitive
|
||||
information, don't use JavaScript generation!
|
||||
"""
|
||||
from warnings import warn
|
||||
warn(DeprecationWarning('This module is deprecated'))
|
||||
map.update()
|
||||
rules = []
|
||||
converters = []
|
||||
for rule in map.iter_rules():
|
||||
trace = [{
|
||||
'is_dynamic': is_dynamic,
|
||||
'data': data
|
||||
} for is_dynamic, data in rule._trace]
|
||||
rule_converters = {}
|
||||
for key, converter in iteritems(rule._converters):
|
||||
js_func = js_to_url_function(converter)
|
||||
try:
|
||||
index = converters.index(js_func)
|
||||
except ValueError:
|
||||
converters.append(js_func)
|
||||
index = len(converters) - 1
|
||||
rule_converters[key] = index
|
||||
rules.append({
|
||||
u'endpoint': rule.endpoint,
|
||||
u'arguments': list(rule.arguments),
|
||||
u'converters': rule_converters,
|
||||
u'trace': trace,
|
||||
u'defaults': rule.defaults
|
||||
})
|
||||
|
||||
return render_template(name_parts=name and name.split('.') or [],
|
||||
rules=dumps(rules),
|
||||
converters=converters)
|
||||
|
||||
|
||||
def generate_adapter(adapter, name='url_for', map_name='url_map'):
|
||||
"""Generates the url building function for a map."""
|
||||
values = {
|
||||
u'server_name': dumps(adapter.server_name),
|
||||
u'script_name': dumps(adapter.script_name),
|
||||
u'subdomain': dumps(adapter.subdomain),
|
||||
u'url_scheme': dumps(adapter.url_scheme),
|
||||
u'name': name,
|
||||
u'map_name': map_name
|
||||
}
|
||||
return u'''\
|
||||
var %(name)s = %(map_name)s(
|
||||
%(server_name)s,
|
||||
%(script_name)s,
|
||||
%(subdomain)s,
|
||||
%(url_scheme)s
|
||||
);''' % values
|
||||
|
||||
|
||||
def js_to_url_function(converter):
|
||||
"""Get the JavaScript converter function from a rule."""
|
||||
if hasattr(converter, 'js_to_url_function'):
|
||||
data = converter.js_to_url_function()
|
||||
else:
|
||||
for cls in getmro(type(converter)):
|
||||
if cls in js_to_url_functions:
|
||||
data = js_to_url_functions[cls](converter)
|
||||
break
|
||||
else:
|
||||
return 'encodeURIComponent'
|
||||
return '(function(value) { %s })' % data
|
||||
|
||||
|
||||
def NumberConverter_js_to_url(conv):
|
||||
if conv.fixed_digits:
|
||||
return u'''\
|
||||
var result = value.toString();
|
||||
while (result.length < %s)
|
||||
result = '0' + result;
|
||||
return result;''' % conv.fixed_digits
|
||||
return u'return value.toString();'
|
||||
|
||||
|
||||
js_to_url_functions = {
|
||||
NumberConverter: NumberConverter_js_to_url
|
||||
}
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/jsrouting.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/jsrouting.pyc
Normal file
Binary file not shown.
41
venv/lib/python2.7/site-packages/werkzeug/contrib/limiter.py
Normal file
41
venv/lib/python2.7/site-packages/werkzeug/contrib/limiter.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.contrib.limiter
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A middleware that limits incoming data. This works around problems with
|
||||
Trac_ or Django_ because those directly stream into the memory.
|
||||
|
||||
.. _Trac: http://trac.edgewall.org/
|
||||
.. _Django: http://www.djangoproject.com/
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from warnings import warn
|
||||
|
||||
from werkzeug.wsgi import LimitedStream
|
||||
|
||||
|
||||
class StreamLimitMiddleware(object):
|
||||
|
||||
"""Limits the input stream to a given number of bytes. This is useful if
|
||||
you have a WSGI application that reads form data into memory (django for
|
||||
example) and you don't want users to harm the server by uploading tons of
|
||||
data.
|
||||
|
||||
Default is 10MB
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
Deprecated middleware.
|
||||
"""
|
||||
|
||||
def __init__(self, app, maximum_size=1024 * 1024 * 10):
|
||||
warn(DeprecationWarning('This middleware is deprecated'))
|
||||
self.app = app
|
||||
self.maximum_size = maximum_size
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
limit = min(self.maximum_size, int(environ.get('CONTENT_LENGTH') or 0))
|
||||
environ['wsgi.input'] = LimitedStream(environ['wsgi.input'], limit)
|
||||
return self.app(environ, start_response)
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/limiter.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/limiter.pyc
Normal file
Binary file not shown.
343
venv/lib/python2.7/site-packages/werkzeug/contrib/lint.py
Normal file
343
venv/lib/python2.7/site-packages/werkzeug/contrib/lint.py
Normal file
@@ -0,0 +1,343 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.contrib.lint
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
This module provides a middleware that performs sanity checks of the WSGI
|
||||
application. It checks that :pep:`333` is properly implemented and warns
|
||||
on some common HTTP errors such as non-empty responses for 304 status
|
||||
codes.
|
||||
|
||||
This module provides a middleware, the :class:`LintMiddleware`. Wrap your
|
||||
application with it and it will warn about common problems with WSGI and
|
||||
HTTP while your application is running.
|
||||
|
||||
It's strongly recommended to use it during development.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from urlparse import urlparse
|
||||
|
||||
from warnings import warn
|
||||
|
||||
from werkzeug.datastructures import Headers
|
||||
from werkzeug.http import is_entity_header
|
||||
from werkzeug.wsgi import FileWrapper
|
||||
from werkzeug._compat import string_types
|
||||
|
||||
|
||||
class WSGIWarning(Warning):
|
||||
|
||||
"""Warning class for WSGI warnings."""
|
||||
|
||||
|
||||
class HTTPWarning(Warning):
|
||||
|
||||
"""Warning class for HTTP warnings."""
|
||||
|
||||
|
||||
def check_string(context, obj, stacklevel=3):
|
||||
if type(obj) is not str:
|
||||
warn(WSGIWarning('%s requires bytestrings, got %s' %
|
||||
(context, obj.__class__.__name__)))
|
||||
|
||||
|
||||
class InputStream(object):
|
||||
|
||||
def __init__(self, stream):
|
||||
self._stream = stream
|
||||
|
||||
def read(self, *args):
|
||||
if len(args) == 0:
|
||||
warn(WSGIWarning('wsgi does not guarantee an EOF marker on the '
|
||||
'input stream, thus making calls to '
|
||||
'wsgi.input.read() unsafe. Conforming servers '
|
||||
'may never return from this call.'),
|
||||
stacklevel=2)
|
||||
elif len(args) != 1:
|
||||
warn(WSGIWarning('too many parameters passed to wsgi.input.read()'),
|
||||
stacklevel=2)
|
||||
return self._stream.read(*args)
|
||||
|
||||
def readline(self, *args):
|
||||
if len(args) == 0:
|
||||
warn(WSGIWarning('Calls to wsgi.input.readline() without arguments'
|
||||
' are unsafe. Use wsgi.input.read() instead.'),
|
||||
stacklevel=2)
|
||||
elif len(args) == 1:
|
||||
warn(WSGIWarning('wsgi.input.readline() was called with a size hint. '
|
||||
'WSGI does not support this, although it\'s available '
|
||||
'on all major servers.'),
|
||||
stacklevel=2)
|
||||
else:
|
||||
raise TypeError('too many arguments passed to wsgi.input.readline()')
|
||||
return self._stream.readline(*args)
|
||||
|
||||
def __iter__(self):
|
||||
try:
|
||||
return iter(self._stream)
|
||||
except TypeError:
|
||||
warn(WSGIWarning('wsgi.input is not iterable.'), stacklevel=2)
|
||||
return iter(())
|
||||
|
||||
def close(self):
|
||||
warn(WSGIWarning('application closed the input stream!'),
|
||||
stacklevel=2)
|
||||
self._stream.close()
|
||||
|
||||
|
||||
class ErrorStream(object):
|
||||
|
||||
def __init__(self, stream):
|
||||
self._stream = stream
|
||||
|
||||
def write(self, s):
|
||||
check_string('wsgi.error.write()', s)
|
||||
self._stream.write(s)
|
||||
|
||||
def flush(self):
|
||||
self._stream.flush()
|
||||
|
||||
def writelines(self, seq):
|
||||
for line in seq:
|
||||
self.write(seq)
|
||||
|
||||
def close(self):
|
||||
warn(WSGIWarning('application closed the error stream!'),
|
||||
stacklevel=2)
|
||||
self._stream.close()
|
||||
|
||||
|
||||
class GuardedWrite(object):
|
||||
|
||||
def __init__(self, write, chunks):
|
||||
self._write = write
|
||||
self._chunks = chunks
|
||||
|
||||
def __call__(self, s):
|
||||
check_string('write()', s)
|
||||
self._write.write(s)
|
||||
self._chunks.append(len(s))
|
||||
|
||||
|
||||
class GuardedIterator(object):
|
||||
|
||||
def __init__(self, iterator, headers_set, chunks):
|
||||
self._iterator = iterator
|
||||
self._next = iter(iterator).next
|
||||
self.closed = False
|
||||
self.headers_set = headers_set
|
||||
self.chunks = chunks
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
if self.closed:
|
||||
warn(WSGIWarning('iterated over closed app_iter'),
|
||||
stacklevel=2)
|
||||
rv = self._next()
|
||||
if not self.headers_set:
|
||||
warn(WSGIWarning('Application returned before it '
|
||||
'started the response'), stacklevel=2)
|
||||
check_string('application iterator items', rv)
|
||||
self.chunks.append(len(rv))
|
||||
return rv
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
if hasattr(self._iterator, 'close'):
|
||||
self._iterator.close()
|
||||
|
||||
if self.headers_set:
|
||||
status_code, headers = self.headers_set
|
||||
bytes_sent = sum(self.chunks)
|
||||
content_length = headers.get('content-length', type=int)
|
||||
|
||||
if status_code == 304:
|
||||
for key, value in headers:
|
||||
key = key.lower()
|
||||
if key not in ('expires', 'content-location') and \
|
||||
is_entity_header(key):
|
||||
warn(HTTPWarning('entity header %r found in 304 '
|
||||
'response' % key))
|
||||
if bytes_sent:
|
||||
warn(HTTPWarning('304 responses must not have a body'))
|
||||
elif 100 <= status_code < 200 or status_code == 204:
|
||||
if content_length != 0:
|
||||
warn(HTTPWarning('%r responses must have an empty '
|
||||
'content length') % status_code)
|
||||
if bytes_sent:
|
||||
warn(HTTPWarning('%r responses must not have a body' %
|
||||
status_code))
|
||||
elif content_length is not None and content_length != bytes_sent:
|
||||
warn(WSGIWarning('Content-Length and the number of bytes '
|
||||
'sent to the client do not match.'))
|
||||
|
||||
def __del__(self):
|
||||
if not self.closed:
|
||||
try:
|
||||
warn(WSGIWarning('Iterator was garbage collected before '
|
||||
'it was closed.'))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class LintMiddleware(object):
|
||||
|
||||
"""This middleware wraps an application and warns on common errors.
|
||||
Among other thing it currently checks for the following problems:
|
||||
|
||||
- invalid status codes
|
||||
- non-bytestrings sent to the WSGI server
|
||||
- strings returned from the WSGI application
|
||||
- non-empty conditional responses
|
||||
- unquoted etags
|
||||
- relative URLs in the Location header
|
||||
- unsafe calls to wsgi.input
|
||||
- unclosed iterators
|
||||
|
||||
Detected errors are emitted using the standard Python :mod:`warnings`
|
||||
system and usually end up on :data:`stderr`.
|
||||
|
||||
::
|
||||
|
||||
from werkzeug.contrib.lint import LintMiddleware
|
||||
app = LintMiddleware(app)
|
||||
|
||||
:param app: the application to wrap
|
||||
"""
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def check_environ(self, environ):
|
||||
if type(environ) is not dict:
|
||||
warn(WSGIWarning('WSGI environment is not a standard python dict.'),
|
||||
stacklevel=4)
|
||||
for key in ('REQUEST_METHOD', 'SERVER_NAME', 'SERVER_PORT',
|
||||
'wsgi.version', 'wsgi.input', 'wsgi.errors',
|
||||
'wsgi.multithread', 'wsgi.multiprocess',
|
||||
'wsgi.run_once'):
|
||||
if key not in environ:
|
||||
warn(WSGIWarning('required environment key %r not found'
|
||||
% key), stacklevel=3)
|
||||
if environ['wsgi.version'] != (1, 0):
|
||||
warn(WSGIWarning('environ is not a WSGI 1.0 environ'),
|
||||
stacklevel=3)
|
||||
|
||||
script_name = environ.get('SCRIPT_NAME', '')
|
||||
if script_name and script_name[:1] != '/':
|
||||
warn(WSGIWarning('SCRIPT_NAME does not start with a slash: %r'
|
||||
% script_name), stacklevel=3)
|
||||
path_info = environ.get('PATH_INFO', '')
|
||||
if path_info[:1] != '/':
|
||||
warn(WSGIWarning('PATH_INFO does not start with a slash: %r'
|
||||
% path_info), stacklevel=3)
|
||||
|
||||
def check_start_response(self, status, headers, exc_info):
|
||||
check_string('status', status)
|
||||
status_code = status.split(None, 1)[0]
|
||||
if len(status_code) != 3 or not status_code.isdigit():
|
||||
warn(WSGIWarning('Status code must be three digits'), stacklevel=3)
|
||||
if len(status) < 4 or status[3] != ' ':
|
||||
warn(WSGIWarning('Invalid value for status %r. Valid '
|
||||
'status strings are three digits, a space '
|
||||
'and a status explanation'), stacklevel=3)
|
||||
status_code = int(status_code)
|
||||
if status_code < 100:
|
||||
warn(WSGIWarning('status code < 100 detected'), stacklevel=3)
|
||||
|
||||
if type(headers) is not list:
|
||||
warn(WSGIWarning('header list is not a list'), stacklevel=3)
|
||||
for item in headers:
|
||||
if type(item) is not tuple or len(item) != 2:
|
||||
warn(WSGIWarning('Headers must tuple 2-item tuples'),
|
||||
stacklevel=3)
|
||||
name, value = item
|
||||
if type(name) is not str or type(value) is not str:
|
||||
warn(WSGIWarning('header items must be strings'),
|
||||
stacklevel=3)
|
||||
if name.lower() == 'status':
|
||||
warn(WSGIWarning('The status header is not supported due to '
|
||||
'conflicts with the CGI spec.'),
|
||||
stacklevel=3)
|
||||
|
||||
if exc_info is not None and not isinstance(exc_info, tuple):
|
||||
warn(WSGIWarning('invalid value for exc_info'), stacklevel=3)
|
||||
|
||||
headers = Headers(headers)
|
||||
self.check_headers(headers)
|
||||
|
||||
return status_code, headers
|
||||
|
||||
def check_headers(self, headers):
|
||||
etag = headers.get('etag')
|
||||
if etag is not None:
|
||||
if etag.startswith(('W/', 'w/')):
|
||||
if etag.startswith('w/'):
|
||||
warn(HTTPWarning('weak etag indicator should be upcase.'),
|
||||
stacklevel=4)
|
||||
etag = etag[2:]
|
||||
if not (etag[:1] == etag[-1:] == '"'):
|
||||
warn(HTTPWarning('unquoted etag emitted.'), stacklevel=4)
|
||||
|
||||
location = headers.get('location')
|
||||
if location is not None:
|
||||
if not urlparse(location).netloc:
|
||||
warn(HTTPWarning('absolute URLs required for location header'),
|
||||
stacklevel=4)
|
||||
|
||||
def check_iterator(self, app_iter):
|
||||
if isinstance(app_iter, string_types):
|
||||
warn(WSGIWarning('application returned string. Response will '
|
||||
'send character for character to the client '
|
||||
'which will kill the performance. Return a '
|
||||
'list or iterable instead.'), stacklevel=3)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if len(args) != 2:
|
||||
warn(WSGIWarning('Two arguments to WSGI app required'), stacklevel=2)
|
||||
if kwargs:
|
||||
warn(WSGIWarning('No keyword arguments to WSGI app allowed'),
|
||||
stacklevel=2)
|
||||
environ, start_response = args
|
||||
|
||||
self.check_environ(environ)
|
||||
environ['wsgi.input'] = InputStream(environ['wsgi.input'])
|
||||
environ['wsgi.errors'] = ErrorStream(environ['wsgi.errors'])
|
||||
|
||||
# hook our own file wrapper in so that applications will always
|
||||
# iterate to the end and we can check the content length
|
||||
environ['wsgi.file_wrapper'] = FileWrapper
|
||||
|
||||
headers_set = []
|
||||
chunks = []
|
||||
|
||||
def checking_start_response(*args, **kwargs):
|
||||
if len(args) not in (2, 3):
|
||||
warn(WSGIWarning('Invalid number of arguments: %s, expected '
|
||||
'2 or 3' % len(args), stacklevel=2))
|
||||
if kwargs:
|
||||
warn(WSGIWarning('no keyword arguments allowed.'))
|
||||
|
||||
status, headers = args[:2]
|
||||
if len(args) == 3:
|
||||
exc_info = args[2]
|
||||
else:
|
||||
exc_info = None
|
||||
|
||||
headers_set[:] = self.check_start_response(status, headers,
|
||||
exc_info)
|
||||
return GuardedWrite(start_response(status, headers, exc_info),
|
||||
chunks)
|
||||
|
||||
app_iter = self.app(environ, checking_start_response)
|
||||
self.check_iterator(app_iter)
|
||||
return GuardedIterator(app_iter, headers_set, chunks)
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/lint.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/lint.pyc
Normal file
Binary file not shown.
147
venv/lib/python2.7/site-packages/werkzeug/contrib/profiler.py
Normal file
147
venv/lib/python2.7/site-packages/werkzeug/contrib/profiler.py
Normal file
@@ -0,0 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.contrib.profiler
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides a simple WSGI profiler middleware for finding
|
||||
bottlenecks in web application. It uses the :mod:`profile` or
|
||||
:mod:`cProfile` module to do the profiling and writes the stats to the
|
||||
stream provided (defaults to stderr).
|
||||
|
||||
Example usage::
|
||||
|
||||
from werkzeug.contrib.profiler import ProfilerMiddleware
|
||||
app = ProfilerMiddleware(app)
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
import os.path
|
||||
try:
|
||||
try:
|
||||
from cProfile import Profile
|
||||
except ImportError:
|
||||
from profile import Profile
|
||||
from pstats import Stats
|
||||
available = True
|
||||
except ImportError:
|
||||
available = False
|
||||
|
||||
|
||||
class MergeStream(object):
|
||||
|
||||
"""An object that redirects `write` calls to multiple streams.
|
||||
Use this to log to both `sys.stdout` and a file::
|
||||
|
||||
f = open('profiler.log', 'w')
|
||||
stream = MergeStream(sys.stdout, f)
|
||||
profiler = ProfilerMiddleware(app, stream)
|
||||
"""
|
||||
|
||||
def __init__(self, *streams):
|
||||
if not streams:
|
||||
raise TypeError('at least one stream must be given')
|
||||
self.streams = streams
|
||||
|
||||
def write(self, data):
|
||||
for stream in self.streams:
|
||||
stream.write(data)
|
||||
|
||||
|
||||
class ProfilerMiddleware(object):
|
||||
|
||||
"""Simple profiler middleware. Wraps a WSGI application and profiles
|
||||
a request. This intentionally buffers the response so that timings are
|
||||
more exact.
|
||||
|
||||
By giving the `profile_dir` argument, pstat.Stats files are saved to that
|
||||
directory, one file per request. Without it, a summary is printed to
|
||||
`stream` instead.
|
||||
|
||||
For the exact meaning of `sort_by` and `restrictions` consult the
|
||||
:mod:`profile` documentation.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
Added support for `restrictions` and `profile_dir`.
|
||||
|
||||
:param app: the WSGI application to profile.
|
||||
:param stream: the stream for the profiled stats. defaults to stderr.
|
||||
:param sort_by: a tuple of columns to sort the result by.
|
||||
:param restrictions: a tuple of profiling strictions, not used if dumping
|
||||
to `profile_dir`.
|
||||
:param profile_dir: directory name to save pstat files
|
||||
"""
|
||||
|
||||
def __init__(self, app, stream=None,
|
||||
sort_by=('time', 'calls'), restrictions=(), profile_dir=None):
|
||||
if not available:
|
||||
raise RuntimeError('the profiler is not available because '
|
||||
'profile or pstat is not installed.')
|
||||
self._app = app
|
||||
self._stream = stream or sys.stdout
|
||||
self._sort_by = sort_by
|
||||
self._restrictions = restrictions
|
||||
self._profile_dir = profile_dir
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
response_body = []
|
||||
|
||||
def catching_start_response(status, headers, exc_info=None):
|
||||
start_response(status, headers, exc_info)
|
||||
return response_body.append
|
||||
|
||||
def runapp():
|
||||
appiter = self._app(environ, catching_start_response)
|
||||
response_body.extend(appiter)
|
||||
if hasattr(appiter, 'close'):
|
||||
appiter.close()
|
||||
|
||||
p = Profile()
|
||||
start = time.time()
|
||||
p.runcall(runapp)
|
||||
body = b''.join(response_body)
|
||||
elapsed = time.time() - start
|
||||
|
||||
if self._profile_dir is not None:
|
||||
prof_filename = os.path.join(self._profile_dir,
|
||||
'%s.%s.%06dms.%d.prof' % (
|
||||
environ['REQUEST_METHOD'],
|
||||
environ.get('PATH_INFO').strip(
|
||||
'/').replace('/', '.') or 'root',
|
||||
elapsed * 1000.0,
|
||||
time.time()
|
||||
))
|
||||
p.dump_stats(prof_filename)
|
||||
|
||||
else:
|
||||
stats = Stats(p, stream=self._stream)
|
||||
stats.sort_stats(*self._sort_by)
|
||||
|
||||
self._stream.write('-' * 80)
|
||||
self._stream.write('\nPATH: %r\n' % environ.get('PATH_INFO'))
|
||||
stats.print_stats(*self._restrictions)
|
||||
self._stream.write('-' * 80 + '\n\n')
|
||||
|
||||
return [body]
|
||||
|
||||
|
||||
def make_action(app_factory, hostname='localhost', port=5000,
|
||||
threaded=False, processes=1, stream=None,
|
||||
sort_by=('time', 'calls'), restrictions=()):
|
||||
"""Return a new callback for :mod:`werkzeug.script` that starts a local
|
||||
server with the profiler enabled.
|
||||
|
||||
::
|
||||
|
||||
from werkzeug.contrib import profiler
|
||||
action_profile = profiler.make_action(make_app)
|
||||
"""
|
||||
def action(hostname=('h', hostname), port=('p', port),
|
||||
threaded=threaded, processes=processes):
|
||||
"""Start a new development server."""
|
||||
from werkzeug.serving import run_simple
|
||||
app = ProfilerMiddleware(app_factory(), stream, sort_by, restrictions)
|
||||
run_simple(hostname, port, app, False, None, threaded, processes)
|
||||
return action
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/profiler.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/profiler.pyc
Normal file
Binary file not shown.
@@ -0,0 +1,323 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""
|
||||
werkzeug.contrib.securecookie
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module implements a cookie that is not alterable from the client
|
||||
because it adds a checksum the server checks for. You can use it as
|
||||
session replacement if all you have is a user id or something to mark
|
||||
a logged in user.
|
||||
|
||||
Keep in mind that the data is still readable from the client as a
|
||||
normal cookie is. However you don't have to store and flush the
|
||||
sessions you have at the server.
|
||||
|
||||
Example usage:
|
||||
|
||||
>>> from werkzeug.contrib.securecookie import SecureCookie
|
||||
>>> x = SecureCookie({"foo": 42, "baz": (1, 2, 3)}, "deadbeef")
|
||||
|
||||
Dumping into a string so that one can store it in a cookie:
|
||||
|
||||
>>> value = x.serialize()
|
||||
|
||||
Loading from that string again:
|
||||
|
||||
>>> x = SecureCookie.unserialize(value, "deadbeef")
|
||||
>>> x["baz"]
|
||||
(1, 2, 3)
|
||||
|
||||
If someone modifies the cookie and the checksum is wrong the unserialize
|
||||
method will fail silently and return a new empty `SecureCookie` object.
|
||||
|
||||
Keep in mind that the values will be visible in the cookie so do not
|
||||
store data in a cookie you don't want the user to see.
|
||||
|
||||
Application Integration
|
||||
=======================
|
||||
|
||||
If you are using the werkzeug request objects you could integrate the
|
||||
secure cookie into your application like this::
|
||||
|
||||
from werkzeug.utils import cached_property
|
||||
from werkzeug.wrappers import BaseRequest
|
||||
from werkzeug.contrib.securecookie import SecureCookie
|
||||
|
||||
# don't use this key but a different one; you could just use
|
||||
# os.urandom(20) to get something random
|
||||
SECRET_KEY = '\xfa\xdd\xb8z\xae\xe0}4\x8b\xea'
|
||||
|
||||
class Request(BaseRequest):
|
||||
|
||||
@cached_property
|
||||
def client_session(self):
|
||||
data = self.cookies.get('session_data')
|
||||
if not data:
|
||||
return SecureCookie(secret_key=SECRET_KEY)
|
||||
return SecureCookie.unserialize(data, SECRET_KEY)
|
||||
|
||||
def application(environ, start_response):
|
||||
request = Request(environ)
|
||||
|
||||
# get a response object here
|
||||
response = ...
|
||||
|
||||
if request.client_session.should_save:
|
||||
session_data = request.client_session.serialize()
|
||||
response.set_cookie('session_data', session_data,
|
||||
httponly=True)
|
||||
return response(environ, start_response)
|
||||
|
||||
A less verbose integration can be achieved by using shorthand methods::
|
||||
|
||||
class Request(BaseRequest):
|
||||
|
||||
@cached_property
|
||||
def client_session(self):
|
||||
return SecureCookie.load_cookie(self, secret_key=COOKIE_SECRET)
|
||||
|
||||
def application(environ, start_response):
|
||||
request = Request(environ)
|
||||
|
||||
# get a response object here
|
||||
response = ...
|
||||
|
||||
request.client_session.save_cookie(response)
|
||||
return response(environ, start_response)
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import pickle
|
||||
import base64
|
||||
from hmac import new as hmac
|
||||
from time import time
|
||||
from hashlib import sha1 as _default_hash
|
||||
|
||||
from werkzeug._compat import iteritems, text_type
|
||||
from werkzeug.urls import url_quote_plus, url_unquote_plus
|
||||
from werkzeug._internal import _date_to_unix
|
||||
from werkzeug.contrib.sessions import ModificationTrackingDict
|
||||
from werkzeug.security import safe_str_cmp
|
||||
from werkzeug._compat import to_native
|
||||
|
||||
|
||||
class UnquoteError(Exception):
|
||||
|
||||
"""Internal exception used to signal failures on quoting."""
|
||||
|
||||
|
||||
class SecureCookie(ModificationTrackingDict):
|
||||
|
||||
"""Represents a secure cookie. You can subclass this class and provide
|
||||
an alternative mac method. The import thing is that the mac method
|
||||
is a function with a similar interface to the hashlib. Required
|
||||
methods are update() and digest().
|
||||
|
||||
Example usage:
|
||||
|
||||
>>> x = SecureCookie({"foo": 42, "baz": (1, 2, 3)}, "deadbeef")
|
||||
>>> x["foo"]
|
||||
42
|
||||
>>> x["baz"]
|
||||
(1, 2, 3)
|
||||
>>> x["blafasel"] = 23
|
||||
>>> x.should_save
|
||||
True
|
||||
|
||||
:param data: the initial data. Either a dict, list of tuples or `None`.
|
||||
:param secret_key: the secret key. If not set `None` or not specified
|
||||
it has to be set before :meth:`serialize` is called.
|
||||
:param new: The initial value of the `new` flag.
|
||||
"""
|
||||
|
||||
#: The hash method to use. This has to be a module with a new function
|
||||
#: or a function that creates a hashlib object. Such as `hashlib.md5`
|
||||
#: Subclasses can override this attribute. The default hash is sha1.
|
||||
#: Make sure to wrap this in staticmethod() if you store an arbitrary
|
||||
#: function there such as hashlib.sha1 which might be implemented
|
||||
#: as a function.
|
||||
hash_method = staticmethod(_default_hash)
|
||||
|
||||
#: the module used for serialization. Unless overriden by subclasses
|
||||
#: the standard pickle module is used.
|
||||
serialization_method = pickle
|
||||
|
||||
#: if the contents should be base64 quoted. This can be disabled if the
|
||||
#: serialization process returns cookie safe strings only.
|
||||
quote_base64 = True
|
||||
|
||||
def __init__(self, data=None, secret_key=None, new=True):
|
||||
ModificationTrackingDict.__init__(self, data or ())
|
||||
# explicitly convert it into a bytestring because python 2.6
|
||||
# no longer performs an implicit string conversion on hmac
|
||||
if secret_key is not None:
|
||||
secret_key = bytes(secret_key)
|
||||
self.secret_key = secret_key
|
||||
self.new = new
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s%s>' % (
|
||||
self.__class__.__name__,
|
||||
dict.__repr__(self),
|
||||
self.should_save and '*' or ''
|
||||
)
|
||||
|
||||
@property
|
||||
def should_save(self):
|
||||
"""True if the session should be saved. By default this is only true
|
||||
for :attr:`modified` cookies, not :attr:`new`.
|
||||
"""
|
||||
return self.modified
|
||||
|
||||
@classmethod
|
||||
def quote(cls, value):
|
||||
"""Quote the value for the cookie. This can be any object supported
|
||||
by :attr:`serialization_method`.
|
||||
|
||||
:param value: the value to quote.
|
||||
"""
|
||||
if cls.serialization_method is not None:
|
||||
value = cls.serialization_method.dumps(value)
|
||||
if cls.quote_base64:
|
||||
value = b''.join(base64.b64encode(value).splitlines()).strip()
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def unquote(cls, value):
|
||||
"""Unquote the value for the cookie. If unquoting does not work a
|
||||
:exc:`UnquoteError` is raised.
|
||||
|
||||
:param value: the value to unquote.
|
||||
"""
|
||||
try:
|
||||
if cls.quote_base64:
|
||||
value = base64.b64decode(value)
|
||||
if cls.serialization_method is not None:
|
||||
value = cls.serialization_method.loads(value)
|
||||
return value
|
||||
except Exception:
|
||||
# unfortunately pickle and other serialization modules can
|
||||
# cause pretty every error here. if we get one we catch it
|
||||
# and convert it into an UnquoteError
|
||||
raise UnquoteError()
|
||||
|
||||
def serialize(self, expires=None):
|
||||
"""Serialize the secure cookie into a string.
|
||||
|
||||
If expires is provided, the session will be automatically invalidated
|
||||
after expiration when you unseralize it. This provides better
|
||||
protection against session cookie theft.
|
||||
|
||||
:param expires: an optional expiration date for the cookie (a
|
||||
:class:`datetime.datetime` object)
|
||||
"""
|
||||
if self.secret_key is None:
|
||||
raise RuntimeError('no secret key defined')
|
||||
if expires:
|
||||
self['_expires'] = _date_to_unix(expires)
|
||||
result = []
|
||||
mac = hmac(self.secret_key, None, self.hash_method)
|
||||
for key, value in sorted(self.items()):
|
||||
result.append(('%s=%s' % (
|
||||
url_quote_plus(key),
|
||||
self.quote(value).decode('ascii')
|
||||
)).encode('ascii'))
|
||||
mac.update(b'|' + result[-1])
|
||||
return b'?'.join([
|
||||
base64.b64encode(mac.digest()).strip(),
|
||||
b'&'.join(result)
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def unserialize(cls, string, secret_key):
|
||||
"""Load the secure cookie from a serialized string.
|
||||
|
||||
:param string: the cookie value to unserialize.
|
||||
:param secret_key: the secret key used to serialize the cookie.
|
||||
:return: a new :class:`SecureCookie`.
|
||||
"""
|
||||
if isinstance(string, text_type):
|
||||
string = string.encode('utf-8', 'replace')
|
||||
if isinstance(secret_key, text_type):
|
||||
secret_key = secret_key.encode('utf-8', 'replace')
|
||||
try:
|
||||
base64_hash, data = string.split(b'?', 1)
|
||||
except (ValueError, IndexError):
|
||||
items = ()
|
||||
else:
|
||||
items = {}
|
||||
mac = hmac(secret_key, None, cls.hash_method)
|
||||
for item in data.split(b'&'):
|
||||
mac.update(b'|' + item)
|
||||
if b'=' not in item:
|
||||
items = None
|
||||
break
|
||||
key, value = item.split(b'=', 1)
|
||||
# try to make the key a string
|
||||
key = url_unquote_plus(key.decode('ascii'))
|
||||
try:
|
||||
key = to_native(key)
|
||||
except UnicodeError:
|
||||
pass
|
||||
items[key] = value
|
||||
|
||||
# no parsing error and the mac looks okay, we can now
|
||||
# sercurely unpickle our cookie.
|
||||
try:
|
||||
client_hash = base64.b64decode(base64_hash)
|
||||
except TypeError:
|
||||
items = client_hash = None
|
||||
if items is not None and safe_str_cmp(client_hash, mac.digest()):
|
||||
try:
|
||||
for key, value in iteritems(items):
|
||||
items[key] = cls.unquote(value)
|
||||
except UnquoteError:
|
||||
items = ()
|
||||
else:
|
||||
if '_expires' in items:
|
||||
if time() > items['_expires']:
|
||||
items = ()
|
||||
else:
|
||||
del items['_expires']
|
||||
else:
|
||||
items = ()
|
||||
return cls(items, secret_key, False)
|
||||
|
||||
@classmethod
|
||||
def load_cookie(cls, request, key='session', secret_key=None):
|
||||
"""Loads a :class:`SecureCookie` from a cookie in request. If the
|
||||
cookie is not set, a new :class:`SecureCookie` instanced is
|
||||
returned.
|
||||
|
||||
:param request: a request object that has a `cookies` attribute
|
||||
which is a dict of all cookie values.
|
||||
:param key: the name of the cookie.
|
||||
:param secret_key: the secret key used to unquote the cookie.
|
||||
Always provide the value even though it has
|
||||
no default!
|
||||
"""
|
||||
data = request.cookies.get(key)
|
||||
if not data:
|
||||
return cls(secret_key=secret_key)
|
||||
return cls.unserialize(data, secret_key)
|
||||
|
||||
def save_cookie(self, response, key='session', expires=None,
|
||||
session_expires=None, max_age=None, path='/', domain=None,
|
||||
secure=None, httponly=False, force=False):
|
||||
"""Saves the SecureCookie in a cookie on response object. All
|
||||
parameters that are not described here are forwarded directly
|
||||
to :meth:`~BaseResponse.set_cookie`.
|
||||
|
||||
:param response: a response object that has a
|
||||
:meth:`~BaseResponse.set_cookie` method.
|
||||
:param key: the name of the cookie.
|
||||
:param session_expires: the expiration date of the secure cookie
|
||||
stored information. If this is not provided
|
||||
the cookie `expires` date is used instead.
|
||||
"""
|
||||
if force or self.should_save:
|
||||
data = self.serialize(session_expires or expires)
|
||||
response.set_cookie(key, data, expires=expires, max_age=max_age,
|
||||
path=path, domain=domain, secure=secure,
|
||||
httponly=httponly)
|
||||
Binary file not shown.
352
venv/lib/python2.7/site-packages/werkzeug/contrib/sessions.py
Normal file
352
venv/lib/python2.7/site-packages/werkzeug/contrib/sessions.py
Normal file
@@ -0,0 +1,352 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""
|
||||
werkzeug.contrib.sessions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains some helper classes that help one to add session
|
||||
support to a python WSGI application. For full client-side session
|
||||
storage see :mod:`~werkzeug.contrib.securecookie` which implements a
|
||||
secure, client-side session storage.
|
||||
|
||||
|
||||
Application Integration
|
||||
=======================
|
||||
|
||||
::
|
||||
|
||||
from werkzeug.contrib.sessions import SessionMiddleware, \
|
||||
FilesystemSessionStore
|
||||
|
||||
app = SessionMiddleware(app, FilesystemSessionStore())
|
||||
|
||||
The current session will then appear in the WSGI environment as
|
||||
`werkzeug.session`. However it's recommended to not use the middleware
|
||||
but the stores directly in the application. However for very simple
|
||||
scripts a middleware for sessions could be sufficient.
|
||||
|
||||
This module does not implement methods or ways to check if a session is
|
||||
expired. That should be done by a cronjob and storage specific. For
|
||||
example to prune unused filesystem sessions one could check the modified
|
||||
time of the files. If sessions are stored in the database the new()
|
||||
method should add an expiration timestamp for the session.
|
||||
|
||||
For better flexibility it's recommended to not use the middleware but the
|
||||
store and session object directly in the application dispatching::
|
||||
|
||||
session_store = FilesystemSessionStore()
|
||||
|
||||
def application(environ, start_response):
|
||||
request = Request(environ)
|
||||
sid = request.cookies.get('cookie_name')
|
||||
if sid is None:
|
||||
request.session = session_store.new()
|
||||
else:
|
||||
request.session = session_store.get(sid)
|
||||
response = get_the_response_object(request)
|
||||
if request.session.should_save:
|
||||
session_store.save(request.session)
|
||||
response.set_cookie('cookie_name', request.session.sid)
|
||||
return response(environ, start_response)
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
import tempfile
|
||||
from os import path
|
||||
from time import time
|
||||
from random import random
|
||||
from hashlib import sha1
|
||||
from pickle import dump, load, HIGHEST_PROTOCOL
|
||||
|
||||
from werkzeug.datastructures import CallbackDict
|
||||
from werkzeug.utils import dump_cookie, parse_cookie
|
||||
from werkzeug.wsgi import ClosingIterator
|
||||
from werkzeug.posixemulation import rename
|
||||
from werkzeug._compat import PY2, text_type
|
||||
from werkzeug.filesystem import get_filesystem_encoding
|
||||
|
||||
|
||||
_sha1_re = re.compile(r'^[a-f0-9]{40}$')
|
||||
|
||||
|
||||
def _urandom():
|
||||
if hasattr(os, 'urandom'):
|
||||
return os.urandom(30)
|
||||
return text_type(random()).encode('ascii')
|
||||
|
||||
|
||||
def generate_key(salt=None):
|
||||
if salt is None:
|
||||
salt = repr(salt).encode('ascii')
|
||||
return sha1(b''.join([
|
||||
salt,
|
||||
str(time()).encode('ascii'),
|
||||
_urandom()
|
||||
])).hexdigest()
|
||||
|
||||
|
||||
class ModificationTrackingDict(CallbackDict):
|
||||
__slots__ = ('modified',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
def on_update(self):
|
||||
self.modified = True
|
||||
self.modified = False
|
||||
CallbackDict.__init__(self, on_update=on_update)
|
||||
dict.update(self, *args, **kwargs)
|
||||
|
||||
def copy(self):
|
||||
"""Create a flat copy of the dict."""
|
||||
missing = object()
|
||||
result = object.__new__(self.__class__)
|
||||
for name in self.__slots__:
|
||||
val = getattr(self, name, missing)
|
||||
if val is not missing:
|
||||
setattr(result, name, val)
|
||||
return result
|
||||
|
||||
def __copy__(self):
|
||||
return self.copy()
|
||||
|
||||
|
||||
class Session(ModificationTrackingDict):
|
||||
|
||||
"""Subclass of a dict that keeps track of direct object changes. Changes
|
||||
in mutable structures are not tracked, for those you have to set
|
||||
`modified` to `True` by hand.
|
||||
"""
|
||||
__slots__ = ModificationTrackingDict.__slots__ + ('sid', 'new')
|
||||
|
||||
def __init__(self, data, sid, new=False):
|
||||
ModificationTrackingDict.__init__(self, data)
|
||||
self.sid = sid
|
||||
self.new = new
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s%s>' % (
|
||||
self.__class__.__name__,
|
||||
dict.__repr__(self),
|
||||
self.should_save and '*' or ''
|
||||
)
|
||||
|
||||
@property
|
||||
def should_save(self):
|
||||
"""True if the session should be saved.
|
||||
|
||||
.. versionchanged:: 0.6
|
||||
By default the session is now only saved if the session is
|
||||
modified, not if it is new like it was before.
|
||||
"""
|
||||
return self.modified
|
||||
|
||||
|
||||
class SessionStore(object):
|
||||
|
||||
"""Baseclass for all session stores. The Werkzeug contrib module does not
|
||||
implement any useful stores besides the filesystem store, application
|
||||
developers are encouraged to create their own stores.
|
||||
|
||||
:param session_class: The session class to use. Defaults to
|
||||
:class:`Session`.
|
||||
"""
|
||||
|
||||
def __init__(self, session_class=None):
|
||||
if session_class is None:
|
||||
session_class = Session
|
||||
self.session_class = session_class
|
||||
|
||||
def is_valid_key(self, key):
|
||||
"""Check if a key has the correct format."""
|
||||
return _sha1_re.match(key) is not None
|
||||
|
||||
def generate_key(self, salt=None):
|
||||
"""Simple function that generates a new session key."""
|
||||
return generate_key(salt)
|
||||
|
||||
def new(self):
|
||||
"""Generate a new session."""
|
||||
return self.session_class({}, self.generate_key(), True)
|
||||
|
||||
def save(self, session):
|
||||
"""Save a session."""
|
||||
|
||||
def save_if_modified(self, session):
|
||||
"""Save if a session class wants an update."""
|
||||
if session.should_save:
|
||||
self.save(session)
|
||||
|
||||
def delete(self, session):
|
||||
"""Delete a session."""
|
||||
|
||||
def get(self, sid):
|
||||
"""Get a session for this sid or a new session object. This method
|
||||
has to check if the session key is valid and create a new session if
|
||||
that wasn't the case.
|
||||
"""
|
||||
return self.session_class({}, sid, True)
|
||||
|
||||
|
||||
#: used for temporary files by the filesystem session store
|
||||
_fs_transaction_suffix = '.__wz_sess'
|
||||
|
||||
|
||||
class FilesystemSessionStore(SessionStore):
|
||||
|
||||
"""Simple example session store that saves sessions on the filesystem.
|
||||
This store works best on POSIX systems and Windows Vista / Windows
|
||||
Server 2008 and newer.
|
||||
|
||||
.. versionchanged:: 0.6
|
||||
`renew_missing` was added. Previously this was considered `True`,
|
||||
now the default changed to `False` and it can be explicitly
|
||||
deactivated.
|
||||
|
||||
:param path: the path to the folder used for storing the sessions.
|
||||
If not provided the default temporary directory is used.
|
||||
:param filename_template: a string template used to give the session
|
||||
a filename. ``%s`` is replaced with the
|
||||
session id.
|
||||
:param session_class: The session class to use. Defaults to
|
||||
:class:`Session`.
|
||||
:param renew_missing: set to `True` if you want the store to
|
||||
give the user a new sid if the session was
|
||||
not yet saved.
|
||||
"""
|
||||
|
||||
def __init__(self, path=None, filename_template='werkzeug_%s.sess',
|
||||
session_class=None, renew_missing=False, mode=0o644):
|
||||
SessionStore.__init__(self, session_class)
|
||||
if path is None:
|
||||
path = tempfile.gettempdir()
|
||||
self.path = path
|
||||
if isinstance(filename_template, text_type) and PY2:
|
||||
filename_template = filename_template.encode(
|
||||
get_filesystem_encoding())
|
||||
assert not filename_template.endswith(_fs_transaction_suffix), \
|
||||
'filename templates may not end with %s' % _fs_transaction_suffix
|
||||
self.filename_template = filename_template
|
||||
self.renew_missing = renew_missing
|
||||
self.mode = mode
|
||||
|
||||
def get_session_filename(self, sid):
|
||||
# out of the box, this should be a strict ASCII subset but
|
||||
# you might reconfigure the session object to have a more
|
||||
# arbitrary string.
|
||||
if isinstance(sid, text_type) and PY2:
|
||||
sid = sid.encode(get_filesystem_encoding())
|
||||
return path.join(self.path, self.filename_template % sid)
|
||||
|
||||
def save(self, session):
|
||||
fn = self.get_session_filename(session.sid)
|
||||
fd, tmp = tempfile.mkstemp(suffix=_fs_transaction_suffix,
|
||||
dir=self.path)
|
||||
f = os.fdopen(fd, 'wb')
|
||||
try:
|
||||
dump(dict(session), f, HIGHEST_PROTOCOL)
|
||||
finally:
|
||||
f.close()
|
||||
try:
|
||||
rename(tmp, fn)
|
||||
os.chmod(fn, self.mode)
|
||||
except (IOError, OSError):
|
||||
pass
|
||||
|
||||
def delete(self, session):
|
||||
fn = self.get_session_filename(session.sid)
|
||||
try:
|
||||
os.unlink(fn)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def get(self, sid):
|
||||
if not self.is_valid_key(sid):
|
||||
return self.new()
|
||||
try:
|
||||
f = open(self.get_session_filename(sid), 'rb')
|
||||
except IOError:
|
||||
if self.renew_missing:
|
||||
return self.new()
|
||||
data = {}
|
||||
else:
|
||||
try:
|
||||
try:
|
||||
data = load(f)
|
||||
except Exception:
|
||||
data = {}
|
||||
finally:
|
||||
f.close()
|
||||
return self.session_class(data, sid, False)
|
||||
|
||||
def list(self):
|
||||
"""Lists all sessions in the store.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
before, after = self.filename_template.split('%s', 1)
|
||||
filename_re = re.compile(r'%s(.{5,})%s$' % (re.escape(before),
|
||||
re.escape(after)))
|
||||
result = []
|
||||
for filename in os.listdir(self.path):
|
||||
#: this is a session that is still being saved.
|
||||
if filename.endswith(_fs_transaction_suffix):
|
||||
continue
|
||||
match = filename_re.match(filename)
|
||||
if match is not None:
|
||||
result.append(match.group(1))
|
||||
return result
|
||||
|
||||
|
||||
class SessionMiddleware(object):
|
||||
|
||||
"""A simple middleware that puts the session object of a store provided
|
||||
into the WSGI environ. It automatically sets cookies and restores
|
||||
sessions.
|
||||
|
||||
However a middleware is not the preferred solution because it won't be as
|
||||
fast as sessions managed by the application itself and will put a key into
|
||||
the WSGI environment only relevant for the application which is against
|
||||
the concept of WSGI.
|
||||
|
||||
The cookie parameters are the same as for the :func:`~dump_cookie`
|
||||
function just prefixed with ``cookie_``. Additionally `max_age` is
|
||||
called `cookie_age` and not `cookie_max_age` because of backwards
|
||||
compatibility.
|
||||
"""
|
||||
|
||||
def __init__(self, app, store, cookie_name='session_id',
|
||||
cookie_age=None, cookie_expires=None, cookie_path='/',
|
||||
cookie_domain=None, cookie_secure=None,
|
||||
cookie_httponly=False, environ_key='werkzeug.session'):
|
||||
self.app = app
|
||||
self.store = store
|
||||
self.cookie_name = cookie_name
|
||||
self.cookie_age = cookie_age
|
||||
self.cookie_expires = cookie_expires
|
||||
self.cookie_path = cookie_path
|
||||
self.cookie_domain = cookie_domain
|
||||
self.cookie_secure = cookie_secure
|
||||
self.cookie_httponly = cookie_httponly
|
||||
self.environ_key = environ_key
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
cookie = parse_cookie(environ.get('HTTP_COOKIE', ''))
|
||||
sid = cookie.get(self.cookie_name, None)
|
||||
if sid is None:
|
||||
session = self.store.new()
|
||||
else:
|
||||
session = self.store.get(sid)
|
||||
environ[self.environ_key] = session
|
||||
|
||||
def injecting_start_response(status, headers, exc_info=None):
|
||||
if session.should_save:
|
||||
self.store.save(session)
|
||||
headers.append(('Set-Cookie', dump_cookie(self.cookie_name,
|
||||
session.sid, self.cookie_age,
|
||||
self.cookie_expires, self.cookie_path,
|
||||
self.cookie_domain, self.cookie_secure,
|
||||
self.cookie_httponly)))
|
||||
return start_response(status, headers, exc_info)
|
||||
return ClosingIterator(self.app(environ, injecting_start_response),
|
||||
lambda: self.store.save_if_modified(session))
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/sessions.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/sessions.pyc
Normal file
Binary file not shown.
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.contrib.testtools
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module implements extended wrappers for simplified testing.
|
||||
|
||||
`TestResponse`
|
||||
A response wrapper which adds various cached attributes for
|
||||
simplified assertions on various content types.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from werkzeug.utils import cached_property, import_string
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
from warnings import warn
|
||||
warn(DeprecationWarning('werkzeug.contrib.testtools is deprecated and '
|
||||
'will be removed with Werkzeug 1.0'))
|
||||
|
||||
|
||||
class ContentAccessors(object):
|
||||
|
||||
"""
|
||||
A mixin class for response objects that provides a couple of useful
|
||||
accessors for unittesting.
|
||||
"""
|
||||
|
||||
def xml(self):
|
||||
"""Get an etree if possible."""
|
||||
if 'xml' not in self.mimetype:
|
||||
raise AttributeError(
|
||||
'Not a XML response (Content-Type: %s)'
|
||||
% self.mimetype)
|
||||
for module in ['xml.etree.ElementTree', 'ElementTree',
|
||||
'elementtree.ElementTree']:
|
||||
etree = import_string(module, silent=True)
|
||||
if etree is not None:
|
||||
return etree.XML(self.body)
|
||||
raise RuntimeError('You must have ElementTree installed '
|
||||
'to use TestResponse.xml')
|
||||
xml = cached_property(xml)
|
||||
|
||||
def lxml(self):
|
||||
"""Get an lxml etree if possible."""
|
||||
if ('html' not in self.mimetype and 'xml' not in self.mimetype):
|
||||
raise AttributeError('Not an HTML/XML response')
|
||||
from lxml import etree
|
||||
try:
|
||||
from lxml.html import fromstring
|
||||
except ImportError:
|
||||
fromstring = etree.HTML
|
||||
if self.mimetype == 'text/html':
|
||||
return fromstring(self.data)
|
||||
return etree.XML(self.data)
|
||||
lxml = cached_property(lxml)
|
||||
|
||||
def json(self):
|
||||
"""Get the result of simplejson.loads if possible."""
|
||||
if 'json' not in self.mimetype:
|
||||
raise AttributeError('Not a JSON response')
|
||||
try:
|
||||
from simplejson import loads
|
||||
except ImportError:
|
||||
from json import loads
|
||||
return loads(self.data)
|
||||
json = cached_property(json)
|
||||
|
||||
|
||||
class TestResponse(Response, ContentAccessors):
|
||||
|
||||
"""Pass this to `werkzeug.test.Client` for easier unittesting."""
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/testtools.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/testtools.pyc
Normal file
Binary file not shown.
284
venv/lib/python2.7/site-packages/werkzeug/contrib/wrappers.py
Normal file
284
venv/lib/python2.7/site-packages/werkzeug/contrib/wrappers.py
Normal file
@@ -0,0 +1,284 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.contrib.wrappers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Extra wrappers or mixins contributed by the community. These wrappers can
|
||||
be mixed in into request objects to add extra functionality.
|
||||
|
||||
Example::
|
||||
|
||||
from werkzeug.wrappers import Request as RequestBase
|
||||
from werkzeug.contrib.wrappers import JSONRequestMixin
|
||||
|
||||
class Request(RequestBase, JSONRequestMixin):
|
||||
pass
|
||||
|
||||
Afterwards this request object provides the extra functionality of the
|
||||
:class:`JSONRequestMixin`.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import codecs
|
||||
try:
|
||||
from simplejson import loads
|
||||
except ImportError:
|
||||
from json import loads
|
||||
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.utils import cached_property
|
||||
from werkzeug.http import dump_options_header, parse_options_header
|
||||
from werkzeug._compat import wsgi_decoding_dance
|
||||
|
||||
|
||||
def is_known_charset(charset):
|
||||
"""Checks if the given charset is known to Python."""
|
||||
try:
|
||||
codecs.lookup(charset)
|
||||
except LookupError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class JSONRequestMixin(object):
|
||||
|
||||
"""Add json method to a request object. This will parse the input data
|
||||
through simplejson if possible.
|
||||
|
||||
:exc:`~werkzeug.exceptions.BadRequest` will be raised if the content-type
|
||||
is not json or if the data itself cannot be parsed as json.
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
def json(self):
|
||||
"""Get the result of simplejson.loads if possible."""
|
||||
if 'json' not in self.environ.get('CONTENT_TYPE', ''):
|
||||
raise BadRequest('Not a JSON request')
|
||||
try:
|
||||
return loads(self.data.decode(self.charset, self.encoding_errors))
|
||||
except Exception:
|
||||
raise BadRequest('Unable to read JSON request')
|
||||
|
||||
|
||||
class ProtobufRequestMixin(object):
|
||||
|
||||
"""Add protobuf parsing method to a request object. This will parse the
|
||||
input data through `protobuf`_ if possible.
|
||||
|
||||
:exc:`~werkzeug.exceptions.BadRequest` will be raised if the content-type
|
||||
is not protobuf or if the data itself cannot be parsed property.
|
||||
|
||||
.. _protobuf: http://code.google.com/p/protobuf/
|
||||
"""
|
||||
|
||||
#: by default the :class:`ProtobufRequestMixin` will raise a
|
||||
#: :exc:`~werkzeug.exceptions.BadRequest` if the object is not
|
||||
#: initialized. You can bypass that check by setting this
|
||||
#: attribute to `False`.
|
||||
protobuf_check_initialization = True
|
||||
|
||||
def parse_protobuf(self, proto_type):
|
||||
"""Parse the data into an instance of proto_type."""
|
||||
if 'protobuf' not in self.environ.get('CONTENT_TYPE', ''):
|
||||
raise BadRequest('Not a Protobuf request')
|
||||
|
||||
obj = proto_type()
|
||||
try:
|
||||
obj.ParseFromString(self.data)
|
||||
except Exception:
|
||||
raise BadRequest("Unable to parse Protobuf request")
|
||||
|
||||
# Fail if not all required fields are set
|
||||
if self.protobuf_check_initialization and not obj.IsInitialized():
|
||||
raise BadRequest("Partial Protobuf request")
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
class RoutingArgsRequestMixin(object):
|
||||
|
||||
"""This request mixin adds support for the wsgiorg routing args
|
||||
`specification`_.
|
||||
|
||||
.. _specification: https://wsgi.readthedocs.io/en/latest/specifications/routing_args.html
|
||||
"""
|
||||
|
||||
def _get_routing_args(self):
|
||||
return self.environ.get('wsgiorg.routing_args', (()))[0]
|
||||
|
||||
def _set_routing_args(self, value):
|
||||
if self.shallow:
|
||||
raise RuntimeError('A shallow request tried to modify the WSGI '
|
||||
'environment. If you really want to do that, '
|
||||
'set `shallow` to False.')
|
||||
self.environ['wsgiorg.routing_args'] = (value, self.routing_vars)
|
||||
|
||||
routing_args = property(_get_routing_args, _set_routing_args, doc='''
|
||||
The positional URL arguments as `tuple`.''')
|
||||
del _get_routing_args, _set_routing_args
|
||||
|
||||
def _get_routing_vars(self):
|
||||
rv = self.environ.get('wsgiorg.routing_args')
|
||||
if rv is not None:
|
||||
return rv[1]
|
||||
rv = {}
|
||||
if not self.shallow:
|
||||
self.routing_vars = rv
|
||||
return rv
|
||||
|
||||
def _set_routing_vars(self, value):
|
||||
if self.shallow:
|
||||
raise RuntimeError('A shallow request tried to modify the WSGI '
|
||||
'environment. If you really want to do that, '
|
||||
'set `shallow` to False.')
|
||||
self.environ['wsgiorg.routing_args'] = (self.routing_args, value)
|
||||
|
||||
routing_vars = property(_get_routing_vars, _set_routing_vars, doc='''
|
||||
The keyword URL arguments as `dict`.''')
|
||||
del _get_routing_vars, _set_routing_vars
|
||||
|
||||
|
||||
class ReverseSlashBehaviorRequestMixin(object):
|
||||
|
||||
"""This mixin reverses the trailing slash behavior of :attr:`script_root`
|
||||
and :attr:`path`. This makes it possible to use :func:`~urlparse.urljoin`
|
||||
directly on the paths.
|
||||
|
||||
Because it changes the behavior or :class:`Request` this class has to be
|
||||
mixed in *before* the actual request class::
|
||||
|
||||
class MyRequest(ReverseSlashBehaviorRequestMixin, Request):
|
||||
pass
|
||||
|
||||
This example shows the differences (for an application mounted on
|
||||
`/application` and the request going to `/application/foo/bar`):
|
||||
|
||||
+---------------+-------------------+---------------------+
|
||||
| | normal behavior | reverse behavior |
|
||||
+===============+===================+=====================+
|
||||
| `script_root` | ``/application`` | ``/application/`` |
|
||||
+---------------+-------------------+---------------------+
|
||||
| `path` | ``/foo/bar`` | ``foo/bar`` |
|
||||
+---------------+-------------------+---------------------+
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
def path(self):
|
||||
"""Requested path as unicode. This works a bit like the regular path
|
||||
info in the WSGI environment but will not include a leading slash.
|
||||
"""
|
||||
path = wsgi_decoding_dance(self.environ.get('PATH_INFO') or '',
|
||||
self.charset, self.encoding_errors)
|
||||
return path.lstrip('/')
|
||||
|
||||
@cached_property
|
||||
def script_root(self):
|
||||
"""The root path of the script includling a trailing slash."""
|
||||
path = wsgi_decoding_dance(self.environ.get('SCRIPT_NAME') or '',
|
||||
self.charset, self.encoding_errors)
|
||||
return path.rstrip('/') + '/'
|
||||
|
||||
|
||||
class DynamicCharsetRequestMixin(object):
|
||||
|
||||
""""If this mixin is mixed into a request class it will provide
|
||||
a dynamic `charset` attribute. This means that if the charset is
|
||||
transmitted in the content type headers it's used from there.
|
||||
|
||||
Because it changes the behavior or :class:`Request` this class has
|
||||
to be mixed in *before* the actual request class::
|
||||
|
||||
class MyRequest(DynamicCharsetRequestMixin, Request):
|
||||
pass
|
||||
|
||||
By default the request object assumes that the URL charset is the
|
||||
same as the data charset. If the charset varies on each request
|
||||
based on the transmitted data it's not a good idea to let the URLs
|
||||
change based on that. Most browsers assume either utf-8 or latin1
|
||||
for the URLs if they have troubles figuring out. It's strongly
|
||||
recommended to set the URL charset to utf-8::
|
||||
|
||||
class MyRequest(DynamicCharsetRequestMixin, Request):
|
||||
url_charset = 'utf-8'
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
|
||||
#: the default charset that is assumed if the content type header
|
||||
#: is missing or does not contain a charset parameter. The default
|
||||
#: is latin1 which is what HTTP specifies as default charset.
|
||||
#: You may however want to set this to utf-8 to better support
|
||||
#: browsers that do not transmit a charset for incoming data.
|
||||
default_charset = 'latin1'
|
||||
|
||||
def unknown_charset(self, charset):
|
||||
"""Called if a charset was provided but is not supported by
|
||||
the Python codecs module. By default latin1 is assumed then
|
||||
to not lose any information, you may override this method to
|
||||
change the behavior.
|
||||
|
||||
:param charset: the charset that was not found.
|
||||
:return: the replacement charset.
|
||||
"""
|
||||
return 'latin1'
|
||||
|
||||
@cached_property
|
||||
def charset(self):
|
||||
"""The charset from the content type."""
|
||||
header = self.environ.get('CONTENT_TYPE')
|
||||
if header:
|
||||
ct, options = parse_options_header(header)
|
||||
charset = options.get('charset')
|
||||
if charset:
|
||||
if is_known_charset(charset):
|
||||
return charset
|
||||
return self.unknown_charset(charset)
|
||||
return self.default_charset
|
||||
|
||||
|
||||
class DynamicCharsetResponseMixin(object):
|
||||
|
||||
"""If this mixin is mixed into a response class it will provide
|
||||
a dynamic `charset` attribute. This means that if the charset is
|
||||
looked up and stored in the `Content-Type` header and updates
|
||||
itself automatically. This also means a small performance hit but
|
||||
can be useful if you're working with different charsets on
|
||||
responses.
|
||||
|
||||
Because the charset attribute is no a property at class-level, the
|
||||
default value is stored in `default_charset`.
|
||||
|
||||
Because it changes the behavior or :class:`Response` this class has
|
||||
to be mixed in *before* the actual response class::
|
||||
|
||||
class MyResponse(DynamicCharsetResponseMixin, Response):
|
||||
pass
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
|
||||
#: the default charset.
|
||||
default_charset = 'utf-8'
|
||||
|
||||
def _get_charset(self):
|
||||
header = self.headers.get('content-type')
|
||||
if header:
|
||||
charset = parse_options_header(header)[1].get('charset')
|
||||
if charset:
|
||||
return charset
|
||||
return self.default_charset
|
||||
|
||||
def _set_charset(self, charset):
|
||||
header = self.headers.get('content-type')
|
||||
ct, options = parse_options_header(header)
|
||||
if not ct:
|
||||
raise TypeError('Cannot set charset if Content-Type '
|
||||
'header is missing.')
|
||||
options['charset'] = charset
|
||||
self.headers['Content-Type'] = dump_options_header(ct, options)
|
||||
|
||||
charset = property(_get_charset, _set_charset, doc="""
|
||||
The charset for the response. It's stored inside the
|
||||
Content-Type header as a parameter.""")
|
||||
del _get_charset, _set_charset
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/wrappers.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/contrib/wrappers.pyc
Normal file
Binary file not shown.
2740
venv/lib/python2.7/site-packages/werkzeug/datastructures.py
Normal file
2740
venv/lib/python2.7/site-packages/werkzeug/datastructures.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
venv/lib/python2.7/site-packages/werkzeug/datastructures.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/datastructures.pyc
Normal file
Binary file not shown.
466
venv/lib/python2.7/site-packages/werkzeug/debug/__init__.py
Normal file
466
venv/lib/python2.7/site-packages/werkzeug/debug/__init__.py
Normal file
@@ -0,0 +1,466 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.debug
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
WSGI application traceback debugger.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
import json
|
||||
import time
|
||||
import getpass
|
||||
import hashlib
|
||||
import mimetypes
|
||||
from itertools import chain
|
||||
from os.path import join, dirname, basename, isfile
|
||||
from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response
|
||||
from werkzeug.http import parse_cookie
|
||||
from werkzeug.debug.tbtools import get_current_traceback, render_console_html
|
||||
from werkzeug.debug.console import Console
|
||||
from werkzeug.security import gen_salt
|
||||
from werkzeug._internal import _log
|
||||
from werkzeug._compat import text_type
|
||||
|
||||
|
||||
# DEPRECATED
|
||||
#: import this here because it once was documented as being available
|
||||
#: from this module. In case there are users left ...
|
||||
from werkzeug.debug.repr import debug_repr # noqa
|
||||
|
||||
|
||||
# A week
|
||||
PIN_TIME = 60 * 60 * 24 * 7
|
||||
|
||||
|
||||
def hash_pin(pin):
|
||||
if isinstance(pin, text_type):
|
||||
pin = pin.encode('utf-8', 'replace')
|
||||
return hashlib.md5(pin + b'shittysalt').hexdigest()[:12]
|
||||
|
||||
|
||||
_machine_id = None
|
||||
|
||||
|
||||
def get_machine_id():
|
||||
global _machine_id
|
||||
rv = _machine_id
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
def _generate():
|
||||
# Potential sources of secret information on linux. The machine-id
|
||||
# is stable across boots, the boot id is not
|
||||
for filename in '/etc/machine-id', '/proc/sys/kernel/random/boot_id':
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
return f.readline().strip()
|
||||
except IOError:
|
||||
continue
|
||||
|
||||
# On OS X we can use the computer's serial number assuming that
|
||||
# ioreg exists and can spit out that information.
|
||||
try:
|
||||
# Also catch import errors: subprocess may not be available, e.g.
|
||||
# Google App Engine
|
||||
# See https://github.com/pallets/werkzeug/issues/925
|
||||
from subprocess import Popen, PIPE
|
||||
dump = Popen(['ioreg', '-c', 'IOPlatformExpertDevice', '-d', '2'],
|
||||
stdout=PIPE).communicate()[0]
|
||||
match = re.search(b'"serial-number" = <([^>]+)', dump)
|
||||
if match is not None:
|
||||
return match.group(1)
|
||||
except (OSError, ImportError):
|
||||
pass
|
||||
|
||||
# On Windows we can use winreg to get the machine guid
|
||||
wr = None
|
||||
try:
|
||||
import winreg as wr
|
||||
except ImportError:
|
||||
try:
|
||||
import _winreg as wr
|
||||
except ImportError:
|
||||
pass
|
||||
if wr is not None:
|
||||
try:
|
||||
with wr.OpenKey(wr.HKEY_LOCAL_MACHINE,
|
||||
'SOFTWARE\\Microsoft\\Cryptography', 0,
|
||||
wr.KEY_READ | wr.KEY_WOW64_64KEY) as rk:
|
||||
return wr.QueryValueEx(rk, 'MachineGuid')[0]
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
_machine_id = rv = _generate()
|
||||
return rv
|
||||
|
||||
|
||||
class _ConsoleFrame(object):
|
||||
|
||||
"""Helper class so that we can reuse the frame console code for the
|
||||
standalone console.
|
||||
"""
|
||||
|
||||
def __init__(self, namespace):
|
||||
self.console = Console(namespace)
|
||||
self.id = 0
|
||||
|
||||
|
||||
def get_pin_and_cookie_name(app):
|
||||
"""Given an application object this returns a semi-stable 9 digit pin
|
||||
code and a random key. The hope is that this is stable between
|
||||
restarts to not make debugging particularly frustrating. If the pin
|
||||
was forcefully disabled this returns `None`.
|
||||
|
||||
Second item in the resulting tuple is the cookie name for remembering.
|
||||
"""
|
||||
pin = os.environ.get('WERKZEUG_DEBUG_PIN')
|
||||
rv = None
|
||||
num = None
|
||||
|
||||
# Pin was explicitly disabled
|
||||
if pin == 'off':
|
||||
return None, None
|
||||
|
||||
# Pin was provided explicitly
|
||||
if pin is not None and pin.replace('-', '').isdigit():
|
||||
# If there are separators in the pin, return it directly
|
||||
if '-' in pin:
|
||||
rv = pin
|
||||
else:
|
||||
num = pin
|
||||
|
||||
modname = getattr(app, '__module__',
|
||||
getattr(app.__class__, '__module__'))
|
||||
|
||||
try:
|
||||
# `getpass.getuser()` imports the `pwd` module,
|
||||
# which does not exist in the Google App Engine sandbox.
|
||||
username = getpass.getuser()
|
||||
except ImportError:
|
||||
username = None
|
||||
|
||||
mod = sys.modules.get(modname)
|
||||
|
||||
# This information only exists to make the cookie unique on the
|
||||
# computer, not as a security feature.
|
||||
probably_public_bits = [
|
||||
username,
|
||||
modname,
|
||||
getattr(app, '__name__', getattr(app.__class__, '__name__')),
|
||||
getattr(mod, '__file__', None),
|
||||
]
|
||||
|
||||
# This information is here to make it harder for an attacker to
|
||||
# guess the cookie name. They are unlikely to be contained anywhere
|
||||
# within the unauthenticated debug page.
|
||||
private_bits = [
|
||||
str(uuid.getnode()),
|
||||
get_machine_id(),
|
||||
]
|
||||
|
||||
h = hashlib.md5()
|
||||
for bit in chain(probably_public_bits, private_bits):
|
||||
if not bit:
|
||||
continue
|
||||
if isinstance(bit, text_type):
|
||||
bit = bit.encode('utf-8')
|
||||
h.update(bit)
|
||||
h.update(b'cookiesalt')
|
||||
|
||||
cookie_name = '__wzd' + h.hexdigest()[:20]
|
||||
|
||||
# If we need to generate a pin we salt it a bit more so that we don't
|
||||
# end up with the same value and generate out 9 digits
|
||||
if num is None:
|
||||
h.update(b'pinsalt')
|
||||
num = ('%09d' % int(h.hexdigest(), 16))[:9]
|
||||
|
||||
# Format the pincode in groups of digits for easier remembering if
|
||||
# we don't have a result yet.
|
||||
if rv is None:
|
||||
for group_size in 5, 4, 3:
|
||||
if len(num) % group_size == 0:
|
||||
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
|
||||
for x in range(0, len(num), group_size))
|
||||
break
|
||||
else:
|
||||
rv = num
|
||||
|
||||
return rv, cookie_name
|
||||
|
||||
|
||||
class DebuggedApplication(object):
|
||||
"""Enables debugging support for a given application::
|
||||
|
||||
from werkzeug.debug import DebuggedApplication
|
||||
from myapp import app
|
||||
app = DebuggedApplication(app, evalex=True)
|
||||
|
||||
The `evalex` keyword argument allows evaluating expressions in a
|
||||
traceback's frame context.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
The `lodgeit_url` parameter was deprecated.
|
||||
|
||||
:param app: the WSGI application to run debugged.
|
||||
:param evalex: enable exception evaluation feature (interactive
|
||||
debugging). This requires a non-forking server.
|
||||
:param request_key: The key that points to the request object in ths
|
||||
environment. This parameter is ignored in current
|
||||
versions.
|
||||
:param console_path: the URL for a general purpose console.
|
||||
:param console_init_func: the function that is executed before starting
|
||||
the general purpose console. The return value
|
||||
is used as initial namespace.
|
||||
:param show_hidden_frames: by default hidden traceback frames are skipped.
|
||||
You can show them by setting this parameter
|
||||
to `True`.
|
||||
:param pin_security: can be used to disable the pin based security system.
|
||||
:param pin_logging: enables the logging of the pin system.
|
||||
"""
|
||||
|
||||
def __init__(self, app, evalex=False, request_key='werkzeug.request',
|
||||
console_path='/console', console_init_func=None,
|
||||
show_hidden_frames=False, lodgeit_url=None,
|
||||
pin_security=True, pin_logging=True):
|
||||
if lodgeit_url is not None:
|
||||
from warnings import warn
|
||||
warn(DeprecationWarning('Werkzeug now pastes into gists.'))
|
||||
if not console_init_func:
|
||||
console_init_func = None
|
||||
self.app = app
|
||||
self.evalex = evalex
|
||||
self.frames = {}
|
||||
self.tracebacks = {}
|
||||
self.request_key = request_key
|
||||
self.console_path = console_path
|
||||
self.console_init_func = console_init_func
|
||||
self.show_hidden_frames = show_hidden_frames
|
||||
self.secret = gen_salt(20)
|
||||
self._failed_pin_auth = 0
|
||||
|
||||
self.pin_logging = pin_logging
|
||||
if pin_security:
|
||||
# Print out the pin for the debugger on standard out.
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true' and \
|
||||
pin_logging:
|
||||
_log('warning', ' * Debugger is active!')
|
||||
if self.pin is None:
|
||||
_log('warning', ' * Debugger PIN disabled. '
|
||||
'DEBUGGER UNSECURED!')
|
||||
else:
|
||||
_log('info', ' * Debugger PIN: %s' % self.pin)
|
||||
else:
|
||||
self.pin = None
|
||||
|
||||
def _get_pin(self):
|
||||
if not hasattr(self, '_pin'):
|
||||
self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app)
|
||||
return self._pin
|
||||
|
||||
def _set_pin(self, value):
|
||||
self._pin = value
|
||||
|
||||
pin = property(_get_pin, _set_pin)
|
||||
del _get_pin, _set_pin
|
||||
|
||||
@property
|
||||
def pin_cookie_name(self):
|
||||
"""The name of the pin cookie."""
|
||||
if not hasattr(self, '_pin_cookie'):
|
||||
self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app)
|
||||
return self._pin_cookie
|
||||
|
||||
def debug_application(self, environ, start_response):
|
||||
"""Run the application and conserve the traceback frames."""
|
||||
app_iter = None
|
||||
try:
|
||||
app_iter = self.app(environ, start_response)
|
||||
for item in app_iter:
|
||||
yield item
|
||||
if hasattr(app_iter, 'close'):
|
||||
app_iter.close()
|
||||
except Exception:
|
||||
if hasattr(app_iter, 'close'):
|
||||
app_iter.close()
|
||||
traceback = get_current_traceback(
|
||||
skip=1, show_hidden_frames=self.show_hidden_frames,
|
||||
ignore_system_exceptions=True)
|
||||
for frame in traceback.frames:
|
||||
self.frames[frame.id] = frame
|
||||
self.tracebacks[traceback.id] = traceback
|
||||
|
||||
try:
|
||||
start_response('500 INTERNAL SERVER ERROR', [
|
||||
('Content-Type', 'text/html; charset=utf-8'),
|
||||
# Disable Chrome's XSS protection, the debug
|
||||
# output can cause false-positives.
|
||||
('X-XSS-Protection', '0'),
|
||||
])
|
||||
except Exception:
|
||||
# if we end up here there has been output but an error
|
||||
# occurred. in that situation we can do nothing fancy any
|
||||
# more, better log something into the error log and fall
|
||||
# back gracefully.
|
||||
environ['wsgi.errors'].write(
|
||||
'Debugging middleware caught exception in streamed '
|
||||
'response at a point where response headers were already '
|
||||
'sent.\n')
|
||||
else:
|
||||
is_trusted = bool(self.check_pin_trust(environ))
|
||||
yield traceback.render_full(evalex=self.evalex,
|
||||
evalex_trusted=is_trusted,
|
||||
secret=self.secret) \
|
||||
.encode('utf-8', 'replace')
|
||||
|
||||
traceback.log(environ['wsgi.errors'])
|
||||
|
||||
def execute_command(self, request, command, frame):
|
||||
"""Execute a command in a console."""
|
||||
return Response(frame.console.eval(command), mimetype='text/html')
|
||||
|
||||
def display_console(self, request):
|
||||
"""Display a standalone shell."""
|
||||
if 0 not in self.frames:
|
||||
if self.console_init_func is None:
|
||||
ns = {}
|
||||
else:
|
||||
ns = dict(self.console_init_func())
|
||||
ns.setdefault('app', self.app)
|
||||
self.frames[0] = _ConsoleFrame(ns)
|
||||
is_trusted = bool(self.check_pin_trust(request.environ))
|
||||
return Response(render_console_html(secret=self.secret,
|
||||
evalex_trusted=is_trusted),
|
||||
mimetype='text/html')
|
||||
|
||||
def paste_traceback(self, request, traceback):
|
||||
"""Paste the traceback and return a JSON response."""
|
||||
rv = traceback.paste()
|
||||
return Response(json.dumps(rv), mimetype='application/json')
|
||||
|
||||
def get_resource(self, request, filename):
|
||||
"""Return a static resource from the shared folder."""
|
||||
filename = join(dirname(__file__), 'shared', basename(filename))
|
||||
if isfile(filename):
|
||||
mimetype = mimetypes.guess_type(filename)[0] \
|
||||
or 'application/octet-stream'
|
||||
f = open(filename, 'rb')
|
||||
try:
|
||||
return Response(f.read(), mimetype=mimetype)
|
||||
finally:
|
||||
f.close()
|
||||
return Response('Not Found', status=404)
|
||||
|
||||
def check_pin_trust(self, environ):
|
||||
"""Checks if the request passed the pin test. This returns `True` if the
|
||||
request is trusted on a pin/cookie basis and returns `False` if not.
|
||||
Additionally if the cookie's stored pin hash is wrong it will return
|
||||
`None` so that appropriate action can be taken.
|
||||
"""
|
||||
if self.pin is None:
|
||||
return True
|
||||
val = parse_cookie(environ).get(self.pin_cookie_name)
|
||||
if not val or '|' not in val:
|
||||
return False
|
||||
ts, pin_hash = val.split('|', 1)
|
||||
if not ts.isdigit():
|
||||
return False
|
||||
if pin_hash != hash_pin(self.pin):
|
||||
return None
|
||||
return (time.time() - PIN_TIME) < int(ts)
|
||||
|
||||
def _fail_pin_auth(self):
|
||||
time.sleep(self._failed_pin_auth > 5 and 5.0 or 0.5)
|
||||
self._failed_pin_auth += 1
|
||||
|
||||
def pin_auth(self, request):
|
||||
"""Authenticates with the pin."""
|
||||
exhausted = False
|
||||
auth = False
|
||||
trust = self.check_pin_trust(request.environ)
|
||||
|
||||
# If the trust return value is `None` it means that the cookie is
|
||||
# set but the stored pin hash value is bad. This means that the
|
||||
# pin was changed. In this case we count a bad auth and unset the
|
||||
# cookie. This way it becomes harder to guess the cookie name
|
||||
# instead of the pin as we still count up failures.
|
||||
bad_cookie = False
|
||||
if trust is None:
|
||||
self._fail_pin_auth()
|
||||
bad_cookie = True
|
||||
|
||||
# If we're trusted, we're authenticated.
|
||||
elif trust:
|
||||
auth = True
|
||||
|
||||
# If we failed too many times, then we're locked out.
|
||||
elif self._failed_pin_auth > 10:
|
||||
exhausted = True
|
||||
|
||||
# Otherwise go through pin based authentication
|
||||
else:
|
||||
entered_pin = request.args.get('pin')
|
||||
if entered_pin.strip().replace('-', '') == \
|
||||
self.pin.replace('-', ''):
|
||||
self._failed_pin_auth = 0
|
||||
auth = True
|
||||
else:
|
||||
self._fail_pin_auth()
|
||||
|
||||
rv = Response(json.dumps({
|
||||
'auth': auth,
|
||||
'exhausted': exhausted,
|
||||
}), mimetype='application/json')
|
||||
if auth:
|
||||
rv.set_cookie(self.pin_cookie_name, '%s|%s' % (
|
||||
int(time.time()),
|
||||
hash_pin(self.pin)
|
||||
), httponly=True)
|
||||
elif bad_cookie:
|
||||
rv.delete_cookie(self.pin_cookie_name)
|
||||
return rv
|
||||
|
||||
def log_pin_request(self):
|
||||
"""Log the pin if needed."""
|
||||
if self.pin_logging and self.pin is not None:
|
||||
_log('info', ' * To enable the debugger you need to '
|
||||
'enter the security pin:')
|
||||
_log('info', ' * Debugger pin code: %s' % self.pin)
|
||||
return Response('')
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""Dispatch the requests."""
|
||||
# important: don't ever access a function here that reads the incoming
|
||||
# form data! Otherwise the application won't have access to that data
|
||||
# any more!
|
||||
request = Request(environ)
|
||||
response = self.debug_application
|
||||
if request.args.get('__debugger__') == 'yes':
|
||||
cmd = request.args.get('cmd')
|
||||
arg = request.args.get('f')
|
||||
secret = request.args.get('s')
|
||||
traceback = self.tracebacks.get(request.args.get('tb', type=int))
|
||||
frame = self.frames.get(request.args.get('frm', type=int))
|
||||
if cmd == 'resource' and arg:
|
||||
response = self.get_resource(request, arg)
|
||||
elif cmd == 'paste' and traceback is not None and \
|
||||
secret == self.secret:
|
||||
response = self.paste_traceback(request, traceback)
|
||||
elif cmd == 'pinauth' and secret == self.secret:
|
||||
response = self.pin_auth(request)
|
||||
elif cmd == 'printpin' and secret == self.secret:
|
||||
response = self.log_pin_request()
|
||||
elif self.evalex and cmd is not None and frame is not None \
|
||||
and self.secret == secret and \
|
||||
self.check_pin_trust(environ):
|
||||
response = self.execute_command(request, cmd, frame)
|
||||
elif self.evalex and self.console_path is not None and \
|
||||
request.path == self.console_path:
|
||||
response = self.display_console(request)
|
||||
return response(environ, start_response)
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/__init__.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/__init__.pyc
Normal file
Binary file not shown.
215
venv/lib/python2.7/site-packages/werkzeug/debug/console.py
Normal file
215
venv/lib/python2.7/site-packages/werkzeug/debug/console.py
Normal file
@@ -0,0 +1,215 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.debug.console
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Interactive console support.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD.
|
||||
"""
|
||||
import sys
|
||||
import code
|
||||
from types import CodeType
|
||||
|
||||
from werkzeug.utils import escape
|
||||
from werkzeug.local import Local
|
||||
from werkzeug.debug.repr import debug_repr, dump, helper
|
||||
|
||||
|
||||
_local = Local()
|
||||
|
||||
|
||||
class HTMLStringO(object):
|
||||
|
||||
"""A StringO version that HTML escapes on write."""
|
||||
|
||||
def __init__(self):
|
||||
self._buffer = []
|
||||
|
||||
def isatty(self):
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def seek(self, n, mode=0):
|
||||
pass
|
||||
|
||||
def readline(self):
|
||||
if len(self._buffer) == 0:
|
||||
return ''
|
||||
ret = self._buffer[0]
|
||||
del self._buffer[0]
|
||||
return ret
|
||||
|
||||
def reset(self):
|
||||
val = ''.join(self._buffer)
|
||||
del self._buffer[:]
|
||||
return val
|
||||
|
||||
def _write(self, x):
|
||||
if isinstance(x, bytes):
|
||||
x = x.decode('utf-8', 'replace')
|
||||
self._buffer.append(x)
|
||||
|
||||
def write(self, x):
|
||||
self._write(escape(x))
|
||||
|
||||
def writelines(self, x):
|
||||
self._write(escape(''.join(x)))
|
||||
|
||||
|
||||
class ThreadedStream(object):
|
||||
|
||||
"""Thread-local wrapper for sys.stdout for the interactive console."""
|
||||
|
||||
def push():
|
||||
if not isinstance(sys.stdout, ThreadedStream):
|
||||
sys.stdout = ThreadedStream()
|
||||
_local.stream = HTMLStringO()
|
||||
push = staticmethod(push)
|
||||
|
||||
def fetch():
|
||||
try:
|
||||
stream = _local.stream
|
||||
except AttributeError:
|
||||
return ''
|
||||
return stream.reset()
|
||||
fetch = staticmethod(fetch)
|
||||
|
||||
def displayhook(obj):
|
||||
try:
|
||||
stream = _local.stream
|
||||
except AttributeError:
|
||||
return _displayhook(obj)
|
||||
# stream._write bypasses escaping as debug_repr is
|
||||
# already generating HTML for us.
|
||||
if obj is not None:
|
||||
_local._current_ipy.locals['_'] = obj
|
||||
stream._write(debug_repr(obj))
|
||||
displayhook = staticmethod(displayhook)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
raise AttributeError('read only attribute %s' % name)
|
||||
|
||||
def __dir__(self):
|
||||
return dir(sys.__stdout__)
|
||||
|
||||
def __getattribute__(self, name):
|
||||
if name == '__members__':
|
||||
return dir(sys.__stdout__)
|
||||
try:
|
||||
stream = _local.stream
|
||||
except AttributeError:
|
||||
stream = sys.__stdout__
|
||||
return getattr(stream, name)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(sys.__stdout__)
|
||||
|
||||
|
||||
# add the threaded stream as display hook
|
||||
_displayhook = sys.displayhook
|
||||
sys.displayhook = ThreadedStream.displayhook
|
||||
|
||||
|
||||
class _ConsoleLoader(object):
|
||||
|
||||
def __init__(self):
|
||||
self._storage = {}
|
||||
|
||||
def register(self, code, source):
|
||||
self._storage[id(code)] = source
|
||||
# register code objects of wrapped functions too.
|
||||
for var in code.co_consts:
|
||||
if isinstance(var, CodeType):
|
||||
self._storage[id(var)] = source
|
||||
|
||||
def get_source_by_code(self, code):
|
||||
try:
|
||||
return self._storage[id(code)]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def _wrap_compiler(console):
|
||||
compile = console.compile
|
||||
|
||||
def func(source, filename, symbol):
|
||||
code = compile(source, filename, symbol)
|
||||
console.loader.register(code, source)
|
||||
return code
|
||||
console.compile = func
|
||||
|
||||
|
||||
class _InteractiveConsole(code.InteractiveInterpreter):
|
||||
|
||||
def __init__(self, globals, locals):
|
||||
code.InteractiveInterpreter.__init__(self, locals)
|
||||
self.globals = dict(globals)
|
||||
self.globals['dump'] = dump
|
||||
self.globals['help'] = helper
|
||||
self.globals['__loader__'] = self.loader = _ConsoleLoader()
|
||||
self.more = False
|
||||
self.buffer = []
|
||||
_wrap_compiler(self)
|
||||
|
||||
def runsource(self, source):
|
||||
source = source.rstrip() + '\n'
|
||||
ThreadedStream.push()
|
||||
prompt = self.more and '... ' or '>>> '
|
||||
try:
|
||||
source_to_eval = ''.join(self.buffer + [source])
|
||||
if code.InteractiveInterpreter.runsource(self,
|
||||
source_to_eval, '<debugger>', 'single'):
|
||||
self.more = True
|
||||
self.buffer.append(source)
|
||||
else:
|
||||
self.more = False
|
||||
del self.buffer[:]
|
||||
finally:
|
||||
output = ThreadedStream.fetch()
|
||||
return prompt + escape(source) + output
|
||||
|
||||
def runcode(self, code):
|
||||
try:
|
||||
eval(code, self.globals, self.locals)
|
||||
except Exception:
|
||||
self.showtraceback()
|
||||
|
||||
def showtraceback(self):
|
||||
from werkzeug.debug.tbtools import get_current_traceback
|
||||
tb = get_current_traceback(skip=1)
|
||||
sys.stdout._write(tb.render_summary())
|
||||
|
||||
def showsyntaxerror(self, filename=None):
|
||||
from werkzeug.debug.tbtools import get_current_traceback
|
||||
tb = get_current_traceback(skip=4)
|
||||
sys.stdout._write(tb.render_summary())
|
||||
|
||||
def write(self, data):
|
||||
sys.stdout.write(data)
|
||||
|
||||
|
||||
class Console(object):
|
||||
|
||||
"""An interactive console."""
|
||||
|
||||
def __init__(self, globals=None, locals=None):
|
||||
if locals is None:
|
||||
locals = {}
|
||||
if globals is None:
|
||||
globals = {}
|
||||
self._ipy = _InteractiveConsole(globals, locals)
|
||||
|
||||
def eval(self, code):
|
||||
_local._current_ipy = self._ipy
|
||||
old_sys_stdout = sys.stdout
|
||||
try:
|
||||
return self._ipy.runsource(code)
|
||||
finally:
|
||||
sys.stdout = old_sys_stdout
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/console.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/console.pyc
Normal file
Binary file not shown.
280
venv/lib/python2.7/site-packages/werkzeug/debug/repr.py
Normal file
280
venv/lib/python2.7/site-packages/werkzeug/debug/repr.py
Normal file
@@ -0,0 +1,280 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.debug.repr
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module implements object representations for debugging purposes.
|
||||
Unlike the default repr these reprs expose a lot more information and
|
||||
produce HTML instead of ASCII.
|
||||
|
||||
Together with the CSS and JavaScript files of the debugger this gives
|
||||
a colorful and more compact output.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD.
|
||||
"""
|
||||
import sys
|
||||
import re
|
||||
import codecs
|
||||
from traceback import format_exception_only
|
||||
try:
|
||||
from collections import deque
|
||||
except ImportError: # pragma: no cover
|
||||
deque = None
|
||||
from werkzeug.utils import escape
|
||||
from werkzeug._compat import iteritems, PY2, text_type, integer_types, \
|
||||
string_types
|
||||
|
||||
|
||||
missing = object()
|
||||
_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')
|
||||
RegexType = type(_paragraph_re)
|
||||
|
||||
|
||||
HELP_HTML = '''\
|
||||
<div class=box>
|
||||
<h3>%(title)s</h3>
|
||||
<pre class=help>%(text)s</pre>
|
||||
</div>\
|
||||
'''
|
||||
OBJECT_DUMP_HTML = '''\
|
||||
<div class=box>
|
||||
<h3>%(title)s</h3>
|
||||
%(repr)s
|
||||
<table>%(items)s</table>
|
||||
</div>\
|
||||
'''
|
||||
|
||||
|
||||
def debug_repr(obj):
|
||||
"""Creates a debug repr of an object as HTML unicode string."""
|
||||
return DebugReprGenerator().repr(obj)
|
||||
|
||||
|
||||
def dump(obj=missing):
|
||||
"""Print the object details to stdout._write (for the interactive
|
||||
console of the web debugger.
|
||||
"""
|
||||
gen = DebugReprGenerator()
|
||||
if obj is missing:
|
||||
rv = gen.dump_locals(sys._getframe(1).f_locals)
|
||||
else:
|
||||
rv = gen.dump_object(obj)
|
||||
sys.stdout._write(rv)
|
||||
|
||||
|
||||
class _Helper(object):
|
||||
|
||||
"""Displays an HTML version of the normal help, for the interactive
|
||||
debugger only because it requires a patched sys.stdout.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return 'Type help(object) for help about object.'
|
||||
|
||||
def __call__(self, topic=None):
|
||||
if topic is None:
|
||||
sys.stdout._write('<span class=help>%s</span>' % repr(self))
|
||||
return
|
||||
import pydoc
|
||||
pydoc.help(topic)
|
||||
rv = sys.stdout.reset()
|
||||
if isinstance(rv, bytes):
|
||||
rv = rv.decode('utf-8', 'ignore')
|
||||
paragraphs = _paragraph_re.split(rv)
|
||||
if len(paragraphs) > 1:
|
||||
title = paragraphs[0]
|
||||
text = '\n\n'.join(paragraphs[1:])
|
||||
else: # pragma: no cover
|
||||
title = 'Help'
|
||||
text = paragraphs[0]
|
||||
sys.stdout._write(HELP_HTML % {'title': title, 'text': text})
|
||||
|
||||
|
||||
helper = _Helper()
|
||||
|
||||
|
||||
def _add_subclass_info(inner, obj, base):
|
||||
if isinstance(base, tuple):
|
||||
for base in base:
|
||||
if type(obj) is base:
|
||||
return inner
|
||||
elif type(obj) is base:
|
||||
return inner
|
||||
module = ''
|
||||
if obj.__class__.__module__ not in ('__builtin__', 'exceptions'):
|
||||
module = '<span class="module">%s.</span>' % obj.__class__.__module__
|
||||
return '%s%s(%s)' % (module, obj.__class__.__name__, inner)
|
||||
|
||||
|
||||
class DebugReprGenerator(object):
|
||||
|
||||
def __init__(self):
|
||||
self._stack = []
|
||||
|
||||
def _sequence_repr_maker(left, right, base=object(), limit=8):
|
||||
def proxy(self, obj, recursive):
|
||||
if recursive:
|
||||
return _add_subclass_info(left + '...' + right, obj, base)
|
||||
buf = [left]
|
||||
have_extended_section = False
|
||||
for idx, item in enumerate(obj):
|
||||
if idx:
|
||||
buf.append(', ')
|
||||
if idx == limit:
|
||||
buf.append('<span class="extended">')
|
||||
have_extended_section = True
|
||||
buf.append(self.repr(item))
|
||||
if have_extended_section:
|
||||
buf.append('</span>')
|
||||
buf.append(right)
|
||||
return _add_subclass_info(u''.join(buf), obj, base)
|
||||
return proxy
|
||||
|
||||
list_repr = _sequence_repr_maker('[', ']', list)
|
||||
tuple_repr = _sequence_repr_maker('(', ')', tuple)
|
||||
set_repr = _sequence_repr_maker('set([', '])', set)
|
||||
frozenset_repr = _sequence_repr_maker('frozenset([', '])', frozenset)
|
||||
if deque is not None:
|
||||
deque_repr = _sequence_repr_maker('<span class="module">collections.'
|
||||
'</span>deque([', '])', deque)
|
||||
del _sequence_repr_maker
|
||||
|
||||
def regex_repr(self, obj):
|
||||
pattern = repr(obj.pattern)
|
||||
if PY2:
|
||||
pattern = pattern.decode('string-escape', 'ignore')
|
||||
else:
|
||||
pattern = codecs.decode(pattern, 'unicode-escape', 'ignore')
|
||||
if pattern[:1] == 'u':
|
||||
pattern = 'ur' + pattern[1:]
|
||||
else:
|
||||
pattern = 'r' + pattern
|
||||
return u're.compile(<span class="string regex">%s</span>)' % pattern
|
||||
|
||||
def string_repr(self, obj, limit=70):
|
||||
buf = ['<span class="string">']
|
||||
a = repr(obj[:limit])
|
||||
b = repr(obj[limit:])
|
||||
if isinstance(obj, text_type) and PY2:
|
||||
buf.append('u')
|
||||
a = a[1:]
|
||||
b = b[1:]
|
||||
if b != "''":
|
||||
buf.extend((escape(a[:-1]), '<span class="extended">', escape(b[1:]), '</span>'))
|
||||
else:
|
||||
buf.append(escape(a))
|
||||
buf.append('</span>')
|
||||
return _add_subclass_info(u''.join(buf), obj, (bytes, text_type))
|
||||
|
||||
def dict_repr(self, d, recursive, limit=5):
|
||||
if recursive:
|
||||
return _add_subclass_info(u'{...}', d, dict)
|
||||
buf = ['{']
|
||||
have_extended_section = False
|
||||
for idx, (key, value) in enumerate(iteritems(d)):
|
||||
if idx:
|
||||
buf.append(', ')
|
||||
if idx == limit - 1:
|
||||
buf.append('<span class="extended">')
|
||||
have_extended_section = True
|
||||
buf.append('<span class="pair"><span class="key">%s</span>: '
|
||||
'<span class="value">%s</span></span>' %
|
||||
(self.repr(key), self.repr(value)))
|
||||
if have_extended_section:
|
||||
buf.append('</span>')
|
||||
buf.append('}')
|
||||
return _add_subclass_info(u''.join(buf), d, dict)
|
||||
|
||||
def object_repr(self, obj):
|
||||
r = repr(obj)
|
||||
if PY2:
|
||||
r = r.decode('utf-8', 'replace')
|
||||
return u'<span class="object">%s</span>' % escape(r)
|
||||
|
||||
def dispatch_repr(self, obj, recursive):
|
||||
if obj is helper:
|
||||
return u'<span class="help">%r</span>' % helper
|
||||
if isinstance(obj, (integer_types, float, complex)):
|
||||
return u'<span class="number">%r</span>' % obj
|
||||
if isinstance(obj, string_types):
|
||||
return self.string_repr(obj)
|
||||
if isinstance(obj, RegexType):
|
||||
return self.regex_repr(obj)
|
||||
if isinstance(obj, list):
|
||||
return self.list_repr(obj, recursive)
|
||||
if isinstance(obj, tuple):
|
||||
return self.tuple_repr(obj, recursive)
|
||||
if isinstance(obj, set):
|
||||
return self.set_repr(obj, recursive)
|
||||
if isinstance(obj, frozenset):
|
||||
return self.frozenset_repr(obj, recursive)
|
||||
if isinstance(obj, dict):
|
||||
return self.dict_repr(obj, recursive)
|
||||
if deque is not None and isinstance(obj, deque):
|
||||
return self.deque_repr(obj, recursive)
|
||||
return self.object_repr(obj)
|
||||
|
||||
def fallback_repr(self):
|
||||
try:
|
||||
info = ''.join(format_exception_only(*sys.exc_info()[:2]))
|
||||
except Exception: # pragma: no cover
|
||||
info = '?'
|
||||
if PY2:
|
||||
info = info.decode('utf-8', 'ignore')
|
||||
return u'<span class="brokenrepr"><broken repr (%s)>' \
|
||||
u'</span>' % escape(info.strip())
|
||||
|
||||
def repr(self, obj):
|
||||
recursive = False
|
||||
for item in self._stack:
|
||||
if item is obj:
|
||||
recursive = True
|
||||
break
|
||||
self._stack.append(obj)
|
||||
try:
|
||||
try:
|
||||
return self.dispatch_repr(obj, recursive)
|
||||
except Exception:
|
||||
return self.fallback_repr()
|
||||
finally:
|
||||
self._stack.pop()
|
||||
|
||||
def dump_object(self, obj):
|
||||
repr = items = None
|
||||
if isinstance(obj, dict):
|
||||
title = 'Contents of'
|
||||
items = []
|
||||
for key, value in iteritems(obj):
|
||||
if not isinstance(key, string_types):
|
||||
items = None
|
||||
break
|
||||
items.append((key, self.repr(value)))
|
||||
if items is None:
|
||||
items = []
|
||||
repr = self.repr(obj)
|
||||
for key in dir(obj):
|
||||
try:
|
||||
items.append((key, self.repr(getattr(obj, key))))
|
||||
except Exception:
|
||||
pass
|
||||
title = 'Details for'
|
||||
title += ' ' + object.__repr__(obj)[1:-1]
|
||||
return self.render_object_dump(items, title, repr)
|
||||
|
||||
def dump_locals(self, d):
|
||||
items = [(key, self.repr(value)) for key, value in d.items()]
|
||||
return self.render_object_dump(items, 'Local variables in frame')
|
||||
|
||||
def render_object_dump(self, items, title, repr=None):
|
||||
html_items = []
|
||||
for key, value in items:
|
||||
html_items.append('<tr><th>%s<td><pre class=repr>%s</pre>' %
|
||||
(escape(key), value))
|
||||
if not html_items:
|
||||
html_items.append('<tr><td><em>Nothing</em>')
|
||||
return OBJECT_DUMP_HTML % {
|
||||
'title': escape(title),
|
||||
'repr': repr and '<pre class=repr>%s</pre>' % repr or '',
|
||||
'items': '\n'.join(html_items)
|
||||
}
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/repr.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/repr.pyc
Normal file
Binary file not shown.
@@ -0,0 +1,96 @@
|
||||
-------------------------------
|
||||
UBUNTU FONT LICENCE Version 1.0
|
||||
-------------------------------
|
||||
|
||||
PREAMBLE
|
||||
This licence allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely. The fonts, including any derivative works, can be
|
||||
bundled, embedded, and redistributed provided the terms of this licence
|
||||
are met. The fonts and derivatives, however, cannot be released under
|
||||
any other licence. The requirement for fonts to remain under this
|
||||
licence does not require any document created using the fonts or their
|
||||
derivatives to be published under this licence, as long as the primary
|
||||
purpose of the document is not to be a vehicle for the distribution of
|
||||
the fonts.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this licence and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Original Version" refers to the collection of Font Software components
|
||||
as received under this licence.
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to
|
||||
a new environment.
|
||||
|
||||
"Copyright Holder(s)" refers to all individuals and companies who have a
|
||||
copyright ownership of the Font Software.
|
||||
|
||||
"Substantially Changed" refers to Modified Versions which can be easily
|
||||
identified as dissimilar to the Font Software by users of the Font
|
||||
Software comparing the Original Version with the Modified Version.
|
||||
|
||||
To "Propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification and with or without charging
|
||||
a redistribution fee), making available to the public, and in some
|
||||
countries other activities as well.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
This licence does not grant any rights under trademark law and all such
|
||||
rights are reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of the Font Software, to propagate the Font Software, subject to
|
||||
the below conditions:
|
||||
|
||||
1) Each copy of the Font Software must contain the above copyright
|
||||
notice and this licence. These can be included either as stand-alone
|
||||
text files, human-readable headers or in the appropriate machine-
|
||||
readable metadata fields within text or binary files as long as those
|
||||
fields can be easily viewed by the user.
|
||||
|
||||
2) The font name complies with the following:
|
||||
(a) The Original Version must retain its name, unmodified.
|
||||
(b) Modified Versions which are Substantially Changed must be renamed to
|
||||
avoid use of the name of the Original Version or similar names entirely.
|
||||
(c) Modified Versions which are not Substantially Changed must be
|
||||
renamed to both (i) retain the name of the Original Version and (ii) add
|
||||
additional naming elements to distinguish the Modified Version from the
|
||||
Original Version. The name of such Modified Versions must be the name of
|
||||
the Original Version, with "derivative X" where X represents the name of
|
||||
the new work, appended to that name.
|
||||
|
||||
3) The name(s) of the Copyright Holder(s) and any contributor to the
|
||||
Font Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except (i) as required by this licence, (ii) to
|
||||
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
|
||||
their explicit written permission.
|
||||
|
||||
4) The Font Software, modified or unmodified, in part or in whole, must
|
||||
be distributed entirely under this licence, and must not be distributed
|
||||
under any other licence. The requirement for fonts to remain under this
|
||||
licence does not affect any document created using the Font Software,
|
||||
except any version of the Font Software extracted from a document
|
||||
created using the Font Software may only be distributed under this
|
||||
licence.
|
||||
|
||||
TERMINATION
|
||||
This licence becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
||||
DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 507 B |
@@ -0,0 +1,205 @@
|
||||
$(function() {
|
||||
if (!EVALEX_TRUSTED) {
|
||||
initPinBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* if we are in console mode, show the console.
|
||||
*/
|
||||
if (CONSOLE_MODE && EVALEX) {
|
||||
openShell(null, $('div.console div.inner').empty(), 0);
|
||||
}
|
||||
|
||||
$('div.traceback div.frame').each(function() {
|
||||
var
|
||||
target = $('pre', this),
|
||||
consoleNode = null,
|
||||
frameID = this.id.substring(6);
|
||||
|
||||
target.click(function() {
|
||||
$(this).parent().toggleClass('expanded');
|
||||
});
|
||||
|
||||
/**
|
||||
* Add an interactive console to the frames
|
||||
*/
|
||||
if (EVALEX && target.is('.current')) {
|
||||
$('<img src="?__debugger__=yes&cmd=resource&f=console.png">')
|
||||
.attr('title', 'Open an interactive python shell in this frame')
|
||||
.click(function() {
|
||||
consoleNode = openShell(consoleNode, target, frameID);
|
||||
return false;
|
||||
})
|
||||
.prependTo(target);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* toggle traceback types on click.
|
||||
*/
|
||||
$('h2.traceback').click(function() {
|
||||
$(this).next().slideToggle('fast');
|
||||
$('div.plain').slideToggle('fast');
|
||||
}).css('cursor', 'pointer');
|
||||
$('div.plain').hide();
|
||||
|
||||
/**
|
||||
* Add extra info (this is here so that only users with JavaScript
|
||||
* enabled see it.)
|
||||
*/
|
||||
$('span.nojavascript')
|
||||
.removeClass('nojavascript')
|
||||
.html('<p>To switch between the interactive traceback and the plaintext ' +
|
||||
'one, you can click on the "Traceback" headline. From the text ' +
|
||||
'traceback you can also create a paste of it. ' + (!EVALEX ? '' :
|
||||
'For code execution mouse-over the frame you want to debug and ' +
|
||||
'click on the console icon on the right side.' +
|
||||
'<p>You can execute arbitrary Python code in the stack frames and ' +
|
||||
'there are some extra helpers available for introspection:' +
|
||||
'<ul><li><code>dump()</code> shows all variables in the frame' +
|
||||
'<li><code>dump(obj)</code> dumps all that\'s known about the object</ul>'));
|
||||
|
||||
/**
|
||||
* Add the pastebin feature
|
||||
*/
|
||||
$('div.plain form')
|
||||
.submit(function() {
|
||||
var label = $('input[type="submit"]', this);
|
||||
var old_val = label.val();
|
||||
label.val('submitting...');
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: document.location.pathname,
|
||||
data: {__debugger__: 'yes', tb: TRACEBACK, cmd: 'paste',
|
||||
s: SECRET},
|
||||
success: function(data) {
|
||||
$('div.plain span.pastemessage')
|
||||
.removeClass('pastemessage')
|
||||
.text('Paste created: ')
|
||||
.append($('<a>#' + data.id + '</a>').attr('href', data.url));
|
||||
},
|
||||
error: function() {
|
||||
alert('Error: Could not submit paste. No network connection?');
|
||||
label.val(old_val);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
// if we have javascript we submit by ajax anyways, so no need for the
|
||||
// not scaling textarea.
|
||||
var plainTraceback = $('div.plain textarea');
|
||||
plainTraceback.replaceWith($('<pre>').text(plainTraceback.text()));
|
||||
});
|
||||
|
||||
function initPinBox() {
|
||||
$('.pin-prompt form').submit(function(evt) {
|
||||
evt.preventDefault();
|
||||
var pin = this.pin.value;
|
||||
var btn = this.btn;
|
||||
btn.disabled = true;
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: document.location.pathname,
|
||||
data: {__debugger__: 'yes', cmd: 'pinauth', pin: pin,
|
||||
s: SECRET},
|
||||
success: function(data) {
|
||||
btn.disabled = false;
|
||||
if (data.auth) {
|
||||
EVALEX_TRUSTED = true;
|
||||
$('.pin-prompt').fadeOut();
|
||||
} else {
|
||||
if (data.exhausted) {
|
||||
alert('Error: too many attempts. Restart server to retry.');
|
||||
} else {
|
||||
alert('Error: incorrect pin');
|
||||
}
|
||||
}
|
||||
console.log(data);
|
||||
},
|
||||
error: function() {
|
||||
btn.disabled = false;
|
||||
alert('Error: Could not verify PIN. Network error?');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function promptForPin() {
|
||||
if (!EVALEX_TRUSTED) {
|
||||
$.ajax({
|
||||
url: document.location.pathname,
|
||||
data: {__debugger__: 'yes', cmd: 'printpin', s: SECRET}
|
||||
});
|
||||
$('.pin-prompt').fadeIn(function() {
|
||||
$('.pin-prompt input[name="pin"]').focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for shell initialization
|
||||
*/
|
||||
function openShell(consoleNode, target, frameID) {
|
||||
promptForPin();
|
||||
if (consoleNode)
|
||||
return consoleNode.slideToggle('fast');
|
||||
consoleNode = $('<pre class="console">')
|
||||
.appendTo(target.parent())
|
||||
.hide()
|
||||
var historyPos = 0, history = [''];
|
||||
var output = $('<div class="output">[console ready]</div>')
|
||||
.appendTo(consoleNode);
|
||||
var form = $('<form>>>> </form>')
|
||||
.submit(function() {
|
||||
var cmd = command.val();
|
||||
$.get('', {
|
||||
__debugger__: 'yes', cmd: cmd, frm: frameID, s: SECRET}, function(data) {
|
||||
var tmp = $('<div>').html(data);
|
||||
$('span.extended', tmp).each(function() {
|
||||
var hidden = $(this).wrap('<span>').hide();
|
||||
hidden
|
||||
.parent()
|
||||
.append($('<a href="#" class="toggle"> </a>')
|
||||
.click(function() {
|
||||
hidden.toggle();
|
||||
$(this).toggleClass('open')
|
||||
return false;
|
||||
}));
|
||||
});
|
||||
output.append(tmp);
|
||||
command.focus();
|
||||
consoleNode.scrollTop(consoleNode.get(0).scrollHeight);
|
||||
var old = history.pop();
|
||||
history.push(cmd);
|
||||
if (typeof old != 'undefined')
|
||||
history.push(old);
|
||||
historyPos = history.length - 1;
|
||||
});
|
||||
command.val('');
|
||||
return false;
|
||||
}).
|
||||
appendTo(consoleNode);
|
||||
|
||||
var command = $('<input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">')
|
||||
.appendTo(form)
|
||||
.keydown(function(e) {
|
||||
if (e.charCode == 100 && e.ctrlKey) {
|
||||
output.text('--- screen cleared ---');
|
||||
return false;
|
||||
}
|
||||
else if (e.charCode == 0 && (e.keyCode == 38 || e.keyCode == 40)) {
|
||||
if (e.keyCode == 38 && historyPos > 0)
|
||||
historyPos--;
|
||||
else if (e.keyCode == 40 && historyPos < history.length)
|
||||
historyPos++;
|
||||
command.val(history[historyPos]);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return consoleNode.slideDown('fast', function() {
|
||||
command.focus();
|
||||
});
|
||||
}
|
||||
5
venv/lib/python2.7/site-packages/werkzeug/debug/shared/jquery.js
vendored
Normal file
5
venv/lib/python2.7/site-packages/werkzeug/debug/shared/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/shared/less.png
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/shared/less.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 191 B |
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/shared/more.png
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/shared/more.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 B |
Binary file not shown.
|
After Width: | Height: | Size: 818 B |
143
venv/lib/python2.7/site-packages/werkzeug/debug/shared/style.css
Normal file
143
venv/lib/python2.7/site-packages/werkzeug/debug/shared/style.css
Normal file
@@ -0,0 +1,143 @@
|
||||
@font-face {
|
||||
font-family: 'Ubuntu';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: local('Ubuntu'), local('Ubuntu-Regular'),
|
||||
url('?__debugger__=yes&cmd=resource&f=ubuntu.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body, input { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
||||
'Verdana', sans-serif; color: #000; text-align: center;
|
||||
margin: 1em; padding: 0; font-size: 15px; }
|
||||
h1, h2, h3 { font-family: 'Ubuntu', 'Lucida Grande', 'Lucida Sans Unicode',
|
||||
'Geneva', 'Verdana', sans-serif; font-weight: normal; }
|
||||
|
||||
input { background-color: #fff; margin: 0; text-align: left;
|
||||
outline: none !important; }
|
||||
input[type="submit"] { padding: 3px 6px; }
|
||||
a { color: #11557C; }
|
||||
a:hover { color: #177199; }
|
||||
pre, code,
|
||||
textarea { font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans Mono',
|
||||
monospace; font-size: 14px; }
|
||||
|
||||
div.debugger { text-align: left; padding: 12px; margin: auto;
|
||||
background-color: white; }
|
||||
h1 { font-size: 36px; margin: 0 0 0.3em 0; }
|
||||
div.detail p { margin: 0 0 8px 13px; font-size: 14px; white-space: pre-wrap;
|
||||
font-family: monospace; }
|
||||
div.explanation { margin: 20px 13px; font-size: 15px; color: #555; }
|
||||
div.footer { font-size: 13px; text-align: right; margin: 30px 0;
|
||||
color: #86989B; }
|
||||
|
||||
h2 { font-size: 16px; margin: 1.3em 0 0.0 0; padding: 9px;
|
||||
background-color: #11557C; color: white; }
|
||||
h2 em, h3 em { font-style: normal; color: #A5D6D9; font-weight: normal; }
|
||||
|
||||
div.traceback, div.plain { border: 1px solid #ddd; margin: 0 0 1em 0; padding: 10px; }
|
||||
div.plain p { margin: 0; }
|
||||
div.plain textarea,
|
||||
div.plain pre { margin: 10px 0 0 0; padding: 4px;
|
||||
background-color: #E8EFF0; border: 1px solid #D3E7E9; }
|
||||
div.plain textarea { width: 99%; height: 300px; }
|
||||
div.traceback h3 { font-size: 1em; margin: 0 0 0.8em 0; }
|
||||
div.traceback ul { list-style: none; margin: 0; padding: 0 0 0 1em; }
|
||||
div.traceback h4 { font-size: 13px; font-weight: normal; margin: 0.7em 0 0.1em 0; }
|
||||
div.traceback pre { margin: 0; padding: 5px 0 3px 15px;
|
||||
background-color: #E8EFF0; border: 1px solid #D3E7E9; }
|
||||
div.traceback pre:hover { background-color: #DDECEE; color: black; cursor: pointer; }
|
||||
div.traceback div.source.expanded pre + pre { border-top: none; }
|
||||
|
||||
div.traceback span.ws { display: none; }
|
||||
div.traceback pre.before, div.traceback pre.after { display: none; background: white; }
|
||||
div.traceback div.source.expanded pre.before,
|
||||
div.traceback div.source.expanded pre.after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.traceback div.source.expanded span.ws {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.traceback blockquote { margin: 1em 0 0 0; padding: 0; }
|
||||
div.traceback img { float: right; padding: 2px; margin: -3px 2px 0 0; display: none; }
|
||||
div.traceback img:hover { background-color: #ddd; cursor: pointer;
|
||||
border-color: #BFDDE0; }
|
||||
div.traceback pre:hover img { display: block; }
|
||||
div.traceback cite.filename { font-style: normal; color: #3B666B; }
|
||||
|
||||
pre.console { border: 1px solid #ccc; background: white!important;
|
||||
color: black; padding: 5px!important;
|
||||
margin: 3px 0 0 0!important; cursor: default!important;
|
||||
max-height: 400px; overflow: auto; }
|
||||
pre.console form { color: #555; }
|
||||
pre.console input { background-color: transparent; color: #555;
|
||||
width: 90%; font-family: 'Consolas', 'Deja Vu Sans Mono',
|
||||
'Bitstream Vera Sans Mono', monospace; font-size: 14px;
|
||||
border: none!important; }
|
||||
|
||||
span.string { color: #30799B; }
|
||||
span.number { color: #9C1A1C; }
|
||||
span.help { color: #3A7734; }
|
||||
span.object { color: #485F6E; }
|
||||
span.extended { opacity: 0.5; }
|
||||
span.extended:hover { opacity: 1; }
|
||||
a.toggle { text-decoration: none; background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-image: url(?__debugger__=yes&cmd=resource&f=more.png); }
|
||||
a.toggle:hover { background-color: #444; }
|
||||
a.open { background-image: url(?__debugger__=yes&cmd=resource&f=less.png); }
|
||||
|
||||
pre.console div.traceback,
|
||||
pre.console div.box { margin: 5px 10px; white-space: normal;
|
||||
border: 1px solid #11557C; padding: 10px;
|
||||
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
||||
'Verdana', sans-serif; }
|
||||
pre.console div.box h3,
|
||||
pre.console div.traceback h3 { margin: -10px -10px 10px -10px; padding: 5px;
|
||||
background: #11557C; color: white; }
|
||||
|
||||
pre.console div.traceback pre:hover { cursor: default; background: #E8EFF0; }
|
||||
pre.console div.traceback pre.syntaxerror { background: inherit; border: none;
|
||||
margin: 20px -10px -10px -10px;
|
||||
padding: 10px; border-top: 1px solid #BFDDE0;
|
||||
background: #E8EFF0; }
|
||||
pre.console div.noframe-traceback pre.syntaxerror { margin-top: -10px; border: none; }
|
||||
|
||||
pre.console div.box pre.repr { padding: 0; margin: 0; background-color: white; border: none; }
|
||||
pre.console div.box table { margin-top: 6px; }
|
||||
pre.console div.box pre { border: none; }
|
||||
pre.console div.box pre.help { background-color: white; }
|
||||
pre.console div.box pre.help:hover { cursor: default; }
|
||||
pre.console table tr { vertical-align: top; }
|
||||
div.console { border: 1px solid #ccc; padding: 4px; background-color: #fafafa; }
|
||||
|
||||
div.traceback pre, div.console pre {
|
||||
white-space: pre-wrap; /* css-3 should we be so lucky... */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 ?? */
|
||||
white-space: -o-pre-wrap; /* Opera 7 ?? */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
_white-space: pre; /* IE only hack to re-specify in
|
||||
addition to word-wrap */
|
||||
}
|
||||
|
||||
|
||||
div.pin-prompt {
|
||||
position: absolute;
|
||||
display: none;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
div.pin-prompt .inner {
|
||||
background: #eee;
|
||||
padding: 10px 50px;
|
||||
width: 350px;
|
||||
margin: 10% auto 0 auto;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
Binary file not shown.
556
venv/lib/python2.7/site-packages/werkzeug/debug/tbtools.py
Normal file
556
venv/lib/python2.7/site-packages/werkzeug/debug/tbtools.py
Normal file
@@ -0,0 +1,556 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.debug.tbtools
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides various traceback related utility functions.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD.
|
||||
"""
|
||||
import re
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import inspect
|
||||
import traceback
|
||||
import codecs
|
||||
from tokenize import TokenError
|
||||
|
||||
from werkzeug.utils import cached_property, escape
|
||||
from werkzeug.debug.console import Console
|
||||
from werkzeug._compat import range_type, PY2, text_type, string_types, \
|
||||
to_native, to_unicode
|
||||
from werkzeug.filesystem import get_filesystem_encoding
|
||||
|
||||
|
||||
_coding_re = re.compile(br'coding[:=]\s*([-\w.]+)')
|
||||
_line_re = re.compile(br'^(.*?)$', re.MULTILINE)
|
||||
_funcdef_re = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
|
||||
UTF8_COOKIE = b'\xef\xbb\xbf'
|
||||
|
||||
system_exceptions = (SystemExit, KeyboardInterrupt)
|
||||
try:
|
||||
system_exceptions += (GeneratorExit,)
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
|
||||
HEADER = u'''\
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>%(title)s // Werkzeug Debugger</title>
|
||||
<link rel="stylesheet" href="?__debugger__=yes&cmd=resource&f=style.css"
|
||||
type="text/css">
|
||||
<!-- We need to make sure this has a favicon so that the debugger does
|
||||
not by accident trigger a request to /favicon.ico which might
|
||||
change the application state. -->
|
||||
<link rel="shortcut icon"
|
||||
href="?__debugger__=yes&cmd=resource&f=console.png">
|
||||
<script src="?__debugger__=yes&cmd=resource&f=jquery.js"></script>
|
||||
<script src="?__debugger__=yes&cmd=resource&f=debugger.js"></script>
|
||||
<script type="text/javascript">
|
||||
var TRACEBACK = %(traceback_id)d,
|
||||
CONSOLE_MODE = %(console)s,
|
||||
EVALEX = %(evalex)s,
|
||||
EVALEX_TRUSTED = %(evalex_trusted)s,
|
||||
SECRET = "%(secret)s";
|
||||
</script>
|
||||
</head>
|
||||
<body style="background-color: #fff">
|
||||
<div class="debugger">
|
||||
'''
|
||||
FOOTER = u'''\
|
||||
<div class="footer">
|
||||
Brought to you by <strong class="arthur">DON'T PANIC</strong>, your
|
||||
friendly Werkzeug powered traceback interpreter.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pin-prompt">
|
||||
<div class="inner">
|
||||
<h3>Console Locked</h3>
|
||||
<p>
|
||||
The console is locked and needs to be unlocked by entering the PIN.
|
||||
You can find the PIN printed out on the standard output of your
|
||||
shell that runs the server.
|
||||
<form>
|
||||
<p>PIN:
|
||||
<input type=text name=pin size=14>
|
||||
<input type=submit name=btn value="Confirm Pin">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
PAGE_HTML = HEADER + u'''\
|
||||
<h1>%(exception_type)s</h1>
|
||||
<div class="detail">
|
||||
<p class="errormsg">%(exception)s</p>
|
||||
</div>
|
||||
<h2 class="traceback">Traceback <em>(most recent call last)</em></h2>
|
||||
%(summary)s
|
||||
<div class="plain">
|
||||
<form action="/?__debugger__=yes&cmd=paste" method="post">
|
||||
<p>
|
||||
<input type="hidden" name="language" value="pytb">
|
||||
This is the Copy/Paste friendly version of the traceback. <span
|
||||
class="pastemessage">You can also paste this traceback into
|
||||
a <a href="https://gist.github.com/">gist</a>:
|
||||
<input type="submit" value="create paste"></span>
|
||||
</p>
|
||||
<textarea cols="50" rows="10" name="code" readonly>%(plaintext)s</textarea>
|
||||
</form>
|
||||
</div>
|
||||
<div class="explanation">
|
||||
The debugger caught an exception in your WSGI application. You can now
|
||||
look at the traceback which led to the error. <span class="nojavascript">
|
||||
If you enable JavaScript you can also use additional features such as code
|
||||
execution (if the evalex feature is enabled), automatic pasting of the
|
||||
exceptions and much more.</span>
|
||||
</div>
|
||||
''' + FOOTER + '''
|
||||
<!--
|
||||
|
||||
%(plaintext_cs)s
|
||||
|
||||
-->
|
||||
'''
|
||||
|
||||
CONSOLE_HTML = HEADER + u'''\
|
||||
<h1>Interactive Console</h1>
|
||||
<div class="explanation">
|
||||
In this console you can execute Python expressions in the context of the
|
||||
application. The initial namespace was created by the debugger automatically.
|
||||
</div>
|
||||
<div class="console"><div class="inner">The Console requires JavaScript.</div></div>
|
||||
''' + FOOTER
|
||||
|
||||
SUMMARY_HTML = u'''\
|
||||
<div class="%(classes)s">
|
||||
%(title)s
|
||||
<ul>%(frames)s</ul>
|
||||
%(description)s
|
||||
</div>
|
||||
'''
|
||||
|
||||
FRAME_HTML = u'''\
|
||||
<div class="frame" id="frame-%(id)d">
|
||||
<h4>File <cite class="filename">"%(filename)s"</cite>,
|
||||
line <em class="line">%(lineno)s</em>,
|
||||
in <code class="function">%(function_name)s</code></h4>
|
||||
<div class="source">%(lines)s</div>
|
||||
</div>
|
||||
'''
|
||||
|
||||
SOURCE_LINE_HTML = u'''\
|
||||
<tr class="%(classes)s">
|
||||
<td class=lineno>%(lineno)s</td>
|
||||
<td>%(code)s</td>
|
||||
</tr>
|
||||
'''
|
||||
|
||||
|
||||
def render_console_html(secret, evalex_trusted=True):
|
||||
return CONSOLE_HTML % {
|
||||
'evalex': 'true',
|
||||
'evalex_trusted': evalex_trusted and 'true' or 'false',
|
||||
'console': 'true',
|
||||
'title': 'Console',
|
||||
'secret': secret,
|
||||
'traceback_id': -1
|
||||
}
|
||||
|
||||
|
||||
def get_current_traceback(ignore_system_exceptions=False,
|
||||
show_hidden_frames=False, skip=0):
|
||||
"""Get the current exception info as `Traceback` object. Per default
|
||||
calling this method will reraise system exceptions such as generator exit,
|
||||
system exit or others. This behavior can be disabled by passing `False`
|
||||
to the function as first parameter.
|
||||
"""
|
||||
exc_type, exc_value, tb = sys.exc_info()
|
||||
if ignore_system_exceptions and exc_type in system_exceptions:
|
||||
raise
|
||||
for x in range_type(skip):
|
||||
if tb.tb_next is None:
|
||||
break
|
||||
tb = tb.tb_next
|
||||
tb = Traceback(exc_type, exc_value, tb)
|
||||
if not show_hidden_frames:
|
||||
tb.filter_hidden_frames()
|
||||
return tb
|
||||
|
||||
|
||||
class Line(object):
|
||||
"""Helper for the source renderer."""
|
||||
__slots__ = ('lineno', 'code', 'in_frame', 'current')
|
||||
|
||||
def __init__(self, lineno, code):
|
||||
self.lineno = lineno
|
||||
self.code = code
|
||||
self.in_frame = False
|
||||
self.current = False
|
||||
|
||||
def classes(self):
|
||||
rv = ['line']
|
||||
if self.in_frame:
|
||||
rv.append('in-frame')
|
||||
if self.current:
|
||||
rv.append('current')
|
||||
return rv
|
||||
classes = property(classes)
|
||||
|
||||
def render(self):
|
||||
return SOURCE_LINE_HTML % {
|
||||
'classes': u' '.join(self.classes),
|
||||
'lineno': self.lineno,
|
||||
'code': escape(self.code)
|
||||
}
|
||||
|
||||
|
||||
class Traceback(object):
|
||||
"""Wraps a traceback."""
|
||||
|
||||
def __init__(self, exc_type, exc_value, tb):
|
||||
self.exc_type = exc_type
|
||||
self.exc_value = exc_value
|
||||
if not isinstance(exc_type, str):
|
||||
exception_type = exc_type.__name__
|
||||
if exc_type.__module__ not in ('__builtin__', 'exceptions'):
|
||||
exception_type = exc_type.__module__ + '.' + exception_type
|
||||
else:
|
||||
exception_type = exc_type
|
||||
self.exception_type = exception_type
|
||||
|
||||
# we only add frames to the list that are not hidden. This follows
|
||||
# the the magic variables as defined by paste.exceptions.collector
|
||||
self.frames = []
|
||||
while tb:
|
||||
self.frames.append(Frame(exc_type, exc_value, tb))
|
||||
tb = tb.tb_next
|
||||
|
||||
def filter_hidden_frames(self):
|
||||
"""Remove the frames according to the paste spec."""
|
||||
if not self.frames:
|
||||
return
|
||||
|
||||
new_frames = []
|
||||
hidden = False
|
||||
for frame in self.frames:
|
||||
hide = frame.hide
|
||||
if hide in ('before', 'before_and_this'):
|
||||
new_frames = []
|
||||
hidden = False
|
||||
if hide == 'before_and_this':
|
||||
continue
|
||||
elif hide in ('reset', 'reset_and_this'):
|
||||
hidden = False
|
||||
if hide == 'reset_and_this':
|
||||
continue
|
||||
elif hide in ('after', 'after_and_this'):
|
||||
hidden = True
|
||||
if hide == 'after_and_this':
|
||||
continue
|
||||
elif hide or hidden:
|
||||
continue
|
||||
new_frames.append(frame)
|
||||
|
||||
# if we only have one frame and that frame is from the codeop
|
||||
# module, remove it.
|
||||
if len(new_frames) == 1 and self.frames[0].module == 'codeop':
|
||||
del self.frames[:]
|
||||
|
||||
# if the last frame is missing something went terrible wrong :(
|
||||
elif self.frames[-1] in new_frames:
|
||||
self.frames[:] = new_frames
|
||||
|
||||
def is_syntax_error(self):
|
||||
"""Is it a syntax error?"""
|
||||
return isinstance(self.exc_value, SyntaxError)
|
||||
is_syntax_error = property(is_syntax_error)
|
||||
|
||||
def exception(self):
|
||||
"""String representation of the exception."""
|
||||
buf = traceback.format_exception_only(self.exc_type, self.exc_value)
|
||||
rv = ''.join(buf).strip()
|
||||
return rv.decode('utf-8', 'replace') if PY2 else rv
|
||||
exception = property(exception)
|
||||
|
||||
def log(self, logfile=None):
|
||||
"""Log the ASCII traceback into a file object."""
|
||||
if logfile is None:
|
||||
logfile = sys.stderr
|
||||
tb = self.plaintext.rstrip() + u'\n'
|
||||
if PY2:
|
||||
tb = tb.encode('utf-8', 'replace')
|
||||
logfile.write(tb)
|
||||
|
||||
def paste(self):
|
||||
"""Create a paste and return the paste id."""
|
||||
data = json.dumps({
|
||||
'description': 'Werkzeug Internal Server Error',
|
||||
'public': False,
|
||||
'files': {
|
||||
'traceback.txt': {
|
||||
'content': self.plaintext
|
||||
}
|
||||
}
|
||||
}).encode('utf-8')
|
||||
try:
|
||||
from urllib2 import urlopen
|
||||
except ImportError:
|
||||
from urllib.request import urlopen
|
||||
rv = urlopen('https://api.github.com/gists', data=data)
|
||||
resp = json.loads(rv.read().decode('utf-8'))
|
||||
rv.close()
|
||||
return {
|
||||
'url': resp['html_url'],
|
||||
'id': resp['id']
|
||||
}
|
||||
|
||||
def render_summary(self, include_title=True):
|
||||
"""Render the traceback for the interactive console."""
|
||||
title = ''
|
||||
frames = []
|
||||
classes = ['traceback']
|
||||
if not self.frames:
|
||||
classes.append('noframe-traceback')
|
||||
|
||||
if include_title:
|
||||
if self.is_syntax_error:
|
||||
title = u'Syntax Error'
|
||||
else:
|
||||
title = u'Traceback <em>(most recent call last)</em>:'
|
||||
|
||||
for frame in self.frames:
|
||||
frames.append(u'<li%s>%s' % (
|
||||
frame.info and u' title="%s"' % escape(frame.info) or u'',
|
||||
frame.render()
|
||||
))
|
||||
|
||||
if self.is_syntax_error:
|
||||
description_wrapper = u'<pre class=syntaxerror>%s</pre>'
|
||||
else:
|
||||
description_wrapper = u'<blockquote>%s</blockquote>'
|
||||
|
||||
return SUMMARY_HTML % {
|
||||
'classes': u' '.join(classes),
|
||||
'title': title and u'<h3>%s</h3>' % title or u'',
|
||||
'frames': u'\n'.join(frames),
|
||||
'description': description_wrapper % escape(self.exception)
|
||||
}
|
||||
|
||||
def render_full(self, evalex=False, secret=None,
|
||||
evalex_trusted=True):
|
||||
"""Render the Full HTML page with the traceback info."""
|
||||
exc = escape(self.exception)
|
||||
return PAGE_HTML % {
|
||||
'evalex': evalex and 'true' or 'false',
|
||||
'evalex_trusted': evalex_trusted and 'true' or 'false',
|
||||
'console': 'false',
|
||||
'title': exc,
|
||||
'exception': exc,
|
||||
'exception_type': escape(self.exception_type),
|
||||
'summary': self.render_summary(include_title=False),
|
||||
'plaintext': escape(self.plaintext),
|
||||
'plaintext_cs': re.sub('-{2,}', '-', self.plaintext),
|
||||
'traceback_id': self.id,
|
||||
'secret': secret
|
||||
}
|
||||
|
||||
def generate_plaintext_traceback(self):
|
||||
"""Like the plaintext attribute but returns a generator"""
|
||||
yield u'Traceback (most recent call last):'
|
||||
for frame in self.frames:
|
||||
yield u' File "%s", line %s, in %s' % (
|
||||
frame.filename,
|
||||
frame.lineno,
|
||||
frame.function_name
|
||||
)
|
||||
yield u' ' + frame.current_line.strip()
|
||||
yield self.exception
|
||||
|
||||
def plaintext(self):
|
||||
return u'\n'.join(self.generate_plaintext_traceback())
|
||||
plaintext = cached_property(plaintext)
|
||||
|
||||
id = property(lambda x: id(x))
|
||||
|
||||
|
||||
class Frame(object):
|
||||
|
||||
"""A single frame in a traceback."""
|
||||
|
||||
def __init__(self, exc_type, exc_value, tb):
|
||||
self.lineno = tb.tb_lineno
|
||||
self.function_name = tb.tb_frame.f_code.co_name
|
||||
self.locals = tb.tb_frame.f_locals
|
||||
self.globals = tb.tb_frame.f_globals
|
||||
|
||||
fn = inspect.getsourcefile(tb) or inspect.getfile(tb)
|
||||
if fn[-4:] in ('.pyo', '.pyc'):
|
||||
fn = fn[:-1]
|
||||
# if it's a file on the file system resolve the real filename.
|
||||
if os.path.isfile(fn):
|
||||
fn = os.path.realpath(fn)
|
||||
self.filename = to_unicode(fn, get_filesystem_encoding())
|
||||
self.module = self.globals.get('__name__')
|
||||
self.loader = self.globals.get('__loader__')
|
||||
self.code = tb.tb_frame.f_code
|
||||
|
||||
# support for paste's traceback extensions
|
||||
self.hide = self.locals.get('__traceback_hide__', False)
|
||||
info = self.locals.get('__traceback_info__')
|
||||
if info is not None:
|
||||
try:
|
||||
info = text_type(info)
|
||||
except UnicodeError:
|
||||
info = str(info).decode('utf-8', 'replace')
|
||||
self.info = info
|
||||
|
||||
def render(self):
|
||||
"""Render a single frame in a traceback."""
|
||||
return FRAME_HTML % {
|
||||
'id': self.id,
|
||||
'filename': escape(self.filename),
|
||||
'lineno': self.lineno,
|
||||
'function_name': escape(self.function_name),
|
||||
'lines': self.render_line_context(),
|
||||
}
|
||||
|
||||
def render_line_context(self):
|
||||
before, current, after = self.get_context_lines()
|
||||
rv = []
|
||||
|
||||
def render_line(line, cls):
|
||||
line = line.expandtabs().rstrip()
|
||||
stripped_line = line.strip()
|
||||
prefix = len(line) - len(stripped_line)
|
||||
rv.append(
|
||||
'<pre class="line %s"><span class="ws">%s</span>%s</pre>' % (
|
||||
cls, ' ' * prefix, escape(stripped_line) or ' '))
|
||||
|
||||
for line in before:
|
||||
render_line(line, 'before')
|
||||
render_line(current, 'current')
|
||||
for line in after:
|
||||
render_line(line, 'after')
|
||||
|
||||
return '\n'.join(rv)
|
||||
|
||||
def get_annotated_lines(self):
|
||||
"""Helper function that returns lines with extra information."""
|
||||
lines = [Line(idx + 1, x) for idx, x in enumerate(self.sourcelines)]
|
||||
|
||||
# find function definition and mark lines
|
||||
if hasattr(self.code, 'co_firstlineno'):
|
||||
lineno = self.code.co_firstlineno - 1
|
||||
while lineno > 0:
|
||||
if _funcdef_re.match(lines[lineno].code):
|
||||
break
|
||||
lineno -= 1
|
||||
try:
|
||||
offset = len(inspect.getblock([x.code + '\n' for x
|
||||
in lines[lineno:]]))
|
||||
except TokenError:
|
||||
offset = 0
|
||||
for line in lines[lineno:lineno + offset]:
|
||||
line.in_frame = True
|
||||
|
||||
# mark current line
|
||||
try:
|
||||
lines[self.lineno - 1].current = True
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
return lines
|
||||
|
||||
def eval(self, code, mode='single'):
|
||||
"""Evaluate code in the context of the frame."""
|
||||
if isinstance(code, string_types):
|
||||
if PY2 and isinstance(code, unicode): # noqa
|
||||
code = UTF8_COOKIE + code.encode('utf-8')
|
||||
code = compile(code, '<interactive>', mode)
|
||||
return eval(code, self.globals, self.locals)
|
||||
|
||||
@cached_property
|
||||
def sourcelines(self):
|
||||
"""The sourcecode of the file as list of unicode strings."""
|
||||
# get sourcecode from loader or file
|
||||
source = None
|
||||
if self.loader is not None:
|
||||
try:
|
||||
if hasattr(self.loader, 'get_source'):
|
||||
source = self.loader.get_source(self.module)
|
||||
elif hasattr(self.loader, 'get_source_by_code'):
|
||||
source = self.loader.get_source_by_code(self.code)
|
||||
except Exception:
|
||||
# we munch the exception so that we don't cause troubles
|
||||
# if the loader is broken.
|
||||
pass
|
||||
|
||||
if source is None:
|
||||
try:
|
||||
f = open(to_native(self.filename, get_filesystem_encoding()),
|
||||
mode='rb')
|
||||
except IOError:
|
||||
return []
|
||||
try:
|
||||
source = f.read()
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
# already unicode? return right away
|
||||
if isinstance(source, text_type):
|
||||
return source.splitlines()
|
||||
|
||||
# yes. it should be ascii, but we don't want to reject too many
|
||||
# characters in the debugger if something breaks
|
||||
charset = 'utf-8'
|
||||
if source.startswith(UTF8_COOKIE):
|
||||
source = source[3:]
|
||||
else:
|
||||
for idx, match in enumerate(_line_re.finditer(source)):
|
||||
match = _coding_re.search(match.group())
|
||||
if match is not None:
|
||||
charset = match.group(1)
|
||||
break
|
||||
if idx > 1:
|
||||
break
|
||||
|
||||
# on broken cookies we fall back to utf-8 too
|
||||
charset = to_native(charset)
|
||||
try:
|
||||
codecs.lookup(charset)
|
||||
except LookupError:
|
||||
charset = 'utf-8'
|
||||
|
||||
return source.decode(charset, 'replace').splitlines()
|
||||
|
||||
def get_context_lines(self, context=5):
|
||||
before = self.sourcelines[self.lineno - context - 1:self.lineno - 1]
|
||||
past = self.sourcelines[self.lineno:self.lineno + context]
|
||||
return (
|
||||
before,
|
||||
self.current_line,
|
||||
past,
|
||||
)
|
||||
|
||||
@property
|
||||
def current_line(self):
|
||||
try:
|
||||
return self.sourcelines[self.lineno - 1]
|
||||
except IndexError:
|
||||
return u''
|
||||
|
||||
@cached_property
|
||||
def console(self):
|
||||
return Console(self.globals, self.locals)
|
||||
|
||||
id = property(lambda x: id(x))
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/tbtools.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/debug/tbtools.pyc
Normal file
Binary file not shown.
719
venv/lib/python2.7/site-packages/werkzeug/exceptions.py
Normal file
719
venv/lib/python2.7/site-packages/werkzeug/exceptions.py
Normal file
@@ -0,0 +1,719 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.exceptions
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module implements a number of Python exceptions you can raise from
|
||||
within your views to trigger a standard non-200 response.
|
||||
|
||||
|
||||
Usage Example
|
||||
-------------
|
||||
|
||||
::
|
||||
|
||||
from werkzeug.wrappers import BaseRequest
|
||||
from werkzeug.wsgi import responder
|
||||
from werkzeug.exceptions import HTTPException, NotFound
|
||||
|
||||
def view(request):
|
||||
raise NotFound()
|
||||
|
||||
@responder
|
||||
def application(environ, start_response):
|
||||
request = BaseRequest(environ)
|
||||
try:
|
||||
return view(request)
|
||||
except HTTPException as e:
|
||||
return e
|
||||
|
||||
|
||||
As you can see from this example those exceptions are callable WSGI
|
||||
applications. Because of Python 2.4 compatibility those do not extend
|
||||
from the response objects but only from the python exception class.
|
||||
|
||||
As a matter of fact they are not Werkzeug response objects. However you
|
||||
can get a response object by calling ``get_response()`` on a HTTP
|
||||
exception.
|
||||
|
||||
Keep in mind that you have to pass an environment to ``get_response()``
|
||||
because some errors fetch additional information from the WSGI
|
||||
environment.
|
||||
|
||||
If you want to hook in a different exception page to say, a 404 status
|
||||
code, you can add a second except for a specific subclass of an error::
|
||||
|
||||
@responder
|
||||
def application(environ, start_response):
|
||||
request = BaseRequest(environ)
|
||||
try:
|
||||
return view(request)
|
||||
except NotFound, e:
|
||||
return not_found(request)
|
||||
except HTTPException, e:
|
||||
return e
|
||||
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
|
||||
# Because of bootstrapping reasons we need to manually patch ourselves
|
||||
# onto our parent module.
|
||||
import werkzeug
|
||||
werkzeug.exceptions = sys.modules[__name__]
|
||||
|
||||
from werkzeug._internal import _get_environ
|
||||
from werkzeug._compat import iteritems, integer_types, text_type, \
|
||||
implements_to_string
|
||||
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class HTTPException(Exception):
|
||||
|
||||
"""
|
||||
Baseclass for all HTTP exceptions. This exception can be called as WSGI
|
||||
application to render a default error page or you can catch the subclasses
|
||||
of it independently and render nicer error messages.
|
||||
"""
|
||||
|
||||
code = None
|
||||
description = None
|
||||
|
||||
def __init__(self, description=None, response=None):
|
||||
Exception.__init__(self)
|
||||
if description is not None:
|
||||
self.description = description
|
||||
self.response = response
|
||||
|
||||
@classmethod
|
||||
def wrap(cls, exception, name=None):
|
||||
"""This method returns a new subclass of the exception provided that
|
||||
also is a subclass of `BadRequest`.
|
||||
"""
|
||||
class newcls(cls, exception):
|
||||
|
||||
def __init__(self, arg=None, *args, **kwargs):
|
||||
cls.__init__(self, *args, **kwargs)
|
||||
exception.__init__(self, arg)
|
||||
newcls.__module__ = sys._getframe(1).f_globals.get('__name__')
|
||||
newcls.__name__ = name or cls.__name__ + exception.__name__
|
||||
return newcls
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The status name."""
|
||||
return HTTP_STATUS_CODES.get(self.code, 'Unknown Error')
|
||||
|
||||
def get_description(self, environ=None):
|
||||
"""Get the description."""
|
||||
return u'<p>%s</p>' % escape(self.description)
|
||||
|
||||
def get_body(self, environ=None):
|
||||
"""Get the HTML body."""
|
||||
return text_type((
|
||||
u'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
|
||||
u'<title>%(code)s %(name)s</title>\n'
|
||||
u'<h1>%(name)s</h1>\n'
|
||||
u'%(description)s\n'
|
||||
) % {
|
||||
'code': self.code,
|
||||
'name': escape(self.name),
|
||||
'description': self.get_description(environ)
|
||||
})
|
||||
|
||||
def get_headers(self, environ=None):
|
||||
"""Get a list of headers."""
|
||||
return [('Content-Type', 'text/html')]
|
||||
|
||||
def get_response(self, environ=None):
|
||||
"""Get a response object. If one was passed to the exception
|
||||
it's returned directly.
|
||||
|
||||
:param environ: the optional environ for the request. This
|
||||
can be used to modify the response depending
|
||||
on how the request looked like.
|
||||
:return: a :class:`Response` object or a subclass thereof.
|
||||
"""
|
||||
if self.response is not None:
|
||||
return self.response
|
||||
if environ is not None:
|
||||
environ = _get_environ(environ)
|
||||
headers = self.get_headers(environ)
|
||||
return Response(self.get_body(environ), self.code, headers)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""Call the exception as WSGI application.
|
||||
|
||||
:param environ: the WSGI environment.
|
||||
:param start_response: the response callable provided by the WSGI
|
||||
server.
|
||||
"""
|
||||
response = self.get_response(environ)
|
||||
return response(environ, start_response)
|
||||
|
||||
def __str__(self):
|
||||
code = self.code if self.code is not None else '???'
|
||||
return '%s %s: %s' % (code, self.name, self.description)
|
||||
|
||||
def __repr__(self):
|
||||
code = self.code if self.code is not None else '???'
|
||||
return "<%s '%s: %s'>" % (self.__class__.__name__, code, self.name)
|
||||
|
||||
|
||||
class BadRequest(HTTPException):
|
||||
|
||||
"""*400* `Bad Request`
|
||||
|
||||
Raise if the browser sends something to the application the application
|
||||
or server cannot handle.
|
||||
"""
|
||||
code = 400
|
||||
description = (
|
||||
'The browser (or proxy) sent a request that this server could '
|
||||
'not understand.'
|
||||
)
|
||||
|
||||
|
||||
class ClientDisconnected(BadRequest):
|
||||
|
||||
"""Internal exception that is raised if Werkzeug detects a disconnected
|
||||
client. Since the client is already gone at that point attempting to
|
||||
send the error message to the client might not work and might ultimately
|
||||
result in another exception in the server. Mainly this is here so that
|
||||
it is silenced by default as far as Werkzeug is concerned.
|
||||
|
||||
Since disconnections cannot be reliably detected and are unspecified
|
||||
by WSGI to a large extent this might or might not be raised if a client
|
||||
is gone.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
|
||||
|
||||
class SecurityError(BadRequest):
|
||||
|
||||
"""Raised if something triggers a security error. This is otherwise
|
||||
exactly like a bad request error.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
|
||||
|
||||
class BadHost(BadRequest):
|
||||
|
||||
"""Raised if the submitted host is badly formatted.
|
||||
|
||||
.. versionadded:: 0.11.2
|
||||
"""
|
||||
|
||||
|
||||
class Unauthorized(HTTPException):
|
||||
|
||||
"""*401* `Unauthorized`
|
||||
|
||||
Raise if the user is not authorized. Also used if you want to use HTTP
|
||||
basic auth.
|
||||
"""
|
||||
code = 401
|
||||
description = (
|
||||
'The server could not verify that you are authorized to access '
|
||||
'the URL requested. You either supplied the wrong credentials (e.g. '
|
||||
'a bad password), or your browser doesn\'t understand how to supply '
|
||||
'the credentials required.'
|
||||
)
|
||||
|
||||
|
||||
class Forbidden(HTTPException):
|
||||
|
||||
"""*403* `Forbidden`
|
||||
|
||||
Raise if the user doesn't have the permission for the requested resource
|
||||
but was authenticated.
|
||||
"""
|
||||
code = 403
|
||||
description = (
|
||||
'You don\'t have the permission to access the requested resource. '
|
||||
'It is either read-protected or not readable by the server.'
|
||||
)
|
||||
|
||||
|
||||
class NotFound(HTTPException):
|
||||
|
||||
"""*404* `Not Found`
|
||||
|
||||
Raise if a resource does not exist and never existed.
|
||||
"""
|
||||
code = 404
|
||||
description = (
|
||||
'The requested URL was not found on the server. '
|
||||
'If you entered the URL manually please check your spelling and '
|
||||
'try again.'
|
||||
)
|
||||
|
||||
|
||||
class MethodNotAllowed(HTTPException):
|
||||
|
||||
"""*405* `Method Not Allowed`
|
||||
|
||||
Raise if the server used a method the resource does not handle. For
|
||||
example `POST` if the resource is view only. Especially useful for REST.
|
||||
|
||||
The first argument for this exception should be a list of allowed methods.
|
||||
Strictly speaking the response would be invalid if you don't provide valid
|
||||
methods in the header which you can do with that list.
|
||||
"""
|
||||
code = 405
|
||||
description = 'The method is not allowed for the requested URL.'
|
||||
|
||||
def __init__(self, valid_methods=None, description=None):
|
||||
"""Takes an optional list of valid http methods
|
||||
starting with werkzeug 0.3 the list will be mandatory."""
|
||||
HTTPException.__init__(self, description)
|
||||
self.valid_methods = valid_methods
|
||||
|
||||
def get_headers(self, environ):
|
||||
headers = HTTPException.get_headers(self, environ)
|
||||
if self.valid_methods:
|
||||
headers.append(('Allow', ', '.join(self.valid_methods)))
|
||||
return headers
|
||||
|
||||
|
||||
class NotAcceptable(HTTPException):
|
||||
|
||||
"""*406* `Not Acceptable`
|
||||
|
||||
Raise if the server can't return any content conforming to the
|
||||
`Accept` headers of the client.
|
||||
"""
|
||||
code = 406
|
||||
|
||||
description = (
|
||||
'The resource identified by the request is only capable of '
|
||||
'generating response entities which have content characteristics '
|
||||
'not acceptable according to the accept headers sent in the '
|
||||
'request.'
|
||||
)
|
||||
|
||||
|
||||
class RequestTimeout(HTTPException):
|
||||
|
||||
"""*408* `Request Timeout`
|
||||
|
||||
Raise to signalize a timeout.
|
||||
"""
|
||||
code = 408
|
||||
description = (
|
||||
'The server closed the network connection because the browser '
|
||||
'didn\'t finish the request within the specified time.'
|
||||
)
|
||||
|
||||
|
||||
class Conflict(HTTPException):
|
||||
|
||||
"""*409* `Conflict`
|
||||
|
||||
Raise to signal that a request cannot be completed because it conflicts
|
||||
with the current state on the server.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
code = 409
|
||||
description = (
|
||||
'A conflict happened while processing the request. The resource '
|
||||
'might have been modified while the request was being processed.'
|
||||
)
|
||||
|
||||
|
||||
class Gone(HTTPException):
|
||||
|
||||
"""*410* `Gone`
|
||||
|
||||
Raise if a resource existed previously and went away without new location.
|
||||
"""
|
||||
code = 410
|
||||
description = (
|
||||
'The requested URL is no longer available on this server and there '
|
||||
'is no forwarding address. If you followed a link from a foreign '
|
||||
'page, please contact the author of this page.'
|
||||
)
|
||||
|
||||
|
||||
class LengthRequired(HTTPException):
|
||||
|
||||
"""*411* `Length Required`
|
||||
|
||||
Raise if the browser submitted data but no ``Content-Length`` header which
|
||||
is required for the kind of processing the server does.
|
||||
"""
|
||||
code = 411
|
||||
description = (
|
||||
'A request with this method requires a valid <code>Content-'
|
||||
'Length</code> header.'
|
||||
)
|
||||
|
||||
|
||||
class PreconditionFailed(HTTPException):
|
||||
|
||||
"""*412* `Precondition Failed`
|
||||
|
||||
Status code used in combination with ``If-Match``, ``If-None-Match``, or
|
||||
``If-Unmodified-Since``.
|
||||
"""
|
||||
code = 412
|
||||
description = (
|
||||
'The precondition on the request for the URL failed positive '
|
||||
'evaluation.'
|
||||
)
|
||||
|
||||
|
||||
class RequestEntityTooLarge(HTTPException):
|
||||
|
||||
"""*413* `Request Entity Too Large`
|
||||
|
||||
The status code one should return if the data submitted exceeded a given
|
||||
limit.
|
||||
"""
|
||||
code = 413
|
||||
description = (
|
||||
'The data value transmitted exceeds the capacity limit.'
|
||||
)
|
||||
|
||||
|
||||
class RequestURITooLarge(HTTPException):
|
||||
|
||||
"""*414* `Request URI Too Large`
|
||||
|
||||
Like *413* but for too long URLs.
|
||||
"""
|
||||
code = 414
|
||||
description = (
|
||||
'The length of the requested URL exceeds the capacity limit '
|
||||
'for this server. The request cannot be processed.'
|
||||
)
|
||||
|
||||
|
||||
class UnsupportedMediaType(HTTPException):
|
||||
|
||||
"""*415* `Unsupported Media Type`
|
||||
|
||||
The status code returned if the server is unable to handle the media type
|
||||
the client transmitted.
|
||||
"""
|
||||
code = 415
|
||||
description = (
|
||||
'The server does not support the media type transmitted in '
|
||||
'the request.'
|
||||
)
|
||||
|
||||
|
||||
class RequestedRangeNotSatisfiable(HTTPException):
|
||||
|
||||
"""*416* `Requested Range Not Satisfiable`
|
||||
|
||||
The client asked for an invalid part of the file.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
code = 416
|
||||
description = (
|
||||
'The server cannot provide the requested range.'
|
||||
)
|
||||
|
||||
def __init__(self, length=None, units="bytes", description=None):
|
||||
"""Takes an optional `Content-Range` header value based on ``length``
|
||||
parameter.
|
||||
"""
|
||||
HTTPException.__init__(self, description)
|
||||
self.length = length
|
||||
self.units = units
|
||||
|
||||
def get_headers(self, environ):
|
||||
headers = HTTPException.get_headers(self, environ)
|
||||
if self.length is not None:
|
||||
headers.append(
|
||||
('Content-Range', '%s */%d' % (self.units, self.length)))
|
||||
return headers
|
||||
|
||||
|
||||
class ExpectationFailed(HTTPException):
|
||||
|
||||
"""*417* `Expectation Failed`
|
||||
|
||||
The server cannot meet the requirements of the Expect request-header.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
code = 417
|
||||
description = (
|
||||
'The server could not meet the requirements of the Expect header'
|
||||
)
|
||||
|
||||
|
||||
class ImATeapot(HTTPException):
|
||||
|
||||
"""*418* `I'm a teapot`
|
||||
|
||||
The server should return this if it is a teapot and someone attempted
|
||||
to brew coffee with it.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
code = 418
|
||||
description = (
|
||||
'This server is a teapot, not a coffee machine'
|
||||
)
|
||||
|
||||
|
||||
class UnprocessableEntity(HTTPException):
|
||||
|
||||
"""*422* `Unprocessable Entity`
|
||||
|
||||
Used if the request is well formed, but the instructions are otherwise
|
||||
incorrect.
|
||||
"""
|
||||
code = 422
|
||||
description = (
|
||||
'The request was well-formed but was unable to be followed '
|
||||
'due to semantic errors.'
|
||||
)
|
||||
|
||||
|
||||
class Locked(HTTPException):
|
||||
|
||||
"""*423* `Locked`
|
||||
|
||||
Used if the resource that is being accessed is locked.
|
||||
"""
|
||||
code = 423
|
||||
description = (
|
||||
'The resource that is being accessed is locked.'
|
||||
)
|
||||
|
||||
|
||||
class PreconditionRequired(HTTPException):
|
||||
|
||||
"""*428* `Precondition Required`
|
||||
|
||||
The server requires this request to be conditional, typically to prevent
|
||||
the lost update problem, which is a race condition between two or more
|
||||
clients attempting to update a resource through PUT or DELETE. By requiring
|
||||
each client to include a conditional header ("If-Match" or "If-Unmodified-
|
||||
Since") with the proper value retained from a recent GET request, the
|
||||
server ensures that each client has at least seen the previous revision of
|
||||
the resource.
|
||||
"""
|
||||
code = 428
|
||||
description = (
|
||||
'This request is required to be conditional; try using "If-Match" '
|
||||
'or "If-Unmodified-Since".'
|
||||
)
|
||||
|
||||
|
||||
class TooManyRequests(HTTPException):
|
||||
|
||||
"""*429* `Too Many Requests`
|
||||
|
||||
The server is limiting the rate at which this user receives responses, and
|
||||
this request exceeds that rate. (The server may use any convenient method
|
||||
to identify users and their request rates). The server may include a
|
||||
"Retry-After" header to indicate how long the user should wait before
|
||||
retrying.
|
||||
"""
|
||||
code = 429
|
||||
description = (
|
||||
'This user has exceeded an allotted request count. Try again later.'
|
||||
)
|
||||
|
||||
|
||||
class RequestHeaderFieldsTooLarge(HTTPException):
|
||||
|
||||
"""*431* `Request Header Fields Too Large`
|
||||
|
||||
The server refuses to process the request because the header fields are too
|
||||
large. One or more individual fields may be too large, or the set of all
|
||||
headers is too large.
|
||||
"""
|
||||
code = 431
|
||||
description = (
|
||||
'One or more header fields exceeds the maximum size.'
|
||||
)
|
||||
|
||||
|
||||
class UnavailableForLegalReasons(HTTPException):
|
||||
|
||||
"""*451* `Unavailable For Legal Reasons`
|
||||
|
||||
This status code indicates that the server is denying access to the
|
||||
resource as a consequence of a legal demand.
|
||||
"""
|
||||
code = 451
|
||||
description = (
|
||||
'Unavailable for legal reasons.'
|
||||
)
|
||||
|
||||
|
||||
class InternalServerError(HTTPException):
|
||||
|
||||
"""*500* `Internal Server Error`
|
||||
|
||||
Raise if an internal server error occurred. This is a good fallback if an
|
||||
unknown error occurred in the dispatcher.
|
||||
"""
|
||||
code = 500
|
||||
description = (
|
||||
'The server encountered an internal error and was unable to '
|
||||
'complete your request. Either the server is overloaded or there '
|
||||
'is an error in the application.'
|
||||
)
|
||||
|
||||
|
||||
class NotImplemented(HTTPException):
|
||||
|
||||
"""*501* `Not Implemented`
|
||||
|
||||
Raise if the application does not support the action requested by the
|
||||
browser.
|
||||
"""
|
||||
code = 501
|
||||
description = (
|
||||
'The server does not support the action requested by the '
|
||||
'browser.'
|
||||
)
|
||||
|
||||
|
||||
class BadGateway(HTTPException):
|
||||
|
||||
"""*502* `Bad Gateway`
|
||||
|
||||
If you do proxying in your application you should return this status code
|
||||
if you received an invalid response from the upstream server it accessed
|
||||
in attempting to fulfill the request.
|
||||
"""
|
||||
code = 502
|
||||
description = (
|
||||
'The proxy server received an invalid response from an upstream '
|
||||
'server.'
|
||||
)
|
||||
|
||||
|
||||
class ServiceUnavailable(HTTPException):
|
||||
|
||||
"""*503* `Service Unavailable`
|
||||
|
||||
Status code you should return if a service is temporarily unavailable.
|
||||
"""
|
||||
code = 503
|
||||
description = (
|
||||
'The server is temporarily unable to service your request due to '
|
||||
'maintenance downtime or capacity problems. Please try again '
|
||||
'later.'
|
||||
)
|
||||
|
||||
|
||||
class GatewayTimeout(HTTPException):
|
||||
|
||||
"""*504* `Gateway Timeout`
|
||||
|
||||
Status code you should return if a connection to an upstream server
|
||||
times out.
|
||||
"""
|
||||
code = 504
|
||||
description = (
|
||||
'The connection to an upstream server timed out.'
|
||||
)
|
||||
|
||||
|
||||
class HTTPVersionNotSupported(HTTPException):
|
||||
|
||||
"""*505* `HTTP Version Not Supported`
|
||||
|
||||
The server does not support the HTTP protocol version used in the request.
|
||||
"""
|
||||
code = 505
|
||||
description = (
|
||||
'The server does not support the HTTP protocol version used in the '
|
||||
'request.'
|
||||
)
|
||||
|
||||
|
||||
default_exceptions = {}
|
||||
__all__ = ['HTTPException']
|
||||
|
||||
|
||||
def _find_exceptions():
|
||||
for name, obj in iteritems(globals()):
|
||||
try:
|
||||
is_http_exception = issubclass(obj, HTTPException)
|
||||
except TypeError:
|
||||
is_http_exception = False
|
||||
if not is_http_exception or obj.code is None:
|
||||
continue
|
||||
__all__.append(obj.__name__)
|
||||
old_obj = default_exceptions.get(obj.code, None)
|
||||
if old_obj is not None and issubclass(obj, old_obj):
|
||||
continue
|
||||
default_exceptions[obj.code] = obj
|
||||
_find_exceptions()
|
||||
del _find_exceptions
|
||||
|
||||
|
||||
class Aborter(object):
|
||||
|
||||
"""
|
||||
When passed a dict of code -> exception items it can be used as
|
||||
callable that raises exceptions. If the first argument to the
|
||||
callable is an integer it will be looked up in the mapping, if it's
|
||||
a WSGI application it will be raised in a proxy exception.
|
||||
|
||||
The rest of the arguments are forwarded to the exception constructor.
|
||||
"""
|
||||
|
||||
def __init__(self, mapping=None, extra=None):
|
||||
if mapping is None:
|
||||
mapping = default_exceptions
|
||||
self.mapping = dict(mapping)
|
||||
if extra is not None:
|
||||
self.mapping.update(extra)
|
||||
|
||||
def __call__(self, code, *args, **kwargs):
|
||||
if not args and not kwargs and not isinstance(code, integer_types):
|
||||
raise HTTPException(response=code)
|
||||
if code not in self.mapping:
|
||||
raise LookupError('no exception for %r' % code)
|
||||
raise self.mapping[code](*args, **kwargs)
|
||||
|
||||
|
||||
def abort(status, *args, **kwargs):
|
||||
'''
|
||||
Raises an :py:exc:`HTTPException` for the given status code or WSGI
|
||||
application::
|
||||
|
||||
abort(404) # 404 Not Found
|
||||
abort(Response('Hello World'))
|
||||
|
||||
Can be passed a WSGI application or a status code. If a status code is
|
||||
given it's looked up in the list of exceptions and will raise that
|
||||
exception, if passed a WSGI application it will wrap it in a proxy WSGI
|
||||
exception and raise that::
|
||||
|
||||
abort(404)
|
||||
abort(Response('Hello World'))
|
||||
|
||||
'''
|
||||
return _aborter(status, *args, **kwargs)
|
||||
|
||||
_aborter = Aborter()
|
||||
|
||||
|
||||
#: an exception that is used internally to signal both a key error and a
|
||||
#: bad request. Used by a lot of the datastructures.
|
||||
BadRequestKeyError = BadRequest.wrap(KeyError)
|
||||
|
||||
|
||||
# imported here because of circular dependencies of werkzeug.utils
|
||||
from werkzeug.utils import escape
|
||||
from werkzeug.http import HTTP_STATUS_CODES
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/exceptions.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/exceptions.pyc
Normal file
Binary file not shown.
66
venv/lib/python2.7/site-packages/werkzeug/filesystem.py
Normal file
66
venv/lib/python2.7/site-packages/werkzeug/filesystem.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.filesystem
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Various utilities for the local filesystem.
|
||||
|
||||
:copyright: (c) 2015 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import codecs
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
# We do not trust traditional unixes.
|
||||
has_likely_buggy_unicode_filesystem = \
|
||||
sys.platform.startswith('linux') or 'bsd' in sys.platform
|
||||
|
||||
|
||||
def _is_ascii_encoding(encoding):
|
||||
"""
|
||||
Given an encoding this figures out if the encoding is actually ASCII (which
|
||||
is something we don't actually want in most cases). This is necessary
|
||||
because ASCII comes under many names such as ANSI_X3.4-1968.
|
||||
"""
|
||||
if encoding is None:
|
||||
return False
|
||||
try:
|
||||
return codecs.lookup(encoding).name == 'ascii'
|
||||
except LookupError:
|
||||
return False
|
||||
|
||||
|
||||
class BrokenFilesystemWarning(RuntimeWarning, UnicodeWarning):
|
||||
'''The warning used by Werkzeug to signal a broken filesystem. Will only be
|
||||
used once per runtime.'''
|
||||
|
||||
|
||||
_warned_about_filesystem_encoding = False
|
||||
|
||||
|
||||
def get_filesystem_encoding():
|
||||
"""
|
||||
Returns the filesystem encoding that should be used. Note that this is
|
||||
different from the Python understanding of the filesystem encoding which
|
||||
might be deeply flawed. Do not use this value against Python's unicode APIs
|
||||
because it might be different. See :ref:`filesystem-encoding` for the exact
|
||||
behavior.
|
||||
|
||||
The concept of a filesystem encoding in generally is not something you
|
||||
should rely on. As such if you ever need to use this function except for
|
||||
writing wrapper code reconsider.
|
||||
"""
|
||||
global _warned_about_filesystem_encoding
|
||||
rv = sys.getfilesystemencoding()
|
||||
if has_likely_buggy_unicode_filesystem and not rv \
|
||||
or _is_ascii_encoding(rv):
|
||||
if not _warned_about_filesystem_encoding:
|
||||
warnings.warn(
|
||||
'Detected a misconfigured UNIX filesystem: Will use UTF-8 as '
|
||||
'filesystem encoding instead of {0!r}'.format(rv),
|
||||
BrokenFilesystemWarning)
|
||||
_warned_about_filesystem_encoding = True
|
||||
return 'utf-8'
|
||||
return rv
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/filesystem.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/filesystem.pyc
Normal file
Binary file not shown.
522
venv/lib/python2.7/site-packages/werkzeug/formparser.py
Normal file
522
venv/lib/python2.7/site-packages/werkzeug/formparser.py
Normal file
@@ -0,0 +1,522 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.formparser
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module implements the form parsing. It supports url-encoded forms
|
||||
as well as non-nested multipart uploads.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import codecs
|
||||
from io import BytesIO
|
||||
from tempfile import TemporaryFile
|
||||
from itertools import chain, repeat, tee
|
||||
from functools import update_wrapper
|
||||
|
||||
from werkzeug._compat import to_native, text_type
|
||||
from werkzeug.urls import url_decode_stream
|
||||
from werkzeug.wsgi import make_line_iter, \
|
||||
get_input_stream, get_content_length
|
||||
from werkzeug.datastructures import Headers, FileStorage, MultiDict
|
||||
from werkzeug.http import parse_options_header
|
||||
|
||||
|
||||
#: an iterator that yields empty strings
|
||||
_empty_string_iter = repeat('')
|
||||
|
||||
#: a regular expression for multipart boundaries
|
||||
_multipart_boundary_re = re.compile('^[ -~]{0,200}[!-~]$')
|
||||
|
||||
#: supported http encodings that are also available in python we support
|
||||
#: for multipart messages.
|
||||
_supported_multipart_encodings = frozenset(['base64', 'quoted-printable'])
|
||||
|
||||
|
||||
def default_stream_factory(total_content_length, filename, content_type,
|
||||
content_length=None):
|
||||
"""The stream factory that is used per default."""
|
||||
if total_content_length > 1024 * 500:
|
||||
return TemporaryFile('wb+')
|
||||
return BytesIO()
|
||||
|
||||
|
||||
def parse_form_data(environ, stream_factory=None, charset='utf-8',
|
||||
errors='replace', max_form_memory_size=None,
|
||||
max_content_length=None, cls=None,
|
||||
silent=True):
|
||||
"""Parse the form data in the environ and return it as tuple in the form
|
||||
``(stream, form, files)``. You should only call this method if the
|
||||
transport method is `POST`, `PUT`, or `PATCH`.
|
||||
|
||||
If the mimetype of the data transmitted is `multipart/form-data` the
|
||||
files multidict will be filled with `FileStorage` objects. If the
|
||||
mimetype is unknown the input stream is wrapped and returned as first
|
||||
argument, else the stream is empty.
|
||||
|
||||
This is a shortcut for the common usage of :class:`FormDataParser`.
|
||||
|
||||
Have a look at :ref:`dealing-with-request-data` for more details.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
The `max_form_memory_size`, `max_content_length` and
|
||||
`cls` parameters were added.
|
||||
|
||||
.. versionadded:: 0.5.1
|
||||
The optional `silent` flag was added.
|
||||
|
||||
:param environ: the WSGI environment to be used for parsing.
|
||||
:param stream_factory: An optional callable that returns a new read and
|
||||
writeable file descriptor. This callable works
|
||||
the same as :meth:`~BaseResponse._get_file_stream`.
|
||||
:param charset: The character set for URL and url encoded form data.
|
||||
:param errors: The encoding error behavior.
|
||||
:param max_form_memory_size: the maximum number of bytes to be accepted for
|
||||
in-memory stored form data. If the data
|
||||
exceeds the value specified an
|
||||
:exc:`~exceptions.RequestEntityTooLarge`
|
||||
exception is raised.
|
||||
:param max_content_length: If this is provided and the transmitted data
|
||||
is longer than this value an
|
||||
:exc:`~exceptions.RequestEntityTooLarge`
|
||||
exception is raised.
|
||||
:param cls: an optional dict class to use. If this is not specified
|
||||
or `None` the default :class:`MultiDict` is used.
|
||||
:param silent: If set to False parsing errors will not be caught.
|
||||
:return: A tuple in the form ``(stream, form, files)``.
|
||||
"""
|
||||
return FormDataParser(stream_factory, charset, errors,
|
||||
max_form_memory_size, max_content_length,
|
||||
cls, silent).parse_from_environ(environ)
|
||||
|
||||
|
||||
def exhaust_stream(f):
|
||||
"""Helper decorator for methods that exhausts the stream on return."""
|
||||
|
||||
def wrapper(self, stream, *args, **kwargs):
|
||||
try:
|
||||
return f(self, stream, *args, **kwargs)
|
||||
finally:
|
||||
exhaust = getattr(stream, 'exhaust', None)
|
||||
if exhaust is not None:
|
||||
exhaust()
|
||||
else:
|
||||
while 1:
|
||||
chunk = stream.read(1024 * 64)
|
||||
if not chunk:
|
||||
break
|
||||
return update_wrapper(wrapper, f)
|
||||
|
||||
|
||||
class FormDataParser(object):
|
||||
|
||||
"""This class implements parsing of form data for Werkzeug. By itself
|
||||
it can parse multipart and url encoded form data. It can be subclassed
|
||||
and extended but for most mimetypes it is a better idea to use the
|
||||
untouched stream and expose it as separate attributes on a request
|
||||
object.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
:param stream_factory: An optional callable that returns a new read and
|
||||
writeable file descriptor. This callable works
|
||||
the same as :meth:`~BaseResponse._get_file_stream`.
|
||||
:param charset: The character set for URL and url encoded form data.
|
||||
:param errors: The encoding error behavior.
|
||||
:param max_form_memory_size: the maximum number of bytes to be accepted for
|
||||
in-memory stored form data. If the data
|
||||
exceeds the value specified an
|
||||
:exc:`~exceptions.RequestEntityTooLarge`
|
||||
exception is raised.
|
||||
:param max_content_length: If this is provided and the transmitted data
|
||||
is longer than this value an
|
||||
:exc:`~exceptions.RequestEntityTooLarge`
|
||||
exception is raised.
|
||||
:param cls: an optional dict class to use. If this is not specified
|
||||
or `None` the default :class:`MultiDict` is used.
|
||||
:param silent: If set to False parsing errors will not be caught.
|
||||
"""
|
||||
|
||||
def __init__(self, stream_factory=None, charset='utf-8',
|
||||
errors='replace', max_form_memory_size=None,
|
||||
max_content_length=None, cls=None,
|
||||
silent=True):
|
||||
if stream_factory is None:
|
||||
stream_factory = default_stream_factory
|
||||
self.stream_factory = stream_factory
|
||||
self.charset = charset
|
||||
self.errors = errors
|
||||
self.max_form_memory_size = max_form_memory_size
|
||||
self.max_content_length = max_content_length
|
||||
if cls is None:
|
||||
cls = MultiDict
|
||||
self.cls = cls
|
||||
self.silent = silent
|
||||
|
||||
def get_parse_func(self, mimetype, options):
|
||||
return self.parse_functions.get(mimetype)
|
||||
|
||||
def parse_from_environ(self, environ):
|
||||
"""Parses the information from the environment as form data.
|
||||
|
||||
:param environ: the WSGI environment to be used for parsing.
|
||||
:return: A tuple in the form ``(stream, form, files)``.
|
||||
"""
|
||||
content_type = environ.get('CONTENT_TYPE', '')
|
||||
content_length = get_content_length(environ)
|
||||
mimetype, options = parse_options_header(content_type)
|
||||
return self.parse(get_input_stream(environ), mimetype,
|
||||
content_length, options)
|
||||
|
||||
def parse(self, stream, mimetype, content_length, options=None):
|
||||
"""Parses the information from the given stream, mimetype,
|
||||
content length and mimetype parameters.
|
||||
|
||||
:param stream: an input stream
|
||||
:param mimetype: the mimetype of the data
|
||||
:param content_length: the content length of the incoming data
|
||||
:param options: optional mimetype parameters (used for
|
||||
the multipart boundary for instance)
|
||||
:return: A tuple in the form ``(stream, form, files)``.
|
||||
"""
|
||||
if self.max_content_length is not None and \
|
||||
content_length is not None and \
|
||||
content_length > self.max_content_length:
|
||||
raise exceptions.RequestEntityTooLarge()
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
parse_func = self.get_parse_func(mimetype, options)
|
||||
if parse_func is not None:
|
||||
try:
|
||||
return parse_func(self, stream, mimetype,
|
||||
content_length, options)
|
||||
except ValueError:
|
||||
if not self.silent:
|
||||
raise
|
||||
|
||||
return stream, self.cls(), self.cls()
|
||||
|
||||
@exhaust_stream
|
||||
def _parse_multipart(self, stream, mimetype, content_length, options):
|
||||
parser = MultiPartParser(self.stream_factory, self.charset, self.errors,
|
||||
max_form_memory_size=self.max_form_memory_size,
|
||||
cls=self.cls)
|
||||
boundary = options.get('boundary')
|
||||
if boundary is None:
|
||||
raise ValueError('Missing boundary')
|
||||
if isinstance(boundary, text_type):
|
||||
boundary = boundary.encode('ascii')
|
||||
form, files = parser.parse(stream, boundary, content_length)
|
||||
return stream, form, files
|
||||
|
||||
@exhaust_stream
|
||||
def _parse_urlencoded(self, stream, mimetype, content_length, options):
|
||||
if self.max_form_memory_size is not None and \
|
||||
content_length is not None and \
|
||||
content_length > self.max_form_memory_size:
|
||||
raise exceptions.RequestEntityTooLarge()
|
||||
form = url_decode_stream(stream, self.charset,
|
||||
errors=self.errors, cls=self.cls)
|
||||
return stream, form, self.cls()
|
||||
|
||||
#: mapping of mimetypes to parsing functions
|
||||
parse_functions = {
|
||||
'multipart/form-data': _parse_multipart,
|
||||
'application/x-www-form-urlencoded': _parse_urlencoded,
|
||||
'application/x-url-encoded': _parse_urlencoded
|
||||
}
|
||||
|
||||
|
||||
def is_valid_multipart_boundary(boundary):
|
||||
"""Checks if the string given is a valid multipart boundary."""
|
||||
return _multipart_boundary_re.match(boundary) is not None
|
||||
|
||||
|
||||
def _line_parse(line):
|
||||
"""Removes line ending characters and returns a tuple (`stripped_line`,
|
||||
`is_terminated`).
|
||||
"""
|
||||
if line[-2:] in ['\r\n', b'\r\n']:
|
||||
return line[:-2], True
|
||||
elif line[-1:] in ['\r', '\n', b'\r', b'\n']:
|
||||
return line[:-1], True
|
||||
return line, False
|
||||
|
||||
|
||||
def parse_multipart_headers(iterable):
|
||||
"""Parses multipart headers from an iterable that yields lines (including
|
||||
the trailing newline symbol). The iterable has to be newline terminated.
|
||||
|
||||
The iterable will stop at the line where the headers ended so it can be
|
||||
further consumed.
|
||||
|
||||
:param iterable: iterable of strings that are newline terminated
|
||||
"""
|
||||
result = []
|
||||
for line in iterable:
|
||||
line = to_native(line)
|
||||
line, line_terminated = _line_parse(line)
|
||||
if not line_terminated:
|
||||
raise ValueError('unexpected end of line in multipart header')
|
||||
if not line:
|
||||
break
|
||||
elif line[0] in ' \t' and result:
|
||||
key, value = result[-1]
|
||||
result[-1] = (key, value + '\n ' + line[1:])
|
||||
else:
|
||||
parts = line.split(':', 1)
|
||||
if len(parts) == 2:
|
||||
result.append((parts[0].strip(), parts[1].strip()))
|
||||
|
||||
# we link the list to the headers, no need to create a copy, the
|
||||
# list was not shared anyways.
|
||||
return Headers(result)
|
||||
|
||||
|
||||
_begin_form = 'begin_form'
|
||||
_begin_file = 'begin_file'
|
||||
_cont = 'cont'
|
||||
_end = 'end'
|
||||
|
||||
|
||||
class MultiPartParser(object):
|
||||
|
||||
def __init__(self, stream_factory=None, charset='utf-8', errors='replace',
|
||||
max_form_memory_size=None, cls=None, buffer_size=64 * 1024):
|
||||
self.charset = charset
|
||||
self.errors = errors
|
||||
self.max_form_memory_size = max_form_memory_size
|
||||
self.stream_factory = default_stream_factory if stream_factory is None else stream_factory
|
||||
self.cls = MultiDict if cls is None else cls
|
||||
|
||||
# make sure the buffer size is divisible by four so that we can base64
|
||||
# decode chunk by chunk
|
||||
assert buffer_size % 4 == 0, 'buffer size has to be divisible by 4'
|
||||
# also the buffer size has to be at least 1024 bytes long or long headers
|
||||
# will freak out the system
|
||||
assert buffer_size >= 1024, 'buffer size has to be at least 1KB'
|
||||
|
||||
self.buffer_size = buffer_size
|
||||
|
||||
def _fix_ie_filename(self, filename):
|
||||
"""Internet Explorer 6 transmits the full file name if a file is
|
||||
uploaded. This function strips the full path if it thinks the
|
||||
filename is Windows-like absolute.
|
||||
"""
|
||||
if filename[1:3] == ':\\' or filename[:2] == '\\\\':
|
||||
return filename.split('\\')[-1]
|
||||
return filename
|
||||
|
||||
def _find_terminator(self, iterator):
|
||||
"""The terminator might have some additional newlines before it.
|
||||
There is at least one application that sends additional newlines
|
||||
before headers (the python setuptools package).
|
||||
"""
|
||||
for line in iterator:
|
||||
if not line:
|
||||
break
|
||||
line = line.strip()
|
||||
if line:
|
||||
return line
|
||||
return b''
|
||||
|
||||
def fail(self, message):
|
||||
raise ValueError(message)
|
||||
|
||||
def get_part_encoding(self, headers):
|
||||
transfer_encoding = headers.get('content-transfer-encoding')
|
||||
if transfer_encoding is not None and \
|
||||
transfer_encoding in _supported_multipart_encodings:
|
||||
return transfer_encoding
|
||||
|
||||
def get_part_charset(self, headers):
|
||||
# Figure out input charset for current part
|
||||
content_type = headers.get('content-type')
|
||||
if content_type:
|
||||
mimetype, ct_params = parse_options_header(content_type)
|
||||
return ct_params.get('charset', self.charset)
|
||||
return self.charset
|
||||
|
||||
def start_file_streaming(self, filename, headers, total_content_length):
|
||||
if isinstance(filename, bytes):
|
||||
filename = filename.decode(self.charset, self.errors)
|
||||
filename = self._fix_ie_filename(filename)
|
||||
content_type = headers.get('content-type')
|
||||
try:
|
||||
content_length = int(headers['content-length'])
|
||||
except (KeyError, ValueError):
|
||||
content_length = 0
|
||||
container = self.stream_factory(total_content_length, content_type,
|
||||
filename, content_length)
|
||||
return filename, container
|
||||
|
||||
def in_memory_threshold_reached(self, bytes):
|
||||
raise exceptions.RequestEntityTooLarge()
|
||||
|
||||
def validate_boundary(self, boundary):
|
||||
if not boundary:
|
||||
self.fail('Missing boundary')
|
||||
if not is_valid_multipart_boundary(boundary):
|
||||
self.fail('Invalid boundary: %s' % boundary)
|
||||
if len(boundary) > self.buffer_size: # pragma: no cover
|
||||
# this should never happen because we check for a minimum size
|
||||
# of 1024 and boundaries may not be longer than 200. The only
|
||||
# situation when this happens is for non debug builds where
|
||||
# the assert is skipped.
|
||||
self.fail('Boundary longer than buffer size')
|
||||
|
||||
def parse_lines(self, file, boundary, content_length, cap_at_buffer=True):
|
||||
"""Generate parts of
|
||||
``('begin_form', (headers, name))``
|
||||
``('begin_file', (headers, name, filename))``
|
||||
``('cont', bytestring)``
|
||||
``('end', None)``
|
||||
|
||||
Always obeys the grammar
|
||||
parts = ( begin_form cont* end |
|
||||
begin_file cont* end )*
|
||||
"""
|
||||
next_part = b'--' + boundary
|
||||
last_part = next_part + b'--'
|
||||
|
||||
iterator = chain(make_line_iter(file, limit=content_length,
|
||||
buffer_size=self.buffer_size,
|
||||
cap_at_buffer=cap_at_buffer),
|
||||
_empty_string_iter)
|
||||
|
||||
terminator = self._find_terminator(iterator)
|
||||
|
||||
if terminator == last_part:
|
||||
return
|
||||
elif terminator != next_part:
|
||||
self.fail('Expected boundary at start of multipart data')
|
||||
|
||||
while terminator != last_part:
|
||||
headers = parse_multipart_headers(iterator)
|
||||
|
||||
disposition = headers.get('content-disposition')
|
||||
if disposition is None:
|
||||
self.fail('Missing Content-Disposition header')
|
||||
disposition, extra = parse_options_header(disposition)
|
||||
transfer_encoding = self.get_part_encoding(headers)
|
||||
name = extra.get('name')
|
||||
filename = extra.get('filename')
|
||||
|
||||
# if no content type is given we stream into memory. A list is
|
||||
# used as a temporary container.
|
||||
if filename is None:
|
||||
yield _begin_form, (headers, name)
|
||||
|
||||
# otherwise we parse the rest of the headers and ask the stream
|
||||
# factory for something we can write in.
|
||||
else:
|
||||
yield _begin_file, (headers, name, filename)
|
||||
|
||||
buf = b''
|
||||
for line in iterator:
|
||||
if not line:
|
||||
self.fail('unexpected end of stream')
|
||||
|
||||
if line[:2] == b'--':
|
||||
terminator = line.rstrip()
|
||||
if terminator in (next_part, last_part):
|
||||
break
|
||||
|
||||
if transfer_encoding is not None:
|
||||
if transfer_encoding == 'base64':
|
||||
transfer_encoding = 'base64_codec'
|
||||
try:
|
||||
line = codecs.decode(line, transfer_encoding)
|
||||
except Exception:
|
||||
self.fail('could not decode transfer encoded chunk')
|
||||
|
||||
# we have something in the buffer from the last iteration.
|
||||
# this is usually a newline delimiter.
|
||||
if buf:
|
||||
yield _cont, buf
|
||||
buf = b''
|
||||
|
||||
# If the line ends with windows CRLF we write everything except
|
||||
# the last two bytes. In all other cases however we write
|
||||
# everything except the last byte. If it was a newline, that's
|
||||
# fine, otherwise it does not matter because we will write it
|
||||
# the next iteration. this ensures we do not write the
|
||||
# final newline into the stream. That way we do not have to
|
||||
# truncate the stream. However we do have to make sure that
|
||||
# if something else than a newline is in there we write it
|
||||
# out.
|
||||
if line[-2:] == b'\r\n':
|
||||
buf = b'\r\n'
|
||||
cutoff = -2
|
||||
else:
|
||||
buf = line[-1:]
|
||||
cutoff = -1
|
||||
yield _cont, line[:cutoff]
|
||||
|
||||
else: # pragma: no cover
|
||||
raise ValueError('unexpected end of part')
|
||||
|
||||
# if we have a leftover in the buffer that is not a newline
|
||||
# character we have to flush it, otherwise we will chop of
|
||||
# certain values.
|
||||
if buf not in (b'', b'\r', b'\n', b'\r\n'):
|
||||
yield _cont, buf
|
||||
|
||||
yield _end, None
|
||||
|
||||
def parse_parts(self, file, boundary, content_length):
|
||||
"""Generate ``('file', (name, val))`` and
|
||||
``('form', (name, val))`` parts.
|
||||
"""
|
||||
in_memory = 0
|
||||
|
||||
for ellt, ell in self.parse_lines(file, boundary, content_length):
|
||||
if ellt == _begin_file:
|
||||
headers, name, filename = ell
|
||||
is_file = True
|
||||
guard_memory = False
|
||||
filename, container = self.start_file_streaming(
|
||||
filename, headers, content_length)
|
||||
_write = container.write
|
||||
|
||||
elif ellt == _begin_form:
|
||||
headers, name = ell
|
||||
is_file = False
|
||||
container = []
|
||||
_write = container.append
|
||||
guard_memory = self.max_form_memory_size is not None
|
||||
|
||||
elif ellt == _cont:
|
||||
_write(ell)
|
||||
# if we write into memory and there is a memory size limit we
|
||||
# count the number of bytes in memory and raise an exception if
|
||||
# there is too much data in memory.
|
||||
if guard_memory:
|
||||
in_memory += len(ell)
|
||||
if in_memory > self.max_form_memory_size:
|
||||
self.in_memory_threshold_reached(in_memory)
|
||||
|
||||
elif ellt == _end:
|
||||
if is_file:
|
||||
container.seek(0)
|
||||
yield ('file',
|
||||
(name, FileStorage(container, filename, name,
|
||||
headers=headers)))
|
||||
else:
|
||||
part_charset = self.get_part_charset(headers)
|
||||
yield ('form',
|
||||
(name, b''.join(container).decode(
|
||||
part_charset, self.errors)))
|
||||
|
||||
def parse(self, file, boundary, content_length):
|
||||
formstream, filestream = tee(
|
||||
self.parse_parts(file, boundary, content_length), 2)
|
||||
form = (p[1] for p in formstream if p[0] == 'form')
|
||||
files = (p[1] for p in filestream if p[0] == 'file')
|
||||
return self.cls(form), self.cls(files)
|
||||
|
||||
|
||||
from werkzeug import exceptions
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/formparser.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/formparser.pyc
Normal file
Binary file not shown.
1054
venv/lib/python2.7/site-packages/werkzeug/http.py
Normal file
1054
venv/lib/python2.7/site-packages/werkzeug/http.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
venv/lib/python2.7/site-packages/werkzeug/http.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/http.pyc
Normal file
Binary file not shown.
420
venv/lib/python2.7/site-packages/werkzeug/local.py
Normal file
420
venv/lib/python2.7/site-packages/werkzeug/local.py
Normal file
@@ -0,0 +1,420 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.local
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module implements context-local objects.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import copy
|
||||
from functools import update_wrapper
|
||||
from werkzeug.wsgi import ClosingIterator
|
||||
from werkzeug._compat import PY2, implements_bool
|
||||
|
||||
# since each thread has its own greenlet we can just use those as identifiers
|
||||
# for the context. If greenlets are not available we fall back to the
|
||||
# current thread ident depending on where it is.
|
||||
try:
|
||||
from greenlet import getcurrent as get_ident
|
||||
except ImportError:
|
||||
try:
|
||||
from thread import get_ident
|
||||
except ImportError:
|
||||
from _thread import get_ident
|
||||
|
||||
|
||||
def release_local(local):
|
||||
"""Releases the contents of the local for the current context.
|
||||
This makes it possible to use locals without a manager.
|
||||
|
||||
Example::
|
||||
|
||||
>>> loc = Local()
|
||||
>>> loc.foo = 42
|
||||
>>> release_local(loc)
|
||||
>>> hasattr(loc, 'foo')
|
||||
False
|
||||
|
||||
With this function one can release :class:`Local` objects as well
|
||||
as :class:`LocalStack` objects. However it is not possible to
|
||||
release data held by proxies that way, one always has to retain
|
||||
a reference to the underlying local object in order to be able
|
||||
to release it.
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
"""
|
||||
local.__release_local__()
|
||||
|
||||
|
||||
class Local(object):
|
||||
__slots__ = ('__storage__', '__ident_func__')
|
||||
|
||||
def __init__(self):
|
||||
object.__setattr__(self, '__storage__', {})
|
||||
object.__setattr__(self, '__ident_func__', get_ident)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__storage__.items())
|
||||
|
||||
def __call__(self, proxy):
|
||||
"""Create a proxy for a name."""
|
||||
return LocalProxy(self, proxy)
|
||||
|
||||
def __release_local__(self):
|
||||
self.__storage__.pop(self.__ident_func__(), None)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.__storage__[self.__ident_func__()][name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
ident = self.__ident_func__()
|
||||
storage = self.__storage__
|
||||
try:
|
||||
storage[ident][name] = value
|
||||
except KeyError:
|
||||
storage[ident] = {name: value}
|
||||
|
||||
def __delattr__(self, name):
|
||||
try:
|
||||
del self.__storage__[self.__ident_func__()][name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
class LocalStack(object):
|
||||
|
||||
"""This class works similar to a :class:`Local` but keeps a stack
|
||||
of objects instead. This is best explained with an example::
|
||||
|
||||
>>> ls = LocalStack()
|
||||
>>> ls.push(42)
|
||||
>>> ls.top
|
||||
42
|
||||
>>> ls.push(23)
|
||||
>>> ls.top
|
||||
23
|
||||
>>> ls.pop()
|
||||
23
|
||||
>>> ls.top
|
||||
42
|
||||
|
||||
They can be force released by using a :class:`LocalManager` or with
|
||||
the :func:`release_local` function but the correct way is to pop the
|
||||
item from the stack after using. When the stack is empty it will
|
||||
no longer be bound to the current context (and as such released).
|
||||
|
||||
By calling the stack without arguments it returns a proxy that resolves to
|
||||
the topmost item on the stack.
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._local = Local()
|
||||
|
||||
def __release_local__(self):
|
||||
self._local.__release_local__()
|
||||
|
||||
def _get__ident_func__(self):
|
||||
return self._local.__ident_func__
|
||||
|
||||
def _set__ident_func__(self, value):
|
||||
object.__setattr__(self._local, '__ident_func__', value)
|
||||
__ident_func__ = property(_get__ident_func__, _set__ident_func__)
|
||||
del _get__ident_func__, _set__ident_func__
|
||||
|
||||
def __call__(self):
|
||||
def _lookup():
|
||||
rv = self.top
|
||||
if rv is None:
|
||||
raise RuntimeError('object unbound')
|
||||
return rv
|
||||
return LocalProxy(_lookup)
|
||||
|
||||
def push(self, obj):
|
||||
"""Pushes a new item to the stack"""
|
||||
rv = getattr(self._local, 'stack', None)
|
||||
if rv is None:
|
||||
self._local.stack = rv = []
|
||||
rv.append(obj)
|
||||
return rv
|
||||
|
||||
def pop(self):
|
||||
"""Removes the topmost item from the stack, will return the
|
||||
old value or `None` if the stack was already empty.
|
||||
"""
|
||||
stack = getattr(self._local, 'stack', None)
|
||||
if stack is None:
|
||||
return None
|
||||
elif len(stack) == 1:
|
||||
release_local(self._local)
|
||||
return stack[-1]
|
||||
else:
|
||||
return stack.pop()
|
||||
|
||||
@property
|
||||
def top(self):
|
||||
"""The topmost item on the stack. If the stack is empty,
|
||||
`None` is returned.
|
||||
"""
|
||||
try:
|
||||
return self._local.stack[-1]
|
||||
except (AttributeError, IndexError):
|
||||
return None
|
||||
|
||||
|
||||
class LocalManager(object):
|
||||
|
||||
"""Local objects cannot manage themselves. For that you need a local
|
||||
manager. You can pass a local manager multiple locals or add them later
|
||||
by appending them to `manager.locals`. Every time the manager cleans up,
|
||||
it will clean up all the data left in the locals for this context.
|
||||
|
||||
The `ident_func` parameter can be added to override the default ident
|
||||
function for the wrapped locals.
|
||||
|
||||
.. versionchanged:: 0.6.1
|
||||
Instead of a manager the :func:`release_local` function can be used
|
||||
as well.
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
`ident_func` was added.
|
||||
"""
|
||||
|
||||
def __init__(self, locals=None, ident_func=None):
|
||||
if locals is None:
|
||||
self.locals = []
|
||||
elif isinstance(locals, Local):
|
||||
self.locals = [locals]
|
||||
else:
|
||||
self.locals = list(locals)
|
||||
if ident_func is not None:
|
||||
self.ident_func = ident_func
|
||||
for local in self.locals:
|
||||
object.__setattr__(local, '__ident_func__', ident_func)
|
||||
else:
|
||||
self.ident_func = get_ident
|
||||
|
||||
def get_ident(self):
|
||||
"""Return the context identifier the local objects use internally for
|
||||
this context. You cannot override this method to change the behavior
|
||||
but use it to link other context local objects (such as SQLAlchemy's
|
||||
scoped sessions) to the Werkzeug locals.
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
You can pass a different ident function to the local manager that
|
||||
will then be propagated to all the locals passed to the
|
||||
constructor.
|
||||
"""
|
||||
return self.ident_func()
|
||||
|
||||
def cleanup(self):
|
||||
"""Manually clean up the data in the locals for this context. Call
|
||||
this at the end of the request or use `make_middleware()`.
|
||||
"""
|
||||
for local in self.locals:
|
||||
release_local(local)
|
||||
|
||||
def make_middleware(self, app):
|
||||
"""Wrap a WSGI application so that cleaning up happens after
|
||||
request end.
|
||||
"""
|
||||
def application(environ, start_response):
|
||||
return ClosingIterator(app(environ, start_response), self.cleanup)
|
||||
return application
|
||||
|
||||
def middleware(self, func):
|
||||
"""Like `make_middleware` but for decorating functions.
|
||||
|
||||
Example usage::
|
||||
|
||||
@manager.middleware
|
||||
def application(environ, start_response):
|
||||
...
|
||||
|
||||
The difference to `make_middleware` is that the function passed
|
||||
will have all the arguments copied from the inner application
|
||||
(name, docstring, module).
|
||||
"""
|
||||
return update_wrapper(self.make_middleware(func), func)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s storages: %d>' % (
|
||||
self.__class__.__name__,
|
||||
len(self.locals)
|
||||
)
|
||||
|
||||
|
||||
@implements_bool
|
||||
class LocalProxy(object):
|
||||
|
||||
"""Acts as a proxy for a werkzeug local. Forwards all operations to
|
||||
a proxied object. The only operations not supported for forwarding
|
||||
are right handed operands and any kind of assignment.
|
||||
|
||||
Example usage::
|
||||
|
||||
from werkzeug.local import Local
|
||||
l = Local()
|
||||
|
||||
# these are proxies
|
||||
request = l('request')
|
||||
user = l('user')
|
||||
|
||||
|
||||
from werkzeug.local import LocalStack
|
||||
_response_local = LocalStack()
|
||||
|
||||
# this is a proxy
|
||||
response = _response_local()
|
||||
|
||||
Whenever something is bound to l.user / l.request the proxy objects
|
||||
will forward all operations. If no object is bound a :exc:`RuntimeError`
|
||||
will be raised.
|
||||
|
||||
To create proxies to :class:`Local` or :class:`LocalStack` objects,
|
||||
call the object as shown above. If you want to have a proxy to an
|
||||
object looked up by a function, you can (as of Werkzeug 0.6.1) pass
|
||||
a function to the :class:`LocalProxy` constructor::
|
||||
|
||||
session = LocalProxy(lambda: get_current_request().session)
|
||||
|
||||
.. versionchanged:: 0.6.1
|
||||
The class can be instantiated with a callable as well now.
|
||||
"""
|
||||
__slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
|
||||
|
||||
def __init__(self, local, name=None):
|
||||
object.__setattr__(self, '_LocalProxy__local', local)
|
||||
object.__setattr__(self, '__name__', name)
|
||||
if callable(local) and not hasattr(local, '__release_local__'):
|
||||
# "local" is a callable that is not an instance of Local or
|
||||
# LocalManager: mark it as a wrapped function.
|
||||
object.__setattr__(self, '__wrapped__', local)
|
||||
|
||||
def _get_current_object(self):
|
||||
"""Return the current object. This is useful if you want the real
|
||||
object behind the proxy at a time for performance reasons or because
|
||||
you want to pass the object into a different context.
|
||||
"""
|
||||
if not hasattr(self.__local, '__release_local__'):
|
||||
return self.__local()
|
||||
try:
|
||||
return getattr(self.__local, self.__name__)
|
||||
except AttributeError:
|
||||
raise RuntimeError('no object bound to %s' % self.__name__)
|
||||
|
||||
@property
|
||||
def __dict__(self):
|
||||
try:
|
||||
return self._get_current_object().__dict__
|
||||
except RuntimeError:
|
||||
raise AttributeError('__dict__')
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
obj = self._get_current_object()
|
||||
except RuntimeError:
|
||||
return '<%s unbound>' % self.__class__.__name__
|
||||
return repr(obj)
|
||||
|
||||
def __bool__(self):
|
||||
try:
|
||||
return bool(self._get_current_object())
|
||||
except RuntimeError:
|
||||
return False
|
||||
|
||||
def __unicode__(self):
|
||||
try:
|
||||
return unicode(self._get_current_object()) # noqa
|
||||
except RuntimeError:
|
||||
return repr(self)
|
||||
|
||||
def __dir__(self):
|
||||
try:
|
||||
return dir(self._get_current_object())
|
||||
except RuntimeError:
|
||||
return []
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == '__members__':
|
||||
return dir(self._get_current_object())
|
||||
return getattr(self._get_current_object(), name)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._get_current_object()[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._get_current_object()[key]
|
||||
|
||||
if PY2:
|
||||
__getslice__ = lambda x, i, j: x._get_current_object()[i:j]
|
||||
|
||||
def __setslice__(self, i, j, seq):
|
||||
self._get_current_object()[i:j] = seq
|
||||
|
||||
def __delslice__(self, i, j):
|
||||
del self._get_current_object()[i:j]
|
||||
|
||||
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
|
||||
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
|
||||
__str__ = lambda x: str(x._get_current_object())
|
||||
__lt__ = lambda x, o: x._get_current_object() < o
|
||||
__le__ = lambda x, o: x._get_current_object() <= o
|
||||
__eq__ = lambda x, o: x._get_current_object() == o
|
||||
__ne__ = lambda x, o: x._get_current_object() != o
|
||||
__gt__ = lambda x, o: x._get_current_object() > o
|
||||
__ge__ = lambda x, o: x._get_current_object() >= o
|
||||
__cmp__ = lambda x, o: cmp(x._get_current_object(), o) # noqa
|
||||
__hash__ = lambda x: hash(x._get_current_object())
|
||||
__call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
|
||||
__len__ = lambda x: len(x._get_current_object())
|
||||
__getitem__ = lambda x, i: x._get_current_object()[i]
|
||||
__iter__ = lambda x: iter(x._get_current_object())
|
||||
__contains__ = lambda x, i: i in x._get_current_object()
|
||||
__add__ = lambda x, o: x._get_current_object() + o
|
||||
__sub__ = lambda x, o: x._get_current_object() - o
|
||||
__mul__ = lambda x, o: x._get_current_object() * o
|
||||
__floordiv__ = lambda x, o: x._get_current_object() // o
|
||||
__mod__ = lambda x, o: x._get_current_object() % o
|
||||
__divmod__ = lambda x, o: x._get_current_object().__divmod__(o)
|
||||
__pow__ = lambda x, o: x._get_current_object() ** o
|
||||
__lshift__ = lambda x, o: x._get_current_object() << o
|
||||
__rshift__ = lambda x, o: x._get_current_object() >> o
|
||||
__and__ = lambda x, o: x._get_current_object() & o
|
||||
__xor__ = lambda x, o: x._get_current_object() ^ o
|
||||
__or__ = lambda x, o: x._get_current_object() | o
|
||||
__div__ = lambda x, o: x._get_current_object().__div__(o)
|
||||
__truediv__ = lambda x, o: x._get_current_object().__truediv__(o)
|
||||
__neg__ = lambda x: -(x._get_current_object())
|
||||
__pos__ = lambda x: +(x._get_current_object())
|
||||
__abs__ = lambda x: abs(x._get_current_object())
|
||||
__invert__ = lambda x: ~(x._get_current_object())
|
||||
__complex__ = lambda x: complex(x._get_current_object())
|
||||
__int__ = lambda x: int(x._get_current_object())
|
||||
__long__ = lambda x: long(x._get_current_object()) # noqa
|
||||
__float__ = lambda x: float(x._get_current_object())
|
||||
__oct__ = lambda x: oct(x._get_current_object())
|
||||
__hex__ = lambda x: hex(x._get_current_object())
|
||||
__index__ = lambda x: x._get_current_object().__index__()
|
||||
__coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o)
|
||||
__enter__ = lambda x: x._get_current_object().__enter__()
|
||||
__exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw)
|
||||
__radd__ = lambda x, o: o + x._get_current_object()
|
||||
__rsub__ = lambda x, o: o - x._get_current_object()
|
||||
__rmul__ = lambda x, o: o * x._get_current_object()
|
||||
__rdiv__ = lambda x, o: o / x._get_current_object()
|
||||
if PY2:
|
||||
__rtruediv__ = lambda x, o: x._get_current_object().__rtruediv__(o)
|
||||
else:
|
||||
__rtruediv__ = __rdiv__
|
||||
__rfloordiv__ = lambda x, o: o // x._get_current_object()
|
||||
__rmod__ = lambda x, o: o % x._get_current_object()
|
||||
__rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o)
|
||||
__copy__ = lambda x: copy.copy(x._get_current_object())
|
||||
__deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/local.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/local.pyc
Normal file
Binary file not shown.
106
venv/lib/python2.7/site-packages/werkzeug/posixemulation.py
Normal file
106
venv/lib/python2.7/site-packages/werkzeug/posixemulation.py
Normal file
@@ -0,0 +1,106 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""
|
||||
werkzeug.posixemulation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides a POSIX emulation for some features that are relevant to
|
||||
web applications. The main purpose is to simplify support for
|
||||
systems such as Windows NT that are not 100% POSIX compatible.
|
||||
|
||||
Currently this only implements a :func:`rename` function that
|
||||
follows POSIX semantics. Eg: if the target file already exists it
|
||||
will be replaced without asking.
|
||||
|
||||
This module was introduced in 0.6.1 and is not a public interface.
|
||||
It might become one in later versions of Werkzeug.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import errno
|
||||
import time
|
||||
import random
|
||||
|
||||
from ._compat import to_unicode
|
||||
from .filesystem import get_filesystem_encoding
|
||||
|
||||
|
||||
can_rename_open_file = False
|
||||
if os.name == 'nt': # pragma: no cover
|
||||
_rename = lambda src, dst: False
|
||||
_rename_atomic = lambda src, dst: False
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
|
||||
_MOVEFILE_REPLACE_EXISTING = 0x1
|
||||
_MOVEFILE_WRITE_THROUGH = 0x8
|
||||
_MoveFileEx = ctypes.windll.kernel32.MoveFileExW
|
||||
|
||||
def _rename(src, dst):
|
||||
src = to_unicode(src, get_filesystem_encoding())
|
||||
dst = to_unicode(dst, get_filesystem_encoding())
|
||||
if _rename_atomic(src, dst):
|
||||
return True
|
||||
retry = 0
|
||||
rv = False
|
||||
while not rv and retry < 100:
|
||||
rv = _MoveFileEx(src, dst, _MOVEFILE_REPLACE_EXISTING |
|
||||
_MOVEFILE_WRITE_THROUGH)
|
||||
if not rv:
|
||||
time.sleep(0.001)
|
||||
retry += 1
|
||||
return rv
|
||||
|
||||
# new in Vista and Windows Server 2008
|
||||
_CreateTransaction = ctypes.windll.ktmw32.CreateTransaction
|
||||
_CommitTransaction = ctypes.windll.ktmw32.CommitTransaction
|
||||
_MoveFileTransacted = ctypes.windll.kernel32.MoveFileTransactedW
|
||||
_CloseHandle = ctypes.windll.kernel32.CloseHandle
|
||||
can_rename_open_file = True
|
||||
|
||||
def _rename_atomic(src, dst):
|
||||
ta = _CreateTransaction(None, 0, 0, 0, 0, 1000, 'Werkzeug rename')
|
||||
if ta == -1:
|
||||
return False
|
||||
try:
|
||||
retry = 0
|
||||
rv = False
|
||||
while not rv and retry < 100:
|
||||
rv = _MoveFileTransacted(src, dst, None, None,
|
||||
_MOVEFILE_REPLACE_EXISTING |
|
||||
_MOVEFILE_WRITE_THROUGH, ta)
|
||||
if rv:
|
||||
rv = _CommitTransaction(ta)
|
||||
break
|
||||
else:
|
||||
time.sleep(0.001)
|
||||
retry += 1
|
||||
return rv
|
||||
finally:
|
||||
_CloseHandle(ta)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def rename(src, dst):
|
||||
# Try atomic or pseudo-atomic rename
|
||||
if _rename(src, dst):
|
||||
return
|
||||
# Fall back to "move away and replace"
|
||||
try:
|
||||
os.rename(src, dst)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
old = "%s-%08x" % (dst, random.randint(0, sys.maxint))
|
||||
os.rename(dst, old)
|
||||
os.rename(src, dst)
|
||||
try:
|
||||
os.unlink(old)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
rename = os.rename
|
||||
can_rename_open_file = True
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/posixemulation.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/posixemulation.pyc
Normal file
Binary file not shown.
1784
venv/lib/python2.7/site-packages/werkzeug/routing.py
Normal file
1784
venv/lib/python2.7/site-packages/werkzeug/routing.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
venv/lib/python2.7/site-packages/werkzeug/routing.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/routing.pyc
Normal file
Binary file not shown.
332
venv/lib/python2.7/site-packages/werkzeug/script.py
Normal file
332
venv/lib/python2.7/site-packages/werkzeug/script.py
Normal file
@@ -0,0 +1,332 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r'''
|
||||
werkzeug.script
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. admonition:: Deprecated Functionality
|
||||
|
||||
``werkzeug.script`` is deprecated without replacement functionality.
|
||||
Python's command line support improved greatly with :mod:`argparse`
|
||||
and a bunch of alternative modules.
|
||||
|
||||
Most of the time you have recurring tasks while writing an application
|
||||
such as starting up an interactive python interpreter with some prefilled
|
||||
imports, starting the development server, initializing the database or
|
||||
something similar.
|
||||
|
||||
For that purpose werkzeug provides the `werkzeug.script` module which
|
||||
helps you writing such scripts.
|
||||
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
|
||||
The following snippet is roughly the same in every werkzeug script::
|
||||
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from werkzeug import script
|
||||
|
||||
# actions go here
|
||||
|
||||
if __name__ == '__main__':
|
||||
script.run()
|
||||
|
||||
Starting this script now does nothing because no actions are defined.
|
||||
An action is a function in the same module starting with ``"action_"``
|
||||
which takes a number of arguments where every argument has a default. The
|
||||
type of the default value specifies the type of the argument.
|
||||
|
||||
Arguments can then be passed by position or using ``--name=value`` from
|
||||
the shell.
|
||||
|
||||
Because a runserver and shell command is pretty common there are two
|
||||
factory functions that create such commands::
|
||||
|
||||
def make_app():
|
||||
from yourapplication import YourApplication
|
||||
return YourApplication(...)
|
||||
|
||||
action_runserver = script.make_runserver(make_app, use_reloader=True)
|
||||
action_shell = script.make_shell(lambda: {'app': make_app()})
|
||||
|
||||
|
||||
Using The Scripts
|
||||
-----------------
|
||||
|
||||
The script from above can be used like this from the shell now:
|
||||
|
||||
.. sourcecode:: text
|
||||
|
||||
$ ./manage.py --help
|
||||
$ ./manage.py runserver localhost 8080 --debugger --no-reloader
|
||||
$ ./manage.py runserver -p 4000
|
||||
$ ./manage.py shell
|
||||
|
||||
As you can see it's possible to pass parameters as positional arguments
|
||||
or as named parameters, pretty much like Python function calls.
|
||||
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
'''
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import inspect
|
||||
import getopt
|
||||
from warnings import warn
|
||||
from os.path import basename
|
||||
from werkzeug._compat import iteritems
|
||||
|
||||
|
||||
argument_types = {
|
||||
bool: 'boolean',
|
||||
str: 'string',
|
||||
int: 'integer',
|
||||
float: 'float'
|
||||
}
|
||||
|
||||
|
||||
converters = {
|
||||
'boolean': lambda x: x.lower() in ('1', 'true', 'yes', 'on'),
|
||||
'string': str,
|
||||
'integer': int,
|
||||
'float': float
|
||||
}
|
||||
|
||||
|
||||
def _deprecated():
|
||||
warn(DeprecationWarning('werkzeug.script is deprecated and '
|
||||
'will be removed soon'), stacklevel=2)
|
||||
|
||||
|
||||
def run(namespace=None, action_prefix='action_', args=None):
|
||||
"""Run the script. Participating actions are looked up in the caller's
|
||||
namespace if no namespace is given, otherwise in the dict provided.
|
||||
Only items that start with action_prefix are processed as actions. If
|
||||
you want to use all items in the namespace provided as actions set
|
||||
action_prefix to an empty string.
|
||||
|
||||
:param namespace: An optional dict where the functions are looked up in.
|
||||
By default the local namespace of the caller is used.
|
||||
:param action_prefix: The prefix for the functions. Everything else
|
||||
is ignored.
|
||||
:param args: the arguments for the function. If not specified
|
||||
:data:`sys.argv` without the first argument is used.
|
||||
"""
|
||||
_deprecated()
|
||||
if namespace is None:
|
||||
namespace = sys._getframe(1).f_locals
|
||||
actions = find_actions(namespace, action_prefix)
|
||||
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
if not args or args[0] in ('-h', '--help'):
|
||||
return print_usage(actions)
|
||||
elif args[0] not in actions:
|
||||
fail('Unknown action \'%s\'' % args[0])
|
||||
|
||||
arguments = {}
|
||||
types = {}
|
||||
key_to_arg = {}
|
||||
long_options = []
|
||||
formatstring = ''
|
||||
func, doc, arg_def = actions[args.pop(0)]
|
||||
for idx, (arg, shortcut, default, option_type) in enumerate(arg_def):
|
||||
real_arg = arg.replace('-', '_')
|
||||
if shortcut:
|
||||
formatstring += shortcut
|
||||
if not isinstance(default, bool):
|
||||
formatstring += ':'
|
||||
key_to_arg['-' + shortcut] = real_arg
|
||||
long_options.append(isinstance(default, bool) and arg or arg + '=')
|
||||
key_to_arg['--' + arg] = real_arg
|
||||
key_to_arg[idx] = real_arg
|
||||
types[real_arg] = option_type
|
||||
arguments[real_arg] = default
|
||||
|
||||
try:
|
||||
optlist, posargs = getopt.gnu_getopt(args, formatstring, long_options)
|
||||
except getopt.GetoptError as e:
|
||||
fail(str(e))
|
||||
|
||||
specified_arguments = set()
|
||||
for key, value in enumerate(posargs):
|
||||
try:
|
||||
arg = key_to_arg[key]
|
||||
except IndexError:
|
||||
fail('Too many parameters')
|
||||
specified_arguments.add(arg)
|
||||
try:
|
||||
arguments[arg] = converters[types[arg]](value)
|
||||
except ValueError:
|
||||
fail('Invalid value for argument %s (%s): %s' % (key, arg, value))
|
||||
|
||||
for key, value in optlist:
|
||||
arg = key_to_arg[key]
|
||||
if arg in specified_arguments:
|
||||
fail('Argument \'%s\' is specified twice' % arg)
|
||||
if types[arg] == 'boolean':
|
||||
if arg.startswith('no_'):
|
||||
value = 'no'
|
||||
else:
|
||||
value = 'yes'
|
||||
try:
|
||||
arguments[arg] = converters[types[arg]](value)
|
||||
except ValueError:
|
||||
fail('Invalid value for \'%s\': %s' % (key, value))
|
||||
|
||||
newargs = {}
|
||||
for k, v in iteritems(arguments):
|
||||
newargs[k.startswith('no_') and k[3:] or k] = v
|
||||
arguments = newargs
|
||||
return func(**arguments)
|
||||
|
||||
|
||||
def fail(message, code=-1):
|
||||
"""Fail with an error."""
|
||||
_deprecated()
|
||||
print('Error: %s' % message, file=sys.stderr)
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
def find_actions(namespace, action_prefix):
|
||||
"""Find all the actions in the namespace."""
|
||||
_deprecated()
|
||||
actions = {}
|
||||
for key, value in iteritems(namespace):
|
||||
if key.startswith(action_prefix):
|
||||
actions[key[len(action_prefix):]] = analyse_action(value)
|
||||
return actions
|
||||
|
||||
|
||||
def print_usage(actions):
|
||||
"""Print the usage information. (Help screen)"""
|
||||
_deprecated()
|
||||
actions = sorted(iteritems(actions))
|
||||
print('usage: %s <action> [<options>]' % basename(sys.argv[0]))
|
||||
print(' %s --help' % basename(sys.argv[0]))
|
||||
print()
|
||||
print('actions:')
|
||||
for name, (func, doc, arguments) in actions:
|
||||
print(' %s:' % name)
|
||||
for line in doc.splitlines():
|
||||
print(' %s' % line)
|
||||
if arguments:
|
||||
print()
|
||||
for arg, shortcut, default, argtype in arguments:
|
||||
if isinstance(default, bool):
|
||||
print(' %s' % (
|
||||
(shortcut and '-%s, ' % shortcut or '') + '--' + arg
|
||||
))
|
||||
else:
|
||||
print(' %-30s%-10s%s' % (
|
||||
(shortcut and '-%s, ' % shortcut or '') + '--' + arg,
|
||||
argtype, default
|
||||
))
|
||||
print()
|
||||
|
||||
|
||||
def analyse_action(func):
|
||||
"""Analyse a function."""
|
||||
_deprecated()
|
||||
description = inspect.getdoc(func) or 'undocumented action'
|
||||
arguments = []
|
||||
args, varargs, kwargs, defaults = inspect.getargspec(func)
|
||||
if varargs or kwargs:
|
||||
raise TypeError('variable length arguments for action not allowed.')
|
||||
if len(args) != len(defaults or ()):
|
||||
raise TypeError('not all arguments have proper definitions')
|
||||
|
||||
for idx, (arg, definition) in enumerate(zip(args, defaults or ())):
|
||||
if arg.startswith('_'):
|
||||
raise TypeError('arguments may not start with an underscore')
|
||||
if not isinstance(definition, tuple):
|
||||
shortcut = None
|
||||
default = definition
|
||||
else:
|
||||
shortcut, default = definition
|
||||
argument_type = argument_types[type(default)]
|
||||
if isinstance(default, bool) and default is True:
|
||||
arg = 'no-' + arg
|
||||
arguments.append((arg.replace('_', '-'), shortcut,
|
||||
default, argument_type))
|
||||
return func, description, arguments
|
||||
|
||||
|
||||
def make_shell(init_func=None, banner=None, use_ipython=True):
|
||||
"""Returns an action callback that spawns a new interactive
|
||||
python shell.
|
||||
|
||||
:param init_func: an optional initialization function that is
|
||||
called before the shell is started. The return
|
||||
value of this function is the initial namespace.
|
||||
:param banner: the banner that is displayed before the shell. If
|
||||
not specified a generic banner is used instead.
|
||||
:param use_ipython: if set to `True` ipython is used if available.
|
||||
"""
|
||||
_deprecated()
|
||||
if banner is None:
|
||||
banner = 'Interactive Werkzeug Shell'
|
||||
if init_func is None:
|
||||
init_func = dict
|
||||
|
||||
def action(ipython=use_ipython):
|
||||
"""Start a new interactive python session."""
|
||||
namespace = init_func()
|
||||
if ipython:
|
||||
try:
|
||||
try:
|
||||
from IPython.frontend.terminal.embed import InteractiveShellEmbed
|
||||
sh = InteractiveShellEmbed.instance(banner1=banner)
|
||||
except ImportError:
|
||||
from IPython.Shell import IPShellEmbed
|
||||
sh = IPShellEmbed(banner=banner)
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
sh(local_ns=namespace)
|
||||
return
|
||||
from code import interact
|
||||
interact(banner, local=namespace)
|
||||
return action
|
||||
|
||||
|
||||
def make_runserver(app_factory, hostname='localhost', port=5000,
|
||||
use_reloader=False, use_debugger=False, use_evalex=True,
|
||||
threaded=False, processes=1, static_files=None,
|
||||
extra_files=None, ssl_context=None):
|
||||
"""Returns an action callback that spawns a new development server.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
`static_files` and `extra_files` was added.
|
||||
|
||||
..versionadded:: 0.6.1
|
||||
`ssl_context` was added.
|
||||
|
||||
:param app_factory: a function that returns a new WSGI application.
|
||||
:param hostname: the default hostname the server should listen on.
|
||||
:param port: the default port of the server.
|
||||
:param use_reloader: the default setting for the reloader.
|
||||
:param use_evalex: the default setting for the evalex flag of the debugger.
|
||||
:param threaded: the default threading setting.
|
||||
:param processes: the default number of processes to start.
|
||||
:param static_files: optional dict of static files.
|
||||
:param extra_files: optional list of extra files to track for reloading.
|
||||
:param ssl_context: optional SSL context for running server in HTTPS mode.
|
||||
"""
|
||||
_deprecated()
|
||||
|
||||
def action(hostname=('h', hostname), port=('p', port),
|
||||
reloader=use_reloader, debugger=use_debugger,
|
||||
evalex=use_evalex, threaded=threaded, processes=processes):
|
||||
"""Start a new development server."""
|
||||
from werkzeug.serving import run_simple
|
||||
app = app_factory()
|
||||
run_simple(hostname, port, app,
|
||||
use_reloader=reloader, use_debugger=debugger,
|
||||
use_evalex=evalex, extra_files=extra_files,
|
||||
reloader_interval=1, threaded=threaded, processes=processes,
|
||||
static_files=static_files, ssl_context=ssl_context)
|
||||
return action
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/script.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/script.pyc
Normal file
Binary file not shown.
270
venv/lib/python2.7/site-packages/werkzeug/security.py
Normal file
270
venv/lib/python2.7/site-packages/werkzeug/security.py
Normal file
@@ -0,0 +1,270 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.security
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Security related helpers such as secure password hashing tools.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import hmac
|
||||
import hashlib
|
||||
import posixpath
|
||||
import codecs
|
||||
from struct import Struct
|
||||
from random import SystemRandom
|
||||
from operator import xor
|
||||
from itertools import starmap
|
||||
|
||||
from werkzeug._compat import range_type, PY2, text_type, izip, to_bytes, \
|
||||
string_types, to_native
|
||||
|
||||
|
||||
SALT_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
DEFAULT_PBKDF2_ITERATIONS = 50000
|
||||
|
||||
|
||||
_pack_int = Struct('>I').pack
|
||||
_builtin_safe_str_cmp = getattr(hmac, 'compare_digest', None)
|
||||
_sys_rng = SystemRandom()
|
||||
_os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
|
||||
if sep not in (None, '/'))
|
||||
|
||||
|
||||
def _find_hashlib_algorithms():
|
||||
algos = getattr(hashlib, 'algorithms', None)
|
||||
if algos is None:
|
||||
algos = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')
|
||||
rv = {}
|
||||
for algo in algos:
|
||||
func = getattr(hashlib, algo, None)
|
||||
if func is not None:
|
||||
rv[algo] = func
|
||||
return rv
|
||||
_hash_funcs = _find_hashlib_algorithms()
|
||||
|
||||
|
||||
def pbkdf2_hex(data, salt, iterations=DEFAULT_PBKDF2_ITERATIONS,
|
||||
keylen=None, hashfunc=None):
|
||||
"""Like :func:`pbkdf2_bin`, but returns a hex-encoded string.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
:param data: the data to derive.
|
||||
:param salt: the salt for the derivation.
|
||||
:param iterations: the number of iterations.
|
||||
:param keylen: the length of the resulting key. If not provided,
|
||||
the digest size will be used.
|
||||
:param hashfunc: the hash function to use. This can either be the
|
||||
string name of a known hash function, or a function
|
||||
from the hashlib module. Defaults to sha256.
|
||||
"""
|
||||
rv = pbkdf2_bin(data, salt, iterations, keylen, hashfunc)
|
||||
return to_native(codecs.encode(rv, 'hex_codec'))
|
||||
|
||||
|
||||
_has_native_pbkdf2 = hasattr(hashlib, 'pbkdf2_hmac')
|
||||
|
||||
|
||||
def pbkdf2_bin(data, salt, iterations=DEFAULT_PBKDF2_ITERATIONS,
|
||||
keylen=None, hashfunc=None):
|
||||
"""Returns a binary digest for the PBKDF2 hash algorithm of `data`
|
||||
with the given `salt`. It iterates `iterations` times and produces a
|
||||
key of `keylen` bytes. By default, SHA-256 is used as hash function;
|
||||
a different hashlib `hashfunc` can be provided.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
:param data: the data to derive.
|
||||
:param salt: the salt for the derivation.
|
||||
:param iterations: the number of iterations.
|
||||
:param keylen: the length of the resulting key. If not provided
|
||||
the digest size will be used.
|
||||
:param hashfunc: the hash function to use. This can either be the
|
||||
string name of a known hash function or a function
|
||||
from the hashlib module. Defaults to sha256.
|
||||
"""
|
||||
if isinstance(hashfunc, string_types):
|
||||
hashfunc = _hash_funcs[hashfunc]
|
||||
elif not hashfunc:
|
||||
hashfunc = hashlib.sha256
|
||||
data = to_bytes(data)
|
||||
salt = to_bytes(salt)
|
||||
|
||||
# If we're on Python with pbkdf2_hmac we can try to use it for
|
||||
# compatible digests.
|
||||
if _has_native_pbkdf2:
|
||||
_test_hash = hashfunc()
|
||||
if hasattr(_test_hash, 'name') and \
|
||||
_test_hash.name in _hash_funcs:
|
||||
return hashlib.pbkdf2_hmac(_test_hash.name,
|
||||
data, salt, iterations,
|
||||
keylen)
|
||||
|
||||
mac = hmac.HMAC(data, None, hashfunc)
|
||||
if not keylen:
|
||||
keylen = mac.digest_size
|
||||
|
||||
def _pseudorandom(x, mac=mac):
|
||||
h = mac.copy()
|
||||
h.update(x)
|
||||
return bytearray(h.digest())
|
||||
buf = bytearray()
|
||||
for block in range_type(1, -(-keylen // mac.digest_size) + 1):
|
||||
rv = u = _pseudorandom(salt + _pack_int(block))
|
||||
for i in range_type(iterations - 1):
|
||||
u = _pseudorandom(bytes(u))
|
||||
rv = bytearray(starmap(xor, izip(rv, u)))
|
||||
buf.extend(rv)
|
||||
return bytes(buf[:keylen])
|
||||
|
||||
|
||||
def safe_str_cmp(a, b):
|
||||
"""This function compares strings in somewhat constant time. This
|
||||
requires that the length of at least one string is known in advance.
|
||||
|
||||
Returns `True` if the two strings are equal, or `False` if they are not.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
if isinstance(a, text_type):
|
||||
a = a.encode('utf-8')
|
||||
if isinstance(b, text_type):
|
||||
b = b.encode('utf-8')
|
||||
|
||||
if _builtin_safe_str_cmp is not None:
|
||||
return _builtin_safe_str_cmp(a, b)
|
||||
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
|
||||
rv = 0
|
||||
if PY2:
|
||||
for x, y in izip(a, b):
|
||||
rv |= ord(x) ^ ord(y)
|
||||
else:
|
||||
for x, y in izip(a, b):
|
||||
rv |= x ^ y
|
||||
|
||||
return rv == 0
|
||||
|
||||
|
||||
def gen_salt(length):
|
||||
"""Generate a random string of SALT_CHARS with specified ``length``."""
|
||||
if length <= 0:
|
||||
raise ValueError('Salt length must be positive')
|
||||
return ''.join(_sys_rng.choice(SALT_CHARS) for _ in range_type(length))
|
||||
|
||||
|
||||
def _hash_internal(method, salt, password):
|
||||
"""Internal password hash helper. Supports plaintext without salt,
|
||||
unsalted and salted passwords. In case salted passwords are used
|
||||
hmac is used.
|
||||
"""
|
||||
if method == 'plain':
|
||||
return password, method
|
||||
|
||||
if isinstance(password, text_type):
|
||||
password = password.encode('utf-8')
|
||||
|
||||
if method.startswith('pbkdf2:'):
|
||||
args = method[7:].split(':')
|
||||
if len(args) not in (1, 2):
|
||||
raise ValueError('Invalid number of arguments for PBKDF2')
|
||||
method = args.pop(0)
|
||||
iterations = args and int(args[0] or 0) or DEFAULT_PBKDF2_ITERATIONS
|
||||
is_pbkdf2 = True
|
||||
actual_method = 'pbkdf2:%s:%d' % (method, iterations)
|
||||
else:
|
||||
is_pbkdf2 = False
|
||||
actual_method = method
|
||||
|
||||
hash_func = _hash_funcs.get(method)
|
||||
if hash_func is None:
|
||||
raise TypeError('invalid method %r' % method)
|
||||
|
||||
if is_pbkdf2:
|
||||
if not salt:
|
||||
raise ValueError('Salt is required for PBKDF2')
|
||||
rv = pbkdf2_hex(password, salt, iterations,
|
||||
hashfunc=hash_func)
|
||||
elif salt:
|
||||
if isinstance(salt, text_type):
|
||||
salt = salt.encode('utf-8')
|
||||
rv = hmac.HMAC(salt, password, hash_func).hexdigest()
|
||||
else:
|
||||
h = hash_func()
|
||||
h.update(password)
|
||||
rv = h.hexdigest()
|
||||
return rv, actual_method
|
||||
|
||||
|
||||
def generate_password_hash(password, method='pbkdf2:sha256', salt_length=8):
|
||||
"""Hash a password with the given method and salt with a string of
|
||||
the given length. The format of the string returned includes the method
|
||||
that was used so that :func:`check_password_hash` can check the hash.
|
||||
|
||||
The format for the hashed string looks like this::
|
||||
|
||||
method$salt$hash
|
||||
|
||||
This method can **not** generate unsalted passwords but it is possible
|
||||
to set param method='plain' in order to enforce plaintext passwords.
|
||||
If a salt is used, hmac is used internally to salt the password.
|
||||
|
||||
If PBKDF2 is wanted it can be enabled by setting the method to
|
||||
``pbkdf2:method:iterations`` where iterations is optional::
|
||||
|
||||
pbkdf2:sha256:80000$salt$hash
|
||||
pbkdf2:sha256$salt$hash
|
||||
|
||||
:param password: the password to hash.
|
||||
:param method: the hash method to use (one that hashlib supports). Can
|
||||
optionally be in the format ``pbkdf2:<method>[:iterations]``
|
||||
to enable PBKDF2.
|
||||
:param salt_length: the length of the salt in letters.
|
||||
"""
|
||||
salt = method != 'plain' and gen_salt(salt_length) or ''
|
||||
h, actual_method = _hash_internal(method, salt, password)
|
||||
return '%s$%s$%s' % (actual_method, salt, h)
|
||||
|
||||
|
||||
def check_password_hash(pwhash, password):
|
||||
"""check a password against a given salted and hashed password value.
|
||||
In order to support unsalted legacy passwords this method supports
|
||||
plain text passwords, md5 and sha1 hashes (both salted and unsalted).
|
||||
|
||||
Returns `True` if the password matched, `False` otherwise.
|
||||
|
||||
:param pwhash: a hashed string like returned by
|
||||
:func:`generate_password_hash`.
|
||||
:param password: the plaintext password to compare against the hash.
|
||||
"""
|
||||
if pwhash.count('$') < 2:
|
||||
return False
|
||||
method, salt, hashval = pwhash.split('$', 2)
|
||||
return safe_str_cmp(_hash_internal(method, salt, password)[0], hashval)
|
||||
|
||||
|
||||
def safe_join(directory, *pathnames):
|
||||
"""Safely join `directory` and one or more untrusted `pathnames`. If this
|
||||
cannot be done, this function returns ``None``.
|
||||
|
||||
:param directory: the base directory.
|
||||
:param filename: the untrusted filename relative to that directory.
|
||||
"""
|
||||
parts = [directory]
|
||||
for filename in pathnames:
|
||||
if filename != '':
|
||||
filename = posixpath.normpath(filename)
|
||||
for sep in _os_alt_seps:
|
||||
if sep in filename:
|
||||
return None
|
||||
if os.path.isabs(filename) or \
|
||||
filename == '..' or \
|
||||
filename.startswith('../'):
|
||||
return None
|
||||
parts.append(filename)
|
||||
return posixpath.join(*parts)
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/security.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/security.pyc
Normal file
Binary file not shown.
787
venv/lib/python2.7/site-packages/werkzeug/serving.py
Normal file
787
venv/lib/python2.7/site-packages/werkzeug/serving.py
Normal file
@@ -0,0 +1,787 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.serving
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
There are many ways to serve a WSGI application. While you're developing
|
||||
it you usually don't want a full blown webserver like Apache but a simple
|
||||
standalone one. From Python 2.5 onwards there is the `wsgiref`_ server in
|
||||
the standard library. If you're using older versions of Python you can
|
||||
download the package from the cheeseshop.
|
||||
|
||||
However there are some caveats. Sourcecode won't reload itself when
|
||||
changed and each time you kill the server using ``^C`` you get an
|
||||
`KeyboardInterrupt` error. While the latter is easy to solve the first
|
||||
one can be a pain in the ass in some situations.
|
||||
|
||||
The easiest way is creating a small ``start-myproject.py`` that runs the
|
||||
application::
|
||||
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from myproject import make_app
|
||||
from werkzeug.serving import run_simple
|
||||
|
||||
app = make_app(...)
|
||||
run_simple('localhost', 8080, app, use_reloader=True)
|
||||
|
||||
You can also pass it a `extra_files` keyword argument with a list of
|
||||
additional files (like configuration files) you want to observe.
|
||||
|
||||
For bigger applications you should consider using `werkzeug.script`
|
||||
instead of a simple start file.
|
||||
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import signal
|
||||
|
||||
|
||||
can_fork = hasattr(os, "fork")
|
||||
|
||||
|
||||
try:
|
||||
import termcolor
|
||||
except ImportError:
|
||||
termcolor = None
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
class _SslDummy(object):
|
||||
def __getattr__(self, name):
|
||||
raise RuntimeError('SSL support unavailable')
|
||||
ssl = _SslDummy()
|
||||
|
||||
|
||||
def _get_openssl_crypto_module():
|
||||
try:
|
||||
from OpenSSL import crypto
|
||||
except ImportError:
|
||||
raise TypeError('Using ad-hoc certificates requires the pyOpenSSL '
|
||||
'library.')
|
||||
else:
|
||||
return crypto
|
||||
|
||||
|
||||
try:
|
||||
import SocketServer as socketserver
|
||||
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
||||
except ImportError:
|
||||
import socketserver
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
ThreadingMixIn = socketserver.ThreadingMixIn
|
||||
|
||||
if can_fork:
|
||||
ForkingMixIn = socketserver.ForkingMixIn
|
||||
else:
|
||||
class ForkingMixIn(object):
|
||||
pass
|
||||
|
||||
# important: do not use relative imports here or python -m will break
|
||||
import werkzeug
|
||||
from werkzeug._internal import _log
|
||||
from werkzeug._compat import PY2, WIN, reraise, wsgi_encoding_dance
|
||||
from werkzeug.urls import url_parse, url_unquote
|
||||
from werkzeug.exceptions import InternalServerError
|
||||
|
||||
|
||||
LISTEN_QUEUE = 128
|
||||
can_open_by_fd = not WIN and hasattr(socket, 'fromfd')
|
||||
|
||||
|
||||
class WSGIRequestHandler(BaseHTTPRequestHandler, object):
|
||||
|
||||
"""A request handler that implements WSGI dispatching."""
|
||||
|
||||
@property
|
||||
def server_version(self):
|
||||
return 'Werkzeug/' + werkzeug.__version__
|
||||
|
||||
def make_environ(self):
|
||||
request_url = url_parse(self.path)
|
||||
|
||||
def shutdown_server():
|
||||
self.server.shutdown_signal = True
|
||||
|
||||
url_scheme = self.server.ssl_context is None and 'http' or 'https'
|
||||
path_info = url_unquote(request_url.path)
|
||||
|
||||
environ = {
|
||||
'wsgi.version': (1, 0),
|
||||
'wsgi.url_scheme': url_scheme,
|
||||
'wsgi.input': self.rfile,
|
||||
'wsgi.errors': sys.stderr,
|
||||
'wsgi.multithread': self.server.multithread,
|
||||
'wsgi.multiprocess': self.server.multiprocess,
|
||||
'wsgi.run_once': False,
|
||||
'werkzeug.server.shutdown': shutdown_server,
|
||||
'SERVER_SOFTWARE': self.server_version,
|
||||
'REQUEST_METHOD': self.command,
|
||||
'SCRIPT_NAME': '',
|
||||
'PATH_INFO': wsgi_encoding_dance(path_info),
|
||||
'QUERY_STRING': wsgi_encoding_dance(request_url.query),
|
||||
'REMOTE_ADDR': self.address_string(),
|
||||
'REMOTE_PORT': self.port_integer(),
|
||||
'SERVER_NAME': self.server.server_address[0],
|
||||
'SERVER_PORT': str(self.server.server_address[1]),
|
||||
'SERVER_PROTOCOL': self.request_version
|
||||
}
|
||||
|
||||
for key, value in self.headers.items():
|
||||
key = key.upper().replace('-', '_')
|
||||
if key not in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
|
||||
key = 'HTTP_' + key
|
||||
environ[key] = value
|
||||
|
||||
if request_url.scheme and request_url.netloc:
|
||||
environ['HTTP_HOST'] = request_url.netloc
|
||||
|
||||
return environ
|
||||
|
||||
def run_wsgi(self):
|
||||
if self.headers.get('Expect', '').lower().strip() == '100-continue':
|
||||
self.wfile.write(b'HTTP/1.1 100 Continue\r\n\r\n')
|
||||
|
||||
self.environ = environ = self.make_environ()
|
||||
headers_set = []
|
||||
headers_sent = []
|
||||
|
||||
def write(data):
|
||||
assert headers_set, 'write() before start_response'
|
||||
if not headers_sent:
|
||||
status, response_headers = headers_sent[:] = headers_set
|
||||
try:
|
||||
code, msg = status.split(None, 1)
|
||||
except ValueError:
|
||||
code, msg = status, ""
|
||||
self.send_response(int(code), msg)
|
||||
header_keys = set()
|
||||
for key, value in response_headers:
|
||||
self.send_header(key, value)
|
||||
key = key.lower()
|
||||
header_keys.add(key)
|
||||
if 'content-length' not in header_keys:
|
||||
self.close_connection = True
|
||||
self.send_header('Connection', 'close')
|
||||
if 'server' not in header_keys:
|
||||
self.send_header('Server', self.version_string())
|
||||
if 'date' not in header_keys:
|
||||
self.send_header('Date', self.date_time_string())
|
||||
self.end_headers()
|
||||
|
||||
assert isinstance(data, bytes), 'applications must write bytes'
|
||||
self.wfile.write(data)
|
||||
self.wfile.flush()
|
||||
|
||||
def start_response(status, response_headers, exc_info=None):
|
||||
if exc_info:
|
||||
try:
|
||||
if headers_sent:
|
||||
reraise(*exc_info)
|
||||
finally:
|
||||
exc_info = None
|
||||
elif headers_set:
|
||||
raise AssertionError('Headers already set')
|
||||
headers_set[:] = [status, response_headers]
|
||||
return write
|
||||
|
||||
def execute(app):
|
||||
application_iter = app(environ, start_response)
|
||||
try:
|
||||
for data in application_iter:
|
||||
write(data)
|
||||
if not headers_sent:
|
||||
write(b'')
|
||||
finally:
|
||||
if hasattr(application_iter, 'close'):
|
||||
application_iter.close()
|
||||
application_iter = None
|
||||
|
||||
try:
|
||||
execute(self.server.app)
|
||||
except (socket.error, socket.timeout) as e:
|
||||
self.connection_dropped(e, environ)
|
||||
except Exception:
|
||||
if self.server.passthrough_errors:
|
||||
raise
|
||||
from werkzeug.debug.tbtools import get_current_traceback
|
||||
traceback = get_current_traceback(ignore_system_exceptions=True)
|
||||
try:
|
||||
# if we haven't yet sent the headers but they are set
|
||||
# we roll back to be able to set them again.
|
||||
if not headers_sent:
|
||||
del headers_set[:]
|
||||
execute(InternalServerError())
|
||||
except Exception:
|
||||
pass
|
||||
self.server.log('error', 'Error on request:\n%s',
|
||||
traceback.plaintext)
|
||||
|
||||
def handle(self):
|
||||
"""Handles a request ignoring dropped connections."""
|
||||
rv = None
|
||||
try:
|
||||
rv = BaseHTTPRequestHandler.handle(self)
|
||||
except (socket.error, socket.timeout) as e:
|
||||
self.connection_dropped(e)
|
||||
except Exception:
|
||||
if self.server.ssl_context is None or not is_ssl_error():
|
||||
raise
|
||||
if self.server.shutdown_signal:
|
||||
self.initiate_shutdown()
|
||||
return rv
|
||||
|
||||
def initiate_shutdown(self):
|
||||
"""A horrible, horrible way to kill the server for Python 2.6 and
|
||||
later. It's the best we can do.
|
||||
"""
|
||||
# Windows does not provide SIGKILL, go with SIGTERM then.
|
||||
sig = getattr(signal, 'SIGKILL', signal.SIGTERM)
|
||||
# reloader active
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
|
||||
os.kill(os.getpid(), sig)
|
||||
# python 2.7
|
||||
self.server._BaseServer__shutdown_request = True
|
||||
# python 2.6
|
||||
self.server._BaseServer__serving = False
|
||||
|
||||
def connection_dropped(self, error, environ=None):
|
||||
"""Called if the connection was closed by the client. By default
|
||||
nothing happens.
|
||||
"""
|
||||
|
||||
def handle_one_request(self):
|
||||
"""Handle a single HTTP request."""
|
||||
self.raw_requestline = self.rfile.readline()
|
||||
if not self.raw_requestline:
|
||||
self.close_connection = 1
|
||||
elif self.parse_request():
|
||||
return self.run_wsgi()
|
||||
|
||||
def send_response(self, code, message=None):
|
||||
"""Send the response header and log the response code."""
|
||||
self.log_request(code)
|
||||
if message is None:
|
||||
message = code in self.responses and self.responses[code][0] or ''
|
||||
if self.request_version != 'HTTP/0.9':
|
||||
hdr = "%s %d %s\r\n" % (self.protocol_version, code, message)
|
||||
self.wfile.write(hdr.encode('ascii'))
|
||||
|
||||
def version_string(self):
|
||||
return BaseHTTPRequestHandler.version_string(self).strip()
|
||||
|
||||
def address_string(self):
|
||||
if getattr(self, 'environ', None):
|
||||
return self.environ['REMOTE_ADDR']
|
||||
else:
|
||||
return self.client_address[0]
|
||||
|
||||
def port_integer(self):
|
||||
return self.client_address[1]
|
||||
|
||||
def log_request(self, code='-', size='-'):
|
||||
msg = self.requestline
|
||||
code = str(code)
|
||||
|
||||
if termcolor:
|
||||
color = termcolor.colored
|
||||
|
||||
if code[0] == '1': # 1xx - Informational
|
||||
msg = color(msg, attrs=['bold'])
|
||||
if code[0] == '2': # 2xx - Success
|
||||
msg = color(msg, color='white')
|
||||
elif code == '304': # 304 - Resource Not Modified
|
||||
msg = color(msg, color='cyan')
|
||||
elif code[0] == '3': # 3xx - Redirection
|
||||
msg = color(msg, color='green')
|
||||
elif code == '404': # 404 - Resource Not Found
|
||||
msg = color(msg, color='yellow')
|
||||
elif code[0] == '4': # 4xx - Client Error
|
||||
msg = color(msg, color='red', attrs=['bold'])
|
||||
else: # 5xx, or any other response
|
||||
msg = color(msg, color='magenta', attrs=['bold'])
|
||||
|
||||
self.log('info', '"%s" %s %s', msg, code, size)
|
||||
|
||||
def log_error(self, *args):
|
||||
self.log('error', *args)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
self.log('info', format, *args)
|
||||
|
||||
def log(self, type, message, *args):
|
||||
_log(type, '%s - - [%s] %s\n' % (self.address_string(),
|
||||
self.log_date_time_string(),
|
||||
message % args))
|
||||
|
||||
|
||||
#: backwards compatible name if someone is subclassing it
|
||||
BaseRequestHandler = WSGIRequestHandler
|
||||
|
||||
|
||||
def generate_adhoc_ssl_pair(cn=None):
|
||||
from random import random
|
||||
crypto = _get_openssl_crypto_module()
|
||||
|
||||
# pretty damn sure that this is not actually accepted by anyone
|
||||
if cn is None:
|
||||
cn = '*'
|
||||
|
||||
cert = crypto.X509()
|
||||
cert.set_serial_number(int(random() * sys.maxsize))
|
||||
cert.gmtime_adj_notBefore(0)
|
||||
cert.gmtime_adj_notAfter(60 * 60 * 24 * 365)
|
||||
|
||||
subject = cert.get_subject()
|
||||
subject.CN = cn
|
||||
subject.O = 'Dummy Certificate'
|
||||
|
||||
issuer = cert.get_issuer()
|
||||
issuer.CN = 'Untrusted Authority'
|
||||
issuer.O = 'Self-Signed'
|
||||
|
||||
pkey = crypto.PKey()
|
||||
pkey.generate_key(crypto.TYPE_RSA, 2048)
|
||||
cert.set_pubkey(pkey)
|
||||
cert.sign(pkey, 'sha256')
|
||||
|
||||
return cert, pkey
|
||||
|
||||
|
||||
def make_ssl_devcert(base_path, host=None, cn=None):
|
||||
"""Creates an SSL key for development. This should be used instead of
|
||||
the ``'adhoc'`` key which generates a new cert on each server start.
|
||||
It accepts a path for where it should store the key and cert and
|
||||
either a host or CN. If a host is given it will use the CN
|
||||
``*.host/CN=host``.
|
||||
|
||||
For more information see :func:`run_simple`.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
:param base_path: the path to the certificate and key. The extension
|
||||
``.crt`` is added for the certificate, ``.key`` is
|
||||
added for the key.
|
||||
:param host: the name of the host. This can be used as an alternative
|
||||
for the `cn`.
|
||||
:param cn: the `CN` to use.
|
||||
"""
|
||||
from OpenSSL import crypto
|
||||
if host is not None:
|
||||
cn = '*.%s/CN=%s' % (host, host)
|
||||
cert, pkey = generate_adhoc_ssl_pair(cn=cn)
|
||||
|
||||
cert_file = base_path + '.crt'
|
||||
pkey_file = base_path + '.key'
|
||||
|
||||
with open(cert_file, 'wb') as f:
|
||||
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
with open(pkey_file, 'wb') as f:
|
||||
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
||||
|
||||
return cert_file, pkey_file
|
||||
|
||||
|
||||
def generate_adhoc_ssl_context():
|
||||
"""Generates an adhoc SSL context for the development server."""
|
||||
crypto = _get_openssl_crypto_module()
|
||||
import tempfile
|
||||
import atexit
|
||||
|
||||
cert, pkey = generate_adhoc_ssl_pair()
|
||||
cert_handle, cert_file = tempfile.mkstemp()
|
||||
pkey_handle, pkey_file = tempfile.mkstemp()
|
||||
atexit.register(os.remove, pkey_file)
|
||||
atexit.register(os.remove, cert_file)
|
||||
|
||||
os.write(cert_handle, crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
os.write(pkey_handle, crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
||||
os.close(cert_handle)
|
||||
os.close(pkey_handle)
|
||||
ctx = load_ssl_context(cert_file, pkey_file)
|
||||
return ctx
|
||||
|
||||
|
||||
def load_ssl_context(cert_file, pkey_file=None, protocol=None):
|
||||
"""Loads SSL context from cert/private key files and optional protocol.
|
||||
Many parameters are directly taken from the API of
|
||||
:py:class:`ssl.SSLContext`.
|
||||
|
||||
:param cert_file: Path of the certificate to use.
|
||||
:param pkey_file: Path of the private key to use. If not given, the key
|
||||
will be obtained from the certificate file.
|
||||
:param protocol: One of the ``PROTOCOL_*`` constants in the stdlib ``ssl``
|
||||
module. Defaults to ``PROTOCOL_SSLv23``.
|
||||
"""
|
||||
if protocol is None:
|
||||
protocol = ssl.PROTOCOL_SSLv23
|
||||
ctx = _SSLContext(protocol)
|
||||
ctx.load_cert_chain(cert_file, pkey_file)
|
||||
return ctx
|
||||
|
||||
|
||||
class _SSLContext(object):
|
||||
|
||||
'''A dummy class with a small subset of Python3's ``ssl.SSLContext``, only
|
||||
intended to be used with and by Werkzeug.'''
|
||||
|
||||
def __init__(self, protocol):
|
||||
self._protocol = protocol
|
||||
self._certfile = None
|
||||
self._keyfile = None
|
||||
self._password = None
|
||||
|
||||
def load_cert_chain(self, certfile, keyfile=None, password=None):
|
||||
self._certfile = certfile
|
||||
self._keyfile = keyfile or certfile
|
||||
self._password = password
|
||||
|
||||
def wrap_socket(self, sock, **kwargs):
|
||||
return ssl.wrap_socket(sock, keyfile=self._keyfile,
|
||||
certfile=self._certfile,
|
||||
ssl_version=self._protocol, **kwargs)
|
||||
|
||||
|
||||
def is_ssl_error(error=None):
|
||||
"""Checks if the given error (or the current one) is an SSL error."""
|
||||
exc_types = (ssl.SSLError,)
|
||||
try:
|
||||
from OpenSSL.SSL import Error
|
||||
exc_types += (Error,)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if error is None:
|
||||
error = sys.exc_info()[1]
|
||||
return isinstance(error, exc_types)
|
||||
|
||||
|
||||
def select_ip_version(host, port):
|
||||
"""Returns AF_INET4 or AF_INET6 depending on where to connect to."""
|
||||
# disabled due to problems with current ipv6 implementations
|
||||
# and various operating systems. Probably this code also is
|
||||
# not supposed to work, but I can't come up with any other
|
||||
# ways to implement this.
|
||||
# try:
|
||||
# info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
|
||||
# socket.SOCK_STREAM, 0,
|
||||
# socket.AI_PASSIVE)
|
||||
# if info:
|
||||
# return info[0][0]
|
||||
# except socket.gaierror:
|
||||
# pass
|
||||
if ':' in host and hasattr(socket, 'AF_INET6'):
|
||||
return socket.AF_INET6
|
||||
return socket.AF_INET
|
||||
|
||||
|
||||
class BaseWSGIServer(HTTPServer, object):
|
||||
|
||||
"""Simple single-threaded, single-process WSGI server."""
|
||||
multithread = False
|
||||
multiprocess = False
|
||||
request_queue_size = LISTEN_QUEUE
|
||||
|
||||
def __init__(self, host, port, app, handler=None,
|
||||
passthrough_errors=False, ssl_context=None, fd=None):
|
||||
if handler is None:
|
||||
handler = WSGIRequestHandler
|
||||
|
||||
self.address_family = select_ip_version(host, port)
|
||||
|
||||
if fd is not None:
|
||||
real_sock = socket.fromfd(fd, self.address_family,
|
||||
socket.SOCK_STREAM)
|
||||
port = 0
|
||||
HTTPServer.__init__(self, (host, int(port)), handler)
|
||||
self.app = app
|
||||
self.passthrough_errors = passthrough_errors
|
||||
self.shutdown_signal = False
|
||||
self.host = host
|
||||
self.port = self.socket.getsockname()[1]
|
||||
|
||||
# Patch in the original socket.
|
||||
if fd is not None:
|
||||
self.socket.close()
|
||||
self.socket = real_sock
|
||||
self.server_address = self.socket.getsockname()
|
||||
|
||||
if ssl_context is not None:
|
||||
if isinstance(ssl_context, tuple):
|
||||
ssl_context = load_ssl_context(*ssl_context)
|
||||
if ssl_context == 'adhoc':
|
||||
ssl_context = generate_adhoc_ssl_context()
|
||||
# If we are on Python 2 the return value from socket.fromfd
|
||||
# is an internal socket object but what we need for ssl wrap
|
||||
# is the wrapper around it :(
|
||||
sock = self.socket
|
||||
if PY2 and not isinstance(sock, socket.socket):
|
||||
sock = socket.socket(sock.family, sock.type, sock.proto, sock)
|
||||
self.socket = ssl_context.wrap_socket(sock, server_side=True)
|
||||
self.ssl_context = ssl_context
|
||||
else:
|
||||
self.ssl_context = None
|
||||
|
||||
def log(self, type, message, *args):
|
||||
_log(type, message, *args)
|
||||
|
||||
def serve_forever(self):
|
||||
self.shutdown_signal = False
|
||||
try:
|
||||
HTTPServer.serve_forever(self)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
self.server_close()
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
if self.passthrough_errors:
|
||||
raise
|
||||
return HTTPServer.handle_error(self, request, client_address)
|
||||
|
||||
def get_request(self):
|
||||
con, info = self.socket.accept()
|
||||
return con, info
|
||||
|
||||
|
||||
class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
|
||||
|
||||
"""A WSGI server that does threading."""
|
||||
multithread = True
|
||||
daemon_threads = True
|
||||
|
||||
|
||||
class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
|
||||
|
||||
"""A WSGI server that does forking."""
|
||||
multiprocess = True
|
||||
|
||||
def __init__(self, host, port, app, processes=40, handler=None,
|
||||
passthrough_errors=False, ssl_context=None, fd=None):
|
||||
if not can_fork:
|
||||
raise ValueError('Your platform does not support forking.')
|
||||
BaseWSGIServer.__init__(self, host, port, app, handler,
|
||||
passthrough_errors, ssl_context, fd)
|
||||
self.max_children = processes
|
||||
|
||||
|
||||
def make_server(host=None, port=None, app=None, threaded=False, processes=1,
|
||||
request_handler=None, passthrough_errors=False,
|
||||
ssl_context=None, fd=None):
|
||||
"""Create a new server instance that is either threaded, or forks
|
||||
or just processes one request after another.
|
||||
"""
|
||||
if threaded and processes > 1:
|
||||
raise ValueError("cannot have a multithreaded and "
|
||||
"multi process server.")
|
||||
elif threaded:
|
||||
return ThreadedWSGIServer(host, port, app, request_handler,
|
||||
passthrough_errors, ssl_context, fd=fd)
|
||||
elif processes > 1:
|
||||
return ForkingWSGIServer(host, port, app, processes, request_handler,
|
||||
passthrough_errors, ssl_context, fd=fd)
|
||||
else:
|
||||
return BaseWSGIServer(host, port, app, request_handler,
|
||||
passthrough_errors, ssl_context, fd=fd)
|
||||
|
||||
|
||||
def is_running_from_reloader():
|
||||
"""Checks if the application is running from within the Werkzeug
|
||||
reloader subprocess.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
"""
|
||||
return os.environ.get('WERKZEUG_RUN_MAIN') == 'true'
|
||||
|
||||
|
||||
def run_simple(hostname, port, application, use_reloader=False,
|
||||
use_debugger=False, use_evalex=True,
|
||||
extra_files=None, reloader_interval=1,
|
||||
reloader_type='auto', threaded=False,
|
||||
processes=1, request_handler=None, static_files=None,
|
||||
passthrough_errors=False, ssl_context=None):
|
||||
"""Start a WSGI application. Optional features include a reloader,
|
||||
multithreading and fork support.
|
||||
|
||||
This function has a command-line interface too::
|
||||
|
||||
python -m werkzeug.serving --help
|
||||
|
||||
.. versionadded:: 0.5
|
||||
`static_files` was added to simplify serving of static files as well
|
||||
as `passthrough_errors`.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
support for SSL was added.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
Added support for automatically loading a SSL context from certificate
|
||||
file and private key.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
Added command-line interface.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
Improved the reloader and added support for changing the backend
|
||||
through the `reloader_type` parameter. See :ref:`reloader`
|
||||
for more information.
|
||||
|
||||
:param hostname: The host for the application. eg: ``'localhost'``
|
||||
:param port: The port for the server. eg: ``8080``
|
||||
:param application: the WSGI application to execute
|
||||
:param use_reloader: should the server automatically restart the python
|
||||
process if modules were changed?
|
||||
:param use_debugger: should the werkzeug debugging system be used?
|
||||
:param use_evalex: should the exception evaluation feature be enabled?
|
||||
:param extra_files: a list of files the reloader should watch
|
||||
additionally to the modules. For example configuration
|
||||
files.
|
||||
:param reloader_interval: the interval for the reloader in seconds.
|
||||
:param reloader_type: the type of reloader to use. The default is
|
||||
auto detection. Valid values are ``'stat'`` and
|
||||
``'watchdog'``. See :ref:`reloader` for more
|
||||
information.
|
||||
:param threaded: should the process handle each request in a separate
|
||||
thread?
|
||||
:param processes: if greater than 1 then handle each request in a new process
|
||||
up to this maximum number of concurrent processes.
|
||||
:param request_handler: optional parameter that can be used to replace
|
||||
the default one. You can use this to replace it
|
||||
with a different
|
||||
:class:`~BaseHTTPServer.BaseHTTPRequestHandler`
|
||||
subclass.
|
||||
:param static_files: a dict of paths for static files. This works exactly
|
||||
like :class:`SharedDataMiddleware`, it's actually
|
||||
just wrapping the application in that middleware before
|
||||
serving.
|
||||
:param passthrough_errors: set this to `True` to disable the error catching.
|
||||
This means that the server will die on errors but
|
||||
it can be useful to hook debuggers in (pdb etc.)
|
||||
:param ssl_context: an SSL context for the connection. Either an
|
||||
:class:`ssl.SSLContext`, a tuple in the form
|
||||
``(cert_file, pkey_file)``, the string ``'adhoc'`` if
|
||||
the server should automatically create one, or ``None``
|
||||
to disable SSL (which is the default).
|
||||
"""
|
||||
if use_debugger:
|
||||
from werkzeug.debug import DebuggedApplication
|
||||
application = DebuggedApplication(application, use_evalex)
|
||||
if static_files:
|
||||
from werkzeug.wsgi import SharedDataMiddleware
|
||||
application = SharedDataMiddleware(application, static_files)
|
||||
|
||||
def log_startup(sock):
|
||||
display_hostname = hostname not in ('', '*') and hostname or 'localhost'
|
||||
if ':' in display_hostname:
|
||||
display_hostname = '[%s]' % display_hostname
|
||||
quit_msg = '(Press CTRL+C to quit)'
|
||||
port = sock.getsockname()[1]
|
||||
_log('info', ' * Running on %s://%s:%d/ %s',
|
||||
ssl_context is None and 'http' or 'https',
|
||||
display_hostname, port, quit_msg)
|
||||
|
||||
def inner():
|
||||
try:
|
||||
fd = int(os.environ['WERKZEUG_SERVER_FD'])
|
||||
except (LookupError, ValueError):
|
||||
fd = None
|
||||
srv = make_server(hostname, port, application, threaded,
|
||||
processes, request_handler,
|
||||
passthrough_errors, ssl_context,
|
||||
fd=fd)
|
||||
if fd is None:
|
||||
log_startup(srv.socket)
|
||||
srv.serve_forever()
|
||||
|
||||
if use_reloader:
|
||||
# If we're not running already in the subprocess that is the
|
||||
# reloader we want to open up a socket early to make sure the
|
||||
# port is actually available.
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
|
||||
if port == 0 and not can_open_by_fd:
|
||||
raise ValueError('Cannot bind to a random port with enabled '
|
||||
'reloader if the Python interpreter does '
|
||||
'not support socket opening by fd.')
|
||||
|
||||
# Create and destroy a socket so that any exceptions are
|
||||
# raised before we spawn a separate Python interpreter and
|
||||
# lose this ability.
|
||||
address_family = select_ip_version(hostname, port)
|
||||
s = socket.socket(address_family, socket.SOCK_STREAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind((hostname, port))
|
||||
if hasattr(s, 'set_inheritable'):
|
||||
s.set_inheritable(True)
|
||||
|
||||
# If we can open the socket by file descriptor, then we can just
|
||||
# reuse this one and our socket will survive the restarts.
|
||||
if can_open_by_fd:
|
||||
os.environ['WERKZEUG_SERVER_FD'] = str(s.fileno())
|
||||
s.listen(LISTEN_QUEUE)
|
||||
log_startup(s)
|
||||
else:
|
||||
s.close()
|
||||
|
||||
# Do not use relative imports, otherwise "python -m werkzeug.serving"
|
||||
# breaks.
|
||||
from werkzeug._reloader import run_with_reloader
|
||||
run_with_reloader(inner, extra_files, reloader_interval,
|
||||
reloader_type)
|
||||
else:
|
||||
inner()
|
||||
|
||||
|
||||
def run_with_reloader(*args, **kwargs):
|
||||
# People keep using undocumented APIs. Do not use this function
|
||||
# please, we do not guarantee that it continues working.
|
||||
from werkzeug._reloader import run_with_reloader
|
||||
return run_with_reloader(*args, **kwargs)
|
||||
|
||||
|
||||
def main():
|
||||
'''A simple command-line interface for :py:func:`run_simple`.'''
|
||||
|
||||
# in contrast to argparse, this works at least under Python < 2.7
|
||||
import optparse
|
||||
from werkzeug.utils import import_string
|
||||
|
||||
parser = optparse.OptionParser(
|
||||
usage='Usage: %prog [options] app_module:app_object')
|
||||
parser.add_option('-b', '--bind', dest='address',
|
||||
help='The hostname:port the app should listen on.')
|
||||
parser.add_option('-d', '--debug', dest='use_debugger',
|
||||
action='store_true', default=False,
|
||||
help='Use Werkzeug\'s debugger.')
|
||||
parser.add_option('-r', '--reload', dest='use_reloader',
|
||||
action='store_true', default=False,
|
||||
help='Reload Python process if modules change.')
|
||||
options, args = parser.parse_args()
|
||||
|
||||
hostname, port = None, None
|
||||
if options.address:
|
||||
address = options.address.split(':')
|
||||
hostname = address[0]
|
||||
if len(address) > 1:
|
||||
port = address[1]
|
||||
|
||||
if len(args) != 1:
|
||||
sys.stdout.write('No application supplied, or too much. See --help\n')
|
||||
sys.exit(1)
|
||||
app = import_string(args[0])
|
||||
|
||||
run_simple(
|
||||
hostname=(hostname or '127.0.0.1'), port=int(port or 5000),
|
||||
application=app, use_reloader=options.use_reloader,
|
||||
use_debugger=options.use_debugger
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/serving.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/serving.pyc
Normal file
Binary file not shown.
909
venv/lib/python2.7/site-packages/werkzeug/test.py
Normal file
909
venv/lib/python2.7/site-packages/werkzeug/test.py
Normal file
@@ -0,0 +1,909 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.test
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This module implements a client to WSGI applications for testing.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
import mimetypes
|
||||
from time import time
|
||||
from random import random
|
||||
from itertools import chain
|
||||
from tempfile import TemporaryFile
|
||||
from io import BytesIO
|
||||
|
||||
try:
|
||||
from urllib2 import Request as U2Request
|
||||
except ImportError:
|
||||
from urllib.request import Request as U2Request
|
||||
try:
|
||||
from http.cookiejar import CookieJar
|
||||
except ImportError: # Py2
|
||||
from cookielib import CookieJar
|
||||
|
||||
from werkzeug._compat import iterlists, iteritems, itervalues, to_bytes, \
|
||||
string_types, text_type, reraise, wsgi_encoding_dance, \
|
||||
make_literal_wrapper
|
||||
from werkzeug._internal import _empty_stream, _get_environ
|
||||
from werkzeug.wrappers import BaseRequest
|
||||
from werkzeug.urls import url_encode, url_fix, iri_to_uri, url_unquote, \
|
||||
url_unparse, url_parse
|
||||
from werkzeug.wsgi import get_host, get_current_url, ClosingIterator
|
||||
from werkzeug.utils import dump_cookie
|
||||
from werkzeug.datastructures import FileMultiDict, MultiDict, \
|
||||
CombinedMultiDict, Headers, FileStorage
|
||||
|
||||
|
||||
def stream_encode_multipart(values, use_tempfile=True, threshold=1024 * 500,
|
||||
boundary=None, charset='utf-8'):
|
||||
"""Encode a dict of values (either strings or file descriptors or
|
||||
:class:`FileStorage` objects.) into a multipart encoded string stored
|
||||
in a file descriptor.
|
||||
"""
|
||||
if boundary is None:
|
||||
boundary = '---------------WerkzeugFormPart_%s%s' % (time(), random())
|
||||
_closure = [BytesIO(), 0, False]
|
||||
|
||||
if use_tempfile:
|
||||
def write_binary(string):
|
||||
stream, total_length, on_disk = _closure
|
||||
if on_disk:
|
||||
stream.write(string)
|
||||
else:
|
||||
length = len(string)
|
||||
if length + _closure[1] <= threshold:
|
||||
stream.write(string)
|
||||
else:
|
||||
new_stream = TemporaryFile('wb+')
|
||||
new_stream.write(stream.getvalue())
|
||||
new_stream.write(string)
|
||||
_closure[0] = new_stream
|
||||
_closure[2] = True
|
||||
_closure[1] = total_length + length
|
||||
else:
|
||||
write_binary = _closure[0].write
|
||||
|
||||
def write(string):
|
||||
write_binary(string.encode(charset))
|
||||
|
||||
if not isinstance(values, MultiDict):
|
||||
values = MultiDict(values)
|
||||
|
||||
for key, values in iterlists(values):
|
||||
for value in values:
|
||||
write('--%s\r\nContent-Disposition: form-data; name="%s"' %
|
||||
(boundary, key))
|
||||
reader = getattr(value, 'read', None)
|
||||
if reader is not None:
|
||||
filename = getattr(value, 'filename',
|
||||
getattr(value, 'name', None))
|
||||
content_type = getattr(value, 'content_type', None)
|
||||
if content_type is None:
|
||||
content_type = filename and \
|
||||
mimetypes.guess_type(filename)[0] or \
|
||||
'application/octet-stream'
|
||||
if filename is not None:
|
||||
write('; filename="%s"\r\n' % filename)
|
||||
else:
|
||||
write('\r\n')
|
||||
write('Content-Type: %s\r\n\r\n' % content_type)
|
||||
while 1:
|
||||
chunk = reader(16384)
|
||||
if not chunk:
|
||||
break
|
||||
write_binary(chunk)
|
||||
else:
|
||||
if not isinstance(value, string_types):
|
||||
value = str(value)
|
||||
|
||||
value = to_bytes(value, charset)
|
||||
write('\r\n\r\n')
|
||||
write_binary(value)
|
||||
write('\r\n')
|
||||
write('--%s--\r\n' % boundary)
|
||||
|
||||
length = int(_closure[0].tell())
|
||||
_closure[0].seek(0)
|
||||
return _closure[0], length, boundary
|
||||
|
||||
|
||||
def encode_multipart(values, boundary=None, charset='utf-8'):
|
||||
"""Like `stream_encode_multipart` but returns a tuple in the form
|
||||
(``boundary``, ``data``) where data is a bytestring.
|
||||
"""
|
||||
stream, length, boundary = stream_encode_multipart(
|
||||
values, use_tempfile=False, boundary=boundary, charset=charset)
|
||||
return boundary, stream.read()
|
||||
|
||||
|
||||
def File(fd, filename=None, mimetype=None):
|
||||
"""Backwards compat."""
|
||||
from warnings import warn
|
||||
warn(DeprecationWarning('werkzeug.test.File is deprecated, use the '
|
||||
'EnvironBuilder or FileStorage instead'))
|
||||
return FileStorage(fd, filename=filename, content_type=mimetype)
|
||||
|
||||
|
||||
class _TestCookieHeaders(object):
|
||||
|
||||
"""A headers adapter for cookielib
|
||||
"""
|
||||
|
||||
def __init__(self, headers):
|
||||
self.headers = headers
|
||||
|
||||
def getheaders(self, name):
|
||||
headers = []
|
||||
name = name.lower()
|
||||
for k, v in self.headers:
|
||||
if k.lower() == name:
|
||||
headers.append(v)
|
||||
return headers
|
||||
|
||||
def get_all(self, name, default=None):
|
||||
rv = []
|
||||
for k, v in self.headers:
|
||||
if k.lower() == name.lower():
|
||||
rv.append(v)
|
||||
return rv or default or []
|
||||
|
||||
|
||||
class _TestCookieResponse(object):
|
||||
|
||||
"""Something that looks like a httplib.HTTPResponse, but is actually just an
|
||||
adapter for our test responses to make them available for cookielib.
|
||||
"""
|
||||
|
||||
def __init__(self, headers):
|
||||
self.headers = _TestCookieHeaders(headers)
|
||||
|
||||
def info(self):
|
||||
return self.headers
|
||||
|
||||
|
||||
class _TestCookieJar(CookieJar):
|
||||
|
||||
"""A cookielib.CookieJar modified to inject and read cookie headers from
|
||||
and to wsgi environments, and wsgi application responses.
|
||||
"""
|
||||
|
||||
def inject_wsgi(self, environ):
|
||||
"""Inject the cookies as client headers into the server's wsgi
|
||||
environment.
|
||||
"""
|
||||
cvals = []
|
||||
for cookie in self:
|
||||
cvals.append('%s=%s' % (cookie.name, cookie.value))
|
||||
if cvals:
|
||||
environ['HTTP_COOKIE'] = '; '.join(cvals)
|
||||
|
||||
def extract_wsgi(self, environ, headers):
|
||||
"""Extract the server's set-cookie headers as cookies into the
|
||||
cookie jar.
|
||||
"""
|
||||
self.extract_cookies(
|
||||
_TestCookieResponse(headers),
|
||||
U2Request(get_current_url(environ)),
|
||||
)
|
||||
|
||||
|
||||
def _iter_data(data):
|
||||
"""Iterates over a `dict` or :class:`MultiDict` yielding all keys and
|
||||
values.
|
||||
This is used to iterate over the data passed to the
|
||||
:class:`EnvironBuilder`.
|
||||
"""
|
||||
if isinstance(data, MultiDict):
|
||||
for key, values in iterlists(data):
|
||||
for value in values:
|
||||
yield key, value
|
||||
else:
|
||||
for key, values in iteritems(data):
|
||||
if isinstance(values, list):
|
||||
for value in values:
|
||||
yield key, value
|
||||
else:
|
||||
yield key, values
|
||||
|
||||
|
||||
class EnvironBuilder(object):
|
||||
|
||||
"""This class can be used to conveniently create a WSGI environment
|
||||
for testing purposes. It can be used to quickly create WSGI environments
|
||||
or request objects from arbitrary data.
|
||||
|
||||
The signature of this class is also used in some other places as of
|
||||
Werkzeug 0.5 (:func:`create_environ`, :meth:`BaseResponse.from_values`,
|
||||
:meth:`Client.open`). Because of this most of the functionality is
|
||||
available through the constructor alone.
|
||||
|
||||
Files and regular form data can be manipulated independently of each
|
||||
other with the :attr:`form` and :attr:`files` attributes, but are
|
||||
passed with the same argument to the constructor: `data`.
|
||||
|
||||
`data` can be any of these values:
|
||||
|
||||
- a `str` or `bytes` object: The object is converted into an
|
||||
:attr:`input_stream`, the :attr:`content_length` is set and you have to
|
||||
provide a :attr:`content_type`.
|
||||
- a `dict` or :class:`MultiDict`: The keys have to be strings. The values
|
||||
have to be either any of the following objects, or a list of any of the
|
||||
following objects:
|
||||
|
||||
- a :class:`file`-like object: These are converted into
|
||||
:class:`FileStorage` objects automatically.
|
||||
- a `tuple`: The :meth:`~FileMultiDict.add_file` method is called
|
||||
with the key and the unpacked `tuple` items as positional
|
||||
arguments.
|
||||
- a `str`: The string is set as form data for the associated key.
|
||||
- a file-like object: The object content is loaded in memory and then
|
||||
handled like a regular `str` or a `bytes`.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
`path` and `base_url` can now be unicode strings that are encoded using
|
||||
the :func:`iri_to_uri` function.
|
||||
|
||||
:param path: the path of the request. In the WSGI environment this will
|
||||
end up as `PATH_INFO`. If the `query_string` is not defined
|
||||
and there is a question mark in the `path` everything after
|
||||
it is used as query string.
|
||||
:param base_url: the base URL is a URL that is used to extract the WSGI
|
||||
URL scheme, host (server name + server port) and the
|
||||
script root (`SCRIPT_NAME`).
|
||||
:param query_string: an optional string or dict with URL parameters.
|
||||
:param method: the HTTP method to use, defaults to `GET`.
|
||||
:param input_stream: an optional input stream. Do not specify this and
|
||||
`data`. As soon as an input stream is set you can't
|
||||
modify :attr:`args` and :attr:`files` unless you
|
||||
set the :attr:`input_stream` to `None` again.
|
||||
:param content_type: The content type for the request. As of 0.5 you
|
||||
don't have to provide this when specifying files
|
||||
and form data via `data`.
|
||||
:param content_length: The content length for the request. You don't
|
||||
have to specify this when providing data via
|
||||
`data`.
|
||||
:param errors_stream: an optional error stream that is used for
|
||||
`wsgi.errors`. Defaults to :data:`stderr`.
|
||||
:param multithread: controls `wsgi.multithread`. Defaults to `False`.
|
||||
:param multiprocess: controls `wsgi.multiprocess`. Defaults to `False`.
|
||||
:param run_once: controls `wsgi.run_once`. Defaults to `False`.
|
||||
:param headers: an optional list or :class:`Headers` object of headers.
|
||||
:param data: a string or dict of form data or a file-object.
|
||||
See explanation above.
|
||||
:param environ_base: an optional dict of environment defaults.
|
||||
:param environ_overrides: an optional dict of environment overrides.
|
||||
:param charset: the charset used to encode unicode data.
|
||||
"""
|
||||
|
||||
#: the server protocol to use. defaults to HTTP/1.1
|
||||
server_protocol = 'HTTP/1.1'
|
||||
|
||||
#: the wsgi version to use. defaults to (1, 0)
|
||||
wsgi_version = (1, 0)
|
||||
|
||||
#: the default request class for :meth:`get_request`
|
||||
request_class = BaseRequest
|
||||
|
||||
def __init__(self, path='/', base_url=None, query_string=None,
|
||||
method='GET', input_stream=None, content_type=None,
|
||||
content_length=None, errors_stream=None, multithread=False,
|
||||
multiprocess=False, run_once=False, headers=None, data=None,
|
||||
environ_base=None, environ_overrides=None, charset='utf-8'):
|
||||
path_s = make_literal_wrapper(path)
|
||||
if query_string is None and path_s('?') in path:
|
||||
path, query_string = path.split(path_s('?'), 1)
|
||||
self.charset = charset
|
||||
self.path = iri_to_uri(path)
|
||||
if base_url is not None:
|
||||
base_url = url_fix(iri_to_uri(base_url, charset), charset)
|
||||
self.base_url = base_url
|
||||
if isinstance(query_string, (bytes, text_type)):
|
||||
self.query_string = query_string
|
||||
else:
|
||||
if query_string is None:
|
||||
query_string = MultiDict()
|
||||
elif not isinstance(query_string, MultiDict):
|
||||
query_string = MultiDict(query_string)
|
||||
self.args = query_string
|
||||
self.method = method
|
||||
if headers is None:
|
||||
headers = Headers()
|
||||
elif not isinstance(headers, Headers):
|
||||
headers = Headers(headers)
|
||||
self.headers = headers
|
||||
if content_type is not None:
|
||||
self.content_type = content_type
|
||||
if errors_stream is None:
|
||||
errors_stream = sys.stderr
|
||||
self.errors_stream = errors_stream
|
||||
self.multithread = multithread
|
||||
self.multiprocess = multiprocess
|
||||
self.run_once = run_once
|
||||
self.environ_base = environ_base
|
||||
self.environ_overrides = environ_overrides
|
||||
self.input_stream = input_stream
|
||||
self.content_length = content_length
|
||||
self.closed = False
|
||||
|
||||
if data:
|
||||
if input_stream is not None:
|
||||
raise TypeError('can\'t provide input stream and data')
|
||||
if hasattr(data, 'read'):
|
||||
data = data.read()
|
||||
if isinstance(data, text_type):
|
||||
data = data.encode(self.charset)
|
||||
if isinstance(data, bytes):
|
||||
self.input_stream = BytesIO(data)
|
||||
if self.content_length is None:
|
||||
self.content_length = len(data)
|
||||
else:
|
||||
for key, value in _iter_data(data):
|
||||
if isinstance(value, (tuple, dict)) or \
|
||||
hasattr(value, 'read'):
|
||||
self._add_file_from_data(key, value)
|
||||
else:
|
||||
self.form.setlistdefault(key).append(value)
|
||||
|
||||
def _add_file_from_data(self, key, value):
|
||||
"""Called in the EnvironBuilder to add files from the data dict."""
|
||||
if isinstance(value, tuple):
|
||||
self.files.add_file(key, *value)
|
||||
elif isinstance(value, dict):
|
||||
from warnings import warn
|
||||
warn(DeprecationWarning('it\'s no longer possible to pass dicts '
|
||||
'as `data`. Use tuples or FileStorage '
|
||||
'objects instead'), stacklevel=2)
|
||||
value = dict(value)
|
||||
mimetype = value.pop('mimetype', None)
|
||||
if mimetype is not None:
|
||||
value['content_type'] = mimetype
|
||||
self.files.add_file(key, **value)
|
||||
else:
|
||||
self.files.add_file(key, value)
|
||||
|
||||
def _get_base_url(self):
|
||||
return url_unparse((self.url_scheme, self.host,
|
||||
self.script_root, '', '')).rstrip('/') + '/'
|
||||
|
||||
def _set_base_url(self, value):
|
||||
if value is None:
|
||||
scheme = 'http'
|
||||
netloc = 'localhost'
|
||||
script_root = ''
|
||||
else:
|
||||
scheme, netloc, script_root, qs, anchor = url_parse(value)
|
||||
if qs or anchor:
|
||||
raise ValueError('base url must not contain a query string '
|
||||
'or fragment')
|
||||
self.script_root = script_root.rstrip('/')
|
||||
self.host = netloc
|
||||
self.url_scheme = scheme
|
||||
|
||||
base_url = property(_get_base_url, _set_base_url, doc='''
|
||||
The base URL is a URL that is used to extract the WSGI
|
||||
URL scheme, host (server name + server port) and the
|
||||
script root (`SCRIPT_NAME`).''')
|
||||
del _get_base_url, _set_base_url
|
||||
|
||||
def _get_content_type(self):
|
||||
ct = self.headers.get('Content-Type')
|
||||
if ct is None and not self._input_stream:
|
||||
if self._files:
|
||||
return 'multipart/form-data'
|
||||
elif self._form:
|
||||
return 'application/x-www-form-urlencoded'
|
||||
return None
|
||||
return ct
|
||||
|
||||
def _set_content_type(self, value):
|
||||
if value is None:
|
||||
self.headers.pop('Content-Type', None)
|
||||
else:
|
||||
self.headers['Content-Type'] = value
|
||||
|
||||
content_type = property(_get_content_type, _set_content_type, doc='''
|
||||
The content type for the request. Reflected from and to the
|
||||
:attr:`headers`. Do not set if you set :attr:`files` or
|
||||
:attr:`form` for auto detection.''')
|
||||
del _get_content_type, _set_content_type
|
||||
|
||||
def _get_content_length(self):
|
||||
return self.headers.get('Content-Length', type=int)
|
||||
|
||||
def _set_content_length(self, value):
|
||||
if value is None:
|
||||
self.headers.pop('Content-Length', None)
|
||||
else:
|
||||
self.headers['Content-Length'] = str(value)
|
||||
|
||||
content_length = property(_get_content_length, _set_content_length, doc='''
|
||||
The content length as integer. Reflected from and to the
|
||||
:attr:`headers`. Do not set if you set :attr:`files` or
|
||||
:attr:`form` for auto detection.''')
|
||||
del _get_content_length, _set_content_length
|
||||
|
||||
def form_property(name, storage, doc):
|
||||
key = '_' + name
|
||||
|
||||
def getter(self):
|
||||
if self._input_stream is not None:
|
||||
raise AttributeError('an input stream is defined')
|
||||
rv = getattr(self, key)
|
||||
if rv is None:
|
||||
rv = storage()
|
||||
setattr(self, key, rv)
|
||||
|
||||
return rv
|
||||
|
||||
def setter(self, value):
|
||||
self._input_stream = None
|
||||
setattr(self, key, value)
|
||||
return property(getter, setter, doc=doc)
|
||||
|
||||
form = form_property('form', MultiDict, doc='''
|
||||
A :class:`MultiDict` of form values.''')
|
||||
files = form_property('files', FileMultiDict, doc='''
|
||||
A :class:`FileMultiDict` of uploaded files. You can use the
|
||||
:meth:`~FileMultiDict.add_file` method to add new files to the
|
||||
dict.''')
|
||||
del form_property
|
||||
|
||||
def _get_input_stream(self):
|
||||
return self._input_stream
|
||||
|
||||
def _set_input_stream(self, value):
|
||||
self._input_stream = value
|
||||
self._form = self._files = None
|
||||
|
||||
input_stream = property(_get_input_stream, _set_input_stream, doc='''
|
||||
An optional input stream. If you set this it will clear
|
||||
:attr:`form` and :attr:`files`.''')
|
||||
del _get_input_stream, _set_input_stream
|
||||
|
||||
def _get_query_string(self):
|
||||
if self._query_string is None:
|
||||
if self._args is not None:
|
||||
return url_encode(self._args, charset=self.charset)
|
||||
return ''
|
||||
return self._query_string
|
||||
|
||||
def _set_query_string(self, value):
|
||||
self._query_string = value
|
||||
self._args = None
|
||||
|
||||
query_string = property(_get_query_string, _set_query_string, doc='''
|
||||
The query string. If you set this to a string :attr:`args` will
|
||||
no longer be available.''')
|
||||
del _get_query_string, _set_query_string
|
||||
|
||||
def _get_args(self):
|
||||
if self._query_string is not None:
|
||||
raise AttributeError('a query string is defined')
|
||||
if self._args is None:
|
||||
self._args = MultiDict()
|
||||
return self._args
|
||||
|
||||
def _set_args(self, value):
|
||||
self._query_string = None
|
||||
self._args = value
|
||||
|
||||
args = property(_get_args, _set_args, doc='''
|
||||
The URL arguments as :class:`MultiDict`.''')
|
||||
del _get_args, _set_args
|
||||
|
||||
@property
|
||||
def server_name(self):
|
||||
"""The server name (read-only, use :attr:`host` to set)"""
|
||||
return self.host.split(':', 1)[0]
|
||||
|
||||
@property
|
||||
def server_port(self):
|
||||
"""The server port as integer (read-only, use :attr:`host` to set)"""
|
||||
pieces = self.host.split(':', 1)
|
||||
if len(pieces) == 2 and pieces[1].isdigit():
|
||||
return int(pieces[1])
|
||||
elif self.url_scheme == 'https':
|
||||
return 443
|
||||
return 80
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
"""Closes all files. If you put real :class:`file` objects into the
|
||||
:attr:`files` dict you can call this method to automatically close
|
||||
them all in one go.
|
||||
"""
|
||||
if self.closed:
|
||||
return
|
||||
try:
|
||||
files = itervalues(self.files)
|
||||
except AttributeError:
|
||||
files = ()
|
||||
for f in files:
|
||||
try:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.closed = True
|
||||
|
||||
def get_environ(self):
|
||||
"""Return the built environ."""
|
||||
input_stream = self.input_stream
|
||||
content_length = self.content_length
|
||||
content_type = self.content_type
|
||||
|
||||
if input_stream is not None:
|
||||
start_pos = input_stream.tell()
|
||||
input_stream.seek(0, 2)
|
||||
end_pos = input_stream.tell()
|
||||
input_stream.seek(start_pos)
|
||||
content_length = end_pos - start_pos
|
||||
elif content_type == 'multipart/form-data':
|
||||
values = CombinedMultiDict([self.form, self.files])
|
||||
input_stream, content_length, boundary = \
|
||||
stream_encode_multipart(values, charset=self.charset)
|
||||
content_type += '; boundary="%s"' % boundary
|
||||
elif content_type == 'application/x-www-form-urlencoded':
|
||||
# XXX: py2v3 review
|
||||
values = url_encode(self.form, charset=self.charset)
|
||||
values = values.encode('ascii')
|
||||
content_length = len(values)
|
||||
input_stream = BytesIO(values)
|
||||
else:
|
||||
input_stream = _empty_stream
|
||||
|
||||
result = {}
|
||||
if self.environ_base:
|
||||
result.update(self.environ_base)
|
||||
|
||||
def _path_encode(x):
|
||||
return wsgi_encoding_dance(url_unquote(x, self.charset), self.charset)
|
||||
|
||||
qs = wsgi_encoding_dance(self.query_string)
|
||||
|
||||
result.update({
|
||||
'REQUEST_METHOD': self.method,
|
||||
'SCRIPT_NAME': _path_encode(self.script_root),
|
||||
'PATH_INFO': _path_encode(self.path),
|
||||
'QUERY_STRING': qs,
|
||||
'SERVER_NAME': self.server_name,
|
||||
'SERVER_PORT': str(self.server_port),
|
||||
'HTTP_HOST': self.host,
|
||||
'SERVER_PROTOCOL': self.server_protocol,
|
||||
'CONTENT_TYPE': content_type or '',
|
||||
'CONTENT_LENGTH': str(content_length or '0'),
|
||||
'wsgi.version': self.wsgi_version,
|
||||
'wsgi.url_scheme': self.url_scheme,
|
||||
'wsgi.input': input_stream,
|
||||
'wsgi.errors': self.errors_stream,
|
||||
'wsgi.multithread': self.multithread,
|
||||
'wsgi.multiprocess': self.multiprocess,
|
||||
'wsgi.run_once': self.run_once
|
||||
})
|
||||
for key, value in self.headers.to_wsgi_list():
|
||||
result['HTTP_%s' % key.upper().replace('-', '_')] = value
|
||||
if self.environ_overrides:
|
||||
result.update(self.environ_overrides)
|
||||
return result
|
||||
|
||||
def get_request(self, cls=None):
|
||||
"""Returns a request with the data. If the request class is not
|
||||
specified :attr:`request_class` is used.
|
||||
|
||||
:param cls: The request wrapper to use.
|
||||
"""
|
||||
if cls is None:
|
||||
cls = self.request_class
|
||||
return cls(self.get_environ())
|
||||
|
||||
|
||||
class ClientRedirectError(Exception):
|
||||
|
||||
"""
|
||||
If a redirect loop is detected when using follow_redirects=True with
|
||||
the :cls:`Client`, then this exception is raised.
|
||||
"""
|
||||
|
||||
|
||||
class Client(object):
|
||||
|
||||
"""This class allows to send requests to a wrapped application.
|
||||
|
||||
The response wrapper can be a class or factory function that takes
|
||||
three arguments: app_iter, status and headers. The default response
|
||||
wrapper just returns a tuple.
|
||||
|
||||
Example::
|
||||
|
||||
class ClientResponse(BaseResponse):
|
||||
...
|
||||
|
||||
client = Client(MyApplication(), response_wrapper=ClientResponse)
|
||||
|
||||
The use_cookies parameter indicates whether cookies should be stored and
|
||||
sent for subsequent requests. This is True by default, but passing False
|
||||
will disable this behaviour.
|
||||
|
||||
If you want to request some subdomain of your application you may set
|
||||
`allow_subdomain_redirects` to `True` as if not no external redirects
|
||||
are allowed.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
`use_cookies` is new in this version. Older versions did not provide
|
||||
builtin cookie support.
|
||||
"""
|
||||
|
||||
def __init__(self, application, response_wrapper=None, use_cookies=True,
|
||||
allow_subdomain_redirects=False):
|
||||
self.application = application
|
||||
self.response_wrapper = response_wrapper
|
||||
if use_cookies:
|
||||
self.cookie_jar = _TestCookieJar()
|
||||
else:
|
||||
self.cookie_jar = None
|
||||
self.allow_subdomain_redirects = allow_subdomain_redirects
|
||||
|
||||
def set_cookie(self, server_name, key, value='', max_age=None,
|
||||
expires=None, path='/', domain=None, secure=None,
|
||||
httponly=False, charset='utf-8'):
|
||||
"""Sets a cookie in the client's cookie jar. The server name
|
||||
is required and has to match the one that is also passed to
|
||||
the open call.
|
||||
"""
|
||||
assert self.cookie_jar is not None, 'cookies disabled'
|
||||
header = dump_cookie(key, value, max_age, expires, path, domain,
|
||||
secure, httponly, charset)
|
||||
environ = create_environ(path, base_url='http://' + server_name)
|
||||
headers = [('Set-Cookie', header)]
|
||||
self.cookie_jar.extract_wsgi(environ, headers)
|
||||
|
||||
def delete_cookie(self, server_name, key, path='/', domain=None):
|
||||
"""Deletes a cookie in the test client."""
|
||||
self.set_cookie(server_name, key, expires=0, max_age=0,
|
||||
path=path, domain=domain)
|
||||
|
||||
def run_wsgi_app(self, environ, buffered=False):
|
||||
"""Runs the wrapped WSGI app with the given environment."""
|
||||
if self.cookie_jar is not None:
|
||||
self.cookie_jar.inject_wsgi(environ)
|
||||
rv = run_wsgi_app(self.application, environ, buffered=buffered)
|
||||
if self.cookie_jar is not None:
|
||||
self.cookie_jar.extract_wsgi(environ, rv[2])
|
||||
return rv
|
||||
|
||||
def resolve_redirect(self, response, new_location, environ, buffered=False):
|
||||
"""Resolves a single redirect and triggers the request again
|
||||
directly on this redirect client.
|
||||
"""
|
||||
scheme, netloc, script_root, qs, anchor = url_parse(new_location)
|
||||
base_url = url_unparse((scheme, netloc, '', '', '')).rstrip('/') + '/'
|
||||
|
||||
cur_server_name = netloc.split(':', 1)[0].split('.')
|
||||
real_server_name = get_host(environ).rsplit(':', 1)[0].split('.')
|
||||
if cur_server_name == ['']:
|
||||
# this is a local redirect having autocorrect_location_header=False
|
||||
cur_server_name = real_server_name
|
||||
base_url = EnvironBuilder(environ).base_url
|
||||
|
||||
if self.allow_subdomain_redirects:
|
||||
allowed = cur_server_name[-len(real_server_name):] == real_server_name
|
||||
else:
|
||||
allowed = cur_server_name == real_server_name
|
||||
|
||||
if not allowed:
|
||||
raise RuntimeError('%r does not support redirect to '
|
||||
'external targets' % self.__class__)
|
||||
|
||||
status_code = int(response[1].split(None, 1)[0])
|
||||
if status_code == 307:
|
||||
method = environ['REQUEST_METHOD']
|
||||
else:
|
||||
method = 'GET'
|
||||
|
||||
# For redirect handling we temporarily disable the response
|
||||
# wrapper. This is not threadsafe but not a real concern
|
||||
# since the test client must not be shared anyways.
|
||||
old_response_wrapper = self.response_wrapper
|
||||
self.response_wrapper = None
|
||||
try:
|
||||
return self.open(path=script_root, base_url=base_url,
|
||||
query_string=qs, as_tuple=True,
|
||||
buffered=buffered, method=method)
|
||||
finally:
|
||||
self.response_wrapper = old_response_wrapper
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
"""Takes the same arguments as the :class:`EnvironBuilder` class with
|
||||
some additions: You can provide a :class:`EnvironBuilder` or a WSGI
|
||||
environment as only argument instead of the :class:`EnvironBuilder`
|
||||
arguments and two optional keyword arguments (`as_tuple`, `buffered`)
|
||||
that change the type of the return value or the way the application is
|
||||
executed.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
If a dict is provided as file in the dict for the `data` parameter
|
||||
the content type has to be called `content_type` now instead of
|
||||
`mimetype`. This change was made for consistency with
|
||||
:class:`werkzeug.FileWrapper`.
|
||||
|
||||
The `follow_redirects` parameter was added to :func:`open`.
|
||||
|
||||
Additional parameters:
|
||||
|
||||
:param as_tuple: Returns a tuple in the form ``(environ, result)``
|
||||
:param buffered: Set this to True to buffer the application run.
|
||||
This will automatically close the application for
|
||||
you as well.
|
||||
:param follow_redirects: Set this to True if the `Client` should
|
||||
follow HTTP redirects.
|
||||
"""
|
||||
as_tuple = kwargs.pop('as_tuple', False)
|
||||
buffered = kwargs.pop('buffered', False)
|
||||
follow_redirects = kwargs.pop('follow_redirects', False)
|
||||
environ = None
|
||||
if not kwargs and len(args) == 1:
|
||||
if isinstance(args[0], EnvironBuilder):
|
||||
environ = args[0].get_environ()
|
||||
elif isinstance(args[0], dict):
|
||||
environ = args[0]
|
||||
if environ is None:
|
||||
builder = EnvironBuilder(*args, **kwargs)
|
||||
try:
|
||||
environ = builder.get_environ()
|
||||
finally:
|
||||
builder.close()
|
||||
|
||||
response = self.run_wsgi_app(environ, buffered=buffered)
|
||||
|
||||
# handle redirects
|
||||
redirect_chain = []
|
||||
while 1:
|
||||
status_code = int(response[1].split(None, 1)[0])
|
||||
if status_code not in (301, 302, 303, 305, 307) \
|
||||
or not follow_redirects:
|
||||
break
|
||||
new_location = response[2]['location']
|
||||
new_redirect_entry = (new_location, status_code)
|
||||
if new_redirect_entry in redirect_chain:
|
||||
raise ClientRedirectError('loop detected')
|
||||
redirect_chain.append(new_redirect_entry)
|
||||
environ, response = self.resolve_redirect(response, new_location,
|
||||
environ,
|
||||
buffered=buffered)
|
||||
|
||||
if self.response_wrapper is not None:
|
||||
response = self.response_wrapper(*response)
|
||||
if as_tuple:
|
||||
return environ, response
|
||||
return response
|
||||
|
||||
def get(self, *args, **kw):
|
||||
"""Like open but method is enforced to GET."""
|
||||
kw['method'] = 'GET'
|
||||
return self.open(*args, **kw)
|
||||
|
||||
def patch(self, *args, **kw):
|
||||
"""Like open but method is enforced to PATCH."""
|
||||
kw['method'] = 'PATCH'
|
||||
return self.open(*args, **kw)
|
||||
|
||||
def post(self, *args, **kw):
|
||||
"""Like open but method is enforced to POST."""
|
||||
kw['method'] = 'POST'
|
||||
return self.open(*args, **kw)
|
||||
|
||||
def head(self, *args, **kw):
|
||||
"""Like open but method is enforced to HEAD."""
|
||||
kw['method'] = 'HEAD'
|
||||
return self.open(*args, **kw)
|
||||
|
||||
def put(self, *args, **kw):
|
||||
"""Like open but method is enforced to PUT."""
|
||||
kw['method'] = 'PUT'
|
||||
return self.open(*args, **kw)
|
||||
|
||||
def delete(self, *args, **kw):
|
||||
"""Like open but method is enforced to DELETE."""
|
||||
kw['method'] = 'DELETE'
|
||||
return self.open(*args, **kw)
|
||||
|
||||
def options(self, *args, **kw):
|
||||
"""Like open but method is enforced to OPTIONS."""
|
||||
kw['method'] = 'OPTIONS'
|
||||
return self.open(*args, **kw)
|
||||
|
||||
def trace(self, *args, **kw):
|
||||
"""Like open but method is enforced to TRACE."""
|
||||
kw['method'] = 'TRACE'
|
||||
return self.open(*args, **kw)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (
|
||||
self.__class__.__name__,
|
||||
self.application
|
||||
)
|
||||
|
||||
|
||||
def create_environ(*args, **kwargs):
|
||||
"""Create a new WSGI environ dict based on the values passed. The first
|
||||
parameter should be the path of the request which defaults to '/'. The
|
||||
second one can either be an absolute path (in that case the host is
|
||||
localhost:80) or a full path to the request with scheme, netloc port and
|
||||
the path to the script.
|
||||
|
||||
This accepts the same arguments as the :class:`EnvironBuilder`
|
||||
constructor.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
This function is now a thin wrapper over :class:`EnvironBuilder` which
|
||||
was added in 0.5. The `headers`, `environ_base`, `environ_overrides`
|
||||
and `charset` parameters were added.
|
||||
"""
|
||||
builder = EnvironBuilder(*args, **kwargs)
|
||||
try:
|
||||
return builder.get_environ()
|
||||
finally:
|
||||
builder.close()
|
||||
|
||||
|
||||
def run_wsgi_app(app, environ, buffered=False):
|
||||
"""Return a tuple in the form (app_iter, status, headers) of the
|
||||
application output. This works best if you pass it an application that
|
||||
returns an iterator all the time.
|
||||
|
||||
Sometimes applications may use the `write()` callable returned
|
||||
by the `start_response` function. This tries to resolve such edge
|
||||
cases automatically. But if you don't get the expected output you
|
||||
should set `buffered` to `True` which enforces buffering.
|
||||
|
||||
If passed an invalid WSGI application the behavior of this function is
|
||||
undefined. Never pass non-conforming WSGI applications to this function.
|
||||
|
||||
:param app: the application to execute.
|
||||
:param buffered: set to `True` to enforce buffering.
|
||||
:return: tuple in the form ``(app_iter, status, headers)``
|
||||
"""
|
||||
environ = _get_environ(environ)
|
||||
response = []
|
||||
buffer = []
|
||||
|
||||
def start_response(status, headers, exc_info=None):
|
||||
if exc_info is not None:
|
||||
reraise(*exc_info)
|
||||
response[:] = [status, headers]
|
||||
return buffer.append
|
||||
|
||||
app_rv = app(environ, start_response)
|
||||
close_func = getattr(app_rv, 'close', None)
|
||||
app_iter = iter(app_rv)
|
||||
|
||||
# when buffering we emit the close call early and convert the
|
||||
# application iterator into a regular list
|
||||
if buffered:
|
||||
try:
|
||||
app_iter = list(app_iter)
|
||||
finally:
|
||||
if close_func is not None:
|
||||
close_func()
|
||||
|
||||
# otherwise we iterate the application iter until we have a response, chain
|
||||
# the already received data with the already collected data and wrap it in
|
||||
# a new `ClosingIterator` if we need to restore a `close` callable from the
|
||||
# original return value.
|
||||
else:
|
||||
while not response:
|
||||
buffer.append(next(app_iter))
|
||||
if buffer:
|
||||
app_iter = chain(buffer, app_iter)
|
||||
if close_func is not None and app_iter is not app_rv:
|
||||
app_iter = ClosingIterator(app_iter, close_func)
|
||||
|
||||
return app_iter, response[0], Headers(response[1])
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/test.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/test.pyc
Normal file
Binary file not shown.
230
venv/lib/python2.7/site-packages/werkzeug/testapp.py
Normal file
230
venv/lib/python2.7/site-packages/werkzeug/testapp.py
Normal file
@@ -0,0 +1,230 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.testapp
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Provide a small test application that can be used to test a WSGI server
|
||||
and check it for WSGI compliance.
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import werkzeug
|
||||
from textwrap import wrap
|
||||
from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response
|
||||
from werkzeug.utils import escape
|
||||
import base64
|
||||
|
||||
logo = Response(base64.b64decode('''
|
||||
R0lGODlhoACgAOMIAAEDACwpAEpCAGdgAJaKAM28AOnVAP3rAP/////////
|
||||
//////////////////////yH5BAEKAAgALAAAAACgAKAAAAT+EMlJq704680R+F0ojmRpnuj0rWnrv
|
||||
nB8rbRs33gu0bzu/0AObxgsGn3D5HHJbCUFyqZ0ukkSDlAidctNFg7gbI9LZlrBaHGtzAae0eloe25
|
||||
7w9EDOX2fst/xenyCIn5/gFqDiVVDV4aGeYiKkhSFjnCQY5OTlZaXgZp8nJ2ekaB0SQOjqphrpnOiq
|
||||
ncEn65UsLGytLVmQ6m4sQazpbtLqL/HwpnER8bHyLrLOc3Oz8PRONPU1crXN9na263dMt/g4SzjMeX
|
||||
m5yDpLqgG7OzJ4u8lT/P69ej3JPn69kHzN2OIAHkB9RUYSFCFQYQJFTIkCDBiwoXWGnowaLEjRm7+G
|
||||
p9A7Hhx4rUkAUaSLJlxHMqVMD/aSycSZkyTplCqtGnRAM5NQ1Ly5OmzZc6gO4d6DGAUKA+hSocWYAo
|
||||
SlM6oUWX2O/o0KdaVU5vuSQLAa0ADwQgMEMB2AIECZhVSnTno6spgbtXmHcBUrQACcc2FrTrWS8wAf
|
||||
78cMFBgwIBgbN+qvTt3ayikRBk7BoyGAGABAdYyfdzRQGV3l4coxrqQ84GpUBmrdR3xNIDUPAKDBSA
|
||||
ADIGDhhqTZIWaDcrVX8EsbNzbkvCOxG8bN5w8ly9H8jyTJHC6DFndQydbguh2e/ctZJFXRxMAqqPVA
|
||||
tQH5E64SPr1f0zz7sQYjAHg0In+JQ11+N2B0XXBeeYZgBZFx4tqBToiTCPv0YBgQv8JqA6BEf6RhXx
|
||||
w1ENhRBnWV8ctEX4Ul2zc3aVGcQNC2KElyTDYyYUWvShdjDyMOGMuFjqnII45aogPhz/CodUHFwaDx
|
||||
lTgsaOjNyhGWJQd+lFoAGk8ObghI0kawg+EV5blH3dr+digkYuAGSaQZFHFz2P/cTaLmhF52QeSb45
|
||||
Jwxd+uSVGHlqOZpOeJpCFZ5J+rkAkFjQ0N1tah7JJSZUFNsrkeJUJMIBi8jyaEKIhKPomnC91Uo+NB
|
||||
yyaJ5umnnpInIFh4t6ZSpGaAVmizqjpByDegYl8tPE0phCYrhcMWSv+uAqHfgH88ak5UXZmlKLVJhd
|
||||
dj78s1Fxnzo6yUCrV6rrDOkluG+QzCAUTbCwf9SrmMLzK6p+OPHx7DF+bsfMRq7Ec61Av9i6GLw23r
|
||||
idnZ+/OO0a99pbIrJkproCQMA17OPG6suq3cca5ruDfXCCDoS7BEdvmJn5otdqscn+uogRHHXs8cbh
|
||||
EIfYaDY1AkrC0cqwcZpnM6ludx72x0p7Fo/hZAcpJDjax0UdHavMKAbiKltMWCF3xxh9k25N/Viud8
|
||||
ba78iCvUkt+V6BpwMlErmcgc502x+u1nSxJSJP9Mi52awD1V4yB/QHONsnU3L+A/zR4VL/indx/y64
|
||||
gqcj+qgTeweM86f0Qy1QVbvmWH1D9h+alqg254QD8HJXHvjQaGOqEqC22M54PcftZVKVSQG9jhkv7C
|
||||
JyTyDoAJfPdu8v7DRZAxsP/ky9MJ3OL36DJfCFPASC3/aXlfLOOON9vGZZHydGf8LnxYJuuVIbl83y
|
||||
Az5n/RPz07E+9+zw2A2ahz4HxHo9Kt79HTMx1Q7ma7zAzHgHqYH0SoZWyTuOLMiHwSfZDAQTn0ajk9
|
||||
YQqodnUYjByQZhZak9Wu4gYQsMyEpIOAOQKze8CmEF45KuAHTvIDOfHJNipwoHMuGHBnJElUoDmAyX
|
||||
c2Qm/R8Ah/iILCCJOEokGowdhDYc/yoL+vpRGwyVSCWFYZNljkhEirGXsalWcAgOdeAdoXcktF2udb
|
||||
qbUhjWyMQxYO01o6KYKOr6iK3fE4MaS+DsvBsGOBaMb0Y6IxADaJhFICaOLmiWTlDAnY1KzDG4ambL
|
||||
cWBA8mUzjJsN2KjSaSXGqMCVXYpYkj33mcIApyhQf6YqgeNAmNvuC0t4CsDbSshZJkCS1eNisKqlyG
|
||||
cF8G2JeiDX6tO6Mv0SmjCa3MFb0bJaGPMU0X7c8XcpvMaOQmCajwSeY9G0WqbBmKv34DsMIEztU6Y2
|
||||
KiDlFdt6jnCSqx7Dmt6XnqSKaFFHNO5+FmODxMCWBEaco77lNDGXBM0ECYB/+s7nKFdwSF5hgXumQe
|
||||
EZ7amRg39RHy3zIjyRCykQh8Zo2iviRKyTDn/zx6EefptJj2Cw+Ep2FSc01U5ry4KLPYsTyWnVGnvb
|
||||
UpyGlhjBUljyjHhWpf8OFaXwhp9O4T1gU9UeyPPa8A2l0p1kNqPXEVRm1AOs1oAGZU596t6SOR2mcB
|
||||
Oco1srWtkaVrMUzIErrKri85keKqRQYX9VX0/eAUK1hrSu6HMEX3Qh2sCh0q0D2CtnUqS4hj62sE/z
|
||||
aDs2Sg7MBS6xnQeooc2R2tC9YrKpEi9pLXfYXp20tDCpSP8rKlrD4axprb9u1Df5hSbz9QU0cRpfgn
|
||||
kiIzwKucd0wsEHlLpe5yHXuc6FrNelOl7pY2+11kTWx7VpRu97dXA3DO1vbkhcb4zyvERYajQgAADs
|
||||
='''), mimetype='image/png')
|
||||
|
||||
|
||||
TEMPLATE = u'''\
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<title>WSGI Information</title>
|
||||
<style type="text/css">
|
||||
@import url(http://fonts.googleapis.com/css?family=Ubuntu);
|
||||
|
||||
body { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
||||
'Verdana', sans-serif; background-color: white; color: #000;
|
||||
font-size: 15px; text-align: center; }
|
||||
#logo { float: right; padding: 0 0 10px 10px; }
|
||||
div.box { text-align: left; width: 45em; margin: auto; padding: 50px 0;
|
||||
background-color: white; }
|
||||
h1, h2 { font-family: 'Ubuntu', 'Lucida Grande', 'Lucida Sans Unicode',
|
||||
'Geneva', 'Verdana', sans-serif; font-weight: normal; }
|
||||
h1 { margin: 0 0 30px 0; }
|
||||
h2 { font-size: 1.4em; margin: 1em 0 0.5em 0; }
|
||||
table { width: 100%%; border-collapse: collapse; border: 1px solid #AFC5C9 }
|
||||
table th { background-color: #AFC1C4; color: white; font-size: 0.72em;
|
||||
font-weight: normal; width: 18em; vertical-align: top;
|
||||
padding: 0.5em 0 0.1em 0.5em; }
|
||||
table td { border: 1px solid #AFC5C9; padding: 0.1em 0 0.1em 0.5em; }
|
||||
code { font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans Mono',
|
||||
monospace; font-size: 0.7em; }
|
||||
ul li { line-height: 1.5em; }
|
||||
ul.path { font-size: 0.7em; margin: 0 -30px; padding: 8px 30px;
|
||||
list-style: none; background: #E8EFF0; }
|
||||
ul.path li { line-height: 1.6em; }
|
||||
li.virtual { color: #999; text-decoration: underline; }
|
||||
li.exp { background: white; }
|
||||
</style>
|
||||
<div class="box">
|
||||
<img src="?resource=logo" id="logo" alt="[The Werkzeug Logo]" />
|
||||
<h1>WSGI Information</h1>
|
||||
<p>
|
||||
This page displays all available information about the WSGI server and
|
||||
the underlying Python interpreter.
|
||||
<h2 id="python-interpreter">Python Interpreter</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Python Version
|
||||
<td>%(python_version)s
|
||||
<tr>
|
||||
<th>Platform
|
||||
<td>%(platform)s [%(os)s]
|
||||
<tr>
|
||||
<th>API Version
|
||||
<td>%(api_version)s
|
||||
<tr>
|
||||
<th>Byteorder
|
||||
<td>%(byteorder)s
|
||||
<tr>
|
||||
<th>Werkzeug Version
|
||||
<td>%(werkzeug_version)s
|
||||
</table>
|
||||
<h2 id="wsgi-environment">WSGI Environment</h2>
|
||||
<table>%(wsgi_env)s</table>
|
||||
<h2 id="installed-eggs">Installed Eggs</h2>
|
||||
<p>
|
||||
The following python packages were installed on the system as
|
||||
Python eggs:
|
||||
<ul>%(python_eggs)s</ul>
|
||||
<h2 id="sys-path">System Path</h2>
|
||||
<p>
|
||||
The following paths are the current contents of the load path. The
|
||||
following entries are looked up for Python packages. Note that not
|
||||
all items in this path are folders. Gray and underlined items are
|
||||
entries pointing to invalid resources or used by custom import hooks
|
||||
such as the zip importer.
|
||||
<p>
|
||||
Items with a bright background were expanded for display from a relative
|
||||
path. If you encounter such paths in the output you might want to check
|
||||
your setup as relative paths are usually problematic in multithreaded
|
||||
environments.
|
||||
<ul class="path">%(sys_path)s</ul>
|
||||
</div>
|
||||
'''
|
||||
|
||||
|
||||
def iter_sys_path():
|
||||
if os.name == 'posix':
|
||||
def strip(x):
|
||||
prefix = os.path.expanduser('~')
|
||||
if x.startswith(prefix):
|
||||
x = '~' + x[len(prefix):]
|
||||
return x
|
||||
else:
|
||||
strip = lambda x: x
|
||||
|
||||
cwd = os.path.abspath(os.getcwd())
|
||||
for item in sys.path:
|
||||
path = os.path.join(cwd, item or os.path.curdir)
|
||||
yield strip(os.path.normpath(path)), \
|
||||
not os.path.isdir(path), path != item
|
||||
|
||||
|
||||
def render_testapp(req):
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
eggs = ()
|
||||
else:
|
||||
eggs = sorted(pkg_resources.working_set,
|
||||
key=lambda x: x.project_name.lower())
|
||||
python_eggs = []
|
||||
for egg in eggs:
|
||||
try:
|
||||
version = egg.version
|
||||
except (ValueError, AttributeError):
|
||||
version = 'unknown'
|
||||
python_eggs.append('<li>%s <small>[%s]</small>' % (
|
||||
escape(egg.project_name),
|
||||
escape(version)
|
||||
))
|
||||
|
||||
wsgi_env = []
|
||||
sorted_environ = sorted(req.environ.items(),
|
||||
key=lambda x: repr(x[0]).lower())
|
||||
for key, value in sorted_environ:
|
||||
wsgi_env.append('<tr><th>%s<td><code>%s</code>' % (
|
||||
escape(str(key)),
|
||||
' '.join(wrap(escape(repr(value))))
|
||||
))
|
||||
|
||||
sys_path = []
|
||||
for item, virtual, expanded in iter_sys_path():
|
||||
class_ = []
|
||||
if virtual:
|
||||
class_.append('virtual')
|
||||
if expanded:
|
||||
class_.append('exp')
|
||||
sys_path.append('<li%s>%s' % (
|
||||
class_ and ' class="%s"' % ' '.join(class_) or '',
|
||||
escape(item)
|
||||
))
|
||||
|
||||
return (TEMPLATE % {
|
||||
'python_version': '<br>'.join(escape(sys.version).splitlines()),
|
||||
'platform': escape(sys.platform),
|
||||
'os': escape(os.name),
|
||||
'api_version': sys.api_version,
|
||||
'byteorder': sys.byteorder,
|
||||
'werkzeug_version': werkzeug.__version__,
|
||||
'python_eggs': '\n'.join(python_eggs),
|
||||
'wsgi_env': '\n'.join(wsgi_env),
|
||||
'sys_path': '\n'.join(sys_path)
|
||||
}).encode('utf-8')
|
||||
|
||||
|
||||
def test_app(environ, start_response):
|
||||
"""Simple test application that dumps the environment. You can use
|
||||
it to check if Werkzeug is working properly:
|
||||
|
||||
.. sourcecode:: pycon
|
||||
|
||||
>>> from werkzeug.serving import run_simple
|
||||
>>> from werkzeug.testapp import test_app
|
||||
>>> run_simple('localhost', 3000, test_app)
|
||||
* Running on http://localhost:3000/
|
||||
|
||||
The application displays important information from the WSGI environment,
|
||||
the Python interpreter and the installed libraries.
|
||||
"""
|
||||
req = Request(environ, populate_request=False)
|
||||
if req.args.get('resource') == 'logo':
|
||||
response = logo
|
||||
else:
|
||||
response = Response(render_testapp(req), mimetype='text/html')
|
||||
return response(environ, start_response)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from werkzeug.serving import run_simple
|
||||
run_simple('localhost', 5000, test_app, use_reloader=True)
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/testapp.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/testapp.pyc
Normal file
Binary file not shown.
1004
venv/lib/python2.7/site-packages/werkzeug/urls.py
Normal file
1004
venv/lib/python2.7/site-packages/werkzeug/urls.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
venv/lib/python2.7/site-packages/werkzeug/urls.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/urls.pyc
Normal file
Binary file not shown.
202
venv/lib/python2.7/site-packages/werkzeug/useragents.py
Normal file
202
venv/lib/python2.7/site-packages/werkzeug/useragents.py
Normal file
@@ -0,0 +1,202 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.useragents
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides a helper to inspect user agent strings. This module
|
||||
is far from complete but should work for most of the currently available
|
||||
browsers.
|
||||
|
||||
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
|
||||
|
||||
class UserAgentParser(object):
|
||||
|
||||
"""A simple user agent parser. Used by the `UserAgent`."""
|
||||
|
||||
platforms = (
|
||||
('cros', 'chromeos'),
|
||||
('iphone|ios', 'iphone'),
|
||||
('ipad', 'ipad'),
|
||||
(r'darwin|mac|os\s*x', 'macos'),
|
||||
('win', 'windows'),
|
||||
(r'android', 'android'),
|
||||
('netbsd', 'netbsd'),
|
||||
('openbsd', 'openbsd'),
|
||||
('freebsd', 'freebsd'),
|
||||
('dragonfly', 'dragonflybsd'),
|
||||
('(sun|i86)os', 'solaris'),
|
||||
(r'x11|lin(\b|ux)?', 'linux'),
|
||||
(r'nintendo\s+wii', 'wii'),
|
||||
('irix', 'irix'),
|
||||
('hp-?ux', 'hpux'),
|
||||
('aix', 'aix'),
|
||||
('sco|unix_sv', 'sco'),
|
||||
('bsd', 'bsd'),
|
||||
('amiga', 'amiga'),
|
||||
('blackberry|playbook', 'blackberry'),
|
||||
('symbian', 'symbian')
|
||||
)
|
||||
browsers = (
|
||||
('googlebot', 'google'),
|
||||
('msnbot', 'msn'),
|
||||
('yahoo', 'yahoo'),
|
||||
('ask jeeves', 'ask'),
|
||||
(r'aol|america\s+online\s+browser', 'aol'),
|
||||
('opera', 'opera'),
|
||||
('chrome', 'chrome'),
|
||||
('seamonkey', 'seamonkey'),
|
||||
('firefox|firebird|phoenix|iceweasel', 'firefox'),
|
||||
('galeon', 'galeon'),
|
||||
('safari|version', 'safari'),
|
||||
('webkit', 'webkit'),
|
||||
('camino', 'camino'),
|
||||
('konqueror', 'konqueror'),
|
||||
('k-meleon', 'kmeleon'),
|
||||
('netscape', 'netscape'),
|
||||
(r'msie|microsoft\s+internet\s+explorer|trident/.+? rv:', 'msie'),
|
||||
('lynx', 'lynx'),
|
||||
('links', 'links'),
|
||||
('Baiduspider', 'baidu'),
|
||||
('bingbot', 'bing'),
|
||||
('mozilla', 'mozilla')
|
||||
)
|
||||
|
||||
_browser_version_re = r'(?:%s)[/\sa-z(]*(\d+[.\da-z]+)?'
|
||||
_language_re = re.compile(
|
||||
r'(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|'
|
||||
r'(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)'
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platforms]
|
||||
self.browsers = [(b, re.compile(self._browser_version_re % a, re.I))
|
||||
for a, b in self.browsers]
|
||||
|
||||
def __call__(self, user_agent):
|
||||
for platform, regex in self.platforms:
|
||||
match = regex.search(user_agent)
|
||||
if match is not None:
|
||||
break
|
||||
else:
|
||||
platform = None
|
||||
for browser, regex in self.browsers:
|
||||
match = regex.search(user_agent)
|
||||
if match is not None:
|
||||
version = match.group(1)
|
||||
break
|
||||
else:
|
||||
browser = version = None
|
||||
match = self._language_re.search(user_agent)
|
||||
if match is not None:
|
||||
language = match.group(1) or match.group(2)
|
||||
else:
|
||||
language = None
|
||||
return platform, browser, version, language
|
||||
|
||||
|
||||
class UserAgent(object):
|
||||
|
||||
"""Represents a user agent. Pass it a WSGI environment or a user agent
|
||||
string and you can inspect some of the details from the user agent
|
||||
string via the attributes. The following attributes exist:
|
||||
|
||||
.. attribute:: string
|
||||
|
||||
the raw user agent string
|
||||
|
||||
.. attribute:: platform
|
||||
|
||||
the browser platform. The following platforms are currently
|
||||
recognized:
|
||||
|
||||
- `aix`
|
||||
- `amiga`
|
||||
- `android`
|
||||
- `bsd`
|
||||
- `chromeos`
|
||||
- `hpux`
|
||||
- `iphone`
|
||||
- `ipad`
|
||||
- `irix`
|
||||
- `linux`
|
||||
- `macos`
|
||||
- `sco`
|
||||
- `solaris`
|
||||
- `wii`
|
||||
- `windows`
|
||||
|
||||
.. attribute:: browser
|
||||
|
||||
the name of the browser. The following browsers are currently
|
||||
recognized:
|
||||
|
||||
- `aol` *
|
||||
- `ask` *
|
||||
- `camino`
|
||||
- `chrome`
|
||||
- `firefox`
|
||||
- `galeon`
|
||||
- `google` *
|
||||
- `kmeleon`
|
||||
- `konqueror`
|
||||
- `links`
|
||||
- `lynx`
|
||||
- `msie`
|
||||
- `msn`
|
||||
- `netscape`
|
||||
- `opera`
|
||||
- `safari`
|
||||
- `seamonkey`
|
||||
- `webkit`
|
||||
- `yahoo` *
|
||||
|
||||
(Browsers maked with a star (``*``) are crawlers.)
|
||||
|
||||
.. attribute:: version
|
||||
|
||||
the version of the browser
|
||||
|
||||
.. attribute:: language
|
||||
|
||||
the language of the browser
|
||||
"""
|
||||
|
||||
_parser = UserAgentParser()
|
||||
|
||||
def __init__(self, environ_or_string):
|
||||
if isinstance(environ_or_string, dict):
|
||||
environ_or_string = environ_or_string.get('HTTP_USER_AGENT', '')
|
||||
self.string = environ_or_string
|
||||
self.platform, self.browser, self.version, self.language = \
|
||||
self._parser(environ_or_string)
|
||||
|
||||
def to_header(self):
|
||||
return self.string
|
||||
|
||||
def __str__(self):
|
||||
return self.string
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.browser)
|
||||
|
||||
__bool__ = __nonzero__
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r/%s>' % (
|
||||
self.__class__.__name__,
|
||||
self.browser,
|
||||
self.version
|
||||
)
|
||||
|
||||
|
||||
# conceptionally this belongs in this module but because we want to lazily
|
||||
# load the user agent module (which happens in wrappers.py) we have to import
|
||||
# it afterwards. The class itself has the module set to this module so
|
||||
# pickle, inspect and similar modules treat the object as if it was really
|
||||
# implemented here.
|
||||
from werkzeug.wrappers import UserAgentMixin # noqa
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/useragents.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/useragents.pyc
Normal file
Binary file not shown.
628
venv/lib/python2.7/site-packages/werkzeug/utils.py
Normal file
628
venv/lib/python2.7/site-packages/werkzeug/utils.py
Normal file
@@ -0,0 +1,628 @@
|
||||
# -*- 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) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import pkgutil
|
||||
try:
|
||||
from html.entities import name2codepoint
|
||||
except ImportError:
|
||||
from htmlentitydefs import name2codepoint
|
||||
|
||||
from werkzeug._compat import unichr, text_type, string_types, iteritems, \
|
||||
reraise, PY2
|
||||
from werkzeug._internal import _DictAccessorProperty, \
|
||||
_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(property):
|
||||
|
||||
"""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.
|
||||
"""
|
||||
|
||||
# implementation detail: A subclass of python's builtin property
|
||||
# decorator, we override __get__ to check for a cached value. 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):
|
||||
self.__name__ = name or func.__name__
|
||||
self.__module__ = func.__module__
|
||||
self.__doc__ = doc or func.__doc__
|
||||
self.func = func
|
||||
|
||||
def __set__(self, obj, value):
|
||||
obj.__dict__[self.__name__] = value
|
||||
|
||||
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>'
|
||||
"""
|
||||
|
||||
_entity_re = re.compile(r'&([^;]+);')
|
||||
_entities = name2codepoint.copy()
|
||||
_entities['apos'] = 39
|
||||
_empty_elements = set([
|
||||
'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame',
|
||||
'hr', 'img', 'input', 'keygen', 'isindex', 'link', 'meta', 'param',
|
||||
'source', 'wbr'
|
||||
])
|
||||
_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'])
|
||||
|
||||
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
|
||||
for key, value in iteritems(arguments):
|
||||
if value is None:
|
||||
continue
|
||||
if key[-1] == '_':
|
||||
key = key[:-1]
|
||||
if key in self._boolean_attributes:
|
||||
if not value:
|
||||
continue
|
||||
if self._dialect == 'xhtml':
|
||||
value = '="' + key + '"'
|
||||
else:
|
||||
value = ''
|
||||
else:
|
||||
value = '="' + escape(value) + '"'
|
||||
buffer += ' ' + key + value
|
||||
if not children and tag in self._empty_elements:
|
||||
if self._dialect == 'xhtml':
|
||||
buffer += ' />'
|
||||
else:
|
||||
buffer += '>'
|
||||
return buffer
|
||||
buffer += '>'
|
||||
|
||||
children_as_string = ''.join([text_type(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[*/' + \
|
||||
children_as_string + '/*]]>*/'
|
||||
buffer += children_as_string + '</' + tag + '>'
|
||||
return 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):
|
||||
"""Returns 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, string_types):
|
||||
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 systems 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, text_type):
|
||||
from unicodedata import normalize
|
||||
filename = normalize('NFKD', filename).encode('ascii', 'ignore')
|
||||
if not PY2:
|
||||
filename = filename.decode('ascii')
|
||||
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=None):
|
||||
"""Replace special characters "&", "<", ">" and (") to HTML-safe sequences.
|
||||
|
||||
There is a special handling for `None` which escapes to an empty string.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
`quote` is now implicitly on.
|
||||
|
||||
:param s: the string to escape.
|
||||
:param quote: ignored.
|
||||
"""
|
||||
if s is None:
|
||||
return ''
|
||||
elif hasattr(s, '__html__'):
|
||||
return text_type(s.__html__())
|
||||
elif not isinstance(s, string_types):
|
||||
s = text_type(s)
|
||||
if quote is not None:
|
||||
from warnings import warn
|
||||
warn(DeprecationWarning('quote parameter is implicit now'), stacklevel=2)
|
||||
s = s.replace('&', '&').replace('<', '<') \
|
||||
.replace('>', '>').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 redirect(location, code=302, Response=None):
|
||||
"""Returns 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.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
The class used for the Response object can now be passed in.
|
||||
|
||||
:param location: the location the response should redirect to.
|
||||
:param code: the redirect status code. defaults to 302.
|
||||
:param class Response: a Response class to use when instantiating a
|
||||
response. The default is :class:`werkzeug.wrappers.Response` if
|
||||
unspecified.
|
||||
"""
|
||||
if Response is None:
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
display_location = escape(location)
|
||||
if isinstance(location, text_type):
|
||||
# Safe conversion is necessary here as we might redirect
|
||||
# to a broken URI scheme (for instance itms-services).
|
||||
from werkzeug.urls import iri_to_uri
|
||||
location = iri_to_uri(location, safe_conversion=True)
|
||||
response = Response(
|
||||
'<!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.' %
|
||||
(escape(location), display_location), code, mimetype='text/html')
|
||||
response.headers['Location'] = location
|
||||
return response
|
||||
|
||||
|
||||
def append_slash_redirect(environ, code=301):
|
||||
"""Redirects 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
|
||||
# __import__ is not able to handle unicode strings in the fromlist
|
||||
# if the module is a package
|
||||
import_name = str(import_name).replace(':', '.')
|
||||
try:
|
||||
try:
|
||||
__import__(import_name)
|
||||
except ImportError:
|
||||
if '.' not in import_name:
|
||||
raise
|
||||
else:
|
||||
return sys.modules[import_name]
|
||||
|
||||
module_name, obj_name = import_name.rsplit('.', 1)
|
||||
try:
|
||||
module = __import__(module_name, None, None, [obj_name])
|
||||
except ImportError:
|
||||
# support importing modules not yet set up by the parent module
|
||||
# (or package for that matter)
|
||||
module = import_string(module_name)
|
||||
|
||||
try:
|
||||
return getattr(module, obj_name)
|
||||
except AttributeError as e:
|
||||
raise ImportError(e)
|
||||
|
||||
except ImportError as e:
|
||||
if not silent:
|
||||
reraise(
|
||||
ImportStringError,
|
||||
ImportStringError(import_name, e),
|
||||
sys.exc_info()[2])
|
||||
|
||||
|
||||
def find_modules(import_path, include_packages=False, recursive=False):
|
||||
"""Finds 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 importer, modname, ispkg in pkgutil.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):
|
||||
"""Checks 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.utils 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(next(iter(multikw))))
|
||||
values[kwarg_var] = extra
|
||||
elif extra:
|
||||
raise TypeError('got unexpected keyword argument ' +
|
||||
repr(next(iter(extra))))
|
||||
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)
|
||||
))
|
||||
|
||||
|
||||
class ImportStringError(ImportError):
|
||||
|
||||
"""Provides information about a failed :func:`import_string` attempt."""
|
||||
|
||||
#: String in dotted notation that failed to be imported.
|
||||
import_name = None
|
||||
#: Wrapped exception.
|
||||
exception = None
|
||||
|
||||
def __init__(self, import_name, exception):
|
||||
self.import_name = import_name
|
||||
self.exception = exception
|
||||
|
||||
msg = (
|
||||
'import_string() failed for %r. Possible reasons are:\n\n'
|
||||
'- missing __init__.py in a package;\n'
|
||||
'- package or module path not included in sys.path;\n'
|
||||
'- duplicated package or module name taking precedence in '
|
||||
'sys.path;\n'
|
||||
'- missing module, class, function or variable;\n\n'
|
||||
'Debugged import:\n\n%s\n\n'
|
||||
'Original exception:\n\n%s: %s')
|
||||
|
||||
name = ''
|
||||
tracked = []
|
||||
for part in import_name.replace(':', '.').split('.'):
|
||||
name += (name and '.') + part
|
||||
imported = import_string(name, silent=True)
|
||||
if imported:
|
||||
tracked.append((name, getattr(imported, '__file__', None)))
|
||||
else:
|
||||
track = ['- %r found in %r.' % (n, i) for n, i in tracked]
|
||||
track.append('- %r not found.' % name)
|
||||
msg = msg % (import_name, '\n'.join(track),
|
||||
exception.__class__.__name__, str(exception))
|
||||
break
|
||||
|
||||
ImportError.__init__(self, msg)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s(%r, %r)>' % (self.__class__.__name__, self.import_name,
|
||||
self.exception)
|
||||
|
||||
|
||||
# DEPRECATED
|
||||
# these objects were previously in this module as well. we import
|
||||
# them here for backwards compatibility with old pickles.
|
||||
from werkzeug.datastructures import ( # noqa
|
||||
MultiDict, CombinedMultiDict, Headers, EnvironHeaders)
|
||||
from werkzeug.http import parse_cookie, dump_cookie # noqa
|
||||
BIN
venv/lib/python2.7/site-packages/werkzeug/utils.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/utils.pyc
Normal file
Binary file not shown.
1970
venv/lib/python2.7/site-packages/werkzeug/wrappers.py
Normal file
1970
venv/lib/python2.7/site-packages/werkzeug/wrappers.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
venv/lib/python2.7/site-packages/werkzeug/wrappers.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/wrappers.pyc
Normal file
Binary file not shown.
1195
venv/lib/python2.7/site-packages/werkzeug/wsgi.py
Normal file
1195
venv/lib/python2.7/site-packages/werkzeug/wsgi.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
venv/lib/python2.7/site-packages/werkzeug/wsgi.pyc
Normal file
BIN
venv/lib/python2.7/site-packages/werkzeug/wsgi.pyc
Normal file
Binary file not shown.
Reference in New Issue
Block a user