Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust file-like objects and accept bytes/bytearray/numpy #45

Merged
merged 30 commits into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
748dc50
Initial File object
milesgranger Mar 10, 2021
dfe4ac0
impl Read/Write for RustyBuffer/File
milesgranger Mar 10, 2021
a0c4980
Successfull conversion to accepting Read/Write; missing lz4 and snapp…
milesgranger Mar 10, 2021
dd6915e
Add example python test, rust tests are fubar for the moment
milesgranger Mar 10, 2021
1384f84
Swap lz4, minor updates to generic macro
milesgranger Mar 11, 2021
007f07f
Update README, lz4 supports de/compress_into now
milesgranger Mar 11, 2021
ab63626
chkpt: fix tests, need to implement Seek for WritablePyByteArray
milesgranger Mar 11, 2021
587c978
full lz4 support
milesgranger Mar 12, 2021
e581346
Update Cargo.toml pyo3 features
milesgranger Mar 12, 2021
c1ffcc8
chkpt: start on a mostly IOBase interface
milesgranger Mar 12, 2021
5fa1fea
Support .tell()
milesgranger Mar 12, 2021
bc8d02a
Initial impl of readinto
milesgranger Mar 12, 2021
24292e0
rust file-like obj api test, other cleanup
milesgranger Mar 12, 2021
753db39
.write also take BytesType enum
milesgranger Mar 13, 2021
e1f69a2
get ci to work, remove rlib?
milesgranger Mar 13, 2021
024922b
Use PyBytes::new_with in read with known n_bytes
milesgranger Mar 13, 2021
3201a5a
Fix PyPy and linux cross compilation (#46)
messense Mar 13, 2021
a28dc56
Support Write for BytesType
milesgranger Mar 13, 2021
8a497dc
Initial impl support seek from positions .seek(.., whence=..)
milesgranger Mar 13, 2021
b6ba051
Convert all variants to some Rust wrapper
milesgranger Mar 13, 2021
1a5b811
impl Seek for BytesType and update lz4 n compressed bytes
milesgranger Mar 14, 2021
b36947e
chkpt: RawEncoder/RawDecoder impl
milesgranger Mar 14, 2021
7b9aad0
RawEncoder/Decoder use entire bytes, cannot stream
milesgranger Mar 16, 2021
26024a9
Support Buffer taking BytesType directly, like BytesIO does
milesgranger Mar 17, 2021
6b2557b
chkpt: working on docs
milesgranger Mar 17, 2021
5ce7bbd
chkpt: working on docs
milesgranger Mar 17, 2021
6e33f60
chkpt: more docs
milesgranger Mar 17, 2021
57ba3b9
more docs
milesgranger Mar 18, 2021
66150e1
even more..
milesgranger Mar 18, 2021
d3c1328
Remove dead code
milesgranger Mar 18, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ jobs:
profile: minimal
default: true
- name: Build
run: cargo build
run: cargo build --release
- name: Tests
run: cargo test
run: cargo test --no-default-features --release
- name: Install maturin
run: pip install maturin
- name: Build wheels - x86_64
Expand Down Expand Up @@ -78,10 +78,10 @@ jobs:
default: true
- name: Build
if: matrix.platform.python-architecture == 'x64'
run: cargo build
run: cargo build --release
- name: Tests
if: matrix.platform.python-architecture == 'x64'
run: cargo test
run: cargo test --no-default-features --release
- name: Install maturin
run: pip install maturin
- name: Build wheels
Expand Down Expand Up @@ -115,9 +115,9 @@ jobs:
profile: minimal
default: true
- name: Build
run: cargo build
run: cargo build --release
- name: Tests
run: cargo test
run: cargo test --no-default-features
- uses: actions/setup-python@v2
with:
python-version: 3.6
Expand Down Expand Up @@ -220,9 +220,9 @@ jobs:
profile: minimal
default: true
- name: Build
run: cargo build
run: cargo build --release
- name: Tests
run: cargo test
run: cargo test --no-default-features
- uses: actions/setup-python@v2
with:
python-version: pypy-3.6
Expand Down
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,24 @@ readme = "README.md"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
crate-type = ["cdylib", "rlib"]

[features]
default = ["abi3", "mimallocator"]
default = ["abi3", "mimallocator", "extension-module"]
abi3 = ["pyo3/abi3-py36"]
mimallocator = ["mimalloc"]
extension-module = ["pyo3/extension-module"]

[profile.release]
lto = "fat"
codegen-units = 1
opt-level = 3

[dependencies]
pyo3 = { version = "0.13.2", features = ["extension-module"] }
pyo3 = { version = "0.13.2" }
milesgranger marked this conversation as resolved.
Show resolved Hide resolved
snap = "^1"
brotli2 = "^0.3"
lz-fear = "0.1.1"
lz4 = "^1"
flate2 = "^1"
zstd = "0.6.0+zstd.1.4.8"
numpy = "0.13.0"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Where the API is `cramjam.<compression-variant>.compress/decompress` and accepts
both `bytes` and `bytearray` objects.

**de/compress_into**
Additionally, all variants except for lz4, support `decompress_into` and `compress_into`.
Additionally, support `decompress_into` and `compress_into`.
If you have a numpy array preallocated, that can be used as the output location for de/compression.
Ex.
```python
Expand Down
4 changes: 2 additions & 2 deletions src/brotli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ pub(crate) mod internal {
use std::io::Error;

/// Decompress via Brotli
pub fn decompress<W: Write + ?Sized>(input: &[u8], output: &mut W) -> Result<usize, Error> {
pub fn decompress<W: Write + ?Sized, R: Read>(input: R, output: &mut W) -> Result<usize, Error> {
let mut decoder = BrotliDecoder::new(input);
let n_bytes = std::io::copy(&mut decoder, output)?;
Ok(n_bytes as usize)
}

/// Compress via Brotli
pub fn compress<W: Write + ?Sized>(input: &[u8], output: &mut W, level: Option<u32>) -> Result<usize, Error> {
pub fn compress<W: Write + ?Sized, R: Read>(input: R, output: &mut W, level: Option<u32>) -> Result<usize, Error> {
let level = level.unwrap_or_else(|| 11);
let mut encoder = BrotliEncoder::new(input, level);
let n_bytes = std::io::copy(&mut encoder, output)?;
Expand Down
4 changes: 2 additions & 2 deletions src/deflate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@ pub(crate) mod internal {
use std::io::Error;

/// Decompress gzip data
pub fn decompress<W: Write + ?Sized>(input: &[u8], output: &mut W) -> Result<usize, Error> {
pub fn decompress<W: Write + ?Sized, R: Read>(input: R, output: &mut W) -> Result<usize, Error> {
let mut decoder = DeflateDecoder::new(input);
let n_bytes = std::io::copy(&mut decoder, output)?;
Ok(n_bytes as usize)
}

/// Compress gzip data
pub fn compress<W: Write + ?Sized>(input: &[u8], output: &mut W, level: Option<u32>) -> Result<usize, Error> {
pub fn compress<W: Write + ?Sized, R: Read>(input: R, output: &mut W, level: Option<u32>) -> Result<usize, Error> {
let level = level.unwrap_or_else(|| 6);

let mut encoder = DeflateEncoder::new(input, Compression::new(level));
Expand Down
4 changes: 2 additions & 2 deletions src/gzip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ pub(crate) mod internal {
use std::io::Error;

/// Decompress gzip data
pub fn decompress<W: Write + ?Sized>(input: &[u8], output: &mut W) -> Result<usize, Error> {
pub fn decompress<W: Write + ?Sized, R: Read>(input: R, output: &mut W) -> Result<usize, Error> {
let mut decoder = GzDecoder::new(input);
let n_bytes = std::io::copy(&mut decoder, output)?;
Ok(n_bytes as usize)
}

/// Compress gzip data
pub fn compress<W: Write + ?Sized>(input: &[u8], output: &mut W, level: Option<u32>) -> Result<usize, Error> {
pub fn compress<W: Write + ?Sized, R: Read>(input: R, output: &mut W, level: Option<u32>) -> Result<usize, Error> {
let level = level.unwrap_or_else(|| 6);
let mut encoder = GzEncoder::new(input, Compression::new(level));
let n_bytes = std::io::copy(&mut encoder, output)?;
Expand Down
152 changes: 152 additions & 0 deletions src/io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use std::fs::{File, OpenOptions};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};

use pyo3::prelude::*;
use pyo3::types::PyBytes;
use pyo3::number::pos;

#[pyclass(name = "File")]
pub struct RustyFile {
inner: File,
}

#[pymethods]
impl RustyFile {
#[new]
pub fn new(
path: &str,
read: Option<bool>,
write: Option<bool>,
truncate: Option<bool>,
append: Option<bool>,
) -> PyResult<Self> {
Ok(Self {
inner: OpenOptions::new()
.read(read.unwrap_or_else(|| true))
.write(write.unwrap_or_else(|| true))
.truncate(truncate.unwrap_or_else(|| false))
.create(true) // create if doesn't exist, but open if it does.
.append(append.unwrap_or_else(|| false))
.open(path)?,
})
}

pub fn write(&mut self, buf: &[u8]) -> PyResult<usize> {
let r = Write::write(self, buf)?;
Ok(r)
}
pub fn read<'a>(&mut self, py: Python<'a>, n_bytes: Option<usize>) -> PyResult<&'a PyBytes> {
match n_bytes {
Some(n) => {
let mut buf = vec![0; n];
self.inner.read(buf.as_mut_slice())?;
Ok(PyBytes::new(py, buf.as_slice()))
milesgranger marked this conversation as resolved.
Show resolved Hide resolved
}
None => {
let mut buf = vec![];
self.inner.read_to_end(&mut buf)?;
Ok(PyBytes::new(py, buf.as_slice()))
}
}
}
pub fn seek(&mut self, position: usize) -> PyResult<usize> {
let r = Seek::seek(self, SeekFrom::Start(position as u64))?;
Ok(r as usize)
}
pub fn set_len(&mut self, size: usize) -> PyResult<()> {
self.inner.set_len(size as u64)?;
Ok(())
}
pub fn truncate(&mut self) -> PyResult<()> {
self.set_len(0)
}
}

#[pyclass(name = "Buffer")]
#[derive(Default)]
pub struct RustyBuffer {
inner: Cursor<Vec<u8>>,
}

#[pymethods]
impl RustyBuffer {
#[new]
pub fn new(len: Option<usize>) -> Self {
Self {
inner: Cursor::new(vec![0; len.unwrap_or_else(|| 0)]),
}
}

pub fn write(&mut self, buf: &[u8]) -> PyResult<usize> {
let r = Write::write(self, buf)?;
Ok(r)
}
pub fn read<'a>(&mut self, py: Python<'a>, n_bytes: Option<usize>) -> PyResult<&'a PyBytes> {
match n_bytes {
Some(n) => {
let mut buf = vec![0; n];
self.inner.read(buf.as_mut_slice())?;
Ok(PyBytes::new(py, buf.as_slice()))
}
None => {
let mut buf = vec![];
self.inner.read_to_end(&mut buf)?;
Ok(PyBytes::new(py, buf.as_slice()))
}
}
}
pub fn seek(&mut self, position: usize) -> PyResult<usize> {
// TODO: Support SeekFrom from python side as in IOBase.seek definition
let r = Seek::seek(self, SeekFrom::Start(position as u64))?;
Ok(r as usize)
}
pub fn set_len(&mut self, size: usize) -> PyResult<()> {
self.inner.get_mut().resize(size, 0);
Ok(())
}
pub fn truncate(&mut self) -> PyResult<()> {
self.inner.get_mut().truncate(0);
Ok(())
}
}

impl Seek for RustyBuffer {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
}
impl Seek for RustyFile {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
}

impl Write for RustyBuffer {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}

impl Write for RustyFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}

impl Read for RustyBuffer {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inner.read(buf)
}
}

impl Read for RustyFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inner.read(buf)
}
}
Loading