1"""
2fs.filelike
3===========
4
5This module takes care of the groundwork for implementing and manipulating
6objects that provide a rich file-like interface, including reading, writing,
7seeking and iteration.
8
9The main class is FileLikeBase, which implements the entire file-like interface
10on top of primitive _read(), _write(), _seek(), _tell() and _truncate() methods.
11Subclasses may implement any or all of these methods to obtain the related
12higher-level file behaviors.
13
14Other useful classes include:
15
16    * StringIO:   a version of the builtin StringIO class, patched to more
17                  closely preserve the semantics of a standard file.
18
19    * FileWrapper:  a generic base class for wrappers around a filelike object
20                    (think e.g. compression or decryption).
21
22    * SpooledTemporaryFile:  a version of the builtin SpooledTemporaryFile
23                             class, patched to more closely preserve the
24                             semantics of a standard file.
25
26    * LimitBytesFile:  a filelike wrapper that limits the total bytes read
27                       from a file; useful for turning a socket into a file
28                       without reading past end-of-data.
29
30"""
31# Copyright (C) 2006-2009, Ryan Kelly
32# All rights reserved; available under the terms of the MIT License.
33
34import tempfile as _tempfile
35
36import fs
37
38
39class NotReadableError(IOError):
40    pass
41class NotWritableError(IOError):
42    pass
43class NotSeekableError(IOError):
44    pass
45class NotTruncatableError(IOError):
46    pass
47
48import six
49from six import PY3, b
50
51if PY3:
52    from six import BytesIO as _StringIO
53else:
54    try:
55        from cStringIO import StringIO as _StringIO
56    except ImportError:
57        from StringIO import StringIO as _StringIO
58
59
60class FileLikeBase(object):
61    """Base class for implementing file-like objects.
62
63    This class takes a lot of the legwork out of writing file-like objects
64    with a rich interface.  It implements the higher-level file-like methods
65    on top of five primitive methods: _read, _write, _seek, _tell and
66    _truncate. See their docstrings for precise details on how these methods
67    behave.
68
69    Subclasses then need only implement some subset of these methods for
70    rich file-like interface compatibility.  They may of course override
71    other methods as desired.
72
73    The class is missing the following attributes and methods, which don't
74    really make sense for anything but real files:
75
76        * fileno()
77        * isatty()
78        * encoding
79        * mode
80        * name
81        * newlines
82
83    Unlike standard file objects, all read methods share the same buffer
84    and so can be freely mixed (e.g. read(), readline(), next(), ...).
85
86    This class understands and will accept the following mode strings,
87    with any additional characters being ignored:
88
89        * r    - open the file for reading only.
90        * r+   - open the file for reading and writing.
91        * r-   - open the file for streamed reading; do not allow seek/tell.
92        * w    - open the file for writing only; create the file if
93                 it doesn't exist; truncate it to zero length.
94        * w+   - open the file for reading and writing; create the file
95                 if it doesn't exist; truncate it to zero length.
96        * w-   - open the file for streamed writing; do not allow seek/tell.
97        * a    - open the file for writing only; create the file if it
98                 doesn't exist; place pointer at end of file.
99        * a+   - open the file for reading and writing; create the file
100                 if it doesn't exist; place pointer at end of file.
101
102    These are mostly standard except for the "-" indicator, which has
103    been added for efficiency purposes in cases where seeking can be
104    expensive to simulate (e.g. compressed files).  Note that any file
105    opened for both reading and writing must also support seeking.
106    """
107
108    def __init__(self,bufsize=1024*64):
109        """FileLikeBase Constructor.
110
111        The optional argument 'bufsize' specifies the number of bytes to
112        read at a time when looking for a newline character.  Setting this to
113        a larger number when lines are long should improve efficiency.
114        """
115        super(FileLikeBase, self).__init__()
116        # File-like attributes
117        self.closed = False
118        self.softspace = 0
119        # Our own attributes
120        self._bufsize = bufsize  # buffer size for chunked reading
121        self._rbuffer = None     # data that's been read but not returned
122        self._wbuffer = None     # data that's been given but not written
123        self._sbuffer = None     # data between real & apparent file pos
124        self._soffset = 0        # internal offset of file pointer
125
126    #
127    #  The following five methods are the ones that subclasses are expected
128    #  to implement.  Carefully check their docstrings.
129    #
130
131    def _read(self,sizehint=-1):
132        """Read approximately <sizehint> bytes from the file-like object.
133
134        This method is to be implemented by subclasses that wish to be
135        readable.  It should read approximately <sizehint> bytes from the
136        file and return them as a string.  If <sizehint> is missing or
137        less than or equal to zero, try to read all the remaining contents.
138
139        The method need not guarantee any particular number of bytes -
140        it may return more bytes than requested, or fewer.  If needed the
141        size hint may be completely ignored.  It may even return an empty
142        string if no data is yet available.
143
144        Because of this, the method must return None to signify that EOF
145        has been reached.  The higher-level methods will never indicate EOF
146        until None has been read from _read().  Once EOF is reached, it
147        should be safe to call _read() again, immediately returning None.
148        """
149        raise NotReadableError("Object not readable")
150
151    def _write(self,string,flushing=False):
152        """Write the given string to the file-like object.
153
154        This method must be implemented by subclasses wishing to be writable.
155        It must attempt to write as much of the given data as possible to the
156        file, but need not guarantee that it is all written.  It may return
157        None to indicate that all data was written, or return as a string any
158        data that could not be written.
159
160        If the keyword argument 'flushing' is true, it indicates that the
161        internal write buffers are being flushed, and *all* the given data
162        is expected to be written to the file. If unwritten data is returned
163        when 'flushing' is true, an IOError will be raised.
164        """
165        raise NotWritableError("Object not writable")
166
167    def _seek(self,offset,whence):
168        """Set the file's internal position pointer, approximately.
169
170        This method should set the file's position to approximately 'offset'
171        bytes relative to the position specified by 'whence'.  If it is
172        not possible to position the pointer exactly at the given offset,
173        it should be positioned at a convenient *smaller* offset and the
174        file data between the real and apparent position should be returned.
175
176        At minimum, this method must implement the ability to seek to
177        the start of the file, i.e. offset=0 and whence=0.  If more
178        complex seeks are difficult to implement then it may raise
179        NotImplementedError to have them simulated (inefficiently) by
180        the higher-level machinery of this class.
181        """
182        raise NotSeekableError("Object not seekable")
183
184    def _tell(self):
185        """Get the location of the file's internal position pointer.
186
187        This method must be implemented by subclasses that wish to be
188        seekable, and must return the position of the file's internal
189        pointer.
190
191        Due to buffering, the position seen by users of this class
192        (the "apparent position") may be different to the position
193        returned by this method (the "actual position").
194        """
195        raise NotSeekableError("Object not seekable")
196
197    def _truncate(self,size):
198        """Truncate the file's size to <size>.
199
200        This method must be implemented by subclasses that wish to be
201        truncatable.  It must truncate the file to exactly the given size
202        or fail with an IOError.
203
204        Note that <size> will never be None; if it was not specified by the
205        user then it is calculated as the file's apparent position (which may
206        be different to its actual position due to buffering).
207        """
208        raise NotTruncatableError("Object not truncatable")
209
210    #
211    #  The following methods provide the public API of the filelike object.
212    #  Subclasses shouldn't need to mess with these (except perhaps for
213    #  close() and flush())
214    #
215
216    def _check_mode(self,mode,mstr=None):
217        """Check whether the file may be accessed in the given mode.
218
219        'mode' must be one of "r" or "w", and this function returns False
220        if the file-like object has a 'mode' attribute, and it does not
221        permit access in that mode.  If there is no 'mode' attribute,
222        it defaults to "r+".
223
224        If seek support is not required, use "r-" or "w-" as the mode string.
225
226        To check a mode string other than self.mode, pass it in as the
227        second argument.
228        """
229        if mstr is None:
230            try:
231                mstr = self.mode
232            except AttributeError:
233                mstr = "r+"
234        if "+" in mstr:
235            return True
236        if "-" in mstr and "-" not in mode:
237            return False
238        if "r" in mode:
239            if "r" not in mstr:
240                return False
241        if "w" in mode:
242            if "w" not in mstr and "a" not in mstr:
243                return False
244        return True
245
246    def _assert_mode(self,mode,mstr=None):
247        """Check whether the file may be accessed in the given mode.
248
249        This method is equivalent to _check_assert(), but raises IOError
250        instead of returning False.
251        """
252        if mstr is None:
253            try:
254                mstr = self.mode
255            except AttributeError:
256                mstr = "r+"
257        if "+" in mstr:
258            return True
259        if "-" in mstr and "-" not in mode:
260            raise NotSeekableError("File does not support seeking.")
261        if "r" in mode:
262            if "r" not in mstr:
263                raise NotReadableError("File not opened for reading")
264        if "w" in mode:
265            if "w" not in mstr and "a" not in mstr:
266                raise NotWritableError("File not opened for writing")
267        return True
268
269    def flush(self):
270        """Flush internal write buffer, if necessary."""
271        if self.closed:
272            raise IOError("File has been closed")
273        if self._check_mode("w-") and self._wbuffer is not None:
274            buffered = b("")
275            if self._sbuffer:
276                buffered = buffered + self._sbuffer
277                self._sbuffer = None
278            buffered = buffered + self._wbuffer
279            self._wbuffer = None
280            leftover = self._write(buffered,flushing=True)
281            if leftover and not isinstance(leftover, int):
282                raise IOError("Could not flush write buffer.")
283
284    def close(self):
285        """Flush write buffers and close the file.
286
287        The file may not be accessed further once it is closed.
288        """
289        #  Errors in subclass constructors can cause this to be called without
290        #  having called FileLikeBase.__init__().  Since we need the attrs it
291        #  initializes in cleanup, ensure we call it here.
292        if not hasattr(self,"closed"):
293            FileLikeBase.__init__(self)
294        if not self.closed:
295            self.flush()
296            self.closed = True
297
298    def __del__(self):
299        self.close()
300
301    def __enter__(self):
302        return self
303
304    def __exit__(self,exc_type,exc_val,exc_tb):
305        self.close()
306        return False
307
308    def next(self):
309        """next() method complying with the iterator protocol.
310
311        File-like objects are their own iterators, with each call to
312        next() returning subsequent lines from the file.
313        """
314        ln = self.readline()
315        if ln == b(""):
316            raise StopIteration()
317        return ln
318
319    def __iter__(self):
320        return self
321
322    def truncate(self,size=None):
323        """Truncate the file to the given size.
324
325        If <size> is not specified or is None, the current file position is
326        used.  Note that this method may fail at runtime if the underlying
327        filelike object is not truncatable.
328        """
329        if "-" in getattr(self,"mode",""):
330            raise NotTruncatableError("File is not seekable, can't truncate.")
331        if self._wbuffer:
332            self.flush()
333        if size is None:
334            size = self.tell()
335        self._truncate(size)
336
337    def seek(self,offset,whence=0):
338        """Move the internal file pointer to the given location."""
339        if whence > 2 or whence < 0:
340            raise ValueError("Invalid value for 'whence': " + str(whence))
341        if "-" in getattr(self,"mode",""):
342            raise NotSeekableError("File is not seekable.")
343        # Ensure that there's nothing left in the write buffer
344        if self._wbuffer:
345            self.flush()
346        # Adjust for any data left in the read buffer
347        if whence == 1 and self._rbuffer:
348            offset = offset - len(self._rbuffer)
349        self._rbuffer = None
350        # Adjust for any discrepancy in actual vs apparent seek position
351        if whence == 1:
352            if self._sbuffer:
353                offset = offset + len(self._sbuffer)
354            if self._soffset:
355                offset = offset + self._soffset
356        self._sbuffer = None
357        self._soffset = 0
358        # Shortcut the special case of staying put.
359        # As per posix, this has already cases the buffers to be flushed.
360        if offset == 0 and whence == 1:
361            return
362        # Catch any failed attempts to read while simulating seek
363        try:
364            # Try to do a whence-wise seek if it is implemented.
365            sbuf = None
366            try:
367                sbuf = self._seek(offset,whence)
368            except NotImplementedError:
369                # Try to simulate using an absolute seek.
370                try:
371                    if whence == 1:
372                        offset = self._tell() + offset
373                    elif whence == 2:
374                        if hasattr(self,"size"):
375                            offset = self.size + offset
376                        else:
377                            self._do_read_rest()
378                            offset = self.tell() + offset
379                    else:
380                        # absolute seek already failed, don't try again
381                        raise NotImplementedError
382                    sbuf = self._seek(offset,0)
383                except NotImplementedError:
384                    # Simulate by reseting to start
385                    self._seek(0,0)
386                    self._soffset = offset
387            finally:
388                self._sbuffer = sbuf
389        except NotReadableError:
390            raise NotSeekableError("File not readable, can't simulate seek")
391
392    def tell(self):
393        """Determine current position of internal file pointer."""
394        # Need to adjust for unread/unwritten data in buffers
395        pos = self._tell()
396        if self._rbuffer:
397            pos = pos - len(self._rbuffer)
398        if self._wbuffer:
399            pos = pos + len(self._wbuffer)
400        if self._sbuffer:
401            pos = pos + len(self._sbuffer)
402        if self._soffset:
403            pos = pos + self._soffset
404        return pos
405
406    def read(self,size=-1):
407        """Read at most 'size' bytes from the file.
408
409        Bytes are returned as a string.  If 'size' is negative, zero or
410        missing, the remainder of the file is read.  If EOF is encountered
411        immediately, the empty string is returned.
412        """
413        if self.closed:
414            raise IOError("File has been closed")
415        self._assert_mode("r-")
416        return self._do_read(size)
417
418    def _do_read(self,size):
419        """Private method to read from the file.
420
421        This method behaves the same as self.read(), but skips some
422        permission and sanity checks.  It is intended for use in simulating
423        seek(), where we may want to read (and discard) information from
424        a file not opened in read mode.
425
426        Note that this may still fail if the file object actually can't
427        be read from - it just won't check whether the mode string gives
428        permission.
429        """
430        # If we were previously writing, ensure position is correct
431        if self._wbuffer is not None:
432            self.seek(0,1)
433        # Discard any data that should have been seeked over
434        if self._sbuffer:
435            s = len(self._sbuffer)
436            self._sbuffer = None
437            self.read(s)
438        elif self._soffset:
439            s = self._soffset
440            self._soffset = 0
441            while s > self._bufsize:
442                self._do_read(self._bufsize)
443                s -= self._bufsize
444            self._do_read(s)
445        # Should the entire file be read?
446        if size < 0:
447            if self._rbuffer:
448                data = [self._rbuffer]
449            else:
450                data = []
451            self._rbuffer = b("")
452            newData = self._read()
453            while newData is not None:
454                data.append(newData)
455                newData = self._read()
456            output = b("").join(data)
457        # Otherwise, we need to return a specific amount of data
458        else:
459            if self._rbuffer:
460                newData = self._rbuffer
461                data = [newData]
462            else:
463                newData = b("")
464                data = []
465            sizeSoFar = len(newData)
466            while sizeSoFar < size:
467                newData = self._read(size-sizeSoFar)
468                if not newData:
469                    break
470                data.append(newData)
471                sizeSoFar += len(newData)
472            data = b("").join(data)
473            if sizeSoFar > size:
474                # read too many bytes, store in the buffer
475                self._rbuffer = data[size:]
476                data = data[:size]
477            else:
478                self._rbuffer = b("")
479            output = data
480        return output
481
482    def _do_read_rest(self):
483        """Private method to read the file through to EOF."""
484        data = self._do_read(self._bufsize)
485        while data != b(""):
486            data = self._do_read(self._bufsize)
487
488    def readline(self,size=-1):
489        """Read a line from the file, or at most <size> bytes."""
490        bits = []
491        indx = -1
492        sizeSoFar = 0
493        while indx == -1:
494            nextBit = self.read(self._bufsize)
495            bits.append(nextBit)
496            sizeSoFar += len(nextBit)
497            if not nextBit:
498                break
499            if size > 0 and sizeSoFar >= size:
500                break
501            indx = nextBit.find(b("\n"))
502        # If not found, return whole string up to <size> length
503        # Any leftovers are pushed onto front of buffer
504        if indx == -1:
505            data = b("").join(bits)
506            if size > 0 and sizeSoFar > size:
507                extra = data[size:]
508                data = data[:size]
509                self._rbuffer = extra + self._rbuffer
510            return data
511        # If found, push leftovers onto front of buffer
512        # Add one to preserve the newline in the return value
513        indx += 1
514        extra = bits[-1][indx:]
515        bits[-1] = bits[-1][:indx]
516        self._rbuffer = extra + self._rbuffer
517        return b("").join(bits)
518
519    def readlines(self,sizehint=-1):
520        """Return a list of all lines in the file."""
521        return [ln for ln in self]
522
523    def xreadlines(self):
524        """Iterator over lines in the file - equivalent to iter(self)."""
525        return iter(self)
526
527    def write(self,string):
528        """Write the given string to the file."""
529        if self.closed:
530            raise IOError("File has been closed")
531        self._assert_mode("w-")
532        # If we were previously reading, ensure position is correct
533        if self._rbuffer is not None:
534            self.seek(0, 1)
535        # If we're actually behind the apparent position, we must also
536        # write the data in the gap.
537        if self._sbuffer:
538            string = self._sbuffer + string
539            self._sbuffer = None
540        elif self._soffset:
541            s = self._soffset
542            self._soffset = 0
543            try:
544                string = self._do_read(s) + string
545            except NotReadableError:
546                raise NotSeekableError("File not readable, could not complete simulation of seek")
547            self.seek(0, 0)
548        if self._wbuffer:
549            string = self._wbuffer + string
550        leftover = self._write(string)
551        if leftover is None or isinstance(leftover, int):
552            self._wbuffer = b("")
553            return len(string) - (leftover or 0)
554        else:
555            self._wbuffer = leftover
556            return len(string) - len(leftover)
557
558    def writelines(self,seq):
559        """Write a sequence of lines to the file."""
560        for ln in seq:
561            self.write(ln)
562
563
564class FileWrapper(FileLikeBase):
565    """Base class for objects that wrap a file-like object.
566
567    This class provides basic functionality for implementing file-like
568    objects that wrap another file-like object to alter its functionality
569    in some way.  It takes care of house-keeping duties such as flushing
570    and closing the wrapped file.
571
572    Access to the wrapped file is given by the attribute wrapped_file.
573    By convention, the subclass's constructor should accept this as its
574    first argument and pass it to its superclass's constructor in the
575    same position.
576
577    This class provides a basic implementation of _read() and _write()
578    which just calls read() and write() on the wrapped object.  Subclasses
579    will probably want to override these.
580    """
581
582    _append_requires_overwrite = False
583
584    def __init__(self,wrapped_file,mode=None):
585        """FileWrapper constructor.
586
587        'wrapped_file' must be a file-like object, which is to be wrapped
588        in another file-like object to provide additional functionality.
589
590        If given, 'mode' must be the access mode string under which
591        the wrapped file is to be accessed.  If not given or None, it
592        is looked up on the wrapped file if possible.  Otherwise, it
593        is not set on the object.
594        """
595        # This is used for working around flush/close inefficiencies
596        self.__closing = False
597        super(FileWrapper,self).__init__()
598        self.wrapped_file = wrapped_file
599        if mode is None:
600            self.mode = getattr(wrapped_file,"mode","r+")
601        else:
602            self.mode = mode
603        self._validate_mode()
604        # Copy useful attributes of wrapped_file
605        if hasattr(wrapped_file,"name"):
606            self.name = wrapped_file.name
607        # Respect append-mode setting
608        if "a" in self.mode:
609            if self._check_mode("r"):
610                self.wrapped_file.seek(0)
611            self.seek(0,2)
612
613    def _validate_mode(self):
614        """Check that various file-mode conditions are satisfied."""
615        #  If append mode requires overwriting the underlying file,
616        #  if must not be opened in append mode.
617        if self._append_requires_overwrite:
618            if self._check_mode("w"):
619                if "a" in getattr(self.wrapped_file,"mode",""):
620                    raise ValueError("Underlying file can't be in append mode")
621
622    def __del__(self):
623        #  Errors in subclass constructors could result in this being called
624        #  without invoking FileWrapper.__init__.  Establish some simple
625        #  invariants to prevent errors in this case.
626        if not hasattr(self,"wrapped_file"):
627            self.wrapped_file = None
628        if not hasattr(self,"_FileWrapper__closing"):
629            self.__closing = False
630        #  Close the wrapper and the underlying file independently, so the
631        #  latter is still closed on cleanup even if the former errors out.
632        try:
633            if FileWrapper is not None:
634                super(FileWrapper,self).close()
635        finally:
636            if hasattr(getattr(self,"wrapped_file",None),"close"):
637                self.wrapped_file.close()
638
639    def close(self):
640        """Close the object for reading/writing."""
641        #  The superclass implementation of this will call flush(),
642        #  which calls flush() on our wrapped object.  But we then call
643        #  close() on it, which will call its flush() again!  To avoid
644        #  this inefficiency, our flush() will not flush the wrapped
645        #  file when we're closing.
646        if not self.closed:
647            self.__closing = True
648            super(FileWrapper,self).close()
649            if hasattr(self.wrapped_file,"close"):
650                self.wrapped_file.close()
651
652    def flush(self):
653        """Flush the write buffers of the file."""
654        super(FileWrapper,self).flush()
655        if not self.__closing and hasattr(self.wrapped_file,"flush"):
656            self.wrapped_file.flush()
657
658    def _read(self,sizehint=-1):
659        data = self.wrapped_file.read(sizehint)
660        if data == b(""):
661            return None
662        return data
663
664    def _write(self,string,flushing=False):
665        self.wrapped_file.write(string)
666
667    def _seek(self,offset,whence):
668        self.wrapped_file.seek(offset,whence)
669
670    def _tell(self):
671        return self.wrapped_file.tell()
672
673    def _truncate(self,size):
674        return self.wrapped_file.truncate(size)
675
676
677class StringIO(FileWrapper):
678    """StringIO wrapper that more closely matches standard file behavior.
679
680    This is a simple compatibility wrapper around the native StringIO class
681    which fixes some corner-cases of its behavior.  Specifically:
682
683        * adding __enter__ and __exit__ methods
684        * having truncate(size) zero-fill when growing the file
685
686    """
687
688    def __init__(self,data=None,mode=None):
689        wrapped_file = _StringIO()
690        if data is not None:
691            wrapped_file.write(data)
692            wrapped_file.seek(0)
693        super(StringIO,self).__init__(wrapped_file,mode)
694
695    def getvalue(self):
696        return self.wrapped_file.getvalue()
697
698    def _truncate(self,size):
699        pos = self.wrapped_file.tell()
700        self.wrapped_file.truncate(size)
701        curlen = len(self.wrapped_file.getvalue())
702        if size > curlen:
703            self.wrapped_file.seek(curlen)
704            try:
705                self.wrapped_file.write(b("\x00")*(size-curlen))
706            finally:
707                self.wrapped_file.seek(pos)
708
709
710class SpooledTemporaryFile(FileWrapper):
711    """SpooledTemporaryFile wrapper with some compatibility fixes.
712
713    This is a simple compatibility wrapper around the native class of the
714    same name, fixing some corner-cases of its behavior.  Specifically:
715
716        * have truncate() accept a size argument
717        * roll to disk is seeking past the max in-memory size
718        * use improved StringIO class from this module
719
720    """
721
722    def __init__(self,max_size=0,mode="w+b",bufsize=-1,*args,**kwds):
723        try:
724            stf_args = (max_size,mode,bufsize) + args
725            wrapped_file = _tempfile.SpooledTemporaryFile(*stf_args,**kwds)
726            wrapped_file._file = StringIO()
727            #wrapped_file._file = six.BytesIO()
728            self.__is_spooled = True
729        except AttributeError:
730            ntf_args = (mode,bufsize) + args
731            wrapped_file = _tempfile.NamedTemporaryFile(*ntf_args,**kwds)
732            self.__is_spooled = False
733        super(SpooledTemporaryFile,self).__init__(wrapped_file)
734
735    def _seek(self,offset,whence):
736        if self.__is_spooled:
737            max_size = self.wrapped_file._max_size
738            if whence == fs.SEEK_SET:
739                if offset > max_size:
740                    self.wrapped_file.rollover()
741            elif whence == fs.SEEK_CUR:
742                if offset + self.wrapped_file.tell() > max_size:
743                    self.wrapped_file.rollover()
744            else:
745                if offset > 0:
746                    self.wrapped_file.rollover()
747        self.wrapped_file.seek(offset,whence)
748
749    def _truncate(self,size):
750        if self.__is_spooled:
751            self.wrapped_file._file.truncate(size)
752        else:
753            self.wrapped_file.truncate(size)
754
755    def fileno(self):
756        return self.wrapped_file.fileno()
757
758
759class LimitBytesFile(FileWrapper):
760    """Filelike wrapper to limit bytes read from a stream."""
761
762    def __init__(self,size,fileobj,*args,**kwds):
763        self.size = size
764        self.__remaining = size
765        super(LimitBytesFile,self).__init__(fileobj,*args,**kwds)
766
767    def _read(self,sizehint=-1):
768        if self.__remaining <= 0:
769            return None
770        if sizehint is None or sizehint < 0 or sizehint > self.__remaining:
771            sizehint = self.__remaining
772        data = super(LimitBytesFile,self)._read(sizehint)
773        if data is not None:
774            self.__remaining -= len(data)
775        return data
776
777
778