Skip to content

Commit

Permalink
gzip updates
Browse files Browse the repository at this point in the history
  • Loading branch information
jessekrubin committed Feb 3, 2025
1 parent 2b509df commit 617606f
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 51 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/ryo3-flate2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ repository.workspace = true
[dependencies]
flate2 = "1.0.35"
pyo3 = { workspace = true, features = ["experimental-inspect"] }
ryo3-macros.workspace = true

[lints]
workspace = true
7 changes: 7 additions & 0 deletions crates/ryo3-flate2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# `ryo3-flate2`

Wrapper around the `flate2` crate for Python.

REF:
- [crates.io](https://crates.io/crates/flate2)
- [docs.rs](https://docs.rs/flate2)
53 changes: 53 additions & 0 deletions crates/ryo3-flate2/src/gz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use ryo3_macros::py_value_error;
use std::io::{Read, Write};

#[pyfunction]
#[pyo3(signature = (data, quality=None))]
pub fn gzip_encode(py: Python<'_>, data: &[u8], quality: Option<u32>) -> PyResult<PyObject> {
let quality = if let Some(param) = quality {
if param > 9 {
return Err(py_value_error!(
"Quality must be between 0 and 9 - got: {param:?}"
));
}
Compression::new(param)
} else {
Compression::default()
};
let mut gzip_encoder = GzEncoder::new(Vec::new(), quality);
gzip_encoder.write_all(data).map_err(|e| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("gzip-encode-error: {e:?}"))
})?;
let encoded = gzip_encoder.finish().map_err(|e| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("gzip-encode-error: {e:?}"))
})?;
Ok(PyBytes::new(py, &encoded).into())
}

#[pyfunction]
pub fn gzip_decode(py: Python<'_>, data: &[u8]) -> PyResult<PyObject> {
let mut decompressed = Vec::new();
GzDecoder::new(data)
.read_to_end(&mut decompressed)
.map_err(|e| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("gzip-decode-error: {e:?}"))
})?;
Ok(PyBytes::new(py, &decompressed).into())
}

// aliases...
#[pyfunction]
#[pyo3(signature = (data, quality=None))]
pub fn gzip(py: Python<'_>, data: &[u8], quality: Option<u32>) -> PyResult<PyObject> {
gzip_encode(py, data, quality)
}

#[pyfunction]
pub fn gunzip(py: Python<'_>, data: &[u8]) -> PyResult<PyObject> {
gzip_decode(py, data)
}
53 changes: 2 additions & 51 deletions crates/ryo3-flate2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,7 @@
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
mod gz;
pub use gz::*;
use pyo3::prelude::PyModule;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use std::io::{Read, Write};

#[pyfunction]
#[pyo3(signature = (data, quality=None))]
pub fn gzip_encode(py: Python<'_>, data: &[u8], quality: Option<u32>) -> PyResult<PyObject> {
let quality = if let Some(param) = quality {
if param > 9 {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"The optional second argument to gzip() must be between 0 and 9",
));
}
Compression::new(param)
} else {
Compression::default()
};
let mut gzip_encoder = GzEncoder::new(Vec::new(), quality);
gzip_encoder.write_all(data).map_err(|e| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("gzip-encode-error: {e:?}"))
})?;
let encoded = gzip_encoder.finish().map_err(|e| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("gzip-encode-error: {e:?}"))
})?;
Ok(PyBytes::new(py, &encoded).into())
}

#[pyfunction]
pub fn gzip_decode(py: Python<'_>, data: &[u8]) -> PyResult<PyObject> {
let mut decompressed = Vec::new();
GzDecoder::new(data)
.read_to_end(&mut decompressed)
.map_err(|e| {
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("gzip-decode-error: {e:?}"))
})?;
Ok(PyBytes::new(py, &decompressed).into())
}

// aliases...
#[pyfunction]
#[pyo3(signature = (data, quality=None))]
pub fn gzip(py: Python<'_>, data: &[u8], quality: Option<u32>) -> PyResult<PyObject> {
gzip_encode(py, data, quality)
}

#[pyfunction]
pub fn gunzip(py: Python<'_>, data: &[u8]) -> PyResult<PyObject> {
gzip_decode(py, data)
}

pub fn pymod_add(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(gzip_encode, m)?)?;
Expand Down
1 change: 1 addition & 0 deletions crates/ryo3-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod not_implemented;
mod py_errs;
7 changes: 7 additions & 0 deletions crates/ryo3-macros/src/py_errs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// Return a `PyValueError` with formatted string
#[macro_export]
macro_rules! py_value_error {
($($arg:tt)*) => {
::pyo3::PyErr::new::<pyo3::exceptions::PyValueError, _>( format!($($arg)*) )
};
}
17 changes: 17 additions & 0 deletions tests/flate2/test_gzip.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import gzip
import io

import pytest

import ry


Expand Down Expand Up @@ -86,3 +88,18 @@ def test_cross_compatibility() -> None:
# ry
ry_decompressed = ry.gzip_decode(stdlib_compressed)
assert ry_decompressed == input_data


@pytest.mark.parametrize("quality", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
def test_quality_gzip(quality: int) -> None:
input_data = b"XXXXXXXXXXYYYYYYYYYY"
output_data = ry.gzip_encode(input_data, quality=quality)
assert output_data is not None
decoded = ry.gzip_decode(output_data)
assert decoded == input_data


def test_gzip_quality_value_error() -> None:
with pytest.raises(ValueError) as e:
ry.gzip(b"test", quality=10)
s = str(e.value)

0 comments on commit 617606f

Please sign in to comment.