Skip to content

Commit 1dad9a5

Browse files
authored
[compression] Add Protocol _Decompressor (#14885)
1 parent 684c0a6 commit 1dad9a5

File tree

3 files changed

+84
-4
lines changed

3 files changed

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

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, but `DecompressReader` calls it:
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, but `DecompressReader` calls it:
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)