Patch implementing bz2 module.

* setup.py
  (PyBuildExt.detect_modules): Included bz2 module detection.

* Modules/bz2module.c
* Lib/test/test_bz2.py
* Doc/lib/libbz2.tex
  Included files implementing, testing, and documenting bz2 module.

* Doc/Makefile.deps
* Doc/lib/lib.tex
  Include references to libbz2.tex.

* Misc/NEWS
  (Library): Mention distutils' c++ linkage patch, and new bz2 module.
This commit is contained in:
Gustavo Niemeyer 2002-11-05 16:50:05 +00:00
parent 6b016852f8
commit f8ca8364c9
7 changed files with 2578 additions and 0 deletions

View File

@ -227,6 +227,7 @@ LIBFILES= $(MANSTYLES) $(INDEXSTYLES) $(COMMONTEX) \
lib/libcommands.tex \
lib/libcmath.tex \
lib/libgzip.tex \
lib/libbz2.tex \
lib/libzipfile.tex \
lib/libpprint.tex \
lib/libcode.tex \

View File

@ -169,6 +169,7 @@ and how to embed it in other applications.
\input{libbsddb}
\input{libzlib}
\input{libgzip}
\input{libbz2}
\input{libzipfile}
\input{libreadline}
\input{librlcompleter}

174
Doc/lib/libbz2.tex Normal file
View File

