Update Flask
This commit is contained in:
Executable → Regular
+1
-1
@@ -19,7 +19,7 @@ import sys
|
||||
|
||||
|
||||
# the version. Usually set automatically by a script.
|
||||
__version__ = '0.9-dev'
|
||||
__version__ = '0.8.3'
|
||||
|
||||
|
||||
# This import magic raises concerns quite often which is why the implementation
|
||||
|
||||
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
+16
-1
@@ -88,6 +88,7 @@ r"""
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
import cPickle as pickle
|
||||
from hmac import new as hmac
|
||||
from time import time
|
||||
@@ -97,7 +98,21 @@ from werkzeug.contrib.sessions import ModificationTrackingDict
|
||||
from werkzeug.security import safe_str_cmp
|
||||
|
||||
|
||||
from hashlib import sha1 as _default_hash
|
||||
# rather ugly way to import the correct hash method. Because
|
||||
# hmac either accepts modules with a new method (sha, md5 etc.)
|
||||
# or a hashlib factory function we have to figure out what to
|
||||
# pass to it. If we have 2.5 or higher (so not 2.4 with a
|
||||
# custom hashlib) we import from hashlib and fail if it does
|
||||
# not exist (have seen that in old OS X versions).
|
||||
# in all other cases the now deprecated sha module is used.
|
||||
_default_hash = None
|
||||
if sys.version_info >= (2, 5):
|
||||
try:
|
||||
from hashlib import sha1 as _default_hash
|
||||
except ImportError:
|
||||
pass
|
||||
if _default_hash is None:
|
||||
import sha as _default_hash
|
||||
|
||||
|
||||
class UnquoteError(Exception):
|
||||
|
||||
Executable → Regular
+4
-1
@@ -58,7 +58,10 @@ import tempfile
|
||||
from os import path
|
||||
from time import time
|
||||
from random import random
|
||||
from hashlib import sha1
|
||||
try:
|
||||
from hashlib import sha1
|
||||
except ImportError:
|
||||
from sha import new as sha1
|
||||
from cPickle import dump, load, HIGHEST_PROTOCOL
|
||||
|
||||
from werkzeug.datastructures import CallbackDict
|
||||
|
||||
Executable → Regular
Executable → Regular
Executable → Regular
+1
-1
@@ -2079,7 +2079,7 @@ class ETags(object):
|
||||
return etag in self._strong
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.star_tag or self._strong or self._weak)
|
||||
return bool(self.star_tag or self._strong)
|
||||
|
||||
def __str__(self):
|
||||
return self.to_header()
|
||||
|
||||
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
+3
-4
@@ -100,7 +100,7 @@ import posixpath
|
||||
from pprint import pformat
|
||||
from urlparse import urljoin
|
||||
|
||||
from werkzeug.urls import url_encode, url_quote
|
||||
from werkzeug.urls import url_encode, url_decode, url_quote
|
||||
from werkzeug.utils import redirect, format_string
|
||||
from werkzeug.exceptions import HTTPException, NotFound, MethodNotAllowed
|
||||
from werkzeug._internal import _get_environ
|
||||
@@ -715,7 +715,7 @@ class Rule(RuleFactory):
|
||||
return
|
||||
processed.add(data)
|
||||
else:
|
||||
add(url_quote(data, self.map.charset, safe='/:|+'))
|
||||
add(url_quote(data, self.map.charset, safe='/:|'))
|
||||
domain_part, url = (u''.join(tmp)).split('|', 1)
|
||||
|
||||
if append_unknown:
|
||||
@@ -1503,8 +1503,7 @@ class MapAdapter(object):
|
||||
self.url_scheme,
|
||||
self.get_host(domain_part),
|
||||
posixpath.join(self.script_name[:-1].lstrip('/'),
|
||||
url_quote(path_info.lstrip('/'), self.map.charset,
|
||||
safe='/:|+')),
|
||||
url_quote(path_info.lstrip('/'), self.map.charset)),
|
||||
suffix
|
||||
))
|
||||
|
||||
|
||||
Executable → Regular
Executable → Regular
+17
-17
@@ -14,6 +14,20 @@ import posixpath
|
||||
from itertools import izip
|
||||
from random import SystemRandom
|
||||
|
||||
# because the API of hmac changed with the introduction of the
|
||||
# new hashlib module, we have to support both. This sets up a
|
||||
# mapping to the digest factory functions and the digest modules
|
||||
# (or factory functions with changed API)
|
||||
try:
|
||||
from hashlib import sha1, md5
|
||||
_hash_funcs = _hash_mods = {'sha1': sha1, 'md5': md5}
|
||||
_sha1_mod = sha1
|
||||
_md5_mod = md5
|
||||
except ImportError:
|
||||
import sha as _sha1_mod, md5 as _md5_mod
|
||||
_hash_mods = {'sha1': _sha1_mod, 'md5': _md5_mod}
|
||||
_hash_funcs = {'sha1': _sha1_mod.new, 'md5': _md5_mod.new}
|
||||
|
||||
|
||||
SALT_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
|
||||
@@ -23,20 +37,6 @@ _os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
|
||||
if sep not in (None, '/'))
|
||||
|
||||
|
||||
def _find_hashlib_algorithms():
|
||||
import hashlib
|
||||
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 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.
|
||||
@@ -68,11 +68,11 @@ def _hash_internal(method, salt, password):
|
||||
if method == 'plain':
|
||||
return password
|
||||
if salt:
|
||||
if method not in _hash_funcs:
|
||||
if method not in _hash_mods:
|
||||
return None
|
||||
if isinstance(salt, unicode):
|
||||
salt = salt.encode('utf-8')
|
||||
h = hmac.new(salt, None, _hash_funcs[method])
|
||||
h = hmac.new(salt, None, _hash_mods[method])
|
||||
else:
|
||||
if method not in _hash_funcs:
|
||||
return None
|
||||
@@ -97,7 +97,7 @@ def generate_password_hash(password, method='sha1', salt_length=8):
|
||||
is used, hmac is used internally to salt the password.
|
||||
|
||||
:param password: the password to hash
|
||||
:param method: the hash method to use (one that hashlib supports)
|
||||
:param method: the hash method to use (``'md5'`` or ``'sha1'``)
|
||||
:param salt_length: the lengt of the salt in letters
|
||||
"""
|
||||
salt = method != 'plain' and gen_salt(salt_length) or ''
|
||||
|
||||
Executable → Regular
+5
-69
@@ -35,8 +35,6 @@
|
||||
:copyright: (c) 2011 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
|
||||
@@ -252,13 +250,10 @@ class WSGIRequestHandler(BaseHTTPRequestHandler, object):
|
||||
BaseRequestHandler = WSGIRequestHandler
|
||||
|
||||
|
||||
def generate_adhoc_ssl_pair(cn=None):
|
||||
def generate_adhoc_ssl_context():
|
||||
"""Generates an adhoc SSL context for the development server."""
|
||||
from random import random
|
||||
from OpenSSL import crypto
|
||||
|
||||
# pretty damn sure that this is not actually accepted by anyone
|
||||
if cn is None:
|
||||
cn = '*'
|
||||
from OpenSSL import crypto, SSL
|
||||
|
||||
cert = crypto.X509()
|
||||
cert.set_serial_number(int(random() * sys.maxint))
|
||||
@@ -266,7 +261,7 @@ def generate_adhoc_ssl_pair(cn=None):
|
||||
cert.gmtime_adj_notAfter(60 * 60 * 24 * 365)
|
||||
|
||||
subject = cert.get_subject()
|
||||
subject.CN = cn
|
||||
subject.CN = '*'
|
||||
subject.O = 'Dummy Certificate'
|
||||
|
||||
issuer = cert.get_issuer()
|
||||
@@ -278,59 +273,10 @@ def generate_adhoc_ssl_pair(cn=None):
|
||||
cert.set_pubkey(pkey)
|
||||
cert.sign(pkey, 'md5')
|
||||
|
||||
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, 'w') as f:
|
||||
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
with open(pkey_file, 'w') 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."""
|
||||
from OpenSSL import SSL
|
||||
pkey, cert = generate_adhoc_ssl_pair()
|
||||
ctx = SSL.Context(SSL.SSLv23_METHOD)
|
||||
ctx.use_privatekey(pkey)
|
||||
ctx.use_certificate(cert)
|
||||
return ctx
|
||||
|
||||
|
||||
def load_ssl_context(cert_file, pkey_file):
|
||||
"""Loads an SSL context from a certificate and private key file."""
|
||||
from OpenSSL import SSL
|
||||
ctx = SSL.Context(SSL.SSLv23_METHOD)
|
||||
ctx.use_certificate_file(cert_file)
|
||||
ctx.use_privatekey_file(pkey_file)
|
||||
return ctx
|
||||
|
||||
|
||||
@@ -354,9 +300,6 @@ class _SSLConnectionFix(object):
|
||||
def __getattr__(self, attrib):
|
||||
return getattr(self._con, attrib)
|
||||
|
||||
def shutdown(self, arg=None):
|
||||
self._con.shutdown()
|
||||
|
||||
|
||||
def select_ip_version(host, port):
|
||||
"""Returns AF_INET4 or AF_INET6 depending on where to connect to."""
|
||||
@@ -399,8 +342,6 @@ class BaseWSGIServer(HTTPServer, object):
|
||||
except ImportError:
|
||||
raise TypeError('SSL is not available if the OpenSSL '
|
||||
'library is not installed.')
|
||||
if isinstance(ssl_context, tuple):
|
||||
ssl_context = load_ssl_context(*ssl_context)
|
||||
if ssl_context == 'adhoc':
|
||||
ssl_context = generate_adhoc_ssl_context()
|
||||
self.socket = tsafe.Connection(ssl_context, self.socket)
|
||||
@@ -614,10 +555,6 @@ def run_simple(hostname, port, application, use_reloader=False,
|
||||
.. 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.
|
||||
|
||||
: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
|
||||
@@ -645,8 +582,7 @@ def run_simple(hostname, port, application, use_reloader=False,
|
||||
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 OpenSSL
|
||||
context, a tuple in the form ``(cert_file, pkey_file)``,
|
||||
the string ``'adhoc'`` if the server should
|
||||
context, the string ``'adhoc'`` if the server should
|
||||
automatically create one, or `None` to disable SSL
|
||||
(which is the default).
|
||||
"""
|
||||
|
||||
Executable → Regular
Executable → Regular
+55
-59
@@ -474,10 +474,7 @@ class EnvironBuilder(object):
|
||||
return 80
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
"""Closes all files. If you put real :class:`file` objects into the
|
||||
@@ -603,11 +600,14 @@ class Client(object):
|
||||
def __init__(self, application, response_wrapper=None, use_cookies=True,
|
||||
allow_subdomain_redirects=False):
|
||||
self.application = application
|
||||
if response_wrapper is None:
|
||||
response_wrapper = lambda a, s, h: (a, s, h)
|
||||
self.response_wrapper = response_wrapper
|
||||
if use_cookies:
|
||||
self.cookie_jar = _TestCookieJar()
|
||||
else:
|
||||
self.cookie_jar = None
|
||||
self.redirect_client = None
|
||||
self.allow_subdomain_redirects = allow_subdomain_redirects
|
||||
|
||||
def set_cookie(self, server_name, key, value='', max_age=None,
|
||||
@@ -629,46 +629,6 @@ class Client(object):
|
||||
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 = urlparse.urlsplit(new_location)
|
||||
base_url = urlparse.urlunsplit((scheme, netloc, '', '', '')).rstrip('/') + '/'
|
||||
|
||||
cur_server_name = netloc.split(':', 1)[0].split('.')
|
||||
real_server_name = get_host(environ).rsplit(':', 1)[0].split('.')
|
||||
|
||||
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__)
|
||||
|
||||
# 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)
|
||||
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
|
||||
@@ -710,25 +670,61 @@ class Client(object):
|
||||
finally:
|
||||
builder.close()
|
||||
|
||||
response = self.run_wsgi_app(environ, buffered=buffered)
|
||||
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])
|
||||
|
||||
# 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 = Headers.linked(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)
|
||||
status_code = int(rv[1].split(None, 1)[0])
|
||||
while status_code in (301, 302, 303, 305, 307) and follow_redirects:
|
||||
if not self.redirect_client:
|
||||
# assume that we're not using the user defined response wrapper
|
||||
# so that we don't need any ugly hacks to get the status
|
||||
# code from the response.
|
||||
self.redirect_client = Client(self.application)
|
||||
self.redirect_client.cookie_jar = self.cookie_jar
|
||||
|
||||
if self.response_wrapper is not None:
|
||||
response = self.response_wrapper(*response)
|
||||
redirect = dict(rv[2])['Location']
|
||||
|
||||
scheme, netloc, script_root, qs, anchor = urlparse.urlsplit(redirect)
|
||||
base_url = urlparse.urlunsplit((scheme, netloc, '', '', '')).rstrip('/') + '/'
|
||||
|
||||
cur_server_name = netloc.split(':', 1)[0].split('.')
|
||||
real_server_name = get_host(environ).split(':', 1)[0].split('.')
|
||||
|
||||
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__)
|
||||
|
||||
redirect_chain.append((redirect, status_code))
|
||||
|
||||
# the redirect request should be a new request, and not be based on
|
||||
# the old request
|
||||
|
||||
redirect_kwargs = {
|
||||
'path': script_root,
|
||||
'base_url': base_url,
|
||||
'query_string': qs,
|
||||
'as_tuple': True,
|
||||
'buffered': buffered,
|
||||
'follow_redirects': False,
|
||||
}
|
||||
environ, rv = self.redirect_client.open(**redirect_kwargs)
|
||||
status_code = int(rv[1].split(None, 1)[0])
|
||||
|
||||
# Prevent loops
|
||||
if redirect_chain[-1] in redirect_chain[:-1]:
|
||||
raise ClientRedirectError("loop detected")
|
||||
|
||||
response = self.response_wrapper(*rv)
|
||||
if as_tuple:
|
||||
return environ, response
|
||||
return response
|
||||
|
||||
Executable → Regular
Executable → Regular
+1
-1
@@ -141,7 +141,7 @@ def iri_to_uri(iri, charset='utf-8'):
|
||||
if port:
|
||||
hostname += ':' + port
|
||||
|
||||
path = _quote(path.encode(charset), safe="/:~+%")
|
||||
path = _quote(path.encode(charset), safe="/:~+")
|
||||
query = _quote(query.encode(charset), safe="=%&[]:;$()+,!?*/")
|
||||
|
||||
# this absolutely always must return a string. Otherwise some parts of
|
||||
|
||||
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
+37
-39
@@ -14,11 +14,10 @@ import urllib
|
||||
import urlparse
|
||||
import posixpath
|
||||
import mimetypes
|
||||
from itertools import chain, repeat
|
||||
from itertools import chain
|
||||
from zlib import adler32
|
||||
from time import time, mktime
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
|
||||
from werkzeug._internal import _patch_wrapper
|
||||
from werkzeug.http import is_resource_modified, http_date
|
||||
@@ -582,13 +581,6 @@ def make_limited_stream(stream, limit):
|
||||
return stream
|
||||
|
||||
|
||||
def make_chunk_iter_func(stream, limit, buffer_size):
|
||||
"""Helper for the line and chunk iter functions."""
|
||||
if hasattr(stream, 'read'):
|
||||
return partial(make_limited_stream(stream, limit).read, buffer_size)
|
||||
return iter(chain(stream, repeat(''))).next
|
||||
|
||||
|
||||
def make_line_iter(stream, limit=None, buffer_size=10 * 1024):
|
||||
"""Safely iterates line-based over an input stream. If the input stream
|
||||
is not a :class:`LimitedStream` the `limit` parameter is mandatory.
|
||||
@@ -605,31 +597,46 @@ def make_line_iter(stream, limit=None, buffer_size=10 * 1024):
|
||||
.. versionchanged:: 0.8
|
||||
This function now ensures that the limit was reached.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
added support for iterators as input stream.
|
||||
|
||||
:param stream: the stream or iterate to iterate over.
|
||||
:param stream: the stream to iterate over.
|
||||
:param limit: the limit in bytes for the stream. (Usually
|
||||
content length. Not necessary if the `stream`
|
||||
is a :class:`LimitedStream`.
|
||||
:param buffer_size: The optional buffer size.
|
||||
"""
|
||||
stream = make_limited_stream(stream, limit)
|
||||
def _iter_basic_lines():
|
||||
_read = make_chunk_iter_func(stream, limit, buffer_size)
|
||||
_read = stream.read
|
||||
buffer = []
|
||||
while 1:
|
||||
new_data = _read()
|
||||
if not new_data:
|
||||
if len(buffer) > 1:
|
||||
yield buffer.pop()
|
||||
continue
|
||||
|
||||
# we reverse the chunks because popping from the last
|
||||
# position of the list is O(1) and the number of chunks
|
||||
# read will be quite large for binary files.
|
||||
chunks = _read(buffer_size).splitlines(True)
|
||||
chunks.reverse()
|
||||
|
||||
first_chunk = buffer and buffer[0] or ''
|
||||
if chunks:
|
||||
if first_chunk and first_chunk[-1] in '\r\n':
|
||||
yield first_chunk
|
||||
first_chunk = ''
|
||||
first_chunk += chunks.pop()
|
||||
else:
|
||||
yield first_chunk
|
||||
break
|
||||
new_buf = []
|
||||
for item in chain(buffer, new_data.splitlines(True)):
|
||||
new_buf.append(item)
|
||||
if item and item[-1:] in '\r\n':
|
||||
yield ''.join(new_buf)
|
||||
new_buf = []
|
||||
buffer = new_buf
|
||||
if buffer:
|
||||
yield ''.join(buffer)
|
||||
|
||||
buffer = chunks
|
||||
|
||||
# in case the line is longer than the buffer size we
|
||||
# can't yield yet. This will only happen if the buffer
|
||||
# is empty.
|
||||
if not buffer and first_chunk[-1] not in '\r\n':
|
||||
buffer = [first_chunk]
|
||||
else:
|
||||
yield first_chunk
|
||||
|
||||
# This hackery is necessary to merge 'foo\r' and '\n' into one item
|
||||
# of 'foo\r\n' if we were unlucky and we hit a chunk boundary.
|
||||
@@ -648,26 +655,24 @@ def make_line_iter(stream, limit=None, buffer_size=10 * 1024):
|
||||
def make_chunk_iter(stream, separator, limit=None, buffer_size=10 * 1024):
|
||||
"""Works like :func:`make_line_iter` but accepts a separator
|
||||
which divides chunks. If you want newline based processing
|
||||
you should use :func:`make_limited_stream` instead as it
|
||||
you shuold use :func:`make_limited_stream` instead as it
|
||||
supports arbitrary newline markers.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
.. versionadded:: 0.9
|
||||
added support for iterators as input stream.
|
||||
|
||||
:param stream: the stream or iterate to iterate over.
|
||||
:param stream: the stream to iterate over.
|
||||
:param separator: the separator that divides chunks.
|
||||
:param limit: the limit in bytes for the stream. (Usually
|
||||
content length. Not necessary if the `stream`
|
||||
is a :class:`LimitedStream`.
|
||||
:param buffer_size: The optional buffer size.
|
||||
"""
|
||||
_read = make_chunk_iter_func(stream, limit, buffer_size)
|
||||
stream = make_limited_stream(stream, limit)
|
||||
_read = stream.read
|
||||
_split = re.compile(r'(%s)' % re.escape(separator)).split
|
||||
buffer = []
|
||||
while 1:
|
||||
new_data = _read()
|
||||
new_data = _read(buffer_size)
|
||||
if not new_data:
|
||||
break
|
||||
chunks = _split(new_data)
|
||||
@@ -840,13 +845,6 @@ class LimitedStream(object):
|
||||
last_pos = self._pos
|
||||
return result
|
||||
|
||||
def tell(self):
|
||||
"""Returns the position of the stream.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
return self._pos
|
||||
|
||||
def next(self):
|
||||
line = self.readline()
|
||||
if line is None:
|
||||
|
||||
Reference in New Issue
Block a user