Skip to content

Commit 8462952

Browse files
committed
[compression] Add common _Decompressor Protocol
1 parent 7f2d948 commit 8462952

File tree

3 files changed

+87
-4
lines changed

3 files changed

+87
-4
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from __future__ import annotations
2+
3+
import io
4+
from _compression import DecompressReader as DecompressReader_other, _Reader as _Reader_other
5+
from _typeshed import ReadableBuffer
6+
from bz2 import BZ2Decompressor
7+
from compression._common._streams import DecompressReader, _Decompressor, _Reader
8+
from compression.zstd import ZstdDecompressor
9+
from lzma import LZMADecompressor
10+
from typing import cast
11+
from typing_extensions import assert_type
12+
from zlib import decompressobj
13+
14+
###
15+
# Tests for DecompressReader/_Decompressor
16+
###
17+
18+
19+
class CustomDecompressor:
20+
def decompress(self, data: ReadableBuffer, max_length: int = -1) -> bytes:
21+
return b""
22+
23+
@property
24+
def unused_data(self) -> bytes:
25+
return b""
26+
27+
@property
28+
def eof(self) -> bool:
29+
return False
30+
31+
@property
32+
def needs_input(self) -> bool:
33+
return False
34+
35+
36+
def accept_decompressor(d: _Decompressor) -> None:
37+
d.decompress(b"random bytes", 0)
38+
assert_type(d.eof, bool)
39+
assert_type(d.unused_data, bytes)
40+
41+
42+
# Test objects from compression._common._streams
43+
fp = cast(_Reader, io.BytesIO(b"hello world")) # type: ignore
44+
DecompressReader(fp, decompressobj)
45+
DecompressReader(fp, BZ2Decompressor)
46+
DecompressReader(fp, LZMADecompressor)
47+
DecompressReader(fp, ZstdDecompressor)
48+
DecompressReader(fp, CustomDecompressor)
49+
accept_decompressor(decompressobj())
50+
accept_decompressor(BZ2Decompressor())
51+
accept_decompressor(LZMADecompressor())
52+
accept_decompressor(ZstdDecompressor())
53+
accept_decompressor(CustomDecompressor())
54+
55+
# Test objects from _compression
56+
fp = cast(_Reader_other, io.BytesIO(b"hello world")) # type: ignore
57+
DecompressReader_other(fp, decompressobj)
58+
DecompressReader_other(fp, BZ2Decompressor)
59+
DecompressReader_other(fp, LZMADecompressor)
60+
DecompressReader_other(fp, ZstdDecompressor)
61+
DecompressReader_other(fp, CustomDecompressor)

stdlib/_compression.pyi

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# _compression is replaced by compression._common._streams on Python 3.14+ (PEP-784)
22

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

16+
@type_check_only
17+
class _Decompressor(Protocol):
18+
def decompress(self, data: ReadableBuffer, /, max_length: int = ...) -> bytes: ...
19+
@property
20+
def unused_data(self) -> bytes: ...
21+
@property
22+
def eof(self) -> bool: ...
23+
# `zlib._Decompress` does not have next property, unlike other Decompressors:
24+
# @property
25+
# def needs_input(self) -> bool: ...
26+
1627
class BaseStream(BufferedIOBase): ...
1728

1829
class DecompressReader(RawIOBase):
1930
def __init__(
2031
self,
2132
fp: _Reader,
22-
decomp_factory: Callable[..., Incomplete],
33+
decomp_factory: Callable[..., _Decompressor],
2334
trailing_error: type[Exception] | tuple[type[Exception], ...] = (),
2435
**decomp_args: Any, # These are passed to decomp_factory.
2536
) -> None: ...

stdlib/compression/_common/_streams.pyi

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from _typeshed import Incomplete, WriteableBuffer
1+
from _typeshed import ReadableBuffer, WriteableBuffer
22
from collections.abc import Callable
33
from io import DEFAULT_BUFFER_SIZE, BufferedIOBase, RawIOBase
44
from typing import Any, Protocol, type_check_only
@@ -11,13 +11,24 @@ class _Reader(Protocol):
1111
def seekable(self) -> bool: ...
1212
def seek(self, n: int, /) -> Any: ...
1313

14+
@type_check_only
15+
class _Decompressor(Protocol):
16+
def decompress(self, data: ReadableBuffer, /, max_length: int = ...) -> bytes: ...
17+
@property
18+
def unused_data(self) -> bytes: ...
19+
@property
20+
def eof(self) -> bool: ...
21+
# `zlib._Decompress` does not have next property, unlike other Decompressors:
22+
# @property
23+
# def needs_input(self) -> bool: ...
24+
1425
class BaseStream(BufferedIOBase): ...
1526

1627
class DecompressReader(RawIOBase):
1728
def __init__(
1829
self,
1930
fp: _Reader,
20-
decomp_factory: Callable[..., Incomplete], # Consider backporting changes to _compression
31+
decomp_factory: Callable[..., _Decompressor], # Consider backporting changes to _compression
2132
trailing_error: type[Exception] | tuple[type[Exception], ...] = (),
2233
**decomp_args: Any, # These are passed to decomp_factory.
2334
) -> None: ...

0 commit comments

Comments
 (0)