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
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
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
0170 serialized = self.format.serialize(self.decoded[1])
0171 self._read_finished = True
0172 return serialized