From 2fd9d7f548c39bcf538220226315d83cd83e4081 Mon Sep 17 00:00:00 2001 From: Paillat-dev Date: Tue, 10 Jun 2025 22:30:08 +0200 Subject: [PATCH] Add ESC data and improve scoring system functionality --- pyproject.toml | 11 ++++ src/__main__.py | 67 +++++++++++++---------- tests/__init__.py | 0 tests/esc_data.json | 118 +++++++++++++++++++++++++++++++++++++++++ tests/test_esc.py | 61 +++++++++++++++++++++ tests/test_esc_2025.py | 85 ----------------------------- uv.lock | 40 ++++++++++++++ 7 files changed, 269 insertions(+), 113 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/esc_data.json create mode 100644 tests/test_esc.py delete mode 100644 tests/test_esc_2025.py diff --git a/pyproject.toml b/pyproject.toml index d0b3cf0..5103a42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ [dependency-groups] dev = [ "basedpyright>=1.29.2", + "orjson>=3.10.18", "ruff>=0.11.13", ] @@ -38,3 +39,13 @@ docstring-code-line-length = "dynamic" [tool.ruff.lint] select = ["ALL"] +ignore = [ + "PLR0915", + "D100", + "D104", + "COM812", + "D213", + "D203", + "ISC003" # conflicts with basedpyright reportImplicitStringConcatenation" +] +per-file-ignores = { "tests/**/*" = ["S101", "BLE001"] } \ No newline at end of file diff --git a/src/__main__.py b/src/__main__.py index 5a31928..9166b4e 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -1,22 +1,25 @@ -from pathlib import Path import time +from pathlib import Path + import typer -from rich import print -from rich.table import Table +from rich import print # noqa: A004 from rich.console import Console -from rich.prompt import Prompt, Confirm -from rich.spinner import Spinner -from rich.panel import Panel from rich.live import Live +from rich.panel import Panel +from rich.prompt import Confirm, Prompt +from rich.spinner import Spinner +from rich.table import Table app = typer.Typer() + def parse_jury_file(file_path: Path) -> dict[str, int]: + """Parse the jury file and return a dictionary of scores.""" with file_path.open("r", encoding="utf-8") as file: jury_data: dict[str, int] = {} for line in file: parts = line.strip().split() - if len(parts) >= 2: + if len(parts) >= 2: # noqa: PLR2004 name = " ".join(parts[:-1]) score = parts[-1] jury_data[name] = int(score) @@ -24,7 +27,9 @@ def parse_jury_file(file_path: Path) -> dict[str, int]: print(f"[red]Invalid line format in [bold]{file_path}[/bold]: {line.strip()}[/red]") return jury_data + def render_table(jury_data: dict[str, int], televoting_data: dict[str, int]) -> Table: + """Render the jury and televoting scores table.""" table = Table("Name", "Jury Score", "Televoting Score", title="Scores") for name in sorted(jury_data.keys()): jury_score = jury_data.get(name, 0) @@ -32,21 +37,28 @@ def render_table(jury_data: dict[str, int], televoting_data: dict[str, int]) -> table.add_row(name, str(jury_score), str(televoting_score)) return table -def render_table_final(final_scores: dict[str, int], jury_data: dict[str, int], televoting_data: dict[str, int]) -> Table: + +def render_table_final( + final_scores: dict[str, int], jury_data: dict[str, int], televoting_data: dict[str, int] +) -> Table: + """Render the final scores table with jury and televoting scores.""" table = Table("Name", "Jury Score", "Televoting Score", "Final Score", title="Final Scores") - for name in final_scores.keys(): + for name, score in final_scores.items(): jury_score = jury_data.get(name, 0) televoting_score = televoting_data.get(name, 0) - final_score = final_scores[name] + final_score = score table.add_row(name, str(jury_score), str(televoting_score), str(final_score)) return table -def sort_dict[A, B](d: dict[A, B], reverse: bool = True) -> dict[A, B]: + +def sort_dict[A, B](d: dict[A, B], *, reverse: bool = True) -> dict[A, B]: """Sorts a dictionary by its values in descending order.""" - return dict(sorted(d.items(), key=lambda item: item[1], reverse=reverse)) + return dict(sorted(d.items(), key=lambda item: item[1], reverse=reverse)) # pyright: ignore [reportCallIssue, reportUnknownArgumentType, reportArgumentType] + @app.command() -def run(jury_path: Path = "jury.txt"): # pyright: ignore [reportArgumentType] +def run(jury_path: Path = "jury.txt", participating_countries: int = 37) -> None: # pyright: ignore [reportArgumentType] + """Run the ESC jury and televoting scoring prediction system.""" jury_file = Path(jury_path) if not jury_file.exists(): print(f"[red]File [bold]{jury_path}[bold/] does not exist.[/red]") @@ -61,7 +73,7 @@ def run(jury_path: Path = "jury.txt"): # pyright: ignore [reportArgumentType] table = render_table(jury_scores, televoting_data) console.print(table) - # Collect N–1 televote scores + # Collect N-1 televote scores while len(televoting_data) < len(jury_scores) - 1: name = list(jury_scores.keys())[len(televoting_data)] r: str = Prompt.ask(f"Enter televoting score for [bold]{name}[/bold] (go back with b):", default="0") @@ -86,13 +98,13 @@ def run(jury_path: Path = "jury.txt"): # pyright: ignore [reportArgumentType] console.print(render_table(jury_scores, televoting_data)) # After N-1 entries, confirm before computing last - if len(televoting_data) == len(jury_scores) - 1: - if not Confirm.ask("Final score entered! Review and continue?", default=True): - # remove last entry if they want to re-enter - last = list(televoting_data.keys())[-1] - del televoting_data[last] - console.clear() - console.print(render_table(jury_scores, televoting_data)) + if len(televoting_data) == len(jury_scores) - 1 and not Confirm.ask( + "Final score entered! Review and continue?", default=True + ): + last = list(televoting_data.keys())[-1] + del televoting_data[last] + console.clear() + console.print(render_table(jury_scores, televoting_data)) console.clear() print("[green]Televoting scores have been successfully entered![/green]") @@ -103,8 +115,10 @@ def run(jury_path: Path = "jury.txt"): # pyright: ignore [reportArgumentType] for _ in range(20): time.sleep(0.1) - # Compute the 38Ɨ58 total and subtract the N-1 sum - total_available = sum((12, 10, 8, 7, 6, 5, 4, 3, 2, 1)) * (len(jury_scores) + 1) # N-1 televoting + 1 rest of the world + # Compute the (participating_countries + 1)x58 total and subtract the N-1 sum + total_available = sum((12, 10, 8, 7, 6, 5, 4, 3, 2, 1)) * ( + participating_countries + 1 + ) # N-1 televoting + 1 rest of the world sum_entered = sum(televoting_data.values()) missing = set(jury_scores) - set(televoting_data) country_to_predict = missing.pop() @@ -112,12 +126,9 @@ def run(jury_path: Path = "jury.txt"): # pyright: ignore [reportArgumentType] # Sort and compute finals televoting_data = sort_dict(televoting_data) - + jury_scores = sort_dict(jury_scores) - final_scores: dict[str, int] = { - name: jury_scores[name] + televoting_data.get(name, 0) - for name in jury_scores - } + final_scores: dict[str, int] = {name: jury_scores[name] + televoting_data.get(name, 0) for name in jury_scores} final_scores = dict(sorted(final_scores.items(), key=lambda item: item[1], reverse=True)) # Render and announce diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/esc_data.json b/tests/esc_data.json new file mode 100644 index 0000000..02ec9cf --- /dev/null +++ b/tests/esc_data.json @@ -0,0 +1,118 @@ +{ + "2025": { + "jury": { + "Austria": 258, + "Switzerland": 214, + "France": 180, + "Italy": 159, + "Netherlands": 133, + "Sweden": 126, + "Latvia": 116, + "Greece": 105, + "Estonia": 98, + "United Kingdom": 88, + "Finland": 88, + "Malta": 83, + "Germany": 77, + "Ukraine": 60, + "Israel": 60, + "Albania": 45, + "Denmark": 45, + "Armenia": 42, + "Portugal": 37, + "Lithuania": 34, + "Spain": 27, + "Luxembourg": 23, + "Norway": 22, + "Poland": 17, + "San Marino": 9, + "Iceland": 0 + }, + "televote": { + "Israel": 297, + "Estonia": 258, + "Sweden": 195, + "Austria": 178, + "Albania": 173, + "Ukraine": 158, + "Poland": 139, + "Greece": 126, + "Finland": 108, + "Italy": 97, + "Germany": 74, + "Norway": 67, + "Lithuania": 62, + "France": 50, + "Latvia": 42, + "Netherlands": 42, + "Iceland": 33, + "Armenia": 30, + "Luxembourg": 24, + "San Marino": 18, + "Portugal": 13, + "Spain": 10, + "Malta": 8, + "Denmark": 2, + "United Kingdom": 0, + "Switzerland": 0 + }, + "winner": "Austria" + }, + "2024": { + "jury": { + "Switzerland": 365, + "France": 218, + "Croatia": 210, + "Italy": 164, + "Ukraine": 146, + "Ireland": 142, + "Portugal": 139, + "Sweden": 125, + "Armenia": 101, + "Germany": 99, + "Luxembourg": 83, + "Israel": 52, + "United Kingdom": 46, + "Greece": 41, + "Latvia": 36, + "Cyprus": 34, + "Lithuania": 32, + "Serbia": 22, + "Spain": 19, + "Austria": 19, + "Georgia": 15, + "Slovenia": 15, + "Norway": 12, + "Finland": 7, + "Estonia": 4 + }, + "televote": { + "Croatia": 337, + "Israel": 323, + "Ukraine": 307, + "France": 227, + "Switzerland": 226, + "Ireland": 136, + "Italy": 104, + "Greece": 85, + "Armenia": 82, + "Lithuania": 58, + "Sweden": 49, + "Cyprus": 44, + "Estonia": 33, + "Serbia": 32, + "Finland": 31, + "Latvia": 28, + "Luxembourg": 20, + "Georgia": 19, + "Germany": 18, + "Portugal": 13, + "Slovenia": 12, + "Spain": 11, + "Austria": 5, + "Norway": 4, + "United Kingdom": 0 + }, + "winner": "Switzerland" + } +} diff --git a/tests/test_esc.py b/tests/test_esc.py new file mode 100644 index 0000000..610db69 --- /dev/null +++ b/tests/test_esc.py @@ -0,0 +1,61 @@ +import os +import sys + +import orjson + +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) # noqa: PTH120, PTH118 + +import tempfile +from pathlib import Path +from typing import TypedDict + +import pytest +from typer.testing import CliRunner + +from src.__main__ import app + +data_path = Path(__file__).parent / "esc_data.json" + + +class ESCData(TypedDict): + """TypedDict for ESC data.""" + + jury: dict[str, int] + televote: dict[str, int] + winner: str + + +with data_path.open("r", encoding="utf-8") as f: + data = orjson.loads(f.read()) + +ESC_DATA: dict[int, "ESCData"] = {int(year): value for year, value in data.items()} + + +TADA = "šŸŽ‰" + + +@pytest.mark.parametrize(("year", "data"), ESC_DATA.items()) +def test_esc_grand_final(year: int, data: ESCData) -> None: + """Test the ESC grand final for a given year.""" + jury_scores: dict[str, int] = data["jury"] + televote_scores: dict[str, int] = data["televote"] + expected_winner: str = data["winner"] + + with tempfile.NamedTemporaryFile("w", delete=False, encoding="utf-8") as f: + for country, score in jury_scores.items(): + f.write(f"{country} {score}\n") + + inputs: list[str] = [] + for country in reversed(list(jury_scores.keys())): + inputs.append(str(televote_scores[country])) # noqa: PERF401 + inputs.append("y") # to confirm the winner + + runner = CliRunner() + result = runner.invoke(app, ["--jury-path", f.name], input="\n".join(inputs)) + + try: + actual = result.output.split(TADA)[1].strip().split()[0] + except Exception: + pytest.fail(f"Could not parse winner from output:\n{result.output}", pytrace=False) + + assert actual == expected_winner, f"For {year}, expected winner {expected_winner} but got {actual!r}" diff --git a/tests/test_esc_2025.py b/tests/test_esc_2025.py deleted file mode 100644 index f1fa13f..0000000 --- a/tests/test_esc_2025.py +++ /dev/null @@ -1,85 +0,0 @@ -import os -import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..')) - -import tempfile -from typer.testing import CliRunner -from src.__main__ import app - -# 2025 Eurovision Grand Final jury scores -JURY_SCORES = { - "Austria": 258, - "Switzerland": 214, - "France": 180, - "Italy": 159, - "Netherlands": 133, - "Sweden": 126, - "Latvia": 116, - "Greece": 105, - "Estonia": 98, - "United Kingdom": 88, - "Finland": 88, - "Malta": 83, - "Germany": 77, - "Ukraine": 60, - "Israel": 60, - "Albania": 45, - "Denmark": 45, - "Armenia": 42, - "Portugal": 37, - "Lithuania": 34, - "Spain": 27, - "Luxembourg": 23, - "Norway": 22, - "Poland": 17, - "San Marino": 9, - "Iceland": 0, -} - - -# 2025 Eurovision Grand Final televoting scores -TELEVOTE_SCORES = { - "Israel": 297, - "Estonia": 258, - "Sweden": 195, - "Austria": 178, - "Albania": 173, - "Ukraine": 158, - "Poland": 139, - "Greece": 126, - "Finland": 108, - "Italy": 97, - "Germany": 74, - "Norway": 67, - "Lithuania": 62, - "France": 50, - "Latvia": 42, - "Netherlands": 42, - "Iceland": 33, - "Armenia": 30, - "Luxembourg": 24, - "San Marino": 18, - "Portugal": 13, - "Spain": 10, - "Malta": 8, - "Denmark": 2, - "United Kingdom": 0, - "Switzerland": 0, -} - -RETURN = """ -╭──────────────────────────── Winner Announcement ─────────────────────────────╮ -│ This year's winner is: šŸŽ‰ Austria šŸŽ‰ │ -╰────────────────────────────── Congratulations! ──────────────────────────────╯ -""" - -def test_esc_2025(): - with tempfile.NamedTemporaryFile(delete=False, mode='w', encoding='utf-8') as jury_file: - jury_file.write("\n".join(f"{country} {score}" for country, score in JURY_SCORES.items())) - runner = CliRunner() - i: str = "" - for country in reversed(list(JURY_SCORES.keys())): - i += f"{TELEVOTE_SCORES[country]}\n" - i += "y\n" - result = runner.invoke(app, ["--jury-path", jury_file.name], input=i) - assert result.stdout.strip().endswith(RETURN), f"Expected output to end with {RETURN.strip()}, got {result.output.strip()}" diff --git a/uv.lock b/uv.lock index b490fea..7879ea8 100644 --- a/uv.lock +++ b/uv.lock @@ -48,6 +48,7 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "basedpyright" }, + { name = "orjson" }, { name = "ruff" }, ] @@ -61,6 +62,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "basedpyright", specifier = ">=1.29.2" }, + { name = "orjson", specifier = ">=3.10.18" }, { name = "ruff", specifier = ">=0.11.13" }, ] @@ -110,6 +112,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/03/a852711aec73dfb965844592dfe226024c0da28e37d1ee54083342e38f57/nodejs_wheel_binaries-22.16.0-py2.py3-none-win_arm64.whl", hash = "sha256:2728972d336d436d39ee45988978d8b5d963509e06f063e80fe41b203ee80b28", size = 38828154 }, ] +[[package]] +name = "orjson" +version = "3.10.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/0b/fea456a3ffe74e70ba30e01ec183a9b26bec4d497f61dcfce1b601059c60/orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53", size = 5422810 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/1a/67236da0916c1a192d5f4ccbe10ec495367a726996ceb7614eaa687112f2/orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753", size = 249184 }, + { url = "https://files.pythonhosted.org/packages/b3/bc/c7f1db3b1d094dc0c6c83ed16b161a16c214aaa77f311118a93f647b32dc/orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17", size = 133279 }, + { url = "https://files.pythonhosted.org/packages/af/84/664657cd14cc11f0d81e80e64766c7ba5c9b7fc1ec304117878cc1b4659c/orjson-3.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559eb40a70a7494cd5beab2d73657262a74a2c59aff2068fdba8f0424ec5b39d", size = 136799 }, + { url = "https://files.pythonhosted.org/packages/9a/bb/f50039c5bb05a7ab024ed43ba25d0319e8722a0ac3babb0807e543349978/orjson-3.10.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3c29eb9a81e2fbc6fd7ddcfba3e101ba92eaff455b8d602bf7511088bbc0eae", size = 132791 }, + { url = "https://files.pythonhosted.org/packages/93/8c/ee74709fc072c3ee219784173ddfe46f699598a1723d9d49cbc78d66df65/orjson-3.10.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6612787e5b0756a171c7d81ba245ef63a3533a637c335aa7fcb8e665f4a0966f", size = 137059 }, + { url = "https://files.pythonhosted.org/packages/6a/37/e6d3109ee004296c80426b5a62b47bcadd96a3deab7443e56507823588c5/orjson-3.10.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ac6bd7be0dcab5b702c9d43d25e70eb456dfd2e119d512447468f6405b4a69c", size = 138359 }, + { url = "https://files.pythonhosted.org/packages/4f/5d/387dafae0e4691857c62bd02839a3bf3fa648eebd26185adfac58d09f207/orjson-3.10.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f72f100cee8dde70100406d5c1abba515a7df926d4ed81e20a9730c062fe9ad", size = 142853 }, + { url = "https://files.pythonhosted.org/packages/27/6f/875e8e282105350b9a5341c0222a13419758545ae32ad6e0fcf5f64d76aa/orjson-3.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca85398d6d093dd41dc0983cbf54ab8e6afd1c547b6b8a311643917fbf4e0c", size = 133131 }, + { url = "https://files.pythonhosted.org/packages/48/b2/73a1f0b4790dcb1e5a45f058f4f5dcadc8a85d90137b50d6bbc6afd0ae50/orjson-3.10.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22748de2a07fcc8781a70edb887abf801bb6142e6236123ff93d12d92db3d406", size = 134834 }, + { url = "https://files.pythonhosted.org/packages/56/f5/7ed133a5525add9c14dbdf17d011dd82206ca6840811d32ac52a35935d19/orjson-3.10.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a83c9954a4107b9acd10291b7f12a6b29e35e8d43a414799906ea10e75438e6", size = 413368 }, + { url = "https://files.pythonhosted.org/packages/11/7c/439654221ed9c3324bbac7bdf94cf06a971206b7b62327f11a52544e4982/orjson-3.10.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:303565c67a6c7b1f194c94632a4a39918e067bd6176a48bec697393865ce4f06", size = 153359 }, + { url = "https://files.pythonhosted.org/packages/48/e7/d58074fa0cc9dd29a8fa2a6c8d5deebdfd82c6cfef72b0e4277c4017563a/orjson-3.10.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:86314fdb5053a2f5a5d881f03fca0219bfdf832912aa88d18676a5175c6916b5", size = 137466 }, + { url = "https://files.pythonhosted.org/packages/57/4d/fe17581cf81fb70dfcef44e966aa4003360e4194d15a3f38cbffe873333a/orjson-3.10.18-cp312-cp312-win32.whl", hash = "sha256:187ec33bbec58c76dbd4066340067d9ece6e10067bb0cc074a21ae3300caa84e", size = 142683 }, + { url = "https://files.pythonhosted.org/packages/e6/22/469f62d25ab5f0f3aee256ea732e72dc3aab6d73bac777bd6277955bceef/orjson-3.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:f9f94cf6d3f9cd720d641f8399e390e7411487e493962213390d1ae45c7814fc", size = 134754 }, + { url = "https://files.pythonhosted.org/packages/10/b0/1040c447fac5b91bc1e9c004b69ee50abb0c1ffd0d24406e1350c58a7fcb/orjson-3.10.18-cp312-cp312-win_arm64.whl", hash = "sha256:3d600be83fe4514944500fa8c2a0a77099025ec6482e8087d7659e891f23058a", size = 131218 }, + { url = "https://files.pythonhosted.org/packages/04/f0/8aedb6574b68096f3be8f74c0b56d36fd94bcf47e6c7ed47a7bd1474aaa8/orjson-3.10.18-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:69c34b9441b863175cc6a01f2935de994025e773f814412030f269da4f7be147", size = 249087 }, + { url = "https://files.pythonhosted.org/packages/bc/f7/7118f965541aeac6844fcb18d6988e111ac0d349c9b80cda53583e758908/orjson-3.10.18-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1ebeda919725f9dbdb269f59bc94f861afbe2a27dce5608cdba2d92772364d1c", size = 133273 }, + { url = "https://files.pythonhosted.org/packages/fb/d9/839637cc06eaf528dd8127b36004247bf56e064501f68df9ee6fd56a88ee/orjson-3.10.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5adf5f4eed520a4959d29ea80192fa626ab9a20b2ea13f8f6dc58644f6927103", size = 136779 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/f226ecfef31a1f0e7d6bf9a31a0bbaf384c7cbe3fce49cc9c2acc51f902a/orjson-3.10.18-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7592bb48a214e18cd670974f289520f12b7aed1fa0b2e2616b8ed9e069e08595", size = 132811 }, + { url = "https://files.pythonhosted.org/packages/73/2d/371513d04143c85b681cf8f3bce743656eb5b640cb1f461dad750ac4b4d4/orjson-3.10.18-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f872bef9f042734110642b7a11937440797ace8c87527de25e0c53558b579ccc", size = 137018 }, + { url = "https://files.pythonhosted.org/packages/69/cb/a4d37a30507b7a59bdc484e4a3253c8141bf756d4e13fcc1da760a0b00cb/orjson-3.10.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0315317601149c244cb3ecef246ef5861a64824ccbcb8018d32c66a60a84ffbc", size = 138368 }, + { url = "https://files.pythonhosted.org/packages/1e/ae/cd10883c48d912d216d541eb3db8b2433415fde67f620afe6f311f5cd2ca/orjson-3.10.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0da26957e77e9e55a6c2ce2e7182a36a6f6b180ab7189315cb0995ec362e049", size = 142840 }, + { url = "https://files.pythonhosted.org/packages/6d/4c/2bda09855c6b5f2c055034c9eda1529967b042ff8d81a05005115c4e6772/orjson-3.10.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb70d489bc79b7519e5803e2cc4c72343c9dc1154258adf2f8925d0b60da7c58", size = 133135 }, + { url = "https://files.pythonhosted.org/packages/13/4a/35971fd809a8896731930a80dfff0b8ff48eeb5d8b57bb4d0d525160017f/orjson-3.10.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9e86a6af31b92299b00736c89caf63816f70a4001e750bda179e15564d7a034", size = 134810 }, + { url = "https://files.pythonhosted.org/packages/99/70/0fa9e6310cda98365629182486ff37a1c6578e34c33992df271a476ea1cd/orjson-3.10.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c382a5c0b5931a5fc5405053d36c1ce3fd561694738626c77ae0b1dfc0242ca1", size = 413491 }, + { url = "https://files.pythonhosted.org/packages/32/cb/990a0e88498babddb74fb97855ae4fbd22a82960e9b06eab5775cac435da/orjson-3.10.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8e4b2ae732431127171b875cb2668f883e1234711d3c147ffd69fe5be51a8012", size = 153277 }, + { url = "https://files.pythonhosted.org/packages/92/44/473248c3305bf782a384ed50dd8bc2d3cde1543d107138fd99b707480ca1/orjson-3.10.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d808e34ddb24fc29a4d4041dcfafbae13e129c93509b847b14432717d94b44f", size = 137367 }, + { url = "https://files.pythonhosted.org/packages/ad/fd/7f1d3edd4ffcd944a6a40e9f88af2197b619c931ac4d3cfba4798d4d3815/orjson-3.10.18-cp313-cp313-win32.whl", hash = "sha256:ad8eacbb5d904d5591f27dee4031e2c1db43d559edb8f91778efd642d70e6bea", size = 142687 }, + { url = "https://files.pythonhosted.org/packages/4b/03/c75c6ad46be41c16f4cfe0352a2d1450546f3c09ad2c9d341110cd87b025/orjson-3.10.18-cp313-cp313-win_amd64.whl", hash = "sha256:aed411bcb68bf62e85588f2a7e03a6082cc42e5a2796e06e72a962d7c6310b52", size = 134794 }, + { url = "https://files.pythonhosted.org/packages/c2/28/f53038a5a72cc4fd0b56c1eafb4ef64aec9685460d5ac34de98ca78b6e29/orjson-3.10.18-cp313-cp313-win_arm64.whl", hash = "sha256:f54c1385a0e6aba2f15a40d703b858bedad36ded0491e55d35d905b2c34a4cc3", size = 131186 }, +] + [[package]] name = "packaging" version = "25.0"