@ -0,0 +1,174 @@
\section{\module{bz2} ---
Compression compatible with \program{bzip2}}
\declaremodule{builtin}{bz2}
\modulesynopsis{Interface to compression and decompression
routines compatible with \program{bzip2}.}
\moduleauthor{Gustavo Niemeyer}{niemeyer@conectiva.com}
\sectionauthor{Gustavo Niemeyer}{niemeyer@conectiva.com}
\versionadded{2.3}
This module provides a comprehensive interface for the bz2 compression library.
It implements a complete file interface, one-shot (de)compression functions,
and types for sequential (de)compression.
Here is a resume of the features offered by the bz2 module:
\begin{itemize}
\item \class{BZ2File} class implements a complete file interface, including
\method{readline()}, \method{readlines()}, \method{xreadlines()},
\method{writelines()}, \method{seek()}, etc;
\item \class{BZ2File} class implements emulated \method{seek()} support;
\item \class{BZ2File} class implements universal newline support;
\item \class{BZ2File} class offers an optimized line iteration using
the readahead algorithm borrowed from file objects;
\item \class{BZ2File} class developed inheriting builtin file type
(\code{isinstance(BZ2File(), file) == 1});
\item Sequential (de)compression supported by \class{BZ2Compressor} and
\class{BZ2Decompressor} classes;
\item One-shot (de)compression supported by \function{compress()} and
\function{decompress()} functions;
\item Thread safety uses individual locking mechanism;
\item Complete inline documentation;
\end{itemize}
\subsection{(De)compression of files}
Handling of compressed files is offered by the \class{BZ2File} class.
\begin{classdesc}{BZ2File}{filename \optional{, mode='r'\optional{,
buffering=0\optional{, compresslevel=9}}}}
Open a bz2 file. Mode can be either \code{'r'} or \code{'w'}, for reading
(default) or writing. When opened for writing, the file will be created if
it doesn't exist, and truncated otherwise. If the buffering argument is given,
\code{0} means unbuffered, and larger numbers specify the buffer size. If
compresslevel is given, must be a number between \code{1} and \code{9}.
Add a \code{'U'} to mode to open the file for input with universal newline
support. Any line ending in the input file will be seen as a
\code{'\textbackslash n'}
in Python. Also, a file so opened gains the attribute \member{newlines};
the value for this attribute is one of \code{None} (no newline read yet),
\code{'\textbackslash r'}, \code{'\textbackslash n'},
\code{'\textbackslash r\textbackslash n'} or a tuple containing all the
newline types seen. Universal newlines are available only when reading.
\end{classdesc}
\begin{methoddesc}[BZ2File]{close}{}
Close the file. Sets data attribute \member{closed} to true. A closed file
cannot be used for further I/O operations. \method{close()} may be called
more than once without error.
\end{methoddesc}
\begin{methoddesc}[BZ2File]{read}{\optional{size}}
Read at most \var{size} uncompressed bytes, returned as a string. If the
\var{size} argument is negative or omitted, read until EOF is reached.
\end{methoddesc}
\begin{methoddesc}[BZ2File]{readline}{\optional{size}}
Return the next line from the file, as a string, retaining newline.
A non-negative \var{size} argument limits the maximum number of bytes to
return (an incomplete line may be returned then). Return an empty
string at EOF.
\end{methoddesc}
\begin{methoddesc}[BZ2File]{readlines}{\optional{size}}
Return a list of lines read. The optional \var{size} argument, if given,
is an approximate bound on the total number of bytes in the lines returned.
\end{methoddesc}
\begin{methoddesc}[BZ2File]{xreadlines}{}
For backward compatibility. \class{BZ2File} objects now include the
performance optimizations previously implemented in the \module{xreadlines}
module.
\end{methoddesc}
\begin{methoddesc}[BZ2File]{\_\_iter\_\_}{}
Iterate trough the file lines. Iteration optimization is implemented
using the same readahead algorithm available in \class{file} objects.
\end{methoddesc}
\begin{methoddesc}[BZ2File]{seek}{offset \optional{, whence}}
Move to new file position. Argument \var{offset} is a byte count. Optional
argument \var{whence} defaults to \code{0} (offset from start of file,
offset should be \code{>= 0}); other values are \code{1} (move relative to
current position, positive or negative), and \code{2} (move relative to end
of file, usually negative, although many platforms allow seeking beyond
the end of a file).
Note that seeking of bz2 files is emulated, and depending on the parameters
the operation may be extremely slow.
\end{methoddesc}
\begin{methoddesc}[BZ2File]{tell}{}
Return the current file position, an integer (may be a long integer).
\end{methoddesc}
\begin{methoddesc}[BZ2File]{write}{data}
Write string \var{data} to file. Note that due to buffering, \method{close()}
may be needed before the file on disk reflects the data written.
\end{methoddesc}
\begin{methoddesc}[BZ2File]{writelines}{sequence_of_strings}
Write the sequence of strings to the file. Note that newlines are not added.
The sequence can be any iterable object producing strings. This is equivalent
to calling write() for each string.
\end{methoddesc}
\subsection{Sequential (de)compression}
Sequential compression and decompression is done using the classes
\class{BZ2Compressor} and \class{BZ2Decompressor}.
\begin{classdesc}{BZ2Compressor}{\optional{compresslevel=9}}
Create a new compressor object. This object may be used to compress
data sequentially. If you want to compress data in one shot, use the
\function{compress()} function instead. The \var{compresslevel} parameter,
if given, must be a number between \code{1} and \code{9}.
\end{classdesc}
\begin{methoddesc}[BZ2Compressor]{compress}{data}
Provide more data to the compressor object. It will return chunks of compressed
data whenever possible. When you've finished providing data to compress, call
the \method{flush()} method to finish the compression process, and return what
is left in internal buffers.
\end{methoddesc}
\begin{methoddesc}[BZ2Compressor]{flush}{}
Finish the compression process and return what is left in internal buffers. You
must not use the compressor object after calling this method.
\end{methoddesc}
\begin{classdesc}{BZ2Decompressor}{}
Create a new decompressor object. This object may be used to decompress
data sequentially. If you want to decompress data in one shot, use the
\function{decompress()} function instead.
\end{classdesc}
\begin{methoddesc}[BZ2Decompressor]{decompress}{data}
Provide more data to the decompressor object. It will return chunks of
decompressed data whenever possible. If you try to decompress data after the
end of stream is found, \exception{EOFError} will be raised. If any data was
found after the end of stream, it'll be ignored and saved in
\member{unused\_data} attribute.
\end{methoddesc}
\subsection{One-shot (de)compression}
One-shot compression and decompression is provided trough the
\function{compress()} and \function{decompress()} functions.
\begin{funcdesc}{compress}{data\optional{, compresslevel=9}}
Compress \var{data} in one shot. If you want to compress data sequentially,
use an instance of \class{BZ2Compressor} instead. The \var{compresslevel}
parameter, if given, must be a number between \code{1} and \code{9}.
\end{funcdesc}
\begin{funcdesc}{decompress}{}
Decompress \var{data} in one shot. If you want to decompress data
sequentially, use an instance of \class{BZ2Decompressor} instead.
\end{funcdesc}

