omlish 0.0.0.dev140__py3-none-any.whl → 0.0.0.dev142__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev140'
2
- __revision__ = '52063e007f706aee55c0e2f212e3f6714f063b6e'
1
+ __version__ = '0.0.0.dev142'
2
+ __revision__ = 'fe684cfe6dc46f867fb6990935e8b145b67bbefa'
3
3
 
4
4
 
5
5
  #
@@ -32,7 +32,7 @@ class Project(ProjectBase):
32
32
 
33
33
  optional_dependencies = {
34
34
  'async': [
35
- 'anyio ~= 4.6',
35
+ 'anyio ~= 4.7',
36
36
  'sniffio ~= 1.3',
37
37
 
38
38
  'greenlet ~= 3.1',
@@ -48,6 +48,8 @@ class Project(ProjectBase):
48
48
  'python-snappy ~= 0.7',
49
49
 
50
50
  'zstandard ~= 0.23',
51
+
52
+ 'brotli ~= 1.1',
51
53
  ],
52
54
 
53
55
  'diag': [
omlish/check.py CHANGED
@@ -144,6 +144,8 @@ def _raise(
144
144
 
145
145
 
146
146
  def _unpack_isinstance_spec(spec: ta.Any) -> tuple:
147
+ if _isinstance(spec, type):
148
+ return (spec,)
147
149
  if not _isinstance(spec, tuple):
148
150
  spec = (spec,)
149
151
  if None in spec:
omlish/funcs/pairs.py CHANGED
@@ -10,9 +10,9 @@ TODO:
10
10
 
11
11
  Compression choice:
12
12
  - lzma if-available minimal-space
13
- - lz4 if-available write-once
13
+ - lz4 if-available read-heavy
14
14
  - zstd if-available
15
- - bz2 write-once (but no parallel decompress)
15
+ - bz2 read-heavy (but no parallel decompress)
16
16
  - gz
17
17
  """
18
18
  import abc
@@ -0,0 +1,9 @@
1
+ """
2
+ TODO:
3
+ - zipfile?
4
+ - tarfile?
5
+ - lzf?
6
+ - blosc?
7
+ - python-lzo?
8
+ - zfp -> fp8?
9
+ """
omlish/io/compress/abc.py CHANGED
@@ -9,7 +9,7 @@ import abc
9
9
  ##
10
10
 
11
11
 
12
- class Compressor(abc.ABC):
12
+ class CompressorObject(abc.ABC):
13
13
  @abc.abstractmethod
14
14
  def compress(self, data: bytes) -> bytes:
15
15
  """
@@ -36,7 +36,7 @@ class Compressor(abc.ABC):
36
36
  ##
37
37
 
38
38
 
39
- class Decompressor(abc.ABC):
39
+ class DecompressorObject(abc.ABC):
40
40
  @property
41
41
  @abc.abstractmethod
42
42
  def unused_data(self) -> bytes:
@@ -68,7 +68,7 @@ class Decompressor(abc.ABC):
68
68
  raise NotImplementedError
69
69
 
70
70
 
71
- class NeedsInputDecompressor(Decompressor):
71
+ class NeedsInputDecompressorObject(DecompressorObject):
72
72
  """
73
73
  Used by:
74
74
  - bz2.BZ2Decompressor
@@ -85,7 +85,7 @@ class NeedsInputDecompressor(Decompressor):
85
85
  raise NotImplementedError
86
86
 
87
87
 
88
- class UnconsumedTailDecompressor(Decompressor):
88
+ class UnconsumedTailDecompressorObject(DecompressorObject):
89
89
  """
90
90
  Used by:
91
91
  - zlib.decompressobj
@@ -37,26 +37,26 @@
37
37
  import typing as ta
38
38
 
39
39
  from ... import check
40
- from .abc import Compressor
41
- from .abc import NeedsInputDecompressor
42
- from .abc import UnconsumedTailDecompressor
43
- from .types import IncrementalCompressor
44
- from .types import IncrementalDecompressor
40
+ from ..generators import BytesSteppedGenerator
41
+ from ..generators import BytesSteppedReaderGenerator
42
+ from .abc import CompressorObject
43
+ from .abc import NeedsInputDecompressorObject
44
+ from .abc import UnconsumedTailDecompressorObject
45
45
 
46
46
 
47
47
  ##
48
48
 
49
49
 
50
- class CompressorIncrementalAdapter:
50
+ class CompressorObjectIncrementalAdapter:
51
51
  def __init__(
52
52
  self,
53
- factory: ta.Callable[..., Compressor],
53
+ factory: ta.Callable[..., CompressorObject],
54
54
  ) -> None:
55
55
  super().__init__()
56
56
 
57
57
  self._factory = factory
58
58
 
59
- def __call__(self) -> IncrementalCompressor:
59
+ def __call__(self) -> BytesSteppedGenerator:
60
60
  compressor = self._factory()
61
61
 
62
62
  while True:
@@ -77,10 +77,10 @@ class CompressorIncrementalAdapter:
77
77
  ##
78
78
 
79
79
 
80
- class DecompressorIncrementalAdapter:
80
+ class DecompressorObjectIncrementalAdapter:
81
81
  def __init__(
82
82
  self,
83
- factory: ta.Callable[..., NeedsInputDecompressor | UnconsumedTailDecompressor],
83
+ factory: ta.Callable[..., NeedsInputDecompressorObject | UnconsumedTailDecompressorObject],
84
84
  *,
85
85
  trailing_error: type[BaseException] | tuple[type[BaseException], ...] = (),
86
86
  ) -> None:
@@ -89,7 +89,7 @@ class DecompressorIncrementalAdapter:
89
89
  self._factory = factory
90
90
  self._trailing_error = trailing_error
91
91
 
92
- def __call__(self) -> IncrementalDecompressor:
92
+ def __call__(self) -> BytesSteppedReaderGenerator:
93
93
  pos = 0
94
94
 
95
95
  data = None # Default if EOF is encountered
@@ -145,3 +145,4 @@ class DecompressorIncrementalAdapter:
145
145
 
146
146
  pos += len(data)
147
147
  check.none((yield data))
148
+ data = None
@@ -0,0 +1,24 @@
1
+ import abc
2
+
3
+ from ..generators import BytesSteppedGenerator
4
+ from ..generators import BytesSteppedReaderGenerator
5
+
6
+
7
+ class Compression(abc.ABC):
8
+ @abc.abstractmethod
9
+ def compress(self, d: bytes) -> bytes:
10
+ raise NotImplementedError
11
+
12
+ @abc.abstractmethod
13
+ def decompress(self, d: bytes) -> bytes:
14
+ raise NotImplementedError
15
+
16
+
17
+ class IncrementalCompression(abc.ABC):
18
+ @abc.abstractmethod
19
+ def compress_incremental(self) -> BytesSteppedGenerator[None]:
20
+ raise NotImplementedError
21
+
22
+ @abc.abstractmethod
23
+ def decompress_incremental(self) -> BytesSteppedReaderGenerator[None]:
24
+ raise NotImplementedError
@@ -0,0 +1,33 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ... import lang
5
+ from .base import Compression
6
+
7
+
8
+ if ta.TYPE_CHECKING:
9
+ import brotli
10
+ else:
11
+ brotli = lang.proxy_import('brotli')
12
+
13
+
14
+ @dc.dataclass(frozen=True, kw_only=True)
15
+ class SnappyCompression(Compression):
16
+ mode: int | None = None
17
+ quality: int | None = None
18
+ lgwin: int | None = None
19
+ lgblock: int | None = None
20
+
21
+ def compress(self, d: bytes) -> bytes:
22
+ return brotli.compress(
23
+ d,
24
+ **(dict(mode=self.mode) if self.mode is not None else {}),
25
+ **(dict(mode=self.quality) if self.quality is not None else {}),
26
+ **(dict(mode=self.lgwin) if self.lgwin is not None else {}),
27
+ **(dict(mode=self.lgblock) if self.lgblock is not None else {}),
28
+ )
29
+
30
+ def decompress(self, d: bytes) -> bytes:
31
+ return brotli.decompress(
32
+ d,
33
+ )
omlish/io/compress/bz2.py CHANGED
@@ -1,11 +1,14 @@
1
+ import dataclasses as dc
1
2
  import functools
2
3
  import typing as ta
3
4
 
4
5
  from ... import lang
5
- from .adapters import CompressorIncrementalAdapter
6
- from .adapters import DecompressorIncrementalAdapter
7
- from .types import IncrementalCompressor
8
- from .types import IncrementalDecompressor
6
+ from ..generators import BytesSteppedGenerator
7
+ from ..generators import BytesSteppedReaderGenerator
8
+ from .adapters import CompressorObjectIncrementalAdapter
9
+ from .adapters import DecompressorObjectIncrementalAdapter
10
+ from .base import Compression
11
+ from .base import IncrementalCompression
9
12
 
10
13
 
11
14
  if ta.TYPE_CHECKING:
@@ -14,29 +17,31 @@ else:
14
17
  bz2 = lang.proxy_import('bz2')
15
18
 
16
19
 
17
- class IncrementalBz2Compressor:
18
- def __init__(
19
- self,
20
- *,
21
- compresslevel: int = 9,
22
- ) -> None:
23
- super().__init__()
20
+ @dc.dataclass(frozen=True, kw_only=True)
21
+ class Bz2Compression(Compression, IncrementalCompression):
22
+ level: int = 9
24
23
 
25
- self._compresslevel = compresslevel
24
+ def compress(self, d: bytes) -> bytes:
25
+ return bz2.compress(
26
+ d,
27
+ self.level,
28
+ )
26
29
 
27
- @lang.autostart
28
- def __call__(self) -> IncrementalCompressor:
29
- return CompressorIncrementalAdapter(
30
+ def decompress(self, d: bytes) -> bytes:
31
+ return bz2.decompress(
32
+ d,
33
+ )
34
+
35
+ def compress_incremental(self) -> BytesSteppedGenerator[None]:
36
+ return lang.nextgen(CompressorObjectIncrementalAdapter(
30
37
  functools.partial(
31
38
  bz2.BZ2Compressor, # type: ignore
32
- self._compresslevel,
39
+ self.level,
33
40
  ),
34
- )()
35
-
41
+ )())
36
42
 
37
- class IncrementalBz2Decompressor:
38
- def __call__(self) -> IncrementalDecompressor:
39
- return DecompressorIncrementalAdapter(
43
+ def decompress_incremental(self) -> BytesSteppedReaderGenerator[None]:
44
+ return DecompressorObjectIncrementalAdapter(
40
45
  bz2.BZ2Decompressor, # type: ignore
41
46
  trailing_error=OSError,
42
47
  )()
@@ -33,6 +33,7 @@
33
33
  #
34
34
  # 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this
35
35
  # License Agreement.
36
+ import dataclasses as dc
36
37
  import functools
37
38
  import os.path
38
39
  import struct
@@ -42,9 +43,11 @@ import typing as ta
42
43
  from ... import cached
43
44
  from ... import check
44
45
  from ... import lang
46
+ from ..generators import BytesSteppedGenerator
47
+ from ..generators import BytesSteppedReaderGenerator
45
48
  from ..generators.readers import PrependableBytesGeneratorReader
46
- from .types import IncrementalCompressor
47
- from .types import IncrementalDecompressor
49
+ from .base import Compression
50
+ from .base import IncrementalCompression
48
51
 
49
52
 
50
53
  if ta.TYPE_CHECKING:
@@ -63,6 +66,37 @@ COMPRESS_LEVEL_TRADEOFF = 6
63
66
  COMPRESS_LEVEL_BEST = 9
64
67
 
65
68
 
69
+ @dc.dataclass(frozen=True, kw_only=True)
70
+ class GzipCompression(Compression, IncrementalCompression):
71
+ level: int = COMPRESS_LEVEL_BEST
72
+
73
+ mtime: float | None = None
74
+
75
+ def compress(self, d: bytes) -> bytes:
76
+ return gzip.compress(
77
+ d,
78
+ self.level,
79
+ mtime=self.mtime,
80
+ )
81
+
82
+ def decompress(self, d: bytes) -> bytes:
83
+ return gzip.decompress(
84
+ d,
85
+ )
86
+
87
+ def compress_incremental(self) -> BytesSteppedGenerator[None]:
88
+ return lang.nextgen(IncrementalGzipCompressor(
89
+ level=self.level,
90
+ mtime=self.mtime,
91
+ )())
92
+
93
+ def decompress_incremental(self) -> BytesSteppedReaderGenerator[None]:
94
+ return IncrementalGzipDecompressor()()
95
+
96
+
97
+ ##
98
+
99
+
66
100
  @cached.function
67
101
  def _zero_crc() -> int:
68
102
  return zlib.crc32(b'')
@@ -75,14 +109,14 @@ class IncrementalGzipCompressor:
75
109
  def __init__(
76
110
  self,
77
111
  *,
78
- compresslevel: int = COMPRESS_LEVEL_BEST,
112
+ level: int = COMPRESS_LEVEL_BEST,
79
113
  name: str | bytes | None = None,
80
114
  mtime: float | None = None,
81
115
  ) -> None:
82
116
  super().__init__()
83
117
 
84
118
  self._name = name or ''
85
- self._compresslevel = compresslevel
119
+ self._level = level
86
120
  self._mtime = mtime
87
121
 
88
122
  def _write_gzip_header(self) -> ta.Generator[bytes, None, None]:
@@ -110,9 +144,9 @@ class IncrementalGzipCompressor:
110
144
  mtime = time.time()
111
145
  check.none((yield struct.pack('<L', int(mtime))))
112
146
 
113
- if self._compresslevel == COMPRESS_LEVEL_BEST:
147
+ if self._level == COMPRESS_LEVEL_BEST:
114
148
  xfl = b'\002'
115
- elif self._compresslevel == COMPRESS_LEVEL_FAST:
149
+ elif self._level == COMPRESS_LEVEL_FAST:
116
150
  xfl = b'\004'
117
151
  else:
118
152
  xfl = b'\000'
@@ -123,15 +157,14 @@ class IncrementalGzipCompressor:
123
157
  if fname:
124
158
  check.none((yield fname + b'\000'))
125
159
 
126
- @lang.autostart
127
- def __call__(self) -> IncrementalCompressor:
160
+ def __call__(self) -> BytesSteppedGenerator:
128
161
  crc = _zero_crc()
129
162
  size = 0
130
163
  offset = 0 # Current file offset for seek(), tell(), etc
131
164
  wrote_header = False
132
165
 
133
166
  compress = zlib.compressobj(
134
- self._compresslevel,
167
+ self._level,
135
168
  zlib.DEFLATED,
136
169
  -zlib.MAX_WBITS,
137
170
  zlib.DEF_MEM_LEVEL,
@@ -251,7 +284,7 @@ class IncrementalGzipDecompressor:
251
284
  if c:
252
285
  rdr.prepend(c)
253
286
 
254
- def __call__(self) -> IncrementalDecompressor:
287
+ def __call__(self) -> BytesSteppedReaderGenerator:
255
288
  rdr = PrependableBytesGeneratorReader()
256
289
 
257
290
  pos = 0 # Current offset in decompressed stream
@@ -0,0 +1,77 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ... import check
5
+ from ... import lang
6
+ from ..generators import BytesSteppedGenerator
7
+ from .base import Compression
8
+ from .base import IncrementalCompression
9
+
10
+
11
+ if ta.TYPE_CHECKING:
12
+ import lz4.frame as lz4_frame
13
+ else:
14
+ lz4_frame = lang.proxy_import('lz4.frame')
15
+
16
+
17
+ @dc.dataclass(frozen=True, kw_only=True)
18
+ class Lz4Compression(Compression, IncrementalCompression):
19
+ level: int = 0
20
+
21
+ block_size: int = 0
22
+ block_linked: bool = True
23
+ block_checksum: bool = False
24
+ content_checksum: bool = False
25
+ store_size: bool = True
26
+ auto_flush: bool = False
27
+
28
+ def compress(self, d: bytes) -> bytes:
29
+ return lz4_frame.compress(
30
+ d,
31
+ compression_level=self.level,
32
+ block_size=self.block_size,
33
+ content_checksum=self.content_checksum,
34
+ block_linked=self.block_linked,
35
+ store_size=self.store_size,
36
+ )
37
+
38
+ def decompress(self, d: bytes) -> bytes:
39
+ return lz4_frame.decompress(
40
+ d,
41
+ )
42
+
43
+ @lang.autostart
44
+ def compress_incremental(self) -> BytesSteppedGenerator[None]:
45
+ with lz4_frame.LZ4FrameCompressor(
46
+ compression_level=self.level,
47
+ block_size=self.block_size,
48
+ block_linked=self.block_linked,
49
+ block_checksum=self.block_checksum,
50
+ content_checksum=self.content_checksum,
51
+ auto_flush=self.auto_flush,
52
+ ) as compressor:
53
+ started = False
54
+ while True:
55
+ i = check.isinstance((yield None), bytes)
56
+ if not started:
57
+ yield compressor.begin()
58
+ started = True
59
+ if not i:
60
+ yield compressor.flush()
61
+ yield b''
62
+ return
63
+ if (o := compressor.compress(i)):
64
+ yield o
65
+
66
+ @lang.autostart
67
+ def decompress_incremental(self) -> BytesSteppedGenerator[None]:
68
+ # lz4 lib does internal buffering so this is simply a BytesSteppedGenerator not a BytesSteppedReaderGenerator as
69
+ # it only yields None, accepting any number of bytes at a time.
70
+ with lz4_frame.LZ4FrameDecompressor() as decompressor:
71
+ while True:
72
+ i = check.isinstance((yield None), bytes)
73
+ if not i:
74
+ yield b''
75
+ return
76
+ if (o := decompressor.decompress(i)):
77
+ yield o
@@ -1,10 +1,14 @@
1
+ import dataclasses as dc
2
+ import functools
1
3
  import typing as ta
2
4
 
3
5
  from ... import lang
4
- from .adapters import CompressorIncrementalAdapter
5
- from .adapters import DecompressorIncrementalAdapter
6
- from .types import IncrementalCompressor
7
- from .types import IncrementalDecompressor
6
+ from ..generators import BytesSteppedGenerator
7
+ from ..generators import BytesSteppedReaderGenerator
8
+ from .adapters import CompressorObjectIncrementalAdapter
9
+ from .adapters import DecompressorObjectIncrementalAdapter
10
+ from .base import Compression
11
+ from .base import IncrementalCompression
8
12
 
9
13
 
10
14
  if ta.TYPE_CHECKING:
@@ -13,20 +17,51 @@ else:
13
17
  lzma = lang.proxy_import('lzma')
14
18
 
15
19
 
16
- class IncrementalLzmaCompressor:
17
- def __init__(self) -> None:
18
- super().__init__()
20
+ @dc.dataclass(frozen=True, kw_only=True)
21
+ class LzmaCompression(Compression, IncrementalCompression):
22
+ format: int | None = None
19
23
 
20
- @lang.autostart
21
- def __call__(self) -> IncrementalCompressor:
22
- return CompressorIncrementalAdapter(
23
- lzma.LZMACompressor, # type: ignore
24
- )()
24
+ check: int = -1
25
+ preset: int | None = None
26
+ filters: dict | None = None
27
+
28
+ mem_limit: int | None = None
29
+
30
+ def compress(self, d: bytes) -> bytes:
31
+ return lzma.compress(
32
+ d,
33
+ format=self.format if self.format is not None else lzma.FORMAT_XZ,
34
+ check=self.check,
35
+ preset=self.preset,
36
+ filters=self.filters, # type: ignore[arg-type]
37
+ )
38
+
39
+ def decompress(self, d: bytes) -> bytes:
40
+ return lzma.decompress(
41
+ d,
42
+ format=self.format if self.format is not None else lzma.FORMAT_AUTO,
43
+ memlimit=self.mem_limit,
44
+ filters=self.filters, # type: ignore[arg-type]
45
+ )
25
46
 
47
+ def compress_incremental(self) -> BytesSteppedGenerator[None]:
48
+ return lang.nextgen(CompressorObjectIncrementalAdapter(
49
+ functools.partial( # type: ignore
50
+ lzma.LZMACompressor,
51
+ format=self.format if self.format is not None else lzma.FORMAT_XZ,
52
+ check=self.check,
53
+ preset=self.preset,
54
+ filters=self.filters, # type: ignore[arg-type]
55
+ ),
56
+ )())
26
57
 
27
- class IncrementalLzmaDecompressor:
28
- def __call__(self) -> IncrementalDecompressor:
29
- return DecompressorIncrementalAdapter(
30
- lzma.LZMADecompressor, # type: ignore
58
+ def decompress_incremental(self) -> BytesSteppedReaderGenerator[None]:
59
+ return DecompressorObjectIncrementalAdapter(
60
+ functools.partial( # type: ignore
61
+ lzma.LZMADecompressor,
62
+ format=self.format if self.format is not None else lzma.FORMAT_AUTO,
63
+ memlimit=self.mem_limit,
64
+ filters=self.filters, # type: ignore[arg-type]
65
+ ),
31
66
  trailing_error=lzma.LZMAError,
32
67
  )()
@@ -0,0 +1,20 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ... import lang
5
+ from .base import Compression
6
+
7
+
8
+ if ta.TYPE_CHECKING:
9
+ import snappy
10
+ else:
11
+ snappy = lang.proxy_import('snappy')
12
+
13
+
14
+ @dc.dataclass(frozen=True)
15
+ class SnappyCompression(Compression):
16
+ def compress(self, d: bytes) -> bytes:
17
+ return snappy.compress(d)
18
+
19
+ def decompress(self, d: bytes) -> bytes:
20
+ return snappy.decompress(d)
@@ -0,0 +1,60 @@
1
+ import dataclasses as dc
2
+ import functools
3
+ import typing as ta
4
+
5
+ from ... import lang
6
+ from ..generators import BytesSteppedGenerator
7
+ from ..generators import BytesSteppedReaderGenerator
8
+ from .adapters import CompressorObjectIncrementalAdapter
9
+ from .adapters import DecompressorObjectIncrementalAdapter
10
+ from .base import Compression
11
+ from .base import IncrementalCompression
12
+
13
+
14
+ if ta.TYPE_CHECKING:
15
+ import zlib
16
+ else:
17
+ zlib = lang.proxy_import('zlib')
18
+
19
+
20
+ @dc.dataclass(frozen=True, kw_only=True)
21
+ class ZlibCompression(Compression, IncrementalCompression):
22
+ level: int = 9
23
+
24
+ wbits: int | None = None
25
+ strategy: int | None = None
26
+ zdict: bytes | None = None
27
+
28
+ def compress(self, d: bytes) -> bytes:
29
+ return zlib.compress(
30
+ d,
31
+ self.level,
32
+ **(dict(wbits=self.wbits) if self.wbits is not None else {}),
33
+ )
34
+
35
+ def decompress(self, d: bytes) -> bytes:
36
+ return zlib.decompress(
37
+ d,
38
+ **(dict(wbits=self.wbits) if self.wbits is not None else {}),
39
+ )
40
+
41
+ def compress_incremental(self) -> BytesSteppedGenerator[None]:
42
+ return lang.nextgen(CompressorObjectIncrementalAdapter(
43
+ functools.partial(
44
+ zlib.compressobj, # type: ignore
45
+ self.level,
46
+ **(dict(wbits=self.wbits) if self.wbits is not None else {}), # type: ignore[arg-type]
47
+ **(dict(strategy=self.strategy) if self.strategy is not None else {}), # type: ignore[arg-type]
48
+ **(dict(zdict=self.zdict) if self.zdict is not None else {}), # type: ignore[arg-type]
49
+ ),
50
+ )())
51
+
52
+ def decompress_incremental(self) -> BytesSteppedReaderGenerator[None]:
53
+ return DecompressorObjectIncrementalAdapter(
54
+ functools.partial( # type: ignore
55
+ zlib.decompressobj,
56
+ **(dict(wbits=self.wbits) if self.wbits is not None else {}), # type: ignore[arg-type]
57
+ **(dict(zdict=self.zdict) if self.zdict is not None else {}), # type: ignore[arg-type]
58
+ ),
59
+ trailing_error=OSError,
60
+ )()
@@ -0,0 +1,30 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from ... import lang
5
+ from .base import Compression
6
+
7
+
8
+ if ta.TYPE_CHECKING:
9
+ import zstandard
10
+ else:
11
+ zstandard = lang.proxy_import('zstandard')
12
+
13
+
14
+ @dc.dataclass(frozen=True, kw_only=True)
15
+ class ZstdCompression(Compression):
16
+ level: int | None = None
17
+
18
+ max_output_size: int = 0
19
+
20
+ def compress(self, d: bytes) -> bytes:
21
+ return zstandard.compress(
22
+ d,
23
+ **(dict(level=self.level) if self.level is not None else {}),
24
+ )
25
+
26
+ def decompress(self, d: bytes) -> bytes:
27
+ return zstandard.decompress(
28
+ d,
29
+ max_output_size=self.max_output_size,
30
+ )