From 3cc94c10a22d7f05fd2bab9903fd2098dc29c876 Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Sat, 8 Nov 2025 17:08:11 +0100 Subject: [PATCH] Skip pyzstd with Python 3.14+ 3.14 has builtin zstd support, so use the builtin one there. --- app/exttarfile.py | 62 ++++++++++++++++++++++++---------------- app/fetch/cygwin.py | 7 +++-- pyproject.toml | 2 +- tests/test_exttarfile.py | 2 +- uv.lock | 4 +-- 5 files changed, 47 insertions(+), 30 deletions(-) diff --git a/app/exttarfile.py b/app/exttarfile.py index 9bb27dd..ca36d7f 100644 --- a/app/exttarfile.py +++ b/app/exttarfile.py @@ -1,34 +1,48 @@ import tarfile -from pyzstd import ZstdFile, ZstdError +HAS_ZSTD = True +try: + from compression import zstd +except ImportError: + HAS_ZSTD = False -class ExtTarFile(tarfile.TarFile): - """Extends TarFile to support zstandard""" +ExtTarFile: type[tarfile.TarFile] - @classmethod - def zstdopen(cls, name, mode="r", fileobj=None, **kwargs): # type: ignore - """Open zstd compressed tar archive""" +if not HAS_ZSTD: + from pyzstd import ZstdFile, ZstdError - if mode not in ("r", "w", "x", "a"): - raise ValueError("mode must be 'r', 'w' or 'x' or 'a'") + class ZstTarFile(tarfile.TarFile): + """Extends TarFile to support zstandard""" - zstfileobj = None - try: - zstfileobj = ZstdFile(fileobj or name, mode) - if "r" in mode: - zstfileobj.peek(1) # raises ZstdError if not a zstd file - except (ZstdError, EOFError) as e: - if zstfileobj is not None: + @classmethod + def zstdopen(cls, name, mode="r", fileobj=None, **kwargs): # type: ignore + """Open zstd compressed tar archive""" + + if mode not in ("r", "w", "x", "a"): + raise ValueError("mode must be 'r', 'w' or 'x' or 'a'") + + zstfileobj = None + try: + zstfileobj = ZstdFile(fileobj or name, mode) + if "r" in mode: + zstfileobj.peek(1) # raises ZstdError if not a zstd file + except (ZstdError, EOFError) as e: + if zstfileobj is not None: + zstfileobj.close() + raise tarfile.ReadError("not a zstd file") from e + + try: + t = cls.taropen(name, mode, zstfileobj, **kwargs) + except Exception: zstfileobj.close() - raise tarfile.ReadError("not a zstd file") from e + raise - try: - t = cls.taropen(name, mode, zstfileobj, **kwargs) - except Exception: - zstfileobj.close() - raise + t._extfileobj = False + return t - t._extfileobj = False - return t + OPEN_METH = {"zst": "zstdopen", **tarfile.TarFile.OPEN_METH} - OPEN_METH = {"zstd": "zstdopen", **tarfile.TarFile.OPEN_METH} + ExtTarFile = ZstTarFile +else: + assert zstd + ExtTarFile = tarfile.TarFile diff --git a/app/fetch/cygwin.py b/app/fetch/cygwin.py index 8939922..863a53f 100644 --- a/app/fetch/cygwin.py +++ b/app/fetch/cygwin.py @@ -3,7 +3,10 @@ import asyncio -import pyzstd +try: + from compression import zstd +except ImportError: + import pyzstd as zstd from ..appconfig import CYGWIN_METADATA_URL, REQUEST_TIMEOUT from ..appstate import ExtId, ExtInfo, state @@ -63,7 +66,7 @@ async def update_cygwin_versions() -> None: logger.info("update cygwin info") logger.info(f"Loading {url!r}") data = await get_content_cached(url, timeout=REQUEST_TIMEOUT) - data = pyzstd.decompress(data) + data = zstd.decompress(data) cygwin_versions, cygwin_versions_mingw64 = await asyncio.to_thread(parse_cygwin_versions, url, data) state.set_ext_infos(ExtId("cygwin", "Cygwin", True, True), cygwin_versions) state.set_ext_infos(ExtId("cygwin-mingw64", "Cygwin-mingw64", False, True), cygwin_versions_mingw64) diff --git a/pyproject.toml b/pyproject.toml index ee0766c..0df0dac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "MarkupSafe>=3.0.2,<4", "uvicorn-worker>=0.4.0,<0.5", "packageurl-python>=0.17.0,<0.18", - "pyzstd>=0.18.0,<0.19", + "pyzstd>=0.18.0,<0.19; python_version < '3.14'", "mcp>=1.10.1,<2", ] diff --git a/tests/test_exttarfile.py b/tests/test_exttarfile.py index bf3febc..12dbc7e 100644 --- a/tests/test_exttarfile.py +++ b/tests/test_exttarfile.py @@ -25,7 +25,7 @@ def test_zst() -> None: def test_zstd_write() -> None: fileobj = io.BytesIO() - with ExtTarFile.open(fileobj=fileobj, mode='w:zstd') as tar: # type: ignore + with ExtTarFile.open(fileobj=fileobj, mode='w:zst') as tar: # type: ignore data = b"Hello world!" info = tarfile.TarInfo("test.txt") info.size = len(data) diff --git a/uv.lock b/uv.lock index 828d14a..c9a07b4 100644 --- a/uv.lock +++ b/uv.lock @@ -485,7 +485,7 @@ dependencies = [ { name = "mcp" }, { name = "packageurl-python" }, { name = "pydantic" }, - { name = "pyzstd" }, + { name = "pyzstd", marker = "python_full_version < '3.14'" }, { name = "uvicorn" }, { name = "uvicorn-worker" }, ] @@ -512,7 +512,7 @@ requires-dist = [ { name = "mcp", specifier = ">=1.10.1,<2" }, { name = "packageurl-python", specifier = ">=0.17.0,<0.18" }, { name = "pydantic", specifier = ">=2.0.3,<3" }, - { name = "pyzstd", specifier = ">=0.18.0,<0.19" }, + { name = "pyzstd", marker = "python_full_version < '3.14'", specifier = ">=0.18.0,<0.19" }, { name = "uvicorn", specifier = ">=0.38.0,<0.39" }, { name = "uvicorn-worker", specifier = ">=0.4.0,<0.5" }, ]