Skip to content

Commit f278e47

Browse files
authored
PYTHON-5522: Support std lib zstandard in 3.14 (#2592)
1 parent 5f00966 commit f278e47

File tree

9 files changed

+172
-114
lines changed

9 files changed

+172
-114
lines changed

.evergreen/generated_configs/variants.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,15 @@ buildvariants:
133133
- rhel87-small
134134
expansions:
135135
COMPRESSOR: zstd
136+
- name: compression-zstd-ubuntu-22
137+
tasks:
138+
- name: .test-standard !.server-4.2 !.server-4.4 !.server-5.0 .python-3.14
139+
- name: .test-standard !.server-4.2 !.server-4.4 !.server-5.0 .python-3.14t
140+
display_name: Compression zstd Ubuntu-22
141+
run_on:
142+
- ubuntu2204-small
143+
expansions:
144+
COMPRESSOR: ztsd
136145

137146
# Coverage report tests
138147
- name: coverage-report

.evergreen/scripts/generate_config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,22 @@ def create_compression_variants():
194194
expansions=expansions,
195195
)
196196
)
197+
# Add explicit tests with compression.zstd support on linux.
198+
host = HOSTS["ubuntu22"]
199+
expansions = dict(COMPRESSOR="ztsd")
200+
tasks = [
201+
".test-standard !.server-4.2 !.server-4.4 !.server-5.0 .python-3.14",
202+
".test-standard !.server-4.2 !.server-4.4 !.server-5.0 .python-3.14t",
203+
]
204+
display_name = get_variant_name(f"Compression {compressor}", host)
205+
variants.append(
206+
create_variant(
207+
tasks,
208+
display_name,
209+
host=host,
210+
expansions=expansions,
211+
)
212+
)
197213
return variants
198214

199215

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ python -m pip install "pymongo[snappy]"
139139
```
140140

141141
Wire protocol compression with zstandard requires
142-
[zstandard](https://pypi.org/project/zstandard):
142+
[backports.zstd](https://pypi.org/project/backports.zstd)
143+
when used with Python versions before 3.14:
143144

144145
```bash
145146
python -m pip install "pymongo[zstd]"

doc/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ PyMongo 4.16 brings a number of changes including:
1616
Python 3.10+. The minimum version is ``2.6.1`` to account for `CVE-2023-29483 <https://www.cve.org/CVERecord?id=CVE-2023-29483>`_.
1717
- Removed support for Eventlet.
1818
Eventlet is actively being sunset by its maintainers and has compatibility issues with PyMongo's dnspython dependency.
19+
- Use Zstandard support from the standard library for Python 3.14+, and use ``backports.zstd`` for older versions.
1920

2021
Changes in Version 4.15.4 (2025/10/21)
2122
--------------------------------------

pymongo/asynchronous/mongo_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,8 +422,8 @@ def __init__(
422422
with the server. Currently supported options are "snappy", "zlib"
423423
and "zstd". Support for snappy requires the
424424
`python-snappy <https://pypi.org/project/python-snappy/>`_ package.
425-
zlib support requires the Python standard library zlib module. zstd
426-
requires the `zstandard <https://pypi.org/project/zstandard/>`_
425+
zlib support requires the Python standard library zlib module. For
426+
Python before 3.14 zstd requires the `backports.zstd <https://pypi.org/project/backports.zstd/>`_
427427
package. By default no compression is used. Compression support
428428
must also be enabled on the server. MongoDB 3.6+ supports snappy
429429
and zlib compression. MongoDB 4.2+ adds support for zstd.

pymongo/compression_support.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414
from __future__ import annotations
1515

16+
import sys
1617
import warnings
1718
from typing import Any, Iterable, Optional, Union
1819

@@ -44,7 +45,10 @@ def _have_zlib() -> bool:
4445

4546
def _have_zstd() -> bool:
4647
try:
47-
import zstandard # noqa: F401
48+
if sys.version_info >= (3, 14):
49+
from compression import zstd
50+
else:
51+
from backports import zstd # noqa: F401
4852

4953
return True
5054
except ImportError:
@@ -79,11 +83,18 @@ def validate_compressors(dummy: Any, value: Union[str, Iterable[str]]) -> list[s
7983
)
8084
elif compressor == "zstd" and not _have_zstd():
8185
compressors.remove(compressor)
82-
warnings.warn(
83-
"Wire protocol compression with zstandard is not available. "
84-
"You must install the zstandard module for zstandard support.",
85-
stacklevel=2,
86-
)
86+
if sys.version_info >= (3, 14):
87+
warnings.warn(
88+
"Wire protocol compression with zstandard is not available. "
89+
"The compression.zstd module is not available.",
90+
stacklevel=2,
91+
)
92+
else:
93+
warnings.warn(
94+
"Wire protocol compression with zstandard is not available. "
95+
"You must install the backports.zstd module for zstandard support.",
96+
stacklevel=2,
97+
)
8798
return compressors
8899

89100

@@ -144,12 +155,12 @@ class ZstdContext:
144155

145156
@staticmethod
146157
def compress(data: bytes) -> bytes:
147-
# ZstdCompressor is not thread safe.
148-
# TODO: Use a pool?
149-
150-
import zstandard
158+
if sys.version_info >= (3, 14):
159+
from compression import zstd
160+
else:
161+
from backports import zstd
151162

152-
return zstandard.ZstdCompressor().compress(data)
163+
return zstd.compress(data)
153164

154165

155166
def decompress(data: bytes | memoryview, compressor_id: int) -> bytes:
@@ -166,10 +177,11 @@ def decompress(data: bytes | memoryview, compressor_id: int) -> bytes:
166177

167178
return zlib.decompress(data)
168179
elif compressor_id == ZstdContext.compressor_id:
169-
# ZstdDecompressor is not thread safe.
170-
# TODO: Use a pool?
171-
import zstandard
180+
if sys.version_info >= (3, 14):
181+
from compression import zstd
182+
else:
183+
from backports import zstd
172184

173-
return zstandard.ZstdDecompressor().decompress(data)
185+
return zstd.decompress(data)
174186
else:
175187
raise ValueError("Unknown compressorId %d" % (compressor_id,))

pymongo/synchronous/mongo_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,8 +422,8 @@ def __init__(
422422
with the server. Currently supported options are "snappy", "zlib"
423423
and "zstd". Support for snappy requires the
424424
`python-snappy <https://pypi.org/project/python-snappy/>`_ package.
425-
zlib support requires the Python standard library zlib module. zstd
426-
requires the `zstandard <https://pypi.org/project/zstandard/>`_
425+
zlib support requires the Python standard library zlib module. For
426+
Python before 3.14 zstd requires the `backports.zstd <https://pypi.org/project/backports.zstd/>`_
427427
package. By default no compression is used. Compression support
428428
must also be enabled on the server. MongoDB 3.6+ supports snappy
429429
and zlib compression. MongoDB 4.2+ adds support for zstd.

requirements/zstd.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
zstandard
1+
backports.zstd>=1.0.0;python_version<'3.14'

0 commit comments

Comments
 (0)