diff --git a/README.rst b/README.rst index 6f70cc8..ffd0781 100644 --- a/README.rst +++ b/README.rst @@ -29,12 +29,19 @@ Requirements This library is compatible with Python >= 3.10 and requires: - ``numpy`` -- ``aiohttp`` +- ``aiohttp >= 3.14.0`` - ``pandas`` - ``pydantic >= 2`` -- ``requests`` +- ``requests >= 2.32.5`` +- ``urllib3[zstd] >= 2.7.0`` - ``tqdm`` +Both clients request and automatically decode Zstandard (``zstd``) responses, +while continuing to accept gzip, deflate, and uncompressed responses. Zstandard +support is required and installed automatically: Python 3.10–3.13 use +``backports.zstd`` through ``urllib3[zstd]``, and Python 3.14 and later use the standard +library's ``compression.zstd``. No extra installation option is needed. + Product history can be plotted from the raw data when ``matplotlib`` is installed. diff --git a/docs/source/index.rst b/docs/source/index.rst index 7729df5..07d6330 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,6 +16,11 @@ Install pip install keepa +Zstandard (``zstd``) response compression is enabled for both clients, including +graph-image downloads. Codec support is installed automatically on Python +3.10–3.13 and provided by the standard library on Python 3.14. The clients also +accept gzip, deflate, and uncompressed responses. + Query Product Data ================== Start with a product query. Dictionary responses remain the default for diff --git a/pyproject.toml b/pyproject.toml index 164ef0b..613e00f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,9 +19,10 @@ classifiers = [ ] dependencies = [ "numpy >=1.9.3", - "requests >=2.2", + "requests >=2.32.5", + "urllib3[zstd] >=2.7.0", "tqdm", - "aiohttp", + "aiohttp >=3.14.0", "pandas <= 3.0", "pydantic >=2" ] diff --git a/src/keepa/keepa_async.py b/src/keepa/keepa_async.py index b031cd3..19b0125 100644 --- a/src/keepa/keepa_async.py +++ b/src/keepa/keepa_async.py @@ -555,19 +555,22 @@ async def download_graph_image( async with session.get( "https://api.keepa.com/graphimage", params=payload, + headers={"Accept-Encoding": "zstd, gzip, deflate"}, timeout=self._timeout, ) as resp: - first_chunk = True + # A decompressed HTTP chunk may contain only part of the PNG signature. + try: + signature = await resp.content.readexactly(8) + except asyncio.IncompleteReadError as exc: + signature = exc.partial + if signature != b"\x89PNG\r\n\x1a\n": + raise ValueError( + "Response from api.keepa.com/graphimage is not a valid PNG image" + ) filename = Path(filename) with open(filename, "wb") as f: + f.write(signature) async for chunk in resp.content.iter_chunked(8192): - if first_chunk: - if not chunk.startswith(b"\x89PNG\r\n\x1a\n"): - raise ValueError( - "Response from api.keepa.com/graphimage is not a valid " - "PNG image" - ) - first_chunk = False f.write(chunk) async def _request( @@ -584,6 +587,7 @@ async def _request( async with session.get( f"https://api.keepa.com/{request_type}/?", params=payload, + headers={"Accept-Encoding": "zstd, gzip, deflate"}, timeout=self._timeout, ) as raw: status_code = str(raw.status) diff --git a/src/keepa/keepa_sync.py b/src/keepa/keepa_sync.py index 1aab0ac..d2de793 100644 --- a/src/keepa/keepa_sync.py +++ b/src/keepa/keepa_sync.py @@ -1549,6 +1549,7 @@ def _request( raw = requests.get( f"https://api.keepa.com/{request_type}/?", payload, + headers={"Accept-Encoding": "zstd, gzip, deflate"}, timeout=self._timeout, ) status_code = str(raw.status_code) diff --git a/tests/test_async_interface.py b/tests/test_async_interface.py index 15258d5..5ab3627 100644 --- a/tests/test_async_interface.py +++ b/tests/test_async_interface.py @@ -108,6 +108,15 @@ async def test_wait_for_tokens(api: keepa.AsyncKeepa) -> None: assert api.status.tokensLeft +@pytest.mark.asyncio +async def test_live_zstd_response(api: keepa.AsyncKeepa) -> None: + """Verify negotiation and decoding against Keepa's free token endpoint.""" + response = await api._request("token", {"key": api.accesskey}, wait=False, raw_response=True) + assert "zstd" in response.request_info.headers["Accept-Encoding"] + assert response.headers["Content-Encoding"] == "zstd" + assert (await response.json())["tokensLeft"] == api.tokens_left + + @pytest.mark.asyncio async def test_deals(api: keepa.AsyncKeepa) -> None: deal_parms = { diff --git a/tests/test_compression.py b/tests/test_compression.py new file mode 100644 index 0000000..13c4f28 --- /dev/null +++ b/tests/test_compression.py @@ -0,0 +1,283 @@ +"""Exercise compression through real HTTP sockets and the clients' decoders.""" + +import base64 +import gzip +import json +import sys +import threading +import zlib +from dataclasses import dataclass, field +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from urllib.parse import urlsplit + +import aiohttp +import pytest +import requests + +import keepa + +if sys.version_info >= (3, 14): + from compression import zstd +else: + from backports import zstd + + +PRODUCT = {"asin": "B000000000", "domainId": 1, "csv": None, "title": "Café ☕"} +PAYLOAD = { + "products": [PRODUCT], + "tokensLeft": 42, + "refillIn": 0, + "refillRate": 5, + "timestamp": 0, +} +PNG = base64.b64decode( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aX1kAAAAASUVORK5CYII=" +) + + +@dataclass +class LocalAPI: + body: bytes = json.dumps(PAYLOAD, ensure_ascii=False).encode() + encoding: str = "zstd" + status: int = 200 + chunked: bool = False + content_type: str = "application/json; charset=utf-8" + encoded_body: bytes | None = None + headers: list[dict[str, str]] = field(default_factory=list) + statuses: list[int] = field(default_factory=list) + + def encode(self) -> bytes: + if self.encoded_body is not None: + return self.encoded_body + if self.encoding == "zstd": + return zstd.compress(self.body) + if self.encoding == "gzip": + return gzip.compress(self.body) + if self.encoding == "deflate": + return zlib.compress(self.body) + return self.body + + +@pytest.fixture +def local_api(monkeypatch): + """Redirect only the destination; HTTP and decompression remain real.""" + api = LocalAPI() + + class Handler(BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + + def do_GET(self): + api.headers.append(dict(self.headers)) + body = api.encode() + self.send_response(api.statuses.pop(0) if api.statuses else api.status) + self.send_header("Content-Type", api.content_type) + self.send_header("Connection", "close") + if api.encoding: + self.send_header("Content-Encoding", api.encoding) + if api.chunked: + self.send_header("Transfer-Encoding", "chunked") + else: + self.send_header("Content-Length", str(len(body))) + self.end_headers() + if api.chunked: + # Split frame headers and compressed blocks across HTTP chunks. + for start in range(0, len(body), 7): + chunk = body[start : start + 7] + self.wfile.write(f"{len(chunk):x}\r\n".encode() + chunk + b"\r\n") + self.wfile.write(b"0\r\n\r\n") + else: + self.wfile.write(body) + + def log_message(self, *args): + pass + + server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) + base_url = f"http://127.0.0.1:{server.server_port}" + thread = threading.Thread(target=server.serve_forever, kwargs={"poll_interval": 0.01}) + thread.start() + real_sync_get = requests.get + real_async_get = aiohttp.ClientSession.get + + def sync_get(url, *args, **kwargs): + assert url.startswith("https://api.keepa.com/") + return real_sync_get(base_url + urlsplit(url).path, *args, **kwargs) + + def async_get(session, url, *args, **kwargs): + assert url.startswith("https://api.keepa.com/") + return real_async_get(session, base_url + urlsplit(url).path, *args, **kwargs) + + monkeypatch.setattr(requests, "get", sync_get) + monkeypatch.setattr(aiohttp.ClientSession, "get", async_get) + try: + yield api + assert api.headers + for headers in api.headers: + encodings = {value.strip() for value in headers["Accept-Encoding"].split(",")} + assert {"zstd", "gzip", "deflate"} <= encodings + finally: + server.shutdown() + thread.join(timeout=5) + server.server_close() + assert not thread.is_alive() + + +@pytest.mark.parametrize("encoding", ["zstd", "gzip", "deflate", ""]) +@pytest.mark.parametrize("chunked", [False, True]) +def test_sync_compressed_json(local_api, encoding, chunked): + local_api.encoding = encoding + local_api.chunked = chunked + api = keepa.Keepa("x" * 64) + assert api.query(PRODUCT["asin"], history=False, progress_bar=False) == [PRODUCT] + assert api.tokens_left == 42 + assert api.status.refillRate == 5 + + +@pytest.mark.asyncio +@pytest.mark.parametrize("encoding", ["zstd", "gzip", "deflate", ""]) +@pytest.mark.parametrize("chunked", [False, True]) +async def test_async_compressed_json(local_api, encoding, chunked): + local_api.encoding = encoding + local_api.chunked = chunked + api = await keepa.AsyncKeepa.create("x" * 64) + response = await api.query(PRODUCT["asin"], history=False, progress_bar=False) + assert response == [PRODUCT] + assert api.tokens_left == 42 + assert api.status.refillRate == 5 + + +@pytest.mark.parametrize("frame_mode", ["unknown_size", "concatenated"]) +def test_sync_zstd_frames(local_api, frame_mode): + _configure_frames(local_api, frame_mode) + api = keepa.Keepa("x" * 64) + assert api.query(PRODUCT["asin"], history=False, progress_bar=False) == [PRODUCT] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("frame_mode", ["unknown_size", "concatenated"]) +async def test_async_zstd_frames(local_api, frame_mode): + _configure_frames(local_api, frame_mode) + api = await keepa.AsyncKeepa.create("x" * 64) + response = await api.query(PRODUCT["asin"], history=False, progress_bar=False) + assert response == [PRODUCT] + + +def _configure_frames(local_api, frame_mode): + local_api.chunked = True + if frame_mode == "unknown_size": + compressor = zstd.ZstdCompressor() + local_api.encoded_body = compressor.compress(local_api.body) + compressor.flush() + else: + midpoint = len(local_api.body) // 2 + local_api.encoded_body = zstd.compress(local_api.body[:midpoint]) + zstd.compress( + local_api.body[midpoint:] + ) + + +def test_sync_zstd_raw_response(local_api): + api = keepa.Keepa("x" * 64) + response = api.query(PRODUCT["asin"], history=False, progress_bar=False, raw=True)[0] + assert isinstance(response, requests.Response) + assert response.headers["Content-Encoding"] == "zstd" + assert response.content == local_api.body + assert response.json() == PAYLOAD + + +@pytest.mark.parametrize("chunked", [False, True]) +def test_sync_zstd_graph_image(local_api, tmp_path, chunked): + local_api.body = PNG + local_api.content_type = "image/png" + local_api.chunked = chunked + filename = tmp_path / "graph.png" + api = keepa.Keepa("x" * 64) + api.download_graph_image(PRODUCT["asin"], filename) + assert filename.read_bytes() == PNG + + +@pytest.mark.asyncio +@pytest.mark.parametrize("chunked", [False, True]) +async def test_async_zstd_graph_image(local_api, tmp_path, chunked): + local_api.body = PNG + local_api.content_type = "image/png" + local_api.chunked = chunked + filename = tmp_path / "graph.png" + api = await keepa.AsyncKeepa.create("x" * 64) + await api.download_graph_image(PRODUCT["asin"], filename) + assert filename.read_bytes() == PNG + + +@pytest.mark.asyncio +@pytest.mark.parametrize("body", [b"", b"\x89PNG", b"not png!"]) +async def test_async_zstd_invalid_graph_image(local_api, tmp_path, body): + local_api.body = body + local_api.content_type = "image/png" + local_api.chunked = True + filename = tmp_path / "graph.png" + api = await keepa.AsyncKeepa.create("x" * 64) + with pytest.raises(ValueError, match="not a valid PNG image"): + await api.download_graph_image(PRODUCT["asin"], filename) + assert not filename.exists() + + +@pytest.mark.parametrize("status,message", [(400, "REQUEST_REJECTED"), (429, "NOT_ENOUGH_TOKEN")]) +def test_sync_zstd_api_error(local_api, status, message): + local_api.status = status + api = keepa.Keepa("x" * 64) + with pytest.raises(RuntimeError, match=message): + api.query(PRODUCT["asin"], history=False, progress_bar=False, wait=False) + assert api.tokens_left == 42 + + +@pytest.mark.asyncio +@pytest.mark.parametrize("status,message", [(400, "REQUEST_REJECTED"), (429, "NOT_ENOUGH_TOKEN")]) +async def test_async_zstd_api_error(local_api, status, message): + local_api.status = status + api = await keepa.AsyncKeepa.create("x" * 64) + with pytest.raises(RuntimeError, match=message): + await api.query(PRODUCT["asin"], history=False, progress_bar=False, wait=False) + assert api.tokens_left == 42 + + +def test_sync_invalid_zstd(local_api): + local_api.encoded_body = b"not a zstd frame" + api = keepa.Keepa("x" * 64) + with pytest.raises(requests.exceptions.ContentDecodingError): + api.query(PRODUCT["asin"], history=False, progress_bar=False) + + +@pytest.mark.asyncio +async def test_async_invalid_zstd(local_api): + local_api.encoded_body = b"not a zstd frame" + api = await keepa.AsyncKeepa.create("x" * 64) + with pytest.raises(RuntimeError, match="Invalid JSON from Keepa API"): + await api.query(PRODUCT["asin"], history=False, progress_bar=False) + + +def test_sync_zstd_invalid_json(local_api): + local_api.body = b"not JSON" + api = keepa.Keepa("x" * 64) + with pytest.raises(RuntimeError, match="Invalid JSON from Keepa API"): + api.query(PRODUCT["asin"], history=False, progress_bar=False) + + +@pytest.mark.asyncio +async def test_async_zstd_invalid_json(local_api): + local_api.body = b"not JSON" + api = await keepa.AsyncKeepa.create("x" * 64) + with pytest.raises(RuntimeError, match="Invalid JSON from Keepa API"): + await api.query(PRODUCT["asin"], history=False, progress_bar=False) + + +def test_sync_zstd_token_retry(local_api): + local_api.statuses = [200, 429, 200] + api = keepa.Keepa("x" * 64) + assert api.query(PRODUCT["asin"], history=False, progress_bar=False) == [PRODUCT] + assert len(local_api.headers) == 3 + + +@pytest.mark.asyncio +async def test_async_zstd_token_retry(local_api): + local_api.statuses = [429, 200] + api = await keepa.AsyncKeepa.create("x" * 64) + response = await api.query(PRODUCT["asin"], history=False, progress_bar=False) + assert response == [PRODUCT] + assert len(local_api.headers) == 2 diff --git a/tests/test_interface.py b/tests/test_interface.py index bf7c331..4daea5c 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -127,6 +127,14 @@ def test_update_status(api: keepa.Keepa) -> None: assert api.status.tokensLeft +def test_live_zstd_response(api: keepa.Keepa) -> None: + """Verify negotiation and decoding against Keepa's free token endpoint.""" + response = api._request("token", {"key": api.accesskey}, wait=False, raw_response=True) + assert "zstd" in response.request.headers["Accept-Encoding"] + assert response.headers["Content-Encoding"] == "zstd" + assert response.json()["tokensLeft"] == api.tokens_left + + def test_wait_for_tokens(api: keepa.Keepa) -> None: assert api.status.tokensLeft is None api.wait_for_tokens() diff --git a/uv.lock b/uv.lock index 41c9b6b..d31b14e 100644 --- a/uv.lock +++ b/uv.lock @@ -247,6 +247,99 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z"} ] +[[package]] +name = "backports-zstd" +sdist = {url = "https://files.pythonhosted.org/packages/75/f0/9ba1b05811aa5f5434f69768253129460a5744e1814f359efba39a01ce20/backports_zstd-1.7.0.tar.gz", hash = "sha256:1a967189c1822b6e85a2e550fdfc88a3272c17633ea0a4732dac5911a8034f2b", size = 1003722, upload-time = "2026-08-15T17:26:43.96Z"} +source = {registry = "https://pypi.org/simple"} +version = "1.7.0" +wheels = [ + {url = "https://files.pythonhosted.org/packages/bd/16/d38a2d79e9d5c4ed3654787ac97a57ba20f2c24b750c2cb2be6f43b009a3/backports_zstd-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c4f557bd8579d38316344c205b2a540e84b1014fb3721205eb6c3eb5322e9d9", size = 438784, upload-time = "2026-08-15T17:24:55.15Z"}, + {url = "https://files.pythonhosted.org/packages/c5/31/d6eeaab06dc7b58cc6d44873c7a4995e42239e081ae968c91378cda3434f/backports_zstd-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:68ee21f0efa3f06d3d3cbb5f291c177497fc550ebef732b0a38599de8db1ee32", size = 367596, upload-time = "2026-08-15T17:24:56.529Z"}, + {url = "https://files.pythonhosted.org/packages/20/87/430e3aecc290031c374c05e1f096be4988805a68192ce0294ce6394772cf/backports_zstd-1.7.0-cp310-cp310-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:beb8d6cf5ab3c27cca3a5fdcfeeb228885083d606f0709ffc0a698aabc4f13ee", size = 507936, upload-time = "2026-08-15T17:24:57.844Z"}, + {url = "https://files.pythonhosted.org/packages/ab/63/a8629f1b58f8805ff412f9dc780f01412ffd98e422d0c38d8d2a400e0b76/backports_zstd-1.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f1ecac082932870df519818e88eb938a03573245f629e34979141583112123", size = 477864, upload-time = "2026-08-15T17:24:59.074Z"}, + {url = "https://files.pythonhosted.org/packages/92/77/faa166b8079091f0b5cc4e44fdf5a2fa02be941c3a70c45906f2578e2700/backports_zstd-1.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0469fbe83c85f5a1fb83657242477ed612d4d4d3c000b67f8a67bc839115b09", size = 583230, upload-time = "2026-08-15T17:25:00.402Z"}, + {url = "https://files.pythonhosted.org/packages/1a/df/fa177293c9e81239f4f691e31eacc8573521e541532c1907c311701eafdd/backports_zstd-1.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f53a23a40d25236aab6e0e817f2cbbf27e6f8fe976fedaa6b9ee53fc809b9", size = 642939, upload-time = "2026-08-15T17:25:01.615Z"}, + {url = "https://files.pythonhosted.org/packages/c0/83/ddf5e358efa80d951278e73892c22d57a710e0b0be7f98257579babe8c61/backports_zstd-1.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0b7929cbaff68c124d2366d803dc654347f37637a3df73a2a0a8f2dbee4819cc", size = 493153, upload-time = "2026-08-15T17:25:02.796Z"}, + {url = "https://files.pythonhosted.org/packages/37/71/eb4a44a4c8cc835d3ea1d2bcc50c704c059e22f8afc29cd30fad3051df52/backports_zstd-1.7.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4108fa7b126fb4b08853670bb32c4a812aab355b8264aa1a27b7bb724ae6ce0", size = 567045, upload-time = "2026-08-15T17:25:03.919Z"}, + {url = "https://files.pythonhosted.org/packages/13/87/1c1fb060fabeca988c6f020d097750be7586ad8572358914b994b6abfe18/backports_zstd-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1a868cff3de171b4961acd9fcf9e843cc966783aa0b2bdfdba876ec20023e24f", size = 483870, upload-time = "2026-08-15T17:25:05.079Z"}, + {url = "https://files.pythonhosted.org/packages/b8/40/aebec21e0527ab149bdfb39980951bad26272f5ffc8a5e3fa197465b3eca/backports_zstd-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e833fb85673c0a8c880dc3f759c87726680f953492e9275f666fcbfd127c6e8e", size = 511452, upload-time = "2026-08-15T17:25:06.351Z"}, + {url = "https://files.pythonhosted.org/packages/06/74/de754b415121a65bd2622b7a4cf6ed82c29ef0c8c3b562f3d30ed790a1d1/backports_zstd-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3c32951fe1ae22f6f059d3c02cfdc21155cee83be456c424d955bf493ba2a9dc", size = 587585, upload-time = "2026-08-15T17:25:07.556Z"}, + {url = "https://files.pythonhosted.org/packages/c1/f7/6ddf25c04fcf0ef980dd3acc22299e381a0107280bd8630ed6b81d8532aa/backports_zstd-1.7.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d312ff5018199e1f889ca470a98361feaf2d194f82091cbbd366bb539e7c3583", size = 564889, upload-time = "2026-08-15T17:25:08.813Z"}, + {url = "https://files.pythonhosted.org/packages/94/fb/137f60f71f6275560dbbfb962e81992e8e585cfba99c15da5a1971790f6b/backports_zstd-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a43e03d7769a06b5ccf4cad5fcf4b3e690b1b36476632d3e1bc923e12579963f", size = 633499, upload-time = "2026-08-15T17:25:10.128Z"}, + {url = "https://files.pythonhosted.org/packages/96/9d/0db357287b73ee2ba972e54d5de583793f7bfd76d4230c918a6fa297b477/backports_zstd-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff08fbdf4090c8075bcc0f3ffccf3098e4fd6a0d9a4c5c2078398ea5bb2ddd1f", size = 497143, upload-time = "2026-08-15T17:25:11.383Z"}, + {url = "https://files.pythonhosted.org/packages/96/ab/c3c9e61071cf4ce2945ab2a83c980ff4dc918b179e81c5255590310fee6e/backports_zstd-1.7.0-cp310-cp310-win32.whl", hash = "sha256:e9e7bf426a21772b3ac1fe5c967678063d7bfcb58d2f559b98bf4c9c6c52f95f", size = 292072, upload-time = "2026-08-15T17:25:12.541Z"}, + {url = "https://files.pythonhosted.org/packages/28/4d/dd6414c1408c5fe76204d545fecc433ec023501ee71507c0ac228707ac36/backports_zstd-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:02c4458f25f884131c59d54549a3bdfd649ca3384f1dd15204762171d9e24739", size = 329679, upload-time = "2026-08-15T17:25:13.754Z"}, + {url = "https://files.pythonhosted.org/packages/e0/c3/2a5eefbc4466c0eb9442ab62fe5956889b823853ad088e96ac791ff943ae/backports_zstd-1.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:db2fb308ca3669e2913e66aae9173d9a9d5c448caaa2f1bdd12efbfe480f0fde", size = 292163, upload-time = "2026-08-15T17:25:15.026Z"}, + {url = "https://files.pythonhosted.org/packages/04/14/416e2e75d434bf2b8433ba54f10e5ec01a63bb1dfc7c6a82151faa735b50/backports_zstd-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:165a8898c5514b69533edf4ab1f4f4b4bacc62a137a76f36889b473150ec28a5", size = 438790, upload-time = "2026-08-15T17:25:16.171Z"}, + {url = "https://files.pythonhosted.org/packages/08/0d/97e70a1d47d660c3854dfbbbd8a8ea9a98a0993976d9b0e0da07525dcff1/backports_zstd-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:700ebb797956767679dbca38e45eaa5c21630e460e31ef53bb4b849125bb5d87", size = 367595, upload-time = "2026-08-15T17:25:17.382Z"}, + {url = "https://files.pythonhosted.org/packages/5c/8f/9b09bc4d29c2b697e9557a54e1e52b264a1ca3babd36542e7be6a0609cf6/backports_zstd-1.7.0-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:47f14a24428a2bc070e26c402f8d6d25676345c32fa116b16b60167a2925df2a", size = 507936, upload-time = "2026-08-15T17:25:18.695Z"}, + {url = "https://files.pythonhosted.org/packages/89/e6/7eb513bb06fb2733e71cf358f227969996b74883de86458935c09f08d1c4/backports_zstd-1.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c358e72e5ff8f23e9f3ec778be4d67ddc5ced3e6d8f03521db29d7357a773fc3", size = 477864, upload-time = "2026-08-15T17:25:19.833Z"}, + {url = "https://files.pythonhosted.org/packages/3f/68/fe0e57f2c8e04560eecc106bba18ad62d0576001722e8c5b619bb4517991/backports_zstd-1.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6c8c183027eae38f5b0643d153f7f91e569d22ee8db25639aea0745677a38ed8", size = 583229, upload-time = "2026-08-15T17:25:21.144Z"}, + {url = "https://files.pythonhosted.org/packages/e3/d3/5944fbdfc8c03b8ef72c73c36652a32bb251b1b8ccefab07a8a8fbf202cb/backports_zstd-1.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d8493f71d9c5c05d18554afc6bb9a319a6674478e8f3042c7e22900c3a06f4d", size = 642898, upload-time = "2026-08-15T17:25:22.379Z"}, + {url = "https://files.pythonhosted.org/packages/96/f2/661bd15e062ceb1a20a78e40598fee599a31472e60a7961dcd75b467c94f/backports_zstd-1.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b2e505d8923e1e9224cf249b99c92cf728e9eb91fbd1e07a9c2816013621fad3", size = 493145, upload-time = "2026-08-15T17:25:23.833Z"}, + {url = "https://files.pythonhosted.org/packages/00/6b/454369a552a3d114b293706441dc43412639a30665b9551959f0773e9b62/backports_zstd-1.7.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d1bdc293267200e31baf35aa142c6d0ac3e8cce650f79c84e6a32980dfbfd5c", size = 567043, upload-time = "2026-08-15T17:25:25.104Z"}, + {url = "https://files.pythonhosted.org/packages/bc/f2/e7af20299deb43f52aaf24f74e60b994201aea6d22c8a2adaaa13dc4b109/backports_zstd-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d85c18170e8cdba339edc67a5021cf79ccc858f5fda6aeae71f9015c5e463f6", size = 483881, upload-time = "2026-08-15T17:25:26.634Z"}, + {url = "https://files.pythonhosted.org/packages/a9/c4/0882cfac8714345cfcc5ba139e16c7b64aee9f2fec3ebff9de77131f1d14/backports_zstd-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:96a6f8d3f4cefb6b11ebfc30fc0d970430ecfb169a6555990734a2a46977ec4b", size = 511455, upload-time = "2026-08-15T17:25:27.808Z"}, + {url = "https://files.pythonhosted.org/packages/be/85/01cfbb2f07475ac1091ea93fbd04762c95ee6d82c937e2508072e12a9eb6/backports_zstd-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c2c01cb823ed1b2422905a9791759bdc986e44e7a12b4661e9d712d5c8946016", size = 587585, upload-time = "2026-08-15T17:25:29.012Z"}, + {url = "https://files.pythonhosted.org/packages/61/2f/f378daf513ca0feb5740aa4b1291c5133e5095830a7052da6088974f477b/backports_zstd-1.7.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:86785aef2b4663a97c932d829ddc9565354cc628e2ae61764d9d93c8b186d65f", size = 564894, upload-time = "2026-08-15T17:25:30.28Z"}, + {url = "https://files.pythonhosted.org/packages/34/ef/cb031d27b06863aa666d49dada3c1010151306b98861b8b826ae722af1a6/backports_zstd-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:633ceee3ba86f696fc4e992f7bce558c132c26d04d64d0bb8c2f5d487d5b3aee", size = 633546, upload-time = "2026-08-15T17:25:31.652Z"}, + {url = "https://files.pythonhosted.org/packages/f0/44/b5f1480f6c250ed72f22e8682d6532f992aef0e2033b21b8d8bff96be034/backports_zstd-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a80bc6a8c9aeaad76cc3ecd58067ec038a764807186b0df970c760df39b89c7", size = 497152, upload-time = "2026-08-15T17:25:32.957Z"}, + {url = "https://files.pythonhosted.org/packages/db/30/13f0447faef940a763dfddf6ba2d4941bb45a350bba8c9ba56a22e933dfd/backports_zstd-1.7.0-cp311-cp311-win32.whl", hash = "sha256:1713271e2faea852a1682a6143c19c3506cd2e1b71f60a8924c59a9d2554d2b2", size = 292182, upload-time = "2026-08-15T17:25:34.262Z"}, + {url = "https://files.pythonhosted.org/packages/99/04/8f67d5436f7ef4b1d286b8b186fb4a3e371416921110f8dc0f6c4d9e497d/backports_zstd-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:ae840be71108f6020567dd389c973e70a4374a6c0b03c02d3242c8a98a9b3cdb", size = 329717, upload-time = "2026-08-15T17:25:35.517Z"}, + {url = "https://files.pythonhosted.org/packages/c2/eb/e446bb3d4520e618571a929ceb7776124d6f8491ea17d3355a8867deb031/backports_zstd-1.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:8827a5601c749a986faa163f3b59d59eedc5947812be114f7132c3d4ad153fee", size = 292294, upload-time = "2026-08-15T17:25:36.797Z"}, + {url = "https://files.pythonhosted.org/packages/df/23/240495dec973dcfb34816248956ca8d05b32fb75936c226c1cf497b83b83/backports_zstd-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5548a857bb0fcc5449cc3687353547396c6b1ecd4bd882f1cd34fa8d29e70ca", size = 439047, upload-time = "2026-08-15T17:25:38.084Z"}, + {url = "https://files.pythonhosted.org/packages/6d/24/5556959c7d03bfee5ff14d7f07dd9bf8de737c69f81d823a32784ab39c34/backports_zstd-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bab192b934fdf5a03df4752556d9c8af2d058163fdfbafd4a253cdfe25449a6f", size = 367666, upload-time = "2026-08-15T17:25:39.233Z"}, + {url = "https://files.pythonhosted.org/packages/b2/cb/557db98001c4a7202beed19e8bd42603a2315b80fd5def7e21a0b048ec3b/backports_zstd-1.7.0-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:8344260bed9842c415a93d9bfe23ea834e5f27758827d56933d8c0d06db507a2", size = 508475, upload-time = "2026-08-15T17:25:40.367Z"}, + {url = "https://files.pythonhosted.org/packages/40/4b/820acbc2c1d1d945aedca0c0d22546a948630ffb186df523098fbd669a95/backports_zstd-1.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c55e55e1e9dee312bc5e186386e6aa5207482a6d2242bd7c14709ded254f87f", size = 478240, upload-time = "2026-08-15T17:25:41.806Z"}, + {url = "https://files.pythonhosted.org/packages/f9/76/77fa9b385e79d4c106ce15d66681978f39a844b0eb5db02682687246b716/backports_zstd-1.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cf609af3735c7e697ccd13f6b0c88da57c201b6ea63c6afbfe81d6f9b50e298c", size = 583611, upload-time = "2026-08-15T17:25:43.104Z"}, + {url = "https://files.pythonhosted.org/packages/95/a4/fbb7c73336f3279dad36da94382a59755100b656301ea836ebaa42736581/backports_zstd-1.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:676a37971f676830d4f90cee8fdf4e438781596fb2f2d1984ac76c9b3eb39a69", size = 642496, upload-time = "2026-08-15T17:25:44.322Z"}, + {url = "https://files.pythonhosted.org/packages/1e/40/121917bd2671bc3f1507c25503c0554f0b52483edcca4e6210e6d22228df/backports_zstd-1.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:470895d0bcddc850766e593d1b26764fb138c2feed149f515a2627ef9587d54c", size = 496195, upload-time = "2026-08-15T17:25:45.503Z"}, + {url = "https://files.pythonhosted.org/packages/de/c7/c6379a0d734bea1c7f14d07c23258108cc92b994654e25cfe3a3e88cd785/backports_zstd-1.7.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:02f2f6649a342d0901ddb35596ddadb7c3bb1cf6bb54d691e5e0285f1fa0674f", size = 570964, upload-time = "2026-08-15T17:25:46.648Z"}, + {url = "https://files.pythonhosted.org/packages/7a/a4/372c3dd3017c3f93cda0acbc282f8073b70efdc1b56d1fdeebe023660725/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:132ba81fad59d44958b7d10da31545e7128c469cfbc2e268d0eaab96daa64175", size = 484276, upload-time = "2026-08-15T17:25:48.129Z"}, + {url = "https://files.pythonhosted.org/packages/f5/50/83fa7bdd5e1d808203b9143848fdf7e15de399b8119a0d4378b2aea9be78/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a3e1c6ce0b232ee6703ed24ee126e8186107f5a4e56edbd21cd1aa5a8c6bfd12", size = 511963, upload-time = "2026-08-15T17:25:49.666Z"}, + {url = "https://files.pythonhosted.org/packages/56/d2/d4ed32c353148acc18f3b665ab24a677b9c49d3640244424c5d6046400c5/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d7a7cb964eb8d1bb5d039970b16fe54802ea47dc935ae96d9874844a126bf8ff", size = 588025, upload-time = "2026-08-15T17:25:51.288Z"}, + {url = "https://files.pythonhosted.org/packages/ae/5a/df8b5b848e8dfdec6edca55f22067ffbafa081d81aec1313e28155c3fea3/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:12a9842a2ec2854cbec7f252ab29d44c2b772788a9bbafded743ca4bf73b115f", size = 568223, upload-time = "2026-08-15T17:25:52.633Z"}, + {url = "https://files.pythonhosted.org/packages/43/8c/f970f15e7fdbf8a251f121c91364fa68bbc2dfab4d5eca058427dec63397/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:138154eea8ced84394991bf0e819dba6b690306a178dd528c28eee724b7d4aec", size = 632937, upload-time = "2026-08-15T17:25:54.511Z"}, + {url = "https://files.pythonhosted.org/packages/6a/d0/e36c18c87a74421954502d123ff7027e0a63a7624dffa99ec0f7474deff9/backports_zstd-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:468b636ed365627b364c94be1c35a52858e13b5bc1fa3f068bbc71b1af65f3d7", size = 500735, upload-time = "2026-08-15T17:25:56.064Z"}, + {url = "https://files.pythonhosted.org/packages/1c/57/fc72280334d2aa94238c5882052263bd7796c1fa924044353c30d058e0c3/backports_zstd-1.7.0-cp312-cp312-win32.whl", hash = "sha256:f026fe2e89b7ff01ba6ebec6abaff34c6063919151a32afb68714cf139e17c50", size = 292394, upload-time = "2026-08-15T17:25:57.469Z"}, + {url = "https://files.pythonhosted.org/packages/71/89/6cea747bdeef34cd12482b17e604b832fdb0962987132b99496f1a6c3f82/backports_zstd-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:2ea62ba2f1a6e6c9e6dc108921f9ae881969ca72e073162fa488d0de3eb2713f", size = 329876, upload-time = "2026-08-15T17:25:58.798Z"}, + {url = "https://files.pythonhosted.org/packages/64/28/b4a17c07d5a50a45cb04592960d1593cdf3b3728968371f332aa3643b804/backports_zstd-1.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:cefb983345c55ccaa20423a4eb96434730e6d640ffa2db9b60e5bedb0fbef94e", size = 292461, upload-time = "2026-08-15T17:25:59.928Z"}, + {url = "https://files.pythonhosted.org/packages/6f/24/32b3358ae3a4df0ebad85ebbce721818c6d76a836119bee76089d103e951/backports_zstd-1.7.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:a3fbcbf819bee2b06b8666b13742098d0f40663ee34e64a12bc360ec0f5e3d89", size = 400913, upload-time = "2026-08-15T17:26:01.089Z"}, + {url = "https://files.pythonhosted.org/packages/af/f3/39ef7dd75eb1e699e25a19212737a73d3c030a0c9fd1d0ed1572b5f8e493/backports_zstd-1.7.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:efee02f18e04c2e9e6d694c5cf9b7457c4bda3ea96f48b1ee69769e06bb9d89f", size = 454915, upload-time = "2026-08-15T17:26:02.294Z"}, + {url = "https://files.pythonhosted.org/packages/76/e8/8209081e094aa98b2f28bac388619c85b1a44aed813d6b3c54d1da79d19a/backports_zstd-1.7.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:ecc95fa0e91d92951d74468e7789afdf91d9e702f40af2d0fcbf0ded4d0f650a", size = 357992, upload-time = "2026-08-15T17:26:03.552Z"}, + {url = "https://files.pythonhosted.org/packages/b3/65/64025302bae4ba924d613e404c6120bf194b5636786960ece274622a4a3e/backports_zstd-1.7.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:34154d82fc0246738159084d146401073f9ac9cfd755b66bb8853ca06037810c", size = 366686, upload-time = "2026-08-15T17:26:04.812Z"}, + {url = "https://files.pythonhosted.org/packages/4a/b9/c4d24d113d28b774662152c462d38d28109741d6d45c1aea7834371741dc/backports_zstd-1.7.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44b687b1c0be5cb279693d2682f91ff84c559d679b2ef2fbe501fe4b2db2c4bb", size = 447221, upload-time = "2026-08-15T17:26:05.979Z"}, + {url = "https://files.pythonhosted.org/packages/cb/9f/8db55c7f77aec60879844a879ac026065d8f03aab74080701acc060c4168/backports_zstd-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dcdbd368659f46b570114eeea36b75347716523870d71f6bc5d7801862aefd6e", size = 438571, upload-time = "2026-08-15T17:26:07.421Z"}, + {url = "https://files.pythonhosted.org/packages/cd/f8/72930ae4bb7bf6b9d6c7c31bce7b3e5751c062269a4ee718066e25f1973b/backports_zstd-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eda97fa535d4651a4ccdeed4ee7dde3978369046abc8a7456a7117d4271f9333", size = 367041, upload-time = "2026-08-15T17:26:08.537Z"}, + {url = "https://files.pythonhosted.org/packages/17/9b/7289dc191b34279d8f176bf5b181c3b26f8e049d14a2c0a2637650f034e5/backports_zstd-1.7.0-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:7e3999b5141d7f85171822d06112f70f7f317d162f0120530dd2c7a28dbf8add", size = 507676, upload-time = "2026-08-15T17:26:09.909Z"}, + {url = "https://files.pythonhosted.org/packages/7c/4d/6dd730b79ab96532e23fe851003545b4cc79e50c5b4c79ffcbe1b724eec4/backports_zstd-1.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69367726f4075c2574746f5883b0dc045805c5b02a81fdf8c829c26d33969de3", size = 477744, upload-time = "2026-08-15T17:26:11.038Z"}, + {url = "https://files.pythonhosted.org/packages/e1/53/11687e5019d56ea47893cf2ba59a6b4884a4e2d1496d0e653aed373b973f/backports_zstd-1.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:15e97edfd173ade365c01bac7d9d297fa906686015cdbcb5f32a0d410887826b", size = 583215, upload-time = "2026-08-15T17:26:12.379Z"}, + {url = "https://files.pythonhosted.org/packages/aa/c7/5a8c58542469ab31680c403b844770c119a976fd4cf1000fd7d53e7d0f77/backports_zstd-1.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:32a94cdcf16b44395cd55086ea38877395ca6bf3362cb507b0eb86db2a45a6a4", size = 644125, upload-time = "2026-08-15T17:26:13.651Z"}, + {url = "https://files.pythonhosted.org/packages/11/35/be5485e65df95b86c4981ad4a577b505cfeec6b700a46a86e2e3175ac718/backports_zstd-1.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3f4887a8a1fd1290017fe5a1d29a7d1dc5c57f9477fbd64f119316a7e3ae769", size = 492714, upload-time = "2026-08-15T17:26:14.838Z"}, + {url = "https://files.pythonhosted.org/packages/96/8b/a0603458ca08e4a56f09ae58588ce3c0453425e753df704d9aeaabb66ae5/backports_zstd-1.7.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e590313ce156f1d8986dff3107e8ed1651d6d106a56b3a95f965ff8d845ba979", size = 567953, upload-time = "2026-08-15T17:26:16.276Z"}, + {url = "https://files.pythonhosted.org/packages/02/87/2296db4c3c578947c35ccd8dcdf7992316d7e1f5f43cc829c062b3ed9319/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:565270b0d6497970fa97a0df59593ae0d225e4176678bbce851d39e5f8aa422b", size = 483564, upload-time = "2026-08-15T17:26:17.493Z"}, + {url = "https://files.pythonhosted.org/packages/c0/d8/f53a79e6bf3cdb7ae08f95220c80bd0d606f3d6c3482995deaf21d024fb9/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:37ef23c6c522fe935726c8fba6344350973c4a23b06d10194d90d0868b09ff7a", size = 511193, upload-time = "2026-08-15T17:26:18.7Z"}, + {url = "https://files.pythonhosted.org/packages/31/ea/d4e2eb159cd5813debd5a34d0644caff5fe7cf2e569bf5b02a82934aeee7/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b3975330159f1efdd1fba76afe1c7b84f66f26e2bf209b32630fb148d647e0d5", size = 587748, upload-time = "2026-08-15T17:26:20.148Z"}, + {url = "https://files.pythonhosted.org/packages/81/d2/b5ec9709660fb1c193508215d9c30e781fac406183faac7c3c36b1c583a9/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b40bc8cd0a86cbbe8263a9c3a2bf2e34897483516c6d799725412a19524c32e3", size = 565808, upload-time = "2026-08-15T17:26:21.349Z"}, + {url = "https://files.pythonhosted.org/packages/bd/13/004735cc4536483cbd973a60346a9dbc7bb977b13c28b55a11da14bb0a1e/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f37e12ef10747f76901b1f20ef70d33221e861de177dba5ba08552242c6fd4bd", size = 634520, upload-time = "2026-08-15T17:26:22.944Z"}, + {url = "https://files.pythonhosted.org/packages/a3/28/05b11f7084d1100491cf7c60962aafd900c3dd01b1fc1ce85914476cdae0/backports_zstd-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5992143b2a8b71b4d17afed20cce2df50f8718228e31d6e716493b1fe9201712", size = 497098, upload-time = "2026-08-15T17:26:24.181Z"}, + {url = "https://files.pythonhosted.org/packages/f5/a5/bdc98d039ddbd5815fc1dd71912bbfb9f820a46ec12004ead51c8d60ea50/backports_zstd-1.7.0-cp313-cp313-win32.whl", hash = "sha256:31ae30d216ffae9243dfa607bcb995f94a70de5765bb8fae1e35ea1ad6497959", size = 291974, upload-time = "2026-08-15T17:26:25.512Z"}, + {url = "https://files.pythonhosted.org/packages/13/7d/fb0da7351e8b152d5149127594972922829281c316618df37a7e724f2eb9/backports_zstd-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:8086b4a7443bb2863f7ef8edb317b715d5f3ccec6c5512619bd23d57661ba1b7", size = 329550, upload-time = "2026-08-15T17:26:26.683Z"}, + {url = "https://files.pythonhosted.org/packages/37/f9/109ac272d650483fbdfa611c0040253a405f640604fbc90acc8076c6d37f/backports_zstd-1.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:7eaceeec75e1dbdce40b81fb0ed1ffdb7ce492d970db7f8aabd6a95ccd6c3dd3", size = 292174, upload-time = "2026-08-15T17:26:27.819Z"}, + {url = "https://files.pythonhosted.org/packages/64/21/efd7e8b677a8942c5b4d9c740a9d2bb8bf3099c7e50ec5570916393a1ab1/backports_zstd-1.7.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8d59b145d379745c4461adbe9a9afc647f90ca164f50ea2566c08d6601531d1c", size = 413377, upload-time = "2026-08-15T17:26:28.988Z"}, + {url = "https://files.pythonhosted.org/packages/7f/d4/b617451d1455db3ac0ffee70e417551f2d899359c75d4e539d4afb49d0b2/backports_zstd-1.7.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d3c3cda113aacabe7fd0594ad2832b7023a5fee84009406fe4d230906d80fb25", size = 344064, upload-time = "2026-08-15T17:26:30.175Z"}, + {url = "https://files.pythonhosted.org/packages/50/ed/62ca90940133e1f45eeb0d4782775f15ac24c50a1f201fe498855f1ccdb1/backports_zstd-1.7.0-pp310-pypy310_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f8da4758af21788a9a90f56b7f658a35d33034e55d416fd40e8bcfbb347b90c2", size = 422220, upload-time = "2026-08-15T17:26:31.467Z"}, + {url = "https://files.pythonhosted.org/packages/97/1d/6e6a8d946fbeef93c225943f34292bafbc6b033278360825d4db80141655/backports_zstd-1.7.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e27916c92272ab4285d8d2e02eebe5f4198da10d82250b6edfa3ce372aff6f79", size = 395760, upload-time = "2026-08-15T17:26:32.718Z"}, + {url = "https://files.pythonhosted.org/packages/06/fc/a4bcb4e917f2b79adad76f82bbe8156d7ad0d15d2794d02236dc9da96b43/backports_zstd-1.7.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:76e205599a60acc0824bf03522fb9ad25449492535e1efba18f047e2ce48e745", size = 415725, upload-time = "2026-08-15T17:26:33.928Z"}, + {url = "https://files.pythonhosted.org/packages/53/fd/1adf8f2d231b29f56c0a001b2a6d625ec9a64a6fc63d4589f7683f84a1bd/backports_zstd-1.7.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2feaefcca77c6ac97a46a64f9d41c429caa135a837c54b46398022716abd8184", size = 316245, upload-time = "2026-08-15T17:26:35.112Z"}, + {url = "https://files.pythonhosted.org/packages/7a/ea/42fe3258e02a65603d1eab26200712e37bef6ea408e7f9dbfd6858bc036a/backports_zstd-1.7.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:de58be0a3109cfb83b4e61e2b6eb770201cc132ee5a7c677cd8e0140ad2be80c", size = 413302, upload-time = "2026-08-15T17:26:36.286Z"}, + {url = "https://files.pythonhosted.org/packages/37/00/486044556d715a7b1a41e9cd69544bf8cb3988b383453657c021d24c5c27/backports_zstd-1.7.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:c13f73d0389cdc88b02c05e8175d8ad3030e9e70ee079748763166aa843b647d", size = 343983, upload-time = "2026-08-15T17:26:37.603Z"}, + {url = "https://files.pythonhosted.org/packages/02/f8/f078a32c80ef7546ec2d1206a38bedf4d150cbaac653f8f32d7329f987ff/backports_zstd-1.7.0-pp311-pypy311_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:a2e30ea49c673533d40eb73d0f7abc0ebe9d2e4fc6dbada5ad60b42ff98ffa86", size = 422218, upload-time = "2026-08-15T17:26:38.911Z"}, + {url = "https://files.pythonhosted.org/packages/ac/e2/e5151b85ca9ddfce58388f0fb0316adaacda25d494d2a668842e09f02063/backports_zstd-1.7.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e3f760ee9d16378e3cde9d862e1c9ced577a86736763fb486b9f731d5116807", size = 395760, upload-time = "2026-08-15T17:26:40.09Z"}, + {url = "https://files.pythonhosted.org/packages/78/bf/fd7d55452431d836b3ae81170689f19ddab210fa6c385a72e22006320afe/backports_zstd-1.7.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25caf23dc36de3b839d16c25893751323cf51a8c986f2d01478c16b25133e2e8", size = 415725, upload-time = "2026-08-15T17:26:41.322Z"}, + {url = "https://files.pythonhosted.org/packages/f9/fe/f30ad42bd082b9c6d419c23311a8904a55e248e07c61bf6b91e1691188aa/backports_zstd-1.7.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a64e796c7eee69dfe45827b2e003b7731785ec890c73ea5f5fbc30a1c362fcad", size = 316244, upload-time = "2026-08-15T17:26:42.614Z"} +] + [[package]] dependencies = [ {name = "soupsieve"}, @@ -369,7 +462,7 @@ wheels = [ [[package]] dependencies = [ - {name = "numpy", version = "2.2.6", source = {registry = "https://pypi.org/simple"}} + {name = "numpy", version = "2.2.6", source = {registry = "https://pypi.org/simple"}, marker = "python_full_version < '3.11'"} ] name = "contourpy" resolution-markers = [ @@ -439,7 +532,7 @@ wheels = [ [[package]] dependencies = [ - {name = "numpy", version = "2.3.4", source = {registry = "https://pypi.org/simple"}} + {name = "numpy", version = "2.3.4", source = {registry = "https://pypi.org/simple"}, marker = "python_full_version >= '3.11'"} ] name = "contourpy" resolution-markers = [ @@ -647,7 +740,7 @@ wheels = [ [[package]] dependencies = [ - {name = "typing-extensions"} + {name = "typing-extensions", marker = "python_full_version < '3.11'"} ] name = "exceptiongroup" sdist = {url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z"} @@ -882,7 +975,8 @@ dependencies = [ {name = "pandas"}, {name = "pydantic"}, {name = "requests"}, - {name = "tqdm"} + {name = "tqdm"}, + {name = "urllib3", extra = ["zstd"]} ] name = "keepa" source = {editable = "."} @@ -890,7 +984,7 @@ source = {editable = "."} [package.metadata] provides-extras = ["doc", "test"] requires-dist = [ - {name = "aiohttp"}, + {name = "aiohttp", specifier = ">=3.14.0"}, {name = "autodoc-pydantic", marker = "extra == 'doc'", specifier = "==2.2.0"}, {name = "matplotlib", marker = "extra == 'test'"}, {name = "numpy", specifier = ">=1.9.3"}, @@ -903,13 +997,14 @@ requires-dist = [ {name = "pytest-asyncio", marker = "extra == 'test'"}, {name = "pytest-cov", marker = "extra == 'test'"}, {name = "pytest-rerunfailures", marker = "extra == 'test'"}, - {name = "requests", specifier = ">=2.2"}, + {name = "requests", specifier = ">=2.32.5"}, {name = "sphinx", marker = "python_full_version == '3.11.*' and extra == 'doc'", specifier = "==8.2.3"}, {name = "sphinx", marker = "python_full_version < '3.11' and extra == 'doc'", specifier = "==8.1.3"}, {name = "sphinx", marker = "python_full_version >= '3.12' and extra == 'doc'", specifier = "==9.1.0"}, {name = "sphinx-copybutton", marker = "extra == 'doc'", specifier = "==0.5.2"}, {name = "tomli", marker = "python_full_version < '3.11' and extra == 'doc'"}, - {name = "tqdm"} + {name = "tqdm"}, + {name = "urllib3", extras = ["zstd"], specifier = ">=2.7.0"} ] [package.optional-dependencies] @@ -2087,7 +2182,7 @@ wheels = [ [[package]] dependencies = [ - {name = "roman-numerals"} + {name = "roman-numerals", marker = "python_full_version == '3.11.*'"} ] name = "roman-numerals-py" sdist = {url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z"} @@ -2126,23 +2221,23 @@ wheels = [ [[package]] dependencies = [ - {name = "alabaster"}, - {name = "babel"}, - {name = "colorama", marker = "sys_platform == 'win32'"}, - {name = "docutils"}, - {name = "imagesize"}, - {name = "jinja2"}, - {name = "packaging"}, - {name = "pygments"}, - {name = "requests"}, - {name = "snowballstemmer"}, - {name = "sphinxcontrib-applehelp"}, - {name = "sphinxcontrib-devhelp"}, - {name = "sphinxcontrib-htmlhelp"}, - {name = "sphinxcontrib-jsmath"}, - {name = "sphinxcontrib-qthelp"}, - {name = "sphinxcontrib-serializinghtml"}, - {name = "tomli"} + {name = "alabaster", marker = "python_full_version < '3.11'"}, + {name = "babel", marker = "python_full_version < '3.11'"}, + {name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'"}, + {name = "docutils", marker = "python_full_version < '3.11'"}, + {name = "imagesize", marker = "python_full_version < '3.11'"}, + {name = "jinja2", marker = "python_full_version < '3.11'"}, + {name = "packaging", marker = "python_full_version < '3.11'"}, + {name = "pygments", marker = "python_full_version < '3.11'"}, + {name = "requests", marker = "python_full_version < '3.11'"}, + {name = "snowballstemmer", marker = "python_full_version < '3.11'"}, + {name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'"}, + {name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'"}, + {name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'"}, + {name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'"}, + {name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'"}, + {name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'"}, + {name = "tomli", marker = "python_full_version < '3.11'"} ] name = "sphinx" resolution-markers = [ @@ -2157,23 +2252,23 @@ wheels = [ [[package]] dependencies = [ - {name = "alabaster"}, - {name = "babel"}, - {name = "colorama", marker = "sys_platform == 'win32'"}, - {name = "docutils"}, - {name = "imagesize"}, - {name = "jinja2"}, - {name = "packaging"}, - {name = "pygments"}, - {name = "requests"}, - {name = "roman-numerals-py"}, - {name = "snowballstemmer"}, - {name = "sphinxcontrib-applehelp"}, - {name = "sphinxcontrib-devhelp"}, - {name = "sphinxcontrib-htmlhelp"}, - {name = "sphinxcontrib-jsmath"}, - {name = "sphinxcontrib-qthelp"}, - {name = "sphinxcontrib-serializinghtml"} + {name = "alabaster", marker = "python_full_version == '3.11.*'"}, + {name = "babel", marker = "python_full_version == '3.11.*'"}, + {name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'"}, + {name = "docutils", marker = "python_full_version == '3.11.*'"}, + {name = "imagesize", marker = "python_full_version == '3.11.*'"}, + {name = "jinja2", marker = "python_full_version == '3.11.*'"}, + {name = "packaging", marker = "python_full_version == '3.11.*'"}, + {name = "pygments", marker = "python_full_version == '3.11.*'"}, + {name = "requests", marker = "python_full_version == '3.11.*'"}, + {name = "roman-numerals-py", marker = "python_full_version == '3.11.*'"}, + {name = "snowballstemmer", marker = "python_full_version == '3.11.*'"}, + {name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*'"}, + {name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*'"}, + {name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*'"}, + {name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*'"}, + {name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*'"}, + {name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*'"} ] name = "sphinx" resolution-markers = [ @@ -2188,23 +2283,23 @@ wheels = [ [[package]] dependencies = [ - {name = "alabaster"}, - {name = "babel"}, - {name = "colorama", marker = "sys_platform == 'win32'"}, - {name = "docutils"}, - {name = "imagesize"}, - {name = "jinja2"}, - {name = "packaging"}, - {name = "pygments"}, - {name = "requests"}, - {name = "roman-numerals"}, - {name = "snowballstemmer"}, - {name = "sphinxcontrib-applehelp"}, - {name = "sphinxcontrib-devhelp"}, - {name = "sphinxcontrib-htmlhelp"}, - {name = "sphinxcontrib-jsmath"}, - {name = "sphinxcontrib-qthelp"}, - {name = "sphinxcontrib-serializinghtml"} + {name = "alabaster", marker = "python_full_version >= '3.12'"}, + {name = "babel", marker = "python_full_version >= '3.12'"}, + {name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'"}, + {name = "docutils", marker = "python_full_version >= '3.12'"}, + {name = "imagesize", marker = "python_full_version >= '3.12'"}, + {name = "jinja2", marker = "python_full_version >= '3.12'"}, + {name = "packaging", marker = "python_full_version >= '3.12'"}, + {name = "pygments", marker = "python_full_version >= '3.12'"}, + {name = "requests", marker = "python_full_version >= '3.12'"}, + {name = "roman-numerals", marker = "python_full_version >= '3.12'"}, + {name = "snowballstemmer", marker = "python_full_version >= '3.12'"}, + {name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'"}, + {name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'"}, + {name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'"}, + {name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'"}, + {name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'"}, + {name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'"} ] name = "sphinx" resolution-markers = [ @@ -2385,6 +2480,11 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z"} ] +[package.optional-dependencies] +zstd = [ + {name = "backports-zstd", marker = "python_full_version < '3.14'"} +] + [[package]] dependencies = [ {name = "idna"},