Metadata-Version: 2.1
Name: rustgzip
Version: 0.8.1
Summary: Rust-accelerated gzip I/O, integrity checks and CLI for Python 3.4+
Requires-Python: >=3.4
License: MIT
Description-Content-Type: text/markdown

# rustgzip

**面向 Python 3.4+ 的 Rust gzip 加速库：文件读写、完整性校验、内存编解码和命令行工具。**

压缩、解压及 CRC 使用 Rust 后端。Python 通过随包动态库调用，只依赖标准库；安装预编译 wheel 后不需要 Rust 编译器。生成标准 gzip 文件，可与系统 gzip、Python 标准库互通。

当前版本 **0.8.1** 支持 Linux x86_64（GLIBC 2.17+）和 Windows x86_64。已在实际 Python 3.4.10 / GLIBC 2.17 环境测试。尚未提供 ARM、龙芯、32 位或 macOS wheel。

## 安装

```bash
python -m pip install --upgrade rustgzip
python -c "import rustgzip; print(rustgzip.backend_info())"
```

内网 UOS 可在 PyPI 的 Release files 下载 manylinux wheel，复制到目标机器后离线安装：

```bash
python3.4 -m pip install --user --upgrade --no-index --no-deps ./rustgzip-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
```

支持 manylinux2014 的 pip 可以直接安装上述 wheel。旧 pip 不识别该标签时，可使用配套离线交付的 `rustgzip-0.8.1-py3-none-linux_x86_64.whl`（仅供离线分发，不上传 PyPI），或将 PyPI wheel 解压到独立目录使用。无需升级系统 GLIBC：

```bash
python3.4 -m zipfile -e rustgzip-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl ./rustgzip-local
PYTHONPATH=./rustgzip-local python3.4 -c "import rustgzip; print(rustgzip.backend_info())"
```

解压后保留完整 `rustgzip` 目录及其中全部 `.py`、`.so` 文件。

wheel 使用 `py3-none-平台` 标签：不绑定某个 CPython ABI，**仍依赖操作系统和 CPU 架构**。升级时整体替换包并重启 Python，不要混用旧 `.py` 和新动态库。原生 ABI 为 7。

## 选择接口

| 需求 | 接口 | 特点 |
| --- | --- | --- |
| 顺序读取/写入超大文件 | `open()` | 固定内部缓冲；文本、二进制、逐行处理 |
| 检查整个文件完整性 | `verify()` | Rust 内部循环，返回解压字节数，不保存明文 |
| 多核校验普通 gzip | `verify_parallel()` / `verify_parallel_bytes()` | 返回字节数；文件入口先读取有大小限制的压缩快照 |
| 内存编解码 | `compress()` / `decompress()` | bytes-like 输入，完整校验后返回结果 |
| 复用解压输出缓冲 | `decompress_into()` | 写入 bytearray/memoryview，返回有效长度 |
| 管道、标准输入、已有 fd | `open_fd()` / `open(fd)` | 默认复制 fd，仅关闭自己的副本 |
| 压缩数据分块到达 | `decompressobj()` | 单 gzip member 增量解码器 |
| gzip 信息 | `inspect()` | 默认检查头部；`verify=True` 全文校验 |
| 后台并行读取文件 | `open_parallel()` **实验** | 有界预取与取消；部分输入有性能退化 |

### 文件读写

```python
import rustgzip as gzip

with gzip.open('app.log.gz', 'wt', encoding='utf-8') as f:
    f.write('第一条日志\n')

with gzip.open('app.log.gz', 'rt', encoding='utf-8') as f:
    for line in f:
        print(line.rstrip('\n'))

buffer = bytearray(1024 * 1024)
with gzip.open('app.log.gz', 'rb') as f:
    while True:
        count = f.readinto(buffer)
        if not count:
            break
        # 处理 memoryview(buffer)[:count]；下轮读取会覆盖它。
```

二进制读取支持 `skip(n)`、向前 `seek(offset)` 和 `seek(offset, 1)`。跳过仍须解压经过的数据，由 Rust 复用缓冲完成；不支持倒带和 `SEEK_END`，`seekable()` 为 False。文本位置不是可任意设置的解压字节偏移。

### 完整性校验

```python
import rustgzip

try:
    size = rustgzip.verify('backup.tar.gz')
    print('完整性校验成功，解压字节数：%d' % size)
except OSError as exc:
    print('校验失败：%s' % exc)

# 对内存允许的输入，尝试多核校验；不生成解压文件。
size = rustgzip.verify_parallel('backup.tar.gz', workers=4,
                                max_output_size=16 * 1024**3)
```

