Skip to content

Commit 3c9cad5

Browse files
authored
Merge pull request #77 from khvn26/master
Fix #76: support new buffer API
2 parents 0c926b4 + 3bc76ed commit 3c9cad5

4 files changed

Lines changed: 47 additions & 23 deletions

File tree

snappy/snappy.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,12 @@ def add_chunk(self, data, compress=None):
127127
snappy's performance. If compress == True, compression always happens,
128128
and if compress == False, compression never happens.
129129
"""
130+
out = bytearray()
130131
if not self._header_chunk_written:
131132
self._header_chunk_written = True
132-
out = [struct.pack("<L", _IDENTIFIER_CHUNK +
133-
(len(_STREAM_IDENTIFIER) << 8)),
134-
_STREAM_IDENTIFIER]
135-
else:
136-
out = []
133+
out.extend(struct.pack("<L", _IDENTIFIER_CHUNK +
134+
(len(_STREAM_IDENTIFIER) << 8)))
135+
out.extend(_STREAM_IDENTIFIER)
137136
for i in range(0, len(data), _CHUNK_MAX):
138137
chunk = data[i:i + _CHUNK_MAX]
139138
crc = _masked_crc32c(chunk)
@@ -151,10 +150,10 @@ def add_chunk(self, data, compress=None):
151150
chunk_type = _COMPRESSED_CHUNK
152151
else:
153152
chunk_type = _UNCOMPRESSED_CHUNK
154-
out.append(struct.pack("<LL", chunk_type + ((len(chunk) + 4) << 8),
153+
out.extend(struct.pack("<LL", chunk_type + ((len(chunk) + 4) << 8),
155154
crc))
156-
out.append(chunk)
157-
return b"".join(out)
155+
out.extend(chunk)
156+
return bytes(out)
158157

159158
def compress(self, data):
160159
"""This method is simply an alias for compatibility with zlib
@@ -193,7 +192,7 @@ class StreamDecompressor(object):
193192
__slots__ = ["_buf", "_header_found"]
194193

195194
def __init__(self):
196-
self._buf = b""
195+
self._buf = bytearray()
197196
self._header_found = False
198197

