Skip to content

Commit

Permalink
Support zlib (#2), bump libdeflate version, add python stdlib roundtr…
Browse files Browse the repository at this point in the history
…ip tests
  • Loading branch information
dcwatson committed Dec 1, 2023
1 parent 2d97532 commit 65c550d
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 9 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## 0.5.0

* Added raw DEFLATE functionality from libdeflate
* Added raw DEFLATE functionality from libdeflate (#39)
* Added zlib functionality from libdeflate (#2)
* Updated bundled libdeflate to v1.19


## 0.4.0
Expand Down
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.PHONY: clean format test

clean:
rm -rf *.so deflate.egg-info build

format:
clang-format -i deflate.c
black tests
isort tests

test: clean
pip install -e .
pytest
96 changes: 89 additions & 7 deletions deflate.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ static PyObject *deflate_adler32(PyObject *self, PyObject *args) {
}

static PyObject *deflate_deflate_compress(PyObject *self, PyObject *args,
PyObject *kwargs) {
PyObject *kwargs) {
static char *keywords[] = {"data", "compresslevel", NULL};
Py_buffer data;
int compression_level = 6;
Expand All @@ -160,8 +160,8 @@ static PyObject *deflate_deflate_compress(PyObject *self, PyObject *args,
return PyErr_NoMemory();
}

size_t compressed_size = libdeflate_deflate_compress(compressor, data.buf, data.len,
PyBytes_AsString(bytes), bound);
size_t compressed_size = libdeflate_deflate_compress(
compressor, data.buf, data.len, PyBytes_AsString(bytes), bound);
libdeflate_free_compressor(compressor);
PyBuffer_Release(&data);

Expand Down Expand Up @@ -190,11 +190,89 @@ static PyObject *deflate_deflate_decompress(PyObject *self, PyObject *args) {
return PyErr_NoMemory();
}

size_t decompressed_size;
struct libdeflate_decompressor *decompressor = libdeflate_alloc_decompressor();
enum libdeflate_result result = libdeflate_deflate_decompress(
decompressor, data.buf, data.len, PyBytes_AsString(output), size,
&decompressed_size);
libdeflate_free_decompressor(decompressor);

// Resize the bytes object to the decompressed size and release the input buffer.
_PyBytes_Resize(&output, decompressed_size);
PyBuffer_Release(&data);

if (result != LIBDEFLATE_SUCCESS) {
Py_DECREF(output);
PyErr_SetString(DeflateError, "Decompression failed.");
return NULL;
}

return output;
}

static PyObject *deflate_zlib_compress(PyObject *self, PyObject *args,
PyObject *kwargs) {
static char *keywords[] = {"data", "compresslevel", NULL};
Py_buffer data;
int compression_level = 6;

if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y*|i", keywords, &data,
&compression_level)) {
return NULL;
}

if (compression_level < 1 || compression_level > 12) {
PyBuffer_Release(&data);
PyErr_SetString(PyExc_ValueError, "compresslevel must be between 1 and 12.");
return NULL;
}

struct libdeflate_compressor *compressor =
libdeflate_alloc_compressor(compression_level);
size_t bound = libdeflate_zlib_compress_bound(compressor, data.len);

PyObject *bytes = PyBytes_FromStringAndSize(NULL, bound);
if (bytes == NULL) {
libdeflate_free_compressor(compressor);
PyBuffer_Release(&data);
return PyErr_NoMemory();
}

size_t compressed_size = libdeflate_zlib_compress(compressor, data.buf, data.len,
PyBytes_AsString(bytes), bound);
libdeflate_free_compressor(compressor);
PyBuffer_Release(&data);

if (compressed_size == 0) {
Py_DECREF(bytes);
PyErr_SetString(DeflateError, "Compression failed.");
return NULL;
}

_PyBytes_Resize(&bytes, compressed_size);

return bytes;
}

static PyObject *deflate_zlib_decompress(PyObject *self, PyObject *args) {
Py_buffer data;
unsigned int size = 0;

if (!PyArg_ParseTuple(args, "y*|I", &data, &size)) {
return NULL;
}

PyObject *output = PyBytes_FromStringAndSize(NULL, size);
if (output == NULL) {
PyBuffer_Release(&data);
return PyErr_NoMemory();
}

size_t decompressed_size;
struct libdeflate_decompressor *decompressor = libdeflate_alloc_decompressor();
enum libdeflate_result result =
libdeflate_deflate_decompress(decompressor, data.buf, data.len,
PyBytes_AsString(output), size, &decompressed_size);
libdeflate_zlib_decompress(decompressor, data.buf, data.len,
PyBytes_AsString(output), size, &decompressed_size);
libdeflate_free_decompressor(decompressor);

// Resize the bytes object to the decompressed size and release the input buffer.
Expand All @@ -215,10 +293,14 @@ static PyMethodDef deflate_methods[] = {
"Compress data using gzip."},
{"gzip_decompress", (PyCFunction)deflate_gzip_decompress, METH_VARARGS,
"Decompress gzip data."},
{"deflate_compress", (PyCFunction)deflate_deflate_compress, METH_VARARGS | METH_KEYWORDS,
"Compress data using raw DEFLATE."},
{"deflate_compress", (PyCFunction)deflate_deflate_compress,
METH_VARARGS | METH_KEYWORDS, "Compress data using raw DEFLATE."},
{"deflate_decompress", (PyCFunction)deflate_deflate_decompress, METH_VARARGS,
"Decompress raw DEFLATE data."},
{"zlib_compress", (PyCFunction)deflate_zlib_compress, METH_VARARGS | METH_KEYWORDS,
"Compress data using zlib."},
{"zlib_decompress", (PyCFunction)deflate_zlib_decompress, METH_VARARGS,
"Decompress zlib data."},
{"crc32", (PyCFunction)deflate_crc32, METH_VARARGS,
"CRC32 algorithm from libdeflate"},
{"adler32", (PyCFunction)deflate_adler32, METH_VARARGS,
Expand Down
9 changes: 9 additions & 0 deletions tests/test_compress.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# test compress / decompress functionality

import gzip
import os
import zlib

import pytest

Expand All @@ -21,6 +23,13 @@
def test_roundtrip(data):
"""test whether decompressing compressed data yields the original data"""
assert deflate.gzip_decompress(deflate.gzip_compress(data)) == data
assert deflate.gzip_decompress(gzip.compress(data)) == data
assert gzip.decompress(deflate.gzip_compress(data)) == data

assert deflate.zlib_decompress(deflate.zlib_compress(data), len(data)) == data
assert deflate.zlib_decompress(zlib.compress(data), len(data)) == data
assert zlib.decompress(deflate.zlib_compress(data)) == data

assert deflate.deflate_decompress(deflate.deflate_compress(data), len(data)) == data


Expand Down

0 comments on commit 65c550d

Please sign in to comment.