0001"""
0002Lazy serialization and decoding wrappers for app_iter and wsgi.input
0003objects
0004"""
0005
0006from paste.util.filemixin import FileMixin
0007
0008class FileLengthWrapper(FileMixin):
0009    """
0010    Wraps a file-like object that has a fixed length, returning
0011    '' after the object has been exhausted.
0012
0013    Files are read-only
0014    """
0015
0016    def __init__(self, file, length):
0017        self.file = file
0018        self.length = length
0019        self._read = 0
0020        self.closed = False
0021        if hasattr(self.file, 'flush'):
0022            self.flush = self.file.flush
0023        if hasattr(self.file, 'seek'):
0024            self.seek = self._seek
0025        if hasattr(self.file, 'mode'):
0026            self.mode = self.file.mode
0027
0028    def read(self, size=None):
0029        if self.closed:
0030            raise Exception("File already closed")
0031        if size is None:
0032            size = self.length - self._read
0033        data = self.file.read(size)
0034        self._read += len(data)
0035        return data
0036
0037    def close(self):
0038        self.closed = True
0039        self.file.close()
0040
0041    def tell(self):
0042        return self._read
0043
0044    def _seek(self, offset):
0045        # @@ No whence
0046        self.file.seek(offset)
0047        self._read = offset
0048
0049class FileAppIterWrapper(FileMixin):
0050
0051    """
0052    Wraps a WSGI app_iter and presents a file-like interface to it.
0053
0054    This does not call ``app_iter.close()`` until you close the file
0055    itself.
0056    """
0057
0058    def __init__(self, app_iter):
0059        self.app_iter = app_iter
0060        self.app_iter_iter = iter(app_iter)
0061        self._buffer = ''
0062        self.closed = False
0063
0064    def read(self, size=None):
0065        if size is None:
0066            data = self._buffer + ''.join(self.app_iter)
0067            self._buffer = ''
0068            return data
0069        data = self._buffer
0070        data_size = len(data)
0071        while data_size < size:
0072            try:
0073                data += self.app_iter_iter.next()
0074            except StopIteration:
0075                # Reached end of app_iter
0076                break
0077        self._buffer = data[size:]
0078        return data[:size]
0079
0080    def close(self):
0081        if hasattr(self.app_iter, 'close'):
0082            self.app_iter.close()
0083        self.closed = True
0084
0085class LazySerialize(object):
0086
0087    """
0088    Iterator that serializes the data lazily, and returns the data as
0089    an iterator, but also allows the data to be retrieved without
0090    serialization.
0091    """
0092
0093    def __init__(self, format, content_type, data):
0094        self.format = format
0095        self._serialized_iter = None
0096        self.content_type = content_type
0097        self.decoded = (format.type, data)
0098
0099    def __iter__(self):
0100        return self
0101
0102    def httpencode_dump_iter(self, content_type=None):
0103        assert (content_type is None
0104                or self.content_type is None
0105                or content_type == self.content_type), (
0106            "content_type argument and constructor do not "
0107            "match (got %r in constructor, %r now)"
0108            % (self.content_type, content_type))
0109        return self.format.dump_iter(
0110            self.decoded[1],
0111            content_type or self.content_type)
0112
0113    def next(self):
0114        if self._serialized_iter is None:
0115            s_data = self.format.dump_iter(self.decoded[1], self.content_type)
0116            if isinstance(s_data, str):
0117                s_data = [s_data]
0118            self._serialized_iter = iter(s_data)
0119        return self._serialized_iter.next()
0120
0121class ReplacementInput(object):
0122
0123    """
0124    A replacement for a ``wsgi.input`` file that acts as a file, but
0125    lazily serializes the data.  Also, the data can be retrieved
0126    without serialization.
0127    """
0128
0129    def __init__(self, format, decoded):
0130        self.format = format
0131        assert isinstance(decoded, tuple), (
0132            "decoded should be a tuple of (output_type, data), not %r" % decoded)
0133        self.decoded = decoded
0134        self._leftover = None
0135        self._read_finished = False
0136
0137    def __repr__(self):
0138        return '<wsgi.input replacement serving %s>' % (
0139            self.decoded[0])
0140
0141    def __iter__(self):
0142        return self
0143
0144    def flush(self):
0145        pass
0146
0147    def next(self):
0148        return self.read()
0149
0150    def readline(self, size=None):
0151        if self._leftover is None:
0152            data = self.read()
0153            self._leftover = data.splitlines(True)
0154        if not self._leftover:
0155            return ''
0156        next = self._leftover.pop(0)
0157        return next
0158
0159    def xreadlines(self):
0160        return self
0161
0162    def read(self, size=None):
0163        if self._leftover is not None:
0164            next = ''.join(self._leftover)
0165            self._leftover = []
0166            return next
0167        if self._read_finished:
0168            return ''
0169        # We ignore size, but whatever...
0170        serialized = self.format.serialize(self.decoded[1])
0171        self._read_finished = True
0172        return serialized