Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
58 changes: 58 additions & 0 deletions stdlib/@tests/test_cases/check_compression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from __future__ import annotations

import io
import sys
from _typeshed import ReadableBuffer
from bz2 import BZ2Decompressor
from lzma import LZMADecompressor
from typing import cast
from typing_extensions import assert_type
from zlib import decompressobj

if sys.version_info >= (3, 14):
from compression._common._streams import DecompressReader, _Decompressor, _Reader
from compression.zstd import ZstdDecompressor
else:
from _compression import DecompressReader, _Decompressor, _Reader

###
# Tests for DecompressReader/_Decompressor
###


class CustomDecompressor:
def decompress(self, data: ReadableBuffer, max_length: int = -1) -> bytes:
return b""

@property
def unused_data(self) -> bytes:
return b""

@property
def eof(self) -> bool:
return False

@property
def needs_input(self) -> bool:
return False


def accept_decompressor(d: _Decompressor) -> None:
d.decompress(b"random bytes", 0)
assert_type(d.eof, bool)
assert_type(d.unused_data, bytes)


fp = cast(_Reader, io.BytesIO(b"hello world"))
DecompressReader(fp, decompressobj)
DecompressReader(fp, BZ2Decompressor)
DecompressReader(fp, LZMADecompressor)
DecompressReader(fp, CustomDecompressor)
accept_decompressor(decompressobj())
accept_decompressor(BZ2Decompressor())
accept_decompressor(LZMADecompressor())
accept_decompressor(CustomDecompressor())

if sys.version_info >= (3, 14):
DecompressReader(fp, ZstdDecompressor)
accept_decompressor(ZstdDecompressor())
15 changes: 13 additions & 2 deletions stdlib/_compression.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# _compression is replaced by compression._common._streams on Python 3.14+ (PEP-784)

from _typeshed import Incomplete, WriteableBuffer
from _typeshed import ReadableBuffer, WriteableBuffer
from collections.abc import Callable
from io import DEFAULT_BUFFER_SIZE, BufferedIOBase, RawIOBase
from typing import Any, Protocol, type_check_only
Expand All @@ -13,13 +13,24 @@ class _Reader(Protocol):
def seekable(self) -> bool: ...
def seek(self, n: int, /) -> Any: ...

@type_check_only
class _Decompressor(Protocol):
def decompress(self, data: ReadableBuffer, /, max_length: int = ...) -> bytes: ...
@property
def unused_data(self) -> bytes: ...
@property
def eof(self) -> bool: ...
# `zlib._Decompress` does not have next property, but `DecompressReader` calls it:
# @property
# def needs_input(self) -> bool: ...

class BaseStream(BufferedIOBase): ...

class DecompressReader(RawIOBase):
def __init__(
self,
fp: _Reader,
decomp_factory: Callable[..., Incomplete],
decomp_factory: Callable[..., _Decompressor],
trailing_error: type[Exception] | tuple[type[Exception], ...] = (),
**decomp_args: Any, # These are passed to decomp_factory.
) -> None: ...
Expand Down
15 changes: 13 additions & 2 deletions stdlib/compression/_common/_streams.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from _typeshed import Incomplete, WriteableBuffer
from _typeshed import ReadableBuffer, WriteableBuffer
from collections.abc import Callable
from io import DEFAULT_BUFFER_SIZE, BufferedIOBase, RawIOBase
from typing import Any, Protocol, type_check_only
Expand All @@ -11,13 +11,24 @@ class _Reader(Protocol):
def seekable(self) -> bool: ...
def seek(self, n: int, /) -> Any: ...

@type_check_only
class _Decompressor(Protocol):
def decompress(self, data: ReadableBuffer, /, max_length: int = ...) -> bytes: ...
@property
def unused_data(self) -> bytes: ...
@property
def eof(self) -> bool: ...
# `zlib._Decompress` does not have next property, but `DecompressReader` calls it:
# @property
# def needs_input(self) -> bool: ...

class BaseStream(BufferedIOBase): ...

class DecompressReader(RawIOBase):
def __init__(
self,
fp: _Reader,
decomp_factory: Callable[..., Incomplete], # Consider backporting changes to _compression
decomp_factory: Callable[..., _Decompressor], # Consider backporting changes to _compression
trailing_error: type[Exception] | tuple[type[Exception], ...] = (),
**decomp_args: Any, # These are passed to decomp_factory.
) -> None: ...
Expand Down