多核校验默认压缩输入上限 512 MiB，工作池 `memory_limit` 另为 512 MiB，不包括输入快照、线程栈和分配器开销。`workers` 为 1..16，小输入或部分 stored 输入可自动走顺序路径。不保证所有文件多核都更快。压缩输入很大或需固定内存检查时，使用 `verify()`。

`verify_parallel_bytes(packed, ...)` 直接接收内存数据；精确 bytes 无额外快照，其他缓冲会先检查大小并复制为不可变输入。另一线程可调用 `CancellationToken.cancel()` 中止使用该 token 的校验；取消后 token 不可复位。

### 内存与增量解码

```python
import rustgzip

packed = rustgzip.compress(b'hello\n', compresslevel=6)
plain = rustgzip.decompress(packed, max_output_size=16 * 1024**2)
output = bytearray(1024)
count = rustgzip.decompress_into(packed, output)
plain_view = memoryview(output)[:count]

# 任意网络分块都可作为输入；这里只演示两段。
d = rustgzip.decompressobj(max_output_size=16 * 1024**2)
try:
    first = d.decompress(packed[:7])
    second = d.decompress(packed[7:])
    final = d.finish()  # 要求有效尾部；截断报错，调用后对象关闭。
finally:
    d.close()
```

增量对象只解码 **一个 gzip member**，不是所有 zlib/raw 格式的完整替代品。`eof` 在 CRC/ISIZE 通过后为 True，后续字节保存在 `unused_data`。使用 `max_length` 或 `decompress_into` 限制单次输出时，须继续传入 `unconsumed_tail`，并按需用空输入排空后端输出。增量解码不自动获得多核加速。

内存 `decompress` 和增量对象默认累计输出上限为 512 MiB；增量对象可显式设 `max_output_size=None`。增量 `max_input_size` 限制单次输入和保留的 unused_data，不是累计网络流长度。into 容量不足或格式错误后，输出缓冲可能部分改写，须丢弃本次结果。

### 文件描述符与元数据

```python
import rustgzip

with rustgzip.open_fd(0, 'rb') as f:
    chunk = f.read(65536)  # 支持 stdin/管道，默认保留原 fd。

header = rustgzip.inspect('data.gz')
# verified=False，uncompressed_size/crc32 均为 None。
full = rustgzip.inspect('data.gz', verify=True, max_output_size=16 * 1024**3)
print(full['uncompressed_size'], full['crc32'])
```

fd 仅支持读/写，副本共享文件位置，不能与原缓冲对象交替读取。fd 写入从当前位置开始，不截断。`closefd=True` 会在对象关闭时同时关闭原 fd，期间不得自行关闭或复用它。

`inspect()` 只接受普通文件，返回首 member 头部信息。默认头部有效 **不代表正文完整**，不能凭物理末尾八字节推断总大小。`verify=True` 完整扫描所有 member，返回准确累计大小与全部明文 CRC32，而非最后 member 的 trailer CRC。mtime=0 表示 gzip 未提供时间。

## 命令行

```bash
python3.4 -m rustgzip -t backup.tar.gz
python3.4 -m rustgzip -t -j 4 backup.tar.gz
python3.4 -m rustgzip -d input.gz -o output.log
python3.4 -m rustgzip -c app.log -o app.log.gz
python3.4 -m rustgzip -c -j 4 app.log -o app.log.gz
python3.4 -m rustgzip -l --verify input.gz
cat input.gz | python3.4 -m rustgzip -d - > output.log
```

默认 `-j 1` 顺序流式处理；`-c -j 2..32` 是最多 512 MiB 输入的内存并行压缩，`-t -j 2..16` 使用多核校验。`-d` 当前只接受 `-j 1`。`--max-output-size` 限制校验或解压明文字节数，`-l` 默认仅输出头部 JSON。

文件输出先写同目录临时文件，转换和尾部检查成功后提交。默认禁止覆盖，`--force` 成功后原子替换；不删除输入。无覆盖提交需文件系统支持 hard link。stdout 无法回滚，应等待进程退出成功再信任输出。退出码：0 成功、1 运行/数据错误、2 参数错误、130 Ctrl+C。CLI 拒绝无 gzip member 的空输入，合法空 member 不受影响。

## 实验：后台并行读取