199198
@staticmethod
@@ -222,11 +221,11 @@ def decompress(self, data):
222221
the decompress() method. Some of the input data may be preserved in
223222
internal buffers for later processing.
224223
"""
225-
self._buf += data
226-
uncompressed = []
224+
self._buf.extend(data)
225+
uncompressed = bytearray()
227226
while True:
228227
if len(self._buf) < 4:
229-
return b"".join(uncompressed)
228+
return bytes(uncompressed)
230229
chunk_type = struct.unpack("<L", self._buf[:4])[0]
231230
size = (chunk_type >> 8)
232231
chunk_type &= 0xff
@@ -240,7 +239,7 @@ def decompress(self, data):
240239
raise UncompressError(
241240
"stream received unskippable but unknown chunk")
242241
if len(self._buf) < 4 + size:
243-
return b"".join(uncompressed)
242+
return bytes(uncompressed)
244243
chunk, self._buf = self._buf[4:4 + size], self._buf[4 + size:]
245244
if chunk_type == _IDENTIFIER_CHUNK:
246245
if chunk != _STREAM_IDENTIFIER:
@@ -256,7 +255,7 @@ def decompress(self, data):
256255
chunk = _uncompress(chunk)
257256
if struct.pack("<L", _masked_crc32c(chunk)) != crc:
258257
raise UncompressError("crc mismatch")
259-
uncompressed.append(chunk)
258+
uncompressed += chunk
260259

261260
def flush(self):
262261
"""All pending input is processed, and a string containing the
@@ -274,7 +273,7 @@ def copy(self):
274273
to speed up random seeks into the stream at a future point.
275274
"""
276275
copy = StreamDecompressor()
277-
copy._buf, copy._header_found = self._buf, self._header_found
276+
copy._buf, copy._header_found = bytearray(self._buf), self._header_found
278277
return copy
279278

280279

snappy/snappy_cffi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def prepare(data):
2020
_out_data = None
2121
_out_size = None
2222

23-
_out_data = ffi.new('char[]', data)
23+
_out_data = ffi.from_buffer(data)
2424
_out_size = ffi.cast('size_t', len(data))
2525

2626
return (_out_data, _out_size)
@@ -89,6 +89,6 @@ def isValidCompressed(data):
8989
decompress = uncompress
9090

9191
def _crc32c(data):
92-
c_data = ffi.new('char[]', data)
92+
c_data = ffi.from_buffer(data)
9393
size = ffi.cast('int', len(data))
9494
return int(lib._crc32c(c_data, size))

snappy/snappymodule.cc

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,18 +203,22 @@ snappy__is_valid_compressed_buffer(PyObject *self, PyObject *args)
203203
static PyObject *
204204
snappy__crc32c(PyObject *self, PyObject *args)
205205
{
206-
const unsigned char * input;
207-
int input_size;
206+
Py_buffer input;
207+
PyObject * result;
208208

209209
#if PY_MAJOR_VERSION >= 3
210-
if (!PyArg_ParseTuple(args, "y#", &input, &input_size))
210+
if (!PyArg_ParseTuple(args, "y*", &input))
211211
#else
212-
if (!PyArg_ParseTuple(args, "s#", &input, &input_size))
212+
if (!PyArg_ParseTuple(args, "s*", &input))
213213
#endif
214214
return NULL;
215215

216-
return PyLong_FromUnsignedLong(
217-
crc_finalize(crc_update(crc_init(), input, input_size)));
216+
result = PyLong_FromUnsignedLong(
217+
crc_finalize(crc_update(crc_init(), (const unsigned char *) input.buf, input.len)));
218+
219+
PyBuffer_Release(&input);
220+
221+
return result;
218222
}
219223

220224
static PyMethodDef snappy_methods[] = {

test_snappy.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,18 @@ def test_compression(self):
163163
b"\x01\x04\x00\x01" + crc1 + data[:snappy.snappy._CHUNK_MAX] +
164164
b"\x01\xff\xff\x00" + crc2 + data[snappy.snappy._CHUNK_MAX:])
165165

166+
# test that we can compress a memoryview
167+
data = memoryview(b"\x01" * (snappy.snappy._CHUNK_MAX * 2 - 5))
168+
crc1 = struct.pack("<L",
169+
snappy.snappy._masked_crc32c(data[:snappy.snappy._CHUNK_MAX]))
170+
self.assertEqual(crc1, b"h#6\x8e")
171+
crc2 = struct.pack("<L",
172+
snappy.snappy._masked_crc32c(data[snappy.snappy._CHUNK_MAX:]))
173+
self.assertEqual(crc2, b"q\x8foE")
174+
self.assertEqual(compressor.add_chunk(data, compress=False),
175+
b"\x01\x04\x00\x01" + crc1 + data[:snappy.snappy._CHUNK_MAX].tobytes() +
176+
b"\x01\xff\xff\x00" + crc2 + data[snappy.snappy._CHUNK_MAX:].tobytes())
177+
166178
def test_decompression(self):
167179
# test that we check for the initial stream identifier
168180
data = b"\x01" * 50
@@ -218,6 +230,15 @@ def test_decompression(self):
218230
decompressor.decompress(compressed_data[split2:])),
219231
uncompressed_data)
220232

233+
# test that we can decompress a memoryview
234+
decompressor = snappy.StreamDecompressor()
235+
decompressor.decompress(memoryview(b"\xff\x06\x00\x00sNaPpY"))
236+
self.assertEqual(
237+
decompressor.copy().decompress(
238+
memoryview(b"\x01\x36\x00\x00" +
239+
struct.pack("<L", snappy.snappy._masked_crc32c(data)) + data)),
240+
data)
241+
221242
def test_concatenation(self):
222243
data1 = os.urandom(snappy.snappy._CHUNK_MAX * 2)
223244
data2 = os.urandom(4096)

0 commit comments

Comments
 (0)