1# filelike/__init__.py 2# 3# Copyright (C) 2006-2009, Ryan Kelly 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the 17# Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18# Boston, MA 02111-1307, USA. 19# 20""" 21 22 filelike: a python module for creating and handling file-like objects. 23 24This module takes care of the groundwork for implementing and manipulating 25objects that provide a rich file-like interface, including reading, writing, 26seeking and iteration. It also provides a number of useful classes built on 27top of this functionality. 28 29The main class is FileLikeBase, which implements the entire file-like interface 30on top of primitive _read(), _write(), _seek(), _tell() and _truncate() methods. 31Subclasses may implement any or all of these methods to obtain the related 32higher-level file behaviors. 33 34It also provides some nifty file-handling functions: 35 36 :open: mirrors the standard open() function but is much cleverer; 37 URLs are automatically fetched, .bz2 files are transparently 38 decompressed, and so-on. 39 40 :join: concatenate multiple file-like objects together so that they 41 act like a single file. 42 43 :slice: access a section of a file-like object as if it were an 44 independent file. 45 46 47The "wrappers" subpackage contains a collection of useful classes built on 48top of this framework. These include: 49 50 :Translate: pass file contents through an arbitrary translation 51 function (e.g. compression, encryption, ...) 52 53 :Decrypt: on-the-fly reading and writing to an encrypted file 54 (using PEP272 cipher API) 55 56 :UnBZip2: on-the-fly decompression of bzip'd files 57 (like the standard library's bz2 module, but accepts 58 any file-like object) 59 60As an example of the type of thing this module is designed to achieve, here's 61how the Decrypt wrapper can be used to transparently access an encrypted 62file:: 63 64 # Create the decryption key 65 from Crypto.Cipher import DES 66 cipher = DES.new('abcdefgh',DES.MODE_ECB) 67 # Open the encrypted file 68 from filelike.wrappers import Decrypt 69 f = Decrypt(file("some_encrypted_file.bin","r"),cipher) 70 71The object in 'f' now behaves as a file-like object, transparently decrypting 72the file on-the-fly as it is read. 73 74 75The "pipeline" subpackage contains facilities for composing these wrappers 76in the form of a unix pipeline. In the following example, 'f' will read the 77first five lines of an encrypted file:: 78 79 from filelike.pipeline import Decrypt, Head 80 f = file("some_encrypted_file.bin") > Decrypt(cipher) | Head(lines=5) 81 82 83Finally, two utility functions are provided for when code expects to deal with 84file-like objects: 85 86 :is_filelike(obj): checks that an object is file-like 87 :to_filelike(obj): wraps a variety of objects in a file-like interface 88 89""" 90 91__ver_major__ = 0 92__ver_minor__ = 4 93__ver_patch__ = 1 94__ver_sub__ = "" 95__ver_tuple__ = (__ver_major__,__ver_minor__,__ver_patch__,__ver_sub__) 96__version__ = "%d.%d.%d%s" % __ver_tuple__ 97 98 99from StringIO import StringIO 100import urllib2 101import urlparse 102import tempfile 103 104 105class NotReadableError(IOError): 106 pass 107class NotWritableError(IOError): 108 pass 109class NotSeekableError(IOError): 110 pass 111class NotTruncatableError(IOError): 112 pass 113 114 115class FileLikeBase(object): 116 """Base class for implementing file-like objects. 117 118 This class takes a lot of the legwork out of writing file-like objects 119 with a rich interface. It implements the higher-level file-like 120 methods on top of five primitive methods: _read, _write, _seek, _tell and 121 _truncate. See their docstrings for precise details on how these methods 122 behave. 123 124 Subclasses then need only implement some subset of these methods for 125 rich file-like interface compatability. They may of course override 126 other methods as desired. 127 128 The class is missing the following attributes and methods, which dont 129 really make sense for anything but real files: 130 131 * fileno() 132 * isatty() 133 * encoding 134 * mode 135 * name 136 * newlines 137 138 Unlike standard file objects, all read methods share the same buffer 139 and so can be freely mixed (e.g. read(), readline(), next(), ...). 140 141 This class understands and will accept the following mode strings, 142 with any additional characters being ignored: 143 144 * r - open the file for reading only. 145 * r+ - open the file for reading and writing. 146 * r- - open the file for streamed reading; do not allow seek/tell. 147 * w - open the file for writing only; create the file if 148 it doesn't exist; truncate it to zero length. 149 * w+ - open the file for reading and writing; create the file 150 if it doesn't exist; truncate it to zero length. 151 * w- - open the file for streamed writing; do not allow seek/tell. 152 * a - open the file for writing only; create the file if it 153 doesn't exist; place pointer at end of file. 154 * a+ - open the file for reading and writing; create the file 155 if it doesn't exist; place pointer at end of file. 156 157 These are mostly standard except for the "-" indicator, which has 158 been added for efficiency purposes in cases where seeking can be 159 expensive to simulate (e.g. compressed files). Note that any file 160 opened for both reading and writing must also support seeking. 161 162 """ 163 164 def __init__(self,bufsize=1024*64): 165 """FileLikeBase Constructor. 166 167 The optional argument 'bufsize' specifies the number of bytes to 168 read at a time when looking for a newline character. Setting this to 169 a larger number when lines are long should improve efficiency. 170 """ 171 # File-like attributes 172 self.closed = False 173 self.softspace = 0 174 # Our own attributes 175 self._bufsize = bufsize # buffer size for chunked reading 176 self._rbuffer = None # data that's been read but not returned 177 self._wbuffer = None # data that's been given but not written 178 self._sbuffer = None # data between real & apparent file pos 179 self._soffset = 0 # internal offset of file pointer 180 181 def _check_mode(self,mode,mstr=None): 182 """Check whether the file may be accessed in the given mode. 183 184 'mode' must be one of "r" or "w", and this function returns False 185 if the file-like object has a 'mode' attribute, and it does not 186 permit access in that mode. If there is no 'mode' attribute, 187 it defaults to "r+". 188 189 If seek support is not required, use "r-" or "w-" as the mode string. 190 191 To check a mode string other than self.mode, pass it in as the 192 second argument. 193 """ 194 if mstr is None: 195 try: 196 mstr = self.mode 197 except AttributeError: 198 mstr = "r+" 199 if "+" in mstr: 200 return True 201 if "-" in mstr and "-" not in mode: 202 return False 203 if "r" in mode: 204 if "r" not in mstr: 205 return False 206 if "w" in mode: 207 if "w" not in mstr and "a" not in mstr: 208 return False 209 return True 210 211 def _assert_mode(self,mode,mstr=None): 212 """Check whether the file may be accessed in the given mode. 213 214 This method is equivalent to _check_assert(), but raises IOError 215 instead of returning False. 216 """ 217 if mstr is None: 218 try: 219 mstr = self.mode 220 except AttributeError: 221 mstr = "r+" 222 if "+" in mstr: 223 return True 224 if "-" in mstr and "-" not in mode: 225 raise NotSeekableError("File does not support seeking.") 226 if "r" in mode: 227 if "r" not in mstr: 228 raise NotReadableError("File not opened for reading") 229 if "w" in mode: 230 if "w" not in mstr and "a" not in mstr: 231 raise NotWritableError("File not opened for writing") 232 return True 233 234 def flush(self): 235 """Flush internal write buffer, if necessary.""" 236 if self.closed: 237 raise IOError("File has been closed") 238 if self._check_mode("w-") and self._wbuffer is not None: 239 buffered = "" 240 if self._sbuffer: 241 buffered = buffered + self._sbuffer 242 self._sbuffer = None 243 buffered = buffered + self._wbuffer 244 self._wbuffer = None 245 leftover = self._write(buffered,flushing=True) 246 if leftover: 247 raise IOError("Could not flush write buffer.") 248 249 def close(self): 250 """Flush write buffers and close the file. 251 252 The file may not be accessed further once it is closed. 253 """ 254 # Errors in subclass constructors can cause this to be called without 255 # having called FileLikeBase.__init__(). Since we need the attrs it 256 # initialises in cleanup, ensure we call it here. 257 if not hasattr(self,"closed"): 258 FileLikeBase.__init__(self) 259 if not self.closed: 260 self.flush() 261 self.closed = True 262 263 def __del__(self): 264 self.close() 265 266 def __enter__(self): 267 return self 268 269 def __exit__(self,exc_type,exc_val,exc_tb): 270 self.close() 271 return False 272 273 def next(self): 274 """next() method complying with the iterator protocol. 275 276 File-like objects are their own iterators, with each call to 277 next() returning subsequent lines from the file. 278 """ 279 ln = self.readline() 280 if ln == "": 281 raise StopIteration() 282 return ln 283 284 def __iter__(self): 285 return self 286 287 def truncate(self,size=None): 288 """Truncate the file to the given size. 289 290 If <size> is not specified or is None, the current file position is 291 used. Note that this method may fail at runtime if the underlying 292 filelike object is not truncatable. 293 """ 294 if "-" in getattr(self,"mode",""): 295 raise NotTruncatableError("File is not seekable, can't truncate.") 296 if self._wbuffer: 297 self.flush() 298 if size is None: 299 size = self.tell() 300 self._truncate(size) 301 302 def seek(self,offset,whence=0): 303 """Move the internal file pointer to the given location.""" 304 if whence > 2 or whence < 0: 305 raise ValueError("Invalid value for 'whence': " + str(whence)) 306 if "-" in getattr(self,"mode",""): 307 raise NotSeekableError("File is not seekable.") 308 # Ensure that there's nothing left in the write buffer 309 if self._wbuffer: 310 self.flush() 311 # Adjust for any data left in the read buffer 312 if whence == 1 and self._rbuffer: 313 offset = offset - len(self._rbuffer) 314 self._rbuffer = None 315 # Adjust for any discrepancy in actual vs apparent seek position 316 if whence == 1: 317 if self._sbuffer: 318 offset = offset + len(self._sbuffer) 319 if self._soffset: 320 offset = offset + self._soffset 321 self._sbuffer = None 322 self._soffset = 0 323 # Shortcut the special case of staying put 324 if offset == 0 and whence == 1: 325 return 326 # Catch any failed attempts to read while simulating seek 327 try: 328 # Try to do a whence-wise seek if it is implemented. 329 sbuf = None 330 try: 331 sbuf = self._seek(offset,whence) 332 except NotImplementedError: 333 # Try to simulate using an absolute seek. 334 try: 335 if whence == 1: 336 offset = self._tell() + offset 337 elif whence == 2: 338 if hasattr(self,"size"): 339 offset = self.size + offset 340 else: 341 self._do_read_rest() 342 offset = self.tell() + offset 343 else: 344 # absolute seek already failed, don't try again 345 raise NotImplementedError 346 sbuf = self._seek(offset,0) 347 except NotImplementedError: 348 # Simulate by reseting to start 349 self._seek(0,0) 350 self._soffset = offset 351 finally: 352 self._sbuffer = sbuf 353 except NotReadableError: 354 raise NotSeekableError("File not readable, can't simulate seek") 355 356 def tell(self): 357 """Determine current position of internal file pointer.""" 358 # Need to adjust for unread/unwritten data in buffers 359 pos = self._tell() 360 if self._rbuffer: 361 pos = pos - len(self._rbuffer) 362 if self._wbuffer: 363 pos = pos + len(self._wbuffer) 364 if self._sbuffer: 365 pos = pos + len(self._sbuffer) 366 if self._soffset: 367 pos = pos + self._soffset 368 return pos 369 370 def read(self,size=-1): 371 """Read at most 'size' bytes from the file. 372 373 Bytes are returned as a string. If 'size' is negative, zero or 374 missing, the remainder of the file is read. If EOF is encountered 375 immediately, the empty string is returned. 376 """ 377 if self.closed: 378 raise IOError("File has been closed") 379 self._assert_mode("r-") 380 return self._do_read(size) 381 382 def _do_read(self,size): 383 """Private method to read from the file. 384 385 This method behaves the same as self.read(), but skips some 386 permission and sanity checks. It is intended for use in simulating 387 seek(), where we may want to read (and discard) information from 388 a file not opened in read mode. 389 390 Note that this may still fail if the file object actually can't 391 be read from - it just won't check whether the mode string gives 392 permission. 393 """ 394 # If we were previously writing, ensure position is correct 395 if self._wbuffer is not None: 396 self.seek(0,1) 397 # Discard any data that should have been seeked over 398 if self._sbuffer: 399 s = len(self._sbuffer) 400 self._sbuffer = None 401 self.read(s) 402 elif self._soffset: 403 s = self._soffset 404 self._soffset = 0 405 while s > self._bufsize: 406 self._do_read(self._bufsize) 407 s -= self._bufsize 408 self._do_read(s) 409 # Should the entire file be read? 410 if size <= 0: 411 if self._rbuffer: 412 data = [self._rbuffer] 413 else: 414 data = [] 415 self._rbuffer = "" 416 newData = self._read() 417 while newData is not None: 418 data.append(newData) 419 newData = self._read() 420 output = "".join(data) 421 # Otherwise, we need to return a specific amount of data 422 else: 423 if self._rbuffer: 424 newData = self._rbuffer 425 data = [newData] 426 else: 427 newData = "" 428 data = [] 429 sizeSoFar = len(newData) 430 while sizeSoFar < size: 431 newData = self._read(size-sizeSoFar) 432 if newData is None: 433 break 434 data.append(newData) 435 sizeSoFar += len(newData) 436 data = "".join(data) 437 if sizeSoFar > size: 438 # read too many bytes, store in the buffer 439 self._rbuffer = data[size:] 440 data = data[:size] 441 else: 442 self._rbuffer = "" 443 output = data 444 return output 445 446 def _do_read_rest(self): 447 """Private method to read the file through to EOF.""" 448 data = self._do_read(self._bufsize) 449 while data != "": 450 data = self._do_read(self._bufsize) 451 452 def readline(self,size=-1): 453 """Read a line from the file, or at most <size> bytes.""" 454 bits = [] 455 indx = -1 456 sizeSoFar = 0 457 while indx == -1: 458 nextBit = self.read(self._bufsize) 459 bits.append(nextBit) 460 sizeSoFar += len(nextBit) 461 if nextBit == "": 462 break 463 if size > 0 and sizeSoFar >= size: 464 break 465 indx = nextBit.find("\n") 466 # If not found, return whole string up to <size> length 467 # Any leftovers are pushed onto front of buffer 468 if indx == -1: 469 data = "".join(bits) 470 if size > 0 and sizeSoFar > size: 471 extra = data[size:] 472 data = data[:size] 473 self._rbuffer = extra + self._rbuffer 474 return data 475 # If found, push leftovers onto front of buffer 476 # Add one to preserve the newline in the return value 477 indx += 1 478 extra = bits[-1][indx:] 479 bits[-1] = bits[-1][:indx] 480 self._rbuffer = extra + self._rbuffer 481 return "".join(bits) 482 483 def readlines(self,sizehint=-1): 484 """Return a list of all lines in the file.""" 485 return [ln for ln in self] 486 487 def xreadlines(self): 488 """Iterator over lines in the file - equivalent to iter(self).""" 489 return iter(self) 490 491 def write(self,string): 492 """Write the given string to the file.""" 493 if self.closed: 494 raise IOError("File has been closed") 495 self._assert_mode("w-") 496 # If we were previously reading, ensure position is correct 497 if self._rbuffer is not None: 498 self.seek(0,1) 499 # If we're actually behind the apparent position, we must also 500 # write the data in the gap. 501 if self._sbuffer: 502 string = self._sbuffer + string 503 self._sbuffer = None 504 elif self._soffset: 505 s = self._soffset 506 self._soffset = 0 507 try: 508 string = self._do_read(s) + string 509 except NotReadableError: 510 raise NotSeekableError("File not readable, could not complete simulation of seek") 511 self.seek(0,0) 512 if self._wbuffer: 513 string = self._wbuffer + string 514 leftover = self._write(string) 515 if leftover is None: 516 self._wbuffer = "" 517 else: 518 self._wbuffer = leftover 519 520 def writelines(self,seq): 521 """Write a sequence of lines to the file.""" 522 for ln in seq: 523 self.write(ln) 524 525 def _read(self,sizehint=-1): 526 """Read approximately <sizehint> bytes from the file-like object. 527 528 This method is to be implemented by subclasses that wish to be 529 readable. It should read approximately <sizehint> bytes from the 530 file and return them as a string. If <sizehint> is missing or 531 less than or equal to zero, try to read all the remaining contents. 532 533 The method need not guarantee any particular number of bytes - 534 it may return more bytes than requested, or fewer. If needed, the 535 size hint may be completely ignored. It may even return an empty 536 string if no data is yet available. 537 538 Because of this, the method must return None to signify that EOF 539 has been reached. The higher-level methods will never indicate EOF 540 until None has been read from _read(). Once EOF is reached, it 541 should be safe to call _read() again, immediately returning None. 542 """ 543 raise NotReadableError("Object not readable") 544 545 def _write(self,string,flushing=False): 546 """Write the given string to the file-like object. 547 548 This method must be implemented by subclasses wishing to be writable. 549 It must attempt to write as much of the given data as possible to the 550 file, but need not guarantee that it is all written. It may return 551 None to indicate that all data was written, or return as a string any 552 data that could not be written. 553 554 If the keyword argument 'flushing' is true, it indicates that the 555 internal write buffers are being flushed, and *all* the given data 556 is expected to be written to the file. If unwritten data is returned 557 when 'flushing' is true, an IOError will be raised. 558 """ 559 raise NotWritableError("Object not writable") 560 561 def _seek(self,offset,whence): 562 """Set the file's internal position pointer, approximately. 563 564 This method should set the file's position to approximately 'offset' 565 bytes relative to the position specified by 'whence'. If it is 566 not possible to position the pointer exactly at the given offset, 567 it should be positioned at a convenient *smaller* offset and the 568 file data between the real and apparent position should be returned. 569 570 At minimum, this method must implement the ability to seek to 571 the start of the file, i.e. offset=0 and whence=0. If more 572 complex seeks are difficult to implement then it may raise 573 NotImplementedError to have them simulated (inefficiently) by 574 the higher-level machinery of this class. 575 """ 576 raise NotSeekableError("Object not seekable") 577 578 def _tell(self): 579 """Get the location of the file's internal position pointer. 580 581 This method must be implemented by subclasses that wish to be 582 seekable, and must return the position of the file's internal 583 pointer. 584 585 Due to buffering, the position seen by users of this class 586 (the "apparent position") may be different to the position 587 returned by this method (the "actual position"). 588 """ 589 raise NotSeekableError("Object not seekable") 590 591 def _truncate(self,size): 592 """Truncate the file's size to <size>. 593 594 This method must be implemented by subclasses that wish to be 595 truncatable. It must truncate the file to exactly the given size 596 or fail with an IOError. 597 598 Note that <size> will never be None; if it was not specified by the 599 user then it is calculated as the file's apparent position (which may 600 be different to its actual position due to buffering). 601 """ 602 raise NotTruncatableError("Object not truncatable") 603 604 605class Opener(object): 606 """Class allowing clever opening of files. 607 608 Instances of this class are callable using inst(filename,mode), 609 and are intended as a 'smart' replacement for the standard file 610 constructor and open command. Given a filename and a mode, it returns 611 a file-like object representing that file, according to rules such 612 as: 613 614 * URLs are opened using urllib2 615 * files with names ending in ".gz" are gunzipped on the fly 616 * etc... 617 618 The precise rules that are implemented are determined by two lists 619 of functions - openers and decoders. First, each successive opener 620 function is called with the filename and mode until one returns non-None. 621 Theese functions must attempt to open the given filename and return it as 622 a filelike object. 623 624 Once the file has been opened, it is passed to each successive decoder 625 function. These should return non-None if they perform some decoding 626 step on the file. In this case, they must wrap and return the file-like 627 object, modifying its name if appropriate. 628 """ 629 630 def __init__(self,openers=(),decoders=()): 631 self.openers = [o for o in openers] 632 self.decoders = [d for d in decoders] 633 634 def __call__(self,filename,mode="r"): 635 # Open the file 636 for o in self.openers: 637 try: 638 f = o(filename,mode) 639 except IOError,e: 640 f = None 641 if f is not None: 642 break 643 else: 644 raise IOError("Could not open file %s in mode '%s'" \ 645 %(filename,mode)) 646 # Decode the file as many times as required 647 goAgain = True 648 while goAgain: 649 for d in self.decoders: 650 res = d(f) 651 if res is not None: 652 f = res 653 break 654 else: 655 goAgain = False 656 # Return the final file object 657 return f 658 659## Create default Opener that uses urllib2.urlopen() and file() as openers 660def _urllib_opener(filename,mode): 661 if mode not in ("r","r-"): 662 return None 663 comps = urlparse.urlparse(filename) 664 # ensure it's a URL 665 if comps[0] == "": 666 return None 667 f = urllib2.urlopen(filename) 668 f.name = f.geturl() 669 f.mode = mode 670 return f 671def _file_opener(filename,mode): 672 # Dont open URLS as local files 673 comps = urlparse.urlparse(filename) 674 if comps[0] and comps[1]: 675 return None 676 return file(filename,mode) 677 678open = Opener(openers=(_urllib_opener,_file_opener)) 679 680 681def is_filelike(obj,mode="rw"): 682 """Test whether an object implements the file-like interface. 683 684 'obj' must be the object to be tested, and 'mode' a file access 685 mode such as "r", "w" or "rw". This function returns True if 686 the given object implements the full reading/writing interface 687 as required by the given mode, and False otherwise. 688 689 If 'mode' is not specified, it deaults to "rw" - that is, 690 checking that the full file interface is supported. 691 692 This method is not intended for checking basic functionality such as 693 existance of read(), but for ensuring the richer interface is 694 available. If only read() or write() is needed, it's probably 695 simpler to (a) catch the AttributeError, or (b) use to_filelike(obj) 696 to ensure a suitable object. 697 """ 698 # Check reading interface 699 if "r" in mode: 700 # Special-case for FileLikeBase subclasses 701 if isinstance(obj,FileLikeBase): 702 if not hasattr(obj,"_read"): 703 return False 704 if obj._read.im_class is FileLikeBase: 705 return False 706 else: 707 attrs = ("read","readline","readlines","__iter__",) 708 for a in attrs: 709 if not hasattr(obj,a): 710 return False 711 # Check writing interface 712 if "w" in mode or "a" in mode: 713 # Special-case for FileLikeBase subclasses 714 if isinstance(obj,FileLikeBase): 715 if not hasattr(obj,"_write"): 716 return False 717 if obj._write.im_class is FileLikeBase: 718 return False 719 else: 720 attrs = ("write","writelines","close") 721 for a in attrs: 722 if not hasattr(obj,a): 723 return False 724 # Check for seekability 725 if "-" not in mode: 726 if isinstance(obj,FileLikeBase): 727 if not hasattr(obj,"_seek"): 728 return False 729 if obj._seek.im_class is FileLikeBase: 730 return False 731 else: 732 attrs = ("seek","tell",) 733 for a in attrs: 734 if not hasattr(obj,a): 735 return False 736 return True 737 738 739class join(FileLikeBase): 740 """Class concatenating several file-like objects into a single file. 741 742 This class is similar in spirit to the unix `cat` command, except that 743 it produces a file-like object that is readable, writable and seekable 744 (so long as the underlying files permit those operations, of course). 745 746 When reading, data is read from each file in turn until it has been 747 exhausted. Seeks and tells are calculated using the individual positions 748 of each file. 749 750 When writing, data is spread across each file according to its size, 751 and only the last file in the sequence will grow as data is appended. 752 This requires that the size of each file can be determined, either by 753 checking for a 'size' attribute or using seek/tell. 754 """ 755 756 def __init__(self,files,mode=None): 757 """Filelike join constructor. 758 759 This first argument must be a sequence of file-like objects 760 that are to be joined together. The optional second argument 761 specifies the access mode and can be used e.g. to prevent 762 writing even when the underlying files are writable. 763 """ 764 super(join,self).__init__() 765 if mode: 766 self.mode = mode 767 self._files = list(files) 768 self._curFile = 0 769 if mode and "a" in mode: 770 self.seek(0,2) 771 772 def close(self): 773 super(join,self).close() 774 for f in self._files: 775 if hasattr(f,"close"): 776 f.close() 777 778 def flush(self): 779 super(join,self).flush() 780 for f in self._files: 781 if hasattr(f,"flush"): 782 f.flush() 783 784 def _read(self,sizehint=-1): 785 data = self._files[self._curFile].read(sizehint) 786 if data == "": 787 if self._curFile == len(self._files) - 1: 788 return None 789 else: 790 self._curFile += 1 791 return self._read(sizehint) 792 else: 793 return data 794 795 def _write(self,data,flushing=False): 796 cf = self._files[self._curFile] 797 # If we're at the last file, just write it all out 798 if self._curFile == len(self._files) - 1: 799 cf.write(data) 800 return None 801 # Otherwise, we may need to write into multiple files 802 pos = cf.tell() 803 try: 804 size = cf.size 805 except AttributeError: 806 cf.seek(0,2) 807 size = cf.tell() 808 cf.seek(pos,0) 809 # If the data will all fit in the current file, just write it 810 gap = size - pos 811 if gap >= len(data): 812 cf.write(data) 813 return None 814 # Otherwise, split up the data and recurse 815 cf.write(data[:gap]) 816 self._curFile += 1 817 return self._write(data[gap:],flushing=flushing) 818 819 def _seek(self,offset,whence): 820 # Seek-from-end simulated using seek-to-end, then relative seek. 821 if whence == 2: 822 for f in self._files[self._curFile:]: 823 f.seek(0,2) 824 self._curFile = len(self._files)-1 825 self._seek(offset,1) 826 # Absolute seek simulated using tell() and relative seek. 827 elif whence == 0: 828 offset = offset - self._tell() 829 self._seek(offset,1) 830 # Relative seek 831 elif whence == 1: 832 # Working backwards, we simply rewind each file until 833 # the offset is small enough to be within the current file 834 if offset < 0: 835 off1 = self._files[self._curFile].tell() 836 while off1 < -1*offset: 837 offset += off1 838 self._files[self._curFile].seek(0,0) 839 # If seeking back past start of first file, stop at zero 840 if self._curFile == 0: 841 return None 842 self._curFile -= 1 843 off1 = self._files[self._curFile].tell() 844 self._files[self._curFile].seek(offset,1) 845 # Working forwards, we wind each file forward to its end, 846 # then seek backwards once we've gone too far. 847 elif offset > 0: 848 offset += self._files[self._curFile].tell() 849 self._files[self._curFile].seek(0,2) 850 offset -= self._files[self._curFile].tell() 851 while offset > 0: 852 self._curFile += 1 853 self._files[self._curFile].seek(0,2) 854 offset -= self._files[self._curFile].tell() 855 self.seek(offset,1) 856 857 def _tell(self): 858 return sum([f.tell() for f in self._files[:self._curFile+1]]) 859 860 861def slice(f,start=0,stop=None,mode=None,resizable=False): 862 """Manipulate a portion of a file-like object. 863 864 This function simply exposes the class filelike.wrappers.Slice 865 at the top-level of the module, since it has a nice symmetry 866 with the 'join' operation. 867 """ 868 return filelike.wrappers.Slice(f,start,stop,mode,resizable) 869 870 871def to_filelike(obj,mode="r+"): 872 """Convert 'obj' to a file-like object if possible. 873 874 This method takes an arbitrary object 'obj', and attempts to 875 wrap it in a file-like interface. This will results in the 876 object itself if it is already file-like, or some sort of 877 wrapper class otherwise. 878 879 'mode', if provided, should specify how the resulting object 880 will be accessed. 881 882 If the object cannot be converted, ValueError is raised. 883 """ 884 # File-like objects are sutiable on their own 885 if is_filelike(obj,mode): 886 return obj 887 # Strings can be wrapped using StringIO 888 if isinstance(obj,basestring): 889 return StringIO(obj) 890 # Anything with read() and/or write() can be trivially wrapped 891 hasRead = hasattr(obj,"read") 892 hasWrite = hasattr(obj,"write") 893 hasSeek = hasattr(obj,"seek") 894 if "r" in mode: 895 if "w" in mode or "a" in mode or "+" in mode: 896 if hasRead and hasWrite and hasSeek: 897 return filelike.wrappers.FileWrapper(obj) 898 elif "-" not in mode: 899 if hasRead and hasSeek: 900 return filelike.wrappers.FileWrapper(obj) 901 else: 902 if hasRead: 903 return filelike.wrappers.FileWrapper(obj) 904 if "w" in mode or "a" in mode: 905 if "-" not in mode: 906 if hasWrite and hasSeek: 907 return filelike.wrappers.FileWrapper(obj) 908 elif hasWrite: 909 return filelike.wrappers.FileWrapper(obj) 910 # TODO: lots more could be done here... 911 raise ValueError("Could not make object file-like: %s", (obj,)) 912 913# Imported here to aoid circular imports 914import filelike.wrappers 915