123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- # -*- 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
|