```python
import rustgzip

with rustgzip.open_parallel('large.gz', workers=4,
                            memory_limit=256 * 1024**2,
                            chunk_size=1024**2, prefetch=2) as f:
    while True:
        chunk = f.read(1024**2)
        if not chunk:
            break
        # 处理本批；重要最终输出应在 EOF 成功后提交。
    print(f.verified, f.stats())
```

Rust 文件工作池按偏移读取有限窗口、推测解码并顺序交付。队列满时等待，消费后的输出槽复用；没有整文件快照或明文缓存。只支持稳定普通文件路径，`workers=1` 是后台顺序生产。输入须保持不变，元数据核对不是文件系统快照。

`memory_limit` 是工作池和输出队列的 payload 预约预算，不是 RSS 硬上限；线程栈、Python 缓冲和业务保留输出不计入。`read(-1)` 仍要求返回全部明文，超大文件应采用定长 read/readinto。

`cancel()` 可由另一线程调用，关闭会取消并回收线程。前缀在完整尾部通过前是暂定数据，提前退出不代表完整校验。`verified` 表示后台已成功验证全部输入，可能先于用户消费完预读数据。`stats()` 给出实际 worker 数、采用输出量和队列峰值；文本模式从 `f.buffer` 获取。

**性能尚未全面达标。** 0.8.0 同机 32 MiB、三轮交替测量（含 SHA256）中，随机 Base64 文本约 453→517 MiB/s，但结构化日志约 899→530 MiB/s；重复/stored 收益主要来自流水线和缓冲复用。0.8.1 修复生命周期问题，未声称解决日志退化。普通 `open()` 默认路径不变；返回全部 bytes 的多核 `decompress_parallel` 尚未公开。

## 兼容性与错误处理

- 支持串接 gzip member 和 member 后零填充；CRC/ISIZE、截断、非零尾部垃圾报错。库接口保留空输入返回空结果的历史行为。
- 流式读取可能先返回前缀，稍后发现坏尾部。只读一部分、read(0)、close 或头部探针都不能替代完整校验。
- 普通文件流不限制解压总量；不可信输入需由应用限制大小/时间，或使用带 max_output_size 的接口。CRC 不是密码学真实性验证。
- 不是标准库 gzip 的完全替代品：不支持任意倒带、fileobj、fileno、mtime 参数或同名类继承契约。tar 顺序读取建议 `tarfile.open(fileobj=f, mode='r|')`。
- wb 会截断原文件，防覆盖用 xb；重要文件可用 CLI 的临时文件提交。flush 不是 fsync，不保证断电持久化。
- 原生调用期间释放 GIL，不同流可并发。同一增量对象串行化调用；调用期间不要并发改写缓冲。文本流不承诺跨线程安全。
- 打开原生流或启动 worker 后不要 fork 并复用对象；子进程应重新打开。磁盘阻塞不承诺硬实时取消。
- Python 3.4 已结束上游维护；本项目保留运行兼容性，不代表 Python 本身仍得到安全更新。

## 后端、许可与源码构建

后端锁定 [flate2 1.1.10](https://github.com/rust-lang/flate2-rs) 与 [zlib-rs 0.6.8](https://github.com/trifectatechfoundation/zlib-rs)，使用运行时 CPU 特性探测，不以 target-cpu=native 生成构建机专用指令。没有新增 ISA-L、rapidgzip、zlib-ng 等 C/C++ 运行库。

本项目封装和新增调度代码采用 MIT。依赖各自许可、版权告知和来源说明随 wheel 的 licenses 目录，以及源码包的 THIRD_PARTY_LICENSES/、PROVENANCE.md 交付。未包含单位/他人的非公开代码、业务样本、密钥或编译缓存。

源码包提供锁文件、公开 vendor、测试及开发工具。构建使用现代 Rust/Python 工具链，**运行时 Python 3.4 不需要参与构建**。Linux 标准打包流程需要 cargo-zigbuild 和 Zig，以 GLIBC 2.17 为目标；安装 wheel 不需要这些工具。

```bash
cargo build --release --locked --offline
# Linux 发布还须以 cargo zigbuild 的 2.17 目标构建并核查 ELF 版本需求。
# 将对应动态库放进 python/rustgzip 后运行：
PYTHONPATH=python python3.4 tests/test_api.py
PYTHONPATH=python python3.4 tests/test_features.py
PYTHONPATH=python python3.4 tests/test_parallel_reader.py
```

测试使用临时生成数据和公开格式夹具，不上传用户文件。速度取决于 CPU、数据、压缩方式和 I/O；不能把内部校验 benchmark 或旧 Python 的加速比推广为所有调用的速度保证。