290
Lib/test/test_bz2.py Normal file
View File

@ -0,0 +1,290 @@
#!/usr/bin/python
import unittest
from cStringIO import StringIO
import os
import popen2
import tempfile
from bz2 import *
from test import test_support
class BaseTest(unittest.TestCase):
"Base for other testcases."
TEXT = 'root:x:0:0:root:/root:/bin/bash\nbin:x:1:1:bin:/bin:\ndaemon:x:2:2:daemon:/sbin:\nadm:x:3:4:adm:/var/adm:\nlp:x:4:7:lp:/var/spool/lpd:\nsync:x:5:0:sync:/sbin:/bin/sync\nshutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\nhalt:x:7:0:halt:/sbin:/sbin/halt\nmail:x:8:12:mail:/var/spool/mail:\nnews:x:9:13:news:/var/spool/news:\nuucp:x:10:14:uucp:/var/spool/uucp:\noperator:x:11:0:operator:/root:\ngames:x:12:100:games:/usr/games:\ngopher:x:13:30:gopher:/usr/lib/gopher-data:\nftp:x:14:50:FTP User:/var/ftp:/bin/bash\nnobody:x:65534:65534:Nobody:/home:\npostfix:x:100:101:postfix:/var/spool/postfix:\nniemeyer:x:500:500::/home/niemeyer:/bin/bash\npostgres:x:101:102:PostgreSQL Server:/var/lib/pgsql:/bin/bash\nmysql:x:102:103:MySQL server:/var/lib/mysql:/bin/bash\nwww:x:103:104::/var/www:/bin/false\n'
DATA = 'BZh91AY&SY.\xc8N\x18\x00\x01>_\x80\x00\x10@\x02\xff\xf0\x01\x07n\x00?\xe7\xff\xe00\x01\x99\xaa\x00\xc0\x03F\x86\x8c#&\x83F\x9a\x03\x06\xa6\xd0\xa6\x93M\x0fQ\xa7\xa8\x06\x804hh\x12$\x11\xa4i4\xf14S\xd2<Q\xb5\x0fH\xd3\xd4\xdd\xd5\x87\xbb\xf8\x94\r\x8f\xafI\x12\xe1\xc9\xf8/E\x00pu\x89\x12]\xc9\xbbDL\nQ\x0e\t1\x12\xdf\xa0\xc0\x97\xac2O9\x89\x13\x94\x0e\x1c7\x0ed\x95I\x0c\xaaJ\xa4\x18L\x10\x05#\x9c\xaf\xba\xbc/\x97\x8a#C\xc8\xe1\x8cW\xf9\xe2\xd0\xd6M\xa7\x8bXa<e\x84t\xcbL\xb3\xa7\xd9\xcd\xd1\xcb\x84.\xaf\xb3\xab\xab\xad`n}\xa0lh\tE,\x8eZ\x15\x17VH>\x88\xe5\xcd9gd6\x0b\n\xe9\x9b\xd5\x8a\x99\xf7\x08.K\x8ev\xfb\xf7xw\xbb\xdf\xa1\x92\xf1\xdd|/";\xa2\xba\x9f\xd5\xb1#A\xb6\xf6\xb3o\xc9\xc5y\\\xebO\xe7\x85\x9a\xbc\xb6f8\x952\xd5\xd7"%\x89>V,\xf7\xa6z\xe2\x9f\xa3\xdf\x11\x11"\xd6E)I\xa9\x13^\xca\xf3r\xd0\x03U\x922\xf26\xec\xb6\xed\x8b\xc3U\x13\x9d\xc5\x170\xa4\xfa^\x92\xacDF\x8a\x97\xd6\x19\xfe\xdd\xb8\xbd\x1a\x9a\x19\xa3\x80ankR\x8b\xe5\xd83]\xa9\xc6\x08\x82f\xf6\xb9"6l$\xb8j@\xc0\x8a\xb0l1..\xbak\x83ls\x15\xbc\xf4\xc1\x13\xbe\xf8E\xb8\x9d\r\xa8\x9dk\x84\xd3n\xfa\xacQ\x07\xb1%y\xaav\xb4\x08\xe0z\x1b\x16\xf5\x04\xe9\xcc\xb9\x08z\x1en7.G\xfc]\xc9\x14\xe1B@\xbb!8`'
DATA_CRLF = 'BZh91AY&SY\xaez\xbbN\x00\x01H\xdf\x80\x00\x12@\x02\xff\xf0\x01\x07n\x00?\xe7\xff\xe0@\x01\xbc\xc6`\x86*\x8d=M\xa9\x9a\x86\xd0L@\x0fI\xa6!\xa1\x13\xc8\x88jdi\x8d@\x03@\x1a\x1a\x0c\x0c\x83 \x00\xc4h2\x19\x01\x82D\x84e\t\xe8\x99\x89\x19\x1ah\x00\r\x1a\x11\xaf\x9b\x0fG\xf5(\x1b\x1f?\t\x12\xcf\xb5\xfc\x95E\x00ps\x89\x12^\xa4\xdd\xa2&\x05(\x87\x04\x98\x89u\xe40%\xb6\x19\'\x8c\xc4\x89\xca\x07\x0e\x1b!\x91UIFU%C\x994!DI\xd2\xfa\xf0\xf1N8W\xde\x13A\xf5\x9cr%?\x9f3;I45A\xd1\x8bT\xb1<l\xba\xcb_\xc00xY\x17r\x17\x88\x08\x08@\xa0\ry@\x10\x04$)`\xf2\xce\x89z\xb0s\xec\x9b.iW\x9d\x81\xb5-+t\x9f\x1a\'\x97dB\xf5x\xb5\xbe.[.\xd7\x0e\x81\xe7\x08\x1cN`\x88\x10\xca\x87\xc3!"\x80\x92R\xa1/\xd1\xc0\xe6mf\xac\xbd\x99\xcca\xb3\x8780>\xa4\xc7\x8d\x1a\\"\xad\xa1\xabyBg\x15\xb9l\x88\x88\x91k"\x94\xa4\xd4\x89\xae*\xa6\x0b\x10\x0c\xd6\xd4m\xe86\xec\xb5j\x8a\x86j\';\xca.\x01I\xf2\xaaJ\xe8\x88\x8cU+t3\xfb\x0c\n\xa33\x13r2\r\x16\xe0\xb3(\xbf\x1d\x83r\xe7M\xf0D\x1365\xd8\x88\xd3\xa4\x92\xcb2\x06\x04\\\xc1\xb0\xea//\xbek&\xd8\xe6+t\xe5\xa1\x13\xada\x16\xder5"w]\xa2i\xb7[\x97R \xe2IT\xcd;Z\x04dk4\xad\x8a\t\xd3\x81z\x10\xf1:^`\xab\x1f\xc5\xdc\x91N\x14$+\x9e\xae\xd3\x80'
def decompress(self, data):
pop = popen2.Popen3("bunzip2", capturestderr=1)
pop.tochild.write(data)
pop.tochild.close()
ret = pop.fromchild.read()
pop.fromchild.close()
if pop.wait() != 0:
ret = decompress(data)
return ret
class BZ2FileTest(BaseTest):
"Test MCRYPT type miscelaneous methods."
def setUp(self):
self.filename = tempfile.mktemp("bz2")
def tearDown(self):
if os.path.isfile(self.filename):
os.unlink(self.filename)
def createTempFile(self, crlf=0):
f = open(self.filename, "w")
if crlf:
data = self.DATA_CRLF
else:
data = self.DATA
f.write(data)
f.close()
def testRead(self):
"Test BZ2File.read()"
self.createTempFile()
bz2f = BZ2File(self.filename)
self.assertEqual(bz2f.read(), self.TEXT)
bz2f.close()
def testReadChunk10(self):
"Test BZ2File.read() in chunks of 10 bytes"
self.createTempFile()
bz2f = BZ2File(self.filename)
text = ''
while 1:
str = bz2f.read(10)
if not str:
break
text += str
self.assertEqual(text, text)
bz2f.close()
def testRead100(self):
"Test BZ2File.read(100)"
self.createTempFile()
bz2f = BZ2File(self.filename)
self.assertEqual(bz2f.read(100), self.TEXT[:100])
bz2f.close()
def testReadLine(self):
"Test BZ2File.readline()"
self.createTempFile()
bz2f = BZ2File(self.filename)
sio = StringIO(self.TEXT)
for line in sio.readlines():
self.assertEqual(bz2f.readline(), line)
bz2f.close()
def testReadLines(self):
"Test BZ2File.readlines()"
self.createTempFile()
bz2f = BZ2File(self.filename)
sio = StringIO(self.TEXT)
self.assertEqual(bz2f.readlines(), sio.readlines())
bz2f.close()
def testIterator(self):
"Test iter(BZ2File)"
self.createTempFile()
bz2f = BZ2File(self.filename)
sio = StringIO(self.TEXT)
self.assertEqual(list(iter(bz2f)), sio.readlines())
bz2f.close()
def testXReadLines(self):
"Test BZ2File.xreadlines()"
self.createTempFile()
bz2f = BZ2File(self.filename)
sio = StringIO(self.TEXT)
self.assertEqual(list(bz2f.xreadlines()), sio.readlines())
bz2f.close()
def testUniversalNewlinesLF(self):
"Test BZ2File.read() with universal newlines (\\n)"
self.createTempFile()
bz2f = BZ2File(self.filename, "rU")
self.assertEqual(bz2f.read(), self.TEXT)
self.assertEqual(bz2f.newlines, "\n")
bz2f.close()
def testUniversalNewlinesCRLF(self):
"Test BZ2File.read() with universal newlines (\\r\\n)"
self.createTempFile(crlf=1)
bz2f = BZ2File(self.filename, "rU")
self.assertEqual(bz2f.read(), self.TEXT)
self.assertEqual(bz2f.newlines, "\r\n")
bz2f.close()
def testWrite(self):
"Test BZ2File.write()"
bz2f = BZ2File(self.filename, "w")
bz2f.write(self.TEXT)
bz2f.close()
f = open(self.filename)
self.assertEqual(self.decompress(f.read()), self.TEXT)
f.close()
def testWriteChunks10(self):
"Test BZ2File.write() with chunks of 10 bytes"
bz2f = BZ2File(self.filename, "w")
n = 0
while 1:
str = self.TEXT[n*10:(n+1)*10]
if not str:
break
bz2f.write(str)
n += 1
bz2f.close()
f = open(self.filename)
self.assertEqual(self.decompress(f.read()), self.TEXT)
f.close()
def testWriteLines(self):
"Test BZ2File.writelines()"
bz2f = BZ2File(self.filename, "w")
sio = StringIO(self.TEXT)
bz2f.writelines(sio.readlines())
bz2f.close()
f = open(self.filename)
self.assertEqual(self.decompress(f.read()), self.TEXT)
f.close()
def testSeekForward(self):
"Test BZ2File.seek(150, 0)"
self.createTempFile()
bz2f = BZ2File(self.filename)
bz2f.seek(150)
self.assertEqual(bz2f.read(), self.TEXT[150:])
def testSeekBackwards(self):
"Test BZ2File.seek(-150, 1)"
self.createTempFile()
bz2f = BZ2File(self.filename)
bz2f.read(500)
bz2f.seek(-150, 1)
self.assertEqual(bz2f.read(), self.TEXT[500-150:])
def testSeekBackwardsFromEnd(self):
"Test BZ2File.seek(-150, 2)"
self.createTempFile()
bz2f = BZ2File(self.filename)
bz2f.seek(-150, 2)
self.assertEqual(bz2f.read(), self.TEXT[len(self.TEXT)-150:])
def testSeekPostEnd(self):
"Test BZ2File.seek(150000)"
self.createTempFile()
bz2f = BZ2File(self.filename)
bz2f.seek(150000)
self.assertEqual(bz2f.tell(), len(self.TEXT))
self.assertEqual(bz2f.read(), "")
def testSeekPostEndTwice(self):
"Test BZ2File.seek(150000) twice"
self.createTempFile()
bz2f = BZ2File(self.filename)
bz2f.seek(150000)
bz2f.seek(150000)
self.assertEqual(bz2f.tell(), len(self.TEXT))
self.assertEqual(bz2f.read(), "")
def testSeekPreStart(self):
"Test BZ2File.seek(-150, 0)"
self.createTempFile()
bz2f = BZ2File(self.filename)
bz2f.seek(-150)
self.assertEqual(bz2f.tell(), 0)
self.assertEqual(bz2f.read(), self.TEXT)
class BZ2CompressorTest(BaseTest):
def testCompress(self):
"Test BZ2Compressor.compress()/flush()"
bz2c = BZ2Compressor()
data = bz2c.compress(self.TEXT)
data += bz2c.flush()
self.assertEqual(self.decompress(data), self.TEXT)
def testCompressChunks10(self):
"Test BZ2Compressor.compress()/flush() with chunks of 10 bytes"
bz2c = BZ2Compressor()
n = 0
data = ''
while 1:
str = self.TEXT[n*10:(n+1)*10]
if not str:
break
data += bz2c.compress(str)
n += 1
data += bz2c.flush()
self.assertEqual(self.decompress(data), self.TEXT)
class BZ2DecompressorTest(BaseTest):
def testDecompress(self):
"Test BZ2Decompressor.decompress()"
bz2d = BZ2Decompressor()
text = bz2d.decompress(self.DATA)
self.assertEqual(text, self.TEXT)
def testDecompressChunks10(self):
"Test BZ2Decompressor.decompress() with chunks of 10 bytes"
bz2d = BZ2Decompressor()
text = ''
n = 0
while 1:
str = self.DATA[n*10:(n+1)*10]
if not str:
break
text += bz2d.decompress(str)
n += 1
self.assertEqual(text, self.TEXT)
def testDecompressUnusedData(self):
"Test BZ2Decompressor.decompress() with unused data"
bz2d = BZ2Decompressor()
unused_data = "this is unused data"
text = bz2d.decompress(self.DATA+unused_data)
self.assertEqual(text, self.TEXT)
self.assertEqual(bz2d.unused_data, unused_data)
def testEOFError(self):
"Calling BZ2Decompressor.decompress() after EOS must raise EOFError"
bz2d = BZ2Decompressor()
text = bz2d.decompress(self.DATA)
self.assertRaises(EOFError, bz2d.decompress, "anything")
class FuncTest(BaseTest):
"Test module functions"
def testCompress(self):
"Test compress() function"
data = compress(self.TEXT)
self.assertEqual(self.decompress(data), self.TEXT)
def testDecompress(self):
"Test decompress() function"
text = decompress(self.DATA)
self.assertEqual(text, self.TEXT)
def testDecompressEmpty(self):
"Test decompress() function with empty string"
text = decompress("")
self.assertEqual(text, "")
def testDecompressIncomplete(self):
"Test decompress() function with incomplete data"
self.assertRaises(ValueError, decompress, self.DATA[:-10])
def test_main():
test_support.run_unittest(BZ2FileTest)
test_support.run_unittest(BZ2CompressorTest)
test_support.run_unittest(BZ2DecompressorTest)
test_support.run_unittest(FuncTest)
if __name__ == '__main__':
test_main()
# vim:ts=4:sw=4

View File

@ -534,6 +534,14 @@ Library
has changed slightly so that an explicit maxlinelen value is always
honored.
- distutils' build_ext command now links c++ extensions with the c++
compiler available in the Makefile or CXX environment variable, if
running under *nix.
- New module bz2: provides a comprehensive interface for the bz2 compression
library. It implements a complete file interface, one-shot (de)compression
functions, and types for sequential (de)compression.
Tools/Demos
-----------

2099
Modules/bz2module.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -702,6 +702,11 @@ class PyBuildExt(build_ext):
exts.append( Extension('zlib', ['zlibmodule.c'],
libraries = ['z']) )
# Gustavo Niemeyer's bz2 module.
if (self.compiler.find_library_file(lib_dirs, 'bz2')):
exts.append( Extension('bz2', ['bz2module.c'],
libraries = ['bz2']) )
# Interface to the Expat XML parser
#
# Expat was written by James Clark and is now maintained by a